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