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