OR-Tools  9.6
cp_model_presolve.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <algorithm>
17 #include <cstddef>
18 #include <cstdint>
19 #include <cstdlib>
20 #include <deque>
21 #include <iostream>
22 #include <limits>
23 #include <numeric>
24 #include <string>
25 #include <tuple>
26 #include <utility>
27 #include <vector>
28 
29 #include "absl/base/attributes.h"
30 #include "absl/container/btree_map.h"
31 #include "absl/container/btree_set.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/numeric/int128.h"
36 #include "absl/strings/str_cat.h"
37 #include "absl/types/span.h"
39 #include "ortools/base/logging.h"
40 #include "ortools/base/mathutil.h"
41 #include "ortools/base/stl_util.h"
42 #include "ortools/base/timer.h"
44 #include "ortools/sat/circuit.h"
45 #include "ortools/sat/clause.h"
46 #include "ortools/sat/cp_model.pb.h"
52 #include "ortools/sat/diffn_util.h"
54 #include "ortools/sat/inclusion.h"
55 #include "ortools/sat/integer.h"
56 #include "ortools/sat/model.h"
59 #include "ortools/sat/probing.h"
60 #include "ortools/sat/sat_base.h"
61 #include "ortools/sat/sat_parameters.pb.h"
62 #include "ortools/sat/sat_solver.h"
64 #include "ortools/sat/util.h"
67 #include "ortools/util/bitset.h"
68 #include "ortools/util/logging.h"
72 
73 namespace operations_research {
74 namespace sat {
75 
76 namespace {
77 
78 // TODO(user): Just make sure this invariant is enforced in all our linear
79 // constraint after copy, and simplify the code!
80 bool LinearConstraintIsClean(const LinearConstraintProto& linear) {
81  const int num_vars = linear.vars().size();
82  for (int i = 0; i < num_vars; ++i) {
83  if (!RefIsPositive(linear.vars(i))) return false;
84  if (linear.coeffs(i) == 0) return false;
85  }
86  return true;
87 }
88 
89 } // namespace
90 
91 bool CpModelPresolver::RemoveConstraint(ConstraintProto* ct) {
92  ct->Clear();
93  return true;
94 }
95 
96 // Remove all empty constraints. Note that we need to remap the interval
97 // references.
98 //
99 // Now that they have served their purpose, we also remove dummy constraints,
100 // otherwise that causes issue because our model are invalid in tests.
102  std::vector<int> interval_mapping(context_->working_model->constraints_size(),
103  -1);
104  int new_num_constraints = 0;
105  const int old_num_non_empty_constraints =
106  context_->working_model->constraints_size();
107  for (int c = 0; c < old_num_non_empty_constraints; ++c) {
108  const auto type = context_->working_model->constraints(c).constraint_case();
109  if (type == ConstraintProto::CONSTRAINT_NOT_SET) continue;
110  if (type == ConstraintProto::kDummyConstraint) continue;
111  if (type == ConstraintProto::kInterval) {
112  interval_mapping[c] = new_num_constraints;
113  }
114  context_->working_model->mutable_constraints(new_num_constraints++)
115  ->Swap(context_->working_model->mutable_constraints(c));
116  }
117  context_->working_model->mutable_constraints()->DeleteSubrange(
118  new_num_constraints, old_num_non_empty_constraints - new_num_constraints);
119  for (ConstraintProto& ct_ref :
120  *context_->working_model->mutable_constraints()) {
122  [&interval_mapping](int* ref) {
123  *ref = interval_mapping[*ref];
124  CHECK_NE(-1, *ref);
125  },
126  &ct_ref);
127  }
128 }
129 
130 bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto* ct) {
131  if (context_->ModelIsUnsat()) return false;
132  if (!HasEnforcementLiteral(*ct)) return false;
133 
134  int new_size = 0;
135  const int old_size = ct->enforcement_literal().size();
136  context_->tmp_literal_set.clear();
137  for (const int literal : ct->enforcement_literal()) {
138  if (context_->LiteralIsTrue(literal)) {
139  // We can remove a literal at true.
140  context_->UpdateRuleStats("enforcement: true literal");
141  continue;
142  }
143 
144  if (context_->LiteralIsFalse(literal)) {
145  context_->UpdateRuleStats("enforcement: false literal");
146  return RemoveConstraint(ct);
147  }
148 
149  if (context_->VariableIsUniqueAndRemovable(literal)) {
150  // We can simply set it to false and ignore the constraint in this case.
151  context_->UpdateRuleStats("enforcement: literal not used");
152  CHECK(context_->SetLiteralToFalse(literal));
153  return RemoveConstraint(ct);
154  }
155 
156  // If the literal only appear in the objective, we might be able to fix it
157  // to false. TODO(user): generalize if the literal always appear with the
158  // same polarity.
160  const int64_t obj_coeff =
161  context_->ObjectiveMap().at(PositiveRef(literal));
162  if (RefIsPositive(literal) == (obj_coeff > 0)) {
163  // It is just more advantageous to set it to false!
164  context_->UpdateRuleStats("enforcement: literal with unique direction");
165  CHECK(context_->SetLiteralToFalse(literal));
166  return RemoveConstraint(ct);
167  }
168  }
169 
170  // Deals with duplicate literals.
171  //
172  // TODO(user): Ideally we could do that just once during the first copy,
173  // and later never create such constraint.
174  if (old_size > 1) {
175  const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
176  if (!inserted) {
177  context_->UpdateRuleStats("enforcement: removed duplicate literal");
178  continue;
179  }
180  if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
181  context_->UpdateRuleStats("enforcement: can never be true");
182  return RemoveConstraint(ct);
183  }
184  }
185 
186  ct->set_enforcement_literal(new_size++, literal);
187  }
188  ct->mutable_enforcement_literal()->Truncate(new_size);
189  return new_size != old_size;
190 }
191 
192 bool CpModelPresolver::PresolveBoolXor(ConstraintProto* ct) {
193  if (context_->ModelIsUnsat()) return false;
194  if (HasEnforcementLiteral(*ct)) return false;
195 
196  int new_size = 0;
197  bool changed = false;
198  int num_true_literals = 0;
199  int true_literal = std::numeric_limits<int32_t>::min();
200  for (const int literal : ct->bool_xor().literals()) {
201  // TODO(user): More generally, if a variable appear in only bool xor
202  // constraints, we can simply eliminate it using linear algebra on Z/2Z.
203  // This should solve in polynomial time the parity-learning*.fzn problems
204  // for instance. This seems low priority, but it is also easy to do. Even
205  // better would be to have a dedicated propagator with all bool_xor
206  // constraints that do the necessary linear algebra.
207  if (context_->VariableIsUniqueAndRemovable(literal)) {
208  context_->UpdateRuleStats("TODO bool_xor: remove constraint");
209  }
210 
211  if (context_->LiteralIsFalse(literal)) {
212  context_->UpdateRuleStats("bool_xor: remove false literal");
213  changed = true;
214  continue;
215  } else if (context_->LiteralIsTrue(literal)) {
216  true_literal = literal; // Keep if we need to put one back.
217  num_true_literals++;
218  continue;
219  }
220 
221  ct->mutable_bool_xor()->set_literals(new_size++, literal);
222  }
223 
224  if (new_size == 0) {
225  if (num_true_literals % 2 == 0) {
226  return context_->NotifyThatModelIsUnsat("bool_xor: always false");
227  } else {
228  context_->UpdateRuleStats("bool_xor: always true");
229  return RemoveConstraint(ct);
230  }
231  } else if (new_size == 1) { // We can fix the only active literal.
232  if (num_true_literals % 2 == 0) {
233  if (!context_->SetLiteralToTrue(ct->bool_xor().literals(0))) {
234  return context_->NotifyThatModelIsUnsat(
235  "bool_xor: cannot fix last literal");
236  }
237  } else {
238  if (!context_->SetLiteralToFalse(ct->bool_xor().literals(0))) {
239  return context_->NotifyThatModelIsUnsat(
240  "bool_xor: cannot fix last literal");
241  }
242  }
243  context_->UpdateRuleStats("bool_xor: one active literal");
244  return RemoveConstraint(ct);
245  } else if (new_size == 2) { // We can simplify the bool_xor.
246  const int a = ct->bool_xor().literals(0);
247  const int b = ct->bool_xor().literals(1);
248  if (a == b) {
249  if (num_true_literals % 2 == 0) {
250  return context_->NotifyThatModelIsUnsat("bool_xor: always false");
251  } else {
252  context_->UpdateRuleStats("bool_xor: always true");
253  return RemoveConstraint(ct);
254  }
255  }
256  if (a == NegatedRef(b)) {
257  if (num_true_literals % 2 == 1) {
258  return context_->NotifyThatModelIsUnsat("bool_xor: always false");
259  } else {
260  context_->UpdateRuleStats("bool_xor: always true");
261  return RemoveConstraint(ct);
262  }
263  }
264  if (num_true_literals % 2 == 0) { // a == not(b).
266  } else { // a == b.
267  context_->StoreBooleanEqualityRelation(a, b);
268  }
270  context_->UpdateRuleStats("bool_xor: two active literals");
271  return RemoveConstraint(ct);
272  }
273 
274  if (num_true_literals % 2 == 1) {
275  CHECK_NE(true_literal, std::numeric_limits<int32_t>::min());
276  ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
277  }
278  if (num_true_literals > 1) {
279  context_->UpdateRuleStats("bool_xor: remove even number of true literals");
280  changed = true;
281  }
282  ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
283  return changed;
284 }
285 
286 bool CpModelPresolver::PresolveBoolOr(ConstraintProto* ct) {
287  if (context_->ModelIsUnsat()) return false;
288 
289  // Move the enforcement literal inside the clause if any. Note that we do not
290  // mark this as a change since the literal in the constraint are the same.
291  if (HasEnforcementLiteral(*ct)) {
292  context_->UpdateRuleStats("bool_or: removed enforcement literal");
293  for (const int literal : ct->enforcement_literal()) {
294  ct->mutable_bool_or()->add_literals(NegatedRef(literal));
295  }
296  ct->clear_enforcement_literal();
297  }
298 
299  // Inspects the literals and deal with fixed ones.
300  //
301  // TODO(user): Because we remove literal on the first copy, maybe we can get
302  // rid of the set here. However we still need to be careful when remapping
303  // literals to their representatives.
304  bool changed = false;
305  context_->tmp_literals.clear();
306  context_->tmp_literal_set.clear();
307  for (const int literal : ct->bool_or().literals()) {
308  if (context_->LiteralIsFalse(literal)) {
309  changed = true;
310  continue;
311  }
312  if (context_->LiteralIsTrue(literal)) {
313  context_->UpdateRuleStats("bool_or: always true");
314  return RemoveConstraint(ct);
315  }
316  // We can just set the variable to true in this case since it is not
317  // used in any other constraint (note that we artificially bump the
318  // objective var usage by 1).
319  if (context_->VariableIsUniqueAndRemovable(literal)) {
320  context_->UpdateRuleStats("bool_or: singleton");
321  if (!context_->SetLiteralToTrue(literal)) return true;
322  return RemoveConstraint(ct);
323  }
324  if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
325  context_->UpdateRuleStats("bool_or: always true");
326  return RemoveConstraint(ct);
327  }
328 
329  if (context_->tmp_literal_set.contains(literal)) {
330  changed = true;
331  } else {
332  context_->tmp_literal_set.insert(literal);
333  context_->tmp_literals.push_back(literal);
334  }
335  }
336  context_->tmp_literal_set.clear();
337 
338  if (context_->tmp_literals.empty()) {
339  context_->UpdateRuleStats("bool_or: empty");
340  return context_->NotifyThatModelIsUnsat();
341  }
342  if (context_->tmp_literals.size() == 1) {
343  context_->UpdateRuleStats("bool_or: only one literal");
344  if (!context_->SetLiteralToTrue(context_->tmp_literals[0])) return true;
345  return RemoveConstraint(ct);
346  }
347  if (context_->tmp_literals.size() == 2) {
348  // For consistency, we move all "implication" into half-reified bool_and.
349  // TODO(user): merge by enforcement literal and detect implication cycles.
350  context_->UpdateRuleStats("bool_or: implications");
351  ct->add_enforcement_literal(NegatedRef(context_->tmp_literals[0]));
352  ct->mutable_bool_and()->add_literals(context_->tmp_literals[1]);
353  return changed;
354  }
355 
356  if (changed) {
357  context_->UpdateRuleStats("bool_or: fixed literals");
358  ct->mutable_bool_or()->mutable_literals()->Clear();
359  for (const int lit : context_->tmp_literals) {
360  ct->mutable_bool_or()->add_literals(lit);
361  }
362  }
363  return changed;
364 }
365 
366 // Note this function does not update the constraint graph. It assumes this is
367 // done elsewhere.
368 ABSL_MUST_USE_RESULT bool CpModelPresolver::MarkConstraintAsFalse(
369  ConstraintProto* ct) {
370  if (HasEnforcementLiteral(*ct)) {
371  // Change the constraint to a bool_or.
372  ct->mutable_bool_or()->clear_literals();
373  for (const int lit : ct->enforcement_literal()) {
374  ct->mutable_bool_or()->add_literals(NegatedRef(lit));
375  }
376  ct->clear_enforcement_literal();
377  PresolveBoolOr(ct);
378  return true;
379  } else {
380  return context_->NotifyThatModelIsUnsat();
381  }
382 }
383 
384 bool CpModelPresolver::PresolveBoolAnd(ConstraintProto* ct) {
385  if (context_->ModelIsUnsat()) return false;
386 
387  if (!HasEnforcementLiteral(*ct)) {
388  context_->UpdateRuleStats("bool_and: non-reified.");
389  for (const int literal : ct->bool_and().literals()) {
390  if (!context_->SetLiteralToTrue(literal)) return true;
391  }
392  return RemoveConstraint(ct);
393  }
394 
395  bool changed = false;
396  context_->tmp_literals.clear();
397  context_->tmp_literal_set.clear();
398  const absl::flat_hash_set<int> enforcement_literals_set(
399  ct->enforcement_literal().begin(), ct->enforcement_literal().end());
400  for (const int literal : ct->bool_and().literals()) {
401  if (context_->LiteralIsFalse(literal)) {
402  context_->UpdateRuleStats("bool_and: always false");
403  return MarkConstraintAsFalse(ct);
404  }
405  if (context_->LiteralIsTrue(literal)) {
406  changed = true;
407  continue;
408  }
409  if (enforcement_literals_set.contains(literal)) {
410  context_->UpdateRuleStats("bool_and: x => x");
411  changed = true;
412  continue;
413  }
414  if (enforcement_literals_set.contains(NegatedRef(literal))) {
415  context_->UpdateRuleStats("bool_and: x => not x");
416  return MarkConstraintAsFalse(ct);
417  }
418  if (context_->VariableIsUniqueAndRemovable(literal)) {
419  changed = true;
420  if (!context_->SetLiteralToTrue(literal)) return true;
421  continue;
422  }
423 
424  if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
425  context_->UpdateRuleStats("bool_and: cannot be enforced");
426  return MarkConstraintAsFalse(ct);
427  }
428 
429  const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
430  if (inserted) {
431  context_->tmp_literals.push_back(literal);
432  } else {
433  changed = true;
434  context_->UpdateRuleStats("bool_and: removed duplicate literal");
435  }
436  }
437 
438  // Note that this is not the same behavior as a bool_or:
439  // - bool_or means "at least one", so it is false if empty.
440  // - bool_and means "all literals inside true", so it is true if empty.
441  if (context_->tmp_literals.empty()) return RemoveConstraint(ct);
442 
443  if (changed) {
444  ct->mutable_bool_and()->mutable_literals()->Clear();
445  for (const int lit : context_->tmp_literals) {
446  ct->mutable_bool_and()->add_literals(lit);
447  }
448  context_->UpdateRuleStats("bool_and: fixed literals");
449  }
450 
451  // If a variable can move freely in one direction except for this constraint,
452  // we can make it an equality.
453  //
454  // TODO(user): also consider literal on the other side of the =>.
455  if (ct->enforcement_literal().size() == 1 &&
456  ct->bool_and().literals().size() == 1) {
457  const int enforcement = ct->enforcement_literal(0);
458  if (context_->VariableWithCostIsUniqueAndRemovable(enforcement)) {
459  int var = PositiveRef(enforcement);
460  int64_t obj_coeff = context_->ObjectiveMap().at(var);
461  if (!RefIsPositive(enforcement)) obj_coeff = -obj_coeff;
462 
463  // The other case where the constraint is redundant is treated elsewhere.
464  if (obj_coeff < 0) {
465  context_->UpdateRuleStats("bool_and: dual equality.");
466  context_->StoreBooleanEqualityRelation(enforcement,
467  ct->bool_and().literals(0));
468  }
469  }
470  }
471 
472  return changed;
473 }
474 
475 bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto* ct) {
476  bool is_at_most_one = ct->constraint_case() == ConstraintProto::kAtMostOne;
477  const std::string name = is_at_most_one ? "at_most_one: " : "exactly_one: ";
478  auto* literals = is_at_most_one
479  ? ct->mutable_at_most_one()->mutable_literals()
480  : ct->mutable_exactly_one()->mutable_literals();
481 
482  // Having a canonical constraint is needed for duplicate detection.
483  // This also change how we regroup bool_and.
484  std::sort(literals->begin(), literals->end());
485 
486  // Deal with duplicate variable reference.
487  context_->tmp_literal_set.clear();
488  for (const int literal : *literals) {
489  const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
490  if (!inserted) {
491  if (!context_->SetLiteralToFalse(literal)) return false;
492  context_->UpdateRuleStats(absl::StrCat(name, "duplicate literals"));
493  }
494  if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
495  int num_positive = 0;
496  int num_negative = 0;
497  for (const int other : *literals) {
498  if (PositiveRef(other) != PositiveRef(literal)) {
499  if (!context_->SetLiteralToFalse(other)) return false;
500  context_->UpdateRuleStats(absl::StrCat(name, "x and not(x)"));
501  } else {
502  if (other == literal) {
503  ++num_positive;
504  } else {
505  ++num_negative;
506  }
507  }
508  }
509 
510  // This is tricky for the case where the at most one reduce to (lit,
511  // not(lit), not(lit)) for instance.
512  if (num_positive > 1 && !context_->SetLiteralToFalse(literal)) {
513  return false;
514  }
515  if (num_negative > 1 && !context_->SetLiteralToTrue(literal)) {
516  return false;
517  }
518  return RemoveConstraint(ct);
519  }
520  }
521 
522  // We can always remove all singleton variables (with or without cost) in an
523  // at_most_one or exactly one. We collect them and deal with this at the end.
524  std::vector<std::pair<int, int64_t>> singleton_literal_with_cost;
525 
526  // Remove fixed variables.
527  bool changed = false;
528  context_->tmp_literals.clear();
529  for (const int literal : *literals) {
530  if (context_->LiteralIsTrue(literal)) {
531  context_->UpdateRuleStats(absl::StrCat(name, "satisfied"));
532  for (const int other : *literals) {
533  if (other != literal) {
534  if (!context_->SetLiteralToFalse(other)) return false;
535  }
536  }
537  return RemoveConstraint(ct);
538  }
539 
540  if (context_->LiteralIsFalse(literal)) {
541  changed = true;
542  continue;
543  }
544 
545  // A singleton variable with or without cost can be removed. See below.
546  if (context_->VariableIsUniqueAndRemovable(literal)) {
547  singleton_literal_with_cost.push_back({literal, 0});
548  continue;
549  }
551  const auto it = context_->ObjectiveMap().find(PositiveRef(literal));
552  DCHECK(it != context_->ObjectiveMap().end());
553  if (RefIsPositive(literal)) {
554  singleton_literal_with_cost.push_back({literal, it->second});
555  } else {
556  // Note that we actually just store the objective change if this literal
557  // is true compared to it being false.
558  singleton_literal_with_cost.push_back({literal, -it->second});
559  }
560  continue;
561  }
562 
563  context_->tmp_literals.push_back(literal);
564  }
565 
566  bool transform_to_at_most_one = false;
567  if (!singleton_literal_with_cost.empty()) {
568  changed = true;
569 
570  // By domination argument, we can fix to false everything but the minimum.
571  if (singleton_literal_with_cost.size() > 1) {
572  std::sort(
573  singleton_literal_with_cost.begin(),
574  singleton_literal_with_cost.end(),
575  [](const std::pair<int, int64_t>& a,
576  const std::pair<int, int64_t>& b) { return a.second < b.second; });
577  for (int i = 1; i < singleton_literal_with_cost.size(); ++i) {
578  context_->UpdateRuleStats("at_most_one: dominated singleton");
579  if (!context_->SetLiteralToFalse(
580  singleton_literal_with_cost[i].first)) {
581  return false;
582  }
583  }
584  singleton_literal_with_cost.resize(1);
585  }
586 
587  const int literal = singleton_literal_with_cost[0].first;
588  const int64_t literal_cost = singleton_literal_with_cost[0].second;
589  if (is_at_most_one && literal_cost >= 0) {
590  // We can just always set it to false in this case.
591  context_->UpdateRuleStats("at_most_one: singleton");
592  if (!context_->SetLiteralToFalse(literal)) return false;
593  } else if (context_->ShiftCostInExactlyOne(*literals, literal_cost)) {
594  // We can make the constraint an exactly one if needed since it is always
595  // beneficial to set this literal to true if everything else is zero. Now
596  // that we have an exactly one, we can transfer the cost to the other
597  // terms. The objective of literal should become zero, and we can then
598  // decide its value at postsolve and just have an at most one on the other
599  // literals.
600  DCHECK(!context_->ObjectiveMap().contains(PositiveRef(literal)));
601 
602  if (!is_at_most_one) transform_to_at_most_one = true;
603  is_at_most_one = true;
604 
605  context_->UpdateRuleStats("exactly_one: singleton");
607 
608  // Put a constraint in the mapping proto for postsolve.
609  auto* mapping_exo =
610  context_->mapping_model->add_constraints()->mutable_exactly_one();
611  for (const int lit : context_->tmp_literals) {
612  mapping_exo->add_literals(lit);
613  }
614  mapping_exo->add_literals(literal);
615  }
616  }
617 
618  if (!is_at_most_one && !transform_to_at_most_one &&
619  context_->ExploitExactlyOneInObjective(context_->tmp_literals)) {
620  context_->UpdateRuleStats("exactly_one: simplified objective");
621  }
622 
623  if (transform_to_at_most_one) {
624  CHECK(changed);
625  ct->Clear();
626  literals = ct->mutable_at_most_one()->mutable_literals();
627  }
628  if (changed) {
629  literals->Clear();
630  for (const int lit : context_->tmp_literals) {
631  literals->Add(lit);
632  }
633  context_->UpdateRuleStats(absl::StrCat(name, "removed literals"));
634  }
635  return changed;
636 }
637 
638 bool CpModelPresolver::PresolveAtMostOne(ConstraintProto* ct) {
639  if (context_->ModelIsUnsat()) return false;
640 
641  CHECK(!HasEnforcementLiteral(*ct));
642  const bool changed = PresolveAtMostOrExactlyOne(ct);
643  if (ct->constraint_case() != ConstraintProto::kAtMostOne) return changed;
644 
645  // Size zero: ok.
646  const auto& literals = ct->at_most_one().literals();
647  if (literals.empty()) {
648  context_->UpdateRuleStats("at_most_one: empty or all false");
649  return RemoveConstraint(ct);
650  }
651 
652  // Size one: always satisfied.
653  if (literals.size() == 1) {
654  context_->UpdateRuleStats("at_most_one: size one");
655  return RemoveConstraint(ct);
656  }
657 
658  return changed;
659 }
660 
661 bool CpModelPresolver::PresolveExactlyOne(ConstraintProto* ct) {
662  if (context_->ModelIsUnsat()) return false;
663  CHECK(!HasEnforcementLiteral(*ct));
664  const bool changed = PresolveAtMostOrExactlyOne(ct);
665  if (ct->constraint_case() != ConstraintProto::kExactlyOne) return changed;
666 
667  // Size zero: UNSAT.
668  const auto& literals = ct->exactly_one().literals();
669  if (literals.empty()) {
670  return context_->NotifyThatModelIsUnsat("exactly_one: empty or all false");
671  }
672 
673  // Size one: fix variable.
674  if (literals.size() == 1) {
675  context_->UpdateRuleStats("exactly_one: size one");
676  if (!context_->SetLiteralToTrue(literals[0])) return false;
677  return RemoveConstraint(ct);
678  }
679 
680  // Size two: Equivalence.
681  if (literals.size() == 2) {
682  context_->UpdateRuleStats("exactly_one: size two");
683  context_->StoreBooleanEqualityRelation(literals[0],
684  NegatedRef(literals[1]));
685  return RemoveConstraint(ct);
686  }
687 
688  return changed;
689 }
690 
691 bool CpModelPresolver::CanonicalizeLinearArgument(const ConstraintProto& ct,
692  LinearArgumentProto* proto) {
693  if (context_->ModelIsUnsat()) return false;
694 
695  // Canonicalize all involved expression.
696  bool changed = CanonicalizeLinearExpression(ct, proto->mutable_target());
697  for (LinearExpressionProto& exp : *(proto->mutable_exprs())) {
698  changed |= CanonicalizeLinearExpression(ct, &exp);
699  }
700  return changed;
701 }
702 
703 bool CpModelPresolver::PresolveLinMax(ConstraintProto* ct) {
704  if (context_->ModelIsUnsat()) return false;
705  if (HasEnforcementLiteral(*ct)) return false;
706 
707  int64_t min_offset = std::numeric_limits<int64_t>::max();
708  for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
709  min_offset = std::min(min_offset, expr.offset());
710  }
711  if (min_offset != std::numeric_limits<int64_t>::max() && min_offset != 0) {
712  LinearArgumentProto* lin_max = ct->mutable_lin_max();
713  lin_max->mutable_target()->set_offset(lin_max->target().offset() -
714  min_offset);
715  for (LinearExpressionProto& expr : *(lin_max->mutable_exprs())) {
716  expr.set_offset(expr.offset() - min_offset);
717  }
718  context_->UpdateRuleStats("lin_max: shift offset");
719  }
720 
721  const LinearExpressionProto& target = ct->lin_max().target();
722 
723  // x = max(x, xi...) => forall i, x >= xi.
724  for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
725  if (LinearExpressionProtosAreEqual(expr, target)) {
726  for (const LinearExpressionProto& e : ct->lin_max().exprs()) {
727  if (LinearExpressionProtosAreEqual(e, target)) continue;
728  LinearConstraintProto* prec =
729  context_->working_model->add_constraints()->mutable_linear();
730  prec->add_domain(0);
731  prec->add_domain(std::numeric_limits<int64_t>::max());
732  AddLinearExpressionToLinearConstraint(target, 1, prec);
734  }
735  context_->UpdateRuleStats("lin_max: x = max(x, ...)");
736  return RemoveConstraint(ct);
737  }
738  }
739 
740  // Compute the infered min/max of the target.
741  // Update target domain (if it is not a complex expression).
742  {
743  int64_t infered_min = context_->MinOf(target);
744  int64_t infered_max = std::numeric_limits<int64_t>::min();
745  for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
746  infered_min = std::max(infered_min, context_->MinOf(expr));
747  infered_max = std::max(infered_max, context_->MaxOf(expr));
748  }
749 
750  if (target.vars().empty()) {
751  if (!Domain(infered_min, infered_max).Contains(target.offset())) {
752  context_->UpdateRuleStats("lin_max: infeasible");
753  return MarkConstraintAsFalse(ct);
754  }
755  }
756  if (target.vars().size() <= 1) { // Affine
757  Domain rhs_domain;
758  for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
759  rhs_domain = rhs_domain.UnionWith(
760  context_->DomainSuperSetOf(expr).IntersectionWith(
761  {infered_min, infered_max}));
762  }
763  bool reduced = false;
764  if (!context_->IntersectDomainWith(target, rhs_domain, &reduced)) {
765  return true;
766  }
767  if (reduced) {
768  context_->UpdateRuleStats("lin_max: target domain reduced");
769  }
770  }
771  }
772 
773  // Filter the expressions which are smaller than target_min.
774  const int64_t target_min = context_->MinOf(target);
775  const int64_t target_max = context_->MaxOf(target);
776  bool changed = false;
777  {
778  // If one expression is >= target_min,
779  // We can remove all the expression <= target min.
780  //
781  // Note that we must keep an expression >= target_min though, for corner
782  // case like [2,3] = max([2], [0][3]);
783  bool has_greater_or_equal_to_target_min = false;
784  int64_t max_at_index_to_keep = std::numeric_limits<int64_t>::min();
785  int index_to_keep = -1;
786  for (int i = 0; i < ct->lin_max().exprs_size(); ++i) {
787  const LinearExpressionProto& expr = ct->lin_max().exprs(i);
788  if (context_->MinOf(expr) >= target_min) {
789  const int64_t expr_max = context_->MaxOf(expr);
790  if (expr_max > max_at_index_to_keep) {
791  max_at_index_to_keep = expr_max;
792  index_to_keep = i;
793  }
794  has_greater_or_equal_to_target_min = true;
795  }
796  }
797 
798  int new_size = 0;
799  for (int i = 0; i < ct->lin_max().exprs_size(); ++i) {
800  const LinearExpressionProto& expr = ct->lin_max().exprs(i);
801  const int64_t expr_max = context_->MaxOf(expr);
802  // TODO(user): Also remove expression whose domain is incompatible with
803  // the target even if the bounds are like [2] and [0][3]?
804  if (expr_max < target_min) continue;
805  if (expr_max == target_min && has_greater_or_equal_to_target_min &&
806  i != index_to_keep) {
807  continue;
808  }
809  *ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
810  new_size++;
811  }
812  if (new_size < ct->lin_max().exprs_size()) {
813  context_->UpdateRuleStats("lin_max: removed exprs");
814  ct->mutable_lin_max()->mutable_exprs()->DeleteSubrange(
815  new_size, ct->lin_max().exprs_size() - new_size);
816  changed = true;
817  }
818  }
819 
820  if (ct->lin_max().exprs().empty()) {
821  context_->UpdateRuleStats("lin_max: no exprs");
822  return MarkConstraintAsFalse(ct);
823  }
824 
825  // If only one is left, we can convert to an equality. Note that we create a
826  // new constraint otherwise it might not be processed again.
827  if (ct->lin_max().exprs().size() == 1) {
828  context_->UpdateRuleStats("lin_max: converted to equality");
829  ConstraintProto* new_ct = context_->working_model->add_constraints();
830  *new_ct = *ct; // copy name and potential reification.
831  auto* arg = new_ct->mutable_linear();
832  const LinearExpressionProto& a = ct->lin_max().target();
833  const LinearExpressionProto& b = ct->lin_max().exprs(0);
834  for (int i = 0; i < a.vars().size(); ++i) {
835  arg->add_vars(a.vars(i));
836  arg->add_coeffs(a.coeffs(i));
837  }
838  for (int i = 0; i < b.vars().size(); ++i) {
839  arg->add_vars(b.vars(i));
840  arg->add_coeffs(-b.coeffs(i));
841  }
842  arg->add_domain(b.offset() - a.offset());
843  arg->add_domain(b.offset() - a.offset());
845  return RemoveConstraint(ct);
846  }
847 
848  // Cut everything above the max if possible.
849  // If one of the linear expression has many term and is above the max, we
850  // abort early since none of the other rule can be applied.
851  {
852  bool abort = false;
853  for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
854  const int64_t value_min = context_->MinOf(expr);
855  bool modified = false;
856  if (!context_->IntersectDomainWith(expr, Domain(value_min, target_max),
857  &modified)) {
858  return true;
859  }
860  if (modified) {
861  context_->UpdateRuleStats("lin_max: reduced expression domain.");
862  }
863  const int64_t value_max = context_->MaxOf(expr);
864  if (value_max > target_max) {
865  context_->UpdateRuleStats("TODO lin_max: linear expression above max.");
866  abort = true;
867  }
868  }
869  if (abort) return changed;
870  }
871 
872  // Deal with fixed target case.
873  if (target_min == target_max) {
874  bool all_booleans = true;
875  std::vector<int> literals;
876  const int64_t fixed_target = target_min;
877  for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
878  const int64_t value_min = context_->MinOf(expr);
879  const int64_t value_max = context_->MaxOf(expr);
880  CHECK_LE(value_max, fixed_target) << "Presolved above";
881  if (value_max < fixed_target) continue;
882 
883  if (value_min == value_max && value_max == fixed_target) {
884  context_->UpdateRuleStats("lin_max: always satisfied");
885  return RemoveConstraint(ct);
886  }
887  if (context_->ExpressionIsAffineBoolean(expr)) {
888  CHECK_EQ(value_max, fixed_target);
889  literals.push_back(context_->LiteralForExpressionMax(expr));
890  } else {
891  all_booleans = false;
892  }
893  }
894  if (all_booleans) {
895  if (literals.empty()) {
896  return MarkConstraintAsFalse(ct);
897  }
898 
899  // At least one true;
900  context_->UpdateRuleStats("lin_max: fixed target and all booleans");
901  for (const int lit : literals) {
902  ct->mutable_bool_or()->add_literals(lit);
903  }
904  return true;
905  }
906  return changed;
907  }
908 
909  changed |= PresolveLinMaxWhenAllBoolean(ct);
910  return changed;
911 }
912 
913 // If everything is Boolean and affine, do not use a lin max!
914 bool CpModelPresolver::PresolveLinMaxWhenAllBoolean(ConstraintProto* ct) {
915  if (context_->ModelIsUnsat()) return false;
916  if (HasEnforcementLiteral(*ct)) return false;
917 
918  const LinearExpressionProto& target = ct->lin_max().target();
919  if (!context_->ExpressionIsAffineBoolean(target)) return false;
920 
921  const int64_t target_min = context_->MinOf(target);
922  const int64_t target_max = context_->MaxOf(target);
923  const int target_ref = context_->LiteralForExpressionMax(target);
924 
925  bool min_is_reachable = false;
926  std::vector<int> min_literals;
927  std::vector<int> literals_above_min;
928  std::vector<int> max_literals;
929 
930  for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
931  if (!context_->ExpressionIsAffineBoolean(expr)) return false;
932  const int64_t value_min = context_->MinOf(expr);
933  const int64_t value_max = context_->MaxOf(expr);
934  const int ref = context_->LiteralForExpressionMax(expr);
935 
936  // Get corner case out of the way, and wait for the constraint to be
937  // processed again in these case.
938  if (value_min > target_min) {
939  context_->UpdateRuleStats("lin_max: fix target");
940  (void)context_->SetLiteralToTrue(target_ref);
941  return false;
942  }
943  if (value_max > target_max) {
944  context_->UpdateRuleStats("lin_max: fix bool expr");
945  (void)context_->SetLiteralToFalse(ref);
946  return false;
947  }
948 
949  // expr is fixed.
950  if (value_min == value_max) {
951  if (value_min == target_min) min_is_reachable = true;
952  continue;
953  }
954 
955  CHECK_LE(value_min, target_min);
956  if (value_min == target_min) {
957  min_literals.push_back(NegatedRef(ref));
958  }
959 
960  CHECK_LE(value_max, target_max);
961  if (value_max == target_max) {
962  max_literals.push_back(ref);
963  literals_above_min.push_back(ref);
964  } else if (value_max > target_min) {
965  literals_above_min.push_back(ref);
966  } else if (value_max == target_min) {
967  min_literals.push_back(ref);
968  }
969  }
970 
971  context_->UpdateRuleStats("lin_max: all Booleans.");
972 
973  // target_ref => at_least_one(max_literals);
974  ConstraintProto* clause = context_->working_model->add_constraints();
975  clause->add_enforcement_literal(target_ref);
976  clause->mutable_bool_or();
977  for (const int lit : max_literals) {
978  clause->mutable_bool_or()->add_literals(lit);
979  }
980 
981  // not(target_ref) => not(lit) for lit in literals_above_min
982  for (const int lit : literals_above_min) {
983  context_->AddImplication(lit, target_ref);
984  }
985 
986  if (!min_is_reachable) {
987  // not(target_ref) => at_least_one(min_literals).
988  ConstraintProto* clause = context_->working_model->add_constraints();
989  clause->add_enforcement_literal(NegatedRef(target_ref));
990  clause->mutable_bool_or();
991  for (const int lit : min_literals) {
992  clause->mutable_bool_or()->add_literals(lit);
993  }
994  }
995 
997  return RemoveConstraint(ct);
998 }
999 
1000 // This presolve expect that the constraint only contains affine expressions.
1001 bool CpModelPresolver::PresolveIntAbs(ConstraintProto* ct) {
1002  CHECK_EQ(ct->enforcement_literal_size(), 0);
1003  if (context_->ModelIsUnsat()) return false;
1004  const LinearExpressionProto& target_expr = ct->lin_max().target();
1005  const LinearExpressionProto& expr = ct->lin_max().exprs(0);
1006  DCHECK_EQ(expr.vars_size(), 1);
1007 
1008  // Propagate domain from the expression to the target.
1009  {
1010  const Domain expr_domain = context_->DomainSuperSetOf(expr);
1011  const Domain new_target_domain =
1012  expr_domain.UnionWith(expr_domain.Negation())
1014  bool target_domain_modified = false;
1015  if (!context_->IntersectDomainWith(target_expr, new_target_domain,
1016  &target_domain_modified)) {
1017  return false;
1018  }
1019  if (expr_domain.IsFixed()) {
1020  context_->UpdateRuleStats("int_abs: fixed expression");
1021  return RemoveConstraint(ct);
1022  }
1023  if (target_domain_modified) {
1024  context_->UpdateRuleStats("int_abs: propagate domain from x to abs(x)");
1025  }
1026  }
1027 
1028  // Propagate from target domain to variable.
1029  {
1030  const Domain target_domain =
1031  context_->DomainSuperSetOf(target_expr)
1033  const Domain new_expr_domain =
1034  target_domain.UnionWith(target_domain.Negation());
1035  bool expr_domain_modified = false;
1036  if (!context_->IntersectDomainWith(expr, new_expr_domain,
1037  &expr_domain_modified)) {
1038  return true;
1039  }
1040  // This is the only reason why we don't support fully generic linear
1041  // expression.
1042  if (context_->IsFixed(target_expr)) {
1043  context_->UpdateRuleStats("int_abs: fixed target");
1044  return RemoveConstraint(ct);
1045  }
1046  if (expr_domain_modified) {
1047  context_->UpdateRuleStats("int_abs: propagate domain from abs(x) to x");
1048  }
1049  }
1050 
1051  // Convert to equality if the sign of expr is fixed.
1052  if (context_->MinOf(expr) >= 0) {
1053  context_->UpdateRuleStats("int_abs: converted to equality");
1054  ConstraintProto* new_ct = context_->working_model->add_constraints();
1055  new_ct->set_name(ct->name());
1056  auto* arg = new_ct->mutable_linear();
1057  arg->add_domain(0);
1058  arg->add_domain(0);
1059  AddLinearExpressionToLinearConstraint(target_expr, 1, arg);
1061  if (!CanonicalizeLinear(new_ct)) return false;
1063  return RemoveConstraint(ct);
1064  }
1065 
1066  if (context_->MaxOf(expr) <= 0) {
1067  context_->UpdateRuleStats("int_abs: converted to equality");
1068  ConstraintProto* new_ct = context_->working_model->add_constraints();
1069  new_ct->set_name(ct->name());
1070  auto* arg = new_ct->mutable_linear();
1071  arg->add_domain(0);
1072  arg->add_domain(0);
1073  AddLinearExpressionToLinearConstraint(target_expr, 1, arg);
1075  if (!CanonicalizeLinear(new_ct)) return false;
1077  return RemoveConstraint(ct);
1078  }
1079 
1080  // Remove the abs constraint if the target is removable and if domains have
1081  // been propagated without loss.
1082  // For now, we known that there is no loss if the target is a single ref.
1083  // Since all the expression are affine, in this case we are fine.
1084  if (ExpressionContainsSingleRef(target_expr) &&
1085  context_->VariableIsUniqueAndRemovable(target_expr.vars(0))) {
1086  context_->MarkVariableAsRemoved(target_expr.vars(0));
1087  *context_->mapping_model->add_constraints() = *ct;
1088  context_->UpdateRuleStats("int_abs: unused target");
1089  return RemoveConstraint(ct);
1090  }
1091 
1092  // Store the x == abs(y) relation if expr and target_expr can be cast into a
1093  // ref.
1094  // TODO(user): Support general affine expression in for expr in the Store
1095  // method call.
1096  {
1097  if (ExpressionContainsSingleRef(target_expr) &&
1099  const int target_ref = GetSingleRefFromExpression(target_expr);
1100  const int expr_ref = GetSingleRefFromExpression(expr);
1101  if (context_->StoreAbsRelation(target_ref, expr_ref)) {
1102  context_->UpdateRuleStats("int_abs: store abs(x) == y");
1103  }
1104  }
1105  }
1106 
1107  return false;
1108 }
1109 
1110 bool CpModelPresolver::PresolveIntProd(ConstraintProto* ct) {
1111  if (context_->ModelIsUnsat()) return false;
1112  if (HasEnforcementLiteral(*ct)) return false;
1113 
1114  // Start by restricting the domain of target. We will be more precise later.
1115  bool domain_modified = false;
1116  {
1117  Domain implied(1);
1118  for (const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1119  implied =
1120  implied.ContinuousMultiplicationBy(context_->DomainSuperSetOf(expr));
1121  }
1122  if (!context_->IntersectDomainWith(ct->int_prod().target(), implied,
1123  &domain_modified)) {
1124  return false;
1125  }
1126  }
1127 
1128  // Remove constant expressions.
1129  int64_t constant_factor = 1;
1130  int new_size = 0;
1131  bool changed = false;
1132  LinearArgumentProto* proto = ct->mutable_int_prod();
1133  for (int i = 0; i < ct->int_prod().exprs().size(); ++i) {
1134  LinearExpressionProto expr = ct->int_prod().exprs(i);
1135  if (context_->IsFixed(expr)) {
1136  context_->UpdateRuleStats("int_prod: removed constant expressions.");
1137  changed = true;
1138  constant_factor = CapProd(constant_factor, context_->FixedValue(expr));
1139  continue;
1140  } else {
1141  const int64_t coeff = expr.coeffs(0);
1142  const int64_t offset = expr.offset();
1143  const int64_t gcd =
1144  MathUtil::GCD64(static_cast<uint64_t>(std::abs(coeff)),
1145  static_cast<uint64_t>(std::abs(offset)));
1146  if (gcd != 1) {
1147  constant_factor = CapProd(constant_factor, gcd);
1148  expr.set_coeffs(0, coeff / gcd);
1149  expr.set_offset(offset / gcd);
1150  }
1151  }
1152  *proto->mutable_exprs(new_size++) = expr;
1153  }
1154  proto->mutable_exprs()->erase(proto->mutable_exprs()->begin() + new_size,
1155  proto->mutable_exprs()->end());
1156 
1157  if (ct->int_prod().exprs().empty()) {
1158  if (!context_->IntersectDomainWith(ct->int_prod().target(),
1159  Domain(constant_factor))) {
1160  return false;
1161  }
1162  context_->UpdateRuleStats("int_prod: constant product");
1163  return RemoveConstraint(ct);
1164  }
1165 
1166  if (constant_factor == 0) {
1167  context_->UpdateRuleStats("int_prod: multiplication by zero");
1168  if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(0))) {
1169  return false;
1170  }
1171  return RemoveConstraint(ct);
1172  }
1173 
1174  // In this case, the only possible value that fit in the domains is zero.
1175  // We will check for UNSAT if zero is not achievable by the rhs below.
1176  if (constant_factor == std::numeric_limits<int64_t>::min() ||
1177  constant_factor == std::numeric_limits<int64_t>::max()) {
1178  context_->UpdateRuleStats("int_prod: overflow if non zero");
1179  if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(0))) {
1180  return false;
1181  }
1182  constant_factor = 1;
1183  }
1184 
1185  // Replace by linear!
1186  if (ct->int_prod().exprs().size() == 1) {
1187  LinearConstraintProto* const lin =
1188  context_->working_model->add_constraints()->mutable_linear();
1189  lin->add_domain(0);
1190  lin->add_domain(0);
1191  AddLinearExpressionToLinearConstraint(ct->int_prod().target(), 1, lin);
1192  AddLinearExpressionToLinearConstraint(ct->int_prod().exprs(0),
1193  -constant_factor, lin);
1195  context_->UpdateRuleStats("int_prod: linearize product by constant.");
1196  return RemoveConstraint(ct);
1197  }
1198 
1199  if (constant_factor != 1) {
1200  // Lets canonicalize the target by introducing a new variable if necessary.
1201  //
1202  // coeff * X + offset must be a multiple of constant_factor, so
1203  // we can rewrite X so that this property is clear.
1204  //
1205  // Note(user): it is important for this to have a restricted target domain
1206  // so we can choose a better representative.
1207  const LinearExpressionProto old_target = ct->int_prod().target();
1208  if (!context_->IsFixed(old_target)) {
1209  const int ref = old_target.vars(0);
1210  const int64_t coeff = old_target.coeffs(0);
1211  const int64_t offset = old_target.offset();
1212  if (!context_->CanonicalizeAffineVariable(ref, coeff, constant_factor,
1213  -offset)) {
1214  return false;
1215  }
1216  if (context_->IsFixed(ref)) {
1217  changed = true;
1218  }
1219  }
1220 
1221  // This can happen during CanonicalizeAffineVariable().
1222  if (context_->IsFixed(old_target)) {
1223  const int64_t target_value = context_->FixedValue(old_target);
1224  if (target_value % constant_factor != 0) {
1225  return context_->NotifyThatModelIsUnsat(
1226  "int_prod: constant factor does not divide constant target");
1227  }
1228  changed = true;
1229  proto->clear_target();
1230  proto->mutable_target()->set_offset(target_value / constant_factor);
1231  context_->UpdateRuleStats(
1232  "int_prod: divide product and fixed target by constant factor");
1233  } else {
1234  // We use absl::int128 to be resistant to overflow here.
1235  const AffineRelation::Relation r =
1236  context_->GetAffineRelation(old_target.vars(0));
1237  const absl::int128 temp_coeff =
1238  absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff);
1239  CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0);
1240  const absl::int128 temp_offset =
1241  absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) +
1242  absl::int128(old_target.offset());
1243  CHECK_EQ(temp_offset % absl::int128(constant_factor), 0);
1244  const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor);
1245  const absl::int128 new_offset =
1246  temp_offset / absl::int128(constant_factor);
1247 
1248  // TODO(user): We try to keep coeff/offset small, if this happens, it
1249  // probably means there is no feasible solution involving int64_t and that
1250  // do not causes overflow while evaluating it, but it is hard to be
1251  // exactly sure we are correct here since it depends on the evaluation
1252  // order. Similarly, by introducing intermediate variable we might loose
1253  // solution if this intermediate variable value do not fit on an int64_t.
1254  if (new_coeff > absl::int128(std::numeric_limits<int64_t>::max()) ||
1255  new_coeff < absl::int128(std::numeric_limits<int64_t>::min()) ||
1256  new_offset > absl::int128(std::numeric_limits<int64_t>::max()) ||
1257  new_offset < absl::int128(std::numeric_limits<int64_t>::min())) {
1258  return context_->NotifyThatModelIsUnsat(
1259  "int_prod: overflow during simplification.");
1260  }
1261 
1262  // Rewrite the target.
1263  proto->mutable_target()->set_coeffs(0, static_cast<int64_t>(new_coeff));
1264  proto->mutable_target()->set_vars(0, r.representative);
1265  proto->mutable_target()->set_offset(static_cast<int64_t>(new_offset));
1266  context_->UpdateRuleStats("int_prod: divide product by constant factor");
1267  changed = true;
1268  }
1269  }
1270 
1271  // Restrict the target domain if possible.
1272  Domain implied(1);
1273  bool is_square = false;
1274  if (ct->int_prod().exprs_size() == 2 &&
1275  LinearExpressionProtosAreEqual(ct->int_prod().exprs(0),
1276  ct->int_prod().exprs(1))) {
1277  is_square = true;
1278  implied =
1279  context_->DomainSuperSetOf(ct->int_prod().exprs(0)).SquareSuperset();
1280  } else {
1281  for (const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1282  implied =
1283  implied.ContinuousMultiplicationBy(context_->DomainSuperSetOf(expr));
1284  }
1285  }
1286  if (!context_->IntersectDomainWith(ct->int_prod().target(), implied,
1287  &domain_modified)) {
1288  return false;
1289  }
1290  if (domain_modified) {
1291  context_->UpdateRuleStats(absl::StrCat(
1292  is_square ? "int_square" : "int_prod", ": reduced target domain."));
1293  }
1294 
1295  // y = x * x, we can reduce the domain of x from the domain of y.
1296  if (is_square) {
1297  const int64_t target_max = context_->MaxOf(ct->int_prod().target());
1298  DCHECK_GE(target_max, 0);
1299  const int64_t sqrt_max = FloorSquareRoot(target_max);
1300  bool expr_reduced = false;
1301  if (!context_->IntersectDomainWith(ct->int_prod().exprs(0),
1302  {-sqrt_max, sqrt_max}, &expr_reduced)) {
1303  return false;
1304  }
1305  if (expr_reduced) {
1306  context_->UpdateRuleStats("int_square: reduced expr domain.");
1307  }
1308  }
1309 
1310  if (ct->int_prod().exprs_size() == 2) {
1311  LinearExpressionProto a = ct->int_prod().exprs(0);
1312  LinearExpressionProto b = ct->int_prod().exprs(1);
1313  const LinearExpressionProto product = ct->int_prod().target();
1316  a, product)) { // x = x * x, only true for {0, 1}.
1317  if (!context_->IntersectDomainWith(product, Domain(0, 1))) {
1318  return false;
1319  }
1320  context_->UpdateRuleStats("int_square: fix variable to zero or one.");
1321  return RemoveConstraint(ct);
1322  }
1323  }
1324 
1325  // For now, we only presolve the case where all variables are Booleans.
1326  const LinearExpressionProto target_expr = ct->int_prod().target();
1327  int target;
1328  if (!context_->ExpressionIsALiteral(target_expr, &target)) {
1329  return changed;
1330  }
1331  std::vector<int> literals;
1332  for (const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1333  int lit;
1334  if (!context_->ExpressionIsALiteral(expr, &lit)) {
1335  return changed;
1336  }
1337  literals.push_back(lit);
1338  }
1339 
1340  // This is a bool constraint!
1341  context_->UpdateRuleStats("int_prod: all Boolean.");
1342  {
1343  ConstraintProto* new_ct = context_->working_model->add_constraints();
1344  new_ct->add_enforcement_literal(target);
1345  auto* arg = new_ct->mutable_bool_and();
1346  for (const int lit : literals) {
1347  arg->add_literals(lit);
1348  }
1349  }
1350  {
1351  ConstraintProto* new_ct = context_->working_model->add_constraints();
1352  auto* arg = new_ct->mutable_bool_or();
1353  arg->add_literals(target);
1354  for (const int lit : literals) {
1355  arg->add_literals(NegatedRef(lit));
1356  }
1357  }
1359  return RemoveConstraint(ct);
1360 }
1361 
1362 bool CpModelPresolver::PresolveIntDiv(ConstraintProto* ct) {
1363  if (context_->ModelIsUnsat()) return false;
1364 
1365  const LinearExpressionProto target = ct->int_div().target();
1366  const LinearExpressionProto expr = ct->int_div().exprs(0);
1367  const LinearExpressionProto div = ct->int_div().exprs(1);
1368 
1369  if (LinearExpressionProtosAreEqual(expr, div)) {
1370  if (!context_->IntersectDomainWith(target, Domain(1))) {
1371  return false;
1372  }
1373  context_->UpdateRuleStats("int_div: y = x / x");
1374  return RemoveConstraint(ct);
1375  } else if (LinearExpressionProtosAreEqual(expr, div, -1)) {
1376  if (!context_->IntersectDomainWith(target, Domain(-1))) {
1377  return false;
1378  }
1379  context_->UpdateRuleStats("int_div: y = - x / x");
1380  return RemoveConstraint(ct);
1381  }
1382 
1383  // For now, we only presolve the case where the divisor is constant.
1384  if (!context_->IsFixed(div)) return false;
1385 
1386  const int64_t divisor = context_->FixedValue(div);
1387 
1388  // Trivial case one: target = expr / +/-1.
1389  if (divisor == 1 || divisor == -1) {
1390  LinearConstraintProto* const lin =
1391  context_->working_model->add_constraints()->mutable_linear();
1392  lin->add_domain(0);
1393  lin->add_domain(0);
1395  AddLinearExpressionToLinearConstraint(target, -divisor, lin);
1397  context_->UpdateRuleStats("int_div: rewrite to equality");
1398  return RemoveConstraint(ct);
1399  }
1400 
1401  // Reduce the domain of target.
1402  {
1403  bool domain_modified = false;
1404  const Domain target_implied_domain =
1405  context_->DomainSuperSetOf(expr).DivisionBy(divisor);
1406 
1407  if (!context_->IntersectDomainWith(target, target_implied_domain,
1408  &domain_modified)) {
1409  return false;
1410  }
1411  if (domain_modified) {
1412  // Note: the case target is fixed has been processed before.
1413  if (target_implied_domain.IsFixed()) {
1414  context_->UpdateRuleStats(
1415  "int_div: target has been fixed by propagating X / cte");
1416  } else {
1417  context_->UpdateRuleStats(
1418  "int_div: updated domain of target in target = X / cte");
1419  }
1420  }
1421  }
1422 
1423  // Trivial case three: fixed_target = expr / fixed_divisor.
1424  if (context_->IsFixed(target) &&
1425  CapAdd(1, CapProd(std::abs(divisor),
1426  1 + std::abs(context_->FixedValue(target)))) !=
1428  int64_t t = context_->FixedValue(target);
1429  int64_t d = divisor;
1430  if (d < 0) {
1431  t = -t;
1432  d = -d;
1433  }
1434 
1435  const Domain expr_implied_domain =
1436  t > 0
1437  ? Domain(t * d, (t + 1) * d - 1)
1438  : (t == 0 ? Domain(1 - d, d - 1) : Domain((t - 1) * d + 1, t * d));
1439  bool domain_modified = false;
1440  if (!context_->IntersectDomainWith(expr, expr_implied_domain,
1441  &domain_modified)) {
1442  return false;
1443  }
1444  if (domain_modified) {
1445  context_->UpdateRuleStats("int_div: target and divisor are fixed");
1446  } else {
1447  context_->UpdateRuleStats("int_div: always true");
1448  }
1449  return RemoveConstraint(ct);
1450  }
1451 
1452  // Linearize if everything is positive, and we have no overflow.
1453  // TODO(user): Deal with other cases where there is no change of
1454  // sign. We can also deal with target = cte, div variable.
1455  if (context_->MinOf(target) >= 0 && context_->MinOf(expr) >= 0 &&
1456  divisor > 1 &&
1457  CapProd(divisor, context_->MaxOf(target)) !=
1459  LinearConstraintProto* const lin =
1460  context_->working_model->add_constraints()->mutable_linear();
1461  lin->add_domain(0);
1462  lin->add_domain(divisor - 1);
1464  AddLinearExpressionToLinearConstraint(target, -divisor, lin);
1466  context_->UpdateRuleStats(
1467  "int_div: linearize positive division with a constant divisor");
1468 
1469  return RemoveConstraint(ct);
1470  }
1471 
1472  // TODO(user): reduce the domain of X by introducing an
1473  // InverseDivisionOfSortedDisjointIntervals().
1474  return false;
1475 }
1476 
1477 bool CpModelPresolver::PresolveIntMod(ConstraintProto* ct) {
1478  if (context_->ModelIsUnsat()) return false;
1479 
1480  const LinearExpressionProto target = ct->int_mod().target();
1481  const LinearExpressionProto expr = ct->int_mod().exprs(0);
1482  const LinearExpressionProto mod = ct->int_mod().exprs(1);
1483 
1484  if (context_->MinOf(target) > 0) {
1485  bool domain_changed = false;
1486  if (!context_->IntersectDomainWith(
1487  expr, Domain(0, std::numeric_limits<int64_t>::max()),
1488  &domain_changed)) {
1489  return false;
1490  }
1491  if (domain_changed) {
1492  context_->UpdateRuleStats(
1493  "int_mod: non negative target implies positive expression");
1494  }
1495  }
1496 
1497  if (context_->MinOf(target) >= context_->MaxOf(mod) ||
1498  context_->MaxOf(target) <= -context_->MaxOf(mod)) {
1499  return context_->NotifyThatModelIsUnsat(
1500  "int_mod: incompatible target and mod");
1501  }
1502 
1503  if (context_->MaxOf(target) < 0) {
1504  bool domain_changed = false;
1505  if (!context_->IntersectDomainWith(
1506  expr, Domain(std::numeric_limits<int64_t>::min(), 0),
1507  &domain_changed)) {
1508  return false;
1509  }
1510  if (domain_changed) {
1511  context_->UpdateRuleStats(
1512  "int_mod: non positive target implies negative expression");
1513  }
1514  }
1515 
1516  if (context_->IsFixed(target) && context_->IsFixed(mod) &&
1517  context_->FixedValue(mod) > 1 && ct->enforcement_literal().empty() &&
1518  expr.vars().size() == 1) {
1519  // We can intersect the domain of expr with {k * mod + target}.
1520  const int64_t fixed_mod = context_->FixedValue(mod);
1521  const int64_t fixed_target = context_->FixedValue(target);
1522 
1523  if (!context_->CanonicalizeAffineVariable(expr.vars(0), expr.coeffs(0),
1524  fixed_mod,
1525  fixed_target - expr.offset())) {
1526  return false;
1527  }
1528 
1529  context_->UpdateRuleStats("int_mod: fixed mod and target");
1530  return RemoveConstraint(ct);
1531  }
1532 
1533  bool domain_changed = false;
1534  if (!context_->IntersectDomainWith(
1535  target,
1537  context_->DomainSuperSetOf(mod)),
1538  &domain_changed)) {
1539  return false;
1540  }
1541 
1542  if (domain_changed) {
1543  context_->UpdateRuleStats("int_mod: reduce target domain");
1544  }
1545 
1546  return false;
1547 }
1548 
1549 // TODO(user): Now that everything has affine relations, we should maybe
1550 // canonicalize all linear subexpression in a generic way.
1551 bool CpModelPresolver::ExploitEquivalenceRelations(int c, ConstraintProto* ct) {
1552  bool changed = false;
1553 
1554  // Optim: Special case for the linear constraint. We just remap the
1555  // enforcement literals, the normal variables will be replaced by their
1556  // representative in CanonicalizeLinear().
1557  if (ct->constraint_case() == ConstraintProto::kLinear) {
1558  for (int& ref : *ct->mutable_enforcement_literal()) {
1559  const int rep = this->context_->GetLiteralRepresentative(ref);
1560  if (rep != ref) {
1561  changed = true;
1562  ref = rep;
1563  }
1564  }
1565  return changed;
1566  }
1567 
1568  // Optim: This extra loop is a lot faster than reparsing the variable from the
1569  // proto when there is nothing to do, which is quite often.
1570  bool work_to_do = false;
1571  for (const int var : context_->ConstraintToVars(c)) {
1572  const AffineRelation::Relation r = context_->GetAffineRelation(var);
1573  if (r.representative != var) {
1574  work_to_do = true;
1575  break;
1576  }
1577  }
1578  if (!work_to_do) return false;
1579 
1580  // Remap literal and negated literal to their representative.
1582  [&changed, this](int* ref) {
1583  const int rep = this->context_->GetLiteralRepresentative(*ref);
1584  if (rep != *ref) {
1585  changed = true;
1586  *ref = rep;
1587  }
1588  },
1589  ct);
1590  return changed;
1591 }
1592 
1593 bool CpModelPresolver::DivideLinearByGcd(ConstraintProto* ct) {
1594  if (context_->ModelIsUnsat()) return false;
1595 
1596  // Compute the GCD of all coefficients.
1597  int64_t gcd = 0;
1598  const int num_vars = ct->linear().vars().size();
1599  for (int i = 0; i < num_vars; ++i) {
1600  const int64_t magnitude = std::abs(ct->linear().coeffs(i));
1601  gcd = MathUtil::GCD64(gcd, magnitude);
1602  if (gcd == 1) break;
1603  }
1604  if (gcd > 1) {
1605  context_->UpdateRuleStats("linear: divide by GCD");
1606  for (int i = 0; i < num_vars; ++i) {
1607  ct->mutable_linear()->set_coeffs(i, ct->linear().coeffs(i) / gcd);
1608  }
1609  const Domain rhs = ReadDomainFromProto(ct->linear());
1610  FillDomainInProto(rhs.InverseMultiplicationBy(gcd), ct->mutable_linear());
1611  if (ct->linear().domain_size() == 0) {
1612  return MarkConstraintAsFalse(ct);
1613  }
1614  }
1615  return false;
1616 }
1617 
1618 template <typename ProtoWithVarsAndCoeffs>
1619 bool CpModelPresolver::CanonicalizeLinearExpressionInternal(
1620  const ConstraintProto& ct, ProtoWithVarsAndCoeffs* proto, int64_t* offset) {
1621  // First regroup the terms on the same variables and sum the fixed ones.
1622  //
1623  // TODO(user): Add a quick pass to skip most of the work below if the
1624  // constraint is already in canonical form?
1625  tmp_terms_.clear();
1626  int64_t sum_of_fixed_terms = 0;
1627  bool remapped = false;
1628  const int old_size = proto->vars().size();
1629  DCHECK_EQ(old_size, proto->coeffs().size());
1630  for (int i = 0; i < old_size; ++i) {
1631  // Remove fixed variable and take affine representative.
1632  //
1633  // Note that we need to do that before we test for equality with an
1634  // enforcement (they should already have been mapped).
1635  int new_var;
1636  int64_t new_coeff;
1637  {
1638  const int ref = proto->vars(i);
1639  const int var = PositiveRef(ref);
1640  const int64_t coeff =
1641  RefIsPositive(ref) ? proto->coeffs(i) : -proto->coeffs(i);
1642  if (coeff == 0) continue;
1643 
1644  if (context_->IsFixed(var)) {
1645  sum_of_fixed_terms += coeff * context_->FixedValue(var);
1646  continue;
1647  }
1648 
1649  const AffineRelation::Relation r = context_->GetAffineRelation(var);
1650  if (r.representative != var) {
1651  remapped = true;
1652  sum_of_fixed_terms += coeff * r.offset;
1653  }
1654 
1655  new_var = r.representative;
1656  new_coeff = coeff * r.coeff;
1657  }
1658 
1659  // TODO(user): Avoid the quadratic loop for the corner case of many
1660  // enforcement literal (this should be pretty rare though).
1661  bool removed = false;
1662  for (const int enf : ct.enforcement_literal()) {
1663  if (new_var == PositiveRef(enf)) {
1664  if (RefIsPositive(enf)) {
1665  // If the constraint is enforced, we can assume the variable is at 1.
1666  sum_of_fixed_terms += new_coeff;
1667  } else {
1668  // We can assume the variable is at zero.
1669  }
1670  removed = true;
1671  break;
1672  }
1673  }
1674  if (removed) {
1675  context_->UpdateRuleStats("linear: enforcement literal in expression");
1676  continue;
1677  }
1678 
1679  tmp_terms_.push_back({new_var, new_coeff});
1680  }
1681  proto->clear_vars();
1682  proto->clear_coeffs();
1683  std::sort(tmp_terms_.begin(), tmp_terms_.end());
1684  int current_var = 0;
1685  int64_t current_coeff = 0;
1686  for (const auto& entry : tmp_terms_) {
1687  CHECK(RefIsPositive(entry.first));
1688  if (entry.first == current_var) {
1689  current_coeff += entry.second;
1690  } else {
1691  if (current_coeff != 0) {
1692  proto->add_vars(current_var);
1693  proto->add_coeffs(current_coeff);
1694  }
1695  current_var = entry.first;
1696  current_coeff = entry.second;
1697  }
1698  }
1699  if (current_coeff != 0) {
1700  proto->add_vars(current_var);
1701  proto->add_coeffs(current_coeff);
1702  }
1703  if (remapped) {
1704  context_->UpdateRuleStats("linear: remapped using affine relations");
1705  }
1706  if (proto->vars().size() < old_size) {
1707  context_->UpdateRuleStats("linear: fixed or dup variables");
1708  }
1709  *offset = sum_of_fixed_terms;
1710  return remapped || proto->vars().size() < old_size;
1711 }
1712 
1713 bool CpModelPresolver::CanonicalizeLinearExpression(
1714  const ConstraintProto& ct, LinearExpressionProto* exp) {
1715  int64_t offset = 0;
1716  const bool result = CanonicalizeLinearExpressionInternal(ct, exp, &offset);
1717  exp->set_offset(exp->offset() + offset);
1718  return result;
1719 }
1720 
1721 bool CpModelPresolver::CanonicalizeLinear(ConstraintProto* ct) {
1722  if (ct->constraint_case() != ConstraintProto::kLinear) return false;
1723  if (context_->ModelIsUnsat()) return false;
1724 
1725  if (ct->linear().domain().empty()) {
1726  context_->UpdateRuleStats("linear: no domain");
1727  return MarkConstraintAsFalse(ct);
1728  }
1729 
1730  int64_t offset = 0;
1731  bool changed =
1732  CanonicalizeLinearExpressionInternal(*ct, ct->mutable_linear(), &offset);
1733  if (offset != 0) {
1735  ReadDomainFromProto(ct->linear()).AdditionWith(Domain(-offset)),
1736  ct->mutable_linear());
1737  }
1738  changed |= DivideLinearByGcd(ct);
1739 
1740  // For duplicate detection, we always make the first coeff positive.
1741  if (!ct->linear().coeffs().empty() && ct->linear().coeffs(0) < 0) {
1742  for (int64_t& ref_coeff : *ct->mutable_linear()->mutable_coeffs()) {
1743  ref_coeff = -ref_coeff;
1744  }
1746  ct->mutable_linear());
1747  }
1748 
1749  return changed;
1750 }
1751 
1752 bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto* ct) {
1753  if (ct->constraint_case() != ConstraintProto::kLinear ||
1754  context_->ModelIsUnsat()) {
1755  return false;
1756  }
1757 
1758  absl::btree_set<int> index_to_erase;
1759  const int num_vars = ct->linear().vars().size();
1760  Domain rhs = ReadDomainFromProto(ct->linear());
1761 
1762  // First pass. Process singleton column that are not in the objective. Note
1763  // that for postsolve, it is important that we process them in the same order
1764  // in which they will be removed.
1765  for (int i = 0; i < num_vars; ++i) {
1766  const int var = ct->linear().vars(i);
1767  const int64_t coeff = ct->linear().coeffs(i);
1768  CHECK(RefIsPositive(var));
1769  if (context_->VariableIsUniqueAndRemovable(var)) {
1770  // This is not needed for the code below, but in practice, removing
1771  // singleton with a large coefficient create holes in the constraint rhs
1772  // and we will need to add more variable to deal with that.
1773  // This works way better on timtab1CUTS.pb.gz for instance.
1774  if (std::abs(coeff) != 1) continue;
1775 
1776  bool exact;
1777  const auto term_domain =
1778  context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
1779  if (!exact) continue;
1780 
1781  // We do not do that if the domain of rhs becomes too complex.
1782  const Domain new_rhs = rhs.AdditionWith(term_domain);
1783  if (new_rhs.NumIntervals() > 100) continue;
1784 
1785  // Note that we can't do that if we loose information in the
1786  // multiplication above because the new domain might not be as strict
1787  // as the initial constraint otherwise. TODO(user): because of the
1788  // addition, it might be possible to cover more cases though.
1789  context_->UpdateRuleStats("linear: singleton column");
1790  index_to_erase.insert(i);
1791  rhs = new_rhs;
1792  continue;
1793  }
1794  }
1795 
1796  // If the whole linear is independent from the rest of the problem, we
1797  // can solve it now. If it is enforced, then each variable will have two
1798  // values: Its minimum one and one minimizing the objective under the
1799  // constraint. The switch can be controlled by a single Boolean.
1800  //
1801  // TODO(user): Cover more case like dedicated algorithm to solve for a small
1802  // number of variable that are faster than the DP we use here.
1803  if (index_to_erase.empty()) {
1804  int num_singletons = 0;
1805  for (const int var : ct->linear().vars()) {
1806  if (!RefIsPositive(var)) break;
1807  if (!context_->VariableWithCostIsUniqueAndRemovable(var) &&
1808  !context_->VariableIsUniqueAndRemovable(var)) {
1809  break;
1810  }
1811  ++num_singletons;
1812  }
1813  if (num_singletons == num_vars) {
1814  // Try to solve the equation.
1815  std::vector<Domain> domains;
1816  std::vector<int64_t> coeffs;
1817  std::vector<int64_t> costs;
1818  for (int i = 0; i < num_vars; ++i) {
1819  const int var = ct->linear().vars(i);
1820  CHECK(RefIsPositive(var));
1821  domains.push_back(context_->DomainOf(var));
1822  coeffs.push_back(ct->linear().coeffs(i));
1823  costs.push_back(context_->ObjectiveCoeff(var));
1824  }
1825  BasicKnapsackSolver solver;
1826  const auto& result = solver.Solve(domains, coeffs, costs,
1827  ReadDomainFromProto(ct->linear()));
1828  if (!result.solved) {
1829  context_->UpdateRuleStats(
1830  "TODO independent linear: minimize single linear constraint");
1831  } else if (result.infeasible) {
1832  context_->UpdateRuleStats(
1833  "independent linear: no DP solution to simple constraint");
1834  return MarkConstraintAsFalse(ct);
1835  } else {
1836  if (ct->enforcement_literal().empty()) {
1837  // Just fix everything.
1838  context_->UpdateRuleStats("independent linear: solved by DP");
1839  for (int i = 0; i < num_vars; ++i) {
1840  if (!context_->IntersectDomainWith(ct->linear().vars(i),
1841  Domain(result.solution[i]))) {
1842  return false;
1843  }
1844  }
1845  return RemoveConstraint(ct);
1846  }
1847 
1848  // Each variable will take two values according to a single Boolean.
1849  int indicator;
1850  if (ct->enforcement_literal().size() == 1) {
1851  indicator = ct->enforcement_literal(0);
1852  } else {
1853  indicator = context_->NewBoolVar();
1854  auto* new_ct = context_->working_model->add_constraints();
1855  *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
1856  new_ct->mutable_bool_or()->add_literals(indicator);
1858  }
1859  for (int i = 0; i < num_vars; ++i) {
1860  const int64_t best_value =
1861  costs[i] > 0 ? domains[i].Min() : domains[i].Max();
1862  const int64_t other_value = result.solution[i];
1863  if (best_value == other_value) {
1864  if (!context_->IntersectDomainWith(ct->linear().vars(i),
1865  Domain(best_value))) {
1866  return false;
1867  }
1868  continue;
1869  }
1870  if (RefIsPositive(indicator)) {
1871  if (!context_->StoreAffineRelation(ct->linear().vars(i), indicator,
1872  other_value - best_value,
1873  best_value)) {
1874  return false;
1875  }
1876  } else {
1877  if (!context_->StoreAffineRelation(
1878  ct->linear().vars(i), PositiveRef(indicator),
1879  best_value - other_value, other_value)) {
1880  return false;
1881  }
1882  }
1883  }
1884  context_->UpdateRuleStats(
1885  "independent linear: with enforcement, but solved by DP");
1886  return RemoveConstraint(ct);
1887  }
1888  }
1889  }
1890 
1891  // If we didn't find any, look for the one appearing in the objective.
1892  if (index_to_erase.empty()) {
1893  // Note that we only do that if we have a non-reified equality.
1894  if (context_->params().presolve_substitution_level() <= 0) return false;
1895  if (!ct->enforcement_literal().empty()) return false;
1896 
1897  // If it is possible to do so, note that we can transform constraint into
1898  // equalities in PropagateDomainsInLinear().
1899  if (rhs.Min() != rhs.Max()) return false;
1900 
1901  for (int i = 0; i < num_vars; ++i) {
1902  const int var = ct->linear().vars(i);
1903  const int64_t coeff = ct->linear().coeffs(i);
1904  CHECK(RefIsPositive(var));
1905 
1906  // If the variable appear only in the objective and we have an equality,
1907  // we can transfer the cost to the rest of the linear expression, and
1908  // remove that variable. Note that this do not remove any feasible
1909  // solution and is not a "dual" reduction.
1910  //
1911  // Note that is similar to the substitution code in PresolveLinear() but
1912  // it doesn't require the variable to be implied free since we do not
1913  // remove the constraints afterwards, just the variable.
1914  if (!context_->VariableWithCostIsUnique(var)) continue;
1915  DCHECK(context_->ObjectiveMap().contains(var));
1916 
1917  // We only support substitution that does not require to multiply the
1918  // objective by some factor.
1919  //
1920  // TODO(user): If the objective is a single variable, we can actually
1921  // "absorb" any factor into the objective scaling.
1922  const int64_t objective_coeff = context_->ObjectiveMap().at(var);
1923  CHECK_NE(coeff, 0);
1924  if (objective_coeff % coeff != 0) continue;
1925 
1926  // TODO(user): We have an issue if objective coeff is not one, because
1927  // the RecomputeSingletonObjectiveDomain() do not properly put holes
1928  // in the objective domain, which might cause an issue. Note that this
1929  // presolve rule is actually almost never applied on the miplib.
1930  if (std::abs(objective_coeff) != 1) continue;
1931 
1932  // We do not do that if the domain of rhs becomes too complex.
1933  bool exact;
1934  const auto term_domain =
1935  context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
1936  if (!exact) continue;
1937  const Domain new_rhs = rhs.AdditionWith(term_domain);
1938  if (new_rhs.NumIntervals() > 100) continue;
1939 
1940  // Special case: If the objective was a single variable, we can transfer
1941  // the domain of var to the objective, and just completely remove this
1942  // equality constraint.
1943  //
1944  // TODO(user): Maybe if var has a complex domain, we might not want to
1945  // substitute it?
1946  if (context_->ObjectiveMap().size() == 1) {
1947  // This make sure the domain of var is restricted and the objective
1948  // domain updated.
1949  if (!context_->RecomputeSingletonObjectiveDomain()) {
1950  return true;
1951  }
1952 
1953  // The function above might fix var, in which case, we just abort.
1954  if (context_->IsFixed(var)) continue;
1955 
1956  if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
1957  if (context_->ModelIsUnsat()) return true;
1958  continue;
1959  }
1960 
1961  context_->UpdateRuleStats("linear: singleton column define objective.");
1962  context_->MarkVariableAsRemoved(var);
1963  *(context_->mapping_model->add_constraints()) = *ct;
1964  return RemoveConstraint(ct);
1965  }
1966 
1967  // On supportcase20, this transformation make the LP relaxation way worse.
1968  // TODO(user): understand why.
1969  if (true) continue;
1970 
1971  // Update the objective and remove the variable from its equality
1972  // constraint by expanding its rhs. This might fail if the new linear
1973  // objective expression can lead to overflow.
1974  if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
1975  if (context_->ModelIsUnsat()) return true;
1976  continue;
1977  }
1978 
1979  context_->UpdateRuleStats(
1980  "linear: singleton column in equality and in objective.");
1981  rhs = new_rhs;
1982  index_to_erase.insert(i);
1983  break;
1984  }
1985  }
1986  if (index_to_erase.empty()) return false;
1987 
1988  // Tricky: If we have a singleton variable in an enforced constraint, and at
1989  // postsolve the enforcement is false, we might just ignore the constraint.
1990  // This is fine, but we still need to assign any removed variable to a
1991  // feasible value, otherwise later postsolve rules might not work correctly.
1992  // Adding these linear1 achieve that.
1993  //
1994  // TODO(user): Alternatively, we could copy the constraint without the
1995  // enforcement to the mapping model, since singleton variable are supposed
1996  // to always have a feasible value anyway.
1997  if (!ct->enforcement_literal().empty()) {
1998  for (const int i : index_to_erase) {
1999  const int var = ct->linear().vars(i);
2000  auto* l = context_->mapping_model->add_constraints()->mutable_linear();
2001  l->add_vars(var);
2002  l->add_coeffs(1);
2003  FillDomainInProto(context_->DomainOf(var), l);
2004  }
2005  }
2006 
2007  // TODO(user): we could add the constraint to mapping_model only once
2008  // instead of adding a reduced version of it each time a new singleton
2009  // variable appear in the same constraint later. That would work but would
2010  // also force the postsolve to take search decisions...
2011  *context_->mapping_model->add_constraints() = *ct;
2012 
2013  int new_size = 0;
2014  for (int i = 0; i < num_vars; ++i) {
2015  if (index_to_erase.count(i)) {
2016  context_->MarkVariableAsRemoved(ct->linear().vars(i));
2017  continue;
2018  }
2019  ct->mutable_linear()->set_coeffs(new_size, ct->linear().coeffs(i));
2020  ct->mutable_linear()->set_vars(new_size, ct->linear().vars(i));
2021  ++new_size;
2022  }
2023  ct->mutable_linear()->mutable_vars()->Truncate(new_size);
2024  ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
2025  FillDomainInProto(rhs, ct->mutable_linear());
2026  DivideLinearByGcd(ct);
2027  return true;
2028 }
2029 
2030 // If the gcd of all but one term (with index target_index) is not one, we can
2031 // rewrite the last term using an affine representative.
2032 bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality(
2033  int target_index, ConstraintProto* ct) {
2034  int64_t gcd = 0;
2035  const int num_variables = ct->linear().vars().size();
2036  for (int i = 0; i < num_variables; ++i) {
2037  if (i == target_index) continue;
2038  const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2039  gcd = MathUtil::GCD64(gcd, magnitude);
2040  if (gcd == 1) return false;
2041  }
2042 
2043  // If we take the constraint % gcd, we have
2044  // ref * coeff % gcd = rhs % gcd
2045  CHECK_GT(gcd, 1);
2046  const int ref = ct->linear().vars(target_index);
2047  const int64_t coeff = ct->linear().coeffs(target_index);
2048  const int64_t rhs = ct->linear().domain(0);
2049 
2050  // This should have been processed before by just dividing the whole
2051  // constraint by the gcd.
2052  if (coeff % gcd == 0) return false;
2053 
2054  if (!context_->CanonicalizeAffineVariable(ref, coeff, gcd, rhs)) {
2055  return false;
2056  }
2057 
2058  // We use the new variable in the constraint.
2059  // Note that we will divide everything by the gcd too.
2060  return CanonicalizeLinear(ct);
2061 }
2062 
2063 // Any equality must be true modulo n.
2064 //
2065 // If the gcd of all but one term is not one, we can rewrite the last term using
2066 // an affine representative by considering the equality modulo that gcd.
2067 // As an heuristic, we only test the smallest term or small primes 2, 3, and 5.
2068 //
2069 // We also handle the special case of having two non-zero literals modulo 2.
2070 //
2071 // TODO(user): Use more complex algo to detect all the cases? By spliting the
2072 // constraint in two, and computing the gcd of each halves, we can reduce the
2073 // problem to two problem of half size. So at least we can do it in O(n log n).
2074 bool CpModelPresolver::PresolveLinearEqualityWithModulo(ConstraintProto* ct) {
2075  if (context_->ModelIsUnsat()) return false;
2076  if (ct->constraint_case() != ConstraintProto::kLinear) return false;
2077  if (ct->linear().domain().size() != 2) return false;
2078  if (ct->linear().domain(0) != ct->linear().domain(1)) return false;
2079  if (!ct->enforcement_literal().empty()) return false;
2080 
2081  const int num_variables = ct->linear().vars().size();
2082  if (num_variables < 2) return false;
2083 
2084  std::vector<int> mod2_indices;
2085  std::vector<int> mod3_indices;
2086  std::vector<int> mod5_indices;
2087 
2088  int64_t min_magnitude;
2089  int num_smallest = 0;
2090  int smallest_index;
2091  for (int i = 0; i < num_variables; ++i) {
2092  const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2093  if (num_smallest == 0 || magnitude < min_magnitude) {
2094  min_magnitude = magnitude;
2095  num_smallest = 1;
2096  smallest_index = i;
2097  } else if (magnitude == min_magnitude) {
2098  ++num_smallest;
2099  }
2100 
2101  if (magnitude % 2 != 0) mod2_indices.push_back(i);
2102  if (magnitude % 3 != 0) mod3_indices.push_back(i);
2103  if (magnitude % 5 != 0) mod5_indices.push_back(i);
2104  }
2105 
2106  if (mod2_indices.size() == 2) {
2107  bool ok = true;
2108  std::vector<int> literals;
2109  for (const int i : mod2_indices) {
2110  const int ref = ct->linear().vars(i);
2111  if (!context_->CanBeUsedAsLiteral(ref)) {
2112  ok = false;
2113  break;
2114  }
2115  literals.push_back(ref);
2116  }
2117  if (ok) {
2118  const int64_t rhs = std::abs(ct->linear().domain(0));
2119  context_->UpdateRuleStats("linear: only two odd Booleans in equality");
2120  if (rhs % 2) {
2121  context_->StoreBooleanEqualityRelation(literals[0],
2122  NegatedRef(literals[1]));
2123  } else {
2124  context_->StoreBooleanEqualityRelation(literals[0], literals[1]);
2125  }
2126  }
2127  }
2128 
2129  // TODO(user): More than one reduction might be possible, so we will need
2130  // to call this again if we apply any of these reduction.
2131  if (mod2_indices.size() == 1) {
2132  return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0], ct);
2133  }
2134  if (mod3_indices.size() == 1) {
2135  return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0], ct);
2136  }
2137  if (mod5_indices.size() == 1) {
2138  return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0], ct);
2139  }
2140  if (num_smallest == 1) {
2141  return AddVarAffineRepresentativeFromLinearEquality(smallest_index, ct);
2142  }
2143 
2144  return false;
2145 }
2146 
2147 bool CpModelPresolver::PresolveLinearOfSizeOne(ConstraintProto* ct) {
2148  DCHECK_EQ(ct->linear().vars().size(), 1);
2149 
2150  // Size one constraint with no enforcement?
2151  if (!HasEnforcementLiteral(*ct)) {
2152  const int64_t coeff = RefIsPositive(ct->linear().vars(0))
2153  ? ct->linear().coeffs(0)
2154  : -ct->linear().coeffs(0);
2155  context_->UpdateRuleStats("linear1: without enforcement");
2156  const int var = PositiveRef(ct->linear().vars(0));
2157  const Domain rhs = ReadDomainFromProto(ct->linear());
2158  if (!context_->IntersectDomainWith(var,
2159  rhs.InverseMultiplicationBy(coeff))) {
2160  return false;
2161  }
2162  return RemoveConstraint(ct);
2163  }
2164 
2165  // This is just an implication, lets convert it right away.
2166  if (context_->CanBeUsedAsLiteral(ct->linear().vars(0))) {
2167  const Domain rhs = ReadDomainFromProto(ct->linear());
2168  const bool zero_ok = rhs.Contains(0);
2169  const bool one_ok = rhs.Contains(ct->linear().coeffs(0));
2170  context_->UpdateRuleStats("linear1: is boolean implication");
2171  if (!zero_ok && !one_ok) {
2172  return MarkConstraintAsFalse(ct);
2173  }
2174  if (zero_ok && one_ok) {
2175  return RemoveConstraint(ct);
2176  }
2177  const int ref = ct->linear().vars(0);
2178  if (zero_ok) {
2179  ct->mutable_bool_and()->add_literals(NegatedRef(ref));
2180  } else {
2181  ct->mutable_bool_and()->add_literals(ref);
2182  }
2183 
2184  // No var <-> constraint graph changes.
2185  // But this is no longer a linear1.
2186  return true;
2187  }
2188 
2189  // If the constraint is literal => x in domain and x = abs(abs_arg), we can
2190  // replace x by abs_arg and hopefully remove the variable x later.
2191  int abs_arg;
2192  if (ct->linear().coeffs(0) == 1 &&
2193  context_->GetAbsRelation(ct->linear().vars(0), &abs_arg) &&
2194  PositiveRef(ct->linear().vars(0)) != abs_arg) {
2195  DCHECK(RefIsPositive(abs_arg));
2196  // TODO(user): Deal with coeff = -1, here or during canonicalization.
2197  context_->UpdateRuleStats("linear1: remove abs from abs(x) in domain");
2198  const Domain implied_abs_target_domain =
2199  ReadDomainFromProto(ct->linear())
2201  .IntersectionWith(context_->DomainOf(ct->linear().vars(0)));
2202 
2203  if (implied_abs_target_domain.IsEmpty()) {
2204  return MarkConstraintAsFalse(ct);
2205  }
2206 
2207  const Domain new_abs_var_domain =
2208  implied_abs_target_domain
2209  .UnionWith(implied_abs_target_domain.Negation())
2210  .IntersectionWith(context_->DomainOf(abs_arg));
2211 
2212  if (new_abs_var_domain.IsEmpty()) {
2213  return MarkConstraintAsFalse(ct);
2214  }
2215 
2216  // Modify the constraint in-place.
2217  ct->clear_linear();
2218  ct->mutable_linear()->add_vars(abs_arg);
2219  ct->mutable_linear()->add_coeffs(1);
2220  FillDomainInProto(new_abs_var_domain, ct->mutable_linear());
2221  return true;
2222  }
2223 
2224  // Detect encoding.
2225  if (ct->enforcement_literal_size() != 1) return false;
2226 
2227  // If we already have an encoding literal, this constraint is really
2228  // an implication.
2229  const int lit = ct->enforcement_literal(0);
2230  const int var = ct->linear().vars(0);
2231  const Domain var_domain = context_->DomainOf(var);
2232  const Domain rhs = ReadDomainFromProto(ct->linear())
2233  .InverseMultiplicationBy(ct->linear().coeffs(0))
2234  .IntersectionWith(var_domain);
2235  if (rhs.IsEmpty()) {
2236  context_->UpdateRuleStats("linear1: infeasible");
2237  return MarkConstraintAsFalse(ct);
2238  }
2239  if (rhs == var_domain) {
2240  context_->UpdateRuleStats("linear1: always true");
2241  return RemoveConstraint(ct);
2242  }
2243 
2244  if (rhs.IsFixed()) {
2245  const int64_t value = rhs.FixedValue();
2246  int encoding_lit;
2247  if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2248  if (lit == encoding_lit) return false;
2249  context_->AddImplication(lit, encoding_lit);
2251  ct->Clear();
2252  context_->UpdateRuleStats("linear1: transformed to implication");
2253  return true;
2254  } else {
2255  if (context_->StoreLiteralImpliesVarEqValue(lit, var, value)) {
2256  // The domain is not actually modified, but we want to rescan the
2257  // constraints linked to this variable.
2258  context_->modified_domains.Set(var);
2259  }
2261  }
2262  return false;
2263  }
2264 
2265  const Domain complement = rhs.Complement().IntersectionWith(var_domain);
2266  if (complement.IsFixed()) {
2267  const int64_t value = complement.FixedValue();
2268  int encoding_lit;
2269  if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2270  if (NegatedRef(lit) == encoding_lit) return false;
2271  context_->AddImplication(lit, NegatedRef(encoding_lit));
2273  ct->Clear();
2274  context_->UpdateRuleStats("linear1: transformed to implication");
2275  return true;
2276  } else {
2277  if (context_->StoreLiteralImpliesVarNEqValue(lit, var, value)) {
2278  // The domain is not actually modified, but we want to rescan the
2279  // constraints linked to this variable.
2280  context_->modified_domains.Set(var);
2281  }
2283  }
2284  }
2285 
2286  return false;
2287 }
2288 
2289 bool CpModelPresolver::PresolveLinearOfSizeTwo(ConstraintProto* ct) {
2290  DCHECK_EQ(ct->linear().vars().size(), 2);
2291 
2292  const LinearConstraintProto& arg = ct->linear();
2293  const int var1 = arg.vars(0);
2294  const int var2 = arg.vars(1);
2295  const int64_t coeff1 = arg.coeffs(0);
2296  const int64_t coeff2 = arg.coeffs(1);
2297 
2298  // If it is not an equality, we only presolve the constraint if one of
2299  // the variable is Boolean. Note that if both are Boolean, then a similar
2300  // reduction is done by PresolveLinearOnBooleans(). If we have an equality,
2301  // then the code below will do something stronger than this.
2302  //
2303  // TODO(user): We should probably instead generalize the code of
2304  // ExtractEnforcementLiteralFromLinearConstraint(), or just temporary
2305  // propagate domain of enforced linear constraints, to detect Boolean that
2306  // must be true or false. This way we can do the same for longer constraints.
2307  const bool is_equality =
2308  arg.domain_size() == 2 && arg.domain(0) == arg.domain(1);
2309  if (!is_equality) {
2310  int lit, var;
2311  int64_t value_on_true, coeff;
2312  if (context_->CanBeUsedAsLiteral(var1)) {
2313  lit = var1;
2314  value_on_true = coeff1;
2315  var = var2;
2316  coeff = coeff2;
2317  } else if (context_->CanBeUsedAsLiteral(var2)) {
2318  lit = var2;
2319  value_on_true = coeff2;
2320  var = var1;
2321  coeff = coeff1;
2322  } else {
2323  return false;
2324  }
2325  if (!RefIsPositive(lit)) return false;
2326 
2327  const Domain rhs = ReadDomainFromProto(ct->linear());
2328  const Domain rhs_if_true =
2329  rhs.AdditionWith(Domain(-value_on_true)).InverseMultiplicationBy(coeff);
2330  const Domain rhs_if_false = rhs.InverseMultiplicationBy(coeff);
2331  const bool implied_false =
2332  context_->DomainOf(var).IntersectionWith(rhs_if_true).IsEmpty();
2333  const bool implied_true =
2334  context_->DomainOf(var).IntersectionWith(rhs_if_false).IsEmpty();
2335  if (implied_true && implied_false) {
2336  context_->UpdateRuleStats("linear2: infeasible.");
2337  return MarkConstraintAsFalse(ct);
2338  } else if (implied_true) {
2339  context_->UpdateRuleStats("linear2: Boolean with one feasible value.");
2340 
2341  // => true.
2342  ConstraintProto* new_ct = context_->working_model->add_constraints();
2343  *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2344  new_ct->mutable_bool_and()->add_literals(lit);
2346 
2347  // Rewrite to => var in rhs_if_true.
2348  ct->mutable_linear()->Clear();
2349  ct->mutable_linear()->add_vars(var);
2350  ct->mutable_linear()->add_coeffs(1);
2351  FillDomainInProto(rhs_if_true, ct->mutable_linear());
2352  return PresolveLinearOfSizeOne(ct) || true;
2353  } else if (implied_false) {
2354  context_->UpdateRuleStats("linear2: Boolean with one feasible value.");
2355 
2356  // => false.
2357  ConstraintProto* new_ct = context_->working_model->add_constraints();
2358  *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2359  new_ct->mutable_bool_and()->add_literals(NegatedRef(lit));
2361 
2362  // Rewrite to => var in rhs_if_false.
2363  ct->mutable_linear()->Clear();
2364  ct->mutable_linear()->add_vars(var);
2365  ct->mutable_linear()->add_coeffs(1);
2366  FillDomainInProto(rhs_if_false, ct->mutable_linear());
2367  return PresolveLinearOfSizeOne(ct) || true;
2368  } else if (ct->enforcement_literal().empty() &&
2369  !context_->CanBeUsedAsLiteral(var)) {
2370  // We currently only do that if there are no enforcement and we don't have
2371  // two Booleans as this can be presolved differently. We expand it into
2372  // two linear1 constraint that have a chance to be merged with other
2373  // "encoding" constraints.
2374  context_->UpdateRuleStats("linear2: contains a Boolean.");
2375 
2376  // lit => var \in rhs_if_true
2377  const Domain var_domain = context_->DomainOf(var);
2378  if (!var_domain.IsIncludedIn(rhs_if_true)) {
2379  ConstraintProto* new_ct = context_->working_model->add_constraints();
2380  new_ct->add_enforcement_literal(lit);
2381  new_ct->mutable_linear()->add_vars(var);
2382  new_ct->mutable_linear()->add_coeffs(1);
2383  FillDomainInProto(rhs_if_true.IntersectionWith(var_domain),
2384  new_ct->mutable_linear());
2385  }
2386 
2387  // NegatedRef(lit) => var \in rhs_if_false
2388  if (!var_domain.IsIncludedIn(rhs_if_false)) {
2389  ConstraintProto* new_ct = context_->working_model->add_constraints();
2390  new_ct->add_enforcement_literal(NegatedRef(lit));
2391  new_ct->mutable_linear()->add_vars(var);
2392  new_ct->mutable_linear()->add_coeffs(1);
2393  FillDomainInProto(rhs_if_false.IntersectionWith(var_domain),
2394  new_ct->mutable_linear());
2395  }
2396 
2398  ct->Clear();
2399  return true;
2400  }
2401 
2402  // Code below require equality.
2403  context_->UpdateRuleStats("TODO linear2: contains a Boolean.");
2404  return false;
2405  }
2406 
2407  // We have: enforcement => (coeff1 * v1 + coeff2 * v2 == rhs).
2408  const int64_t rhs = arg.domain(0);
2409  if (ct->enforcement_literal().empty()) {
2410  // Detect affine relation.
2411  //
2412  // TODO(user): it might be better to first add only the affine relation with
2413  // a coefficient of magnitude 1, and later the one with larger coeffs.
2414  bool added = false;
2415  if (coeff1 == 1) {
2416  added = context_->StoreAffineRelation(var1, var2, -coeff2, rhs);
2417  } else if (coeff2 == 1) {
2418  added = context_->StoreAffineRelation(var2, var1, -coeff1, rhs);
2419  } else if (coeff1 == -1) {
2420  added = context_->StoreAffineRelation(var1, var2, coeff2, -rhs);
2421  } else if (coeff2 == -1) {
2422  added = context_->StoreAffineRelation(var2, var1, coeff1, -rhs);
2423  } else {
2424  // In this case, we can solve the diophantine equation, and write
2425  // both x and y in term of a new affine representative z.
2426  //
2427  // Note that PresolveLinearEqualityWithModulo() will have the same effect.
2428  //
2429  // We can also decide to fully expand the equality if the variables
2430  // are fully encoded.
2431  context_->UpdateRuleStats("TODO linear2: ax + by = cte");
2432  }
2433  if (added) return RemoveConstraint(ct);
2434  } else {
2435  // We look ahead to detect solutions to ax + by == cte.
2436  int64_t a = coeff1;
2437  int64_t b = coeff2;
2438  int64_t cte = rhs;
2439  int64_t x0 = 0;
2440  int64_t y0 = 0;
2441  if (!SolveDiophantineEquationOfSizeTwo(a, b, cte, x0, y0)) {
2442  context_->UpdateRuleStats(
2443  "linear2: implied ax + by = cte has no solutions");
2444  return MarkConstraintAsFalse(ct);
2445  }
2446  const Domain reduced_domain =
2447  context_->DomainOf(var1)
2448  .AdditionWith(Domain(-x0))
2450  .IntersectionWith(context_->DomainOf(var2)
2451  .AdditionWith(Domain(-y0))
2452  .InverseMultiplicationBy(-a));
2453 
2454  if (reduced_domain.IsEmpty()) { // no solution
2455  context_->UpdateRuleStats(
2456  "linear2: implied ax + by = cte has no solutions");
2457  return MarkConstraintAsFalse(ct);
2458  }
2459 
2460  if (reduced_domain.Size() == 1) {
2461  const int64_t z = reduced_domain.FixedValue();
2462  const int64_t value1 = x0 + b * z;
2463  const int64_t value2 = y0 - a * z;
2464 
2465  DCHECK(context_->DomainContains(var1, value1));
2466  DCHECK(context_->DomainContains(var2, value2));
2467  DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
2468 
2469  ConstraintProto* imply1 = context_->working_model->add_constraints();
2470  *imply1->mutable_enforcement_literal() = ct->enforcement_literal();
2471  imply1->mutable_linear()->add_vars(var1);
2472  imply1->mutable_linear()->add_coeffs(1);
2473  imply1->mutable_linear()->add_domain(value1);
2474  imply1->mutable_linear()->add_domain(value1);
2475 
2476  ConstraintProto* imply2 = context_->working_model->add_constraints();
2477  *imply2->mutable_enforcement_literal() = ct->enforcement_literal();
2478  imply2->mutable_linear()->add_vars(var2);
2479  imply2->mutable_linear()->add_coeffs(1);
2480  imply2->mutable_linear()->add_domain(value2);
2481  imply2->mutable_linear()->add_domain(value2);
2482  context_->UpdateRuleStats(
2483  "linear2: implied ax + by = cte has only one solution");
2485  return RemoveConstraint(ct);
2486  }
2487  }
2488 
2489  return false;
2490 }
2491 
2492 bool CpModelPresolver::PresolveSmallLinear(ConstraintProto* ct) {
2493  if (ct->constraint_case() != ConstraintProto::kLinear) return false;
2494  if (context_->ModelIsUnsat()) return false;
2495 
2496  if (ct->linear().vars().empty()) {
2497  context_->UpdateRuleStats("linear: empty");
2498  const Domain rhs = ReadDomainFromProto(ct->linear());
2499  if (rhs.Contains(0)) {
2500  return RemoveConstraint(ct);
2501  } else {
2502  return MarkConstraintAsFalse(ct);
2503  }
2504  } else if (ct->linear().vars().size() == 1) {
2505  return PresolveLinearOfSizeOne(ct);
2506  } else if (ct->linear().vars().size() == 2) {
2507  return PresolveLinearOfSizeTwo(ct);
2508  }
2509 
2510  return false;
2511 }
2512 
2513 bool CpModelPresolver::PresolveDiophantine(ConstraintProto* ct) {
2514  if (ct->constraint_case() != ConstraintProto::kLinear) return false;
2515  if (ct->linear().vars().size() <= 1) return false;
2516 
2517  if (context_->ModelIsUnsat()) return false;
2518 
2519  const LinearConstraintProto& linear_constraint = ct->linear();
2520  if (linear_constraint.domain_size() != 2) return false;
2521  if (linear_constraint.domain(0) != linear_constraint.domain(1)) return false;
2522 
2523  std::vector<int64_t> lbs(linear_constraint.vars_size());
2524  std::vector<int64_t> ubs(linear_constraint.vars_size());
2525  for (int i = 0; i < linear_constraint.vars_size(); ++i) {
2526  lbs[i] = context_->MinOf(linear_constraint.vars(i));
2527  ubs[i] = context_->MaxOf(linear_constraint.vars(i));
2528  }
2529  DiophantineSolution diophantine_solution = SolveDiophantine(
2530  linear_constraint.coeffs(), linear_constraint.domain(0), lbs, ubs);
2531 
2532  if (!diophantine_solution.has_solutions) {
2533  context_->UpdateRuleStats("diophantine: equality has no solutions");
2534  return MarkConstraintAsFalse(ct);
2535  }
2536  if (diophantine_solution.no_reformulation_needed) return false;
2537  // Only first coefficients of kernel_basis elements and special_solution could
2538  // overflow int64_t due to the reduction applied in SolveDiophantineEquation,
2539  for (const std::vector<absl::int128>& b : diophantine_solution.kernel_basis) {
2540  if (!IsNegatableInt64(b[0])) {
2541  context_->UpdateRuleStats(
2542  "diophantine: couldn't apply due to int64_t overflow");
2543  return false;
2544  }
2545  }
2546  if (!IsNegatableInt64(diophantine_solution.special_solution[0])) {
2547  context_->UpdateRuleStats(
2548  "diophantine: couldn't apply due to int64_t overflow");
2549  return false;
2550  }
2551 
2552  const int num_replaced_variables =
2553  static_cast<int>(diophantine_solution.special_solution.size());
2554  const int num_new_variables =
2555  static_cast<int>(diophantine_solution.kernel_vars_lbs.size());
2556  DCHECK_EQ(num_new_variables + 1, num_replaced_variables);
2557  for (int i = 0; i < num_new_variables; ++i) {
2558  if (!IsNegatableInt64(diophantine_solution.kernel_vars_lbs[i]) ||
2559  !IsNegatableInt64(diophantine_solution.kernel_vars_ubs[i])) {
2560  context_->UpdateRuleStats(
2561  "diophantine: couldn't apply due to int64_t overflow");
2562  return false;
2563  }
2564  }
2565  // TODO(user): Make sure the newly generated linear constraint
2566  // satisfy our no-overflow precondition on the min/max activity.
2567  // We should check that the model still satisfy conditions in
2568  // 3/ortools/sat/cp_model_checker.cc;l=165;bpv=0
2569 
2570  // Create new variables.
2571  std::vector<int> new_variables(num_new_variables);
2572  for (int i = 0; i < num_new_variables; ++i) {
2573  new_variables[i] = context_->working_model->variables_size();
2574  IntegerVariableProto* var = context_->working_model->add_variables();
2575  var->add_domain(
2576  static_cast<int64_t>(diophantine_solution.kernel_vars_lbs[i]));
2577  var->add_domain(
2578  static_cast<int64_t>(diophantine_solution.kernel_vars_ubs[i]));
2579  if (!ct->name().empty()) {
2580  var->set_name(absl::StrCat("u_diophantine_", ct->name(), "_", i));
2581  }
2582  }
2583 
2584  // For i = 0, ..., num_replaced_variables - 1, creates
2585  // x[i] = special_solution[i]
2586  // + sum(kernel_basis[k][i]*y[k], max(1, i) <= k < vars.size - 1)
2587  // where:
2588  // y[k] is the newly created variable if 0 <= k < num_new_variables
2589  // y[k] = x[index_permutation[k + 1]] otherwise.
2590  for (int i = 0; i < num_replaced_variables; ++i) {
2591  ConstraintProto* identity = context_->working_model->add_constraints();
2592  LinearConstraintProto* lin = identity->mutable_linear();
2593  if (!ct->name().empty()) {
2594  identity->set_name(absl::StrCat("c_diophantine_", ct->name(), "_", i));
2595  }
2596  *identity->mutable_enforcement_literal() = ct->enforcement_literal();
2597  lin->add_vars(
2598  linear_constraint.vars(diophantine_solution.index_permutation[i]));
2599  lin->add_coeffs(1);
2600  lin->add_domain(
2601  static_cast<int64_t>(diophantine_solution.special_solution[i]));
2602  lin->add_domain(
2603  static_cast<int64_t>(diophantine_solution.special_solution[i]));
2604  for (int j = std::max(1, i); j < num_replaced_variables; ++j) {
2605  lin->add_vars(new_variables[j - 1]);
2606  lin->add_coeffs(
2607  -static_cast<int64_t>(diophantine_solution.kernel_basis[j - 1][i]));
2608  }
2609  for (int j = num_replaced_variables; j < linear_constraint.vars_size();
2610  ++j) {
2611  lin->add_vars(
2612  linear_constraint.vars(diophantine_solution.index_permutation[j]));
2613  lin->add_coeffs(
2614  -static_cast<int64_t>(diophantine_solution.kernel_basis[j - 1][i]));
2615  }
2616 
2617  // TODO(user): The domain in the proto are not necessarily up to date so
2618  // this might be stricter than necessary. Fix? It shouldn't matter too much
2619  // though.
2620  if (PossibleIntegerOverflow(*(context_->working_model), lin->vars(),
2621  lin->coeffs())) {
2622  context_->UpdateRuleStats(
2623  "diophantine: couldn't apply due to overflowing activity of new "
2624  "constraints");
2625  // Cancel working_model changes.
2626  context_->working_model->mutable_constraints()->DeleteSubrange(
2627  context_->working_model->constraints_size() - i - 1, i + 1);
2628  context_->working_model->mutable_variables()->DeleteSubrange(
2629  context_->working_model->variables_size() - num_new_variables,
2630  num_new_variables);
2631  return false;
2632  }
2633  }
2634  context_->InitializeNewDomains();
2635 
2636  if (VLOG_IS_ON(2)) {
2637  std::string log_eq = absl::StrCat(linear_constraint.domain(0), " = ");
2638  const int terms_to_show = std::min<int>(15, linear_constraint.vars_size());
2639  for (int i = 0; i < terms_to_show; ++i) {
2640  if (i > 0) absl::StrAppend(&log_eq, " + ");
2641  absl::StrAppend(
2642  &log_eq,
2643  linear_constraint.coeffs(diophantine_solution.index_permutation[i]),
2644  " x",
2645  linear_constraint.vars(diophantine_solution.index_permutation[i]));
2646  }
2647  if (terms_to_show < linear_constraint.vars_size()) {
2648  absl::StrAppend(&log_eq, "+ ... (", linear_constraint.vars_size(),
2649  " terms)");
2650  }
2651  VLOG(2) << "[Diophantine] " << log_eq;
2652  }
2653 
2654  context_->UpdateRuleStats("diophantine: reformulated equality");
2656  return RemoveConstraint(ct);
2657 }
2658 
2659 // This tries to decompose the constraint into coeff * part1 + part2 and show
2660 // that the value that part2 take is not important, thus the constraint can
2661 // only be transformed on a constraint on the first part.
2662 //
2663 // TODO(user): Improve !! we miss simple case like x + 47 y + 50 z >= 50
2664 // for positive variables. We should remove x, and ideally we should rewrite
2665 // this as y + 2z >= 2 if we can show that its relaxation is just better?
2666 // We should at least see that it is the same as 47y + 50 z >= 48.
2667 //
2668 // TODO(user): One easy algo is to first remove all enforcement term (even
2669 // non-Boolean one) before applying the algo here and then re-linearize the
2670 // non-Boolean terms.
2671 void CpModelPresolver::TryToReduceCoefficientsOfLinearConstraint(
2672  int c, ConstraintProto* ct) {
2673  if (ct->constraint_case() != ConstraintProto::kLinear) return;
2674  if (context_->ModelIsUnsat()) return;
2675 
2676  // Only consider "simple" constraints.
2677  const LinearConstraintProto& lin = ct->linear();
2678  const Domain rhs = ReadDomainFromProto(lin);
2679  if (rhs.NumIntervals() != 1) return;
2680 
2681  // Precompute a bunch of quantities and "canonicalize" the constraint.
2682  int64_t lb_sum = 0;
2683  int64_t ub_sum = 0;
2684  int64_t max_variation = 0;
2685  struct Entry {
2686  int64_t magnitude;
2687  int64_t max_variation;
2688  int index;
2689  };
2690  std::vector<Entry> entries;
2691  std::vector<int> vars;
2692  std::vector<int64_t> coeffs;
2693  std::vector<int64_t> magnitudes;
2694  std::vector<int64_t> lbs;
2695  std::vector<int64_t> ubs;
2696  int64_t max_magnitude = 0;
2697  const int num_terms = lin.vars().size();
2698  for (int i = 0; i < num_terms; ++i) {
2699  const int64_t coeff = lin.coeffs(i);
2700  const int64_t magnitude = std::abs(lin.coeffs(i));
2701  if (magnitude == 0) continue;
2702  max_magnitude = std::max(max_magnitude, magnitude);
2703 
2704  int64_t lb;
2705  int64_t ub;
2706  if (coeff > 0) {
2707  lb = context_->MinOf(lin.vars(i));
2708  ub = context_->MaxOf(lin.vars(i));
2709  } else {
2710  lb = -context_->MaxOf(lin.vars(i));
2711  ub = -context_->MinOf(lin.vars(i));
2712  }
2713  lb_sum += lb * magnitude;
2714  ub_sum += ub * magnitude;
2715 
2716  // Abort if fixed term, that might mess up code below.
2717  if (lb == ub) return;
2718 
2719  vars.push_back(lin.vars(i));
2720  lbs.push_back(lb);
2721  ubs.push_back(ub);
2722  coeffs.push_back(coeff);
2723  magnitudes.push_back(magnitude);
2724  entries.push_back({magnitude, magnitude * (ub - lb), i});
2725  max_variation += entries.back().max_variation;
2726  }
2727 
2728  // Mark trivially false constraint as such. This should have been already
2729  // done, but we require non-negative quantity below.
2730  if (lb_sum > rhs.Max() || rhs.Min() > ub_sum) {
2731  (void)MarkConstraintAsFalse(ct);
2732  context_->UpdateConstraintVariableUsage(c);
2733  return;
2734  }
2735  const IntegerValue rhs_ub(CapSub(rhs.Max(), lb_sum));
2736  const IntegerValue rhs_lb(CapSub(ub_sum, rhs.Min()));
2737  const bool use_ub = max_variation > rhs_ub;
2738  const bool use_lb = max_variation > rhs_lb;
2739  if (!use_ub && !use_lb) {
2740  (void)RemoveConstraint(ct);
2741  context_->UpdateConstraintVariableUsage(c);
2742  return;
2743  }
2744 
2745  // No point doing more work for constraint with all coeff at +/-1.
2746  if (max_magnitude <= 1) return;
2747 
2748  // TODO(user): All the lb/ub_feasible/infeasible class are updated in
2749  // exactly the same way. Find a more efficient algo?
2750  if (use_lb) {
2751  lb_feasible_.Reset(rhs_lb.value());
2752  lb_infeasible_.Reset(rhs.Min() - lb_sum - 1);
2753  }
2754  if (use_ub) {
2755  ub_feasible_.Reset(rhs_ub.value());
2756  ub_infeasible_.Reset(ub_sum - rhs.Max() - 1);
2757  }
2758 
2759  // Process entries by decreasing magnitude. Update max_error to correspond
2760  // only to the sum of the not yet processed terms.
2761  uint64_t gcd = 0;
2762  int64_t max_error = max_variation;
2763  std::stable_sort(
2764  entries.begin(), entries.end(),
2765  [](const Entry& a, const Entry& b) { return a.magnitude > b.magnitude; });
2766  std::vector<int64_t> divisors;
2767  int64_t range = 0;
2768  for (int i = 0; i < entries.size(); ++i) {
2769  const Entry& e = entries[i];
2770  gcd = MathUtil::GCD64(gcd, e.magnitude);
2771  max_error -= e.max_variation;
2772 
2773  // We regroup all term with the same coefficient into one.
2774  //
2775  // TODO(user): I am not sure there is no possible simplification across two
2776  // term with the same coeff, but it should be rare if it ever happens.
2777  range += e.max_variation / e.magnitude;
2778  if (i + 1 < entries.size() && e.magnitude == entries[i + 1].magnitude) {
2779  continue;
2780  }
2781  const int64_t saved_range = range;
2782  range = 0;
2783 
2784  if (e.magnitude > 1) {
2785  if ((!use_ub ||
2786  max_error <= PositiveRemainder(rhs_ub, IntegerValue(e.magnitude))) &&
2787  (!use_lb ||
2788  max_error <= PositiveRemainder(rhs_lb, IntegerValue(e.magnitude)))) {
2789  divisors.push_back(e.magnitude);
2790  }
2791  }
2792 
2793  bool simplify_lb = false;
2794  if (use_lb) {
2795  lb_feasible_.AddMultiples(e.magnitude, saved_range);
2796  lb_infeasible_.AddMultiples(e.magnitude, saved_range);
2797 
2798  // For a <= constraint, the max_feasible + error is still feasible.
2799  if (lb_feasible_.CurrentMax() + max_error <= lb_feasible_.Bound()) {
2800  simplify_lb = true;
2801  }
2802  // For a <= constraint describing the infeasible set, the max_infeasible +
2803  // error is still infeasible.
2804  if (lb_infeasible_.CurrentMax() + max_error <= lb_infeasible_.Bound()) {
2805  simplify_lb = true;
2806  }
2807  } else {
2808  simplify_lb = true;
2809  }
2810  bool simplify_ub = false;
2811  if (use_ub) {
2812  ub_feasible_.AddMultiples(e.magnitude, saved_range);
2813  ub_infeasible_.AddMultiples(e.magnitude, saved_range);
2814  if (ub_feasible_.CurrentMax() + max_error <= ub_feasible_.Bound()) {
2815  simplify_ub = true;
2816  }
2817  if (ub_infeasible_.CurrentMax() + max_error <= ub_infeasible_.Bound()) {
2818  simplify_ub = true;
2819  }
2820  } else {
2821  simplify_ub = true;
2822  }
2823 
2824  if (max_error == 0) break; // Last term.
2825  if (simplify_lb && simplify_ub) {
2826  // We have a simplification since the second part can be ignored.
2827  context_->UpdateRuleStats("linear: remove irrelevant part");
2828  LinearConstraintProto* mutable_linear = ct->mutable_linear();
2829  mutable_linear->clear_vars();
2830  mutable_linear->clear_coeffs();
2831  int64_t shift_lb = 0;
2832  int64_t shift_ub = 0;
2833  for (int j = 0; j <= i; ++j) {
2834  const int index = entries[j].index;
2835  const int64_t m = magnitudes[index];
2836  shift_lb += lbs[index] * m;
2837  shift_ub += ubs[index] * m;
2838  mutable_linear->add_vars(vars[index]);
2839  mutable_linear->add_coeffs(coeffs[index]);
2840  }
2841 
2842  // The constraint become:
2843  // sum ci (X - lb) <= rhs_ub
2844  // sum ci (ub - X) <= rhs_lb
2845  // sum ci ub - rhs_lb <= sum ci X <= rhs_ub + sum ci lb.
2846  const int64_t new_rhs_lb =
2847  use_lb ? shift_ub - lb_feasible_.CurrentMax() : shift_lb;
2848  const int64_t new_rhs_ub =
2849  use_ub ? shift_lb + ub_feasible_.CurrentMax() : shift_ub;
2850  if (new_rhs_lb > new_rhs_ub) {
2851  (void)MarkConstraintAsFalse(ct);
2852  context_->UpdateConstraintVariableUsage(c);
2853  return;
2854  }
2855  FillDomainInProto(Domain(new_rhs_lb, new_rhs_ub), mutable_linear);
2856  DivideLinearByGcd(ct);
2857  context_->UpdateConstraintVariableUsage(c);
2858  return;
2859  }
2860  }
2861 
2862  if (gcd > 1) {
2863  // This might happen as a result of extra reduction after we already tried
2864  // this reduction.
2865  if (DivideLinearByGcd(ct)) {
2866  context_->UpdateConstraintVariableUsage(c);
2867  }
2868  return;
2869  }
2870 
2871  // We didn't remove any irrelevant part, but we might be able to tighten
2872  // the constraint bound.
2873  if ((use_lb && lb_feasible_.CurrentMax() < lb_feasible_.Bound()) ||
2874  (use_ub && ub_feasible_.CurrentMax() < ub_feasible_.Bound())) {
2875  context_->UpdateRuleStats("linear: reduce rhs with DP");
2876  const int64_t new_rhs_lb =
2877  use_lb ? ub_sum - lb_feasible_.CurrentMax() : lb_sum;
2878  const int64_t new_rhs_ub =
2879  use_ub ? lb_sum + ub_feasible_.CurrentMax() : ub_sum;
2880  if (new_rhs_lb > new_rhs_ub) {
2881  (void)MarkConstraintAsFalse(ct);
2882  context_->UpdateConstraintVariableUsage(c);
2883  return;
2884  }
2885  FillDomainInProto(Domain(new_rhs_lb, new_rhs_ub), ct->mutable_linear());
2886  }
2887 
2888  // Limit the number of "divisor" we try for approximate gcd.
2889  if (divisors.size() > 3) divisors.resize(3);
2890  for (const int64_t divisor : divisors) {
2891  // Try the <= side first.
2892  int64_t new_ub;
2894  divisor, magnitudes, lbs, ubs, rhs.Max(), &new_ub)) {
2895  continue;
2896  }
2897 
2898  // The other side.
2899  int64_t minus_new_lb;
2900  for (int i = 0; i < lbs.size(); ++i) {
2901  std::swap(lbs[i], ubs[i]);
2902  lbs[i] = -lbs[i];
2903  ubs[i] = -ubs[i];
2904  }
2906  divisor, magnitudes, lbs, ubs, -rhs.Min(), &minus_new_lb)) {
2907  for (int i = 0; i < lbs.size(); ++i) {
2908  std::swap(lbs[i], ubs[i]);
2909  lbs[i] = -lbs[i];
2910  ubs[i] = -ubs[i];
2911  }
2912  continue;
2913  }
2914 
2915  // Rewrite the constraint !
2916  context_->UpdateRuleStats("linear: simplify using approximate gcd");
2917  LinearConstraintProto* mutable_linear = ct->mutable_linear();
2918  mutable_linear->clear_vars();
2919  mutable_linear->clear_coeffs();
2920  for (int i = 0; i < coeffs.size(); ++i) {
2921  const int64_t new_coeff = ClosestMultiple(coeffs[i], divisor) / divisor;
2922  if (new_coeff == 0) continue;
2923  mutable_linear->add_vars(vars[i]);
2924  mutable_linear->add_coeffs(new_coeff);
2925  }
2926  const Domain new_rhs = Domain(-minus_new_lb, new_ub);
2927  if (new_rhs.IsEmpty()) {
2928  (void)MarkConstraintAsFalse(ct);
2929  } else {
2930  FillDomainInProto(new_rhs, mutable_linear);
2931  }
2932  context_->UpdateConstraintVariableUsage(c);
2933  return;
2934  }
2935 }
2936 
2937 namespace {
2938 
2939 // In the equation terms + coeff * var_domain \included rhs, returns true if can
2940 // we always fix rhs to its min value for any value in terms. It is okay to
2941 // not be as generic as possible here.
2942 bool RhsCanBeFixedToMin(int64_t coeff, const Domain& var_domain,
2943  const Domain& terms, const Domain& rhs) {
2944  if (var_domain.NumIntervals() != 1) return false;
2945  if (std::abs(coeff) != 1) return false;
2946 
2947  // If for all values in terms, there is one value below rhs.Min(), then
2948  // because we add only one integer interval, if there is a feasible value, it
2949  // can be at rhs.Min().
2950  //
2951  // TODO(user): generalize to larger coeff magnitude if rhs is also a multiple
2952  // or if terms is a multiple.
2953  if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
2954  return true;
2955  }
2956  if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
2957  return true;
2958  }
2959  return false;
2960 }
2961 
2962 bool RhsCanBeFixedToMax(int64_t coeff, const Domain& var_domain,
2963  const Domain& terms, const Domain& rhs) {
2964  if (var_domain.NumIntervals() != 1) return false;
2965  if (std::abs(coeff) != 1) return false;
2966 
2967  if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
2968  return true;
2969  }
2970  if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
2971  return true;
2972  }
2973  return false;
2974 }
2975 
2976 int FixLiteralFromSet(const absl::flat_hash_set<int>& literals_at_true,
2977  LinearConstraintProto* linear) {
2978  int new_size = 0;
2979  int num_fixed = 0;
2980  const int num_terms = linear->vars().size();
2981  int64_t shift = 0;
2982  for (int i = 0; i < num_terms; ++i) {
2983  const int var = linear->vars(i);
2984  const int64_t coeff = linear->coeffs(i);
2985  if (literals_at_true.contains(var)) {
2986  // Var is at one.
2987  shift += coeff;
2988  ++num_fixed;
2989  } else if (!literals_at_true.contains(NegatedRef(var))) {
2990  linear->set_vars(new_size, var);
2991  linear->set_coeffs(new_size, coeff);
2992  ++new_size;
2993  } else {
2994  ++num_fixed;
2995  // Else the variable is at zero.
2996  }
2997  }
2998  linear->mutable_vars()->Truncate(new_size);
2999  linear->mutable_coeffs()->Truncate(new_size);
3000  if (shift != 0) {
3001  FillDomainInProto(ReadDomainFromProto(*linear).AdditionWith(Domain(-shift)),
3002  linear);
3003  }
3004  return num_fixed;
3005 }
3006 
3007 } // namespace
3008 
3009 // TODO(user): Similarly amo and bool_or intersection or amo and enforcement
3010 // literals list can be presolved.
3011 //
3012 // TODO(user): This is stronger than the fully included case. Avoid having
3013 // the second code?
3014 void CpModelPresolver::DetectAndProcessAtMostOneInLinear(
3015  int ct_index, ConstraintProto* ct, ActivityBoundHelper* helper) {
3016  if (ct->constraint_case() != ConstraintProto::kLinear) return;
3017  if (ct->linear().vars().size() <= 2) return;
3018 
3019  tmp_terms_.clear();
3020  temp_ct_.Clear();
3021  Domain non_boolean_domain(0);
3022  const int num_ct_terms = ct->linear().vars().size();
3023  int64_t min_magnitude = std::numeric_limits<int64_t>::max();
3024  int64_t max_magnitude = 0;
3025  for (int i = 0; i < num_ct_terms; ++i) {
3026  // TODO(user): Just do not use negative reference in linear!
3027  int ref = ct->linear().vars(i);
3028  int64_t coeff = ct->linear().coeffs(i);
3029  if (!RefIsPositive(ref)) {
3030  ref = NegatedRef(ref);
3031  coeff = -coeff;
3032  }
3033  if (context_->CanBeUsedAsLiteral(ref)) {
3034  tmp_terms_.push_back({ref, coeff});
3035  min_magnitude = std::min(min_magnitude, std::abs(coeff));
3036  max_magnitude = std::max(max_magnitude, std::abs(coeff));
3037  } else {
3038  non_boolean_domain =
3039  non_boolean_domain
3040  .AdditionWith(
3041  context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
3042  .RelaxIfTooComplex();
3043  temp_ct_.mutable_linear()->add_vars(ref);
3044  temp_ct_.mutable_linear()->add_coeffs(coeff);
3045  }
3046  }
3047 
3048  // Skip if there are no Booleans.
3049  if (tmp_terms_.empty()) return;
3050 
3051  // Detect encoded AMO.
3052  //
3053  // TODO(user): Support more coefficient strengthening cases.
3054  // For instance on neos-954925.pb.gz we have stuff like:
3055  // 20 * (AMO1 + AMO2) - [coeff in 48 to 53] >= -15
3056  // this is really AMO1 + AMO2 - 2 * AMO3 >= 0.
3057  // Maybe if we reify the AMO to exactly one, this is visible since large
3058  // AMO can be rewriten with single variable (1 - extra var in exactly one).
3059  const Domain rhs = ReadDomainFromProto(ct->linear());
3060  if (non_boolean_domain == Domain(0) && rhs.NumIntervals() == 1 &&
3061  min_magnitude < max_magnitude) {
3062  int64_t min_activity = 0;
3063  int64_t max_activity = 0;
3064  for (const auto [ref, coeff] : tmp_terms_) {
3065  if (coeff > 0) {
3066  max_activity += coeff;
3067  } else {
3068  min_activity += coeff;
3069  }
3070  }
3071  const int64_t transformed_rhs = rhs.Max() - min_activity;
3072  if (min_activity >= rhs.Min() && max_magnitude <= transformed_rhs) {
3073  std::vector<int> literals;
3074  for (const auto [ref, coeff] : tmp_terms_) {
3075  if (coeff + min_magnitude > transformed_rhs) continue;
3076  literals.push_back(coeff > 0 ? ref : NegatedRef(ref));
3077  }
3078  if (helper->IsAmo(literals)) {
3079  // We actually have an at-most-one in disguise.
3080  context_->UpdateRuleStats("linear + amo: detect hidden AMO");
3081  int64_t shift = 0;
3082  for (int i = 0; i < num_ct_terms; ++i) {
3083  CHECK(RefIsPositive(ct->linear().vars(i)));
3084  if (ct->linear().coeffs(i) > 0) {
3085  ct->mutable_linear()->set_coeffs(i, 1);
3086  } else {
3087  ct->mutable_linear()->set_coeffs(i, -1);
3088  shift -= 1;
3089  }
3090  }
3091  FillDomainInProto(Domain(shift, shift + 1), ct->mutable_linear());
3092  return;
3093  }
3094  }
3095  }
3096 
3097  // Get more precise activity estimate based on at most one and heuristics.
3098  const int64_t min_bool_activity =
3099  helper->ComputeMinActivity(tmp_terms_, &conditional_mins_);
3100  const int64_t max_bool_activity =
3101  helper->ComputeMaxActivity(tmp_terms_, &conditional_maxs_);
3102 
3103  // Detect trivially true/false constraint under these new bounds.
3104  // TODO(user): relax rhs if only one side is trivial.
3105  const Domain activity = non_boolean_domain.AdditionWith(
3106  Domain(min_bool_activity, max_bool_activity));
3107  if (activity.IntersectionWith(rhs).IsEmpty()) {
3108  // Note that this covers min_bool_activity > max_bool_activity.
3109  context_->UpdateRuleStats("linear + amo: infeasible linear constraint");
3110  (void)MarkConstraintAsFalse(ct);
3111  context_->UpdateConstraintVariableUsage(ct_index);
3112  return;
3113  } else if (activity.IsIncludedIn(rhs)) {
3114  context_->UpdateRuleStats("linear + amo: trivial linear constraint");
3115  ct->Clear();
3116  context_->UpdateConstraintVariableUsage(ct_index);
3117  return;
3118  }
3119 
3120  // Extract enforcement or fix literal.
3121  // TODO(user): Do not use domain fonction, can be slow.
3122  std::vector<int> new_enforcement;
3123  std::vector<int> must_be_true;
3124  for (int i = 0; i < tmp_terms_.size(); ++i) {
3125  const int ref = tmp_terms_[i].first;
3126 
3127  const Domain bool0(conditional_mins_[i][0], conditional_maxs_[i][0]);
3128  const Domain activity0 = bool0.AdditionWith(non_boolean_domain);
3129  if (activity0.IntersectionWith(rhs).IsEmpty()) {
3130  // Must be 1.
3131  must_be_true.push_back(ref);
3132  } else if (activity0.IsIncludedIn(rhs)) {
3133  // Trivial constraint on 0.
3134  new_enforcement.push_back(ref);
3135  }
3136 
3137  const Domain bool1(conditional_mins_[i][1], conditional_maxs_[i][1]);
3138  const Domain activity1 = bool1.AdditionWith(non_boolean_domain);
3139  if (activity1.IntersectionWith(rhs).IsEmpty()) {
3140  // Must be 0.
3141  must_be_true.push_back(NegatedRef(ref));
3142  } else if (activity1.IsIncludedIn(rhs)) {
3143  // Trivial constraint on 1.
3144  new_enforcement.push_back(NegatedRef(ref));
3145  }
3146  }
3147 
3148  // Note that both list can be non empty, if for instance we have small * X +
3149  // big * Y + ... <= rhs and amo(X, Y). We could see that Y can never be true
3150  // and if X is true, then the constraint could be trivial.
3151  //
3152  // So we fix things first if we can.
3153  if (ct->enforcement_literal().empty() && !must_be_true.empty()) {
3154  // Note that our logic to do more presolve iteration depends on the
3155  // number of rule applied, so it is important to count this correctly.
3156  context_->UpdateRuleStats("linear + amo: fixed literal",
3157  must_be_true.size());
3158  for (const int lit : must_be_true) {
3159  if (!context_->SetLiteralToTrue(lit)) return;
3160  }
3161  }
3162 
3163  if (!new_enforcement.empty()) {
3164  context_->UpdateRuleStats("linear + amo: extracted enforcement literal",
3165  new_enforcement.size());
3166  for (const int ref : new_enforcement) {
3167  ct->add_enforcement_literal(ref);
3168  }
3169  }
3170 
3171  if (!ct->enforcement_literal().empty()) {
3172  const int old_enf_size = ct->enforcement_literal().size();
3173  if (!helper->PresolveEnforcement(ct->linear().vars(), ct, &temp_set_)) {
3174  context_->UpdateRuleStats("linear + amo: infeasible enforcement");
3175  ct->Clear();
3176  context_->UpdateConstraintVariableUsage(ct_index);
3177  return;
3178  }
3179  if (ct->enforcement_literal().size() < old_enf_size) {
3180  context_->UpdateRuleStats("linear + amo: simplified enforcement list");
3181  context_->UpdateConstraintVariableUsage(ct_index);
3182  }
3183 
3184  for (const int lit : must_be_true) {
3185  if (temp_set_.contains(NegatedRef(lit))) {
3186  // A literal must be true but is incompatible with what the enforcement
3187  // implies. The constraint must be false!
3188  context_->UpdateRuleStats(
3189  "linear + amo: advanced infeasible linear constraint");
3190  (void)MarkConstraintAsFalse(ct);
3191  context_->UpdateConstraintVariableUsage(ct_index);
3192  return;
3193  }
3194  }
3195 
3196  // TODO(user): do that in more cases?
3197  if (ct->enforcement_literal().size() == 1 && !must_be_true.empty()) {
3198  // Add implication, and remove literal from the constraint in this case.
3199  // To remove them, we just add them to temp_set_ and FixLiteralFromSet()
3200  // will take care of it.
3201  context_->UpdateRuleStats("linear + amo: added implications");
3202  ConstraintProto* new_ct = context_->working_model->add_constraints();
3203  *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
3204  for (const int lit : must_be_true) {
3205  new_ct->mutable_bool_and()->add_literals(lit);
3206  temp_set_.insert(lit);
3207  }
3209  }
3210 
3211  const int num_fixed = FixLiteralFromSet(temp_set_, ct->mutable_linear());
3212  if (num_fixed > new_enforcement.size()) {
3213  context_->UpdateRuleStats(
3214  "linear + amo: fixed literal implied by enforcement");
3215  }
3216  if (num_fixed > 0) {
3217  context_->UpdateConstraintVariableUsage(ct_index);
3218  }
3219  }
3220 
3221  // Finally, we can use the new bound to propagate other terms.
3222  if (ct->enforcement_literal().empty() && !temp_ct_.linear().vars().empty()) {
3224  rhs.AdditionWith(
3225  Domain(min_bool_activity, max_bool_activity).Negation()),
3226  temp_ct_.mutable_linear());
3227  PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_);
3228  }
3229 }
3230 
3231 bool CpModelPresolver::PropagateDomainsInLinear(int ct_index,
3232  ConstraintProto* ct) {
3233  if (ct->constraint_case() != ConstraintProto::kLinear) return false;
3234  if (context_->ModelIsUnsat()) return false;
3235 
3236  // Compute the implied rhs bounds from the variable ones.
3237  auto& term_domains = context_->tmp_term_domains;
3238  auto& left_domains = context_->tmp_left_domains;
3239  const int num_vars = ct->linear().vars_size();
3240  term_domains.resize(num_vars + 1);
3241  left_domains.resize(num_vars + 1);
3242  left_domains[0] = Domain(0);
3243  for (int i = 0; i < num_vars; ++i) {
3244  const int var = ct->linear().vars(i);
3245  const int64_t coeff = ct->linear().coeffs(i);
3246  DCHECK(RefIsPositive(var));
3247  term_domains[i] = context_->DomainOf(var).MultiplicationBy(coeff);
3248  left_domains[i + 1] =
3249  left_domains[i].AdditionWith(term_domains[i]).RelaxIfTooComplex();
3250  }
3251  const Domain& implied_rhs = left_domains[num_vars];
3252 
3253  // Abort if trivial.
3254  const Domain old_rhs = ReadDomainFromProto(ct->linear());
3255  if (implied_rhs.IsIncludedIn(old_rhs)) {
3256  if (ct_index != -1) context_->UpdateRuleStats("linear: always true");
3257  return RemoveConstraint(ct);
3258  }
3259 
3260  // Incorporate the implied rhs information.
3261  Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
3262  if (rhs.IsEmpty()) {
3263  context_->UpdateRuleStats("linear: infeasible");
3264  return MarkConstraintAsFalse(ct);
3265  }
3266  if (rhs != old_rhs) {
3267  if (ct_index != -1) context_->UpdateRuleStats("linear: simplified rhs");
3268  }
3269  FillDomainInProto(rhs, ct->mutable_linear());
3270 
3271  // Propagate the variable bounds.
3272  if (ct->enforcement_literal().size() > 1) return false;
3273 
3274  bool new_bounds = false;
3275  bool recanonicalize = false;
3276  Domain negated_rhs = rhs.Negation();
3277  Domain right_domain(0);
3278  Domain new_domain;
3279  Domain implied_term_domain;
3280  term_domains[num_vars] = Domain(0);
3281  for (int i = num_vars - 1; i >= 0; --i) {
3282  const int var = ct->linear().vars(i);
3283  const int64_t var_coeff = ct->linear().coeffs(i);
3284  right_domain =
3285  right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
3286  implied_term_domain = left_domains[i].AdditionWith(right_domain);
3287  new_domain = implied_term_domain.AdditionWith(negated_rhs)
3288  .InverseMultiplicationBy(-var_coeff);
3289 
3290  if (ct->enforcement_literal().empty()) {
3291  // Push the new domain.
3292  if (!context_->IntersectDomainWith(var, new_domain, &new_bounds)) {
3293  return true;
3294  }
3295  } else if (ct->enforcement_literal().size() == 1) {
3296  // We cannot push the new domain, but we can add some deduction.
3297  CHECK(RefIsPositive(var));
3298  if (!context_->DomainOfVarIsIncludedIn(var, new_domain)) {
3299  context_->deductions.AddDeduction(ct->enforcement_literal(0), var,
3300  new_domain);
3301  }
3302  }
3303 
3304  if (context_->IsFixed(var)) {
3305  // This will make sure we remove that fixed variable from the constraint.
3306  recanonicalize = true;
3307  continue;
3308  }
3309 
3310  // The other transformations below require a non-reified constraint.
3311  if (ct_index == -1) continue;
3312  if (!ct->enforcement_literal().empty()) continue;
3313 
3314  // Given a variable that only appear in one constraint and in the
3315  // objective, for any feasible solution, it will be always better to move
3316  // this singleton variable as much as possible towards its good objective
3317  // direction. Sometime, we can detect that we will always be able to
3318  // do this until the only constraint of this singleton variable is tight.
3319  //
3320  // When this happens, we can make the constraint an equality. Note that it
3321  // might not always be good to restrict constraint like this, but in this
3322  // case, the RemoveSingletonInLinear() code should be able to remove this
3323  // variable altogether.
3324  if (rhs.Min() != rhs.Max() &&
3326  const int64_t obj_coeff = context_->ObjectiveMap().at(var);
3327  const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
3328  bool fixed = false;
3329  if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->DomainOf(var),
3330  implied_term_domain, rhs)) {
3331  rhs = Domain(rhs.Min());
3332  fixed = true;
3333  }
3334  if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->DomainOf(var),
3335  implied_term_domain, rhs)) {
3336  rhs = Domain(rhs.Max());
3337  fixed = true;
3338  }
3339  if (fixed) {
3340  context_->UpdateRuleStats("linear: tightened into equality");
3341  FillDomainInProto(rhs, ct->mutable_linear());
3342  negated_rhs = rhs.Negation();
3343 
3344  // Restart the loop.
3345  i = num_vars;
3346  right_domain = Domain(0);
3347  continue;
3348  }
3349  }
3350 
3351  // Can we perform some substitution?
3352  //
3353  // TODO(user): there is no guarantee we will not miss some since we might
3354  // not reprocess a constraint once other have been deleted.
3355 
3356  // Skip affine constraint. It is more efficient to substitute them lazily
3357  // when we process other constraints. Note that if we relax the fact that
3358  // we substitute only equalities, we can deal with inequality of size 2
3359  // here.
3360  if (ct->linear().vars().size() <= 2) continue;
3361 
3362  // TODO(user): We actually do not need a strict equality when
3363  // keep_all_feasible_solutions is false, but that simplifies things as the
3364  // SubstituteVariable() function cannot fail this way.
3365  if (rhs.Min() != rhs.Max()) continue;
3366 
3367  // Only consider "implied free" variables. Note that the coefficient of
3368  // magnitude 1 is important otherwise we can't easily remove the
3369  // constraint since the fact that the sum of the other terms must be a
3370  // multiple of coeff will not be enforced anymore.
3371  if (context_->DomainOf(var) != new_domain) continue;
3372  if (std::abs(var_coeff) != 1) continue;
3373  if (context_->params().presolve_substitution_level() <= 0) continue;
3374 
3375  // NOTE: The mapping doesn't allow us to remove a variable if
3376  // 'keep_all_feasible_solutions' is true.
3377  if (context_->keep_all_feasible_solutions) continue;
3378 
3379  bool is_in_objective = false;
3380  if (context_->VarToConstraints(var).contains(-1)) {
3381  is_in_objective = true;
3382  DCHECK(context_->ObjectiveMap().contains(var));
3383  }
3384 
3385  // Only consider low degree columns.
3386  int col_size = context_->VarToConstraints(var).size();
3387  if (is_in_objective) col_size--;
3388  const int row_size = ct->linear().vars_size();
3389 
3390  // This is actually an upper bound on the number of entries added since
3391  // some of them might already be present.
3392  const int num_entries_added = (row_size - 1) * (col_size - 1);
3393  const int num_entries_removed = col_size + row_size - 1;
3394 
3395  if (num_entries_added > num_entries_removed) {
3396  continue;
3397  }
3398 
3399  // Check pre-conditions on all the constraints in which this variable
3400  // appear. Basically they must all be linear.
3401  std::vector<int> others;
3402  bool abort = false;
3403  for (const int c : context_->VarToConstraints(var)) {
3404  if (c == kObjectiveConstraint) continue;
3405  if (c == kAffineRelationConstraint) {
3406  abort = true;
3407  break;
3408  }
3409  if (c == ct_index) continue;
3410  if (context_->working_model->constraints(c).constraint_case() !=
3411  ConstraintProto::kLinear) {
3412  abort = true;
3413  break;
3414  }
3415  for (const int ref :
3416  context_->working_model->constraints(c).enforcement_literal()) {
3417  if (PositiveRef(ref) == var) {
3418  abort = true;
3419  break;
3420  }
3421  }
3422  others.push_back(c);
3423  }
3424  if (abort) continue;
3425 
3426  // Do the actual substitution.
3427  for (const int c : others) {
3428  // TODO(user): In some corner cases, this might create integer overflow
3429  // issues. The danger is limited since the range of the linear
3430  // expression used in the definition do not exceed the domain of the
3431  // variable we substitute.
3432  const bool ok = SubstituteVariable(
3433  var, var_coeff, *ct, context_->working_model->mutable_constraints(c));
3434  if (!ok) {
3435  // This can happen if the constraint was not canonicalized and the
3436  // variable is actually not there (we have var - var for instance).
3437  CanonicalizeLinear(context_->working_model->mutable_constraints(c));
3438  }
3439 
3440  // TODO(user): We should re-enqueue these constraints for presolve.
3441  context_->UpdateConstraintVariableUsage(c);
3442  }
3443 
3444  // Substitute in objective.
3445  // This can only fail in corner cases.
3446  if (is_in_objective &&
3447  !context_->SubstituteVariableInObjective(var, var_coeff, *ct)) {
3448  continue;
3449  }
3450 
3451  context_->UpdateRuleStats(
3452  absl::StrCat("linear: variable substitution ", others.size()));
3453 
3454  // The variable now only appear in its definition and we can remove it
3455  // because it was implied free.
3456  //
3457  // Tricky: If the linear constraint contains other variables that are only
3458  // used here, then the postsolve needs more info. We do need to indicate
3459  // that whatever the value of those other variables, we will have a way to
3460  // assign var. We do that by putting it fist.
3461  CHECK_EQ(context_->VarToConstraints(var).size(), 1);
3462  context_->MarkVariableAsRemoved(var);
3463  const int ct_index = context_->mapping_model->constraints().size();
3464  *context_->mapping_model->add_constraints() = *ct;
3465  LinearConstraintProto* mapping_linear_ct =
3466  context_->mapping_model->mutable_constraints(ct_index)
3467  ->mutable_linear();
3468  std::swap(mapping_linear_ct->mutable_vars()->at(0),
3469  mapping_linear_ct->mutable_vars()->at(i));
3470  std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
3471  mapping_linear_ct->mutable_coeffs()->at(i));
3472  return RemoveConstraint(ct);
3473  }
3474 
3475  // special case.
3476  if (ct_index == -1) {
3477  if (new_bounds) {
3478  context_->UpdateRuleStats(
3479  "linear: reduced variable domains in derived constraint");
3480  }
3481  return false;
3482  }
3483 
3484  if (new_bounds) {
3485  context_->UpdateRuleStats("linear: reduced variable domains");
3486  }
3487  if (recanonicalize) return CanonicalizeLinear(ct);
3488  return false;
3489 }
3490 
3491 // The constraint from its lower value is sum positive_coeff * X <= rhs.
3492 // If from_lower_bound is false, then it is the constraint from its upper value.
3493 void CpModelPresolver::LowerThanCoeffStrengthening(bool from_lower_bound,
3494  int64_t min_magnitude,
3495  int64_t rhs,
3496  ConstraintProto* ct) {
3497  const LinearConstraintProto& arg = ct->linear();
3498  const int64_t second_threshold = rhs - min_magnitude;
3499  const int num_vars = arg.vars_size();
3500 
3501  // Special case:
3502  // - The terms above rhs must be fixed to zero.
3503  // - The terms in (second_threshold, rhs] can be fixed to rhs as
3504  // they will force all other terms to zero if not at zero themselves.
3505  // - If what is left can be simplified to a single coefficient, we can
3506  // put the constraint into a special form.
3507  //
3508  // TODO(user): More generally, if we ignore term that set everything else to
3509  // zero, we can preprocess the constraint left and then add them back. So we
3510  // can do all our other reduction like normal GCD or mor advanced ones like DP
3511  // based or approximate GCD.
3512  if (min_magnitude <= second_threshold) {
3513  // Compute max_magnitude for the term <= second_threshold.
3514  int64_t max_magnitude_left = 0;
3515  int64_t max_activity_left = 0;
3516  int64_t activity_when_coeff_are_one = 0;
3517  int64_t gcd = 0;
3518  for (int i = 0; i < num_vars; ++i) {
3519  const int64_t magnitude = std::abs(arg.coeffs(i));
3520  if (magnitude <= second_threshold) {
3521  gcd = MathUtil::GCD64(gcd, magnitude);
3522  max_magnitude_left = std::max(max_magnitude_left, magnitude);
3523  const int64_t bound_diff =
3524  context_->MaxOf(arg.vars(i)) - context_->MinOf(arg.vars(i));
3525  activity_when_coeff_are_one += magnitude;
3526  max_activity_left += magnitude * bound_diff;
3527  }
3528  }
3529  CHECK_GT(min_magnitude, 0);
3530  CHECK_LE(min_magnitude, max_magnitude_left);
3531 
3532  // Not considering the variable that set everyone at zero when true:
3533  int64_t new_rhs = 0;
3534  bool set_all_to_one = false;
3535  if (max_activity_left <= rhs) {
3536  // We are left with a trivial constraint.
3537  context_->UpdateRuleStats("linear with partial amo: trivial");
3538  new_rhs = activity_when_coeff_are_one;
3539  set_all_to_one = true;
3540  } else if (rhs / min_magnitude == rhs / max_magnitude_left) {
3541  // We are left with a sum <= new_rhs constraint.
3542  context_->UpdateRuleStats("linear with partial amo: constant coeff");
3543  new_rhs = rhs / min_magnitude;
3544  set_all_to_one = true;
3545  } else if (gcd > 1) {
3546  // We are left with a constraint that can be simplified by gcd.
3547  context_->UpdateRuleStats("linear with partial amo: gcd");
3548  new_rhs = rhs / gcd;
3549  }
3550 
3551  if (new_rhs > 0) {
3552  int64_t rhs_offset = 0;
3553  for (int i = 0; i < num_vars; ++i) {
3554  const int ref = arg.vars(i);
3555  const int64_t coeff = from_lower_bound ? arg.coeffs(i) : -arg.coeffs(i);
3556 
3557  int64_t new_coeff;
3558  const int64_t magnitude = std::abs(coeff);
3559  if (magnitude > rhs) {
3560  new_coeff = new_rhs + 1;
3561  } else if (magnitude > second_threshold) {
3562  new_coeff = new_rhs;
3563  } else {
3564  new_coeff = set_all_to_one ? 1 : magnitude / gcd;
3565  }
3566 
3567  // In the transformed domain we will always have
3568  // magnitude * (var - lb) or magnitude * (ub - var)
3569  if (coeff > 0) {
3570  ct->mutable_linear()->set_coeffs(i, new_coeff);
3571  rhs_offset += new_coeff * context_->MinOf(ref);
3572  } else {
3573  ct->mutable_linear()->set_coeffs(i, -new_coeff);
3574  rhs_offset -= new_coeff * context_->MaxOf(ref);
3575  }
3576  }
3577  FillDomainInProto(Domain(rhs_offset, new_rhs + rhs_offset),
3578  ct->mutable_linear());
3579  return;
3580  }
3581  }
3582 
3583  int64_t rhs_offset = 0;
3584  for (int i = 0; i < num_vars; ++i) {
3585  int ref = arg.vars(i);
3586  int64_t coeff = arg.coeffs(i);
3587  if (coeff < 0) {
3588  ref = NegatedRef(ref);
3589  coeff = -coeff;
3590  }
3591 
3592  if (coeff > rhs) {
3593  if (ct->enforcement_literal().empty()) {
3594  // Shifted variable must be zero.
3595  context_->UpdateRuleStats("linear: fix variable to its bound.");
3596  CHECK(context_->IntersectDomainWith(
3597  ref, Domain(from_lower_bound ? context_->MinOf(ref)
3598  : context_->MaxOf(ref))));
3599  }
3600 
3601  // TODO(user): What to do with the coeff if there is enforcement?
3602  continue;
3603  }
3604  if (coeff > second_threshold && coeff < rhs) {
3605  context_->UpdateRuleStats(
3606  "linear: coefficient strengthening by increasing it.");
3607  if (from_lower_bound) {
3608  // coeff * (X - LB + LB) -> rhs * (X - LB) + coeff * LB
3609  rhs_offset -= (coeff - rhs) * context_->MinOf(ref);
3610  } else {
3611  // coeff * (X - UB + UB) -> rhs * (X - UB) + coeff * UB
3612  rhs_offset -= (coeff - rhs) * context_->MaxOf(ref);
3613  }
3614  ct->mutable_linear()->set_coeffs(i, arg.coeffs(i) > 0 ? rhs : -rhs);
3615  }
3616  }
3617  if (rhs_offset != 0) {
3618  FillDomainInProto(ReadDomainFromProto(arg).AdditionWith(Domain(rhs_offset)),
3619  ct->mutable_linear());
3620  }
3621 }
3622 
3623 // Identify Boolean variable that makes the constraint always true when set to
3624 // true or false. Moves such literal to the constraint enforcement literals
3625 // list.
3626 //
3627 // We also generalize this to integer variable at one of their bound.
3628 //
3629 // This operation is similar to coefficient strengthening in the MIP world.
3630 void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
3631  int ct_index, ConstraintProto* ct) {
3632  if (ct->constraint_case() != ConstraintProto::kLinear) return;
3633  if (context_->ModelIsUnsat()) return;
3634 
3635  const LinearConstraintProto& arg = ct->linear();
3636  const int num_vars = arg.vars_size();
3637 
3638  // No need to process size one constraints, they will be presolved separately.
3639  // We also do not want to split them in two.
3640  if (num_vars <= 1) return;
3641 
3642  int64_t min_sum = 0;
3643  int64_t max_sum = 0;
3644  int64_t max_coeff_magnitude = 0;
3645  int64_t min_coeff_magnitude = std::numeric_limits<int64_t>::max();
3646  for (int i = 0; i < num_vars; ++i) {
3647  const int ref = arg.vars(i);
3648  const int64_t coeff = arg.coeffs(i);
3649  const int64_t term_a = coeff * context_->MinOf(ref);
3650  const int64_t term_b = coeff * context_->MaxOf(ref);
3651  max_coeff_magnitude = std::max(max_coeff_magnitude, std::abs(coeff));
3652  min_coeff_magnitude = std::min(min_coeff_magnitude, std::abs(coeff));
3653  min_sum += std::min(term_a, term_b);
3654  max_sum += std::max(term_a, term_b);
3655  }
3656  if (max_coeff_magnitude == 1) return;
3657 
3658  // We can only extract enforcement literals if the maximum coefficient
3659  // magnitude is large enough. Note that we handle complex domain.
3660  //
3661  // TODO(user): Depending on how we split below, the threshold are not the
3662  // same. This is maybe not too important, we just don't split as often as we
3663  // could, but it is still unclear if splitting is good.
3664  const auto& domain = ct->linear().domain();
3665  const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
3666  const int64_t lb_threshold = max_sum - domain[1];
3667  if (max_coeff_magnitude + min_coeff_magnitude <
3668  std::max(ub_threshold, lb_threshold)) {
3669  // We also have other kind of coefficient strengthening.
3670  // In something like 3x + 5y <= 6, the coefficient 5 can be changed to 6.
3671  // And in 5x + 12y <= 12, the coeff 5 can be changed to 6 (not sure how to
3672  // generalize this one).
3673  if (domain.size() == 2 && min_coeff_magnitude > 1 &&
3674  min_coeff_magnitude < max_coeff_magnitude) {
3675  const int64_t rhs_min = domain[0];
3676  const int64_t rhs_max = domain[1];
3677  if (min_sum >= rhs_min &&
3678  max_coeff_magnitude + min_coeff_magnitude > rhs_max - min_sum) {
3679  LowerThanCoeffStrengthening(/*from_lower_bound=*/true,
3680  min_coeff_magnitude, rhs_max - min_sum, ct);
3681  return;
3682  }
3683  if (max_sum <= rhs_max &&
3684  max_coeff_magnitude + min_coeff_magnitude > max_sum - rhs_min) {
3685  LowerThanCoeffStrengthening(/*from_lower_bound=*/false,
3686  min_coeff_magnitude, max_sum - rhs_min, ct);
3687  return;
3688  }
3689  }
3690  }
3691 
3692  // We need the constraint to be only bounded on one side in order to extract
3693  // enforcement literal.
3694  //
3695  // If it is boxed and we know that some coefficient are big enough (see test
3696  // above), then we split the constraint in two. That might not seems always
3697  // good, but for the CP propagation engine, we don't loose anything by doing
3698  // so, and for the LP we will regroup the constraints if they still have the
3699  // exact same coeff after the presolve.
3700  //
3701  // TODO(user): Creating two new constraints and removing the current one might
3702  // not be the most efficient, but it simplify the presolve code by not having
3703  // to do anything special to trigger a new presolving of these constraints.
3704  // Try to improve if this becomes a problem.
3705  const Domain rhs_domain = ReadDomainFromProto(ct->linear());
3706  const bool lower_bounded = min_sum < rhs_domain.Min();
3707  const bool upper_bounded = max_sum > rhs_domain.Max();
3708  if (!lower_bounded && !upper_bounded) return;
3709  if (lower_bounded && upper_bounded) {
3710  // We disable this for now.
3711  if (true) return;
3712 
3713  // Lets not split except if we extract enforcement.
3714  if (max_coeff_magnitude < std::max(ub_threshold, lb_threshold)) return;
3715 
3716  context_->UpdateRuleStats("linear: split boxed constraint");
3717  ConstraintProto* new_ct1 = context_->working_model->add_constraints();
3718  *new_ct1 = *ct;
3719  if (!ct->name().empty()) {
3720  new_ct1->set_name(absl::StrCat(ct->name(), " (part 1)"));
3721  }
3722  FillDomainInProto(Domain(min_sum, rhs_domain.Max()),
3723  new_ct1->mutable_linear());
3724 
3725  ConstraintProto* new_ct2 = context_->working_model->add_constraints();
3726  *new_ct2 = *ct;
3727  if (!ct->name().empty()) {
3728  new_ct2->set_name(absl::StrCat(ct->name(), " (part 2)"));
3729  }
3730  FillDomainInProto(rhs_domain.UnionWith(Domain(rhs_domain.Max(), max_sum)),
3731  new_ct2->mutable_linear());
3732 
3734  ct->Clear();
3735  context_->UpdateConstraintVariableUsage(ct_index);
3736  return;
3737  }
3738 
3739  // Any coefficient greater than this will cause the constraint to be trivially
3740  // satisfied when the variable move away from its bound. Note that as we
3741  // remove coefficient, the threshold do not change!
3742  const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
3743 
3744  // All coeffs in [second_threshold, threshold) can be reduced to
3745  // second_threshold.
3746  //
3747  // TODO(user): If 2 * min_coeff_magnitude >= bound, then the constraint can
3748  // be completely rewriten to 2 * (enforcement_part) + sum var >= 2 which is
3749  // what happen eventually when bound is even, but not if it is odd currently.
3750  int64_t second_threshold = std::max(CeilOfRatio(threshold, int64_t{2}),
3751  threshold - min_coeff_magnitude);
3752 
3753  // Tricky: The second threshold only work if the domain is simple. If the
3754  // domain has holes, changing the coefficient might change whether the
3755  // variable can be at one or not by herself.
3756  //
3757  // TODO(user): We could still reduce it to the smaller value with same
3758  // feasibility.
3759  if (rhs_domain.NumIntervals() > 1) {
3760  second_threshold = threshold; // Disable.
3761  }
3762 
3763  // Do we only extract Booleans?
3764  //
3765  // Note that for now the default is false, and also there are problem calling
3766  // GetOrCreateVarValueEncoding() after expansion because we might have removed
3767  // the variable used in the encoding.
3768  const bool only_extract_booleans =
3769  !context_->params().presolve_extract_integer_enforcement() ||
3770  context_->ModelIsExpanded();
3771 
3772  // To avoid a quadratic loop, we will rewrite the linear expression at the
3773  // same time as we extract enforcement literals.
3774  int new_size = 0;
3775  int64_t rhs_offset = 0;
3776  bool some_integer_encoding_were_extracted = false;
3777  LinearConstraintProto* mutable_arg = ct->mutable_linear();
3778  for (int i = 0; i < arg.vars_size(); ++i) {
3779  int ref = arg.vars(i);
3780  int64_t coeff = arg.coeffs(i);
3781  if (coeff < 0) {
3782  ref = NegatedRef(ref);
3783  coeff = -coeff;
3784  }
3785 
3786  // TODO(user): If the encoding Boolean already exist, we could extract
3787  // the non-Boolean enforcement term.
3788  const bool is_boolean = context_->CanBeUsedAsLiteral(ref);
3789  if (context_->IsFixed(ref) || coeff < threshold ||
3790  (only_extract_booleans && !is_boolean)) {
3791  mutable_arg->set_vars(new_size, mutable_arg->vars(i));
3792 
3793  int64_t new_magnitude = std::abs(arg.coeffs(i));
3794  if (coeff > threshold) {
3795  // We keep this term but reduces its coeff.
3796  // This is only for the case where only_extract_booleans == true.
3797  new_magnitude = threshold;
3798  context_->UpdateRuleStats("linear: coefficient strenghtening.");
3799  } else if (coeff > second_threshold && coeff < threshold) {
3800  // This cover the special case where one big + on small is enough
3801  // to satisfy the constraint, we can reduce the big.
3802  new_magnitude = second_threshold;
3803  context_->UpdateRuleStats(
3804  "linear: advanced coefficient strenghtening.");
3805  }
3806  if (coeff != new_magnitude) {
3807  if (lower_bounded) {
3808  // coeff * (X - LB + LB) -> new_magnitude * (X - LB) + coeff * LB
3809  rhs_offset -= (coeff - new_magnitude) * context_->MinOf(ref);
3810  } else {
3811  // coeff * (X - UB + UB) -> new_magnitude * (X - UB) + coeff * UB
3812  rhs_offset -= (coeff - new_magnitude) * context_->MaxOf(ref);
3813  }
3814  }
3815 
3816  mutable_arg->set_coeffs(
3817  new_size, arg.coeffs(i) > 0 ? new_magnitude : -new_magnitude);
3818  ++new_size;
3819  continue;
3820  }
3821 
3822  if (is_boolean) {
3823  context_->UpdateRuleStats("linear: extracted enforcement literal");
3824  } else {
3825  some_integer_encoding_were_extracted = true;
3826  context_->UpdateRuleStats(
3827  "linear: extracted integer enforcement literal");
3828  }
3829  if (lower_bounded) {
3830  ct->add_enforcement_literal(is_boolean
3831  ? NegatedRef(ref)
3832  : context_->GetOrCreateVarValueEncoding(
3833  ref, context_->MinOf(ref)));
3834  rhs_offset -= coeff * context_->MinOf(ref);
3835  } else {
3836  ct->add_enforcement_literal(is_boolean
3837  ? ref
3838  : context_->GetOrCreateVarValueEncoding(
3839  ref, context_->MaxOf(ref)));
3840  rhs_offset -= coeff * context_->MaxOf(ref);
3841  }
3842  }
3843  mutable_arg->mutable_vars()->Truncate(new_size);
3844  mutable_arg->mutable_coeffs()->Truncate(new_size);
3845  FillDomainInProto(rhs_domain.AdditionWith(Domain(rhs_offset)), mutable_arg);
3846  if (some_integer_encoding_were_extracted || new_size == 1) {
3847  context_->UpdateConstraintVariableUsage(ct_index);
3849  }
3850 }
3851 
3852 void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto* ct) {
3853  if (context_->ModelIsUnsat()) return;
3854  if (HasEnforcementLiteral(*ct)) return;
3855  const Domain rhs = ReadDomainFromProto(ct->linear());
3856 
3857  const LinearConstraintProto& arg = ct->linear();
3858  const int num_vars = arg.vars_size();
3859  int64_t min_sum = 0;
3860  int64_t max_sum = 0;
3861  for (int i = 0; i < num_vars; ++i) {
3862  const int ref = arg.vars(i);
3863  const int64_t coeff = arg.coeffs(i);
3864  const int64_t term_a = coeff * context_->MinOf(ref);
3865  const int64_t term_b = coeff * context_->MaxOf(ref);
3866  min_sum += std::min(term_a, term_b);
3867  max_sum += std::max(term_a, term_b);
3868  }
3869  for (const int type : {0, 1}) {
3870  std::vector<int> at_most_one;
3871  for (int i = 0; i < num_vars; ++i) {
3872  const int ref = arg.vars(i);
3873  const int64_t coeff = arg.coeffs(i);
3874  if (context_->MinOf(ref) != 0) continue;
3875  if (context_->MaxOf(ref) != 1) continue;
3876 
3877  if (type == 0) {
3878  // TODO(user): we could add one more Boolean with a lower coeff as long
3879  // as we have lower_coeff + min_of_other_coeff > rhs.Max().
3880  if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
3881  at_most_one.push_back(coeff > 0 ? ref : NegatedRef(ref));
3882  }
3883  } else {
3884  if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
3885  at_most_one.push_back(coeff > 0 ? NegatedRef(ref) : ref);
3886  }
3887  }
3888  }
3889  if (at_most_one.size() > 1) {
3890  if (type == 0) {
3891  context_->UpdateRuleStats("linear: extracted at most one (max).");
3892  } else {
3893  context_->UpdateRuleStats("linear: extracted at most one (min).");
3894  }
3895  ConstraintProto* new_ct = context_->working_model->add_constraints();
3896  new_ct->set_name(ct->name());
3897  for (const int ref : at_most_one) {
3898  new_ct->mutable_at_most_one()->add_literals(ref);
3899  }
3901  }
3902  }
3903 }
3904 
3905 // Convert some linear constraint involving only Booleans to their Boolean
3906 // form.
3907 bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto* ct) {
3908  if (ct->constraint_case() != ConstraintProto::kLinear) return false;
3909  if (context_->ModelIsUnsat()) return false;
3910 
3911  const LinearConstraintProto& arg = ct->linear();
3912  const int num_vars = arg.vars_size();
3913  int64_t min_coeff = std::numeric_limits<int64_t>::max();
3914  int64_t max_coeff = 0;
3915  int64_t min_sum = 0;
3916  int64_t max_sum = 0;
3917  for (int i = 0; i < num_vars; ++i) {
3918  // We assume we already ran PresolveLinear().
3919  const int var = arg.vars(i);
3920  const int64_t coeff = arg.coeffs(i);
3921  CHECK(RefIsPositive(var));
3922  CHECK_NE(coeff, 0);
3923  if (context_->MinOf(var) != 0) return false;
3924  if (context_->MaxOf(var) != 1) return false;
3925 
3926  if (coeff > 0) {
3927  max_sum += coeff;
3928  min_coeff = std::min(min_coeff, coeff);
3929  max_coeff = std::max(max_coeff, coeff);
3930  } else {
3931  // We replace the Boolean ref, by a ref to its negation (1 - x).
3932  min_sum += coeff;
3933  min_coeff = std::min(min_coeff, -coeff);
3934  max_coeff = std::max(max_coeff, -coeff);
3935  }
3936  }
3937  CHECK_LE(min_coeff, max_coeff);
3938 
3939  // Detect trivially true/false constraints. Note that this is not necessarily
3940  // detected by PresolveLinear(). We do that here because we assume below
3941  // that this cannot happen.
3942  //
3943  // TODO(user): this could be generalized to constraint not containing only
3944  // Booleans.
3945  const Domain rhs_domain = ReadDomainFromProto(arg);
3946  if ((!rhs_domain.Contains(min_sum) &&
3947  min_sum + min_coeff > rhs_domain.Max()) ||
3948  (!rhs_domain.Contains(max_sum) &&
3949  max_sum - min_coeff < rhs_domain.Min())) {
3950  context_->UpdateRuleStats("linear: all booleans and trivially false");
3951  return MarkConstraintAsFalse(ct);
3952  }
3953  if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
3954  context_->UpdateRuleStats("linear: all booleans and trivially true");
3955  return RemoveConstraint(ct);
3956  }
3957 
3958  // Detect clauses, reified ands, at_most_one.
3959  //
3960  // TODO(user): split a == 1 constraint or similar into a clause and an at
3961  // most one constraint?
3962  DCHECK(!rhs_domain.IsEmpty());
3963  if (min_sum + min_coeff > rhs_domain.Max()) {
3964  // All Boolean are false if the reified literal is true.
3965  context_->UpdateRuleStats("linear: negative reified and");
3966  const auto copy = arg;
3967  ct->mutable_bool_and()->clear_literals();
3968  for (int i = 0; i < num_vars; ++i) {
3969  ct->mutable_bool_and()->add_literals(
3970  copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
3971  }
3972  PresolveBoolAnd(ct);
3973  return true;
3974  } else if (max_sum - min_coeff < rhs_domain.Min()) {
3975  // All Boolean are true if the reified literal is true.
3976  context_->UpdateRuleStats("linear: positive reified and");
3977  const auto copy = arg;
3978  ct->mutable_bool_and()->clear_literals();
3979  for (int i = 0; i < num_vars; ++i) {
3980  ct->mutable_bool_and()->add_literals(
3981  copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
3982  }
3983  PresolveBoolAnd(ct);
3984  return true;
3985  } else if (min_sum + min_coeff >= rhs_domain.Min() &&
3986  rhs_domain.front().end >= max_sum) {
3987  // At least one Boolean is true.
3988  context_->UpdateRuleStats("linear: positive clause");
3989  const auto copy = arg;
3990  ct->mutable_bool_or()->clear_literals();
3991  for (int i = 0; i < num_vars; ++i) {
3992  ct->mutable_bool_or()->add_literals(
3993  copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
3994  }
3995  PresolveBoolOr(ct);
3996  return true;
3997  } else if (max_sum - min_coeff <= rhs_domain.Max() &&
3998  rhs_domain.back().start <= min_sum) {
3999  // At least one Boolean is false.
4000  context_->UpdateRuleStats("linear: negative clause");
4001  const auto copy = arg;
4002  ct->mutable_bool_or()->clear_literals();
4003  for (int i = 0; i < num_vars; ++i) {
4004  ct->mutable_bool_or()->add_literals(
4005  copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
4006  }
4007  PresolveBoolOr(ct);
4008  return true;
4009  } else if (!HasEnforcementLiteral(*ct) &&
4010  min_sum + max_coeff <= rhs_domain.Max() &&
4011  min_sum + 2 * min_coeff > rhs_domain.Max() &&
4012  rhs_domain.back().start <= min_sum) {
4013  // At most one Boolean is true.
4014  // TODO(user): Support enforced at most one.
4015  context_->UpdateRuleStats("linear: positive at most one");
4016  const auto copy = arg;
4017  ct->mutable_at_most_one()->clear_literals();
4018  for (int i = 0; i < num_vars; ++i) {
4019  ct->mutable_at_most_one()->add_literals(
4020  copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
4021  }
4022  return true;
4023  } else if (!HasEnforcementLiteral(*ct) &&
4024  max_sum - max_coeff >= rhs_domain.Min() &&
4025  max_sum - 2 * min_coeff < rhs_domain.Min() &&
4026  rhs_domain.front().end >= max_sum) {
4027  // At most one Boolean is false.
4028  // TODO(user): Support enforced at most one.
4029  context_->UpdateRuleStats("linear: negative at most one");
4030  const auto copy = arg;
4031  ct->mutable_at_most_one()->clear_literals();
4032  for (int i = 0; i < num_vars; ++i) {
4033  ct->mutable_at_most_one()->add_literals(
4034  copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
4035  }
4036  return true;
4037  } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
4038  min_sum < rhs_domain.Min() &&
4039  min_sum + min_coeff >= rhs_domain.Min() &&
4040  min_sum + 2 * min_coeff > rhs_domain.Max() &&
4041  min_sum + max_coeff <= rhs_domain.Max()) {
4042  // TODO(user): Support enforced exactly one.
4043  context_->UpdateRuleStats("linear: positive equal one");
4044  ConstraintProto* exactly_one = context_->working_model->add_constraints();
4045  exactly_one->set_name(ct->name());
4046  for (int i = 0; i < num_vars; ++i) {
4047  exactly_one->mutable_exactly_one()->add_literals(
4048  arg.coeffs(i) > 0 ? arg.vars(i) : NegatedRef(arg.vars(i)));
4049  }
4051  return RemoveConstraint(ct);
4052  } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
4053  max_sum > rhs_domain.Max() &&
4054  max_sum - min_coeff <= rhs_domain.Max() &&
4055  max_sum - 2 * min_coeff < rhs_domain.Min() &&
4056  max_sum - max_coeff >= rhs_domain.Min()) {
4057  // TODO(user): Support enforced exactly one.
4058  context_->UpdateRuleStats("linear: negative equal one");
4059  ConstraintProto* exactly_one = context_->working_model->add_constraints();
4060  exactly_one->set_name(ct->name());
4061  for (int i = 0; i < num_vars; ++i) {
4062  exactly_one->mutable_exactly_one()->add_literals(
4063  arg.coeffs(i) > 0 ? NegatedRef(arg.vars(i)) : arg.vars(i));
4064  }
4066  return RemoveConstraint(ct);
4067  }
4068 
4069  // Expand small expression into clause.
4070  //
4071  // TODO(user): This is bad from a LP relaxation perspective. Do not do that
4072  // now? On another hand it is good for the SAT presolving.
4073  if (num_vars > 3) return false;
4074  context_->UpdateRuleStats("linear: small Boolean expression");
4075 
4076  // Enumerate all possible value of the Booleans and add a clause if constraint
4077  // is false. TODO(user): the encoding could be made better in some cases.
4078  const int max_mask = (1 << arg.vars_size());
4079  for (int mask = 0; mask < max_mask; ++mask) {
4080  int64_t value = 0;
4081  for (int i = 0; i < num_vars; ++i) {
4082  if ((mask >> i) & 1) value += arg.coeffs(i);
4083  }
4084  if (rhs_domain.Contains(value)) continue;
4085 
4086  // Add a new clause to exclude this bad assignment.
4087  ConstraintProto* new_ct = context_->working_model->add_constraints();
4088  auto* new_arg = new_ct->mutable_bool_or();
4089  if (HasEnforcementLiteral(*ct)) {
4090  *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
4091  }
4092  for (int i = 0; i < num_vars; ++i) {
4093  new_arg->add_literals(((mask >> i) & 1) ? NegatedRef(arg.vars(i))
4094  : arg.vars(i));
4095  }
4096  }
4097 
4099  return RemoveConstraint(ct);
4100 }
4101 
4102 bool CpModelPresolver::PresolveInterval(int c, ConstraintProto* ct) {
4103  if (context_->ModelIsUnsat()) return false;
4104  IntervalConstraintProto* interval = ct->mutable_interval();
4105 
4106  // If the size is < 0, then the interval cannot be performed.
4107  if (!ct->enforcement_literal().empty() && context_->SizeMax(c) < 0) {
4108  context_->UpdateRuleStats("interval: negative size implies unperformed");
4109  return MarkConstraintAsFalse(ct);
4110  }
4111 
4112  if (ct->enforcement_literal().empty()) {
4113  bool domain_changed = false;
4114  // Size can't be negative.
4115  if (!context_->IntersectDomainWith(
4116  interval->size(), Domain(0, std::numeric_limits<int64_t>::max()),
4117  &domain_changed)) {
4118  return false;
4119  }
4120  if (domain_changed) {
4121  context_->UpdateRuleStats(
4122  "interval: performed intervals must have a positive size");
4123  }
4124  }
4125 
4126  // Note that the linear relation is stored elsewhere, so it is safe to just
4127  // remove such special interval constraint.
4128  if (context_->ConstraintVariableGraphIsUpToDate() &&
4129  context_->IntervalUsage(c) == 0) {
4130  context_->UpdateRuleStats("intervals: removed unused interval");
4131  return RemoveConstraint(ct);
4132  }
4133 
4134  bool changed = false;
4135  changed |= CanonicalizeLinearExpression(*ct, interval->mutable_start());
4136  changed |= CanonicalizeLinearExpression(*ct, interval->mutable_size());
4137  changed |= CanonicalizeLinearExpression(*ct, interval->mutable_end());
4138  return changed;
4139 }
4140 
4141 // TODO(user): avoid code duplication between expand and presolve.
4142 bool CpModelPresolver::PresolveInverse(ConstraintProto* ct) {
4143  const int size = ct->inverse().f_direct().size();
4144  bool changed = false;
4145 
4146  // Make sure the domains are included in [0, size - 1).
4147  for (const int ref : ct->inverse().f_direct()) {
4148  if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
4149  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
4150  return false;
4151  }
4152  }
4153  for (const int ref : ct->inverse().f_inverse()) {
4154  if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
4155  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
4156  return false;
4157  }
4158  }
4159 
4160  // Detect duplicated variable.
4161  // Even with negated variables, the reduced domain in [0..size - 1]
4162  // implies that the constraint is infeasible if ref and its negation
4163  // appear together.
4164  {
4165  absl::flat_hash_set<int> direct_vars;
4166  for (const int ref : ct->inverse().f_direct()) {
4167  const auto [it, inserted] = direct_vars.insert(PositiveRef(ref));
4168  if (!inserted) {
4169  return context_->NotifyThatModelIsUnsat("inverse: duplicated variable");
4170  }
4171  }
4172 
4173  absl::flat_hash_set<int> inverse_vars;
4174  for (const int ref : ct->inverse().f_inverse()) {
4175  const auto [it, inserted] = inverse_vars.insert(PositiveRef(ref));
4176  if (!inserted) {
4177  return context_->NotifyThatModelIsUnsat("inverse: duplicated variable");
4178  }
4179  }
4180  }
4181 
4182  // Propagate from one vector to its counterpart.
4183  // Note this reaches the fixpoint as there is a one to one mapping between
4184  // (variable-value) pairs in each vector.
4185  const auto filter_inverse_domain =
4186  [this, size, &changed](const auto& direct, const auto& inverse) {
4187  // Build the set of values in the inverse vector.
4188  std::vector<absl::flat_hash_set<int64_t>> inverse_values(size);
4189  for (int i = 0; i < size; ++i) {
4190  const Domain domain = context_->DomainOf(inverse[i]);
4191  for (const int64_t j : domain.Values()) {
4192  inverse_values[i].insert(j);
4193  }
4194  }
4195 
4196  // Propagate from the inverse vector to the direct vector. Reduce the
4197  // domains of each variable in the direct vector by checking that the
4198  // inverse value exists.
4199  std::vector<int64_t> possible_values;
4200  for (int i = 0; i < size; ++i) {
4201  possible_values.clear();
4202  const Domain domain = context_->DomainOf(direct[i]);
4203  bool removed_value = false;
4204  for (const int64_t j : domain.Values()) {
4205  if (inverse_values[j].contains(i)) {
4206  possible_values.push_back(j);
4207  } else {
4208  removed_value = true;
4209  }
4210  }
4211  if (removed_value) {
4212  changed = true;
4213  if (!context_->IntersectDomainWith(
4214  direct[i], Domain::FromValues(possible_values))) {
4215  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
4216  return false;
4217  }
4218  }
4219  }
4220  return true;
4221  };
4222 
4223  if (!filter_inverse_domain(ct->inverse().f_direct(),
4224  ct->inverse().f_inverse())) {
4225  return false;
4226  }
4227 
4228  if (!filter_inverse_domain(ct->inverse().f_inverse(),
4229  ct->inverse().f_direct())) {
4230  return false;
4231  }
4232 
4233  if (changed) {
4234  context_->UpdateRuleStats("inverse: reduce domains");
4235  }
4236 
4237  return false;
4238 }
4239 
4240 bool CpModelPresolver::PresolveElement(ConstraintProto* ct) {
4241  if (context_->ModelIsUnsat()) return false;
4242 
4243  if (ct->element().vars().empty()) {
4244  context_->UpdateRuleStats("element: empty array");
4245  return context_->NotifyThatModelIsUnsat();
4246  }
4247 
4248  const int index_ref = ct->element().index();
4249  const int target_ref = ct->element().target();
4250 
4251  // TODO(user): think about this once we do have such constraint.
4252  if (HasEnforcementLiteral(*ct)) return false;
4253 
4254  bool all_constants = true;
4255  std::vector<int64_t> constants;
4256  bool all_included_in_target_domain = true;
4257 
4258  {
4259  if (!context_->IntersectDomainWith(
4260  index_ref, Domain(0, ct->element().vars_size() - 1))) {
4261  return false;
4262  }
4263 
4264  // Filter impossible index values if index == +/- target
4265  //
4266  // Note that this must be done before the unique_index/target rule.
4267  if (PositiveRef(target_ref) == PositiveRef(index_ref)) {
4268  std::vector<int64_t> possible_indices;
4269  const Domain& index_domain = context_->DomainOf(index_ref);
4270  for (const int64_t index_value : index_domain.Values()) {
4271  const int ref = ct->element().vars(index_value);
4272  const int64_t target_value =
4273  target_ref == index_ref ? index_value : -index_value;
4274  if (context_->DomainContains(ref, target_value)) {
4275  possible_indices.push_back(target_value);
4276  }
4277  }
4278  if (possible_indices.size() < index_domain.Size()) {
4279  if (!context_->IntersectDomainWith(
4280  index_ref, Domain::FromValues(possible_indices))) {
4281  return true;
4282  }
4283  context_->UpdateRuleStats(
4284  "element: reduced index domain when target equals index");
4285  }
4286  }
4287 
4288  // Filter possible index values. Accumulate variable domains to build
4289  // a possible target domain.
4290  Domain infered_domain;
4291  const Domain& initial_index_domain = context_->DomainOf(index_ref);
4292  const Domain& target_domain = context_->DomainOf(target_ref);
4293  std::vector<int64_t> possible_indices;
4294  for (const int64_t value : initial_index_domain.Values()) {
4295  CHECK_GE(value, 0);
4296  CHECK_LT(value, ct->element().vars_size());
4297  const int ref = ct->element().vars(value);
4298 
4299  // We cover the corner cases where the possible domain is actually fixed.
4300  Domain domain = context_->DomainOf(ref);
4301  if (ref == index_ref) {
4302  domain = Domain(value);
4303  } else if (ref == NegatedRef(index_ref)) {
4304  domain = Domain(-value);
4305  } else if (ref == NegatedRef(target_ref)) {
4306  domain = Domain(0);
4307  }
4308 
4309  if (domain.IntersectionWith(target_domain).IsEmpty()) continue;
4310  possible_indices.push_back(value);
4311  if (domain.IsFixed()) {
4312  constants.push_back(domain.Min());
4313  } else {
4314  all_constants = false;
4315  }
4316  if (!domain.IsIncludedIn(target_domain)) {
4317  all_included_in_target_domain = false;
4318  }
4319  infered_domain = infered_domain.UnionWith(domain);
4320  }
4321  if (possible_indices.size() < initial_index_domain.Size()) {
4322  if (!context_->IntersectDomainWith(
4323  index_ref, Domain::FromValues(possible_indices))) {
4324  return true;
4325  }
4326  context_->UpdateRuleStats("element: reduced index domain");
4327  }
4328  bool domain_modified = false;
4329  if (!context_->IntersectDomainWith(target_ref, infered_domain,
4330  &domain_modified)) {
4331  return true;
4332  }
4333  if (domain_modified) {
4334  context_->UpdateRuleStats("element: reduced target domain");
4335  }
4336  }
4337 
4338  // If the index is fixed, this is a equality constraint.
4339  if (context_->IsFixed(index_ref)) {
4340  const int var = ct->element().vars(context_->MinOf(index_ref));
4341  if (var != target_ref) {
4342  LinearConstraintProto* const lin =
4343  context_->working_model->add_constraints()->mutable_linear();
4344  lin->add_vars(var);
4345  lin->add_coeffs(-1);
4346  lin->add_vars(target_ref);
4347  lin->add_coeffs(1);
4348  lin->add_domain(0);
4349  lin->add_domain(0);
4351  }
4352  context_->UpdateRuleStats("element: fixed index");
4353  return RemoveConstraint(ct);
4354  }
4355 
4356  // If the accessible part of the array is made of a single constant value,
4357  // then we do not care about the index. And, because of the previous target
4358  // domain reduction, the target is also fixed.
4359  if (all_constants && context_->IsFixed(target_ref)) {
4360  context_->UpdateRuleStats("element: one value array");
4361  return RemoveConstraint(ct);
4362  }
4363 
4364  // Special case when the index is boolean, and the array does not contain
4365  // variables.
4366  if (context_->MinOf(index_ref) == 0 && context_->MaxOf(index_ref) == 1 &&
4367  all_constants) {
4368  const int64_t v0 = constants[0];
4369  const int64_t v1 = constants[1];
4370 
4371  LinearConstraintProto* const lin =
4372  context_->working_model->add_constraints()->mutable_linear();
4373  lin->add_vars(target_ref);
4374  lin->add_coeffs(1);
4375  lin->add_vars(index_ref);
4376  lin->add_coeffs(v0 - v1);
4377  lin->add_domain(v0);
4378  lin->add_domain(v0);
4380  context_->UpdateRuleStats("element: linearize constant element of size 2");
4381  return RemoveConstraint(ct);
4382  }
4383 
4384  // If the index has a canonical affine representative, rewrite the element.
4385  const AffineRelation::Relation r_index =
4386  context_->GetAffineRelation(index_ref);
4387  if (r_index.representative != index_ref) {
4388  // Checks the domains are synchronized.
4389  if (context_->DomainOf(r_index.representative).Size() >
4390  context_->DomainOf(index_ref).Size()) {
4391  // Postpone, we will come back later when domains are synchronized.
4392  return true;
4393  }
4394  const int r_ref = r_index.representative;
4395  const int64_t r_min = context_->MinOf(r_ref);
4396  const int64_t r_max = context_->MaxOf(r_ref);
4397  const int array_size = ct->element().vars_size();
4398  if (r_min != 0) {
4399  context_->UpdateRuleStats("TODO element: representative has bad domain");
4400  } else if (r_index.offset >= 0 && r_index.offset < array_size &&
4401  r_index.offset + r_max * r_index.coeff >= 0 &&
4402  r_index.offset + r_max * r_index.coeff < array_size) {
4403  // This will happen eventually when domains are synchronized.
4404  ElementConstraintProto* const element =
4405  context_->working_model->add_constraints()->mutable_element();
4406  for (int64_t v = 0; v <= r_max; ++v) {
4407  const int64_t scaled_index = v * r_index.coeff + r_index.offset;
4408  CHECK_GE(scaled_index, 0);
4409  CHECK_LT(scaled_index, array_size);
4410  element->add_vars(ct->element().vars(scaled_index));
4411  }
4412  element->set_index(r_ref);
4413  element->set_target(target_ref);
4414 
4415  if (r_index.coeff == 1) {
4416  context_->UpdateRuleStats("element: shifed index ");
4417  } else {
4418  context_->UpdateRuleStats("element: scaled index");
4419  }
4421  return RemoveConstraint(ct);
4422  }
4423  }
4424 
4425  // Should have been taken care of earlier.
4426  DCHECK(!context_->IsFixed(index_ref));
4427 
4428  // If a variable (target or index) appears only in this constraint, it does
4429  // not necessarily mean that we can remove the constraint, as the variable
4430  // can be used multiple times in the element. So let's count the local
4431  // uses of each variable.
4432  //
4433  // TODO(user): now that we used fixed values for these case, this is no longer
4434  // needed I think.
4435  absl::flat_hash_map<int, int> local_var_occurrence_counter;
4436  local_var_occurrence_counter[PositiveRef(index_ref)]++;
4437  local_var_occurrence_counter[PositiveRef(target_ref)]++;
4438 
4439  for (const ClosedInterval interval : context_->DomainOf(index_ref)) {
4440  for (int64_t value = interval.start; value <= interval.end; ++value) {
4441  DCHECK_GE(value, 0);
4442  DCHECK_LT(value, ct->element().vars_size());
4443  const int ref = ct->element().vars(value);
4444  local_var_occurrence_counter[PositiveRef(ref)]++;
4445  }
4446  }
4447 
4448  if (context_->VariableIsUniqueAndRemovable(index_ref) &&
4449  local_var_occurrence_counter.at(PositiveRef(index_ref)) == 1) {
4450  if (all_constants) {
4451  // This constraint is just here to reduce the domain of the target! We can
4452  // add it to the mapping_model to reconstruct the index value during
4453  // postsolve and get rid of it now.
4454  context_->UpdateRuleStats("element: trivial target domain reduction");
4455  context_->MarkVariableAsRemoved(index_ref);
4456  *(context_->mapping_model->add_constraints()) = *ct;
4457  return RemoveConstraint(ct);
4458  } else {
4459  context_->UpdateRuleStats("TODO element: index not used elsewhere");
4460  }
4461  }
4462 
4463  if (!context_->IsFixed(target_ref) &&
4464  context_->VariableIsUniqueAndRemovable(target_ref) &&
4465  local_var_occurrence_counter.at(PositiveRef(target_ref)) == 1) {
4466  if (all_included_in_target_domain) {
4467  context_->UpdateRuleStats("element: trivial index domain reduction");
4468  context_->MarkVariableAsRemoved(target_ref);
4469  *(context_->mapping_model->add_constraints()) = *ct;
4470  return RemoveConstraint(ct);
4471  } else {
4472  context_->UpdateRuleStats("TODO element: target not used elsewhere");
4473  }
4474  }
4475 
4476  return false;
4477 }
4478 
4479 bool CpModelPresolver::PresolveTable(ConstraintProto* ct) {
4480  if (context_->ModelIsUnsat()) return false;
4481  if (HasEnforcementLiteral(*ct)) return false;
4482  if (ct->table().vars().empty()) {
4483  context_->UpdateRuleStats("table: empty constraint");
4484  return RemoveConstraint(ct);
4485  }
4486 
4487  const int initial_num_vars = ct->table().vars_size();
4488  bool changed = true;
4489 
4490  // Query existing affine relations.
4491  std::vector<AffineRelation::Relation> affine_relations;
4492  std::vector<int64_t> old_var_lb;
4493  std::vector<int64_t> old_var_ub;
4494  {
4495  for (int v = 0; v < initial_num_vars; ++v) {
4496  const int ref = ct->table().vars(v);
4497  AffineRelation::Relation r = context_->GetAffineRelation(ref);
4498  affine_relations.push_back(r);
4499  old_var_lb.push_back(context_->MinOf(ref));
4500  old_var_ub.push_back(context_->MaxOf(ref));
4501  if (r.representative != ref) {
4502  changed = true;
4503  ct->mutable_table()->set_vars(v, r.representative);
4504  context_->UpdateRuleStats(
4505  "table: replace variable by canonical affine one");
4506  }
4507  }
4508  }
4509 
4510  // Check for duplicate occurrences of variables.
4511  // If the ith index is -1, then the variable is not a duplicate of a smaller
4512  // index variable. It if is != from -1, then the values stored is the new
4513  // index of the first occurrence of the variable.
4514  std::vector<int> old_index_of_duplicate_to_new_index_of_first_occurrence(
4515  initial_num_vars, -1);
4516  // If == -1, then the variable is a duplicate of a smaller index variable.
4517  std::vector<int> old_index_to_new_index(initial_num_vars, -1);
4518  int num_vars = 0;
4519  {
4520  absl::flat_hash_map<int, int> first_visit;
4521  for (int p = 0; p < initial_num_vars; ++p) {
4522  const int ref = ct->table().vars(p);
4523  const int var = PositiveRef(ref);
4524  const auto& it = first_visit.find(var);
4525  if (it != first_visit.end()) {
4526  const int previous = it->second;
4527  old_index_of_duplicate_to_new_index_of_first_occurrence[p] = previous;
4528  context_->UpdateRuleStats("table: duplicate variables");
4529  changed = true;
4530  } else {
4531  ct->mutable_table()->set_vars(num_vars, ref);
4532  first_visit[var] = num_vars;
4533  old_index_to_new_index[p] = num_vars;
4534  num_vars++;
4535  }
4536  }
4537 
4538  if (num_vars < initial_num_vars) {
4539  ct->mutable_table()->mutable_vars()->Truncate(num_vars);
4540  }
4541  }
4542 
4543  // Check each tuple for validity w.r.t. affine relations, variable domains,
4544  // and consistency with duplicate variables. Reduce the size of the tuple in
4545  // case of duplicate variables.
4546  std::vector<std::vector<int64_t>> new_tuples;
4547  const int initial_num_tuples = ct->table().values_size() / initial_num_vars;
4548  std::vector<absl::flat_hash_set<int64_t>> new_domains(num_vars);
4549 
4550  {
4551  std::vector<int64_t> tuple(num_vars);
4552  new_tuples.reserve(initial_num_tuples);
4553  for (int i = 0; i < initial_num_tuples; ++i) {
4554  bool delete_row = false;
4555  std::string tmp;
4556  for (int j = 0; j < initial_num_vars; ++j) {
4557  const int64_t old_value = ct->table().values(i * initial_num_vars + j);
4558 
4559  // Corner case to avoid overflow, assuming the domain where already
4560  // propagated between a variable and its affine representative.
4561  if (old_value < old_var_lb[j] || old_value > old_var_ub[j]) {
4562  delete_row = true;
4563  break;
4564  }
4565 
4566  // Affine relations are defined on the initial variables.
4567  const AffineRelation::Relation& r = affine_relations[j];
4568  const int64_t value = (old_value - r.offset) / r.coeff;
4569  if (value * r.coeff + r.offset != old_value) {
4570  // Value not reachable by affine relation.
4571  delete_row = true;
4572  break;
4573  }
4574  const int mapped_position = old_index_to_new_index[j];
4575  if (mapped_position == -1) { // The current variable is duplicate.
4576  const int new_index_of_first_occurrence =
4577  old_index_of_duplicate_to_new_index_of_first_occurrence[j];
4578  if (value != tuple[new_index_of_first_occurrence]) {
4579  delete_row = true;
4580  break;
4581  }
4582  } else {
4583  const int ref = ct->table().vars(mapped_position);
4584  if (!context_->DomainContains(ref, value)) {
4585  delete_row = true;
4586  break;
4587  }
4588  tuple[mapped_position] = value;
4589  }
4590  }
4591  if (delete_row) {
4592  changed = true;
4593  continue;
4594  }
4595  new_tuples.push_back(tuple);
4596  for (int j = 0; j < num_vars; ++j) {
4597  new_domains[j].insert(tuple[j]);
4598  }
4599  }
4600  gtl::STLSortAndRemoveDuplicates(&new_tuples);
4601  if (new_tuples.size() < initial_num_tuples) {
4602  context_->UpdateRuleStats("table: removed rows");
4603  }
4604  }
4605 
4606  // Update the list of tuples if needed.
4607  if (changed) {
4608  ct->mutable_table()->clear_values();
4609  for (const std::vector<int64_t>& t : new_tuples) {
4610  for (const int64_t v : t) {
4611  ct->mutable_table()->add_values(v);
4612  }
4613  }
4614  }
4615 
4616  // Nothing more to do for negated tables.
4617  if (ct->table().negated()) return changed;
4618 
4619  // Filter the variable domains.
4620  for (int j = 0; j < num_vars; ++j) {
4621  const int ref = ct->table().vars(j);
4622  if (!context_->IntersectDomainWith(
4623  PositiveRef(ref),
4624  Domain::FromValues(std::vector<int64_t>(new_domains[j].begin(),
4625  new_domains[j].end())),
4626  &changed)) {
4627  return true;
4628  }
4629  }
4630  if (changed) {
4631  context_->UpdateRuleStats("table: reduced variable domains");
4632  }
4633  if (num_vars == 1) {
4634  // Now that we properly update the domain, we can remove the constraint.
4635  context_->UpdateRuleStats("table: only one column!");
4636  return RemoveConstraint(ct);
4637  }
4638 
4639  // Check that the table is not complete or just here to exclude a few tuples.
4640  double prod = 1.0;
4641  for (int j = 0; j < num_vars; ++j) prod *= new_domains[j].size();
4642  if (prod == new_tuples.size()) {
4643  context_->UpdateRuleStats("table: all tuples!");
4644  return RemoveConstraint(ct);
4645  }
4646 
4647  // Convert to the negated table if we gain a lot of entries by doing so.
4648  // Note however that currently the negated table do not propagate as much as
4649  // it could.
4650  if (new_tuples.size() > 0.7 * prod) {
4651  // Enumerate all tuples.
4652  std::vector<std::vector<int64_t>> var_to_values(num_vars);
4653  for (int j = 0; j < num_vars; ++j) {
4654  var_to_values[j].assign(new_domains[j].begin(), new_domains[j].end());
4655  }
4656  std::vector<std::vector<int64_t>> all_tuples(prod);
4657  for (int i = 0; i < prod; ++i) {
4658  all_tuples[i].resize(num_vars);
4659  int index = i;
4660  for (int j = 0; j < num_vars; ++j) {
4661  all_tuples[i][j] = var_to_values[j][index % var_to_values[j].size()];
4662  index /= var_to_values[j].size();
4663  }
4664  }
4665  gtl::STLSortAndRemoveDuplicates(&all_tuples);
4666 
4667  // Compute the complement of new_tuples.
4668  std::vector<std::vector<int64_t>> diff(prod - new_tuples.size());
4669  std::set_difference(all_tuples.begin(), all_tuples.end(),
4670  new_tuples.begin(), new_tuples.end(), diff.begin());
4671 
4672  // Negate the constraint.
4673  ct->mutable_table()->set_negated(!ct->table().negated());
4674  ct->mutable_table()->clear_values();
4675  for (const std::vector<int64_t>& t : diff) {
4676  for (const int64_t v : t) ct->mutable_table()->add_values(v);
4677  }
4678  context_->UpdateRuleStats("table: negated");
4679  }
4680  return changed;
4681 }
4682 
4683 bool CpModelPresolver::PresolveAllDiff(ConstraintProto* ct) {
4684  if (context_->ModelIsUnsat()) return false;
4685  if (HasEnforcementLiteral(*ct)) return false;
4686 
4687  AllDifferentConstraintProto& all_diff = *ct->mutable_all_diff();
4688 
4689  bool constraint_has_changed = false;
4690  for (LinearExpressionProto& exp :
4691  *(ct->mutable_all_diff()->mutable_exprs())) {
4692  constraint_has_changed |= CanonicalizeLinearExpression(*ct, &exp);
4693  }
4694 
4695  for (;;) {
4696  const int size = all_diff.exprs_size();
4697  if (size == 0) {
4698  context_->UpdateRuleStats("all_diff: empty constraint");
4699  return RemoveConstraint(ct);
4700  }
4701  if (size == 1) {
4702  context_->UpdateRuleStats("all_diff: only one variable");
4703  return RemoveConstraint(ct);
4704  }
4705 
4706  bool something_was_propagated = false;
4707  std::vector<LinearExpressionProto> kept_expressions;
4708  for (int i = 0; i < size; ++i) {
4709  if (!context_->IsFixed(all_diff.exprs(i))) {
4710  kept_expressions.push_back(all_diff.exprs(i));
4711  continue;
4712  }
4713 
4714  const int64_t value = context_->MinOf(all_diff.exprs(i));
4715  bool propagated = false;
4716  for (int j = 0; j < size; ++j) {
4717  if (i == j) continue;
4718  if (context_->DomainContains(all_diff.exprs(j), value)) {
4719  if (!context_->IntersectDomainWith(all_diff.exprs(j),
4720  Domain(value).Complement())) {
4721  return true;
4722  }
4723  propagated = true;
4724  }
4725  }
4726  if (propagated) {
4727  context_->UpdateRuleStats("all_diff: propagated fixed expressions");
4728  something_was_propagated = true;
4729  }
4730  }
4731 
4732  // CanonicalizeLinearExpression() made sure that only positive variable
4733  // appears here, so this order will put expr and -expr one after the other.
4734  std::sort(
4735  kept_expressions.begin(), kept_expressions.end(),
4736  [](const LinearExpressionProto& expr_a,
4737  const LinearExpressionProto& expr_b) {
4738  DCHECK_EQ(expr_a.vars_size(), 1);
4739  DCHECK_EQ(expr_b.vars_size(), 1);
4740  const int ref_a = expr_a.vars(0);
4741  const int ref_b = expr_b.vars(0);
4742  const int64_t coeff_a = expr_a.coeffs(0);
4743  const int64_t coeff_b = expr_b.coeffs(0);
4744  const int64_t abs_coeff_a = std::abs(coeff_a);
4745  const int64_t abs_coeff_b = std::abs(coeff_b);
4746  const int64_t offset_a = expr_a.offset();
4747  const int64_t offset_b = expr_b.offset();
4748  const int64_t abs_offset_a = std::abs(offset_a);
4749  const int64_t abs_offset_b = std::abs(offset_b);
4750  return std::tie(ref_a, abs_coeff_a, coeff_a, abs_offset_a, offset_a) <
4751  std::tie(ref_b, abs_coeff_b, coeff_b, abs_offset_b, offset_b);
4752  });
4753 
4754  // TODO(user): improve algorithm if of (a + offset) and (-a - offset)
4755  // might not be together if (a - offset) is present.
4756 
4757  for (int i = 1; i < kept_expressions.size(); ++i) {
4758  if (LinearExpressionProtosAreEqual(kept_expressions[i],
4759  kept_expressions[i - 1], 1)) {
4760  return context_->NotifyThatModelIsUnsat(
4761  "Duplicate variable in all_diff");
4762  }
4763  if (LinearExpressionProtosAreEqual(kept_expressions[i],
4764  kept_expressions[i - 1], -1)) {
4765  bool domain_modified = false;
4766  if (!context_->IntersectDomainWith(kept_expressions[i],
4767  Domain(0).Complement(),
4768  &domain_modified)) {
4769  return false;
4770  }
4771  if (domain_modified) {
4772  context_->UpdateRuleStats(
4773  "all_diff: remove 0 from expression appearing with its "
4774  "opposite.");
4775  }
4776  }
4777  }
4778 
4779  if (kept_expressions.size() < all_diff.exprs_size()) {
4780  all_diff.clear_exprs();
4781  for (const LinearExpressionProto& expr : kept_expressions) {
4782  *all_diff.add_exprs() = expr;
4783  }
4784  context_->UpdateRuleStats("all_diff: removed fixed variables");
4785  something_was_propagated = true;
4786  constraint_has_changed = true;
4787  if (kept_expressions.size() <= 1) continue;
4788  }
4789 
4790  // Propagate mandatory value if the all diff is actually a permutation.
4791  CHECK_GE(all_diff.exprs_size(), 2);
4792  Domain domain = context_->DomainSuperSetOf(all_diff.exprs(0));
4793  for (int i = 1; i < all_diff.exprs_size(); ++i) {
4794  domain = domain.UnionWith(context_->DomainSuperSetOf(all_diff.exprs(i)));
4795  }
4796  if (all_diff.exprs_size() == domain.Size()) {
4797  absl::flat_hash_map<int64_t, std::vector<LinearExpressionProto>>
4798  value_to_exprs;
4799  for (const LinearExpressionProto& expr : all_diff.exprs()) {
4800  for (const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
4801  value_to_exprs[expr.coeffs(0) * v + expr.offset()].push_back(expr);
4802  }
4803  }
4804  bool propagated = false;
4805  for (const auto& it : value_to_exprs) {
4806  if (it.second.size() == 1 && !context_->IsFixed(it.second.front())) {
4807  const LinearExpressionProto& expr = it.second.front();
4808  if (!context_->IntersectDomainWith(expr, Domain(it.first))) {
4809  return true;
4810  }
4811  propagated = true;
4812  }
4813  }
4814  if (propagated) {
4815  context_->UpdateRuleStats(
4816  "all_diff: propagated mandatory values in permutation");
4817  something_was_propagated = true;
4818  }
4819  }
4820  if (!something_was_propagated) break;
4821  }
4822 
4823  return constraint_has_changed;
4824 }
4825 
4826 namespace {
4827 
4828 // Add the constraint (lhs => rhs) to the given proto. The hash map lhs ->
4829 // bool_and constraint index is used to merge implications with the same lhs.
4830 void AddImplication(int lhs, int rhs, CpModelProto* proto,
4831  absl::flat_hash_map<int, int>* ref_to_bool_and) {
4832  if (ref_to_bool_and->contains(lhs)) {
4833  const int ct_index = (*ref_to_bool_and)[lhs];
4834  proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
4835  } else if (ref_to_bool_and->contains(NegatedRef(rhs))) {
4836  const int ct_index = (*ref_to_bool_and)[NegatedRef(rhs)];
4837  proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
4838  NegatedRef(lhs));
4839  } else {
4840  (*ref_to_bool_and)[lhs] = proto->constraints_size();
4841  ConstraintProto* ct = proto->add_constraints();
4842  ct->add_enforcement_literal(lhs);
4843  ct->mutable_bool_and()->add_literals(rhs);
4844  }
4845 }
4846 
4847 template <typename ClauseContainer>
4848 void ExtractClauses(bool use_bool_and, const ClauseContainer& container,
4849  CpModelProto* proto) {
4850  // We regroup the "implication" into bool_and to have a more concise proto and
4851  // also for nicer information about the number of binary clauses.
4852  //
4853  // Important: however, we do not do that for the model used during presolving
4854  // since the order of the constraints might be important there depending on
4855  // how we perform the postsolve.
4856  absl::flat_hash_map<int, int> ref_to_bool_and;
4857  for (int i = 0; i < container.NumClauses(); ++i) {
4858  const std::vector<Literal>& clause = container.Clause(i);
4859  if (clause.empty()) continue;
4860 
4861  // bool_and.
4862  //
4863  // TODO(user): Be smarter in how we regroup clause of size 2?
4864  if (use_bool_and && clause.size() == 2) {
4865  const int a = clause[0].IsPositive()
4866  ? clause[0].Variable().value()
4867  : NegatedRef(clause[0].Variable().value());
4868  const int b = clause[1].IsPositive()
4869  ? clause[1].Variable().value()
4870  : NegatedRef(clause[1].Variable().value());
4871  AddImplication(NegatedRef(a), b, proto, &ref_to_bool_and);
4872  continue;
4873  }
4874 
4875  // bool_or.
4876  ConstraintProto* ct = proto->add_constraints();
4877  for (const Literal l : clause) {
4878  if (l.IsPositive()) {
4879  ct->mutable_bool_or()->add_literals(l.Variable().value());
4880  } else {
4881  ct->mutable_bool_or()->add_literals(NegatedRef(l.Variable().value()));
4882  }
4883  }
4884  }
4885 }
4886 
4887 } // namespace
4888 
4889 bool CpModelPresolver::PresolveNoOverlap(ConstraintProto* ct) {
4890  if (context_->ModelIsUnsat()) return false;
4891  NoOverlapConstraintProto* proto = ct->mutable_no_overlap();
4892  bool changed = false;
4893 
4894  // Filter out absent intervals. Process duplicate intervals.
4895  {
4896  // Collect duplicate intervals.
4897  absl::flat_hash_set<int> visited_intervals;
4898  absl::flat_hash_set<int> duplicate_intervals;
4899  for (const int interval_index : proto->intervals()) {
4900  if (context_->ConstraintIsInactive(interval_index)) continue;
4901  if (!visited_intervals.insert(interval_index).second) {
4902  duplicate_intervals.insert(interval_index);
4903  }
4904  }
4905 
4906  const int initial_num_intervals = proto->intervals_size();
4907  int new_size = 0;
4908  visited_intervals.clear();
4909 
4910  for (int i = 0; i < initial_num_intervals; ++i) {
4911  const int interval_index = proto->intervals(i);
4912  if (context_->ConstraintIsInactive(interval_index)) continue;
4913 
4914  if (duplicate_intervals.contains(interval_index)) {
4915  // Once processed, we can always remove further duplicates.
4916  if (!visited_intervals.insert(interval_index).second) continue;
4917 
4918  ConstraintProto* interval_ct =
4919  context_->working_model->mutable_constraints(interval_index);
4920 
4921  // Case 1: size > 0. Interval must be unperformed.
4922  if (context_->SizeMin(interval_index) > 0) {
4923  if (!MarkConstraintAsFalse(interval_ct)) {
4924  return false;
4925  }
4926  context_->UpdateRuleStats(
4927  "no_overlap: unperform duplicate non zero-sized intervals");
4928  // We can remove the interval from the no_overlap.
4929  continue;
4930  }
4931 
4932  // No need to do anything if the size is 0.
4933  if (context_->SizeMax(interval_index) > 0) {
4934  // Case 2: interval is performed. Size must be set to 0.
4935  if (!context_->ConstraintIsOptional(interval_index)) {
4936  if (!context_->IntersectDomainWith(interval_ct->interval().size(),
4937  Domain(0))) {
4938  return false;
4939  }
4940  context_->UpdateRuleStats(
4941  "no_overlap: zero the size of performed duplicate intervals");
4942  // We still need to add the interval to the no_overlap as zero sized
4943  // intervals still cannot overlap with other intervals.
4944  } else { // Case 3: interval is optional and size can be > 0.
4945  const int performed_literal = interval_ct->enforcement_literal(0);
4946  ConstraintProto* size_eq_zero =
4947  context_->working_model->add_constraints();
4948  size_eq_zero->add_enforcement_literal(performed_literal);
4949  size_eq_zero->mutable_linear()->add_domain(0);
4950  size_eq_zero->mutable_linear()->add_domain(0);
4952  interval_ct->interval().size(), 1,
4953  size_eq_zero->mutable_linear());
4954  context_->UpdateRuleStats(
4955  "no_overlap: make duplicate intervals as unperformed or zero "
4956  "sized");
4957  }
4958  }
4959  }
4960 
4961  proto->set_intervals(new_size++, interval_index);
4962  }
4963 
4964  if (new_size < initial_num_intervals) {
4965  proto->mutable_intervals()->Truncate(new_size);
4966  context_->UpdateRuleStats("no_overlap: removed absent intervals");
4967  changed = true;
4968  }
4969  }
4970 
4971  // Split constraints in disjoint sets.
4972  if (proto->intervals_size() > 1) {
4973  std::vector<IndexedInterval> indexed_intervals;
4974  for (int i = 0; i < proto->intervals().size(); ++i) {
4975  const int index = proto->intervals(i);
4976  indexed_intervals.push_back({index,
4977  IntegerValue(context_->StartMin(index)),
4978  IntegerValue(context_->EndMax(index))});
4979  }
4980  std::vector<std::vector<int>> components;
4981  GetOverlappingIntervalComponents(&indexed_intervals, &components);
4982 
4983  if (components.size() > 1) {
4984  for (const std::vector<int>& intervals : components) {
4985  if (intervals.size() <= 1) continue;
4986 
4987  NoOverlapConstraintProto* new_no_overlap =
4988  context_->working_model->add_constraints()->mutable_no_overlap();
4989  // Fill in the intervals. Unfortunately, the Assign() method does not
4990  // compile in or-tools.
4991  for (const int i : intervals) {
4992  new_no_overlap->add_intervals(i);
4993  }
4994  }
4996  context_->UpdateRuleStats("no_overlap: split into disjoint components");
4997  return RemoveConstraint(ct);
4998  }
4999  }
5000 
5001  std::vector<int> constant_intervals;
5002  int64_t size_min_of_non_constant_intervals =
5004  for (int i = 0; i < proto->intervals_size(); ++i) {
5005  const int interval_index = proto->intervals(i);
5006  if (context_->IntervalIsConstant(interval_index)) {
5007  constant_intervals.push_back(interval_index);
5008  } else {
5009  size_min_of_non_constant_intervals =
5010  std::min(size_min_of_non_constant_intervals,
5011  context_->SizeMin(interval_index));
5012  }
5013  }
5014 
5015  bool move_constraint_last = false;
5016  if (!constant_intervals.empty()) {
5017  // Sort constant_intervals by start min.
5018  std::sort(constant_intervals.begin(), constant_intervals.end(),
5019  [this](int i1, int i2) {
5020  const int64_t s1 = context_->StartMin(i1);
5021  const int64_t e1 = context_->EndMax(i1);
5022  const int64_t s2 = context_->StartMin(i2);
5023  const int64_t e2 = context_->EndMax(i2);
5024  return std::tie(s1, e1) < std::tie(s2, e2);
5025  });
5026 
5027  // Check for overlapping constant intervals. We need to check feasibility
5028  // before we simplify the constraint, as we might remove conflicting
5029  // overlapping constant intervals.
5030  for (int i = 0; i + 1 < constant_intervals.size(); ++i) {
5031  if (context_->EndMax(constant_intervals[i]) >
5032  context_->StartMin(constant_intervals[i + 1])) {
5033  context_->UpdateRuleStats("no_overlap: constant intervals overlap");
5034  return context_->NotifyThatModelIsUnsat();
5035  }
5036  }
5037 
5038  if (constant_intervals.size() == proto->intervals_size()) {
5039  context_->UpdateRuleStats("no_overlap: no variable intervals");
5040  return RemoveConstraint(ct);
5041  }
5042 
5043  absl::flat_hash_set<int> intervals_to_remove;
5044 
5045  // If two constant intervals are separated by a gap smaller that the min
5046  // size of all non-constant intervals, then we can merge them.
5047  for (int i = 0; i + 1 < constant_intervals.size(); ++i) {
5048  const int start = i;
5049  while (i + 1 < constant_intervals.size() &&
5050  context_->StartMin(constant_intervals[i + 1]) -
5051  context_->EndMax(constant_intervals[i]) <
5052  size_min_of_non_constant_intervals) {
5053  i++;
5054  }
5055  if (i == start) continue;
5056  for (int j = start; j <= i; ++j) {
5057  intervals_to_remove.insert(constant_intervals[j]);
5058  }
5059  const int64_t new_start = context_->StartMin(constant_intervals[start]);
5060  const int64_t new_end = context_->EndMax(constant_intervals[i]);
5061  proto->add_intervals(context_->working_model->constraints_size());
5062  IntervalConstraintProto* new_interval =
5063  context_->working_model->add_constraints()->mutable_interval();
5064  new_interval->mutable_start()->set_offset(new_start);
5065  new_interval->mutable_size()->set_offset(new_end - new_start);
5066  new_interval->mutable_end()->set_offset(new_end);
5067  move_constraint_last = true;
5068  }
5069 
5070  // Cleanup the original proto.
5071  if (!intervals_to_remove.empty()) {
5072  int new_size = 0;
5073  const int old_size = proto->intervals_size();
5074  for (int i = 0; i < old_size; ++i) {
5075  const int interval_index = proto->intervals(i);
5076  if (intervals_to_remove.contains(interval_index)) {
5077  continue;
5078  }
5079  proto->set_intervals(new_size++, interval_index);
5080  }
5081  CHECK_LT(new_size, old_size);
5082  proto->mutable_intervals()->Truncate(new_size);
5083  context_->UpdateRuleStats(
5084  "no_overlap: merge constant contiguous intervals");
5085  intervals_to_remove.clear();
5086  constant_intervals.clear();
5087  changed = true;
5089  }
5090  }
5091 
5092  if (proto->intervals_size() == 1) {
5093  context_->UpdateRuleStats("no_overlap: only one interval");
5094  return RemoveConstraint(ct);
5095  }
5096  if (proto->intervals().empty()) {
5097  context_->UpdateRuleStats("no_overlap: no intervals");
5098  return RemoveConstraint(ct);
5099  }
5100 
5101  // Unfortunately, because we want all intervals to appear before a constraint
5102  // that uses them, we need to move the constraint last when we merged constant
5103  // intervals.
5104  if (move_constraint_last) {
5105  changed = true;
5106  *context_->working_model->add_constraints() = *ct;
5108  return RemoveConstraint(ct);
5109  }
5110 
5111  return changed;
5112 }
5113 
5114 bool CpModelPresolver::PresolveNoOverlap2D(int c, ConstraintProto* ct) {
5115  if (context_->ModelIsUnsat()) {
5116  return false;
5117  }
5118 
5119  const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
5120  const int initial_num_boxes = proto.x_intervals_size();
5121 
5122  bool has_zero_sizes = false;
5123  bool x_constant = true;
5124  bool y_constant = true;
5125 
5126  // Filter absent boxes.
5127  int new_size = 0;
5128  std::vector<Rectangle> bounding_boxes;
5129  std::vector<int> active_boxes;
5130  for (int i = 0; i < proto.x_intervals_size(); ++i) {
5131  const int x_interval_index = proto.x_intervals(i);
5132  const int y_interval_index = proto.y_intervals(i);
5133 
5134  if (context_->ConstraintIsInactive(x_interval_index) ||
5135  context_->ConstraintIsInactive(y_interval_index)) {
5136  continue;
5137  }
5138 
5139  if (proto.boxes_with_null_area_can_overlap() &&
5140  (context_->SizeMax(x_interval_index) == 0 ||
5141  context_->SizeMax(y_interval_index) == 0)) {
5142  if (proto.boxes_with_null_area_can_overlap()) continue;
5143  has_zero_sizes = true;
5144  }
5145  ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index);
5146  ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index);
5147  bounding_boxes.push_back(
5148  {IntegerValue(context_->StartMin(x_interval_index)),
5149  IntegerValue(context_->EndMax(x_interval_index)),
5150  IntegerValue(context_->StartMin(y_interval_index)),
5151  IntegerValue(context_->EndMax(y_interval_index))});
5152  active_boxes.push_back(new_size);
5153  new_size++;
5154 
5155  if (x_constant && !context_->IntervalIsConstant(x_interval_index)) {
5156  x_constant = false;
5157  }
5158  if (y_constant && !context_->IntervalIsConstant(y_interval_index)) {
5159  y_constant = false;
5160  }
5161  }
5162 
5163  std::vector<absl::Span<int>> components = GetOverlappingRectangleComponents(
5164  bounding_boxes, absl::MakeSpan(active_boxes));
5165  if (components.size() > 1) {
5166  for (const absl::Span<int> boxes : components) {
5167  if (boxes.size() <= 1) continue;
5168 
5169  NoOverlap2DConstraintProto* new_no_overlap_2d =
5170  context_->working_model->add_constraints()->mutable_no_overlap_2d();
5171  for (const int b : boxes) {
5172  new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
5173  new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
5174  }
5175  }
5177  context_->UpdateRuleStats("no_overlap_2d: split into disjoint components");
5178  return RemoveConstraint(ct);
5179  }
5180 
5181  if (!has_zero_sizes && (x_constant || y_constant)) {
5182  context_->UpdateRuleStats(
5183  "no_overlap_2d: a dimension is constant, splitting into many no "
5184  "overlaps");
5185  std::vector<IndexedInterval> indexed_intervals;
5186  for (int i = 0; i < new_size; ++i) {
5187  int x = proto.x_intervals(i);
5188  int y = proto.y_intervals(i);
5189  if (x_constant) std::swap(x, y);
5190  indexed_intervals.push_back({x, IntegerValue(context_->StartMin(y)),
5191  IntegerValue(context_->EndMax(y))});
5192  }
5193  std::vector<std::vector<int>> no_overlaps;
5194  ConstructOverlappingSets(/*already_sorted=*/false, &indexed_intervals,
5195  &no_overlaps);
5196  for (const std::vector<int>& no_overlap : no_overlaps) {
5197  ConstraintProto* new_ct = context_->working_model->add_constraints();
5198  // Unfortunately, the Assign() method does not work in or-tools as the
5199  // protobuf int32_t type is not the int type.
5200  for (const int i : no_overlap) {
5201  new_ct->mutable_no_overlap()->add_intervals(i);
5202  }
5203  }
5205  return RemoveConstraint(ct);
5206  }
5207 
5208  if (new_size < initial_num_boxes) {
5209  context_->UpdateRuleStats("no_overlap_2d: removed inactive boxes");
5210  ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
5211  ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
5212  }
5213 
5214  if (new_size == 0) {
5215  context_->UpdateRuleStats("no_overlap_2d: no boxes");
5216  return RemoveConstraint(ct);
5217  }
5218 
5219  if (new_size == 1) {
5220  context_->UpdateRuleStats("no_overlap_2d: only one box");
5221  return RemoveConstraint(ct);
5222  }
5223 
5224  return new_size < initial_num_boxes;
5225 }
5226 
5227 namespace {
5228 LinearExpressionProto ConstantExpressionProto(int64_t value) {
5229  LinearExpressionProto expr;
5230  expr.set_offset(value);
5231  return expr;
5232 }
5233 } // namespace
5234 
5235 void CpModelPresolver::DetectDuplicateIntervals(
5236  int c, google::protobuf::RepeatedField<int32_t>* intervals) {
5237  bool changed = false;
5238  const int size = intervals->size();
5239  for (int i = 0; i < size; ++i) {
5240  const int index = (*intervals)[i];
5241  const int new_index = context_->GetIntervalRepresentative(index);
5242  if (index != new_index) {
5243  changed = true;
5244  intervals->Set(i, new_index);
5245  }
5246  }
5247  if (changed) context_->UpdateConstraintVariableUsage(c);
5248 }
5249 
5250 bool CpModelPresolver::PresolveCumulative(ConstraintProto* ct) {
5251  if (context_->ModelIsUnsat()) return false;
5252 
5253  CumulativeConstraintProto* proto = ct->mutable_cumulative();
5254 
5255  bool changed = CanonicalizeLinearExpression(*ct, proto->mutable_capacity());
5256  for (LinearExpressionProto& exp :
5257  *(ct->mutable_cumulative()->mutable_demands())) {
5258  changed |= CanonicalizeLinearExpression(*ct, &exp);
5259  }
5260 
5261  const int64_t capacity_max = context_->MaxOf(proto->capacity());
5262 
5263  // Checks the capacity of the constraint.
5264  {
5265  bool domain_changed = false;
5266  if (!context_->IntersectDomainWith(
5267  proto->capacity(), Domain(0, capacity_max), &domain_changed)) {
5268  return true;
5269  }
5270  if (domain_changed) {
5271  context_->UpdateRuleStats("cumulative: trimmed negative capacity");
5272  }
5273  }
5274 
5275  // Merge identical intervals if the demand can be merged and is still affine.
5276  //
5277  // TODO(user): We could also merge if the first entry is constant instead of
5278  // the second one. Or if the variable used for the demand is the same.
5279  {
5280  absl::flat_hash_map<int, int> interval_to_i;
5281  int new_size = 0;
5282  for (int i = 0; i < proto->intervals_size(); ++i) {
5283  const auto [it, inserted] =
5284  interval_to_i.insert({proto->intervals(i), new_size});
5285  if (!inserted) {
5286  if (context_->IsFixed(proto->demands(i))) {
5287  const int old_index = it->second;
5288  proto->mutable_demands(old_index)->set_offset(
5289  proto->demands(old_index).offset() +
5290  context_->FixedValue(proto->demands(i)));
5291  context_->UpdateRuleStats(
5292  "cumulative: merged demand of identical interval");
5293  continue;
5294  } else {
5295  context_->UpdateRuleStats(
5296  "TODO cumulative: merged demand of identical interval");
5297  }
5298  }
5299  proto->set_intervals(new_size, proto->intervals(i));
5300  *proto->mutable_demands(new_size) = proto->demands(i);
5301  ++new_size;
5302  }
5303  if (new_size < proto->intervals_size()) {
5304  changed = true;
5305  proto->mutable_intervals()->Truncate(new_size);
5306  proto->mutable_demands()->erase(
5307  proto->mutable_demands()->begin() + new_size,
5308  proto->mutable_demands()->end());
5309  }
5310  }
5311 
5312  // Filter absent intervals, or zero demands, or demand incompatible with the
5313  // capacity.
5314  {
5315  int new_size = 0;
5316  int num_zero_demand_removed = 0;
5317  int num_zero_size_removed = 0;
5318  int num_incompatible_intervals = 0;
5319  for (int i = 0; i < proto->intervals_size(); ++i) {
5320  if (context_->ConstraintIsInactive(proto->intervals(i))) continue;
5321 
5322  const LinearExpressionProto& demand_expr = proto->demands(i);
5323  const int64_t demand_max = context_->MaxOf(demand_expr);
5324  if (demand_max == 0) {
5325  num_zero_demand_removed++;
5326  continue;
5327  }
5328 
5329  const int interval_index = proto->intervals(i);
5330  if (context_->SizeMax(interval_index) == 0) {
5331  // Size 0 intervals cannot contribute to a cumulative.
5332  num_zero_size_removed++;
5333  continue;
5334  }
5335 
5336  const int64_t start_min = context_->StartMin(interval_index);
5337  const int64_t end_max = context_->EndMax(interval_index);
5338  if (start_min > end_max ||
5339  (context_->SizeMin(interval_index) > 0 &&
5340  context_->MinOf(demand_expr) > capacity_max)) {
5341  if (context_->ConstraintIsOptional(interval_index)) {
5342  ConstraintProto* interval_ct =
5343  context_->working_model->mutable_constraints(interval_index);
5344  DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
5345  const int literal = interval_ct->enforcement_literal(0);
5346  if (!context_->SetLiteralToFalse(literal)) {
5347  return true;
5348  }
5349  num_incompatible_intervals++;
5350  continue;
5351  } else { // Interval is performed.
5352  return context_->NotifyThatModelIsUnsat(
5353  "cumulative: performed demand exceeds capacity.");
5354  }
5355  }
5356 
5357  proto->set_intervals(new_size, interval_index);
5358  *proto->mutable_demands(new_size) = proto->demands(i);
5359  new_size++;
5360  }
5361 
5362  if (new_size < proto->intervals_size()) {
5363  changed = true;
5364  proto->mutable_intervals()->Truncate(new_size);
5365  proto->mutable_demands()->erase(
5366  proto->mutable_demands()->begin() + new_size,
5367  proto->mutable_demands()->end());
5368  }
5369 
5370  if (num_zero_demand_removed > 0) {
5371  context_->UpdateRuleStats(
5372  "cumulative: removed intervals with no demands");
5373  }
5374  if (num_zero_size_removed > 0) {
5375  context_->UpdateRuleStats(
5376  "cumulative: removed intervals with a size of zero");
5377  }
5378  if (num_incompatible_intervals > 0) {
5379  context_->UpdateRuleStats(
5380  "cumulative: removed intervals that can't be performed");
5381  }
5382  }
5383 
5384  // Checks the compatibility of demands w.r.t. the capacity.
5385  {
5386  for (int i = 0; i < proto->demands_size(); ++i) {
5387  const int interval = proto->intervals(i);
5388  const LinearExpressionProto& demand_expr = proto->demands(i);
5389  if (context_->ConstraintIsOptional(interval)) continue;
5390  bool domain_changed = false;
5391  if (!context_->IntersectDomainWith(demand_expr, {0, capacity_max},
5392  &domain_changed)) {
5393  return true;
5394  }
5395  if (domain_changed) {
5396  context_->UpdateRuleStats(
5397  "cumulative: fit demand in [0..capacity_max]");
5398  }
5399  }
5400  }
5401 
5402  // Split constraints in disjoint sets.
5403  //
5404  // TODO(user): This can be improved:
5405  // If we detect bridge nodes in the graph of overlapping components, we
5406  // can split the graph around the bridge and add the bridge node to both
5407  // side. Note that if it we take into account precedences between intervals,
5408  // we can detect more bridges.
5409  if (proto->intervals_size() > 1) {
5410  std::vector<IndexedInterval> indexed_intervals;
5411  for (int i = 0; i < proto->intervals().size(); ++i) {
5412  const int index = proto->intervals(i);
5413  indexed_intervals.push_back({i, IntegerValue(context_->StartMin(index)),
5414  IntegerValue(context_->EndMax(index))});
5415  }
5416  std::vector<std::vector<int>> components;
5417  GetOverlappingIntervalComponents(&indexed_intervals, &components);
5418 
5419  if (components.size() > 1) {
5420  for (const std::vector<int>& component : components) {
5421  CumulativeConstraintProto* new_cumulative =
5422  context_->working_model->add_constraints()->mutable_cumulative();
5423  for (const int i : component) {
5424  new_cumulative->add_intervals(proto->intervals(i));
5425  *new_cumulative->add_demands() = proto->demands(i);
5426  }
5427  *new_cumulative->mutable_capacity() = proto->capacity();
5428  }
5430  context_->UpdateRuleStats("cumulative: split into disjoint components");
5431  return RemoveConstraint(ct);
5432  }
5433  }
5434 
5435  // TODO(user): move the algorithmic part of what we do below in a
5436  // separate function to unit test it more properly.
5437  {
5438  // Build max load profiles.
5439  absl::btree_map<int64_t, int64_t> time_to_demand_deltas;
5440  const int64_t capacity_min = context_->MinOf(proto->capacity());
5441  for (int i = 0; i < proto->intervals_size(); ++i) {
5442  const int interval_index = proto->intervals(i);
5443  const int64_t demand_max = context_->MaxOf(proto->demands(i));
5444  time_to_demand_deltas[context_->StartMin(interval_index)] += demand_max;
5445  time_to_demand_deltas[context_->EndMax(interval_index)] -= demand_max;
5446  }
5447 
5448  // We construct the profile which correspond to a set of [time, next_time)
5449  // to max_profile height. And for each time in our discrete set of
5450  // time_exprs (all the start_min and end_max) we count for how often the
5451  // height was above the capacity before this time.
5452  //
5453  // This rely on the iteration in sorted order.
5454  int num_possible_overloads = 0;
5455  int64_t current_load = 0;
5456  absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before;
5457  for (const auto& it : time_to_demand_deltas) {
5458  num_possible_overloads_before[it.first] = num_possible_overloads;
5459  current_load += it.second;
5460  if (current_load > capacity_min) {
5461  ++num_possible_overloads;
5462  }
5463  }
5464  CHECK_EQ(current_load, 0);
5465 
5466  // No possible overload with the min capacity.
5467  if (num_possible_overloads == 0) {
5468  context_->UpdateRuleStats(
5469  "cumulative: max profile is always under the min capacity");
5470  return RemoveConstraint(ct);
5471  }
5472 
5473  // An interval that does not intersect with the potential_overload_domains
5474  // cannot contribute to a conflict. We can safely remove them.
5475  //
5476  // This is an extension of the presolve rule from
5477  // "Presolving techniques and linear relaxations for cumulative
5478  // scheduling" PhD dissertation by Stefan Heinz, ZIB.
5479  int new_size = 0;
5480  for (int i = 0; i < proto->intervals_size(); ++i) {
5481  const int index = proto->intervals(i);
5482  const int64_t start_min = context_->StartMin(index);
5483  const int64_t end_max = context_->EndMax(index);
5484 
5485  // In the cumulative, if start_min == end_max, the interval is of size
5486  // zero and we can just ignore it. If the model is unsat or the interval
5487  // must be absent (start_min > end_max), this should be dealt with at
5488  // the interval constraint level and we can just remove it from here.
5489  //
5490  // Note that currently, the interpretation for interval of length zero
5491  // is different for the no-overlap constraint.
5492  if (start_min >= end_max) continue;
5493 
5494  // Note that by construction, both point are in the map. The formula
5495  // counts exactly for how many time_exprs in [start_min, end_max), we have
5496  // a point in our discrete set of time that exceeded the capacity. Because
5497  // we included all the relevant points, this works.
5498  const int num_diff = num_possible_overloads_before.at(end_max) -
5499  num_possible_overloads_before.at(start_min);
5500  if (num_diff == 0) continue;
5501 
5502  proto->set_intervals(new_size, proto->intervals(i));
5503  *proto->mutable_demands(new_size) = proto->demands(i);
5504  new_size++;
5505  }
5506 
5507  if (new_size < proto->intervals_size()) {
5508  changed = true;
5509  proto->mutable_intervals()->Truncate(new_size);
5510  proto->mutable_demands()->erase(
5511  proto->mutable_demands()->begin() + new_size,
5512  proto->mutable_demands()->end());
5513  context_->UpdateRuleStats(
5514  "cumulative: remove never conflicting intervals.");
5515  }
5516  }
5517 
5518  if (proto->intervals().empty()) {
5519  context_->UpdateRuleStats("cumulative: no intervals");
5520  return RemoveConstraint(ct);
5521  }
5522 
5523  {
5524  int64_t max_of_performed_demand_mins = 0;
5525  int64_t sum_of_max_demands = 0;
5526  for (int i = 0; i < proto->intervals_size(); ++i) {
5527  const ConstraintProto& interval_ct =
5528  context_->working_model->constraints(proto->intervals(i));
5529 
5530  const LinearExpressionProto& demand_expr = proto->demands(i);
5531  sum_of_max_demands += context_->MaxOf(demand_expr);
5532 
5533  if (interval_ct.enforcement_literal().empty()) {
5534  max_of_performed_demand_mins = std::max(max_of_performed_demand_mins,
5535  context_->MinOf(demand_expr));
5536  }
5537  }
5538 
5539  const LinearExpressionProto& capacity_expr = proto->capacity();
5540  if (max_of_performed_demand_mins > context_->MinOf(capacity_expr)) {
5541  context_->UpdateRuleStats("cumulative: propagate min capacity.");
5542  if (!context_->IntersectDomainWith(
5543  capacity_expr, Domain(max_of_performed_demand_mins,
5545  return true;
5546  }
5547  }
5548 
5549  if (max_of_performed_demand_mins > context_->MaxOf(capacity_expr)) {
5550  context_->UpdateRuleStats("cumulative: cannot fit performed demands");
5551  return context_->NotifyThatModelIsUnsat();
5552  }
5553 
5554  if (sum_of_max_demands <= context_->MinOf(capacity_expr)) {
5555  context_->UpdateRuleStats("cumulative: capacity exceeds sum of demands");
5556  return RemoveConstraint(ct);
5557  }
5558  }
5559 
5560  if (context_->IsFixed(proto->capacity())) {
5561  int64_t gcd = 0;
5562  for (int i = 0; i < ct->cumulative().demands_size(); ++i) {
5563  const LinearExpressionProto& demand_expr = ct->cumulative().demands(i);
5564  if (!context_->IsFixed(demand_expr)) {
5565  // Abort if the demand is not fixed.
5566  gcd = 1;
5567  break;
5568  }
5569  gcd = MathUtil::GCD64(gcd, context_->MinOf(demand_expr));
5570  if (gcd == 1) break;
5571  }
5572  if (gcd > 1) {
5573  changed = true;
5574  for (int i = 0; i < ct->cumulative().demands_size(); ++i) {
5575  const int64_t demand = context_->MinOf(ct->cumulative().demands(i));
5576  *proto->mutable_demands(i) = ConstantExpressionProto(demand / gcd);
5577  }
5578 
5579  const int64_t old_capacity = context_->MinOf(proto->capacity());
5580  *proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd);
5581  context_->UpdateRuleStats(
5582  "cumulative: divide demands and capacity by gcd");
5583  }
5584  }
5585 
5586  const int num_intervals = proto->intervals_size();
5587  const LinearExpressionProto& capacity_expr = proto->capacity();
5588 
5589  std::vector<LinearExpressionProto> start_exprs(num_intervals);
5590 
5591  int num_duration_one = 0;
5592  int num_greater_half_capacity = 0;
5593 
5594  bool has_optional_interval = false;
5595  for (int i = 0; i < num_intervals; ++i) {
5596  const int index = proto->intervals(i);
5597  // TODO(user): adapt in the presence of optional intervals.
5598  if (context_->ConstraintIsOptional(index)) has_optional_interval = true;
5599  const ConstraintProto& ct =
5600  context_->working_model->constraints(proto->intervals(i));
5601  const IntervalConstraintProto& interval = ct.interval();
5602  start_exprs[i] = interval.start();
5603 
5604  const LinearExpressionProto& demand_expr = proto->demands(i);
5605  if (context_->SizeMin(index) == 1 && context_->SizeMax(index) == 1) {
5606  num_duration_one++;
5607  }
5608  if (context_->SizeMin(index) == 0) {
5609  // The behavior for zero-duration interval is currently not the same in
5610  // the no-overlap and the cumulative constraint.
5611  return changed;
5612  }
5613  const int64_t demand_min = context_->MinOf(demand_expr);
5614  const int64_t demand_max = context_->MaxOf(demand_expr);
5615  if (demand_min > capacity_max / 2) {
5616  num_greater_half_capacity++;
5617  }
5618  if (demand_min > capacity_max) {
5619  context_->UpdateRuleStats("cumulative: demand_min exceeds capacity max");
5620  if (!context_->ConstraintIsOptional(index)) {
5621  return context_->NotifyThatModelIsUnsat();
5622  } else {
5623  CHECK_EQ(ct.enforcement_literal().size(), 1);
5624  if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
5625  return true;
5626  }
5627  }
5628  return changed;
5629  } else if (demand_max > capacity_max) {
5630  if (ct.enforcement_literal().empty()) {
5631  context_->UpdateRuleStats(
5632  "cumulative: demand_max exceeds capacity max.");
5633  if (!context_->IntersectDomainWith(
5634  demand_expr,
5635  Domain(std::numeric_limits<int64_t>::min(), capacity_max))) {
5636  return true;
5637  }
5638  } else {
5639  // TODO(user): we abort because we cannot convert this to a no_overlap
5640  // for instance.
5641  context_->UpdateRuleStats(
5642  "cumulative: demand_max of optional interval exceeds capacity.");
5643  return changed;
5644  }
5645  }
5646  }
5647  if (num_greater_half_capacity == num_intervals) {
5648  if (num_duration_one == num_intervals && !has_optional_interval) {
5649  context_->UpdateRuleStats("cumulative: convert to all_different");
5650  ConstraintProto* new_ct = context_->working_model->add_constraints();
5651  auto* arg = new_ct->mutable_all_diff();
5652  for (const LinearExpressionProto& expr : start_exprs) {
5653  *arg->add_exprs() = expr;
5654  }
5655  if (!context_->IsFixed(capacity_expr)) {
5656  const int64_t capacity_min = context_->MinOf(capacity_expr);
5657  for (const LinearExpressionProto& expr : proto->demands()) {
5658  if (capacity_min >= context_->MaxOf(expr)) continue;
5659  LinearConstraintProto* fit =
5660  context_->working_model->add_constraints()->mutable_linear();
5661  fit->add_domain(0);
5662  fit->add_domain(std::numeric_limits<int64_t>::max());
5663  AddLinearExpressionToLinearConstraint(capacity_expr, 1, fit);
5665  }
5666  }
5668  return RemoveConstraint(ct);
5669  } else {
5670  context_->UpdateRuleStats("cumulative: convert to no_overlap");
5671  // Before we remove the cumulative, add constraints to enforce that the
5672  // capacity is greater than the demand of any performed intervals.
5673  for (int i = 0; i < proto->demands_size(); ++i) {
5674  const LinearExpressionProto& demand_expr = proto->demands(i);
5675  const int64_t demand_max = context_->MaxOf(demand_expr);
5676  if (demand_max > context_->MinOf(capacity_expr)) {
5677  ConstraintProto* capacity_gt =
5678  context_->working_model->add_constraints();
5679  *capacity_gt->mutable_enforcement_literal() =
5680  context_->working_model->constraints(proto->intervals(i))
5681  .enforcement_literal();
5682  capacity_gt->mutable_linear()->add_domain(0);
5683  capacity_gt->mutable_linear()->add_domain(
5685  AddLinearExpressionToLinearConstraint(capacity_expr, 1,
5686  capacity_gt->mutable_linear());
5687  AddLinearExpressionToLinearConstraint(demand_expr, -1,
5688  capacity_gt->mutable_linear());
5689  }
5690  }
5691 
5692  ConstraintProto* new_ct = context_->working_model->add_constraints();
5693  auto* arg = new_ct->mutable_no_overlap();
5694  for (const int interval : proto->intervals()) {
5695  arg->add_intervals(interval);
5696  }
5698  return RemoveConstraint(ct);
5699  }
5700  }
5701 
5702  return changed;
5703 }
5704 
5705 bool CpModelPresolver::PresolveRoutes(ConstraintProto* ct) {
5706  if (context_->ModelIsUnsat()) return false;
5707  if (HasEnforcementLiteral(*ct)) return false;
5708  RoutesConstraintProto& proto = *ct->mutable_routes();
5709 
5710  const int old_size = proto.literals_size();
5711  int new_size = 0;
5712  std::vector<bool> has_incoming_or_outgoing_arcs;
5713  const int num_arcs = proto.literals_size();
5714  for (int i = 0; i < num_arcs; ++i) {
5715  const int ref = proto.literals(i);
5716  const int tail = proto.tails(i);
5717  const int head = proto.heads(i);
5718 
5719  if (tail >= has_incoming_or_outgoing_arcs.size()) {
5720  has_incoming_or_outgoing_arcs.resize(tail + 1, false);
5721  }
5722  if (head >= has_incoming_or_outgoing_arcs.size()) {
5723  has_incoming_or_outgoing_arcs.resize(head + 1, false);
5724  }
5725 
5726  if (context_->LiteralIsFalse(ref)) {
5727  context_->UpdateRuleStats("routes: removed false arcs");
5728  continue;
5729  }
5730  proto.set_literals(new_size, ref);
5731  proto.set_tails(new_size, tail);
5732  proto.set_heads(new_size, head);
5733  ++new_size;
5734  has_incoming_or_outgoing_arcs[tail] = true;
5735  has_incoming_or_outgoing_arcs[head] = true;
5736  }
5737 
5738  if (old_size > 0 && new_size == 0) {
5739  // A routes constraint cannot have a self loop on 0. Therefore, if there
5740  // were arcs, it means it contains non zero nodes. Without arc, the
5741  // constraint is unfeasible.
5742  return context_->NotifyThatModelIsUnsat(
5743  "routes: graph with nodes and no arcs");
5744  }
5745 
5746  // if a node misses an incomping or outgoing arc, the model is trivially
5747  // infeasible.
5748  for (int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) {
5749  if (!has_incoming_or_outgoing_arcs[n]) {
5750  return context_->NotifyThatModelIsUnsat(absl::StrCat(
5751  "routes: node ", n, " misses incoming or outgoing arcs"));
5752  }
5753  }
5754 
5755  if (new_size < num_arcs) {
5756  proto.mutable_literals()->Truncate(new_size);
5757  proto.mutable_tails()->Truncate(new_size);
5758  proto.mutable_heads()->Truncate(new_size);
5759  return true;
5760  }
5761 
5762  return false;
5763 }
5764 
5765 bool CpModelPresolver::PresolveCircuit(ConstraintProto* ct) {
5766  if (context_->ModelIsUnsat()) return false;
5767  if (HasEnforcementLiteral(*ct)) return false;
5768  CircuitConstraintProto& proto = *ct->mutable_circuit();
5769 
5770  // The indexing might not be dense, so fix that first.
5771  ReindexArcs(ct->mutable_circuit()->mutable_tails(),
5772  ct->mutable_circuit()->mutable_heads());
5773 
5774  // Convert the flat structure to a graph, note that we includes all the arcs
5775  // here (even if they are at false).
5776  std::vector<std::vector<int>> incoming_arcs;
5777  std::vector<std::vector<int>> outgoing_arcs;
5778  int num_nodes = 0;
5779  const int num_arcs = proto.literals_size();
5780  for (int i = 0; i < num_arcs; ++i) {
5781  const int ref = proto.literals(i);
5782  const int tail = proto.tails(i);
5783  const int head = proto.heads(i);
5784  num_nodes = std::max(num_nodes, std::max(tail, head) + 1);
5785  if (std::max(tail, head) >= incoming_arcs.size()) {
5786  incoming_arcs.resize(std::max(tail, head) + 1);
5787  outgoing_arcs.resize(std::max(tail, head) + 1);
5788  }
5789  incoming_arcs[head].push_back(ref);
5790  outgoing_arcs[tail].push_back(ref);
5791  }
5792 
5793  // All the node must have some incoming and outgoing arcs.
5794  for (int i = 0; i < num_nodes; ++i) {
5795  if (incoming_arcs[i].empty() || outgoing_arcs[i].empty()) {
5796  return MarkConstraintAsFalse(ct);
5797  }
5798  }
5799 
5800  // Note that it is important to reach the fixed point here:
5801  // One arc at true, then all other arc at false. This is because we rely
5802  // on this in case the circuit is fully specified below.
5803  //
5804  // TODO(user): Use a better complexity if needed.
5805  bool loop_again = true;
5806  int num_fixed_at_true = 0;
5807  while (loop_again) {
5808  loop_again = false;
5809  for (const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
5810  for (const std::vector<int>& refs : *node_to_refs) {
5811  if (refs.size() == 1) {
5812  if (!context_->LiteralIsTrue(refs.front())) {
5813  ++num_fixed_at_true;
5814  if (!context_->SetLiteralToTrue(refs.front())) return true;
5815  }
5816  continue;
5817  }
5818 
5819  // At most one true, so if there is one, mark all the other to false.
5820  int num_true = 0;
5821  int true_ref;
5822  for (const int ref : refs) {
5823  if (context_->LiteralIsTrue(ref)) {
5824  ++num_true;
5825  true_ref = ref;
5826  break;
5827  }
5828  }
5829  if (num_true > 1) {
5830  return context_->NotifyThatModelIsUnsat();
5831  }
5832  if (num_true == 1) {
5833  for (const int ref : refs) {
5834  if (ref != true_ref) {
5835  if (!context_->IsFixed(ref)) {
5836  context_->UpdateRuleStats("circuit: set literal to false.");
5837  loop_again = true;
5838  }
5839  if (!context_->SetLiteralToFalse(ref)) return true;
5840  }
5841  }
5842  }
5843  }
5844  }
5845  }
5846  if (num_fixed_at_true > 0) {
5847  context_->UpdateRuleStats("circuit: fixed singleton arcs.");
5848  }
5849 
5850  // Remove false arcs.
5851  int new_size = 0;
5852  int num_true = 0;
5853  int circuit_start = -1;
5854  std::vector<int> next(num_nodes, -1);
5855  std::vector<int> new_in_degree(num_nodes, 0);
5856  std::vector<int> new_out_degree(num_nodes, 0);
5857  for (int i = 0; i < num_arcs; ++i) {
5858  const int ref = proto.literals(i);
5859  if (context_->LiteralIsFalse(ref)) continue;
5860  if (context_->LiteralIsTrue(ref)) {
5861  if (next[proto.tails(i)] != -1) {
5862  return context_->NotifyThatModelIsUnsat();
5863  }
5864  next[proto.tails(i)] = proto.heads(i);
5865  if (proto.tails(i) != proto.heads(i)) {
5866  circuit_start = proto.tails(i);
5867  }
5868  ++num_true;
5869  }
5870  ++new_out_degree[proto.tails(i)];
5871  ++new_in_degree[proto.heads(i)];
5872  proto.set_tails(new_size, proto.tails(i));
5873  proto.set_heads(new_size, proto.heads(i));
5874  proto.set_literals(new_size, ref);
5875  ++new_size;
5876  }
5877 
5878  // Detect infeasibility due to a node having no more incoming or outgoing arc.
5879  // This is a bit tricky because for now the meaning of the constraint says
5880  // that all nodes that appear in at least one of the arcs must be in the
5881  // circuit or have a self-arc. So if any such node ends up with an incoming or
5882  // outgoing degree of zero once we remove false arcs then the constraint is
5883  // infeasible!
5884  for (int i = 0; i < num_nodes; ++i) {
5885  if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
5886  return context_->NotifyThatModelIsUnsat();
5887  }
5888  }
5889 
5890  // Test if a subcircuit is already present.
5891  if (circuit_start != -1) {
5892  std::vector<bool> visited(num_nodes, false);
5893  int current = circuit_start;
5894  while (current != -1 && !visited[current]) {
5895  visited[current] = true;
5896  current = next[current];
5897  }
5898  if (current == circuit_start) {
5899  // We have a sub-circuit! mark all other arc false except self-loop not in
5900  // circuit.
5901  std::vector<bool> has_self_arc(num_nodes, false);
5902  for (int i = 0; i < num_arcs; ++i) {
5903  if (visited[proto.tails(i)]) continue;
5904  if (proto.tails(i) == proto.heads(i)) {
5905  has_self_arc[proto.tails(i)] = true;
5906  if (!context_->SetLiteralToTrue(proto.literals(i))) return true;
5907  } else {
5908  if (!context_->SetLiteralToFalse(proto.literals(i))) return true;
5909  }
5910  }
5911  for (int n = 0; n < num_nodes; ++n) {
5912  if (!visited[n] && !has_self_arc[n]) {
5913  // We have a subircuit, but it doesn't cover all the mandatory nodes.
5914  return MarkConstraintAsFalse(ct);
5915  }
5916  }
5917  context_->UpdateRuleStats("circuit: fully specified.");
5918  return RemoveConstraint(ct);
5919  }
5920  } else {
5921  // All self loop?
5922  if (num_true == new_size) {
5923  context_->UpdateRuleStats("circuit: empty circuit.");
5924  return RemoveConstraint(ct);
5925  }
5926  }
5927 
5928  // Look for in/out-degree of two, this will imply that one of the indicator
5929  // Boolean is equal to the negation of the other.
5930  for (int i = 0; i < num_nodes; ++i) {
5931  for (const std::vector<int>* arc_literals :
5932  {&incoming_arcs[i], &outgoing_arcs[i]}) {
5933  std::vector<int> literals;
5934  for (const int ref : *arc_literals) {
5935  if (context_->LiteralIsFalse(ref)) continue;
5936  if (context_->LiteralIsTrue(ref)) {
5937  literals.clear();
5938  break;
5939  }
5940  literals.push_back(ref);
5941  }
5942  if (literals.size() == 2 && literals[0] != NegatedRef(literals[1])) {
5943  context_->UpdateRuleStats("circuit: degree 2");
5944  context_->StoreBooleanEqualityRelation(literals[0],
5945  NegatedRef(literals[1]));
5946  }
5947  }
5948  }
5949 
5950  // Truncate the circuit and return.
5951  if (new_size < num_arcs) {
5952  proto.mutable_tails()->Truncate(new_size);
5953  proto.mutable_heads()->Truncate(new_size);
5954  proto.mutable_literals()->Truncate(new_size);
5955  context_->UpdateRuleStats("circuit: removed false arcs.");
5956  return true;
5957  }
5958  return false;
5959 }
5960 
5961 bool CpModelPresolver::PresolveAutomaton(ConstraintProto* ct) {
5962  if (context_->ModelIsUnsat()) return false;
5963  if (HasEnforcementLiteral(*ct)) return false;
5964  AutomatonConstraintProto& proto = *ct->mutable_automaton();
5965  if (proto.vars_size() == 0 || proto.transition_label_size() == 0) {
5966  return false;
5967  }
5968 
5969  bool all_have_same_affine_relation = true;
5970  std::vector<AffineRelation::Relation> affine_relations;
5971  for (int v = 0; v < proto.vars_size(); ++v) {
5972  const int var = ct->automaton().vars(v);
5973  const AffineRelation::Relation r = context_->GetAffineRelation(var);
5974  affine_relations.push_back(r);
5975  if (r.representative == var) {
5976  all_have_same_affine_relation = false;
5977  break;
5978  }
5979  if (v > 0 && (r.coeff != affine_relations[v - 1].coeff ||
5980  r.offset != affine_relations[v - 1].offset)) {
5981  all_have_same_affine_relation = false;
5982  break;
5983  }
5984  }
5985 
5986  if (all_have_same_affine_relation) { // Unscale labels.
5987  for (int v = 0; v < proto.vars_size(); ++v) {
5988  proto.set_vars(v, affine_relations[v].representative);
5989  }
5990  const AffineRelation::Relation rep = affine_relations.front();
5991  int new_size = 0;
5992  for (int t = 0; t < proto.transition_tail_size(); ++t) {
5993  const int64_t label = proto.transition_label(t);
5994  int64_t inverse_label = (label - rep.offset) / rep.coeff;
5995  if (inverse_label * rep.coeff + rep.offset == label) {
5996  if (new_size != t) {
5997  proto.set_transition_tail(new_size, proto.transition_tail(t));
5998  proto.set_transition_head(new_size, proto.transition_head(t));
5999  }
6000  proto.set_transition_label(new_size, inverse_label);
6001  new_size++;
6002  }
6003  }
6004  if (new_size < proto.transition_tail_size()) {
6005  proto.mutable_transition_tail()->Truncate(new_size);
6006  proto.mutable_transition_label()->Truncate(new_size);
6007  proto.mutable_transition_head()->Truncate(new_size);
6008  context_->UpdateRuleStats("automaton: remove invalid transitions");
6009  }
6010  context_->UpdateRuleStats("automaton: unscale all affine labels");
6011  return true;
6012  }
6013 
6014  std::vector<absl::flat_hash_set<int64_t>> reachable_states;
6015  std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
6016  PropagateAutomaton(proto, *context_, &reachable_states, &reachable_labels);
6017 
6018  // Filter domains and compute the union of all relevant labels.
6019  bool removed_values = false;
6020  Domain hull;
6021  for (int time = 0; time < reachable_labels.size(); ++time) {
6022  if (!context_->IntersectDomainWith(
6023  proto.vars(time),
6025  {reachable_labels[time].begin(), reachable_labels[time].end()}),
6026  &removed_values)) {
6027  return false;
6028  }
6029  hull = hull.UnionWith(context_->DomainOf(proto.vars(time)));
6030  }
6031  if (removed_values) {
6032  context_->UpdateRuleStats("automaton: reduced variable domains");
6033  }
6034 
6035  // Only keep relevant transitions.
6036  int new_size = 0;
6037  for (int t = 0; t < proto.transition_tail_size(); ++t) {
6038  const int64_t label = proto.transition_label(t);
6039  if (hull.Contains(label)) {
6040  if (new_size != t) {
6041  proto.set_transition_tail(new_size, proto.transition_tail(t));
6042  proto.set_transition_label(new_size, label);
6043  proto.set_transition_head(new_size, proto.transition_head(t));
6044  }
6045  new_size++;
6046  }
6047  }
6048  if (new_size < proto.transition_tail_size()) {
6049  proto.mutable_transition_tail()->Truncate(new_size);
6050  proto.mutable_transition_label()->Truncate(new_size);
6051  proto.mutable_transition_head()->Truncate(new_size);
6052  context_->UpdateRuleStats("automaton: remove invalid transitions");
6053  return false;
6054  }
6055 
6056  return false;
6057 }
6058 
6059 bool CpModelPresolver::PresolveReservoir(ConstraintProto* ct) {
6060  if (context_->ModelIsUnsat()) return false;
6061  if (HasEnforcementLiteral(*ct)) return false;
6062 
6063  ReservoirConstraintProto& proto = *ct->mutable_reservoir();
6064  bool changed = false;
6065  for (LinearExpressionProto& exp : *(proto.mutable_time_exprs())) {
6066  changed |= CanonicalizeLinearExpression(*ct, &exp);
6067  }
6068  for (LinearExpressionProto& exp : *(proto.mutable_level_changes())) {
6069  changed |= CanonicalizeLinearExpression(*ct, &exp);
6070  }
6071 
6072  if (proto.active_literals().empty()) {
6073  const int true_literal = context_->GetTrueLiteral();
6074  for (int i = 0; i < proto.time_exprs_size(); ++i) {
6075  proto.add_active_literals(true_literal);
6076  }
6077  changed = true;
6078  }
6079 
6080  const auto& demand_is_null = [&](int i) {
6081  return (context_->IsFixed(proto.level_changes(i)) &&
6082  context_->FixedValue(proto.level_changes(i)) == 0) ||
6083  context_->LiteralIsFalse(proto.active_literals(i));
6084  };
6085 
6086  // Remove zero level_changes, and inactive events.
6087  int num_zeros = 0;
6088  for (int i = 0; i < proto.level_changes_size(); ++i) {
6089  if (demand_is_null(i)) num_zeros++;
6090  }
6091 
6092  if (num_zeros > 0) { // Remove null events
6093  changed = true;
6094  int new_size = 0;
6095  for (int i = 0; i < proto.level_changes_size(); ++i) {
6096  if (demand_is_null(i)) continue;
6097  *proto.mutable_level_changes(new_size) = proto.level_changes(i);
6098  *proto.mutable_time_exprs(new_size) = proto.time_exprs(i);
6099  proto.set_active_literals(new_size, proto.active_literals(i));
6100  new_size++;
6101  }
6102 
6103  proto.mutable_level_changes()->erase(
6104  proto.mutable_level_changes()->begin() + new_size,
6105  proto.mutable_level_changes()->end());
6106  proto.mutable_time_exprs()->erase(
6107  proto.mutable_time_exprs()->begin() + new_size,
6108  proto.mutable_time_exprs()->end());
6109  proto.mutable_active_literals()->Truncate(new_size);
6110 
6111  context_->UpdateRuleStats(
6112  "reservoir: remove zero level_changes or inactive events.");
6113  }
6114 
6115  // The rest of the presolve only applies if all demands are fixed.
6116  for (const LinearExpressionProto& level_change : proto.level_changes()) {
6117  if (!context_->IsFixed(level_change)) return changed;
6118  }
6119 
6120  const int num_events = proto.level_changes_size();
6121  int64_t gcd = proto.level_changes().empty()
6122  ? 0
6123  : std::abs(context_->FixedValue(proto.level_changes(0)));
6124  int num_positives = 0;
6125  int num_negatives = 0;
6126  int64_t max_sum_of_positive_level_changes = 0;
6127  int64_t min_sum_of_negative_level_changes = 0;
6128  for (int i = 0; i < num_events; ++i) {
6129  const int64_t demand = context_->FixedValue(proto.level_changes(i));
6130  gcd = MathUtil::GCD64(gcd, std::abs(demand));
6131  if (demand > 0) {
6132  num_positives++;
6133  max_sum_of_positive_level_changes += demand;
6134  } else {
6135  DCHECK_LT(demand, 0);
6136  num_negatives++;
6137  min_sum_of_negative_level_changes += demand;
6138  }
6139  }
6140 
6141  if (min_sum_of_negative_level_changes >= proto.min_level() &&
6142  max_sum_of_positive_level_changes <= proto.max_level()) {
6143  context_->UpdateRuleStats("reservoir: always feasible");
6144  return RemoveConstraint(ct);
6145  }
6146 
6147  if (min_sum_of_negative_level_changes > proto.max_level() ||
6148  max_sum_of_positive_level_changes < proto.min_level()) {
6149  context_->UpdateRuleStats("reservoir: trivially infeasible");
6150  return context_->NotifyThatModelIsUnsat();
6151  }
6152 
6153  if (min_sum_of_negative_level_changes > proto.min_level()) {
6154  proto.set_min_level(min_sum_of_negative_level_changes);
6155  context_->UpdateRuleStats(
6156  "reservoir: increase min_level to reachable value");
6157  }
6158 
6159  if (max_sum_of_positive_level_changes < proto.max_level()) {
6160  proto.set_max_level(max_sum_of_positive_level_changes);
6161  context_->UpdateRuleStats("reservoir: reduce max_level to reachable value");
6162  }
6163 
6164  if (proto.min_level() <= 0 && proto.max_level() >= 0 &&
6165  (num_positives == 0 || num_negatives == 0)) {
6166  // If all level_changes have the same sign, and if the initial state is
6167  // always feasible, we do not care about the order, just the sum.
6168  auto* const sum =
6169  context_->working_model->add_constraints()->mutable_linear();
6170  int64_t fixed_contrib = 0;
6171  for (int i = 0; i < proto.level_changes_size(); ++i) {
6172  const int64_t demand = context_->FixedValue(proto.level_changes(i));
6173  DCHECK_NE(demand, 0);
6174 
6175  const int active = proto.active_literals(i);
6176  if (RefIsPositive(active)) {
6177  sum->add_vars(active);
6178  sum->add_coeffs(demand);
6179  } else {
6180  sum->add_vars(PositiveRef(active));
6181  sum->add_coeffs(-demand);
6182  fixed_contrib += demand;
6183  }
6184  }
6185  sum->add_domain(proto.min_level() - fixed_contrib);
6186  sum->add_domain(proto.max_level() - fixed_contrib);
6187  context_->UpdateRuleStats("reservoir: converted to linear");
6188  return RemoveConstraint(ct);
6189  }
6190 
6191  if (gcd > 1) {
6192  for (int i = 0; i < proto.level_changes_size(); ++i) {
6193  proto.mutable_level_changes(i)->set_offset(
6194  context_->FixedValue(proto.level_changes(i)) / gcd);
6195  proto.mutable_level_changes(i)->clear_vars();
6196  proto.mutable_level_changes(i)->clear_coeffs();
6197  }
6198 
6199  // Adjust min and max levels.
6200  // max level is always rounded down.
6201  // min level is always rounded up.
6202  const Domain reduced_domain = Domain({proto.min_level(), proto.max_level()})
6203  .InverseMultiplicationBy(gcd);
6204  proto.set_min_level(reduced_domain.Min());
6205  proto.set_max_level(reduced_domain.Max());
6206  context_->UpdateRuleStats(
6207  "reservoir: simplify level_changes and levels by gcd.");
6208  }
6209 
6210  if (num_positives == 1 && num_negatives > 0) {
6211  context_->UpdateRuleStats(
6212  "TODO reservoir: one producer, multiple consumers.");
6213  }
6214 
6215  absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set;
6216  for (int i = 0; i < proto.level_changes_size(); ++i) {
6217  const LinearExpressionProto& time = proto.time_exprs(i);
6218  const int var = context_->IsFixed(time) ? std::numeric_limits<int>::min()
6219  : time.vars(0);
6220  const int64_t coeff = context_->IsFixed(time) ? 0 : time.coeffs(0);
6221  const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple(
6222  var, coeff,
6223  context_->IsFixed(time) ? context_->FixedValue(time) : time.offset(),
6224  proto.active_literals(i));
6225  if (time_active_set.contains(key)) {
6226  context_->UpdateRuleStats("TODO reservoir: merge synchronized events.");
6227  break;
6228  } else {
6229  time_active_set.insert(key);
6230  }
6231  }
6232 
6233  return changed;
6234 }
6235 
6236 // TODO(user): It is probably more efficient to keep all the bool_and in a
6237 // global place during all the presolve, and just output them at the end
6238 // rather than modifying more than once the proto.
6239 void CpModelPresolver::ExtractBoolAnd() {
6240  absl::flat_hash_map<int, int> ref_to_bool_and;
6241  const int num_constraints = context_->working_model->constraints_size();
6242  std::vector<int> to_remove;
6243  for (int c = 0; c < num_constraints; ++c) {
6244  const ConstraintProto& ct = context_->working_model->constraints(c);
6245  if (HasEnforcementLiteral(ct)) continue;
6246 
6247  if (ct.constraint_case() == ConstraintProto::kBoolOr &&
6248  ct.bool_or().literals().size() == 2) {
6249  AddImplication(NegatedRef(ct.bool_or().literals(0)),
6250  ct.bool_or().literals(1), context_->working_model,
6251  &ref_to_bool_and);
6252  to_remove.push_back(c);
6253  continue;
6254  }
6255 
6256  if (ct.constraint_case() == ConstraintProto::kAtMostOne &&
6257  ct.at_most_one().literals().size() == 2) {
6258  AddImplication(ct.at_most_one().literals(0),
6259  NegatedRef(ct.at_most_one().literals(1)),
6260  context_->working_model, &ref_to_bool_and);
6261  to_remove.push_back(c);
6262  continue;
6263  }
6264  }
6265 
6267  for (const int c : to_remove) {
6268  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
6269  CHECK(RemoveConstraint(ct));
6270  context_->UpdateConstraintVariableUsage(c);
6271  }
6272 }
6273 
6274 // TODO(user): It might make sense to run this in parallel. The same apply for
6275 // other expansive and self-contains steps like symmetry detection, etc...
6276 void CpModelPresolver::Probe() {
6277  Model model;
6278  if (!LoadModelForProbing(context_, &model)) return;
6279 
6280  // Probe.
6281  //
6282  // TODO(user): Compute the transitive reduction instead of just the
6283  // equivalences, and use the newly learned binary clauses?
6284  auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
6285  auto* sat_solver = model.GetOrCreate<SatSolver>();
6286  auto* mapping = model.GetOrCreate<CpModelMapping>();
6287  auto* prober = model.GetOrCreate<Prober>();
6288 
6289  // Try to detect trivial clauses thanks to implications.
6290  // This can be slow, so we bound the amount of work done.
6291  //
6292  // Idea: If we have l1, l2 in a bool_or and not(l1) => l2, the constraint is
6293  // always true.
6294  //
6295  // Correctness: Note that we always replace a clause with another one that
6296  // subsumes it. So we are correct even if new clauses are learned and used
6297  // for propagation along the way.
6298  //
6299  // TODO(user): Improve the algo?
6300  int64_t work_done = 0;
6301  const int64_t work_limit = 1e8;
6302  if (true) {
6303  const auto& assignment = sat_solver->Assignment();
6304  prober->SetPropagationCallback([&](Literal decision) {
6305  if (work_done > work_limit) return;
6306  const int decision_var =
6307  mapping->GetProtoVariableFromBooleanVariable(decision.Variable());
6308  if (decision_var < 0) return;
6309  for (const int c : context_->VarToConstraints(decision_var)) {
6310  ++work_done;
6311  if (c < 0) continue;
6312  const ConstraintProto& ct = context_->working_model->constraints(c);
6313  if (ct.enforcement_literal().size() > 2) {
6314  // Any l for which decision => l can be removed.
6315  //
6316  // If decision => not(l), constraint can never be satisfied. However
6317  // because we don't know if this constraint was part of the
6318  // propagation we replace it by an implication.
6319  //
6320  // TODO(user): remove duplication with code below.
6321  // TODO(user): If decision appear positively, we could potentially
6322  // remove a bunch of terms (all the ones involving variables implied
6323  // by the decision) from the inner constraint, especially in the
6324  // linear case.
6325  int decision_ref;
6326  int false_ref;
6327  bool decision_is_positive = false;
6328  bool has_false_literal = false;
6329  bool simplification_possible = false;
6330  for (const int ref : ct.enforcement_literal()) {
6331  ++work_done;
6332  const Literal lit = mapping->Literal(ref);
6333  if (PositiveRef(ref) == decision_var) {
6334  decision_ref = ref;
6335  decision_is_positive = assignment.LiteralIsTrue(lit);
6336  if (!decision_is_positive) break;
6337  continue;
6338  }
6339  if (assignment.LiteralIsFalse(lit)) {
6340  false_ref = ref;
6341  has_false_literal = true;
6342  } else if (assignment.LiteralIsTrue(lit)) {
6343  // If decision => l, we can remove l from the list.
6344  simplification_possible = true;
6345  }
6346  }
6347  if (!decision_is_positive) continue;
6348 
6349  if (has_false_literal) {
6350  // Reduce to implication.
6351  auto* mutable_ct = context_->working_model->mutable_constraints(c);
6352  mutable_ct->Clear();
6353  mutable_ct->add_enforcement_literal(decision_ref);
6354  mutable_ct->mutable_bool_and()->add_literals(NegatedRef(false_ref));
6355  context_->UpdateRuleStats(
6356  "probing: reduced enforced constraint to implication.");
6357  context_->UpdateConstraintVariableUsage(c);
6358  continue;
6359  }
6360 
6361  if (simplification_possible) {
6362  int new_size = 0;
6363  auto* mutable_enforcements =
6364  context_->working_model->mutable_constraints(c)
6365  ->mutable_enforcement_literal();
6366  for (const int ref : ct.enforcement_literal()) {
6367  if (PositiveRef(ref) != decision_var &&
6368  assignment.LiteralIsTrue(mapping->Literal(ref))) {
6369  continue;
6370  }
6371  mutable_enforcements->Set(new_size++, ref);
6372  }
6373  mutable_enforcements->Truncate(new_size);
6374  context_->UpdateRuleStats("probing: simplified enforcement list.");
6375  context_->UpdateConstraintVariableUsage(c);
6376  }
6377  continue;
6378  }
6379 
6380  if (ct.constraint_case() != ConstraintProto::kBoolOr) continue;
6381  if (ct.bool_or().literals().size() <= 2) continue;
6382 
6383  int decision_ref;
6384  int true_ref;
6385  bool decision_is_negative = false;
6386  bool has_true_literal = false;
6387  bool simplification_possible = false;
6388  for (const int ref : ct.bool_or().literals()) {
6389  ++work_done;
6390  const Literal lit = mapping->Literal(ref);
6391  if (PositiveRef(ref) == decision_var) {
6392  decision_ref = ref;
6393  decision_is_negative = assignment.LiteralIsFalse(lit);
6394  if (!decision_is_negative) break;
6395  continue;
6396  }
6397  if (assignment.LiteralIsTrue(lit)) {
6398  true_ref = ref;
6399  has_true_literal = true;
6400  } else if (assignment.LiteralIsFalse(lit)) {
6401  // If not(l1) => not(l2), we can remove l2 from the clause.
6402  simplification_possible = true;
6403  }
6404  }
6405  if (!decision_is_negative) continue;
6406 
6407  if (has_true_literal) {
6408  // This will later be merged with the current implications and removed
6409  // if it is a duplicate.
6410  auto* mutable_bool_or =
6411  context_->working_model->mutable_constraints(c)
6412  ->mutable_bool_or();
6413  mutable_bool_or->mutable_literals()->Clear();
6414  mutable_bool_or->add_literals(decision_ref);
6415  mutable_bool_or->add_literals(true_ref);
6416  context_->UpdateRuleStats("probing: bool_or reduced to implication");
6417  context_->UpdateConstraintVariableUsage(c);
6418  continue;
6419  }
6420 
6421  if (simplification_possible) {
6422  int new_size = 0;
6423  auto* mutable_bool_or =
6424  context_->working_model->mutable_constraints(c)
6425  ->mutable_bool_or();
6426  for (const int ref : ct.bool_or().literals()) {
6427  if (PositiveRef(ref) != decision_var &&
6428  assignment.LiteralIsFalse(mapping->Literal(ref))) {
6429  continue;
6430  }
6431  mutable_bool_or->set_literals(new_size++, ref);
6432  }
6433  mutable_bool_or->mutable_literals()->Truncate(new_size);
6434  context_->UpdateRuleStats("probing: simplified clauses.");
6435  context_->UpdateConstraintVariableUsage(c);
6436  }
6437  }
6438  });
6439  }
6440 
6441  prober->ProbeBooleanVariables(
6442  context_->params().probing_deterministic_time_limit());
6443  context_->time_limit()->AdvanceDeterministicTime(
6444  model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
6445  if (work_done > 0) {
6446  SOLVER_LOG(logger_,
6447  "[Probing] implications and bool_or (work_done=", work_done,
6448  ").", (work_done > work_limit ? " Aborted." : ""));
6449  }
6450  if (sat_solver->ModelIsUnsat() || !implication_graph->DetectEquivalences()) {
6451  return (void)context_->NotifyThatModelIsUnsat("during probing");
6452  }
6453 
6454  // Update the presolve context with fixed Boolean variables.
6455  CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
6456  for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
6457  const Literal l = sat_solver->LiteralTrail()[i];
6458  const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
6459  if (var >= 0) {
6460  const int ref = l.IsPositive() ? var : NegatedRef(var);
6461  if (!context_->SetLiteralToTrue(ref)) return;
6462  }
6463  }
6464 
6465  const int num_variables = context_->working_model->variables().size();
6466  auto* integer_trail = model.GetOrCreate<IntegerTrail>();
6467  for (int var = 0; var < num_variables; ++var) {
6468  // Restrict IntegerVariable domain.
6469  // Note that Boolean are already dealt with above.
6470  if (!mapping->IsBoolean(var)) {
6471  if (!context_->IntersectDomainWith(
6472  var,
6473  integer_trail->InitialVariableDomain(mapping->Integer(var)))) {
6474  return;
6475  }
6476  continue;
6477  }
6478 
6479  // Add Boolean equivalence relations.
6480  const Literal l = mapping->Literal(var);
6481  const Literal r = implication_graph->RepresentativeOf(l);
6482  if (r != l) {
6483  const int r_var =
6484  mapping->GetProtoVariableFromBooleanVariable(r.Variable());
6485  CHECK_GE(r_var, 0);
6486  context_->StoreBooleanEqualityRelation(
6487  var, r.IsPositive() ? r_var : NegatedRef(r_var));
6488  }
6489  }
6490 
6491  // Run clique merging using detected implications from probing.
6492  {
6494  wall_timer.Start();
6495  std::vector<std::vector<Literal>> cliques;
6496 
6497  int64_t num_literals_before = 0;
6498  const int num_constraints = context_->working_model->constraints_size();
6499  for (int c = 0; c < num_constraints; ++c) {
6500  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
6501  if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
6502  std::vector<Literal> clique;
6503  for (const int ref : ct->at_most_one().literals()) {
6504  clique.push_back(mapping->Literal(ref));
6505  }
6506  num_literals_before += clique.size();
6507  cliques.push_back(clique);
6508  ct->Clear();
6509  context_->UpdateConstraintVariableUsage(c);
6510  } else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
6511  if (ct->enforcement_literal().size() != 1) continue;
6512  const Literal enforcement =
6513  mapping->Literal(ct->enforcement_literal(0));
6514  for (const int ref : ct->bool_and().literals()) {
6515  if (ref == ct->enforcement_literal(0)) continue;
6516  num_literals_before += 2;
6517  cliques.push_back({enforcement, mapping->Literal(ref).Negated()});
6518  }
6519  ct->Clear();
6520  context_->UpdateConstraintVariableUsage(c);
6521  }
6522  }
6523  const int64_t num_old_cliques = cliques.size();
6524 
6525  implication_graph->TransformIntoMaxCliques(
6526  &cliques,
6527  SafeDoubleToInt64(context_->params().merge_at_most_one_work_limit()));
6528 
6529  // Note that because TransformIntoMaxCliques() extend cliques, we are ok
6530  // to ignore any unmapped literal. In case of equivalent literal, we always
6531  // use the smaller indices as a representative, so we should be good.
6532  int num_new_cliques = 0;
6533  int64_t num_literals_after = 0;
6534  for (const std::vector<Literal>& clique : cliques) {
6535  if (clique.empty()) continue;
6536  num_new_cliques++;
6537  num_literals_after += clique.size();
6538  ConstraintProto* ct = context_->working_model->add_constraints();
6539  for (const Literal literal : clique) {
6540  const int var =
6541  mapping->GetProtoVariableFromBooleanVariable(literal.Variable());
6542  if (var < 0) continue;
6543  if (literal.IsPositive()) {
6544  ct->mutable_at_most_one()->add_literals(var);
6545  } else {
6546  ct->mutable_at_most_one()->add_literals(NegatedRef(var));
6547  }
6548  }
6549 
6550  // Make sure we do not have duplicate variable reference.
6551  PresolveAtMostOne(ct);
6552  }
6554  if (num_new_cliques != num_old_cliques) {
6555  context_->UpdateRuleStats("at_most_one: transformed into max clique.");
6556  }
6557 
6558  if (num_old_cliques != num_new_cliques ||
6559  num_literals_before != num_literals_after) {
6560  SOLVER_LOG(logger_, "[MaxClique] Merged ", num_old_cliques, "(",
6561  num_literals_before, " literals) into ", num_new_cliques, "(",
6562  num_literals_after, " literals) at_most_ones. ",
6563  "time=", wall_timer.Get(), "s");
6564  }
6565  }
6566 }
6567 
6568 // TODO(user): What to do with the at_most_one/exactly_one constraints?
6569 // currently we do not take them into account here.
6570 void CpModelPresolver::PresolvePureSatPart() {
6571  // TODO(user): Reenable some SAT presolve with
6572  // keep_all_feasible_solutions set to true.
6573  if (context_->ModelIsUnsat() || context_->keep_all_feasible_solutions) return;
6574 
6575  const int num_variables = context_->working_model->variables_size();
6576  SatPostsolver sat_postsolver(num_variables);
6577  SatPresolver sat_presolver(&sat_postsolver, logger_);
6578  sat_presolver.SetNumVariables(num_variables);
6579  sat_presolver.SetTimeLimit(context_->time_limit());
6580 
6581  SatParameters params = context_->params();
6582 
6583  // The "full solver" postsolve does not support changing the value of a
6584  // variable from the solution of the presolved problem, and we do need this
6585  // for blocked clause. It should be possible to allow for this by adding extra
6586  // variable to the mapping model at presolve and some linking constraints, but
6587  // this is messy.
6588  if (params.debug_postsolve_with_full_solver()) {
6589  params.set_presolve_blocked_clause(false);
6590  }
6591 
6592  // TODO(user): BVA takes time and does not seems to help on the minizinc
6593  // benchmarks. That said, it was useful on pure sat problems, so we may want
6594  // to enable it. Note that it is related to our MergeClauses().
6595  params.set_presolve_use_bva(false);
6596  sat_presolver.SetParameters(params);
6597 
6598  // Converts a cp_model literal ref to a sat::Literal used by SatPresolver.
6599  absl::flat_hash_set<int> used_variables;
6600  auto convert = [&used_variables](int ref) {
6601  used_variables.insert(PositiveRef(ref));
6602  if (RefIsPositive(ref)) return Literal(BooleanVariable(ref), true);
6603  return Literal(BooleanVariable(NegatedRef(ref)), false);
6604  };
6605 
6606  // We need all Boolean constraints to be presolved before loading them below.
6607  // Otherwise duplicate literals might result in a wrong outcome.
6608  //
6609  // TODO(user): Be a bit more efficient, and enforce this invariant before we
6610  // reach this point?
6611  for (int c = 0; c < context_->working_model->constraints_size(); ++c) {
6612  const ConstraintProto& ct = context_->working_model->constraints(c);
6613  if (ct.constraint_case() == ConstraintProto::kBoolOr ||
6614  ct.constraint_case() == ConstraintProto::kBoolAnd) {
6615  if (PresolveOneConstraint(c)) {
6616  context_->UpdateConstraintVariableUsage(c);
6617  }
6618  if (context_->ModelIsUnsat()) return;
6619  }
6620  }
6621 
6622  // Load all Clauses into the presolver and remove them from the current model.
6623  //
6624  // TODO(user): The removing and adding back of the same clause when nothing
6625  // happens in the presolve "seems" bad. That said, complexity wise, it is
6626  // a lot faster that what happens in the presolve though.
6627  //
6628  // TODO(user): Add the "small" at most one constraints to the SAT presolver by
6629  // expanding them to implications? that could remove a lot of clauses. Do that
6630  // when we are sure we don't load duplicates at_most_one/implications in the
6631  // solver. Ideally, the pure sat presolve could be improved to handle at most
6632  // one, and we could merge this with what the ProcessSetPPC() is doing.
6633  std::vector<Literal> clause;
6634  int num_removed_constraints = 0;
6635  for (int i = 0; i < context_->working_model->constraints_size(); ++i) {
6636  const ConstraintProto& ct = context_->working_model->constraints(i);
6637 
6638  if (ct.constraint_case() == ConstraintProto::kBoolOr) {
6639  ++num_removed_constraints;
6640  clause.clear();
6641  for (const int ref : ct.bool_or().literals()) {
6642  clause.push_back(convert(ref));
6643  }
6644  for (const int ref : ct.enforcement_literal()) {
6645  clause.push_back(convert(ref).Negated());
6646  }
6647  sat_presolver.AddClause(clause);
6648 
6649  context_->working_model->mutable_constraints(i)->Clear();
6650  context_->UpdateConstraintVariableUsage(i);
6651  continue;
6652  }
6653 
6654  if (ct.constraint_case() == ConstraintProto::kBoolAnd) {
6655  // We currently do not expand "complex" bool_and that would result
6656  // in too many literals.
6657  const int left_size = ct.enforcement_literal().size();
6658  const int right_size = ct.bool_and().literals().size();
6659  if (left_size > 1 && right_size > 1 &&
6660  (left_size + 1) * right_size > 1000) {
6661  continue;
6662  }
6663 
6664  ++num_removed_constraints;
6665  std::vector<Literal> clause;
6666  for (const int ref : ct.enforcement_literal()) {
6667  clause.push_back(convert(ref).Negated());
6668  }
6669  clause.push_back(Literal(kNoLiteralIndex)); // will be replaced below.
6670  for (const int ref : ct.bool_and().literals()) {
6671  clause.back() = convert(ref);
6672  sat_presolver.AddClause(clause);
6673  }
6674 
6675  context_->working_model->mutable_constraints(i)->Clear();
6676  context_->UpdateConstraintVariableUsage(i);
6677  continue;
6678  }
6679  }
6680 
6681  // Abort early if there was no Boolean constraints.
6682  if (num_removed_constraints == 0) return;
6683 
6684  // Mark the variables appearing elsewhere or in the objective as non-removable
6685  // by the sat presolver.
6686  //
6687  // TODO(user): do not remove variable that appear in the decision heuristic?
6688  // TODO(user): We could go further for variable with only one polarity by
6689  // removing variable from the objective if they can be set to their "low"
6690  // objective value, and also removing enforcement literal that can be set to
6691  // false and don't appear elsewhere.
6692  std::vector<bool> can_be_removed(num_variables, false);
6693  for (int i = 0; i < num_variables; ++i) {
6694  if (context_->VarToConstraints(i).empty()) {
6695  can_be_removed[i] = true;
6696  }
6697 
6698  // Because we might not have reached the presove "fixed point" above, some
6699  // variable in the added clauses might be fixed. We need to indicate this to
6700  // the SAT presolver.
6701  if (used_variables.contains(i) && context_->IsFixed(i)) {
6702  if (context_->LiteralIsTrue(i)) {
6703  sat_presolver.AddClause({convert(i)});
6704  } else {
6705  sat_presolver.AddClause({convert(NegatedRef(i))});
6706  }
6707  }
6708  }
6709 
6710  // Run the presolve for a small number of passes.
6711  // TODO(user): Add probing like we do in the pure sat solver presolve loop?
6712  // TODO(user): Add a time limit, this can be slow on big SAT problem.
6713  const int num_passes = params.presolve_use_bva() ? 4 : 1;
6714  for (int i = 0; i < num_passes; ++i) {
6715  const int old_num_clause = sat_postsolver.NumClauses();
6716  if (!sat_presolver.Presolve(can_be_removed)) {
6717  VLOG(1) << "UNSAT during SAT presolve.";
6718  return (void)context_->NotifyThatModelIsUnsat();
6719  }
6720  if (old_num_clause == sat_postsolver.NumClauses()) break;
6721  }
6722 
6723  // Add any new variables to our internal structure.
6724  const int new_num_variables = sat_presolver.NumVariables();
6725  if (new_num_variables > context_->working_model->variables_size()) {
6726  VLOG(1) << "New variables added by the SAT presolver.";
6727  for (int i = context_->working_model->variables_size();
6728  i < new_num_variables; ++i) {
6729  IntegerVariableProto* var_proto =
6730  context_->working_model->add_variables();
6731  var_proto->add_domain(0);
6732  var_proto->add_domain(1);
6733  }
6734  context_->InitializeNewDomains();
6735  }
6736 
6737  // Add the presolver clauses back into the model.
6738  ExtractClauses(/*use_bool_and=*/true, sat_presolver, context_->working_model);
6739 
6740  // Update the constraints <-> variables graph.
6742 
6743  // Add the sat_postsolver clauses to mapping_model.
6744  //
6745  // TODO(user): Mark removed variable as removed to detect any potential bugs.
6746  ExtractClauses(/*use_bool_and=*/false, sat_postsolver,
6747  context_->mapping_model);
6748 }
6749 
6750 void CpModelPresolver::ShiftObjectiveWithExactlyOnes() {
6751  if (context_->ModelIsUnsat()) return;
6752 
6753  // The objective is already loaded in the context, but we re-canonicalize
6754  // it with the latest information.
6755  if (!context_->CanonicalizeObjective()) {
6756  (void)context_->NotifyThatModelIsUnsat();
6757  return;
6758  }
6759 
6760  std::vector<int> exos;
6761  const int num_constraints = context_->working_model->constraints_size();
6762  for (int c = 0; c < num_constraints; ++c) {
6763  const ConstraintProto& ct = context_->working_model->constraints(c);
6764  if (!ct.enforcement_literal().empty()) continue;
6765  if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
6766  exos.push_back(c);
6767  }
6768  }
6769 
6770  // This is not the same from what we do in ExpandObjective() because we do not
6771  // make the minimum cost zero but the second minimum. Note that when we do
6772  // that, we still do not degrade the trivial objective bound as we would if we
6773  // went any further.
6774  //
6775  // One reason why this might be beneficial is that it lower the maximum cost
6776  // magnitude, making more Booleans with the same cost and thus simplifying
6777  // the core optimizer job. I am not 100% sure.
6778  //
6779  // TODO(user): We need to loop a few time to reach a fixed point. Understand
6780  // exactly if there is a fixed-point and how to reach it in a nicer way.
6781  int num_shifts = 0;
6782  for (int i = 0; i < 3; ++i) {
6783  for (const int c : exos) {
6784  const ConstraintProto& ct = context_->working_model->constraints(c);
6785  const int num_terms = ct.exactly_one().literals().size();
6786  if (num_terms <= 1) continue;
6787  int64_t min_obj = std::numeric_limits<int64_t>::max();
6788  int64_t second_min = std::numeric_limits<int64_t>::max();
6789  for (int i = 0; i < num_terms; ++i) {
6790  const int literal = ct.exactly_one().literals(i);
6791  const int64_t var_obj = context_->ObjectiveCoeff(PositiveRef(literal));
6792  const int64_t obj = RefIsPositive(literal) ? var_obj : -var_obj;
6793  if (obj < min_obj) {
6794  second_min = min_obj;
6795  min_obj = obj;
6796  } else if (obj < second_min) {
6797  second_min = obj;
6798  }
6799  }
6800  if (second_min == 0) continue;
6801  ++num_shifts;
6802  if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
6803  second_min)) {
6804  if (context_->ModelIsUnsat()) return;
6805  continue;
6806  }
6807  }
6808  }
6809  if (num_shifts > 0) {
6810  context_->UpdateRuleStats("objective: shifted cost with exactly ones",
6811  num_shifts);
6812  }
6813 }
6814 
6815 // Expand the objective expression in some easy cases.
6816 //
6817 // The ideas is to look at all the "tight" equality constraints. These should
6818 // give a topological order on the variable in which we can perform
6819 // substitution.
6820 //
6821 // Basically, we will only use constraints of the form X' = sum ci * Xi' with ci
6822 // > 0 and the variable X' being shifted version >= 0. Note that if there is a
6823 // cycle with these constraints, all variables involved must be equal to each
6824 // other and likely zero. Otherwise, we can express everything in terms of the
6825 // leaves.
6826 //
6827 // This assumes we are more or less at the propagation fix point, even if we
6828 // try to address cases where we are not.
6829 void CpModelPresolver::ExpandObjective() {
6830  if (context_->ModelIsUnsat()) return;
6832  wall_timer.Start();
6833 
6834  // The objective is already loaded in the context, but we re-canonicalize
6835  // it with the latest information.
6836  if (!context_->CanonicalizeObjective()) {
6837  (void)context_->NotifyThatModelIsUnsat();
6838  return;
6839  }
6840 
6841  const int num_variables = context_->working_model->variables_size();
6842  const int num_constraints = context_->working_model->constraints_size();
6843 
6844  // We consider two types of shifted variables (X - LB(X)) and (UB(X) - X).
6845  const auto get_index = [](int var, bool to_lb) {
6846  return 2 * var + (to_lb ? 0 : 1);
6847  };
6848  const auto get_lit_index = [](int lit) {
6849  return RefIsPositive(lit) ? 2 * lit : 2 * PositiveRef(lit) + 1;
6850  };
6851  const int num_nodes = 2 * num_variables;
6852  std::vector<std::vector<int>> index_graph(num_nodes);
6853 
6854  // TODO(user): instead compute how much each constraint can be further
6855  // expanded?
6856  std::vector<int> index_to_best_c(num_nodes, -1);
6857  std::vector<int> index_to_best_size(num_nodes, 0);
6858 
6859  // Lets see first if there are "tight" constraint and for which variables.
6860  // We stop processing constraint if we have too many entries.
6861  int num_entries = 0;
6862  int num_propagations = 0;
6863  int num_tight_variables = 0;
6864  int num_tight_constraints = 0;
6865  const int kNumEntriesThreshold = 1e8;
6866  for (int c = 0; c < num_constraints; ++c) {
6867  if (num_entries > kNumEntriesThreshold) break;
6868 
6869  const ConstraintProto& ct = context_->working_model->constraints(c);
6870  if (!ct.enforcement_literal().empty()) continue;
6871 
6872  // Deal with exactly one.
6873  // An exactly one is always tight on the upper bound of one term.
6874  if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
6875  const int num_terms = ct.exactly_one().literals().size();
6876  ++num_tight_constraints;
6877  num_tight_variables += num_terms;
6878  for (int i = 0; i < num_terms; ++i) {
6879  if (num_entries > kNumEntriesThreshold) break;
6880  const int neg_index = get_lit_index(ct.exactly_one().literals(i)) ^ 1;
6881 
6882  const int old_c = index_to_best_c[neg_index];
6883  if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
6884  index_to_best_c[neg_index] = c;
6885  index_to_best_size[neg_index] = num_terms;
6886  }
6887 
6888  for (int j = 0; j < num_terms; ++j) {
6889  if (j == i) continue;
6890  const int other_index = get_lit_index(ct.exactly_one().literals(j));
6891  ++num_entries;
6892  index_graph[neg_index].push_back(other_index);
6893  }
6894  }
6895  continue;
6896  }
6897 
6898  // Skip everything that is not a linear equality constraint.
6899  if (ct.constraint_case() != ConstraintProto::kLinear ||
6900  ct.linear().domain().size() != 2 ||
6901  ct.linear().domain(0) != ct.linear().domain(1)) {
6902  continue;
6903  }
6904 
6905  // Let see for which variable is it "tight". We need a coeff of 1, and that
6906  // the implied bounds match exactly.
6907  const auto [min_activity, max_activity] =
6908  context_->ComputeMinMaxActivity(ct.linear());
6909 
6910  bool is_tight = false;
6911  const int64_t rhs = ct.linear().domain(0);
6912  const int num_terms = ct.linear().vars_size();
6913  for (int i = 0; i < num_terms; ++i) {
6914  const int var = ct.linear().vars(i);
6915  const int64_t coeff = ct.linear().coeffs(i);
6916  if (std::abs(coeff) != 1) continue;
6917  if (num_entries > kNumEntriesThreshold) break;
6918 
6919  const int index = get_index(var, coeff > 0);
6920 
6921  const int64_t var_range = context_->MaxOf(var) - context_->MinOf(var);
6922  const int64_t implied_shifted_ub = rhs - min_activity;
6923  if (implied_shifted_ub <= var_range) {
6924  if (implied_shifted_ub < var_range) ++num_propagations;
6925  is_tight = true;
6926  ++num_tight_variables;
6927 
6928  const int neg_index = index ^ 1;
6929  const int old_c = index_to_best_c[neg_index];
6930  if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
6931  index_to_best_c[neg_index] = c;
6932  index_to_best_size[neg_index] = num_terms;
6933  }
6934 
6935  for (int j = 0; j < num_terms; ++j) {
6936  if (j == i) continue;
6937  const int other_index =
6938  get_index(ct.linear().vars(j), ct.linear().coeffs(j) > 0);
6939  ++num_entries;
6940  index_graph[neg_index].push_back(other_index);
6941  }
6942  }
6943  const int64_t implied_shifted_lb = max_activity - rhs;
6944  if (implied_shifted_lb <= var_range) {
6945  if (implied_shifted_lb < var_range) ++num_propagations;
6946  is_tight = true;
6947  ++num_tight_variables;
6948 
6949  const int old_c = index_to_best_c[index];
6950  if (old_c == -1 || num_terms > index_to_best_size[index]) {
6951  index_to_best_c[index] = c;
6952  index_to_best_size[index] = num_terms;
6953  }
6954 
6955  for (int j = 0; j < num_terms; ++j) {
6956  if (j == i) continue;
6957  const int other_index =
6958  get_index(ct.linear().vars(j), ct.linear().coeffs(j) < 0);
6959  ++num_entries;
6960  index_graph[index].push_back(other_index);
6961  }
6962  }
6963  }
6964  if (is_tight) ++num_tight_constraints;
6965  }
6966 
6967  // Note(user): We assume the fixed point was already reached by the linear
6968  // presolve, so we don't add extra code here for that. But we still abort if
6969  // some are left to cover corner cases were linear a still not propagated.
6970  if (num_propagations > 0) {
6971  context_->UpdateRuleStats("TODO objective: propagation possible!");
6972  return;
6973  }
6974 
6975  // In most cases, we should have no cycle and thus a topo order.
6976  //
6977  // In case there is a cycle, then all member of a strongly connected component
6978  // must be equivalent, this is because from X to Y, if we follow the chain we
6979  // will have X = non_negative_sum + Y and Y = non_negative_sum + X.
6980  //
6981  // Moreover, many shifted variables will need to be zero once we start to have
6982  // equivalence.
6983  //
6984  // TODO(user): Make the fixing to zero? or at least when this happen redo
6985  // a presolve pass?
6986  //
6987  // TODO(user): Densify index to only look at variable that can be substituted
6988  // further.
6989  const auto topo_order = util::graph::FastTopologicalSort(index_graph);
6990  if (!topo_order.ok()) {
6991  std::vector<std::vector<int>> components;
6992  FindStronglyConnectedComponents(static_cast<int>(index_graph.size()),
6993  index_graph, &components);
6994  for (const std::vector<int>& compo : components) {
6995  if (compo.size() == 1) continue;
6996 
6997  const int rep_var = compo[0] / 2;
6998  const bool rep_to_lp = (compo[0] % 2) == 0;
6999  for (int i = 1; i < compo.size(); ++i) {
7000  const int var = compo[i] / 2;
7001  const bool to_lb = (compo[i] % 2) == 0;
7002 
7003  // (rep - rep_lb)/(rep_ub - rep) == (var - var_lb)/(ub - var_ub)
7004  // +/- rep = +/- var + offset.
7005  const int64_t rep_coeff = rep_to_lp ? 1 : -1;
7006  const int64_t var_coeff = to_lb ? 1 : -1;
7007  const int64_t offset =
7008  (to_lb ? -context_->MinOf(var) : context_->MaxOf(var)) -
7009  (rep_to_lp ? -context_->MinOf(rep_var) : context_->MaxOf(rep_var));
7010  if (!context_->StoreAffineRelation(rep_var, var, rep_coeff * var_coeff,
7011  rep_coeff * offset)) {
7012  return;
7013  }
7014  }
7015  context_->UpdateRuleStats("objective: detected equivalence",
7016  compo.size() - 1);
7017  }
7018  return;
7019  }
7020 
7021  // If the removed variable is now unique, we could remove it if it is implied
7022  // free. But this should already be done by RemoveSingletonInLinear(), so we
7023  // don't redo it here.
7024  int num_expands = 0;
7025  int num_issues = 0;
7026  for (const int index : *topo_order) {
7027  if (index_graph[index].empty()) continue;
7028 
7029  const int var = index / 2;
7030  const int64_t obj_coeff = context_->ObjectiveCoeff(var);
7031  if (obj_coeff == 0) continue;
7032 
7033  const bool to_lb = (index % 2) == 0;
7034  if (obj_coeff > 0 == to_lb) {
7035  const ConstraintProto& ct =
7036  context_->working_model->constraints(index_to_best_c[index]);
7037  if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
7038  int64_t shift = 0;
7039  for (const int lit : ct.exactly_one().literals()) {
7040  if (PositiveRef(lit) == var) {
7041  shift = RefIsPositive(lit) ? obj_coeff : -obj_coeff;
7042  break;
7043  }
7044  }
7045  if (shift == 0) {
7046  ++num_issues;
7047  continue;
7048  }
7049  if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
7050  shift)) {
7051  if (context_->ModelIsUnsat()) return;
7052  ++num_issues;
7053  continue;
7054  }
7055  CHECK_EQ(context_->ObjectiveCoeff(var), 0);
7056  ++num_expands;
7057  continue;
7058  }
7059 
7060  int64_t objective_coeff_in_expanded_constraint = 0;
7061  const int num_terms = ct.linear().vars().size();
7062  for (int i = 0; i < num_terms; ++i) {
7063  if (ct.linear().vars(i) == var) {
7064  objective_coeff_in_expanded_constraint = ct.linear().coeffs(i);
7065  break;
7066  }
7067  }
7068  if (objective_coeff_in_expanded_constraint == 0) {
7069  ++num_issues;
7070  continue;
7071  }
7072 
7073  if (!context_->SubstituteVariableInObjective(
7074  var, objective_coeff_in_expanded_constraint, ct)) {
7075  if (context_->ModelIsUnsat()) return;
7076  ++num_issues;
7077  continue;
7078  }
7079 
7080  ++num_expands;
7081  }
7082  }
7083 
7084  if (num_expands > 0) {
7085  context_->UpdateRuleStats("objective: expanded via tight equality",
7086  num_expands);
7087  }
7088  SOLVER_LOG(
7089  logger_, "[ExpandObjective]", " #propagations=", num_propagations,
7090  " #entries=", num_entries, " #tight_variables=", num_tight_variables,
7091  " #tight_constraints=", num_tight_constraints, " #expands=", num_expands,
7092  " #issues=", num_issues, " time=", wall_timer.Get(), "s");
7093 }
7094 
7095 void CpModelPresolver::MergeNoOverlapConstraints() {
7096  if (context_->ModelIsUnsat()) return;
7097 
7098  const int num_constraints = context_->working_model->constraints_size();
7099  int old_num_no_overlaps = 0;
7100  int old_num_intervals = 0;
7101 
7102  // Extract the no-overlap constraints.
7103  std::vector<int> disjunctive_index;
7104  std::vector<std::vector<Literal>> cliques;
7105  for (int c = 0; c < num_constraints; ++c) {
7106  const ConstraintProto& ct = context_->working_model->constraints(c);
7107  if (ct.constraint_case() != ConstraintProto::kNoOverlap) continue;
7108  std::vector<Literal> clique;
7109  for (const int i : ct.no_overlap().intervals()) {
7110  clique.push_back(Literal(BooleanVariable(i), true));
7111  }
7112  cliques.push_back(clique);
7113  disjunctive_index.push_back(c);
7114 
7115  old_num_no_overlaps++;
7116  old_num_intervals += clique.size();
7117  }
7118  if (old_num_no_overlaps == 0) return;
7119 
7120  // We reuse the max-clique code from sat.
7121  Model local_model;
7122  local_model.GetOrCreate<Trail>()->Resize(num_constraints);
7123  auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
7124  graph->Resize(num_constraints);
7125  for (const std::vector<Literal>& clique : cliques) {
7126  // All variables at false is always a valid solution of the local model,
7127  // so this should never return UNSAT.
7128  CHECK(graph->AddAtMostOne(clique));
7129  }
7130  CHECK(graph->DetectEquivalences());
7131  graph->TransformIntoMaxCliques(
7132  &cliques,
7133  SafeDoubleToInt64(context_->params().merge_no_overlap_work_limit()));
7134 
7135  // Replace each no-overlap with an extended version, or remove if empty.
7136  int new_num_no_overlaps = 0;
7137  int new_num_intervals = 0;
7138  for (int i = 0; i < cliques.size(); ++i) {
7139  const int ct_index = disjunctive_index[i];
7140  ConstraintProto* ct =
7141  context_->working_model->mutable_constraints(ct_index);
7142  ct->Clear();
7143  if (cliques[i].empty()) continue;
7144  for (const Literal l : cliques[i]) {
7145  CHECK(l.IsPositive());
7146  ct->mutable_no_overlap()->add_intervals(l.Variable().value());
7147  }
7148  new_num_no_overlaps++;
7149  new_num_intervals += cliques[i].size();
7150  }
7151  if (old_num_intervals != new_num_intervals ||
7152  old_num_no_overlaps != new_num_no_overlaps) {
7153  VLOG(1) << absl::StrCat("Merged ", old_num_no_overlaps, " no-overlaps (",
7154  old_num_intervals, " intervals) into ",
7155  new_num_no_overlaps, " no-overlaps (",
7156  new_num_intervals, " intervals).");
7157  context_->UpdateRuleStats("no_overlap: merged constraints");
7158  }
7159 }
7160 
7161 // TODO(user): Should we take into account the exactly_one constraints? note
7162 // that such constraint cannot be extended. If if a literal implies two literals
7163 // at one inside an exactly one constraint then it must be false. Similarly if
7164 // it implies all literals at zero inside the exactly one.
7165 void CpModelPresolver::TransformIntoMaxCliques() {
7166  if (context_->ModelIsUnsat()) return;
7167 
7168  auto convert = [](int ref) {
7169  if (RefIsPositive(ref)) return Literal(BooleanVariable(ref), true);
7170  return Literal(BooleanVariable(NegatedRef(ref)), false);
7171  };
7172  const int num_constraints = context_->working_model->constraints_size();
7173 
7174  // Extract the bool_and and at_most_one constraints.
7175  // TODO(user): use probing info?
7176  std::vector<std::vector<Literal>> cliques;
7177 
7178  for (int c = 0; c < num_constraints; ++c) {
7179  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7180  if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
7181  std::vector<Literal> clique;
7182  for (const int ref : ct->at_most_one().literals()) {
7183  clique.push_back(convert(ref));
7184  }
7185  cliques.push_back(clique);
7186  if (RemoveConstraint(ct)) {
7187  context_->UpdateConstraintVariableUsage(c);
7188  }
7189  } else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
7190  if (ct->enforcement_literal().size() != 1) continue;
7191  const Literal enforcement = convert(ct->enforcement_literal(0));
7192  for (const int ref : ct->bool_and().literals()) {
7193  if (ref == ct->enforcement_literal(0)) continue;
7194  cliques.push_back({enforcement, convert(ref).Negated()});
7195  }
7196  if (RemoveConstraint(ct)) {
7197  context_->UpdateConstraintVariableUsage(c);
7198  }
7199  }
7200  }
7201 
7202  int64_t num_literals_before = 0;
7203  const int num_old_cliques = cliques.size();
7204 
7205  // We reuse the max-clique code from sat.
7206  Model local_model;
7207  const int num_variables = context_->working_model->variables().size();
7208  local_model.GetOrCreate<Trail>()->Resize(num_variables);
7209  auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
7210  graph->Resize(num_variables);
7211  for (const std::vector<Literal>& clique : cliques) {
7212  num_literals_before += clique.size();
7213  if (!graph->AddAtMostOne(clique)) {
7214  return (void)context_->NotifyThatModelIsUnsat();
7215  }
7216  }
7217  if (!graph->DetectEquivalences()) {
7218  return (void)context_->NotifyThatModelIsUnsat();
7219  }
7220  graph->TransformIntoMaxCliques(
7221  &cliques,
7222  SafeDoubleToInt64(context_->params().merge_at_most_one_work_limit()));
7223 
7224  // Add the Boolean variable equivalence detected by DetectEquivalences().
7225  // Those are needed because TransformIntoMaxCliques() will replace all
7226  // variable by its representative.
7227  for (int var = 0; var < num_variables; ++var) {
7228  const Literal l = Literal(BooleanVariable(var), true);
7229  if (graph->RepresentativeOf(l) != l) {
7230  const Literal r = graph->RepresentativeOf(l);
7231  context_->StoreBooleanEqualityRelation(
7232  var, r.IsPositive() ? r.Variable().value()
7233  : NegatedRef(r.Variable().value()));
7234  }
7235  }
7236 
7237  int num_new_cliques = 0;
7238  int64_t num_literals_after = 0;
7239  for (const std::vector<Literal>& clique : cliques) {
7240  if (clique.empty()) continue;
7241  num_new_cliques++;
7242  num_literals_after += clique.size();
7243  ConstraintProto* ct = context_->working_model->add_constraints();
7244  for (const Literal literal : clique) {
7245  if (literal.IsPositive()) {
7246  ct->mutable_at_most_one()->add_literals(literal.Variable().value());
7247  } else {
7248  ct->mutable_at_most_one()->add_literals(
7249  NegatedRef(literal.Variable().value()));
7250  }
7251  }
7252 
7253  // Make sure we do not have duplicate variable reference.
7254  PresolveAtMostOne(ct);
7255  }
7257  if (num_new_cliques != num_old_cliques) {
7258  context_->UpdateRuleStats("at_most_one: transformed into max clique.");
7259  }
7260 
7261  if (num_old_cliques != num_new_cliques ||
7262  num_literals_before != num_literals_after) {
7263  SOLVER_LOG(logger_, "[MaxClique] Merged ", num_old_cliques, "(",
7264  num_literals_before, " literals) into ", num_new_cliques, "(",
7265  num_literals_after, " literals) at_most_ones.");
7266  }
7267 }
7268 
7269 namespace {
7270 
7271 bool IsAffineIntAbs(ConstraintProto* ct) {
7272  if (ct->constraint_case() != ConstraintProto::kLinMax ||
7273  ct->lin_max().exprs_size() != 2 ||
7274  ct->lin_max().target().vars_size() > 1 ||
7275  ct->lin_max().exprs(0).vars_size() != 1 ||
7276  ct->lin_max().exprs(1).vars_size() != 1) {
7277  return false;
7278  }
7279 
7280  const LinearArgumentProto& lin_max = ct->lin_max();
7281  if (lin_max.exprs(0).offset() != -lin_max.exprs(1).offset()) return false;
7282  if (PositiveRef(lin_max.exprs(0).vars(0)) !=
7283  PositiveRef(lin_max.exprs(1).vars(0))) {
7284  return false;
7285  }
7286 
7287  const int64_t left_coeff = RefIsPositive(lin_max.exprs(0).vars(0))
7288  ? lin_max.exprs(0).coeffs(0)
7289  : -lin_max.exprs(0).coeffs(0);
7290  const int64_t right_coeff = RefIsPositive(lin_max.exprs(1).vars(0))
7291  ? lin_max.exprs(1).coeffs(0)
7292  : -lin_max.exprs(1).coeffs(0);
7293  return left_coeff == -right_coeff;
7294 }
7295 
7296 } // namespace
7297 
7299  if (context_->ModelIsUnsat()) return false;
7300  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7301 
7302  // Generic presolve to exploit variable/literal equivalence.
7303  if (ExploitEquivalenceRelations(c, ct)) {
7304  context_->UpdateConstraintVariableUsage(c);
7305  }
7306 
7307  // Generic presolve for reified constraint.
7308  if (PresolveEnforcementLiteral(ct)) {
7309  context_->UpdateConstraintVariableUsage(c);
7310  }
7311 
7312  // Call the presolve function for this constraint if any.
7313  switch (ct->constraint_case()) {
7314  case ConstraintProto::kBoolOr:
7315  return PresolveBoolOr(ct);
7316  case ConstraintProto::kBoolAnd:
7317  return PresolveBoolAnd(ct);
7318  case ConstraintProto::kAtMostOne:
7319  return PresolveAtMostOne(ct);
7320  case ConstraintProto::kExactlyOne:
7321  return PresolveExactlyOne(ct);
7322  case ConstraintProto::kBoolXor:
7323  return PresolveBoolXor(ct);
7324  case ConstraintProto::kLinMax:
7325  if (CanonicalizeLinearArgument(*ct, ct->mutable_lin_max())) {
7326  context_->UpdateConstraintVariableUsage(c);
7327  }
7328  if (IsAffineIntAbs(ct)) {
7329  return PresolveIntAbs(ct);
7330  } else {
7331  return PresolveLinMax(ct);
7332  }
7333  case ConstraintProto::kIntProd:
7334  if (CanonicalizeLinearArgument(*ct, ct->mutable_int_prod())) {
7335  context_->UpdateConstraintVariableUsage(c);
7336  }
7337  return PresolveIntProd(ct);
7338  case ConstraintProto::kIntDiv:
7339  if (CanonicalizeLinearArgument(*ct, ct->mutable_int_div())) {
7340  context_->UpdateConstraintVariableUsage(c);
7341  }
7342  return PresolveIntDiv(ct);
7343  case ConstraintProto::kIntMod:
7344  if (CanonicalizeLinearArgument(*ct, ct->mutable_int_mod())) {
7345  context_->UpdateConstraintVariableUsage(c);
7346  }
7347  return PresolveIntMod(ct);
7348  case ConstraintProto::kLinear: {
7349  if (CanonicalizeLinear(ct)) {
7350  context_->UpdateConstraintVariableUsage(c);
7351  }
7352  if (PropagateDomainsInLinear(c, ct)) {
7353  context_->UpdateConstraintVariableUsage(c);
7354  }
7355  if (PresolveSmallLinear(ct)) {
7356  context_->UpdateConstraintVariableUsage(c);
7357  }
7358  if (PresolveLinearEqualityWithModulo(ct)) {
7359  context_->UpdateConstraintVariableUsage(c);
7360  }
7361  // We first propagate the domains before calling this presolve rule.
7362  if (RemoveSingletonInLinear(ct)) {
7363  context_->UpdateConstraintVariableUsage(c);
7364 
7365  // There is no need to re-do a propagation here, but the constraint
7366  // size might have been reduced.
7367  if (PresolveSmallLinear(ct)) {
7368  context_->UpdateConstraintVariableUsage(c);
7369  }
7370  }
7371  if (PresolveSmallLinear(ct)) {
7372  context_->UpdateConstraintVariableUsage(c);
7373  }
7374  if (PresolveLinearOnBooleans(ct)) {
7375  context_->UpdateConstraintVariableUsage(c);
7376  }
7377 
7378  // If we extracted some enforcement, we redo some presolve.
7379  const int old_num_enforcement_literals = ct->enforcement_literal_size();
7380  ExtractEnforcementLiteralFromLinearConstraint(c, ct);
7381  if (ct->enforcement_literal_size() > old_num_enforcement_literals) {
7382  if (DivideLinearByGcd(ct)) {
7383  context_->UpdateConstraintVariableUsage(c);
7384  }
7385  if (PresolveSmallLinear(ct)) {
7386  context_->UpdateConstraintVariableUsage(c);
7387  }
7388  }
7389 
7390  if (PresolveDiophantine(ct)) {
7391  context_->UpdateConstraintVariableUsage(c);
7392  }
7393 
7394  TryToReduceCoefficientsOfLinearConstraint(c, ct);
7395  return false;
7396  }
7397  case ConstraintProto::kInterval:
7398  return PresolveInterval(c, ct);
7399  case ConstraintProto::kInverse:
7400  return PresolveInverse(ct);
7401  case ConstraintProto::kElement:
7402  return PresolveElement(ct);
7403  case ConstraintProto::kTable:
7404  return PresolveTable(ct);
7405  case ConstraintProto::kAllDiff:
7406  return PresolveAllDiff(ct);
7407  case ConstraintProto::kNoOverlap:
7408  DetectDuplicateIntervals(c,
7409  ct->mutable_no_overlap()->mutable_intervals());
7410  return PresolveNoOverlap(ct);
7411  case ConstraintProto::kNoOverlap2D:
7412  DetectDuplicateIntervals(
7413  c, ct->mutable_no_overlap_2d()->mutable_x_intervals());
7414  DetectDuplicateIntervals(
7415  c, ct->mutable_no_overlap_2d()->mutable_y_intervals());
7416  return PresolveNoOverlap2D(c, ct);
7417  case ConstraintProto::kCumulative:
7418  DetectDuplicateIntervals(c,
7419  ct->mutable_cumulative()->mutable_intervals());
7420  return PresolveCumulative(ct);
7421  case ConstraintProto::kCircuit:
7422  return PresolveCircuit(ct);
7423  case ConstraintProto::kRoutes:
7424  return PresolveRoutes(ct);
7425  case ConstraintProto::kAutomaton:
7426  return PresolveAutomaton(ct);
7427  case ConstraintProto::kReservoir:
7428  return PresolveReservoir(ct);
7429  default:
7430  return false;
7431  }
7432 }
7433 
7434 // Returns false iff the model is UNSAT.
7435 bool CpModelPresolver::ProcessSetPPCSubset(int subset_c, int superset_c,
7436  absl::flat_hash_set<int>* tmp_set,
7437  bool* remove_subset,
7438  bool* remove_superset,
7439  bool* stop_processing_superset) {
7440  ConstraintProto* subset_ct =
7441  context_->working_model->mutable_constraints(subset_c);
7442  ConstraintProto* superset_ct =
7443  context_->working_model->mutable_constraints(superset_c);
7444 
7445  if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
7446  subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
7447  (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
7448  superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
7449  context_->UpdateRuleStats("setppc: bool_or in at_most_one.");
7450 
7451  tmp_set->clear();
7452  if (subset_ct->constraint_case() == ConstraintProto::kBoolOr) {
7453  tmp_set->insert(subset_ct->bool_or().literals().begin(),
7454  subset_ct->bool_or().literals().end());
7455  } else {
7456  tmp_set->insert(subset_ct->exactly_one().literals().begin(),
7457  subset_ct->exactly_one().literals().end());
7458  }
7459 
7460  // Fix extras in superset_c to 0, note that these will be removed from the
7461  // constraint later.
7462  for (const int literal :
7463  superset_ct->constraint_case() == ConstraintProto::kAtMostOne
7464  ? superset_ct->at_most_one().literals()
7465  : superset_ct->exactly_one().literals()) {
7466  if (tmp_set->contains(literal)) continue;
7467  if (!context_->SetLiteralToFalse(literal)) return false;
7468  context_->UpdateRuleStats("setppc: fixed variables");
7469  }
7470 
7471  // Change superset_c to exactly_one if not already.
7472  if (superset_ct->constraint_case() != ConstraintProto::kExactlyOne) {
7473  ConstraintProto copy = *superset_ct;
7474  (*superset_ct->mutable_exactly_one()->mutable_literals()) =
7475  copy.at_most_one().literals();
7476  }
7477 
7478  *remove_subset = true;
7479  return true;
7480  }
7481 
7482  if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
7483  subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
7484  superset_ct->constraint_case() == ConstraintProto::kBoolOr) {
7485  context_->UpdateRuleStats("setppc: removed dominated constraints");
7486  *remove_superset = true;
7487  return true;
7488  }
7489 
7490  if (subset_ct->constraint_case() == ConstraintProto::kAtMostOne &&
7491  (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
7492  superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
7493  context_->UpdateRuleStats("setppc: removed dominated constraints");
7494  *remove_subset = true;
7495  return true;
7496  }
7497 
7498  // Note(user): Only the exactly one should really be needed, the intersection
7499  // is taken care of by DetectAndProcessAtMostOneInLinear() in a better way.
7500  if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne &&
7501  superset_ct->constraint_case() == ConstraintProto::kLinear) {
7502  tmp_set->clear();
7503  int64_t min_sum = std::numeric_limits<int64_t>::max();
7504  int64_t max_sum = std::numeric_limits<int64_t>::min();
7505  tmp_set->insert(subset_ct->exactly_one().literals().begin(),
7506  subset_ct->exactly_one().literals().end());
7507 
7508  // Compute the min/max on the subset of the sum that correspond the exo.
7509  int num_matches = 0;
7510  temp_ct_.Clear();
7511  Domain reachable(0);
7512  std::vector<std::pair<int64_t, int>> coeff_counts;
7513  for (int i = 0; i < superset_ct->linear().vars().size(); ++i) {
7514  const int var = superset_ct->linear().vars(i);
7515  const int64_t coeff = superset_ct->linear().coeffs(i);
7516  if (tmp_set->contains(var)) {
7517  ++num_matches;
7518  min_sum = std::min(min_sum, coeff);
7519  max_sum = std::max(max_sum, coeff);
7520  coeff_counts.push_back({superset_ct->linear().coeffs(i), 1});
7521  } else {
7522  reachable =
7523  reachable
7524  .AdditionWith(
7525  context_->DomainOf(var).ContinuousMultiplicationBy(coeff))
7526  .RelaxIfTooComplex();
7527  temp_ct_.mutable_linear()->add_vars(var);
7528  temp_ct_.mutable_linear()->add_coeffs(coeff);
7529  }
7530  }
7531 
7532  // If a linear constraint contains more than one at_most_one or exactly_one,
7533  // after processing one, we might no longer have an inclusion.
7534  //
7535  // TODO(user): If we have multiple disjoint inclusion, we can propagate
7536  // more. For instance on neos-1593097.mps we basically have a
7537  // weighted_sum_over_at_most_one1 >= weighted_sum_over_at_most_one2.
7538  if (num_matches != tmp_set->size()) return true;
7539  if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne) {
7540  context_->UpdateRuleStats("setppc: exactly_one included in linear");
7541  } else {
7542  context_->UpdateRuleStats("setppc: at_most_one included in linear");
7543  }
7544 
7545  reachable = reachable.AdditionWith(Domain(min_sum, max_sum));
7546  const Domain superset_rhs = ReadDomainFromProto(superset_ct->linear());
7547  if (reachable.IsIncludedIn(superset_rhs)) {
7548  // The constraint is trivial !
7549  context_->UpdateRuleStats("setppc: removed trivial linear constraint");
7550  *remove_superset = true;
7551  return true;
7552  }
7553  if (reachable.IntersectionWith(superset_rhs).IsEmpty()) {
7554  // TODO(user): constraint might become bool_or.
7555  context_->UpdateRuleStats("setppc: removed infeasible linear constraint");
7556  *stop_processing_superset = true;
7557  return MarkConstraintAsFalse(superset_ct);
7558  }
7559 
7560  // We reuse the normal linear constraint code to propagate domains of
7561  // the other variable using the inclusion information.
7562  if (superset_ct->enforcement_literal().empty()) {
7563  CHECK_GT(num_matches, 0);
7564  FillDomainInProto(ReadDomainFromProto(superset_ct->linear())
7565  .AdditionWith(Domain(-max_sum, -min_sum)),
7566  temp_ct_.mutable_linear());
7567  PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_);
7568  }
7569 
7570  // If we have an exactly one in a linear, we can shift the coefficients of
7571  // all these variables by any constant value. We select a value that reduces
7572  // the number of terms the most.
7573  std::sort(coeff_counts.begin(), coeff_counts.end());
7574  int new_size = 0;
7575  for (int i = 0; i < coeff_counts.size(); ++i) {
7576  if (new_size > 0 &&
7577  coeff_counts[i].first == coeff_counts[new_size - 1].first) {
7578  coeff_counts[new_size - 1].second++;
7579  continue;
7580  }
7581  coeff_counts[new_size++] = coeff_counts[i];
7582  }
7583  coeff_counts.resize(new_size);
7584  int64_t best = 0;
7585  int64_t best_count = 0;
7586  for (const auto [coeff, count] : coeff_counts) {
7587  if (count > best_count) {
7588  best = coeff;
7589  best_count = count;
7590  }
7591  }
7592  if (best != 0) {
7593  int new_size = 0;
7594  for (int i = 0; i < superset_ct->linear().vars().size(); ++i) {
7595  const int var = superset_ct->linear().vars(i);
7596  int64_t coeff = superset_ct->linear().coeffs(i);
7597  if (tmp_set->contains(var)) {
7598  if (coeff == best) continue; // delete term.
7599  coeff -= best;
7600  }
7601  superset_ct->mutable_linear()->set_vars(new_size, var);
7602  superset_ct->mutable_linear()->set_coeffs(new_size, coeff);
7603  ++new_size;
7604  }
7605 
7606  superset_ct->mutable_linear()->mutable_vars()->Truncate(new_size);
7607  superset_ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
7608  FillDomainInProto(ReadDomainFromProto(superset_ct->linear())
7609  .AdditionWith(Domain(-best)),
7610  superset_ct->mutable_linear());
7611  context_->UpdateConstraintVariableUsage(superset_c);
7612  context_->UpdateRuleStats("setppc: reduced linear coefficients");
7613  }
7614 
7615  return true;
7616  }
7617 
7618  // We can't deduce anything in the last remaining cases, like an at most one
7619  // in an at least one.
7620  return true;
7621 }
7622 
7623 // TODO(user): TransformIntoMaxCliques() convert the bool_and to
7624 // at_most_one, but maybe also duplicating them into bool_or would allow this
7625 // function to do more presolving.
7626 void CpModelPresolver::ProcessSetPPC() {
7627  if (context_->time_limit()->LimitReached()) return;
7628  if (context_->ModelIsUnsat()) return;
7629  if (context_->params().presolve_inclusion_work_limit() == 0) return;
7630 
7632  wall_timer.Start();
7633 
7634  // TODO(user): compute on the fly instead of temporary storing variables?
7635  std::vector<int> relevant_constraints;
7636  CompactVectorVector<int> storage;
7637  InclusionDetector detector(storage);
7638  detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
7639 
7640  // Used by DetectAndProcessAtMostOneInLinear().
7641  // Cache all at most one to get more precise bounds on the linear constraint.
7642  ActivityBoundHelper amo_in_linear;
7643  amo_in_linear.AddAllAtMostOnes(*context_->working_model);
7644 
7645  // We use an encoding of literal that allows to index arrays.
7646  std::vector<int> temp_literals;
7647  const int num_constraints = context_->working_model->constraints_size();
7648  for (int c = 0; c < num_constraints; ++c) {
7649  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7650  const auto type = ct->constraint_case();
7651  if (type == ConstraintProto::kBoolOr ||
7652  type == ConstraintProto::kAtMostOne ||
7653  type == ConstraintProto::kExactlyOne) {
7654  // Because TransformIntoMaxCliques() can detect literal equivalence
7655  // relation, we make sure the constraints are presolved before being
7656  // inspected.
7657  if (PresolveOneConstraint(c)) {
7658  context_->UpdateConstraintVariableUsage(c);
7659  }
7660  if (context_->ModelIsUnsat()) return;
7661 
7662  temp_literals.clear();
7663  for (const int ref :
7664  type == ConstraintProto::kAtMostOne ? ct->at_most_one().literals()
7665  : type == ConstraintProto::kBoolOr ? ct->bool_or().literals()
7666  : ct->exactly_one().literals()) {
7667  temp_literals.push_back(
7668  Literal(BooleanVariable(PositiveRef(ref)), RefIsPositive(ref))
7669  .Index()
7670  .value());
7671  }
7672  relevant_constraints.push_back(c);
7673  detector.AddPotentialSet(storage.Add(temp_literals));
7674  } else if (type == ConstraintProto::kLinear) {
7675  // TODO(user): Not sure of the best place for this, but since the algo
7676  // is really related to a full inclusion, I put that here. We could also
7677  // do that in the main loop, but ideally we do not want to scan many times
7678  // each constraint.
7679  DetectAndProcessAtMostOneInLinear(c, ct, &amo_in_linear);
7680  if (context_->ModelIsUnsat()) return;
7681  if (ct->constraint_case() != ConstraintProto::kLinear) continue;
7682 
7683  // We also want to test inclusion with the pseudo-Boolean part of
7684  // linear constraints of size at least 3. Exactly one of size two are
7685  // equivalent literals, and we already deal with this case.
7686  //
7687  // TODO(user): This is not ideal as we currently only process exactly one
7688  // included into linear, and we add overhead by detecting all the other
7689  // cases that we ignore later. That said, we could just propagate a bit
7690  // more the domain if we know at_least_one or at_most_one between literals
7691  // in a linear constraint.
7692  const int size = ct->linear().vars().size();
7693  if (size <= 2) continue;
7694 
7695  // TODO(user): We only deal with positive var here. Ideally we should
7696  // match the VARIABLES of the at_most_one/exactly_one with the VARIABLES
7697  // of the linear, and complement all variable to have a literal inclusion.
7698  temp_literals.clear();
7699  for (int i = 0; i < size; ++i) {
7700  const int var = ct->linear().vars(i);
7701  if (!context_->CanBeUsedAsLiteral(var)) continue;
7702  if (!RefIsPositive(var)) continue;
7703  temp_literals.push_back(
7704  Literal(BooleanVariable(var), true).Index().value());
7705  }
7706  if (temp_literals.size() > 2) {
7707  // Note that we only care about the linear being the superset.
7708  relevant_constraints.push_back(c);
7709  detector.AddPotentialSuperset(storage.Add(temp_literals));
7710  }
7711  }
7712  }
7713 
7714  int64_t num_inclusions = 0;
7715  absl::flat_hash_set<int> tmp_set;
7716  detector.DetectInclusions([&](int subset, int superset) {
7717  ++num_inclusions;
7718  bool remove_subset = false;
7719  bool remove_superset = false;
7720  bool stop_processing_superset = false;
7721  const int subset_c = relevant_constraints[subset];
7722  const int superset_c = relevant_constraints[superset];
7723  detector.IncreaseWorkDone(storage[subset].size());
7724  detector.IncreaseWorkDone(storage[superset].size());
7725  if (!ProcessSetPPCSubset(subset_c, superset_c, &tmp_set, &remove_subset,
7726  &remove_superset, &stop_processing_superset)) {
7727  detector.Stop();
7728  return;
7729  }
7730  if (remove_subset) {
7731  context_->working_model->mutable_constraints(subset_c)->Clear();
7732  context_->UpdateConstraintVariableUsage(subset_c);
7733  detector.StopProcessingCurrentSubset();
7734  }
7735  if (remove_superset) {
7736  context_->working_model->mutable_constraints(superset_c)->Clear();
7737  context_->UpdateConstraintVariableUsage(superset_c);
7738  detector.StopProcessingCurrentSuperset();
7739  }
7740  if (stop_processing_superset) {
7741  context_->UpdateConstraintVariableUsage(superset_c);
7742  detector.StopProcessingCurrentSuperset();
7743  }
7744  });
7745 
7746  SOLVER_LOG(logger_, "[ProcessSetPPC]",
7747  " #relevant_constraints=", relevant_constraints.size(),
7748  " #num_inclusions=", num_inclusions,
7749  " work=", detector.work_done(), " time=", wall_timer.Get(), "s");
7750 }
7751 
7752 void CpModelPresolver::DetectIncludedEnforcement() {
7753  if (context_->time_limit()->LimitReached()) return;
7754  if (context_->ModelIsUnsat()) return;
7755  if (context_->params().presolve_inclusion_work_limit() == 0) return;
7756 
7758  wall_timer.Start();
7759 
7760  // TODO(user): compute on the fly instead of temporary storing variables?
7761  std::vector<int> relevant_constraints;
7762  CompactVectorVector<int> storage;
7763  InclusionDetector detector(storage);
7764  detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
7765 
7766  std::vector<int> temp_literals;
7767  const int num_constraints = context_->working_model->constraints_size();
7768  for (int c = 0; c < num_constraints; ++c) {
7769  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7770  if (ct->enforcement_literal().size() <= 1) continue;
7771 
7772  // Make sure there is no x => x.
7773  if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
7774  if (PresolveOneConstraint(c)) {
7775  context_->UpdateConstraintVariableUsage(c);
7776  }
7777  if (context_->ModelIsUnsat()) return;
7778  }
7779 
7780  // We use an encoding of literal that allows to index arrays.
7781  temp_literals.clear();
7782  for (const int ref : ct->enforcement_literal()) {
7783  temp_literals.push_back(
7784  Literal(BooleanVariable(PositiveRef(ref)), RefIsPositive(ref))
7785  .Index()
7786  .value());
7787  }
7788  relevant_constraints.push_back(c);
7789 
7790  // We only deal with bool_and included in other. Not the other way around,
7791  // Altough linear enforcement included in bool_and does happen.
7792  if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
7793  detector.AddPotentialSet(storage.Add(temp_literals));
7794  } else {
7795  detector.AddPotentialSuperset(storage.Add(temp_literals));
7796  }
7797  }
7798 
7799  int64_t num_inclusions = 0;
7800  detector.DetectInclusions([&](int subset, int superset) {
7801  ++num_inclusions;
7802  const int subset_c = relevant_constraints[subset];
7803  const int superset_c = relevant_constraints[superset];
7804  ConstraintProto* subset_ct =
7805  context_->working_model->mutable_constraints(subset_c);
7806  ConstraintProto* superset_ct =
7807  context_->working_model->mutable_constraints(superset_c);
7808  if (subset_ct->constraint_case() != ConstraintProto::kBoolAnd) return;
7809 
7810  context_->tmp_literal_set.clear();
7811  for (const int ref : subset_ct->bool_and().literals()) {
7812  context_->tmp_literal_set.insert(ref);
7813  }
7814 
7815  // Filter superset enforcement.
7816  {
7817  int new_size = 0;
7818  for (const int ref : superset_ct->enforcement_literal()) {
7819  if (context_->tmp_literal_set.contains(ref)) {
7820  context_->UpdateRuleStats("bool_and: filtered enforcement");
7821  } else if (context_->tmp_literal_set.contains(NegatedRef(ref))) {
7822  context_->UpdateRuleStats("bool_and: never enforced");
7823  superset_ct->Clear();
7824  context_->UpdateConstraintVariableUsage(superset_c);
7825  detector.StopProcessingCurrentSuperset();
7826  return;
7827  } else {
7828  superset_ct->set_enforcement_literal(new_size++, ref);
7829  }
7830  }
7831  if (new_size < superset_ct->bool_and().literals().size()) {
7832  context_->UpdateConstraintVariableUsage(superset_c);
7833  superset_ct->mutable_enforcement_literal()->Truncate(new_size);
7834  }
7835  }
7836 
7837  if (superset_ct->constraint_case() == ConstraintProto::kBoolAnd) {
7838  int new_size = 0;
7839  for (const int ref : superset_ct->bool_and().literals()) {
7840  if (context_->tmp_literal_set.contains(ref)) {
7841  context_->UpdateRuleStats("bool_and: filtered literal");
7842  } else if (context_->tmp_literal_set.contains(NegatedRef(ref))) {
7843  context_->UpdateRuleStats("bool_and: must be false");
7844  if (!MarkConstraintAsFalse(superset_ct)) return;
7845  context_->UpdateConstraintVariableUsage(superset_c);
7846  detector.StopProcessingCurrentSuperset();
7847  return;
7848  } else {
7849  superset_ct->mutable_bool_and()->set_literals(new_size++, ref);
7850  }
7851  }
7852  if (new_size < superset_ct->bool_and().literals().size()) {
7853  context_->UpdateConstraintVariableUsage(superset_c);
7854  superset_ct->mutable_bool_and()->mutable_literals()->Truncate(new_size);
7855  }
7856  }
7857 
7858  if (superset_ct->constraint_case() == ConstraintProto::kLinear) {
7859  context_->UpdateRuleStats("TODO bool_and enforcement in linear enf");
7860  }
7861  });
7862 
7863  SOLVER_LOG(logger_, "[DetectIncludedEnforcement]",
7864  " #relevant_constraints=", relevant_constraints.size(),
7865  " #num_inclusions=", num_inclusions,
7866  " work=", detector.work_done(), " time=", wall_timer.Get(), "s");
7867 }
7868 
7869 // Note that because we remove the linear constraint, this will not be called
7870 // often, so it is okay to use "heavy" data structure here.
7871 //
7872 // TODO(user): in the at most one case, consider always creating an associated
7873 // literal (l <=> var == rhs), and add the exactly_one = at_most_one U not(l)?
7874 // This constraint is implicit from what we create, however internally we will
7875 // not recover it easily, so we might not add the linear relaxation
7876 // corresponding to the constraint we just removed.
7877 bool CpModelPresolver::ProcessEncodingFromLinear(
7878  const int linear_encoding_ct_index,
7879  const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms,
7880  int64_t* num_multiple_terms) {
7881  // Preprocess exactly or at most one.
7882  bool in_exactly_one = false;
7883  absl::flat_hash_map<int, int> var_to_ref;
7884  if (at_most_or_exactly_one.constraint_case() == ConstraintProto::kAtMostOne) {
7885  for (const int ref : at_most_or_exactly_one.at_most_one().literals()) {
7886  CHECK(!var_to_ref.contains(PositiveRef(ref)));
7887  var_to_ref[PositiveRef(ref)] = ref;
7888  }
7889  } else {
7890  CHECK_EQ(at_most_or_exactly_one.constraint_case(),
7891  ConstraintProto::kExactlyOne);
7892  in_exactly_one = true;
7893  for (const int ref : at_most_or_exactly_one.exactly_one().literals()) {
7894  CHECK(!var_to_ref.contains(PositiveRef(ref)));
7895  var_to_ref[PositiveRef(ref)] = ref;
7896  }
7897  }
7898 
7899  // Preprocess the linear constraints.
7900  const ConstraintProto& linear_encoding =
7901  context_->working_model->constraints(linear_encoding_ct_index);
7902  int64_t rhs = linear_encoding.linear().domain(0);
7903  int target_ref = std::numeric_limits<int>::min();
7904  std::vector<std::pair<int, int64_t>> ref_to_coeffs;
7905  const int num_terms = linear_encoding.linear().vars().size();
7906  for (int i = 0; i < num_terms; ++i) {
7907  const int ref = linear_encoding.linear().vars(i);
7908  const int64_t coeff = linear_encoding.linear().coeffs(i);
7909  const auto it = var_to_ref.find(PositiveRef(ref));
7910 
7911  if (it == var_to_ref.end()) {
7912  CHECK_EQ(target_ref, std::numeric_limits<int>::min()) << "Uniqueness";
7913  CHECK_EQ(std::abs(coeff), 1);
7914  target_ref = coeff == 1 ? ref : NegatedRef(ref);
7915  continue;
7916  }
7917 
7918  // We transform the constraint so that the Boolean reference match exactly
7919  // what is in the at most one.
7920  if (it->second == ref) {
7921  // The term in the constraint is the same as in the at_most_one.
7922  ref_to_coeffs.push_back({ref, coeff});
7923  } else {
7924  // We replace "coeff * ref" by "coeff - coeff * (1 - ref)"
7925  rhs -= coeff;
7926  ref_to_coeffs.push_back({NegatedRef(ref), -coeff});
7927  }
7928  }
7929  if (target_ref == std::numeric_limits<int>::min() ||
7930  context_->CanBeUsedAsLiteral(target_ref)) {
7931  // We didn't find the unique integer variable. This might have happenned
7932  // because by processing other encoding we might end up with a fully boolean
7933  // constraint. Just abort, it will be presolved later.
7934  context_->UpdateRuleStats("encoding: candidate linear is all Boolean now.");
7935  return true;
7936  }
7937 
7938  // Extract the encoding.
7939  std::vector<int64_t> all_values;
7940  absl::btree_map<int64_t, std::vector<int>> value_to_refs;
7941  for (const auto& [ref, coeff] : ref_to_coeffs) {
7942  const int64_t value = rhs - coeff;
7943  all_values.push_back(value);
7944  value_to_refs[value].push_back(ref);
7945  var_to_ref.erase(PositiveRef(ref));
7946  }
7947  // The one not used "encodes" the rhs value.
7948  for (const auto& [var, ref] : var_to_ref) {
7949  all_values.push_back(rhs);
7950  value_to_refs[rhs].push_back(ref);
7951  }
7952  if (!in_exactly_one) {
7953  // To cover the corner case when the inclusion is an equality. For an at
7954  // most one, the rhs should be always reachable when all Boolean are false.
7955  all_values.push_back(rhs);
7956  }
7957 
7958  // Make sure the target domain is up to date.
7959  const Domain new_domain = Domain::FromValues(all_values);
7960  bool domain_reduced = false;
7961  if (!context_->IntersectDomainWith(target_ref, new_domain, &domain_reduced)) {
7962  return false;
7963  }
7964  if (domain_reduced) {
7965  context_->UpdateRuleStats("encoding: reduced target domain");
7966  }
7967 
7968  if (context_->CanBeUsedAsLiteral(target_ref)) {
7969  // If target is now a literal, lets not process it here.
7970  context_->UpdateRuleStats("encoding: candidate linear is all Boolean now.");
7971  return true;
7972  }
7973 
7974  // Encode the encoding.
7975  absl::flat_hash_set<int64_t> value_set;
7976  for (const int64_t v : context_->DomainOf(target_ref).Values()) {
7977  value_set.insert(v);
7978  }
7979  for (const auto& [value, literals] : value_to_refs) {
7980  // If the value is not in the domain, just set all literal to false.
7981  if (!value_set.contains(value)) {
7982  for (const int lit : literals) {
7983  if (!context_->SetLiteralToFalse(lit)) return false;
7984  }
7985  continue;
7986  }
7987 
7988  if (literals.size() == 1 && (in_exactly_one || value != rhs)) {
7989  // Optimization if there is just one literal for this value.
7990  // Note that for the "at most one" case, we can't do that for the rhs.
7991  ++*num_unique_terms;
7992  if (!context_->InsertVarValueEncoding(literals[0], target_ref, value)) {
7993  return false;
7994  }
7995  } else {
7996  ++*num_multiple_terms;
7997  const int associated_lit =
7998  context_->GetOrCreateVarValueEncoding(target_ref, value);
7999  for (const int lit : literals) {
8000  context_->AddImplication(lit, associated_lit);
8001  }
8002 
8003  // All false means associated_lit is false too.
8004  // But not for the rhs case if we are not in exactly one.
8005  if (in_exactly_one || value != rhs) {
8006  // TODO(user): Insted of bool_or + implications, we could add an
8007  // exactly one! Experiment with this. In particular it might capture
8008  // more structure for later heuristic to add the exactly one instead.
8009  // This also applies to automata/table/element expansion.
8010  auto* bool_or =
8011  context_->working_model->add_constraints()->mutable_bool_or();
8012  for (const int lit : literals) bool_or->add_literals(lit);
8013  bool_or->add_literals(NegatedRef(associated_lit));
8014  }
8015  }
8016  }
8017 
8018  // Remove linear constraint now that it is fully encoded.
8019  context_->working_model->mutable_constraints(linear_encoding_ct_index)
8020  ->Clear();
8021  context_->UpdateNewConstraintsVariableUsage();
8022  context_->UpdateConstraintVariableUsage(linear_encoding_ct_index);
8023  return true;
8024 }
8025 
8026 void CpModelPresolver::DetectDuplicateConstraints() {
8027  if (context_->time_limit()->LimitReached()) return;
8028  if (context_->ModelIsUnsat()) return;
8029 
8031  wall_timer.Start();
8032 
8033  // We need the objective written for this.
8034  if (context_->working_model->has_objective()) {
8035  if (!context_->CanonicalizeObjective()) return;
8036  context_->WriteObjectiveToProto();
8037  }
8038 
8039  // Remove duplicate constraints.
8040  // Note that at this point the objective in the proto should be up to date.
8041  //
8042  // TODO(user): We might want to do that earlier so that our count of variable
8043  // usage is not biased by duplicate constraints.
8044  const std::vector<std::pair<int, int>> duplicates =
8045  FindDuplicateConstraints(*context_->working_model);
8046  for (const auto& [dup, rep] : duplicates) {
8047  // Note that it is important to look at the type of the representative in
8048  // case the constraint became empty.
8049  DCHECK_LT(kObjectiveConstraint, 0);
8050  const int type =
8051  rep == kObjectiveConstraint
8053  : context_->working_model->constraints(rep).constraint_case();
8054 
8055  // For linear constraint, we merge their rhs since it was ignored in the
8056  // FindDuplicateConstraints() call.
8057  if (type == ConstraintProto::kLinear) {
8058  const Domain rep_domain = ReadDomainFromProto(
8059  context_->working_model->constraints(rep).linear());
8060  const Domain d = ReadDomainFromProto(
8061  context_->working_model->constraints(dup).linear());
8062  if (rep_domain != d) {
8063  context_->UpdateRuleStats("duplicate: merged rhs of linear constraint");
8064  const Domain rhs = rep_domain.IntersectionWith(d);
8065  if (rhs.IsEmpty()) {
8066  if (!MarkConstraintAsFalse(
8067  context_->working_model->mutable_constraints(rep))) {
8068  SOLVER_LOG(logger_, "Unsat after merging two linear constraints");
8069  return;
8070  }
8071 
8072  // The representative constraint is no longer a linear constraint,
8073  // so we will not enter this type case again and will just remove
8074  // all subsequent duplicate linear constraints.
8075  context_->UpdateConstraintVariableUsage(rep);
8076  continue;
8077  }
8078  FillDomainInProto(rhs, context_->working_model->mutable_constraints(rep)
8079  ->mutable_linear());
8080  }
8081  }
8082 
8083  if (type == kObjectiveConstraint) {
8084  context_->UpdateRuleStats(
8085  "duplicate: linear constraint parallel to objective");
8086  const Domain objective_domain =
8087  ReadDomainFromProto(context_->working_model->objective());
8088  const Domain d = ReadDomainFromProto(
8089  context_->working_model->constraints(dup).linear());
8090  if (objective_domain != d) {
8091  context_->UpdateRuleStats("duplicate: updated objective domain");
8092  const Domain new_domain = objective_domain.IntersectionWith(d);
8093  if (new_domain.IsEmpty()) {
8094  return (void)context_->NotifyThatModelIsUnsat(
8095  "Constraint parallel to the objective makes the objective domain "
8096  "empty.");
8097  }
8098  FillDomainInProto(new_domain,
8099  context_->working_model->mutable_objective());
8100 
8101  // TODO(user): this write/read is a bit unclean, but needed.
8102  context_->ReadObjectiveFromProto();
8103  }
8104  }
8105  context_->working_model->mutable_constraints(dup)->Clear();
8106  context_->UpdateConstraintVariableUsage(dup);
8107  context_->UpdateRuleStats("duplicate: removed constraint");
8108  }
8109 
8110  // TODO(user): We can also do similar stuff to linear constraint that just
8111  // differ at a singleton variable. Or that are equalities. Like if expr + X =
8112  // cte and expr + Y = other_cte, we can see that X is in affine relation with
8113  // Y.
8114  const std::vector<std::pair<int, int>> duplicates_without_enforcement =
8115  FindDuplicateConstraints(*context_->working_model, true);
8116  for (const auto& [dup, rep] : duplicates_without_enforcement) {
8117  auto* dup_ct = context_->working_model->mutable_constraints(dup);
8118  auto* rep_ct = context_->working_model->mutable_constraints(rep);
8119  if (rep_ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
8120  continue;
8121  }
8122 
8123  // If one of them has no enforcement, then the other can be ignored.
8124  // We always keep rep, but clear its enforcement if any.
8125  if (dup_ct->enforcement_literal().empty() ||
8126  rep_ct->enforcement_literal().empty()) {
8127  context_->UpdateRuleStats("duplicate: removed enforced constraint");
8128  rep_ct->mutable_enforcement_literal()->Clear();
8129  context_->UpdateConstraintVariableUsage(rep);
8130  dup_ct->Clear();
8131  context_->UpdateConstraintVariableUsage(dup);
8132  continue;
8133  }
8134 
8135  // Special case. This looks specific but users might reify with a cost
8136  // a duplicate constraint. In this case, no need to have two variables,
8137  // we can make them equal by duality argument.
8138  const int a = rep_ct->enforcement_literal(0);
8139  const int b = dup_ct->enforcement_literal(0);
8140  if (context_->IsFixed(a) || context_->IsFixed(b)) continue;
8141 
8142  // TODO(user): Deal with more general situation? Note that we already
8143  // do something similar in dual_bound_strengthening.Strengthen() were we
8144  // are more general as we just require an unique blocking constraint rather
8145  // than a singleton variable.
8146  //
8147  // But we could detect that "a <=> constraint" and "b <=> constraint", then
8148  // we can also add the equality. Alternatively, we can just introduce a new
8149  // variable and merge all duplicate constraint into 1 + bunch of boolean
8150  // constraints liking enforcements.
8151  if (context_->VariableWithCostIsUniqueAndRemovable(a) &&
8152  context_->VariableWithCostIsUniqueAndRemovable(b)) {
8153  // Both these case should be presolved before, but it is easy to deal with
8154  // if we encounter them here in some corner cases.
8155  if (RefIsPositive(a) == context_->ObjectiveCoeff(PositiveRef(a)) > 0) {
8156  context_->UpdateRuleStats("duplicate: dual fixing enforcement.");
8157  if (!context_->SetLiteralToFalse(a)) return;
8158  continue;
8159  }
8160  if (RefIsPositive(b) == context_->ObjectiveCoeff(PositiveRef(b)) > 0) {
8161  context_->UpdateRuleStats("duplicate: dual fixing enforcement.");
8162  if (!context_->SetLiteralToFalse(b)) return;
8163  }
8164 
8165  // Sign is correct, i.e. ignoring the constraint is expensive.
8166  // The two enforcement can be made equivalent.
8167  // Note that this work even if there are more than one enforcement.
8168  context_->UpdateRuleStats("duplicate: dual equivalence of enforcement");
8169  context_->StoreBooleanEqualityRelation(a, b);
8170 
8171  // We can also remove duplicate constraint now. It will be done later but
8172  // it seems more efficient to just do it now.
8173  if (dup_ct->enforcement_literal().size() == 1 &&
8174  rep_ct->enforcement_literal().size() == 1) {
8175  dup_ct->Clear();
8176  context_->UpdateConstraintVariableUsage(dup);
8177  }
8178  } else {
8179  context_->UpdateRuleStats(
8180  "TODO duplicate: identical constraint with different enforcements");
8181  }
8182  }
8183 
8184  // Try to find identical linear constraint with incompatible domains.
8185  // This works really well on neos16.mps.gz where we have
8186  // a <=> x <= y
8187  // b <=> x >= y
8188  // and a => not(b),
8189  // Because of this presolve, we detect that not(a) => b and thus that a and
8190  // not(b) are equivalent. We can thus simplify the problem to just
8191  // a => x < y
8192  // not(a) => x > y
8193  //
8194  // TODO(user): On that same problem, we could actually just have x != y and
8195  // remove the enforcement literal that is just used for that. But then we
8196  // will just re-create it, since we don't have a native way to handle x != y.
8197  //
8198  // TODO(user): Again on neos16.mps, we actually have cliques of x != y so we
8199  // end up with a bunch of groups of 7 variables in [0, 6] that are all
8200  // different. If we can detect that, then we close the problem quickly instead
8201  // of not closing it.
8202  bool has_all_diff = false;
8203  std::vector<std::pair<uint64_t, int>> hashes;
8204  std::vector<std::pair<int, int>> different_vars;
8205  const int num_constraints = context_->working_model->constraints_size();
8206  for (int c = 0; c < num_constraints; ++c) {
8207  const ConstraintProto& ct = context_->working_model->constraints(c);
8208  if (ct.constraint_case() == ConstraintProto::kAllDiff) {
8209  has_all_diff = true;
8210  continue;
8211  }
8212  if (ct.constraint_case() != ConstraintProto::kLinear) continue;
8213  if (ct.linear().vars().size() == 1) continue;
8214 
8215  // Detect direct encoding of x != y. Note that we also see that from x > y
8216  // and related.
8217  if (ct.linear().vars().size() == 2 && ct.enforcement_literal().empty() &&
8218  ct.linear().coeffs(0) == -ct.linear().coeffs(1) &&
8219  !ReadDomainFromProto(ct.linear()).Contains(0)) {
8220  different_vars.push_back({ct.linear().vars(0), ct.linear().vars(1)});
8221  }
8222 
8223  // TODO(user): Handle this case?
8224  if (ct.enforcement_literal().size() > 1) continue;
8225 
8226  uint64_t hash = kDefaultFingerprintSeed;
8227  hash = FingerprintRepeatedField(ct.linear().vars(), hash);
8228  hash = FingerprintRepeatedField(ct.linear().coeffs(), hash);
8229  hashes.push_back({hash, c});
8230  }
8231  std::sort(hashes.begin(), hashes.end());
8232  for (int next, start = 0; start < hashes.size(); start = next) {
8233  next = start + 1;
8234  while (next < hashes.size() && hashes[next].first == hashes[start].first) {
8235  ++next;
8236  }
8237  absl::Span<const std::pair<uint64_t, int>> range(&hashes[start],
8238  next - start);
8239  if (range.size() <= 1) continue;
8240  if (range.size() > 10) continue;
8241 
8242  for (int i = 0; i < range.size(); ++i) {
8243  const ConstraintProto& ct1 =
8244  context_->working_model->constraints(range[i].second);
8245  const int num_terms = ct1.linear().vars().size();
8246  for (int j = i + 1; j < range.size(); ++j) {
8247  const ConstraintProto& ct2 =
8248  context_->working_model->constraints(range[j].second);
8249  if (ct2.linear().vars().size() != num_terms) continue;
8250  if (!ReadDomainFromProto(ct1.linear())
8251  .IntersectionWith(ReadDomainFromProto(ct2.linear()))
8252  .IsEmpty()) {
8253  continue;
8254  }
8255  if (absl::MakeSpan(ct1.linear().vars().data(), num_terms) !=
8256  absl::MakeSpan(ct2.linear().vars().data(), num_terms)) {
8257  continue;
8258  }
8259  if (absl::MakeSpan(ct1.linear().coeffs().data(), num_terms) !=
8260  absl::MakeSpan(ct2.linear().coeffs().data(), num_terms)) {
8261  continue;
8262  }
8263 
8264  if (ct1.enforcement_literal().empty() &&
8265  ct2.enforcement_literal().empty()) {
8266  (void)context_->NotifyThatModelIsUnsat(
8267  "two incompatible linear constraint");
8268  return;
8269  }
8270  if (ct1.enforcement_literal().empty()) {
8271  context_->UpdateRuleStats(
8272  "incompatible linear: set enforcement to false");
8273  if (!context_->SetLiteralToFalse(ct2.enforcement_literal(0))) {
8274  return;
8275  }
8276  continue;
8277  }
8278  if (ct2.enforcement_literal().empty()) {
8279  context_->UpdateRuleStats(
8280  "incompatible linear: set enforcement to false");
8281  if (!context_->SetLiteralToFalse(ct1.enforcement_literal(0))) {
8282  return;
8283  }
8284  continue;
8285  }
8286 
8287  // Detect x != y via lit => x > y && not(lit) => x < y.
8288  if (ct1.linear().vars().size() == 2 &&
8289  ct1.linear().coeffs(0) == -ct1.linear().coeffs(1) &&
8290  !ReadDomainFromProto(ct1.linear()).Contains(0) &&
8291  !ReadDomainFromProto(ct2.linear()).Contains(0) &&
8292  ct1.enforcement_literal(0) ==
8293  NegatedRef(ct2.enforcement_literal(0))) {
8294  different_vars.push_back(
8295  {ct1.linear().vars(0), ct1.linear().vars(1)});
8296  }
8297 
8298  context_->UpdateRuleStats("incompatible linear: add implication");
8299  context_->AddImplication(ct1.enforcement_literal(0),
8300  NegatedRef(ct2.enforcement_literal(0)));
8301  }
8302  }
8303  }
8304 
8305  // Detect all_different cliques.
8306  // We reuse the max-clique code from sat.
8307  //
8308  // TODO(user): To avoid doing that more than once, we only run it if there
8309  // is no all-diff in the model already. This is not perfect.
8310  //
8311  // Note(user): The all diff added here will not be expanded since we run this
8312  // after expansion. This is fragile though. Not even sure this is what we
8313  // want.
8314  //
8315  // TODO(user): Start with the existing all diff and expand them rather than
8316  // not running this if there are all_diff present.
8317  if (context_->params().infer_all_diffs() && !has_all_diff &&
8318  different_vars.size() > 2) {
8319  WallTimer local_time;
8320  local_time.Start();
8321 
8322  std::vector<std::vector<Literal>> cliques;
8323  absl::flat_hash_set<int> used_var;
8324 
8325  Model local_model;
8326  const int num_variables = context_->working_model->variables().size();
8327  local_model.GetOrCreate<Trail>()->Resize(num_variables);
8328  auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
8329  graph->Resize(num_variables);
8330  for (const auto [var1, var2] : different_vars) {
8331  if (!RefIsPositive(var1)) continue;
8332  if (!RefIsPositive(var2)) continue;
8333  if (var1 == var2) {
8334  (void)context_->NotifyThatModelIsUnsat("x != y with x == y");
8335  return;
8336  }
8337  // All variables at false is always a valid solution of the local model,
8338  // so this should never return UNSAT.
8339  CHECK(graph->AddAtMostOne({Literal(BooleanVariable(var1), true),
8340  Literal(BooleanVariable(var2), true)}));
8341  if (!used_var.contains(var1)) {
8342  used_var.insert(var1);
8343  cliques.push_back({Literal(BooleanVariable(var1), true),
8344  Literal(BooleanVariable(var2), true)});
8345  }
8346  if (!used_var.contains(var2)) {
8347  used_var.insert(var2);
8348  cliques.push_back({Literal(BooleanVariable(var1), true),
8349  Literal(BooleanVariable(var2), true)});
8350  }
8351  }
8352  CHECK(graph->DetectEquivalences());
8353  graph->TransformIntoMaxCliques(&cliques, 1e8);
8354 
8355  int num_cliques = 0;
8356  int64_t cumulative_size = 0;
8357  for (const std::vector<Literal>& clique : cliques) {
8358  if (clique.size() <= 2) continue;
8359 
8360  ++num_cliques;
8361  cumulative_size += clique.size();
8362  context_->UpdateRuleStats("all_diff: inferred from x != y constraints");
8363  auto* new_ct =
8364  context_->working_model->add_constraints()->mutable_all_diff();
8365  for (const Literal l : clique) {
8366  auto* expr = new_ct->add_exprs();
8367  expr->add_vars(l.Variable().value());
8368  expr->add_coeffs(1);
8369  }
8370  }
8371  SOLVER_LOG(logger_, "[AllDiffInferrence]",
8372  " #different=", different_vars.size(), " #cliques=", num_cliques,
8373  " #size=", cumulative_size, " time=", local_time.Get(), "s");
8374  }
8375 
8376  context_->UpdateNewConstraintsVariableUsage();
8377  SOLVER_LOG(logger_, "[DetectDuplicateConstraints]",
8378  " #duplicates=", duplicates.size(),
8379  " #without_enforcements=", duplicates_without_enforcement.size(),
8380  " time=", wall_timer.Get(), "s");
8381 }
8382 
8383 void CpModelPresolver::DetectDominatedLinearConstraints() {
8384  if (context_->time_limit()->LimitReached()) return;
8385  if (context_->ModelIsUnsat()) return;
8386  if (context_->params().presolve_inclusion_work_limit() == 0) return;
8387 
8389  wall_timer.Start();
8390 
8391  // We will reuse the constraint <-> variable graph as a storage for the
8392  // inclusion detection.
8393  InclusionDetector detector(context_->ConstraintToVarsGraph());
8394  detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
8395 
8396  // Because we use the constraint <-> variable graph, we cannot modify it
8397  // during DetectInclusions(). So we delay the update of the graph.
8398  std::vector<int> constraint_indices_to_clean;
8399 
8400  // Cache the linear expression domain.
8401  // TODO(user): maybe we should store this instead of recomputing it.
8402  absl::flat_hash_map<int, Domain> cached_expr_domain;
8403 
8404  const int num_constraints = context_->working_model->constraints().size();
8405  for (int c = 0; c < num_constraints; ++c) {
8406  const ConstraintProto& ct = context_->working_model->constraints(c);
8407  if (ct.constraint_case() != ConstraintProto::kLinear) continue;
8408 
8409  // TODO(user): We can deal with enforced constraints in some situation.
8410  if (!ct.enforcement_literal().empty()) continue;
8411 
8412  if (!LinearConstraintIsClean(ct.linear())) {
8413  // This shouldn't happen except in potential corner cases were the
8414  // constraints were not canonicalized before this point. We just skip
8415  // such constraint.
8416  continue;
8417  }
8418 
8419  DCHECK_LT(c, context_->ConstraintToVarsGraph().size());
8420  detector.AddPotentialSet(c);
8421 
8422  const auto [min_activity, max_activity] =
8423  context_->ComputeMinMaxActivity(ct.linear());
8424  cached_expr_domain[c] = Domain(min_activity, max_activity);
8425  }
8426 
8427  int64_t num_inclusions = 0;
8428  absl::flat_hash_map<int, int64_t> coeff_map;
8429  detector.DetectInclusions([&](int subset_c, int superset_c) {
8430  ++num_inclusions;
8431 
8432  // Store the coeff of the subset linear constraint in a map.
8433  const ConstraintProto subset_ct =
8434  context_->working_model->constraints(subset_c);
8435  const LinearConstraintProto& subset_lin = subset_ct.linear();
8436  coeff_map.clear();
8437  detector.IncreaseWorkDone(subset_lin.vars().size());
8438  for (int i = 0; i < subset_lin.vars().size(); ++i) {
8439  coeff_map[subset_lin.vars(i)] = subset_lin.coeffs(i);
8440  }
8441 
8442  // We have a perfect match if 'factor_a * subset == factor_b * superset' on
8443  // the common positions. Note that assuming subset has been gcd reduced,
8444  // there is not point considering factor_b != 1.
8445  bool perfect_match = true;
8446  int64_t factor = 0;
8447 
8448  // Lets compute the implied domain of the linear expression
8449  // "superset - subset". Note that we actually do not need exact inclusion
8450  // for this algorithm to work, but it is an heuristic to not try it with
8451  // all pair of constraints.
8452  const ConstraintProto& superset_ct =
8453  context_->working_model->constraints(superset_c);
8454  const LinearConstraintProto& superset_lin = superset_ct.linear();
8455  int64_t diff_min_activity = 0;
8456  int64_t diff_max_activity = 0;
8457  detector.IncreaseWorkDone(superset_lin.vars().size());
8458  for (int i = 0; i < superset_lin.vars().size(); ++i) {
8459  const int var = superset_lin.vars(i);
8460  int64_t coeff = superset_lin.coeffs(i);
8461  const auto it = coeff_map.find(var);
8462  if (it != coeff_map.end()) {
8463  const int64_t subset_coeff = it->second;
8464  if (perfect_match) {
8465  if (coeff % subset_coeff == 0) {
8466  const int64_t div = coeff / subset_coeff;
8467  if (factor == 0) {
8468  // Note that factor can be negative.
8469  factor = div;
8470  } else if (factor != div) {
8471  perfect_match = false;
8472  }
8473  } else {
8474  perfect_match = false;
8475  }
8476  }
8477 
8478  // TODO(user): compute the factor first in case it is != 1 ?
8479  coeff -= subset_coeff;
8480  }
8481  if (coeff == 0) continue;
8482  if (coeff > 0) {
8483  diff_min_activity += coeff * context_->MinOf(var);
8484  diff_max_activity += coeff * context_->MaxOf(var);
8485  } else {
8486  diff_min_activity += coeff * context_->MaxOf(var);
8487  diff_max_activity += coeff * context_->MinOf(var);
8488  }
8489  }
8490 
8491  const Domain diff_domain(diff_min_activity, diff_max_activity);
8492  const Domain subset_ct_domain = ReadDomainFromProto(subset_lin);
8493  const Domain superset_ct_domain = ReadDomainFromProto(superset_lin);
8494 
8495  // Case 1: superset is redundant.
8496  // We process this one first as it let us remove the longest constraint.
8497  const Domain implied_superset_domain =
8498  subset_ct_domain.AdditionWith(diff_domain)
8499  .IntersectionWith(cached_expr_domain[superset_c]);
8500  if (implied_superset_domain.IsIncludedIn(superset_ct_domain)) {
8501  context_->UpdateRuleStats(
8502  "linear inclusion: redundant containing constraint");
8503  context_->working_model->mutable_constraints(superset_c)->Clear();
8504  constraint_indices_to_clean.push_back(superset_c);
8505  detector.StopProcessingCurrentSuperset();
8506  return;
8507  }
8508 
8509  // Case 2: subset is redundant.
8510  const Domain implied_subset_domain =
8511  superset_ct_domain.AdditionWith(diff_domain.Negation())
8512  .IntersectionWith(cached_expr_domain[subset_c]);
8513  if (implied_subset_domain.IsIncludedIn(subset_ct_domain)) {
8514  context_->UpdateRuleStats(
8515  "linear inclusion: redundant included constraint");
8516  context_->working_model->mutable_constraints(subset_c)->Clear();
8517  constraint_indices_to_clean.push_back(subset_c);
8518  detector.StopProcessingCurrentSubset();
8519  return;
8520  }
8521 
8522  // When we have equality constraint, we might try substitution. For now we
8523  // only try that when we have a perfect inclusion with the same coefficients
8524  // after multiplication by factor.
8525  if (perfect_match) {
8526  CHECK_NE(factor, 0);
8527  if (subset_ct_domain.IsFixed()) {
8528  // Rewrite the constraint by removing subset from it and updating
8529  // the domain to domain - factor * subset_domain.
8530  //
8531  // This seems always beneficial, although we might miss some
8532  // oportunities for constraint included in the superset if we do that
8533  // too early.
8534  context_->UpdateRuleStats("linear inclusion: subset is equality");
8535  int new_size = 0;
8536  auto* mutable_linear =
8537  context_->working_model->mutable_constraints(superset_c)
8538  ->mutable_linear();
8539  for (int i = 0; i < mutable_linear->vars().size(); ++i) {
8540  const int var = mutable_linear->vars(i);
8541  const int64_t coeff = mutable_linear->coeffs(i);
8542  const auto it = coeff_map.find(var);
8543  if (it != coeff_map.end()) {
8544  CHECK_EQ(factor * it->second, coeff);
8545  continue;
8546  }
8547  mutable_linear->set_vars(new_size, var);
8548  mutable_linear->set_coeffs(new_size, coeff);
8549  ++new_size;
8550  }
8551  mutable_linear->mutable_vars()->Truncate(new_size);
8552  mutable_linear->mutable_coeffs()->Truncate(new_size);
8553  FillDomainInProto(superset_ct_domain.AdditionWith(
8554  subset_ct_domain.MultiplicationBy(-factor)),
8555  mutable_linear);
8556  constraint_indices_to_clean.push_back(superset_c);
8557  detector.StopProcessingCurrentSuperset();
8558  return;
8559  } else {
8560  // Propagate domain on the superset - subset variables.
8561  // TODO(user): We can probably still do that if the inclusion is not
8562  // perfect.
8563  temp_ct_.Clear();
8564  auto* mutable_linear = temp_ct_.mutable_linear();
8565  for (int i = 0; i < superset_lin.vars().size(); ++i) {
8566  const int var = superset_lin.vars(i);
8567  const int64_t coeff = superset_lin.coeffs(i);
8568  const auto it = coeff_map.find(var);
8569  if (it != coeff_map.end()) continue;
8570  mutable_linear->add_vars(var);
8571  mutable_linear->add_coeffs(coeff);
8572  }
8573  FillDomainInProto(superset_ct_domain.AdditionWith(
8574  subset_ct_domain.MultiplicationBy(-factor)),
8575  mutable_linear);
8576  PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_);
8577  if (context_->ModelIsUnsat()) detector.Stop();
8578  }
8579  if (superset_ct_domain.IsFixed()) {
8580  if (subset_lin.vars().size() + 1 == superset_lin.vars().size()) {
8581  // Because we propagated the equation on the singleton variable above,
8582  // and we have an equality, the subset is redundant!
8583  context_->UpdateRuleStats(
8584  "linear inclusion: subset + singleton is equality");
8585  context_->working_model->mutable_constraints(subset_c)->Clear();
8586  constraint_indices_to_clean.push_back(subset_c);
8587  detector.StopProcessingCurrentSubset();
8588  return;
8589  }
8590 
8591  // This one could make sense if subset is large vs superset.
8592  context_->UpdateRuleStats(
8593  "TODO linear inclusion: superset is equality");
8594  }
8595  }
8596  });
8597 
8598  for (const int c : constraint_indices_to_clean) {
8599  context_->UpdateConstraintVariableUsage(c);
8600  }
8601 
8602  SOLVER_LOG(logger_, "[DetectDominatedLinearConstraints]",
8603  " #relevant_constraints=", detector.num_potential_supersets(),
8604  " #work_done=", detector.work_done(),
8605  " #num_inclusions=", num_inclusions,
8606  " #num_redundant=", constraint_indices_to_clean.size(),
8607  " time=", wall_timer.Get(), "s");
8608 }
8609 
8610 // Note that internally, we already split long linear into smaller chunk, so
8611 // it should be beneficial to identify common part between many linear
8612 // constraint.
8613 //
8614 // Note(user): This was made to work on var-smallemery-m6j6.pb.gz, but applies
8615 // to quite a few miplib problem. Try to improve the heuristics and algorithm to
8616 // be faster and detect larger block.
8617 void CpModelPresolver::FindBigLinearOverlap() {
8618  if (context_->time_limit()->LimitReached()) return;
8619  if (context_->ModelIsUnsat()) return;
8620  if (context_->params().presolve_inclusion_work_limit() == 0) return;
8621 
8623  wall_timer.Start();
8624 
8625  const int num_constraints = context_->working_model->constraints_size();
8626  std::vector<std::pair<int, int>> to_sort;
8627  for (int c = 0; c < num_constraints; ++c) {
8628  const ConstraintProto& ct = context_->working_model->constraints(c);
8629  if (ct.constraint_case() != ConstraintProto::kLinear) continue;
8630  const int size = ct.linear().vars().size();
8631  if (size < 5) continue;
8632  to_sort.push_back({-size, c});
8633  }
8634  std::sort(to_sort.begin(), to_sort.end());
8635 
8636  std::vector<int> sorted_linear;
8637  for (int i = 0; i < to_sort.size(); ++i) {
8638  sorted_linear.push_back(to_sort[i].second);
8639  }
8640 
8641  // In double for more readable display.
8642  double work_done = 0;
8643  const double work_limit = 1e9;
8644 
8645  int64_t num_blocks = 0;
8646  int64_t nz_reduction = 0;
8647  absl::flat_hash_map<int, int64_t> coeff_map;
8648  absl::flat_hash_set<int> processed;
8649  for (int i = 0; i < sorted_linear.size(); ++i) {
8650  const int c = sorted_linear[i];
8651  if (c < 0) continue;
8652  if (work_done > work_limit) break;
8653 
8654  coeff_map.clear();
8655  {
8656  const ConstraintProto& ct = context_->working_model->constraints(c);
8657  const int num_terms = ct.linear().vars().size();
8658  work_done += num_terms;
8659  for (int k = 0; k < num_terms; ++k) {
8660  coeff_map[ct.linear().vars(k)] = ct.linear().coeffs(k);
8661  }
8662  }
8663 
8664  // Look for an initial overlap big enough.
8665  //
8666  // Note that because we construct it incrementally, we need the first two
8667  // constraint to have an overlap of at least half this.
8668  int saved_nz = 100;
8669  std::vector<int> block = {i};
8670  std::vector<std::pair<int, int64_t>> common_part;
8671 
8672  for (int j = 0; j < sorted_linear.size(); ++j) {
8673  if (i == j) continue;
8674  const int other_c = sorted_linear[j];
8675  if (other_c < 0) continue;
8676  const ConstraintProto& ct = context_->working_model->constraints(other_c);
8677 
8678  // No need to continue if linear is not large enough.
8679  const int num_terms = ct.linear().vars().size();
8680  const int best_saved_nz = block.size() * (num_terms - 1) - 2;
8681  if (best_saved_nz <= saved_nz) break;
8682 
8683  work_done += num_terms;
8684  common_part.clear();
8685  for (int k = 0; k < num_terms; ++k) {
8686  const auto it = coeff_map.find(ct.linear().vars(k));
8687  if (it != coeff_map.end() && it->second == ct.linear().coeffs(k)) {
8688  common_part.push_back({ct.linear().vars(k), ct.linear().coeffs(k)});
8689  }
8690  }
8691 
8692  // We replace (new_block_size) * (common_size) by
8693  // 1/ and equation of size common_size + 1
8694  // 2/ new_block_size variable
8695  // So new_block_size * common_size - common_size - 1 - new_block_size
8696  // which is (new_block_size - 1) * (common_size - 1) - 2;
8697  const int64_t new_saved_nz = block.size() * (common_part.size() - 1) - 2;
8698  if (new_saved_nz > saved_nz) {
8699  saved_nz = new_saved_nz;
8700  block.push_back(j);
8701  coeff_map.clear();
8702  for (const auto [var, coeff] : common_part) {
8703  coeff_map[var] = coeff;
8704  }
8705  }
8706  }
8707 
8708  // Introduce a new variable = common_part.
8709  // Use it in all linear constraint.
8710  //
8711  // TODO(user): In some case we only need common_part <= new_var.
8712  //
8713  // TODO(user): If the common part is expressable via one of the constraint
8714  // in the block as == other terms, we could just use these instead of
8715  // creating a new variable?
8716  if (block.size() > 1) {
8717  context_->UpdateRuleStats("linear matrix: common rectangle");
8718  ++num_blocks;
8719  nz_reduction += saved_nz;
8720 
8721  int64_t gcd = 0;
8722  int64_t min_activity = 0;
8723  int64_t max_activity = 0;
8724  common_part.clear();
8725  for (const auto [var, coeff] : coeff_map) {
8726  common_part.push_back({var, coeff});
8727  gcd = std::gcd(gcd, std::abs(coeff));
8728  if (coeff > 0) {
8729  min_activity += coeff * context_->MinOf(var);
8730  max_activity += coeff * context_->MaxOf(var);
8731  } else {
8732  min_activity += coeff * context_->MaxOf(var);
8733  max_activity += coeff * context_->MinOf(var);
8734  }
8735  }
8736 
8737  // Create new variable.
8738  const int new_var =
8739  context_->NewIntVar(Domain(min_activity / gcd, max_activity / gcd));
8740 
8741  // Create new linear constraint sum common_part = new_var
8742  auto* new_linear =
8743  context_->working_model->add_constraints()->mutable_linear();
8744  std::sort(common_part.begin(), common_part.end());
8745  for (const auto [var, coeff] : common_part) {
8746  new_linear->add_vars(var);
8747  new_linear->add_coeffs(coeff / gcd);
8748  }
8749  new_linear->add_vars(new_var);
8750  new_linear->add_coeffs(-1);
8751  new_linear->add_domain(0);
8752  new_linear->add_domain(0);
8753  context_->UpdateNewConstraintsVariableUsage();
8754 
8755  // Replace in each constraint the common part by gcd * new_var !
8756  for (const int j : block) {
8757  const int c = sorted_linear[j];
8758  sorted_linear[j] = -1; // Clear.
8759  auto* mutable_linear =
8760  context_->working_model->mutable_constraints(c)->mutable_linear();
8761  const int num_terms = mutable_linear->vars().size();
8762  int new_size = 0;
8763  for (int k = 0; k < num_terms; ++k) {
8764  if (coeff_map.contains(mutable_linear->vars(k))) continue;
8765  mutable_linear->set_vars(new_size, mutable_linear->vars(k));
8766  mutable_linear->set_coeffs(new_size, mutable_linear->coeffs(k));
8767  ++new_size;
8768  }
8769  CHECK_EQ(new_size, num_terms - common_part.size());
8770  mutable_linear->mutable_vars()->Truncate(new_size);
8771  mutable_linear->mutable_coeffs()->Truncate(new_size);
8772  mutable_linear->add_vars(new_var);
8773  mutable_linear->add_coeffs(gcd);
8774 
8775  context_->UpdateConstraintVariableUsage(c);
8776  }
8777  }
8778  }
8779 
8780  DCHECK(context_->ConstraintVariableUsageIsConsistent());
8781  SOLVER_LOG(logger_, "[FindBigLinearOverlap]", " #blocks=", num_blocks,
8782  " #saved_nz=", nz_reduction, " #linears=", sorted_linear.size(),
8783  " #work_done=", work_done, "/", work_limit,
8784  " time=", wall_timer.Get(), "s");
8785 }
8786 
8787 void CpModelPresolver::ExtractEncodingFromLinear() {
8788  if (context_->time_limit()->LimitReached()) return;
8789  if (context_->ModelIsUnsat()) return;
8790  if (context_->params().presolve_inclusion_work_limit() == 0) return;
8791 
8793  wall_timer.Start();
8794 
8795  // TODO(user): compute on the fly instead of temporary storing variables?
8796  std::vector<int> relevant_constraints;
8797  CompactVectorVector<int> storage;
8798  InclusionDetector detector(storage);
8799  detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
8800 
8801  // Loop over the constraints and fill the structures above.
8802  //
8803  // TODO(user): Ideally we want to process exactly_one first in case a
8804  // linear constraint is both included in an at_most_one and an exactly_one.
8805  std::vector<int> vars;
8806  const int num_constraints = context_->working_model->constraints().size();
8807  for (int c = 0; c < num_constraints; ++c) {
8808  const ConstraintProto& ct = context_->working_model->constraints(c);
8809  switch (ct.constraint_case()) {
8810  case ConstraintProto::kAtMostOne: {
8811  vars.clear();
8812  for (const int ref : ct.at_most_one().literals()) {
8813  vars.push_back(PositiveRef(ref));
8814  }
8815  relevant_constraints.push_back(c);
8816  detector.AddPotentialSuperset(storage.Add(vars));
8817  break;
8818  }
8819  case ConstraintProto::kExactlyOne: {
8820  vars.clear();
8821  for (const int ref : ct.exactly_one().literals()) {
8822  vars.push_back(PositiveRef(ref));
8823  }
8824  relevant_constraints.push_back(c);
8825  detector.AddPotentialSuperset(storage.Add(vars));
8826  break;
8827  }
8828  case ConstraintProto::kLinear: {
8829  // We only consider equality with no enforcement.
8830  if (!ct.enforcement_literal().empty()) continue;
8831  if (ct.linear().domain().size() != 2) continue;
8832  if (ct.linear().domain(0) != ct.linear().domain(1)) continue;
8833 
8834  // We also want a single non-Boolean.
8835  // Note that this assume the constraint is canonicalized.
8836  bool is_candidate = true;
8837  int num_integers = 0;
8838  vars.clear();
8839  const int num_terms = ct.linear().vars().size();
8840  for (int i = 0; i < num_terms; ++i) {
8841  const int ref = ct.linear().vars(i);
8842  if (context_->CanBeUsedAsLiteral(ref)) {
8843  vars.push_back(PositiveRef(ref));
8844  } else {
8845  ++num_integers;
8846  if (std::abs(ct.linear().coeffs(i)) != 1) {
8847  is_candidate = false;
8848  break;
8849  }
8850  if (num_integers == 2) {
8851  is_candidate = false;
8852  break;
8853  }
8854  }
8855  }
8856 
8857  // We ignore cases with just one Boolean as this should be already dealt
8858  // with elsewhere.
8859  if (is_candidate && num_integers == 1 && vars.size() > 1) {
8860  relevant_constraints.push_back(c);
8861  detector.AddPotentialSubset(storage.Add(vars));
8862  }
8863  break;
8864  }
8865  default:
8866  break;
8867  }
8868  }
8869 
8870  // Stats.
8871  int64_t num_exactly_one_encodings = 0;
8872  int64_t num_at_most_one_encodings = 0;
8873  int64_t num_literals = 0;
8874  int64_t num_unique_terms = 0;
8875  int64_t num_multiple_terms = 0;
8876 
8877  detector.DetectInclusions([&](int subset, int superset) {
8878  const int subset_c = relevant_constraints[subset];
8879  const int superset_c = relevant_constraints[superset];
8880  const ConstraintProto& superset_ct =
8881  context_->working_model->constraints(superset_c);
8882  if (superset_ct.constraint_case() == ConstraintProto::kAtMostOne) {
8883  ++num_at_most_one_encodings;
8884  } else {
8885  ++num_exactly_one_encodings;
8886  }
8887  num_literals += storage[subset].size();
8888  context_->UpdateRuleStats("encoding: extracted from linear");
8889 
8890  if (!ProcessEncodingFromLinear(subset_c, superset_ct, &num_unique_terms,
8891  &num_multiple_terms)) {
8892  detector.Stop(); // UNSAT.
8893  }
8894 
8895  detector.StopProcessingCurrentSubset();
8896  });
8897 
8898  SOLVER_LOG(logger_, "[ExtractEncodingFromLinear]",
8899  " #potential_supersets=", detector.num_potential_supersets(),
8900  " #potential_subsets=", detector.num_potential_subsets(),
8901  " #at_most_one_encodings=", num_at_most_one_encodings,
8902  " #exactly_one_encodings=", num_exactly_one_encodings,
8903  " #unique_terms=", num_unique_terms,
8904  " #multiple_terms=", num_multiple_terms,
8905  " #literals=", num_literals, " time=", wall_timer.Get(), "s");
8906 }
8907 
8908 // Special case: if a literal l appear in exactly two constraints:
8909 // - l => var in domain1
8910 // - not(l) => var in domain2
8911 // then we know that domain(var) is included in domain1 U domain2,
8912 // and that the literal l can be removed (and determined at postsolve).
8913 //
8914 // TODO(user): This could be generalized further to linear of size > 1 if for
8915 // example the terms are the same.
8916 //
8917 // We wait for the model expansion to take place in order to avoid removing
8918 // encoding that will later be re-created during expansion.
8919 void CpModelPresolver::LookAtVariableWithDegreeTwo(int var) {
8920  CHECK(RefIsPositive(var));
8921  CHECK(context_->ConstraintVariableGraphIsUpToDate());
8922  if (context_->ModelIsUnsat()) return;
8923  if (context_->keep_all_feasible_solutions) return;
8924  if (context_->IsFixed(var)) return;
8925  if (!context_->ModelIsExpanded()) return;
8926  if (!context_->CanBeUsedAsLiteral(var)) return;
8927 
8928  // TODO(user): If var is in objective, we might be able to tighten domains.
8929  // ex: enf => x \in [0, 1]
8930  // not(enf) => x \in [1, 2]
8931  // The x can be removed from one place. Maybe just do <=> not in [0,1] with
8932  // dual code?
8933  if (context_->VarToConstraints(var).size() != 2) return;
8934 
8935  bool abort = false;
8936  int ct_var = -1;
8937  Domain union_of_domain;
8938  int num_positive = 0;
8939  std::vector<int> constraint_indices_to_remove;
8940  for (const int c : context_->VarToConstraints(var)) {
8941  if (c < 0) {
8942  abort = true;
8943  break;
8944  }
8945  constraint_indices_to_remove.push_back(c);
8946  const ConstraintProto& ct = context_->working_model->constraints(c);
8947  if (ct.enforcement_literal().size() != 1 ||
8948  PositiveRef(ct.enforcement_literal(0)) != var ||
8949  ct.constraint_case() != ConstraintProto::kLinear ||
8950  ct.linear().vars().size() != 1) {
8951  abort = true;
8952  break;
8953  }
8954  if (ct.enforcement_literal(0) == var) ++num_positive;
8955  if (ct_var != -1 && PositiveRef(ct.linear().vars(0)) != ct_var) {
8956  abort = true;
8957  break;
8958  }
8959  ct_var = PositiveRef(ct.linear().vars(0));
8960  union_of_domain = union_of_domain.UnionWith(
8961  ReadDomainFromProto(ct.linear())
8962  .InverseMultiplicationBy(RefIsPositive(ct.linear().vars(0))
8963  ? ct.linear().coeffs(0)
8964  : -ct.linear().coeffs(0)));
8965  }
8966  if (abort) return;
8967  if (num_positive != 1) return;
8968  if (!context_->IntersectDomainWith(ct_var, union_of_domain)) return;
8969 
8970  context_->UpdateRuleStats("variables: removable enforcement literal");
8971  for (const int c : constraint_indices_to_remove) {
8972  *context_->mapping_model->add_constraints() =
8973  context_->working_model->constraints(c);
8974  context_->mapping_model
8975  ->mutable_constraints(context_->mapping_model->constraints().size() - 1)
8976  ->set_name("removable enforcement literal");
8977  context_->working_model->mutable_constraints(c)->Clear();
8978  context_->UpdateConstraintVariableUsage(c);
8979  }
8980  context_->MarkVariableAsRemoved(var);
8981 }
8982 
8983 namespace {
8984 
8985 absl::Span<const int> AtMostOneOrExactlyOneLiterals(const ConstraintProto& ct) {
8986  if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
8987  return {ct.at_most_one().literals()};
8988  } else {
8989  return {ct.exactly_one().literals()};
8990  }
8991 }
8992 
8993 } // namespace
8994 
8995 void CpModelPresolver::ProcessVariableInTwoAtMostOrExactlyOne(int var) {
8996  DCHECK(RefIsPositive(var));
8997  DCHECK(context_->ConstraintVariableGraphIsUpToDate());
8998  if (context_->ModelIsUnsat()) return;
8999  if (context_->keep_all_feasible_solutions) return;
9000  if (context_->IsFixed(var)) return;
9001  if (context_->VariableWasRemoved(var)) return;
9002  if (!context_->ModelIsExpanded()) return;
9003  if (!context_->CanBeUsedAsLiteral(var)) return;
9004 
9005  int64_t cost = 0;
9006  if (context_->VarToConstraints(var).contains(kObjectiveConstraint)) {
9007  if (context_->VarToConstraints(var).size() != 3) return;
9008  cost = context_->ObjectiveMap().at(var);
9009  } else {
9010  if (context_->VarToConstraints(var).size() != 2) return;
9011  }
9012 
9013  // We have a variable with a cost (or without) that appear in two constraints.
9014  // We want two at_most_one or exactly_one.
9015  // TODO(user): Also deal with bool_and.
9016  int c1 = -1;
9017  int c2 = -1;
9018  for (const int c : context_->VarToConstraints(var)) {
9019  if (c < 0) continue;
9020  const ConstraintProto& ct = context_->working_model->constraints(c);
9021  if (ct.constraint_case() != ConstraintProto::kAtMostOne &&
9022  ct.constraint_case() != ConstraintProto::kExactlyOne) {
9023  return;
9024  }
9025  if (c1 == -1) {
9026  c1 = c;
9027  } else {
9028  c2 = c;
9029  }
9030  }
9031 
9032  // This can happen for variable in a kAffineRelationConstraint.
9033  if (c1 == -1 || c2 == -1) return;
9034 
9035  // Tricky: We iterate on a map above, so the order is non-deterministic, we
9036  // do not want that, so we re-order the constraints.
9037  if (c1 > c2) std::swap(c1, c2);
9038 
9039  // We can always sum the two constraints.
9040  // If var appear in one and not(var) in the other, the two term cancel out to
9041  // one, so we still have an <= 1 (or eventually a ==1 (see below).
9042  //
9043  // Note that if the constraint are of size one, they can just be preprocessed
9044  // individually and just be removed. So we abort here as the code below
9045  // is incorrect if new_ct is an empty constraint.
9046  context_->tmp_literals.clear();
9047  int c1_ref = std::numeric_limits<int>::min();
9048  const ConstraintProto& ct1 = context_->working_model->constraints(c1);
9049  if (AtMostOneOrExactlyOneLiterals(ct1).size() <= 1) return;
9050  for (const int lit : AtMostOneOrExactlyOneLiterals(ct1)) {
9051  if (PositiveRef(lit) == var) {
9052  c1_ref = lit;
9053  } else {
9054  context_->tmp_literals.push_back(lit);
9055  }
9056  }
9057  int c2_ref = std::numeric_limits<int>::min();
9058  const ConstraintProto& ct2 = context_->working_model->constraints(c2);
9059  if (AtMostOneOrExactlyOneLiterals(ct2).size() <= 1) return;
9060  for (const int lit : AtMostOneOrExactlyOneLiterals(ct2)) {
9061  if (PositiveRef(lit) == var) {
9062  c2_ref = lit;
9063  } else {
9064  context_->tmp_literals.push_back(lit);
9065  }
9066  }
9067  DCHECK_NE(c1_ref, std::numeric_limits<int>::min());
9068  DCHECK_NE(c2_ref, std::numeric_limits<int>::min());
9069  if (c1_ref != NegatedRef(c2_ref)) return;
9070 
9071  // If the cost is non-zero, we can use an exactly one to make it zero.
9072  // Use that exactly one in the postsolve to recover the value of var.
9073  int64_t cost_shift = 0;
9074  absl::Span<const int> literals;
9075  if (ct1.constraint_case() == ConstraintProto::kExactlyOne) {
9076  cost_shift = RefIsPositive(c1_ref) ? cost : -cost;
9077  literals = ct1.exactly_one().literals();
9078  } else if (ct2.constraint_case() == ConstraintProto::kExactlyOne) {
9079  cost_shift = RefIsPositive(c2_ref) ? cost : -cost;
9080  literals = ct2.exactly_one().literals();
9081  } else {
9082  // Dual argument. The one with a negative cost can be transformed to
9083  // an exactly one.
9084  if (context_->keep_all_feasible_solutions) return;
9085  if (RefIsPositive(c1_ref) == (cost < 0)) {
9086  cost_shift = RefIsPositive(c1_ref) ? cost : -cost;
9087  literals = ct1.at_most_one().literals();
9088  } else {
9089  cost_shift = RefIsPositive(c2_ref) ? cost : -cost;
9090  literals = ct2.at_most_one().literals();
9091  }
9092  }
9093 
9094  if (!context_->ShiftCostInExactlyOne(literals, cost_shift)) return;
9095  DCHECK(!context_->ObjectiveMap().contains(var));
9096  context_->mapping_model->add_constraints()
9097  ->mutable_exactly_one()
9098  ->mutable_literals()
9099  ->Assign(literals.begin(), literals.end());
9100 
9101  // We can now replace the two constraint by a single one, and delete var!
9102  const int new_ct_index = context_->working_model->constraints().size();
9103  ConstraintProto* new_ct = context_->working_model->add_constraints();
9104  if (ct1.constraint_case() == ConstraintProto::kExactlyOne &&
9105  ct2.constraint_case() == ConstraintProto::kExactlyOne) {
9106  for (const int lit : context_->tmp_literals) {
9107  new_ct->mutable_exactly_one()->add_literals(lit);
9108  }
9109  } else {
9110  // At most one here is enough: if all zero, we can satisfy one of the
9111  // two exactly one at postsolve.
9112  for (const int lit : context_->tmp_literals) {
9113  new_ct->mutable_at_most_one()->add_literals(lit);
9114  }
9115  }
9116 
9117  context_->UpdateNewConstraintsVariableUsage();
9118  context_->working_model->mutable_constraints(c1)->Clear();
9119  context_->UpdateConstraintVariableUsage(c1);
9120  context_->working_model->mutable_constraints(c2)->Clear();
9121  context_->UpdateConstraintVariableUsage(c2);
9122 
9123  context_->UpdateRuleStats(
9124  "at_most_one: resolved two constraints with opposite literal");
9125  context_->MarkVariableAsRemoved(var);
9126 
9127  // TODO(user): If the merged list contains duplicates or literal that are
9128  // negation of other, we need to deal with that right away. For some reason
9129  // something is not robust to that it seems. Investigate & fix!
9130  DCHECK_NE(new_ct->constraint_case(), ConstraintProto::CONSTRAINT_NOT_SET);
9131  if (PresolveAtMostOrExactlyOne(new_ct)) {
9132  context_->UpdateConstraintVariableUsage(new_ct_index);
9133  }
9134 }
9135 
9136 // TODO(user): We can still remove the variable even if we want to keep
9137 // all feasible solutions for the cases when we have a full encoding.
9138 //
9139 // TODO(user): In fixed search, we disable this rule because we don't update
9140 // the search strategy, but for some strategy we could.
9141 //
9142 // TODO(user): The hint might get lost if the encoding was created during
9143 // the presolve.
9144 void CpModelPresolver::ProcessVariableOnlyUsedInEncoding(int var) {
9145  if (context_->ModelIsUnsat()) return;
9146  if (context_->keep_all_feasible_solutions) return;
9147  if (context_->IsFixed(var)) return;
9148  if (context_->VariableWasRemoved(var)) return;
9149  if (context_->CanBeUsedAsLiteral(var)) return;
9150  if (!context_->VariableIsOnlyUsedInEncodingAndMaybeInObjective(var)) return;
9151  if (context_->params().search_branching() == SatParameters::FIXED_SEARCH) {
9152  return;
9153  }
9154 
9155  // If a variable var only appear in enf => var \in domain and in the
9156  // objective, we can remove its costs and the variable/constraint by
9157  // transferring part of the cost to the enforcement.
9158  //
9159  // More generally, we can reduce the domain to just two values. Later this
9160  // will be replaced by a Boolean, and the equivalence to the enforcement
9161  // literal will be added if it is unique.
9162  //
9163  // TODO(user): maybe we should do more here rather than delaying some
9164  // reduction. But then it is more code.
9165  if (context_->VariableWithCostIsUniqueAndRemovable(var)) {
9166  int unique_c = -1;
9167  for (const int c : context_->VarToConstraints(var)) {
9168  if (c < 0) continue;
9169  CHECK_EQ(unique_c, -1);
9170  unique_c = c;
9171  }
9172  CHECK_NE(unique_c, -1);
9173  const ConstraintProto& ct = context_->working_model->constraints(unique_c);
9174  const int64_t cost = context_->ObjectiveCoeff(var);
9175  if (ct.linear().vars(0) == var) {
9176  const Domain implied = ReadDomainFromProto(ct.linear())
9177  .InverseMultiplicationBy(ct.linear().coeffs(0))
9178  .IntersectionWith(context_->DomainOf(var));
9179  if (implied.IsEmpty()) {
9180  if (!MarkConstraintAsFalse(
9181  context_->working_model->mutable_constraints(unique_c))) {
9182  return;
9183  }
9184  context_->UpdateConstraintVariableUsage(unique_c);
9185  return;
9186  }
9187 
9188  int64_t value1, value2;
9189  if (cost == 0) {
9190  context_->UpdateRuleStats("variables: fix singleton var in linear1");
9191  return (void)context_->IntersectDomainWith(var, Domain(implied.Min()));
9192  } else if (cost > 0) {
9193  value1 = context_->MinOf(var);
9194  value2 = implied.Min();
9195  } else {
9196  value1 = context_->MaxOf(var);
9197  value2 = implied.Max();
9198  }
9199 
9200  // Nothing else to do in this case, the constraint will be reduced to
9201  // a pure Boolean constraint later.
9202  context_->UpdateRuleStats("variables: reduced domain to two values");
9203  return (void)context_->IntersectDomainWith(
9204  var, Domain::FromValues({value1, value2}));
9205  }
9206  }
9207 
9208  // We can currently only deal with the case where all encoding constraint
9209  // are of the form literal => var ==/!= value.
9210  // If they are more complex linear1 involved, we just abort.
9211  //
9212  // TODO(user): Also deal with the case all >= or <= where we can add a
9213  // serie of implication between all involved literals.
9214  absl::flat_hash_set<int64_t> values_set;
9215  absl::flat_hash_map<int64_t, std::vector<int>> value_to_equal_literals;
9216  absl::flat_hash_map<int64_t, std::vector<int>> value_to_not_equal_literals;
9217  bool abort = false;
9218  for (const int c : context_->VarToConstraints(var)) {
9219  if (c < 0) continue;
9220  const ConstraintProto& ct = context_->working_model->constraints(c);
9221  CHECK_EQ(ct.constraint_case(), ConstraintProto::kLinear);
9222  CHECK_EQ(ct.linear().vars().size(), 1);
9223  int64_t coeff = ct.linear().coeffs(0);
9224  if (std::abs(coeff) != 1 || ct.enforcement_literal().size() != 1) {
9225  abort = true;
9226  break;
9227  }
9228  if (!RefIsPositive(ct.linear().vars(0))) coeff *= 1;
9229  const int var = PositiveRef(ct.linear().vars(0));
9230  const Domain var_domain = context_->DomainOf(var);
9231  const Domain rhs = ReadDomainFromProto(ct.linear())
9232  .InverseMultiplicationBy(coeff)
9233  .IntersectionWith(var_domain);
9234  if (rhs.IsEmpty()) {
9235  if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
9236  return;
9237  }
9238  return;
9239  } else if (rhs.IsFixed()) {
9240  if (!var_domain.Contains(rhs.FixedValue())) {
9241  if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
9242  return;
9243  }
9244  } else {
9245  values_set.insert(rhs.FixedValue());
9246  value_to_equal_literals[rhs.FixedValue()].push_back(
9247  ct.enforcement_literal(0));
9248  }
9249  } else {
9250  const Domain complement = var_domain.IntersectionWith(rhs.Complement());
9251  if (complement.IsEmpty()) {
9252  // TODO(user): This should be dealt with elsewhere.
9253  abort = true;
9254  break;
9255  }
9256  if (complement.IsFixed()) {
9257  if (var_domain.Contains(complement.FixedValue())) {
9258  values_set.insert(complement.FixedValue());
9259  value_to_not_equal_literals[complement.FixedValue()].push_back(
9260  ct.enforcement_literal(0));
9261  }
9262  } else {
9263  abort = true;
9264  break;
9265  }
9266  }
9267  }
9268  if (abort) {
9269  context_->UpdateRuleStats("TODO variables: only used in linear1.");
9270  return;
9271  } else if (value_to_not_equal_literals.empty() &&
9272  value_to_equal_literals.empty()) {
9273  // This is just a variable not used anywhere, it should be removed by
9274  // another part of the presolve.
9275  return;
9276  }
9277 
9278  // For determinism, sort all the encoded values first.
9279  std::vector<int64_t> encoded_values(values_set.begin(), values_set.end());
9280  std::sort(encoded_values.begin(), encoded_values.end());
9281  CHECK(!encoded_values.empty());
9282  const bool is_fully_encoded =
9283  encoded_values.size() == context_->DomainOf(var).Size();
9284 
9285  // Link all Boolean in out linear1 to the encoding literals. Note that we
9286  // should hopefully already have detected such literal before and this
9287  // should add trivial implications.
9288  for (const int64_t v : encoded_values) {
9289  const int encoding_lit = context_->GetOrCreateVarValueEncoding(var, v);
9290  const auto eq_it = value_to_equal_literals.find(v);
9291  if (eq_it != value_to_equal_literals.end()) {
9292  for (const int lit : eq_it->second) {
9293  context_->AddImplication(lit, encoding_lit);
9294  }
9295  }
9296  const auto neq_it = value_to_not_equal_literals.find(v);
9297  if (neq_it != value_to_not_equal_literals.end()) {
9298  for (const int lit : neq_it->second) {
9299  context_->AddImplication(lit, NegatedRef(encoding_lit));
9300  }
9301  }
9302  }
9303  context_->UpdateNewConstraintsVariableUsage();
9304 
9305  // This is the set of other values.
9306  Domain other_values;
9307  if (!is_fully_encoded) {
9308  other_values = context_->DomainOf(var).IntersectionWith(
9309  Domain::FromValues(encoded_values).Complement());
9310  }
9311 
9312  // Update the objective if needed. Note that this operation can fail if
9313  // the new expression result in potential overflow.
9314  if (context_->VarToConstraints(var).contains(kObjectiveConstraint)) {
9315  int64_t min_value;
9316  const int64_t obj_coeff = context_->ObjectiveMap().at(var);
9317  if (is_fully_encoded) {
9318  // We substract the min_value from all coefficients.
9319  // This should reduce the objective size and helps with the bounds.
9320  min_value =
9321  obj_coeff > 0 ? encoded_values.front() : encoded_values.back();
9322  } else {
9323  // Tricky: We cannot just choose an arbitrary value if the objective has
9324  // a restrictive domain!
9325  if (context_->ObjectiveDomainIsConstraining() &&
9326  !other_values.IsFixed()) {
9327  return;
9328  }
9329 
9330  // Tricky: If the variable is not fully encoded, then when all
9331  // partial encoding literal are false, it must take the "best" value
9332  // in other_values. That depend on the sign of the objective coeff.
9333  //
9334  // We also restrict other value so that the postsolve code below
9335  // will fix the variable to the correct value when this happen.
9336  other_values =
9337  Domain(obj_coeff > 0 ? other_values.Min() : other_values.Max());
9338  min_value = other_values.FixedValue();
9339  }
9340 
9341  // Checks for overflow before trying to substitute the variable in the
9342  // objective.
9343  int64_t accumulated = std::abs(min_value);
9344  for (const int64_t value : encoded_values) {
9345  accumulated = CapAdd(accumulated, std::abs(CapSub(value, min_value)));
9346  if (accumulated == std::numeric_limits<int64_t>::max()) {
9347  context_->UpdateRuleStats(
9348  "TODO variables: only used in objective and in encoding");
9349  return;
9350  }
9351  }
9352 
9353  ConstraintProto encoding_ct;
9354  LinearConstraintProto* linear = encoding_ct.mutable_linear();
9355  const int64_t coeff_in_equality = -1;
9356  linear->add_vars(var);
9357  linear->add_coeffs(coeff_in_equality);
9358 
9359  linear->add_domain(-min_value);
9360  linear->add_domain(-min_value);
9361  for (const int64_t value : encoded_values) {
9362  if (value == min_value) continue;
9363  const int enf = context_->GetOrCreateVarValueEncoding(var, value);
9364  const int64_t coeff = value - min_value;
9365  if (RefIsPositive(enf)) {
9366  linear->add_vars(enf);
9367  linear->add_coeffs(coeff);
9368  } else {
9369  // (1 - var) * coeff;
9370  linear->set_domain(0, encoding_ct.linear().domain(0) - coeff);
9371  linear->set_domain(1, encoding_ct.linear().domain(1) - coeff);
9372  linear->add_vars(PositiveRef(enf));
9373  linear->add_coeffs(-coeff);
9374  }
9375  }
9376  if (!context_->SubstituteVariableInObjective(var, coeff_in_equality,
9377  encoding_ct)) {
9378  context_->UpdateRuleStats(
9379  "TODO variables: only used in objective and in encoding");
9380  return;
9381  }
9382  context_->UpdateRuleStats(
9383  "variables: only used in objective and in encoding");
9384  } else {
9385  context_->UpdateRuleStats("variables: only used in encoding");
9386  }
9387 
9388  // Clear all involved constraint.
9389  auto copy = context_->VarToConstraints(var);
9390  for (const int c : copy) {
9391  if (c < 0) continue;
9392  context_->working_model->mutable_constraints(c)->Clear();
9393  context_->UpdateConstraintVariableUsage(c);
9394  }
9395 
9396  // Add enough constraints to the mapping model to recover a valid value
9397  // for var when all the booleans are fixed.
9398  for (const int64_t value : encoded_values) {
9399  const int enf = context_->GetOrCreateVarValueEncoding(var, value);
9400  ConstraintProto* ct = context_->mapping_model->add_constraints();
9401  ct->add_enforcement_literal(enf);
9402  ct->mutable_linear()->add_vars(var);
9403  ct->mutable_linear()->add_coeffs(1);
9404  ct->mutable_linear()->add_domain(value);
9405  ct->mutable_linear()->add_domain(value);
9406  }
9407 
9408  // This must be done after we removed all the constraint containing var.
9409  ConstraintProto* new_ct = context_->working_model->add_constraints();
9410  if (is_fully_encoded) {
9411  // The encoding is full: add an exactly one.
9412  for (const int64_t value : encoded_values) {
9413  new_ct->mutable_exactly_one()->add_literals(
9414  context_->GetOrCreateVarValueEncoding(var, value));
9415  }
9416  PresolveExactlyOne(new_ct);
9417  } else {
9418  // If all literal are false, then var must take one of the other values.
9419  ConstraintProto* mapping_ct = context_->mapping_model->add_constraints();
9420  mapping_ct->mutable_linear()->add_vars(var);
9421  mapping_ct->mutable_linear()->add_coeffs(1);
9422  FillDomainInProto(other_values, mapping_ct->mutable_linear());
9423 
9424  for (const int64_t value : encoded_values) {
9425  const int literal = context_->GetOrCreateVarValueEncoding(var, value);
9426  mapping_ct->add_enforcement_literal(NegatedRef(literal));
9427  new_ct->mutable_at_most_one()->add_literals(literal);
9428  }
9429  PresolveAtMostOne(new_ct);
9430  }
9431 
9432  context_->UpdateNewConstraintsVariableUsage();
9433  context_->MarkVariableAsRemoved(var);
9434 }
9435 
9436 void CpModelPresolver::TryToSimplifyDomain(int var) {
9437  CHECK(RefIsPositive(var));
9438  CHECK(context_->ConstraintVariableGraphIsUpToDate());
9439  if (context_->ModelIsUnsat()) return;
9440  if (context_->IsFixed(var)) return;
9441  if (context_->VariableWasRemoved(var)) return;
9442  if (context_->VariableIsNotUsedAnymore(var)) return;
9443 
9444  const AffineRelation::Relation r = context_->GetAffineRelation(var);
9445  if (r.representative != var) return;
9446 
9447  // Only process discrete domain.
9448  const Domain& domain = context_->DomainOf(var);
9449 
9450  // Special case for non-Boolean domain of size 2.
9451  if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
9452  context_->CanonicalizeDomainOfSizeTwo(var);
9453  return;
9454  }
9455 
9456  if (domain.NumIntervals() != domain.Size()) return;
9457 
9458  const int64_t var_min = domain.Min();
9459  int64_t gcd = domain[1].start - var_min;
9460  for (int index = 2; index < domain.NumIntervals(); ++index) {
9461  const ClosedInterval& i = domain[index];
9462  DCHECK_EQ(i.start, i.end);
9463  const int64_t shifted_value = i.start - var_min;
9464  DCHECK_GT(shifted_value, 0);
9465 
9466  gcd = MathUtil::GCD64(gcd, shifted_value);
9467  if (gcd == 1) break;
9468  }
9469  if (gcd == 1) return;
9470 
9471  // This does all the work since var * 1 % gcd = var_min % gcd.
9472  context_->CanonicalizeAffineVariable(var, 1, gcd, var_min);
9473 }
9474 
9475 // Adds all affine relations to our model for the variables that are still used.
9476 void CpModelPresolver::EncodeAllAffineRelations() {
9477  int64_t num_added = 0;
9478  for (int var = 0; var < context_->working_model->variables_size(); ++var) {
9479  if (context_->IsFixed(var)) continue;
9480 
9481  const AffineRelation::Relation r = context_->GetAffineRelation(var);
9482  if (r.representative == var) continue;
9483 
9484  if (!context_->keep_all_feasible_solutions) {
9485  // TODO(user): It seems some affine relation are still removable at this
9486  // stage even though they should be removed inside PresolveToFixPoint().
9487  // Investigate. For now, we just remove such relations.
9488  if (context_->VariableIsNotUsedAnymore(var)) continue;
9489  if (!PresolveAffineRelationIfAny(var)) break;
9490  if (context_->VariableIsNotUsedAnymore(var)) continue;
9491  if (context_->IsFixed(var)) continue;
9492  }
9493 
9494  ++num_added;
9495  ConstraintProto* ct = context_->working_model->add_constraints();
9496  auto* arg = ct->mutable_linear();
9497  arg->add_vars(var);
9498  arg->add_coeffs(1);
9499  arg->add_vars(r.representative);
9500  arg->add_coeffs(-r.coeff);
9501  arg->add_domain(r.offset);
9502  arg->add_domain(r.offset);
9503  context_->UpdateNewConstraintsVariableUsage();
9504  }
9505 
9506  // Now that we encoded all remaining affine relation with constraints, we
9507  // remove the special marker to have a proper constraint variable graph.
9508  context_->RemoveAllVariablesFromAffineRelationConstraint();
9509 
9510  if (num_added > 0) {
9511  SOLVER_LOG(logger_, num_added, " affine relations still in the model.");
9512  }
9513 }
9514 
9515 // Presolve a variable in relation with its representative.
9516 bool CpModelPresolver::PresolveAffineRelationIfAny(int var) {
9517  const AffineRelation::Relation r = context_->GetAffineRelation(var);
9518  if (r.representative == var) return true;
9519 
9520  // Propagate domains.
9521  if (!context_->PropagateAffineRelation(var)) return false;
9522 
9523  // Once an affine relation is detected, the variables should be added to
9524  // the kAffineRelationConstraint. The only way to be unmarked is if the
9525  // variable do not appear in any other constraint and is not a representative,
9526  // in which case it should never be added back.
9527  if (context_->IsFixed(var)) return true;
9528  DCHECK(context_->VarToConstraints(var).contains(kAffineRelationConstraint));
9529  DCHECK(!context_->VariableIsNotUsedAnymore(r.representative));
9530 
9531  // If var is no longer used, remove. Note that we can always do that since we
9532  // propagated the domain above and so we can find a feasible value for a for
9533  // any value of the representative.
9534  if (context_->VariableIsUniqueAndRemovable(var)) {
9535  // Add relation with current representative to the mapping model.
9536  ConstraintProto* ct = context_->mapping_model->add_constraints();
9537  auto* arg = ct->mutable_linear();
9538  arg->add_vars(var);
9539  arg->add_coeffs(1);
9540  arg->add_vars(r.representative);
9541  arg->add_coeffs(-r.coeff);
9542  arg->add_domain(r.offset);
9543  arg->add_domain(r.offset);
9544  context_->RemoveVariableFromAffineRelation(var);
9545  }
9546  return true;
9547 }
9548 
9549 void CpModelPresolver::PresolveToFixPoint() {
9550  if (context_->ModelIsUnsat()) return;
9551 
9552  // Limit on number of operations.
9553  const int64_t max_num_operations =
9554  context_->params().debug_max_num_presolve_operations() > 0
9555  ? context_->params().debug_max_num_presolve_operations()
9557 
9558  // This is used for constraint having unique variables in them (i.e. not
9559  // appearing anywhere else) to not call the presolve more than once for this
9560  // reason.
9561  absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
9562 
9563  TimeLimit* time_limit = context_->time_limit();
9564 
9565  // The queue of "active" constraints, initialized to the non-empty ones.
9566  std::vector<bool> in_queue(context_->working_model->constraints_size(),
9567  false);
9568  std::deque<int> queue;
9569  for (int c = 0; c < in_queue.size(); ++c) {
9570  if (context_->working_model->constraints(c).constraint_case() !=
9571  ConstraintProto::CONSTRAINT_NOT_SET) {
9572  in_queue[c] = true;
9573  queue.push_back(c);
9574  }
9575  }
9576 
9577  // When thinking about how the presolve works, it seems like a good idea to
9578  // process the "simple" constraints first in order to be more efficient.
9579  // In September 2019, experiment on the flatzinc problems shows no changes in
9580  // the results. We should actually count the number of rules triggered.
9581  if (context_->params().permute_presolve_constraint_order()) {
9582  std::shuffle(queue.begin(), queue.end(), *context_->random());
9583  } else {
9584  std::sort(queue.begin(), queue.end(), [this](int a, int b) {
9585  const int score_a = context_->ConstraintToVars(a).size();
9586  const int score_b = context_->ConstraintToVars(b).size();
9587  return score_a < score_b || (score_a == score_b && a < b);
9588  });
9589  }
9590 
9591  // We put a hard limit on the number of loop to prevent some corner case with
9592  // propagation loops. Note that the limit is quite high so it shouldn't really
9593  // be reached in most situation.
9594  constexpr int kMaxNumLoops = 1000;
9595  for (int i = 0;
9596  i < kMaxNumLoops && !queue.empty() && !context_->ModelIsUnsat(); ++i) {
9597  if (time_limit->LimitReached()) break;
9598  if (context_->num_presolve_operations > max_num_operations) break;
9599  while (!queue.empty() && !context_->ModelIsUnsat()) {
9600  if (time_limit->LimitReached()) break;
9601  if (context_->num_presolve_operations > max_num_operations) break;
9602  const int c = queue.front();
9603  in_queue[c] = false;
9604  queue.pop_front();
9605 
9606  const int old_num_constraint =
9607  context_->working_model->constraints_size();
9608  const bool changed = PresolveOneConstraint(c);
9609  if (context_->ModelIsUnsat()) {
9610  SOLVER_LOG(logger_, "Unsat after presolving constraint #", c,
9611  " (warning, dump might be inconsistent): ",
9612  context_->working_model->constraints(c).ShortDebugString());
9613  }
9614 
9615  // Add to the queue any newly created constraints.
9616  const int new_num_constraints =
9617  context_->working_model->constraints_size();
9618  if (new_num_constraints > old_num_constraint) {
9619  context_->UpdateNewConstraintsVariableUsage();
9620  in_queue.resize(new_num_constraints, true);
9621  for (int c = old_num_constraint; c < new_num_constraints; ++c) {
9622  queue.push_back(c);
9623  }
9624  }
9625 
9626  // TODO(user): Is seems safer to simply remove the changed Boolean.
9627  // We loose a bit of performance, but the code is simpler.
9628  if (changed) {
9629  context_->UpdateConstraintVariableUsage(c);
9630  }
9631  }
9632 
9633  if (context_->ModelIsUnsat()) return;
9634 
9635  in_queue.resize(context_->working_model->constraints_size(), false);
9636  const auto& vector_that_can_grow_during_iter =
9637  context_->var_with_reduced_small_degree.PositionsSetAtLeastOnce();
9638  for (int i = 0; i < vector_that_can_grow_during_iter.size(); ++i) {
9639  const int v = vector_that_can_grow_during_iter[i];
9640  if (context_->VariableIsNotUsedAnymore(v)) continue;
9641 
9642  // Make sure all affine relations are propagated.
9643  // This also remove the relation if the degree is now one.
9644  if (!PresolveAffineRelationIfAny(v)) return;
9645 
9646  const int degree = context_->VarToConstraints(v).size();
9647  if (degree == 0) continue;
9648  if (degree == 2) LookAtVariableWithDegreeTwo(v);
9649  if (degree == 2 || degree == 3) {
9650  // Tricky: this function can add new constraint.
9651  ProcessVariableInTwoAtMostOrExactlyOne(v);
9652  in_queue.resize(context_->working_model->constraints_size(), false);
9653  continue;
9654  }
9655 
9656  // Re-add to the queue constraints that have unique variables. Note that
9657  // to not enter an infinite loop, we call each (var, constraint) pair at
9658  // most once.
9659  if (degree != 1) continue;
9660  const int c = *context_->VarToConstraints(v).begin();
9661  if (c < 0) continue;
9662 
9663  // Note that to avoid bad complexity in problem like a TSP with just one
9664  // big constraint. we mark all the singleton variables of a constraint
9665  // even if this constraint is already in the queue.
9666  if (var_constraint_pair_already_called.contains(
9667  std::pair<int, int>(v, c))) {
9668  continue;
9669  }
9670  var_constraint_pair_already_called.insert({v, c});
9671 
9672  if (!in_queue[c]) {
9673  in_queue[c] = true;
9674  queue.push_back(c);
9675  }
9676  }
9677  context_->var_with_reduced_small_degree.SparseClearAll();
9678 
9679  for (int i = 0; i < 2; ++i) {
9680  // Re-add to the queue the constraints that touch a variable that changed.
9681  //
9682  // TODO(user): Avoid reprocessing the constraints that changed the domain?
9683  if (context_->ModelIsUnsat()) return;
9684  if (time_limit->LimitReached()) break;
9685  in_queue.resize(context_->working_model->constraints_size(), false);
9686  const auto& vector_that_can_grow_during_iter =
9687  context_->modified_domains.PositionsSetAtLeastOnce();
9688  for (int i = 0; i < vector_that_can_grow_during_iter.size(); ++i) {
9689  const int v = vector_that_can_grow_during_iter[i];
9690  if (context_->VariableIsNotUsedAnymore(v)) continue;
9691  if (!PresolveAffineRelationIfAny(v)) return;
9692  if (context_->VariableIsNotUsedAnymore(v)) continue;
9693 
9694  TryToSimplifyDomain(v);
9695 
9696  // TODO(user): Integrate these with TryToSimplifyDomain().
9697  if (context_->ModelIsUnsat()) return;
9698  context_->UpdateNewConstraintsVariableUsage();
9699 
9700  if (!context_->CanonicalizeOneObjectiveVariable(v)) return;
9701 
9702  in_queue.resize(context_->working_model->constraints_size(), false);
9703  for (const int c : context_->VarToConstraints(v)) {
9704  if (c >= 0 && !in_queue[c]) {
9705  in_queue[c] = true;
9706  queue.push_back(c);
9707  }
9708  }
9709  }
9710  context_->modified_domains.SparseClearAll();
9711 
9712  // If we reach the end of the loop, do more costly presolve.
9713  if (!queue.empty() || i == 1) break;
9714 
9715  // Deal with integer variable only appearing in an encoding.
9716  for (int v = 0; v < context_->working_model->variables().size(); ++v) {
9717  ProcessVariableOnlyUsedInEncoding(v);
9718  }
9719 
9720  // Detect & exploit dominance between variables, or variables that can
9721  // move freely in one direction. Or variables that are just blocked by one
9722  // constraint in one direction.
9723  //
9724  // TODO(user): We can support assumptions but we need to not cut them out
9725  // of the feasible region.
9726  if (!context_->keep_all_feasible_solutions &&
9727  context_->working_model->assumptions().empty()) {
9728  VarDomination var_dom;
9729  DualBoundStrengthening dual_bound_strengthening;
9730  DetectDominanceRelations(*context_, &var_dom,
9731  &dual_bound_strengthening);
9732  if (!dual_bound_strengthening.Strengthen(context_)) return;
9733  if (dual_bound_strengthening.NumDeletedConstraints() > 0) {
9734  // Loop again.
9735  // TODO(user): Optimize the code to reach a fix point faster?
9736  i = -1;
9737  continue;
9738  }
9739 
9740  // TODO(user): The Strengthen() function above might make some
9741  // inequality tight. Currently, because we only do that for implication,
9742  // this will not change who dominate who, but in general we should
9743  // process the new constraint direction before calling this.
9744  if (!ExploitDominanceRelations(var_dom, context_)) return;
9745  }
9746  }
9747 
9748  // Make sure the order is deterministic! because var_to_constraints[]
9749  // order changes from one run to the next.
9750  std::sort(queue.begin(), queue.end());
9751  }
9752 
9753  if (context_->ModelIsUnsat()) return;
9754 
9755  // Second "pass" for transformation better done after all of the above and
9756  // that do not need a fix-point loop.
9757  //
9758  // TODO(user): Also add deductions achieved during probing!
9759  //
9760  // TODO(user): ideally we should "wake-up" any constraint that contains an
9761  // absent interval in the main propagation loop above. But we currently don't
9762  // maintain such list.
9763  const int num_constraints = context_->working_model->constraints_size();
9764  for (int c = 0; c < num_constraints; ++c) {
9765  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9766  switch (ct->constraint_case()) {
9767  case ConstraintProto::kNoOverlap:
9768  // Filter out absent intervals.
9769  if (PresolveNoOverlap(ct)) {
9770  context_->UpdateConstraintVariableUsage(c);
9771  }
9772  break;
9773  case ConstraintProto::kNoOverlap2D:
9774  // Filter out absent intervals.
9775  if (PresolveNoOverlap2D(c, ct)) {
9776  context_->UpdateConstraintVariableUsage(c);
9777  }
9778  break;
9779  case ConstraintProto::kCumulative:
9780  // Filter out absent intervals.
9781  if (PresolveCumulative(ct)) {
9782  context_->UpdateConstraintVariableUsage(c);
9783  }
9784  break;
9785  case ConstraintProto::kBoolOr: {
9786  // Try to infer domain reductions from clauses and the saved "implies in
9787  // domain" relations.
9788  for (const auto& pair :
9789  context_->deductions.ProcessClause(ct->bool_or().literals())) {
9790  bool modified = false;
9791  if (!context_->IntersectDomainWith(pair.first, pair.second,
9792  &modified)) {
9793  return;
9794  }
9795  if (modified) {
9796  context_->UpdateRuleStats("deductions: reduced variable domain");
9797  }
9798  }
9799  break;
9800  }
9801  default:
9802  break;
9803  }
9804  }
9805 
9806  context_->deductions.MarkProcessingAsDoneForNow();
9807 }
9808 
9809 ModelCopy::ModelCopy(PresolveContext* context) : context_(context) {}
9810 
9812  const CpModelProto& in_model) {
9813  if (context_->params().ignore_names()) {
9814  context_->working_model->clear_variables();
9815  context_->working_model->mutable_variables()->Reserve(
9816  in_model.variables_size());
9817  for (const IntegerVariableProto& var_proto : in_model.variables()) {
9818  *context_->working_model->add_variables()->mutable_domain() =
9819  var_proto.domain();
9820  }
9821  } else {
9822  *context_->working_model->mutable_variables() = in_model.variables();
9823  }
9824 }
9825 
9826 // TODO(user): Merge with the phase 1 of the presolve code.
9827 //
9828 // TODO(user): It seems easy to forget to update this if any new constraint
9829 // contains an interval or if we add a field to an existing constraint. Find a
9830 // way to remind contributor to not forget this.
9832  const CpModelProto& in_model, const std::vector<int>& ignored_constraints,
9833  bool first_copy) {
9834  const absl::flat_hash_set<int> ignored_constraints_set(
9835  ignored_constraints.begin(), ignored_constraints.end());
9836  context_->InitializeNewDomains();
9837  const bool ignore_names = context_->params().ignore_names();
9838 
9839  // If first_copy is true, we reorder the scheduling constraint to be sure they
9840  // refer to interval before them.
9841  std::vector<int> constraints_using_intervals;
9842 
9843  starting_constraint_index_ = context_->working_model->constraints_size();
9844  for (int c = 0; c < in_model.constraints_size(); ++c) {
9845  if (ignored_constraints_set.contains(c)) continue;
9846 
9847  const ConstraintProto& ct = in_model.constraints(c);
9848  if (OneEnforcementLiteralIsFalse(ct)) continue;
9849 
9850  // TODO(user): if ignore_names is false, we should make sure the
9851  // name are properly copied by all these functions. Or we should never copy
9852  // name and have a separate if (!ignore_name) copy the name...
9853  switch (ct.constraint_case()) {
9854  case ConstraintProto::CONSTRAINT_NOT_SET:
9855  break;
9856  case ConstraintProto::kBoolOr:
9857  if (first_copy) {
9858  if (!CopyBoolOrWithDupSupport(ct)) return CreateUnsatModel();
9859  } else {
9860  if (!CopyBoolOr(ct)) return CreateUnsatModel();
9861  }
9862  break;
9863  case ConstraintProto::kBoolAnd:
9864  if (!CopyBoolAnd(ct)) return CreateUnsatModel();
9865  break;
9866  case ConstraintProto::kLinear:
9867  if (!CopyLinear(ct)) return CreateUnsatModel();
9868  break;
9869  case ConstraintProto::kAtMostOne:
9870  if (!CopyAtMostOne(ct)) return CreateUnsatModel();
9871  break;
9872  case ConstraintProto::kExactlyOne:
9873  if (!CopyExactlyOne(ct)) return CreateUnsatModel();
9874  break;
9875  case ConstraintProto::kInterval:
9876  if (!CopyInterval(ct, c, ignore_names)) return CreateUnsatModel();
9877  break;
9878  case ConstraintProto::kNoOverlap:
9879  if (first_copy) {
9880  constraints_using_intervals.push_back(c);
9881  } else {
9882  CopyAndMapNoOverlap(ct);
9883  }
9884  break;
9885  case ConstraintProto::kNoOverlap2D:
9886  if (first_copy) {
9887  constraints_using_intervals.push_back(c);
9888  } else {
9889  CopyAndMapNoOverlap2D(ct);
9890  }
9891  break;
9892  case ConstraintProto::kCumulative:
9893  if (first_copy) {
9894  constraints_using_intervals.push_back(c);
9895  } else {
9896  CopyAndMapCumulative(ct);
9897  }
9898  break;
9899  default: {
9900  ConstraintProto* new_ct = context_->working_model->add_constraints();
9901  *new_ct = ct;
9902  if (ignore_names) {
9903  // TODO(user): find a better way than copy then clear_name()?
9904  new_ct->clear_name();
9905  }
9906  }
9907  }
9908  }
9909 
9910  // This should be empty if first_copy is false.
9911  DCHECK(first_copy || constraints_using_intervals.empty());
9912  for (const int c : constraints_using_intervals) {
9913  const ConstraintProto& ct = in_model.constraints(c);
9914  switch (ct.constraint_case()) {
9915  case ConstraintProto::kNoOverlap:
9916  CopyAndMapNoOverlap(ct);
9917  break;
9918  case ConstraintProto::kNoOverlap2D:
9919  CopyAndMapNoOverlap2D(ct);
9920  break;
9921  case ConstraintProto::kCumulative:
9922  CopyAndMapCumulative(ct);
9923  break;
9924  default:
9925  LOG(DFATAL) << "Shouldn't be here.";
9926  }
9927  }
9928 
9929  return true;
9930 }
9931 
9932 void ModelCopy::CopyEnforcementLiterals(const ConstraintProto& orig,
9933  ConstraintProto* dest) {
9934  temp_enforcement_literals_.clear();
9935  for (const int lit : orig.enforcement_literal()) {
9936  if (context_->LiteralIsTrue(lit)) {
9937  skipped_non_zero_++;
9938  continue;
9939  }
9940  temp_enforcement_literals_.push_back(lit);
9941  }
9942  dest->mutable_enforcement_literal()->Add(temp_enforcement_literals_.begin(),
9943  temp_enforcement_literals_.end());
9944 }
9945 
9946 bool ModelCopy::OneEnforcementLiteralIsFalse(const ConstraintProto& ct) const {
9947  for (const int lit : ct.enforcement_literal()) {
9948  if (context_->LiteralIsFalse(lit)) {
9949  return true;
9950  }
9951  }
9952  return false;
9953 }
9954 
9955 bool ModelCopy::CopyBoolOr(const ConstraintProto& ct) {
9956  temp_literals_.clear();
9957  for (const int lit : ct.enforcement_literal()) {
9958  if (context_->LiteralIsTrue(lit)) continue;
9959  temp_literals_.push_back(NegatedRef(lit));
9960  }
9961  for (const int lit : ct.bool_or().literals()) {
9962  if (context_->LiteralIsTrue(lit)) {
9963  return true;
9964  }
9965  if (context_->LiteralIsFalse(lit)) {
9966  skipped_non_zero_++;
9967  } else {
9968  temp_literals_.push_back(lit);
9969  }
9970  }
9971 
9972  context_->working_model->add_constraints()
9973  ->mutable_bool_or()
9974  ->mutable_literals()
9975  ->Add(temp_literals_.begin(), temp_literals_.end());
9976  return !temp_literals_.empty();
9977 }
9978 
9979 bool ModelCopy::CopyBoolOrWithDupSupport(const ConstraintProto& ct) {
9980  temp_literals_.clear();
9981  tmp_literals_set_.clear();
9982  for (const int enforcement_lit : ct.enforcement_literal()) {
9983  // Having an enforcement literal is the same as having its negation on
9984  // the clause.
9985  const int lit = NegatedRef(enforcement_lit);
9986 
9987  // This code is a duplicate of the code below.
9988  if (context_->LiteralIsTrue(lit)) {
9989  context_->UpdateRuleStats("bool_or: always true");
9990  return true;
9991  }
9992  if (context_->LiteralIsFalse(lit)) {
9993  skipped_non_zero_++;
9994  continue;
9995  }
9996  if (tmp_literals_set_.contains(NegatedRef(lit))) {
9997  context_->UpdateRuleStats("bool_or: always true");
9998  return true;
9999  }
10000  const auto [it, inserted] = tmp_literals_set_.insert(lit);
10001  if (inserted) temp_literals_.push_back(lit);
10002  }
10003  for (const int lit : ct.bool_or().literals()) {
10004  if (context_->LiteralIsTrue(lit)) {
10005  context_->UpdateRuleStats("bool_or: always true");
10006  return true;
10007  }
10008  if (context_->LiteralIsFalse(lit)) {
10009  skipped_non_zero_++;
10010  continue;
10011  }
10012  if (tmp_literals_set_.contains(NegatedRef(lit))) {
10013  context_->UpdateRuleStats("bool_or: always true");
10014  return true;
10015  }
10016  const auto [it, inserted] = tmp_literals_set_.insert(lit);
10017  if (inserted) temp_literals_.push_back(lit);
10018  }
10019 
10020  context_->working_model->add_constraints()
10021  ->mutable_bool_or()
10022  ->mutable_literals()
10023  ->Add(temp_literals_.begin(), temp_literals_.end());
10024  return !temp_literals_.empty();
10025 }
10026 
10027 bool ModelCopy::CopyBoolAnd(const ConstraintProto& ct) {
10028  bool at_least_one_false = false;
10029  int num_non_fixed_literals = 0;
10030  for (const int lit : ct.bool_and().literals()) {
10031  if (context_->LiteralIsFalse(lit)) {
10032  at_least_one_false = true;
10033  break;
10034  }
10035  if (!context_->LiteralIsTrue(lit)) {
10036  num_non_fixed_literals++;
10037  }
10038  }
10039 
10040  if (at_least_one_false) {
10041  ConstraintProto* new_ct = context_->working_model->add_constraints();
10042  BoolArgumentProto* bool_or = new_ct->mutable_bool_or();
10043 
10044  // One enforcement literal must be false.
10045  for (const int lit : ct.enforcement_literal()) {
10046  if (context_->LiteralIsTrue(lit)) {
10047  skipped_non_zero_++;
10048  continue;
10049  }
10050  bool_or->add_literals(NegatedRef(lit));
10051  }
10052  return !bool_or->literals().empty();
10053  } else if (num_non_fixed_literals > 0) {
10054  ConstraintProto* new_ct = context_->working_model->add_constraints();
10055  CopyEnforcementLiterals(ct, new_ct);
10056  BoolArgumentProto* bool_and = new_ct->mutable_bool_and();
10057  bool_and->mutable_literals()->Reserve(num_non_fixed_literals);
10058  for (const int lit : ct.bool_and().literals()) {
10059  if (context_->LiteralIsTrue(lit)) {
10060  skipped_non_zero_++;
10061  continue;
10062  }
10063  bool_and->add_literals(lit);
10064  }
10065  }
10066  return true;
10067 }
10068 
10069 bool ModelCopy::CopyLinear(const ConstraintProto& ct) {
10070  non_fixed_variables_.clear();
10071  non_fixed_coefficients_.clear();
10072  int64_t offset = 0;
10073  int64_t min_activity = 0;
10074  int64_t max_activity = 0;
10075  for (int i = 0; i < ct.linear().vars_size(); ++i) {
10076  const int ref = ct.linear().vars(i);
10077  const int64_t coeff = ct.linear().coeffs(i);
10078  if (coeff == 0) continue;
10079  if (context_->IsFixed(ref)) {
10080  offset += coeff * context_->MinOf(ref);
10081  skipped_non_zero_++;
10082  continue;
10083  }
10084 
10085  if (coeff > 0) {
10086  min_activity += coeff * context_->MinOf(ref);
10087  max_activity += coeff * context_->MaxOf(ref);
10088  } else {
10089  min_activity += coeff * context_->MaxOf(ref);
10090  max_activity += coeff * context_->MinOf(ref);
10091  }
10092 
10093  // Make sure we never have negative ref in a linear constraint.
10094  if (RefIsPositive(ref)) {
10095  non_fixed_variables_.push_back(ref);
10096  non_fixed_coefficients_.push_back(coeff);
10097  } else {
10098  non_fixed_variables_.push_back(NegatedRef(ref));
10099  non_fixed_coefficients_.push_back(-coeff);
10100  }
10101  }
10102 
10103  const Domain implied(min_activity, max_activity);
10104  const Domain new_rhs =
10105  ReadDomainFromProto(ct.linear()).AdditionWith(Domain(-offset));
10106 
10107  // Trivial constraint?
10108  if (implied.IsIncludedIn(new_rhs)) return true;
10109 
10110  // Constraint is false?
10111  if (implied.IntersectionWith(new_rhs).IsEmpty()) {
10112  if (ct.enforcement_literal().empty()) return false;
10113  temp_literals_.clear();
10114  for (const int literal : ct.enforcement_literal()) {
10115  if (context_->LiteralIsTrue(literal)) {
10116  skipped_non_zero_++;
10117  } else {
10118  temp_literals_.push_back(NegatedRef(literal));
10119  }
10120  }
10121  context_->working_model->add_constraints()
10122  ->mutable_bool_or()
10123  ->mutable_literals()
10124  ->Add(temp_literals_.begin(), temp_literals_.end());
10125  return !temp_literals_.empty();
10126  }
10127 
10128  ConstraintProto* new_ct = context_->working_model->add_constraints();
10129  CopyEnforcementLiterals(ct, new_ct);
10130  LinearConstraintProto* linear = new_ct->mutable_linear();
10131  linear->mutable_vars()->Add(non_fixed_variables_.begin(),
10132  non_fixed_variables_.end());
10133  linear->mutable_coeffs()->Add(non_fixed_coefficients_.begin(),
10134  non_fixed_coefficients_.end());
10135  FillDomainInProto(new_rhs, linear);
10136  return true;
10137 }
10138 
10139 bool ModelCopy::CopyAtMostOne(const ConstraintProto& ct) {
10140  int num_true = 0;
10141  temp_literals_.clear();
10142  for (const int lit : ct.at_most_one().literals()) {
10143  if (context_->LiteralIsFalse(lit)) {
10144  skipped_non_zero_++;
10145  continue;
10146  }
10147  temp_literals_.push_back(lit);
10148  if (context_->LiteralIsTrue(lit)) num_true++;
10149  }
10150 
10151  if (temp_literals_.size() <= 1) return true;
10152  if (num_true > 1) return false;
10153 
10154  // TODO(user): presolve if num_true == 1.
10155  ConstraintProto* new_ct = context_->working_model->add_constraints();
10156  CopyEnforcementLiterals(ct, new_ct);
10157  new_ct->mutable_at_most_one()->mutable_literals()->Add(temp_literals_.begin(),
10158  temp_literals_.end());
10159  return true;
10160 }
10161 
10162 bool ModelCopy::CopyExactlyOne(const ConstraintProto& ct) {
10163  int num_true = 0;
10164  temp_literals_.clear();
10165  for (const int lit : ct.exactly_one().literals()) {
10166  if (context_->LiteralIsFalse(lit)) {
10167  skipped_non_zero_++;
10168  continue;
10169  }
10170  temp_literals_.push_back(lit);
10171  if (context_->LiteralIsTrue(lit)) num_true++;
10172  }
10173 
10174  if (temp_literals_.empty() || num_true > 1) return false;
10175  if (temp_literals_.size() == 1 && num_true == 1) return true;
10176 
10177  // TODO(user): presolve if num_true == 1 and not everything is false.
10178  ConstraintProto* new_ct = context_->working_model->add_constraints();
10179  CopyEnforcementLiterals(ct, new_ct);
10180  new_ct->mutable_exactly_one()->mutable_literals()->Add(temp_literals_.begin(),
10181  temp_literals_.end());
10182  return true;
10183 }
10184 
10185 bool ModelCopy::CopyInterval(const ConstraintProto& ct, int c,
10186  bool ignore_names) {
10187  CHECK_EQ(starting_constraint_index_, 0)
10188  << "Adding new interval constraints to partially filled model is not "
10189  "supported.";
10190  interval_mapping_[c] = context_->working_model->constraints_size();
10191  ConstraintProto* new_ct = context_->working_model->add_constraints();
10192  if (ignore_names) {
10193  *new_ct->mutable_enforcement_literal() = ct.enforcement_literal();
10194  *new_ct->mutable_interval()->mutable_start() = ct.interval().start();
10195  *new_ct->mutable_interval()->mutable_size() = ct.interval().size();
10196  *new_ct->mutable_interval()->mutable_end() = ct.interval().end();
10197  } else {
10198  *new_ct = ct;
10199  }
10200 
10201  return true;
10202 }
10203 
10204 void ModelCopy::CopyAndMapNoOverlap(const ConstraintProto& ct) {
10205  // Note that we don't copy names or enforcement_literal (not supported) here.
10206  auto* new_ct =
10207  context_->working_model->add_constraints()->mutable_no_overlap();
10208  new_ct->mutable_intervals()->Reserve(ct.no_overlap().intervals().size());
10209  for (const int index : ct.no_overlap().intervals()) {
10210  const auto it = interval_mapping_.find(index);
10211  if (it == interval_mapping_.end()) continue;
10212  new_ct->add_intervals(it->second);
10213  }
10214 }
10215 
10216 void ModelCopy::CopyAndMapNoOverlap2D(const ConstraintProto& ct) {
10217  // Note that we don't copy names or enforcement_literal (not supported) here.
10218  auto* new_ct =
10219  context_->working_model->add_constraints()->mutable_no_overlap_2d();
10220  new_ct->set_boxes_with_null_area_can_overlap(
10221  ct.no_overlap_2d().boxes_with_null_area_can_overlap());
10222 
10223  const int num_intervals = ct.no_overlap_2d().x_intervals().size();
10224  new_ct->mutable_x_intervals()->Reserve(num_intervals);
10225  new_ct->mutable_y_intervals()->Reserve(num_intervals);
10226  for (int i = 0; i < num_intervals; ++i) {
10227  const auto x_it = interval_mapping_.find(ct.no_overlap_2d().x_intervals(i));
10228  if (x_it == interval_mapping_.end()) continue;
10229  const auto y_it = interval_mapping_.find(ct.no_overlap_2d().y_intervals(i));
10230  if (y_it == interval_mapping_.end()) continue;
10231  new_ct->add_x_intervals(x_it->second);
10232  new_ct->add_y_intervals(y_it->second);
10233  }
10234 }
10235 
10236 void ModelCopy::CopyAndMapCumulative(const ConstraintProto& ct) {
10237  // Note that we don't copy names or enforcement_literal (not supported) here.
10238  auto* new_ct =
10239  context_->working_model->add_constraints()->mutable_cumulative();
10240  *new_ct->mutable_capacity() = ct.cumulative().capacity();
10241 
10242  const int num_intervals = ct.cumulative().intervals().size();
10243  new_ct->mutable_intervals()->Reserve(num_intervals);
10244  new_ct->mutable_demands()->Reserve(num_intervals);
10245  for (int i = 0; i < num_intervals; ++i) {
10246  const auto it = interval_mapping_.find(ct.cumulative().intervals(i));
10247  if (it == interval_mapping_.end()) continue;
10248  new_ct->add_intervals(it->second);
10249  *new_ct->add_demands() = ct.cumulative().demands(i);
10250  }
10251 }
10252 
10253 bool ModelCopy::CreateUnsatModel() {
10254  context_->working_model->mutable_constraints()->Clear();
10255  context_->working_model->add_constraints()->mutable_bool_or();
10256  return false;
10257 }
10258 
10259 bool ImportModelWithBasicPresolveIntoContext(const CpModelProto& in_model,
10261  ModelCopy copier(context);
10262  copier.ImportVariablesAndMaybeIgnoreNames(in_model);
10263  if (copier.ImportAndSimplifyConstraints(in_model, {}, /*first_copy=*/true)) {
10265  context);
10266  return true;
10267  }
10268  return context->NotifyThatModelIsUnsat();
10269 }
10270 
10272  const CpModelProto& in_model, PresolveContext* context) {
10273  if (!in_model.name().empty()) {
10274  context->working_model->set_name(in_model.name());
10275  }
10276  if (in_model.has_objective()) {
10277  *context->working_model->mutable_objective() = in_model.objective();
10278  }
10279  if (in_model.has_floating_point_objective()) {
10280  *context->working_model->mutable_floating_point_objective() =
10281  in_model.floating_point_objective();
10282  }
10283  if (!in_model.search_strategy().empty()) {
10284  *context->working_model->mutable_search_strategy() =
10285  in_model.search_strategy();
10286  }
10287  if (!in_model.assumptions().empty()) {
10288  *context->working_model->mutable_assumptions() = in_model.assumptions();
10289  }
10290  if (in_model.has_symmetry()) {
10291  *context->working_model->mutable_symmetry() = in_model.symmetry();
10292  }
10293  if (in_model.has_solution_hint()) {
10294  *context->working_model->mutable_solution_hint() = in_model.solution_hint();
10295  }
10296 }
10297 
10298 // TODO(user): Use better heuristic?
10299 //
10300 // TODO(user): This is similar to what Bounded variable addition (BVA) does.
10301 // By adding a new variable, enforcement => literals becomes
10302 // enforcement => x => literals, and we have one clause + #literals implication
10303 // instead of #literals clauses. What BVA does in addition is to use the same
10304 // x for other enforcement list if the rhs literals are shared.
10305 void CpModelPresolver::MergeClauses() {
10306  if (context_->ModelIsUnsat()) return;
10307  ClauseWithOneMissingHasher hasher(*context_->random());
10308 
10310  wall_timer.Start();
10311  int64_t work_done = 0;
10312  const int64_t work_limit = 1e8;
10313 
10314  std::vector<int> to_clean;
10315 
10316  int64_t num_collisions = 0;
10317  int64_t num_merges = 0;
10318  int64_t num_saved_literals = 0;
10319 
10320  // Keep a map from negation of enforcement_literal => bool_and ct index.
10321  absl::flat_hash_map<uint64_t, int> bool_and_map;
10322 
10323  // First loop over the constraint:
10324  // - Register already existing bool_and.
10325  // - score at_most_ones literals.
10326  // - Record bool_or.
10327  const int num_variables = context_->working_model->variables_size();
10328  std::vector<int> bool_or_indices;
10329  std::vector<int64_t> literal_score(2 * num_variables, 0);
10330  const auto get_index = [](int ref) {
10331  return 2 * PositiveRef(ref) + (RefIsPositive(ref) ? 0 : 1);
10332  };
10333 
10334  const int num_constraints = context_->working_model->constraints_size();
10335  for (int c = 0; c < num_constraints; ++c) {
10336  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
10337  if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
10338  if (ct->enforcement_literal().size() > 1) {
10339  // We need to sort the negated literals.
10340  std::sort(ct->mutable_enforcement_literal()->begin(),
10341  ct->mutable_enforcement_literal()->end(),
10342  std::greater<int>());
10343  const auto [it, inserted] = bool_and_map.insert(
10344  {hasher.HashOfNegatedLiterals(ct->enforcement_literal()), c});
10345  if (inserted) {
10346  to_clean.push_back(c);
10347  } else {
10348  // See if this is a true duplicate. If yes, merge rhs.
10349  ConstraintProto* other_ct =
10350  context_->working_model->mutable_constraints(it->second);
10351  const absl::Span<const int> s1(ct->enforcement_literal());
10352  const absl::Span<const int> s2(other_ct->enforcement_literal());
10353  if (s1 == s2) {
10354  context_->UpdateRuleStats(
10355  "bool_and: merged constraints with same enforcement");
10356  other_ct->mutable_bool_and()->mutable_literals()->Add(
10357  ct->bool_and().literals().begin(),
10358  ct->bool_and().literals().end());
10359  ct->Clear();
10360  context_->UpdateConstraintVariableUsage(c);
10361  }
10362  }
10363  }
10364  continue;
10365  }
10366  if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
10367  const int size = ct->at_most_one().literals().size();
10368  for (const int ref : ct->at_most_one().literals()) {
10369  literal_score[get_index(ref)] += size;
10370  }
10371  continue;
10372  }
10373  if (ct->constraint_case() == ConstraintProto::kExactlyOne) {
10374  const int size = ct->exactly_one().literals().size();
10375  for (const int ref : ct->exactly_one().literals()) {
10376  literal_score[get_index(ref)] += size;
10377  }
10378  continue;
10379  }
10380 
10381  if (ct->constraint_case() != ConstraintProto::kBoolOr) continue;
10382 
10383  // Both of these test shouldn't happen, but we have them to be safe.
10384  if (!ct->enforcement_literal().empty()) continue;
10385  if (ct->bool_or().literals().size() <= 2) continue;
10386 
10387  std::sort(ct->mutable_bool_or()->mutable_literals()->begin(),
10388  ct->mutable_bool_or()->mutable_literals()->end());
10389  hasher.RegisterClause(c, ct->bool_or().literals());
10390  bool_or_indices.push_back(c);
10391  }
10392 
10393  for (const int c : bool_or_indices) {
10394  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
10395 
10396  bool merged = false;
10397  work_done += ct->bool_or().literals().size();
10398  if (work_done > work_limit) break;
10399  for (const int ref : ct->bool_or().literals()) {
10400  const uint64_t hash = hasher.HashWithout(c, ref);
10401  const auto it = bool_and_map.find(hash);
10402  if (it != bool_and_map.end()) {
10403  ++num_collisions;
10404  const int base_c = it->second;
10405  auto* and_ct = context_->working_model->mutable_constraints(base_c);
10407  ct->bool_or().literals(), and_ct->enforcement_literal(), ref)) {
10408  ++num_merges;
10409  num_saved_literals += ct->bool_or().literals().size() - 1;
10410  merged = true;
10411  and_ct->mutable_bool_and()->add_literals(ref);
10412  ct->Clear();
10413  context_->UpdateConstraintVariableUsage(c);
10414  break;
10415  }
10416  }
10417  }
10418 
10419  if (!merged) {
10420  // heuristic: take first literal whose negation has highest score.
10421  int best_ref = ct->bool_or().literals(0);
10422  int64_t best_score = literal_score[get_index(NegatedRef(best_ref))];
10423  for (const int ref : ct->bool_or().literals()) {
10424  const int64_t score = literal_score[get_index(NegatedRef(ref))];
10425  if (score > best_score) {
10426  best_ref = ref;
10427  best_score = score;
10428  }
10429  }
10430 
10431  const uint64_t hash = hasher.HashWithout(c, best_ref);
10432  const auto [_, inserted] = bool_and_map.insert({hash, c});
10433  if (inserted) {
10434  to_clean.push_back(c);
10435  context_->tmp_literals.clear();
10436  for (const int lit : ct->bool_or().literals()) {
10437  if (lit == best_ref) continue;
10438  context_->tmp_literals.push_back(NegatedRef(lit));
10439  }
10440  ct->Clear();
10441  ct->mutable_enforcement_literal()->Assign(
10442  context_->tmp_literals.begin(), context_->tmp_literals.end());
10443  ct->mutable_bool_and()->add_literals(best_ref);
10444  }
10445  }
10446  }
10447 
10448  // Retransform to bool_or bool_and with a single rhs.
10449  for (const int c : to_clean) {
10450  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
10451  if (ct->bool_and().literals().size() > 1) {
10452  context_->UpdateConstraintVariableUsage(c);
10453  continue;
10454  }
10455 
10456  // We have a single bool_and, lets transform it back to single bool_or.
10457  context_->tmp_literals.clear();
10458  context_->tmp_literals.push_back(ct->bool_and().literals(0));
10459  for (const int ref : ct->enforcement_literal()) {
10460  context_->tmp_literals.push_back(NegatedRef(ref));
10461  }
10462  ct->Clear();
10463  ct->mutable_bool_or()->mutable_literals()->Assign(
10464  context_->tmp_literals.begin(), context_->tmp_literals.end());
10465  }
10466 
10467  SOLVER_LOG(logger_, "[MergeClauses]", " #num_collisions=", num_collisions,
10468  " #num_merges=", num_merges,
10469  " #num_saved_literals=", num_saved_literals, " work=", work_done,
10470  "/", work_limit, " time=", wall_timer.Get(), "s");
10471 }
10472 
10473 // =============================================================================
10474 // Public API.
10475 // =============================================================================
10476 
10478  std::vector<int>* postsolve_mapping) {
10479  CpModelPresolver presolver(context, postsolve_mapping);
10480  return presolver.Presolve();
10481 }
10482 
10484  std::vector<int>* postsolve_mapping)
10485  : postsolve_mapping_(postsolve_mapping),
10486  context_(context),
10487  logger_(context->logger()) {}
10488 
10489 CpSolverStatus CpModelPresolver::InfeasibleStatus() {
10490  if (logger_->LoggingIsEnabled()) context_->LogInfo();
10492 }
10493 
10494 // The presolve works as follow:
10495 //
10496 // First stage:
10497 // We will process all active constraints until a fix point is reached. During
10498 // this stage:
10499 // - Variable will never be deleted, but their domain will be reduced.
10500 // - Constraint will never be deleted (they will be marked as empty if needed).
10501 // - New variables and new constraints can be added after the existing ones.
10502 // - Constraints are added only when needed to the mapping_problem if they are
10503 // needed during the postsolve.
10504 //
10505 // Second stage:
10506 // - All the variables domain will be copied to the mapping_model.
10507 // - Everything will be remapped so that only the variables appearing in some
10508 // constraints will be kept and their index will be in [0, num_new_variables).
10509 CpSolverStatus CpModelPresolver::Presolve() {
10510  // TODO(user): move in the context.
10511  context_->keep_all_feasible_solutions =
10512  context_->params().keep_all_feasible_solutions_in_presolve() ||
10513  context_->params().enumerate_all_solutions() ||
10514  context_->params().fill_tightened_domains_in_response() ||
10515  !context_->working_model->assumptions().empty() ||
10516  !context_->params().cp_model_presolve();
10517 
10518  // We copy the search strategy to the mapping_model.
10519  for (const auto& decision_strategy :
10520  context_->working_model->search_strategy()) {
10521  *(context_->mapping_model->add_search_strategy()) = decision_strategy;
10522  }
10523 
10524  // Initialize the initial context.working_model domains.
10525  context_->InitializeNewDomains();
10526 
10527  // If the objective is a floating point one, we scale it.
10528  //
10529  // TODO(user): We should probably try to delay this even more. For that we
10530  // just need to isolate more the "dual" reduction that usually need to look at
10531  // the objective.
10532  if (context_->working_model->has_floating_point_objective()) {
10533  if (!context_->ScaleFloatingPointObjective()) {
10534  SOLVER_LOG(logger_,
10535  "The floating point objective cannot be scaled with enough "
10536  "precision");
10538  }
10539 
10540  // At this point, we didn't create any new variables, so the integer
10541  // objective is in term of the orinal problem variables. We save it so that
10542  // we can expose to the user what exact objective we are actually
10543  // optimizing.
10544  *context_->mapping_model->mutable_objective() =
10545  context_->working_model->objective();
10546  }
10547 
10548  // Initialize the objective and the constraint <-> variable graph.
10549  //
10550  // Note that we did some basic presolving during the first copy of the model.
10551  // This is important has initializing the constraint <-> variable graph can
10552  // be costly, so better to remove trivially feasible constraint for instance.
10553  context_->ReadObjectiveFromProto();
10554  if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
10557  DCHECK(context_->ConstraintVariableUsageIsConsistent());
10558 
10559  // If presolve is false, just run expansion.
10560  if (!context_->params().cp_model_presolve()) {
10561  ExpandCpModel(context_);
10562  if (context_->ModelIsUnsat()) return InfeasibleStatus();
10563 
10564  // We still write back the canonical objective has we don't deal well
10565  // with uninitialized domain or duplicate variables.
10566  if (context_->working_model->has_objective()) {
10567  context_->WriteObjectiveToProto();
10568  }
10569 
10570  // We need to append all the variable equivalence that are still used!
10571  EncodeAllAffineRelations();
10572  if (logger_->LoggingIsEnabled()) context_->LogInfo();
10573  return CpSolverStatus::UNKNOWN;
10574  }
10575 
10576  // Presolve all variable domain once. The PresolveToFixPoint() function will
10577  // only reprocess domain that changed.
10578  if (context_->ModelIsUnsat()) return InfeasibleStatus();
10579  for (int var = 0; var < context_->working_model->variables().size(); ++var) {
10580  if (context_->VariableIsNotUsedAnymore(var)) continue;
10581  if (!PresolveAffineRelationIfAny(var)) return InfeasibleStatus();
10582 
10583  // Try to canonicalize the domain, note that we should have detected all
10584  // affine relations before, so we don't recreate "canononical" variables
10585  // if they already exist in the model.
10586  TryToSimplifyDomain(var);
10587  if (context_->ModelIsUnsat()) return InfeasibleStatus();
10589  }
10590  if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
10591 
10592  // Main propagation loop.
10593  for (int iter = 0; iter < context_->params().max_presolve_iterations();
10594  ++iter) {
10595  if (context_->time_limit()->LimitReached()) break;
10596  context_->UpdateRuleStats("presolve: iteration");
10597  const int64_t old_num_presolve_op = context_->num_presolve_operations;
10598 
10599  // TODO(user): The presolve transformations we do after this is called might
10600  // result in even more presolve if we were to call this again! improve the
10601  // code. See for instance plusexample_6_sat.fzn were represolving the
10602  // presolved problem reduces it even more.
10603  PresolveToFixPoint();
10604 
10605  // Call expansion.
10606  if (!context_->ModelIsExpanded()) {
10607  ExtractEncodingFromLinear();
10608  ExpandCpModel(context_);
10609  if (context_->ModelIsUnsat()) return InfeasibleStatus();
10610 
10611  // TODO(user): Make sure we can't have duplicate in these constraint.
10612  // These are due to ExpandCpModel() were we create such constraint with
10613  // duplicate. The problem is that some code assumes these are presolved
10614  // before being called.
10615  const int num_constraints = context_->working_model->constraints().size();
10616  for (int c = 0; c < num_constraints; ++c) {
10617  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
10618  const auto type = ct->constraint_case();
10619  if (type == ConstraintProto::kAtMostOne ||
10620  type == ConstraintProto::kExactlyOne) {
10621  if (PresolveOneConstraint(c)) {
10622  context_->UpdateConstraintVariableUsage(c);
10623  }
10624  if (context_->ModelIsUnsat()) return InfeasibleStatus();
10625  }
10626  }
10627 
10628  // We need to re-evaluate the degree because some presolve rule only
10629  // run after expansion.
10630  const int num_vars = context_->working_model->variables().size();
10631  for (int var = 0; var < num_vars; ++var) {
10632  if (context_->VarToConstraints(var).size() <= 3) {
10634  }
10635  }
10636  }
10637  DCHECK(context_->ConstraintVariableUsageIsConsistent());
10638 
10639  // We run the symmetry before more complex presolve rules as many of them
10640  // are heuristic based and might break the symmetry present in the original
10641  // problems. This happens for example on the flatzinc wordpress problem.
10642  //
10643  // TODO(user): Decide where is the best place for this.
10644  //
10645  // TODO(user): try not to break symmetry in our clique extension or other
10646  // more advanced presolve rule? Ideally we could even exploit them. But in
10647  // this case, it is still good to compute them early.
10648  if (context_->params().symmetry_level() > 0 && !context_->ModelIsUnsat() &&
10649  !context_->time_limit()->LimitReached() &&
10650  !context_->keep_all_feasible_solutions) {
10652  }
10653 
10654  // Runs SAT specific presolve on the pure-SAT part of the problem.
10655  // Note that because this can only remove/fix variable not used in the other
10656  // part of the problem, there is no need to redo more presolve afterwards.
10657  if (context_->params().cp_model_use_sat_presolve()) {
10658  if (!context_->time_limit()->LimitReached()) {
10659  PresolvePureSatPart();
10660  }
10661  }
10662 
10663  // Extract redundant at most one constraint form the linear ones.
10664  //
10665  // TODO(user): more generally if we do some probing, the same relation will
10666  // be detected (and more). Also add an option to turn this off?
10667  //
10668  // TODO(user): instead of extracting at most one, extract pairwise conflicts
10669  // and add them to bool_and clauses? this is some sort of small scale
10670  // probing, but good for sat presolve and clique later?
10671  if (!context_->ModelIsUnsat() && iter == 0) {
10672  const int old_size = context_->working_model->constraints_size();
10673  for (int c = 0; c < old_size; ++c) {
10674  ConstraintProto* ct = context_->working_model->mutable_constraints(c);
10675  if (ct->constraint_case() != ConstraintProto::kLinear) continue;
10676  ExtractAtMostOneFromLinear(ct);
10677  }
10679  }
10680 
10681  if (context_->params().cp_model_probing_level() > 0) {
10682  if (!context_->time_limit()->LimitReached()) {
10683  Probe();
10684  PresolveToFixPoint();
10685  }
10686  } else {
10687  TransformIntoMaxCliques();
10688  }
10689 
10690  // Deal with pair of constraints.
10691  //
10692  // TODO(user): revisit when different transformation appear.
10693  // TODO(user): merge these code instead of doing many passes?
10694  DetectDuplicateConstraints();
10695  DetectDominatedLinearConstraints();
10696  ProcessSetPPC();
10697  if (context_->params().find_big_linear_overlap()) FindBigLinearOverlap();
10698  if (context_->ModelIsUnsat()) return InfeasibleStatus();
10699 
10700  // We do that after the duplicate, SAT and SetPPC constraints.
10701  if (!context_->time_limit()->LimitReached()) {
10702  // Merge clauses that differ in just one literal.
10703  // Heuristic use at_most_one to try to tighten the initial LP Relaxation.
10704  MergeClauses();
10705  if (/*DISABLES CODE*/ (false)) DetectIncludedEnforcement();
10706  }
10707 
10708  // The TransformIntoMaxCliques() call above transform all bool and into
10709  // at most one of size 2. This does the reverse and merge them.
10710  ExtractBoolAnd();
10711 
10712  // Call the main presolve to remove the fixed variables and do more
10713  // deductions.
10714  PresolveToFixPoint();
10715 
10716  // Exit the loop if no operations were performed.
10717  //
10718  // TODO(user): try to be smarter and avoid looping again if little changed.
10719  const int64_t num_ops =
10720  context_->num_presolve_operations - old_num_presolve_op;
10721  if (num_ops == 0) break;
10722  }
10723  if (context_->ModelIsUnsat()) return InfeasibleStatus();
10724 
10725  // Regroup no-overlaps into max-cliques.
10726  MergeNoOverlapConstraints();
10727  if (context_->ModelIsUnsat()) return InfeasibleStatus();
10728 
10729  // Tries to spread the objective amongst many variables.
10730  // We re-do a canonicalization with the final linear expression.
10731  if (context_->working_model->has_objective()) {
10732  ExpandObjective();
10733  if (context_->ModelIsUnsat()) return InfeasibleStatus();
10734  ShiftObjectiveWithExactlyOnes();
10735  if (context_->ModelIsUnsat()) return InfeasibleStatus();
10736 
10737  // We re-do a canonicalization with the final linear expression.
10738  if (!context_->CanonicalizeObjective()) {
10739  (void)context_->NotifyThatModelIsUnsat();
10740  }
10741  if (context_->ModelIsUnsat()) return InfeasibleStatus();
10742  context_->WriteObjectiveToProto();
10743  }
10744 
10745  // Take care of linear constraint with a complex rhs.
10747 
10748  // Adds all needed affine relation to context_->working_model.
10749  EncodeAllAffineRelations();
10750  if (context_->ModelIsUnsat()) return InfeasibleStatus();
10751 
10752  // The strategy variable indices will be remapped in ApplyVariableMapping()
10753  // but first we use the representative of the affine relations for the
10754  // variables that are not present anymore.
10755  //
10756  // Note that we properly take into account the sign of the coefficient which
10757  // will result in the same domain reduction strategy. Moreover, if the
10758  // variable order is not CHOOSE_FIRST, then we also encode the associated
10759  // affine transformation in order to preserve the order.
10760  absl::flat_hash_set<int> used_variables;
10761  for (DecisionStrategyProto& strategy :
10762  *context_->working_model->mutable_search_strategy()) {
10763  DecisionStrategyProto copy = strategy;
10764  strategy.clear_variables();
10765  strategy.clear_transformations();
10766  for (const int ref : copy.variables()) {
10767  const int var = PositiveRef(ref);
10768 
10769  // Remove fixed variables.
10770  if (context_->IsFixed(var)) continue;
10771 
10772  // There is not point having a variable appear twice, so we only keep
10773  // the first occurrence in the first strategy in which it occurs.
10774  if (used_variables.contains(var)) continue;
10775  used_variables.insert(var);
10776 
10777  if (context_->VarToConstraints(var).empty()) {
10778  const AffineRelation::Relation r = context_->GetAffineRelation(var);
10779  if (!context_->VarToConstraints(r.representative).empty()) {
10780  const int rep = (r.coeff > 0) == RefIsPositive(ref)
10781  ? r.representative
10783  if (strategy.variable_selection_strategy() !=
10784  DecisionStrategyProto::CHOOSE_FIRST) {
10785  DecisionStrategyProto::AffineTransformation* t =
10786  strategy.add_transformations();
10787  t->set_index(strategy.variables_size());
10788  t->set_offset(r.offset);
10789  t->set_positive_coeff(std::abs(r.coeff));
10790  }
10791  strategy.add_variables(rep);
10792  } else {
10793  // TODO(user): this variable was removed entirely by the presolve (no
10794  // equivalent variable present). We simply ignore it entirely which
10795  // might result in a different search...
10796  }
10797  } else {
10798  strategy.add_variables(ref);
10799  }
10800  }
10801  }
10802 
10803  // Sync the domains.
10804  for (int i = 0; i < context_->working_model->variables_size(); ++i) {
10805  FillDomainInProto(context_->DomainOf(i),
10806  context_->working_model->mutable_variables(i));
10807  DCHECK_GT(context_->working_model->variables(i).domain_size(), 0);
10808  }
10809 
10810  // Set the variables of the mapping_model.
10811  context_->mapping_model->mutable_variables()->CopyFrom(
10812  context_->working_model->variables());
10813 
10814  // Remove all the unused variables from the presolved model.
10815  postsolve_mapping_->clear();
10816  std::vector<int> mapping(context_->working_model->variables_size(), -1);
10817  absl::flat_hash_map<int64_t, int> constant_to_index;
10818  int num_unused_variables = 0;
10819  for (int i = 0; i < context_->working_model->variables_size(); ++i) {
10820  if (mapping[i] != -1) continue; // Already mapped.
10821 
10822  if (context_->VariableWasRemoved(i)) {
10823  // Heuristic: If a variable is removed and has a representative that is
10824  // not, we "move" the representative to the spot of that variable in the
10825  // original order. This is to preserve any info encoded in the variable
10826  // order by the modeler.
10827  const int r = PositiveRef(context_->GetAffineRelation(i).representative);
10828  if (mapping[r] == -1 && !context_->VariableIsNotUsedAnymore(r)) {
10829  mapping[r] = postsolve_mapping_->size();
10830  postsolve_mapping_->push_back(r);
10831  }
10832  continue;
10833  }
10834 
10835  // TODO(user): we could still remove unused constant even if
10836  // keep_all_feasible_solutions is true.
10837  if (!context_->keep_all_feasible_solutions) {
10838  if (context_->VariableIsNotUsedAnymore(i)) {
10839  // Tricky. Variables that where not removed by a presolve rule should be
10840  // fixed first during postsolve, so that more complex postsolve rules
10841  // can use their values. One way to do that is to fix them here.
10842  //
10843  // We prefer to fix them to zero if possible.
10844  ++num_unused_variables;
10846  context_->mapping_model->mutable_variables(i));
10847  continue;
10848  }
10849 
10850  // Merge identical constant. Note that the only place were constant are
10851  // still left are in the circuit and route constraint for fixed arcs.
10852  if (context_->IsFixed(i)) {
10853  auto [it, inserted] = constant_to_index.insert(
10854  {context_->FixedValue(i), postsolve_mapping_->size()});
10855  if (!inserted) {
10856  mapping[i] = it->second;
10857  continue;
10858  }
10859  }
10860  }
10861 
10862  mapping[i] = postsolve_mapping_->size();
10863  postsolve_mapping_->push_back(i);
10864  }
10865  context_->UpdateRuleStats(absl::StrCat("presolve: ", num_unused_variables,
10866  " unused variables removed."));
10867 
10868  if (context_->params().permute_variable_randomly()) {
10869  // The mapping might merge variable, so we have to be careful here.
10870  const int n = postsolve_mapping_->size();
10871  std::vector<int> perm(n);
10872  std::iota(perm.begin(), perm.end(), 0);
10873  std::shuffle(perm.begin(), perm.end(), *context_->random());
10874  for (int i = 0; i < context_->working_model->variables_size(); ++i) {
10875  if (mapping[i] != -1) mapping[i] = perm[mapping[i]];
10876  }
10877  std::vector<int> new_postsolve_mapping(n);
10878  for (int i = 0; i < n; ++i) {
10879  new_postsolve_mapping[perm[i]] = (*postsolve_mapping_)[i];
10880  }
10881  *postsolve_mapping_ = std::move(new_postsolve_mapping);
10882  }
10883 
10884  DCHECK(context_->ConstraintVariableUsageIsConsistent());
10885  ApplyVariableMapping(mapping, *context_);
10886 
10887  // Compact all non-empty constraint at the beginning.
10889 
10890  // Hack to display the number of deductions stored.
10891  if (context_->deductions.NumDeductions() > 0) {
10892  context_->UpdateRuleStats(absl::StrCat(
10893  "deductions: ", context_->deductions.NumDeductions(), " stored"));
10894  }
10895 
10896  // Stats and checks.
10897  if (logger_->LoggingIsEnabled()) context_->LogInfo();
10898 
10899  // This is not supposed to happen, and is more indicative of an error than an
10900  // INVALID model. But for our no-overflow preconditions, we might run into bad
10901  // situation that causes the final model to be invalid.
10902  {
10903  const std::string error =
10904  ValidateCpModel(*context_->working_model, /*after_presolve=*/true);
10905  if (!error.empty()) {
10906  SOLVER_LOG(logger_, "Error while validating postsolved model: ", error);
10908  }
10909  }
10910  {
10911  const std::string error = ValidateCpModel(*context_->mapping_model);
10912  if (!error.empty()) {
10913  SOLVER_LOG(logger_,
10914  "Error while validating mapping_model model: ", error);
10916  }
10917  }
10918 
10919  return CpSolverStatus::UNKNOWN;
10920 }
10921 
10922 void ApplyVariableMapping(const std::vector<int>& mapping,
10923  const PresolveContext& context) {
10924  CpModelProto* proto = context.working_model;
10925 
10926  // Remap all the variable/literal references in the constraints and the
10927  // enforcement literals in the variables.
10928  auto mapping_function = [&mapping](int* ref) {
10929  const int image = mapping[PositiveRef(*ref)];
10930  CHECK_GE(image, 0);
10931  *ref = RefIsPositive(*ref) ? image : NegatedRef(image);
10932  };
10933  for (ConstraintProto& ct_ref : *proto->mutable_constraints()) {
10934  ApplyToAllVariableIndices(mapping_function, &ct_ref);
10935  ApplyToAllLiteralIndices(mapping_function, &ct_ref);
10936  }
10937 
10938  // Remap the objective variables.
10939  if (proto->has_objective()) {
10940  for (int& mutable_ref : *proto->mutable_objective()->mutable_vars()) {
10941  mapping_function(&mutable_ref);
10942  }
10943  }
10944 
10945  // Remap the assumptions.
10946  for (int& mutable_ref : *proto->mutable_assumptions()) {
10947  mapping_function(&mutable_ref);
10948  }
10949 
10950  // Remap the search decision heuristic.
10951  // Note that we delete any heuristic related to a removed variable.
10952  for (DecisionStrategyProto& strategy : *proto->mutable_search_strategy()) {
10953  const DecisionStrategyProto copy = strategy;
10954  strategy.clear_variables();
10955  std::vector<int> new_indices(copy.variables().size(), -1);
10956  for (int i = 0; i < copy.variables().size(); ++i) {
10957  const int ref = copy.variables(i);
10958  const int image = mapping[PositiveRef(ref)];
10959  if (image >= 0) {
10960  new_indices[i] = strategy.variables_size();
10961  strategy.add_variables(RefIsPositive(ref) ? image : NegatedRef(image));
10962  }
10963  }
10964  strategy.clear_transformations();
10965  for (const auto& transform : copy.transformations()) {
10966  CHECK_LT(transform.index(), new_indices.size());
10967  const int new_index = new_indices[transform.index()];
10968  if (new_index == -1) continue;
10969  auto* new_transform = strategy.add_transformations();
10970  *new_transform = transform;
10971  CHECK_LT(new_index, strategy.variables().size());
10972  new_transform->set_index(new_index);
10973  }
10974  }
10975 
10976  // Remap the solution hint. Note that after remapping, we may have duplicate
10977  // variable, so we only keep the first occurrence.
10978  if (proto->has_solution_hint()) {
10979  absl::flat_hash_set<int> used_vars;
10980  auto* mutable_hint = proto->mutable_solution_hint();
10981  int new_size = 0;
10982  for (int i = 0; i < mutable_hint->vars_size(); ++i) {
10983  const int old_ref = mutable_hint->vars(i);
10984  int64_t old_value = mutable_hint->values(i);
10985 
10986  // We always move a hint within bounds.
10987  // This also make sure a hint of INT_MIN or INT_MAX does not overflow.
10988  if (old_value < context.MinOf(old_ref)) {
10989  old_value = context.MinOf(old_ref);
10990  }
10991  if (old_value > context.MaxOf(old_ref)) {
10992  old_value = context.MaxOf(old_ref);
10993  }
10994 
10995  // Note that if (old_value - r.offset) is not divisible by r.coeff, then
10996  // the hint is clearly infeasible, but we still set it to a "close" value.
10997  const AffineRelation::Relation r = context.GetAffineRelation(old_ref);
10998  const int var = r.representative;
10999  const int64_t value = (old_value - r.offset) / r.coeff;
11000 
11001  const int image = mapping[var];
11002  if (image >= 0) {
11003  if (!used_vars.insert(image).second) continue;
11004  mutable_hint->set_vars(new_size, image);
11005  mutable_hint->set_values(new_size, value);
11006  ++new_size;
11007  }
11008  }
11009  if (new_size > 0) {
11010  mutable_hint->mutable_vars()->Truncate(new_size);
11011  mutable_hint->mutable_values()->Truncate(new_size);
11012  } else {
11013  proto->clear_solution_hint();
11014  }
11015  }
11016 
11017  // Move the variable definitions.
11018  std::vector<IntegerVariableProto> new_variables;
11019  for (int i = 0; i < mapping.size(); ++i) {
11020  const int image = mapping[i];
11021  if (image < 0) continue;
11022  if (image >= new_variables.size()) {
11023  new_variables.resize(image + 1, IntegerVariableProto());
11024  }
11025  new_variables[image].Swap(proto->mutable_variables(i));
11026  }
11027  proto->clear_variables();
11028  for (IntegerVariableProto& proto_ref : new_variables) {
11029  proto->add_variables()->Swap(&proto_ref);
11030  }
11031 
11032  // Check that all variables are used.
11033  for (const IntegerVariableProto& v : proto->variables()) {
11034  CHECK_GT(v.domain_size(), 0);
11035  }
11036 }
11037 
11038 namespace {
11039 
11040 ConstraintProto CopyConstraintForDuplicateDetection(const ConstraintProto& ct,
11041  bool ignore_enforcement) {
11042  ConstraintProto copy = ct;
11043  copy.clear_name();
11044  if (ignore_enforcement) {
11045  copy.mutable_enforcement_literal()->Clear();
11046  } else if (ct.constraint_case() == ConstraintProto::kLinear) {
11047  copy.mutable_linear()->clear_domain();
11048  }
11049  return copy;
11050 }
11051 
11052 // We ignore all the fields but the linear expression.
11053 ConstraintProto CopyObjectiveForDuplicateDetection(
11054  const CpObjectiveProto& objective) {
11055  ConstraintProto copy;
11056  *copy.mutable_linear()->mutable_vars() = objective.vars();
11057  *copy.mutable_linear()->mutable_coeffs() = objective.coeffs();
11058  return copy;
11059 }
11060 
11061 } // namespace
11062 
11063 std::vector<std::pair<int, int>> FindDuplicateConstraints(
11064  const CpModelProto& model_proto, bool ignore_enforcement) {
11065  std::vector<std::pair<int, int>> result;
11066 
11067  // We use a map hash: serialized_constraint_proto hash -> constraint index.
11068  ConstraintProto copy;
11069  std::string s;
11070  absl::flat_hash_map<uint64_t, int> equiv_constraints;
11071 
11072  // Create a special representative for the linear objective.
11073  if (model_proto.has_objective() && !ignore_enforcement) {
11074  copy = CopyObjectiveForDuplicateDetection(model_proto.objective());
11075  s = copy.SerializeAsString();
11076  equiv_constraints[absl::Hash<std::string>()(s)] = kObjectiveConstraint;
11077  }
11078 
11079  const int num_constraints = model_proto.constraints().size();
11080  for (int c = 0; c < num_constraints; ++c) {
11081  const auto type = model_proto.constraints(c).constraint_case();
11082  if (type == ConstraintProto::CONSTRAINT_NOT_SET) continue;
11083 
11084  // TODO(user): we could delete duplicate identical interval, but we need
11085  // to make sure reference to them are updated.
11086  if (type == ConstraintProto::kInterval) continue;
11087 
11088  // Nothing we will presolve in this case.
11089  if (ignore_enforcement && type == ConstraintProto::kBoolAnd) continue;
11090 
11091  // We ignore names when comparing constraints.
11092  //
11093  // TODO(user): This is not particularly efficient.
11094  copy = CopyConstraintForDuplicateDetection(model_proto.constraints(c),
11095  ignore_enforcement);
11096  s = copy.SerializeAsString();
11097 
11098  const uint64_t hash = absl::Hash<std::string>()(s);
11099  const auto [it, inserted] = equiv_constraints.insert({hash, c});
11100  if (!inserted) {
11101  // Already present!
11102  const int other_c_with_same_hash = it->second;
11103  copy = other_c_with_same_hash == kObjectiveConstraint
11104  ? CopyObjectiveForDuplicateDetection(model_proto.objective())
11105  : CopyConstraintForDuplicateDetection(
11106  model_proto.constraints(other_c_with_same_hash),
11107  ignore_enforcement);
11108  if (s == copy.SerializeAsString()) {
11109  result.push_back({c, other_c_with_same_hash});
11110  }
11111  }
11112  }
11113 
11114  return result;
11115 }
11116 
11117 } // namespace sat
11118 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
void Start()
Definition: timer.h:31
double Get() const
Definition: timer.h:45
We call domain any subset of Int64 = [kint64min, kint64max].
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
Domain Negation() const
Returns {x ∈ Int64, ∃ e ∈ D, x = -e}.
bool Contains(int64_t value) const
Returns true iff value is in Domain.
Domain ContinuousMultiplicationBy(int64_t coeff) const
Returns a superset of MultiplicationBy() to avoid the explosion in the representation size.
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
ClosedInterval front() const
int64_t Size() const
Returns the number of elements in the domain.
Domain UnionWith(const Domain &domain) const
Returns the union of D and domain.
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
int64_t Min() const
Returns the min value of the domain.
bool IsEmpty() const
Returns true if this is the empty set.
int64_t SmallestValue() const
Returns the value closest to zero.
Domain RelaxIfTooComplex() const
If NumIntervals() is too large, this return a superset of the domain.
static Domain FromValues(std::vector< int64_t > values)
Creates a domain from the union of an unsorted list of integer values.
Domain SquareSuperset() const
Returns a superset of {x ∈ Int64, ∃ y ∈ D, x = y * y }.
Domain DivisionBy(int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e / coeff}.
DomainIteratorBeginEnd Values() const &
Domain PositiveModuloBySuperset(const Domain &modulo) const
Returns a superset of {x ∈ Int64, ∃ e ∈ D, ∃ m ∈ modulo, x = e % m }.
static int64_t GCD64(int64_t x, int64_t y)
Definition: mathutil.h:107
void Set(IntegerType index)
Definition: bitset.h:792
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:552
void AdvanceDeterministicTime(double deterministic_duration)
Advances the deterministic time.
Definition: time_limit.h:226
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
void AddDeduction(int literal_ref, int var, Domain domain)
void AddMultiples(int64_t coeff, int64_t max_value)
Definition: sat/util.cc:464
bool ImportAndSimplifyConstraints(const CpModelProto &in_model, const std::vector< int > &ignored_constraints, bool first_copy=false)
void ImportVariablesAndMaybeIgnoreNames(const CpModelProto &in_model)
bool CanonicalizeAffineVariable(int ref, int64_t coeff, int64_t mod, int64_t rhs)
bool ExpressionIsALiteral(const LinearExpressionProto &expr, int *literal=nullptr) const
bool StoreAbsRelation(int target_ref, int ref)
ABSL_MUST_USE_RESULT bool SubstituteVariableInObjective(int var_in_equality, int64_t coeff_in_equality, const ConstraintProto &equality)
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64_t value)
ABSL_MUST_USE_RESULT bool CanonicalizeObjective(bool simplify_domain=true)
bool StoreBooleanEqualityRelation(int ref_a, int ref_b)
bool DomainOfVarIsIncludedIn(int var, const Domain &domain)
bool VariableWithCostIsUniqueAndRemovable(int ref) const
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
ABSL_MUST_USE_RESULT bool ScaleFloatingPointObjective()
const std::vector< int > & ConstraintToVars(int c) const
std::pair< int64_t, int64_t > ComputeMinMaxActivity(const ProtoWithVarsAndCoeffs &proto) const
int GetOrCreateVarValueEncoding(int ref, int64_t value)
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(const std::string &message="")
const absl::flat_hash_map< int, int64_t > & ObjectiveMap() const
bool HasVarValueEncoding(int ref, int64_t value, int *literal=nullptr)
bool DomainContains(int ref, int64_t value) const
bool ShiftCostInExactlyOne(absl::Span< const int > exactly_one, int64_t shift)
void UpdateRuleStats(const std::string &name, int num_times=1)
const SatParameters & params() const
AffineRelation::Relation GetAffineRelation(int ref) const
bool StoreAffineRelation(int ref_x, int ref_y, int64_t coeff, int64_t offset, bool debug_no_recursion=false)
const absl::flat_hash_set< int > & VarToConstraints(int var) const
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
int LiteralForExpressionMax(const LinearExpressionProto &expr) const
bool ExpressionIsAffineBoolean(const LinearExpressionProto &expr) const
bool ExploitExactlyOneInObjective(absl::Span< const int > exactly_one)
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
absl::flat_hash_set< int > tmp_literal_set
bool GetAbsRelation(int target_ref, int *ref)
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64_t value)
int64_t b
int64_t a
Block * next
CpModelProto proto
int interval_index
CpModelProto const * model_proto
WallTimer * wall_timer
ModelSharedTimeLimit * time_limit
const std::string name
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
GurobiMPCallbackContext * context
int index
int64_t hash
Definition: matrix_utils.cc:63
void Truncate(RepeatedPtrField< T > *array, int new_size)
Definition: protobuf_util.h:28
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
uint64_t FingerprintRepeatedField(const google::protobuf::RepeatedField< T > &sequence, uint64_t seed)
bool DetectAndExploitSymmetriesInPresolve(PresolveContext *context)
bool RefIsPositive(int ref)
int64_t ClosestMultiple(int64_t value, int64_t base)
Definition: sat/util.cc:228
const LiteralIndex kNoLiteralIndex(-1)
void GetOverlappingIntervalComponents(std::vector< IndexedInterval > *intervals, std::vector< std::vector< int >> *components)
Definition: diffn_util.cc:410
DiophantineSolution SolveDiophantine(absl::Span< const int64_t > coeffs, int64_t rhs, absl::Span< const int64_t > var_lbs, absl::Span< const int64_t > var_ubs)
Definition: diophantine.cc:117
IntType CeilOfRatio(IntType numerator, IntType denominator)
Definition: sat/util.h:428
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(const std::vector< Rectangle > &rectangles, absl::Span< int > active_rectangles)
Definition: diffn_util.cc:41
bool HasEnforcementLiteral(const ConstraintProto &ct)
bool ClauseIsEnforcementImpliesLiteral(absl::Span< const int > clause, absl::Span< const int > enforcement, int literal)
void ExpandCpModel(PresolveContext *context)
bool IsNegatableInt64(absl::int128 x)
Definition: sat/util.h:399
constexpr int kAffineRelationConstraint
void ApplyToAllLiteralIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
std::vector< std::pair< int, int > > FindDuplicateConstraints(const CpModelProto &model_proto, bool ignore_enforcement)
void DetectDominanceRelations(const PresolveContext &context, VarDomination *var_domination, DualBoundStrengthening *dual_bound_strengthening)
void ConstructOverlappingSets(bool already_sorted, std::vector< IndexedInterval > *intervals, std::vector< std::vector< int >> *result)
Definition: diffn_util.cc:361
bool LinearExpressionProtosAreEqual(const LinearExpressionProto &a, const LinearExpressionProto &b, int64_t b_scaling)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
void ApplyToAllIntervalIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
IntegerValue PositiveRemainder(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:113
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
Definition: sat/util.cc:164
void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(const CpModelProto &in_model, PresolveContext *context)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
int ReindexArcs(IntContainer *tails, IntContainer *heads, absl::flat_hash_map< int, int > *mapping_output=nullptr)
Definition: circuit.h:209
void FinalExpansionForLinearConstraint(PresolveContext *context)
int64_t FloorSquareRoot(int64_t a)
Definition: sat/util.cc:211
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void ApplyToAllVariableIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
int64_t SafeDoubleToInt64(double value)
Definition: sat/util.h:387
constexpr uint64_t kDefaultFingerprintSeed
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
InclusionDetector(const Storage &storage) -> InclusionDetector< Storage >
constexpr int kObjectiveConstraint
bool ImportModelWithBasicPresolveIntoContext(const CpModelProto &in_model, PresolveContext *context)
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
int GetSingleRefFromExpression(const LinearExpressionProto &expr)
bool ExpressionContainsSingleRef(const LinearExpressionProto &expr)
void PropagateAutomaton(const AutomatonConstraintProto &proto, const PresolveContext &context, std::vector< absl::flat_hash_set< int64_t >> *states, std::vector< absl::flat_hash_set< int64_t >> *labels)
void ApplyVariableMapping(const std::vector< int > &mapping, const PresolveContext &context)
bool LinearInequalityCanBeReducedWithClosestMultiple(int64_t base, const std::vector< int64_t > &coeffs, const std::vector< int64_t > &lbs, const std::vector< int64_t > &ubs, int64_t rhs, int64_t *new_rhs)
Definition: sat/util.cc:235
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
int64_t CapProd(int64_t x, int64_t y)
absl::StatusOr< std::vector< int > > FastTopologicalSort(const AdjacencyLists &adj)
Literal literal
Definition: optimization.cc:88
if(!yyg->yy_init)
Definition: parser.yy.cc:965
EntryIndex num_entries
ColIndex representative
int64_t demand
Definition: resource.cc:126
int64_t time
Definition: resource.cc:1694
IntervalVar * interval
Definition: resource.cc:101
int64_t tail
int64_t cost
int64_t head
Rev< int64_t > end_max
Rev< int64_t > start_min
std::optional< int64_t > end
int64_t start
const std::optional< Range > & range
Definition: statistics.cc:36
void FindStronglyConnectedComponents(const NodeIndex num_nodes, const Graph &graph, SccOutput *components)
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69
#define VLOG(verboselevel)
Definition: vlog.h:39
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:47