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