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