1 // Copyright 2010-2021 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 
14 #include "ortools/sat/cp_model_presolve.h"
15 
16 #include <sys/stat.h>
17 
18 #include <algorithm>
19 #include <cstdint>
20 #include <cstdlib>
21 #include <deque>
22 #include <limits>
23 #include <map>
24 #include <memory>
25 #include <numeric>
26 #include <set>
27 #include <string>
28 #include <utility>
29 #include <vector>
30 
31 #include "absl/base/attributes.h"
32 #include "absl/container/flat_hash_map.h"
33 #include "absl/container/flat_hash_set.h"
34 #include "absl/hash/hash.h"
35 #include "absl/random/random.h"
36 #include "absl/strings/str_join.h"
37 #include "ortools/base/integral_types.h"
38 #include "ortools/base/logging.h"
39 #include "ortools/base/map_util.h"
40 #include "ortools/base/mathutil.h"
41 #include "ortools/base/stl_util.h"
42 #include "ortools/port/proto_utils.h"
43 #include "ortools/sat/circuit.h"
44 #include "ortools/sat/cp_model.pb.h"
45 #include "ortools/sat/cp_model_checker.h"
46 #include "ortools/sat/cp_model_expand.h"
47 #include "ortools/sat/cp_model_loader.h"
48 #include "ortools/sat/cp_model_mapping.h"
49 #include "ortools/sat/cp_model_objective.h"
50 #include "ortools/sat/cp_model_symmetries.h"
51 #include "ortools/sat/cp_model_utils.h"
52 #include "ortools/sat/diffn_util.h"
53 #include "ortools/sat/presolve_util.h"
54 #include "ortools/sat/probing.h"
55 #include "ortools/sat/sat_base.h"
56 #include "ortools/sat/sat_parameters.pb.h"
57 #include "ortools/sat/simplification.h"
58 #include "ortools/sat/var_domination.h"
59 
60 namespace operations_research {
61 namespace sat {
62 
63 bool CpModelPresolver::RemoveConstraint(ConstraintProto* ct) {
64   ct->Clear();
65   return true;
66 }
67 
68 // Remove all empty constraints. Note that we need to remap the interval
69 // references.
70 //
71 // Now that they have served their purpose, we also remove dummy constraints,
72 // otherwise that causes issue because our model are invalid in tests.
73 void CpModelPresolver::RemoveEmptyConstraints() {
74   std::vector<int> interval_mapping(context_->working_model->constraints_size(),
75                                     -1);
76   int new_num_constraints = 0;
77   const int old_num_non_empty_constraints =
78       context_->working_model->constraints_size();
79   for (int c = 0; c < old_num_non_empty_constraints; ++c) {
80     const auto type = context_->working_model->constraints(c).constraint_case();
81     if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) continue;
82     if (type == ConstraintProto::ConstraintCase::kDummyConstraint) continue;
83     if (type == ConstraintProto::ConstraintCase::kInterval) {
84       interval_mapping[c] = new_num_constraints;
85     }
86     context_->working_model->mutable_constraints(new_num_constraints++)
87         ->Swap(context_->working_model->mutable_constraints(c));
88   }
89   context_->working_model->mutable_constraints()->DeleteSubrange(
90       new_num_constraints, old_num_non_empty_constraints - new_num_constraints);
91   for (ConstraintProto& ct_ref :
92        *context_->working_model->mutable_constraints()) {
93     ApplyToAllIntervalIndices(
94         [&interval_mapping](int* ref) {
95           *ref = interval_mapping[*ref];
96           CHECK_NE(-1, *ref);
97         },
98         &ct_ref);
99   }
100 }
101 
102 bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto* ct) {
103   if (context_->ModelIsUnsat()) return false;
104   if (!HasEnforcementLiteral(*ct)) return false;
105 
106   int new_size = 0;
107   const int old_size = ct->enforcement_literal().size();
108   for (const int literal : ct->enforcement_literal()) {
109     if (context_->LiteralIsTrue(literal)) {
110       // We can remove a literal at true.
111       context_->UpdateRuleStats("true enforcement literal");
112       continue;
113     }
114 
115     if (context_->LiteralIsFalse(literal)) {
116       context_->UpdateRuleStats("false enforcement literal");
117       return RemoveConstraint(ct);
118     }
119 
120     if (context_->VariableIsUniqueAndRemovable(literal)) {
121       // We can simply set it to false and ignore the constraint in this case.
122       context_->UpdateRuleStats("enforcement literal not used");
123       CHECK(context_->SetLiteralToFalse(literal));
124       return RemoveConstraint(ct);
125     }
126 
127     // If the literal only appear in the objective, we might be able to fix it
128     // to false. TODO(user): generalize if the literal always appear with the
129     // same polarity.
130     if (context_->VariableWithCostIsUniqueAndRemovable(literal)) {
131       const int64_t obj_coeff =
132           context_->ObjectiveMap().at(PositiveRef(literal));
133       if (RefIsPositive(literal) == (obj_coeff > 0)) {
134         // It is just more advantageous to set it to false!
135         context_->UpdateRuleStats("enforcement literal with unique direction");
136         CHECK(context_->SetLiteralToFalse(literal));
137         return RemoveConstraint(ct);
138       }
139     }
140 
141     ct->set_enforcement_literal(new_size++, literal);
142   }
143   ct->mutable_enforcement_literal()->Truncate(new_size);
144   return new_size != old_size;
145 }
146 
147 bool CpModelPresolver::PresolveBoolXor(ConstraintProto* ct) {
148   if (context_->ModelIsUnsat()) return false;
149   if (HasEnforcementLiteral(*ct)) return false;
150 
151   int new_size = 0;
152   bool changed = false;
153   int num_true_literals = 0;
154   int true_literal = std::numeric_limits<int32_t>::min();
155   for (const int literal : ct->bool_xor().literals()) {
156     // TODO(user): More generally, if a variable appear in only bool xor
157     // constraints, we can simply eliminate it using linear algebra on Z/2Z.
158     // This should solve in polynomial time the parity-learning*.fzn problems
159     // for instance. This seems low priority, but it is also easy to do. Even
160     // better would be to have a dedicated propagator with all bool_xor
161     // constraints that do the necessary linear algebra.
162     if (context_->VariableIsUniqueAndRemovable(literal)) {
163       context_->UpdateRuleStats("TODO bool_xor: remove constraint");
164     }
165 
166     if (context_->LiteralIsFalse(literal)) {
167       context_->UpdateRuleStats("bool_xor: remove false literal");
168       changed = true;
169       continue;
170     } else if (context_->LiteralIsTrue(literal)) {
171       true_literal = literal;  // Keep if we need to put one back.
172       num_true_literals++;
173       continue;
174     }
175 
176     ct->mutable_bool_xor()->set_literals(new_size++, literal);
177   }
178 
179   if (new_size == 0) {
180     if (num_true_literals % 2 == 0) {
181       return context_->NotifyThatModelIsUnsat("bool_xor: always false");
182     } else {
183       context_->UpdateRuleStats("bool_xor: always true");
184       return RemoveConstraint(ct);
185     }
186   } else if (new_size == 1) {  // We can fix the only active literal.
187     if (num_true_literals % 2 == 0) {
188       if (!context_->SetLiteralToTrue(ct->bool_xor().literals(0))) {
189         return context_->NotifyThatModelIsUnsat(
190             "bool_xor: cannot fix last literal");
191       }
192     } else {
193       if (!context_->SetLiteralToFalse(ct->bool_xor().literals(0))) {
194         return context_->NotifyThatModelIsUnsat(
195             "bool_xor: cannot fix last literal");
196       }
197     }
198     context_->UpdateRuleStats("bool_xor: one active literal");
199     return RemoveConstraint(ct);
200   } else if (new_size == 2) {  // We can simplify the bool_xor.
201     const int a = ct->bool_xor().literals(0);
202     const int b = ct->bool_xor().literals(1);
203     if (a == b) {
204       if (num_true_literals % 2 == 0) {
205         return context_->NotifyThatModelIsUnsat("bool_xor: always false");
206       } else {
207         context_->UpdateRuleStats("bool_xor: always true");
208         return RemoveConstraint(ct);
209       }
210     }
211     if (a == NegatedRef(b)) {
212       if (num_true_literals % 2 == 1) {
213         return context_->NotifyThatModelIsUnsat("bool_xor: always false");
214       } else {
215         context_->UpdateRuleStats("bool_xor: always true");
216         return RemoveConstraint(ct);
217       }
218     }
219     if (num_true_literals % 2 == 0) {  // a == not(b).
220       context_->StoreBooleanEqualityRelation(a, NegatedRef(b));
221     } else {  // a == b.
222       context_->StoreBooleanEqualityRelation(a, b);
223     }
224     context_->UpdateNewConstraintsVariableUsage();
225     context_->UpdateRuleStats("bool_xor: two active literals");
226     return RemoveConstraint(ct);
227   }
228 
229   if (num_true_literals % 2 == 1) {
230     CHECK_NE(true_literal, std::numeric_limits<int32_t>::min());
231     ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
232   }
233   if (num_true_literals > 1) {
234     context_->UpdateRuleStats("bool_xor: remove even number of true literals");
235     changed = true;
236   }
237   ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
238   return changed;
239 }
240 
241 bool CpModelPresolver::PresolveBoolOr(ConstraintProto* ct) {
242   if (context_->ModelIsUnsat()) return false;
243 
244   // Move the enforcement literal inside the clause if any. Note that we do not
245   // mark this as a change since the literal in the constraint are the same.
246   if (HasEnforcementLiteral(*ct)) {
247     context_->UpdateRuleStats("bool_or: removed enforcement literal");
248     for (const int literal : ct->enforcement_literal()) {
249       ct->mutable_bool_or()->add_literals(NegatedRef(literal));
250     }
251     ct->clear_enforcement_literal();
252   }
253 
254   // Inspects the literals and deal with fixed ones.
255   bool changed = false;
256   context_->tmp_literals.clear();
257   context_->tmp_literal_set.clear();
258   for (const int literal : ct->bool_or().literals()) {
259     if (context_->LiteralIsFalse(literal)) {
260       changed = true;
261       continue;
262     }
263     if (context_->LiteralIsTrue(literal)) {
264       context_->UpdateRuleStats("bool_or: always true");
265       return RemoveConstraint(ct);
266     }
267     // We can just set the variable to true in this case since it is not
268     // used in any other constraint (note that we artificially bump the
269     // objective var usage by 1).
270     if (context_->VariableIsUniqueAndRemovable(literal)) {
271       context_->UpdateRuleStats("bool_or: singleton");
272       if (!context_->SetLiteralToTrue(literal)) return true;
273       return RemoveConstraint(ct);
274     }
275     if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
276       context_->UpdateRuleStats("bool_or: always true");
277       return RemoveConstraint(ct);
278     }
279 
280     if (context_->tmp_literal_set.contains(literal)) {
281       changed = true;
282     } else {
283       context_->tmp_literal_set.insert(literal);
284       context_->tmp_literals.push_back(literal);
285     }
286   }
287   context_->tmp_literal_set.clear();
288 
289   if (context_->tmp_literals.empty()) {
290     context_->UpdateRuleStats("bool_or: empty");
291     return context_->NotifyThatModelIsUnsat();
292   }
293   if (context_->tmp_literals.size() == 1) {
294     context_->UpdateRuleStats("bool_or: only one literal");
295     if (!context_->SetLiteralToTrue(context_->tmp_literals[0])) return true;
296     return RemoveConstraint(ct);
297   }
298   if (context_->tmp_literals.size() == 2) {
299     // For consistency, we move all "implication" into half-reified bool_and.
300     // TODO(user): merge by enforcement literal and detect implication cycles.
301     context_->UpdateRuleStats("bool_or: implications");
302     ct->add_enforcement_literal(NegatedRef(context_->tmp_literals[0]));
303     ct->mutable_bool_and()->add_literals(context_->tmp_literals[1]);
304     return changed;
305   }
306 
307   if (changed) {
308     context_->UpdateRuleStats("bool_or: fixed literals");
309     ct->mutable_bool_or()->mutable_literals()->Clear();
310     for (const int lit : context_->tmp_literals) {
311       ct->mutable_bool_or()->add_literals(lit);
312     }
313   }
314   return changed;
315 }
316 
317 // Note this constraint does not update the constraint graph. Therefore, it
318 // assumes that the constraint being marked as false is the constraint being
319 // presolved.
320 ABSL_MUST_USE_RESULT bool CpModelPresolver::MarkConstraintAsFalse(
321     ConstraintProto* ct) {
322   if (HasEnforcementLiteral(*ct)) {
323     // Change the constraint to a bool_or.
324     ct->mutable_bool_or()->clear_literals();
325     for (const int lit : ct->enforcement_literal()) {
326       ct->mutable_bool_or()->add_literals(NegatedRef(lit));
327     }
328     ct->clear_enforcement_literal();
329     PresolveBoolOr(ct);
330     return true;
331   } else {
332     return context_->NotifyThatModelIsUnsat();
333   }
334 }
335 
336 bool CpModelPresolver::PresolveBoolAnd(ConstraintProto* ct) {
337   if (context_->ModelIsUnsat()) return false;
338 
339   if (!HasEnforcementLiteral(*ct)) {
340     context_->UpdateRuleStats("bool_and: non-reified.");
341     for (const int literal : ct->bool_and().literals()) {
342       if (!context_->SetLiteralToTrue(literal)) return true;
343     }
344     return RemoveConstraint(ct);
345   }
346 
347   bool changed = false;
348   context_->tmp_literals.clear();
349   for (const int literal : ct->bool_and().literals()) {
350     if (context_->LiteralIsFalse(literal)) {
351       context_->UpdateRuleStats("bool_and: always false");
352       return MarkConstraintAsFalse(ct);
353     }
354     if (context_->LiteralIsTrue(literal)) {
355       changed = true;
356       continue;
357     }
358     if (context_->VariableIsUniqueAndRemovable(literal)) {
359       changed = true;
360       if (!context_->SetLiteralToTrue(literal)) return true;
361       continue;
362     }
363     context_->tmp_literals.push_back(literal);
364   }
365 
366   // Note that this is not the same behavior as a bool_or:
367   // - bool_or means "at least one", so it is false if empty.
368   // - bool_and means "all literals inside true", so it is true if empty.
369   if (context_->tmp_literals.empty()) return RemoveConstraint(ct);
370 
371   if (changed) {
372     ct->mutable_bool_and()->mutable_literals()->Clear();
373     for (const int lit : context_->tmp_literals) {
374       ct->mutable_bool_and()->add_literals(lit);
375     }
376     context_->UpdateRuleStats("bool_and: fixed literals");
377   }
378 
379   // If a variable can move freely in one direction except for this constraint,
380   // we can make it an equality.
381   //
382   // TODO(user): also consider literal on the other side of the =>.
383   if (ct->enforcement_literal().size() == 1 &&
384       ct->bool_and().literals().size() == 1) {
385     const int enforcement = ct->enforcement_literal(0);
386     if (context_->VariableWithCostIsUniqueAndRemovable(enforcement)) {
387       int var = PositiveRef(enforcement);
388       int64_t obj_coeff = context_->ObjectiveMap().at(var);
389       if (!RefIsPositive(enforcement)) obj_coeff = -obj_coeff;
390 
391       // The other case where the constraint is redundant is treated elsewhere.
392       if (obj_coeff < 0) {
393         context_->UpdateRuleStats("bool_and: dual equality.");
394         context_->StoreBooleanEqualityRelation(enforcement,
395                                                ct->bool_and().literals(0));
396       }
397     }
398   }
399 
400   return changed;
401 }
402 
403 bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto* ct) {
404   bool is_at_most_one = ct->constraint_case() == ConstraintProto::kAtMostOne;
405   const std::string name = is_at_most_one ? "at_most_one: " : "exactly_one: ";
406   auto* literals = is_at_most_one
407                        ? ct->mutable_at_most_one()->mutable_literals()
408                        : ct->mutable_exactly_one()->mutable_literals();
409 
410   // Deal with duplicate variable reference.
411   context_->tmp_literal_set.clear();
412   for (const int literal : *literals) {
413     if (context_->tmp_literal_set.contains(literal)) {
414       if (!context_->SetLiteralToFalse(literal)) return false;
415       context_->UpdateRuleStats(absl::StrCat(name, "duplicate literals"));
416     }
417     if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
418       int num_positive = 0;
419       int num_negative = 0;
420       for (const int other : *literals) {
421         if (PositiveRef(other) != PositiveRef(literal)) {
422           if (!context_->SetLiteralToFalse(other)) return false;
423           context_->UpdateRuleStats(absl::StrCat(name, "x and not(x)"));
424         } else {
425           if (other == literal) {
426             ++num_positive;
427           } else {
428             ++num_negative;
429           }
430         }
431       }
432 
433       // This is tricky for the case where the at most one reduce to (lit,
434       // not(lit), not(lit)) for instance.
435       if (num_positive > 1 && !context_->SetLiteralToFalse(literal)) {
436         return false;
437       }
438       if (num_negative > 1 && !context_->SetLiteralToTrue(literal)) {
439         return false;
440       }
441       return RemoveConstraint(ct);
442     }
443     context_->tmp_literal_set.insert(literal);
444   }
445 
446   // Remove fixed variables.
447   bool changed = false;
448   bool transform_to_at_most_one = false;
449   context_->tmp_literals.clear();
450   for (const int literal : *literals) {
451     if (context_->LiteralIsTrue(literal)) {
452       context_->UpdateRuleStats(absl::StrCat(name, "satisfied"));
453       for (const int other : *literals) {
454         if (other != literal) {
455           if (!context_->SetLiteralToFalse(other)) return false;
456         }
457       }
458       return RemoveConstraint(ct);
459     }
460 
461     if (context_->LiteralIsFalse(literal)) {
462       changed = true;
463       continue;
464     }
465 
466     // A singleton variable in an at most one can just be set to zero.
467     //
468     // In an exactly one, it can be left to the postsolve to decide, and the
469     // rest of the constraint can be transformed to an at most one.
470     bool is_removable = context_->VariableIsUniqueAndRemovable(literal);
471     if (is_at_most_one && !is_removable &&
472         context_->VariableWithCostIsUniqueAndRemovable(literal)) {
473       const auto it = context_->ObjectiveMap().find(PositiveRef(literal));
474       CHECK(it != context_->ObjectiveMap().end());
475       const int64_t coeff = it->second;
476 
477       // Fixing it to zero need to go in the correct direction.
478       is_removable = (coeff > 0) == RefIsPositive(literal);
479     }
480 
481     if (is_removable) {
482       if (is_at_most_one) {
483         context_->UpdateRuleStats("at_most_one: singleton");
484         if (!context_->SetLiteralToFalse(literal)) return false;
485         changed = true;
486         continue;
487       } else {
488         changed = true;
489         is_at_most_one = true;
490         transform_to_at_most_one = true;
491         *(context_->mapping_model->add_constraints()) = *ct;
492         context_->UpdateRuleStats("exactly_one: singleton");
493         context_->MarkVariableAsRemoved(PositiveRef(literal));
494         continue;
495       }
496     }
497 
498     context_->tmp_literals.push_back(literal);
499   }
500 
501   if (!is_at_most_one && !transform_to_at_most_one &&
502       context_->ExploitExactlyOneInObjective(context_->tmp_literals)) {
503     context_->UpdateRuleStats("exactly_one: simplified objective");
504   }
505 
506   if (transform_to_at_most_one) {
507     CHECK(changed);
508     ct->Clear();
509     literals = ct->mutable_at_most_one()->mutable_literals();
510   }
511   if (changed) {
512     literals->Clear();
513     for (const int lit : context_->tmp_literals) {
514       literals->Add(lit);
515     }
516     context_->UpdateRuleStats(absl::StrCat(name, "removed literals"));
517   }
518   return changed;
519 }
520 
521 bool CpModelPresolver::PresolveAtMostOne(ConstraintProto* ct) {
522   if (context_->ModelIsUnsat()) return false;
523 
524   CHECK(!HasEnforcementLiteral(*ct));
525   const bool changed = PresolveAtMostOrExactlyOne(ct);
526   if (ct->constraint_case() != ConstraintProto::kAtMostOne) return changed;
527 
528   // Size zero: ok.
529   const auto& literals = ct->at_most_one().literals();
530   if (literals.empty()) {
531     context_->UpdateRuleStats("at_most_one: empty or all false");
532     return RemoveConstraint(ct);
533   }
534 
535   // Size one: always satisfied.
536   if (literals.size() == 1) {
537     context_->UpdateRuleStats("at_most_one: size one");
538     return RemoveConstraint(ct);
539   }
540 
541   return changed;
542 }
543 
544 bool CpModelPresolver::PresolveExactlyOne(ConstraintProto* ct) {
545   if (context_->ModelIsUnsat()) return false;
546   CHECK(!HasEnforcementLiteral(*ct));
547   const bool changed = PresolveAtMostOrExactlyOne(ct);
548   if (ct->constraint_case() != ConstraintProto::kExactlyOne) return changed;
549 
550   // Size zero: UNSAT.
551   const auto& literals = ct->exactly_one().literals();
552   if (literals.empty()) {
553     return context_->NotifyThatModelIsUnsat("exactly_one: empty or all false");
554   }
555 
556   // Size one: fix variable.
557   if (literals.size() == 1) {
558     context_->UpdateRuleStats("exactly_one: size one");
559     if (!context_->SetLiteralToTrue(literals[0])) return false;
560     return RemoveConstraint(ct);
561   }
562 
563   // Size two: Equivalence.
564   if (literals.size() == 2) {
565     context_->UpdateRuleStats("exactly_one: size two");
566     context_->StoreBooleanEqualityRelation(literals[0],
567                                            NegatedRef(literals[1]));
568     return RemoveConstraint(ct);
569   }
570 
571   return changed;
572 }
573 
574 bool CpModelPresolver::CanonicalizeLinMax(ConstraintProto* ct) {
575   if (context_->ModelIsUnsat()) return false;
576 
577   // Canonicalize all involved expression.
578   //
579   // TODO(user): If we start to have many constraints like this, we should
580   // use reflexion (see cp_model_util) to do that generically.
581   bool changed = CanonicalizeLinearExpression(
582       *ct, ct->mutable_lin_max()->mutable_target());
583   for (LinearExpressionProto& exp : *(ct->mutable_lin_max()->mutable_exprs())) {
584     changed |= CanonicalizeLinearExpression(*ct, &exp);
585   }
586   return changed;
587 }
588 
589 bool CpModelPresolver::PresolveLinMax(ConstraintProto* ct) {
590   if (context_->ModelIsUnsat()) return false;
591 
592   // Compute the infered min/max of the target.
593   // Update target domain (if it is not a complex expression).
594   const LinearExpressionProto& target = ct->lin_max().target();
595   {
596     int64_t infered_min = context_->MinOf(target);
597     int64_t infered_max = std::numeric_limits<int64_t>::min();
598     for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
599       infered_min = std::max(infered_min, context_->MinOf(expr));
600       infered_max = std::max(infered_max, context_->MaxOf(expr));
601     }
602 
603     if (target.vars().empty()) {
604       if (!Domain(infered_min, infered_max).Contains(target.offset())) {
605         context_->UpdateRuleStats("lin_max: infeasible");
606         return MarkConstraintAsFalse(ct);
607       }
608     }
609     if (!HasEnforcementLiteral(*ct) && target.vars().size() <= 1) {  // Affine
610       Domain rhs_domain;
611       for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
612         rhs_domain = rhs_domain.UnionWith(
613             context_->DomainSuperSetOf(expr).IntersectionWith(
614                 {infered_min, infered_max}));
615       }
616       bool reduced = false;
617       if (!context_->IntersectDomainWith(target, rhs_domain, &reduced)) {
618         return true;
619       }
620       if (reduced) {
621         context_->UpdateRuleStats("lin_max: target domain reduced");
622       }
623     }
624   }
625 
626   // Filter the expressions which are smaller than target_min.
627   const int64_t target_min = context_->MinOf(target);
628   const int64_t target_max = context_->MaxOf(target);
629   bool changed = false;
630   {
631     int new_size = 0;
632     for (int i = 0; i < ct->lin_max().exprs_size(); ++i) {
633       const LinearExpressionProto& expr = ct->lin_max().exprs(i);
634       if (context_->MaxOf(expr) < target_min) continue;
635       *ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
636       new_size++;
637     }
638     if (new_size < ct->lin_max().exprs_size()) {
639       context_->UpdateRuleStats("lin_max: removed exprs");
640       ct->mutable_lin_max()->mutable_exprs()->DeleteSubrange(
641           new_size, ct->lin_max().exprs_size() - new_size);
642       changed = true;
643     }
644   }
645 
646   if (ct->lin_max().exprs().empty()) {
647     context_->UpdateRuleStats("lin_max: no exprs");
648     return MarkConstraintAsFalse(ct);
649   }
650 
651   if (ct->lin_max().exprs().size() == 1) {
652     // Convert to an equality. Note that we create a new constraint otherwise it
653     // might not be processed again.
654     context_->UpdateRuleStats("lin_max: converted to equality");
655     ConstraintProto* new_ct = context_->working_model->add_constraints();
656     *new_ct = *ct;  // copy name and potential reification.
657     auto* arg = new_ct->mutable_linear();
658     const LinearExpressionProto& a = ct->lin_max().target();
659     const LinearExpressionProto& b = ct->lin_max().exprs(0);
660     for (int i = 0; i < a.vars().size(); ++i) {
661       arg->add_vars(a.vars(i));
662       arg->add_coeffs(a.coeffs(i));
663     }
664     for (int i = 0; i < b.vars().size(); ++i) {
665       arg->add_vars(b.vars(i));
666       arg->add_coeffs(-b.coeffs(i));
667     }
668     arg->add_domain(b.offset() - a.offset());
669     arg->add_domain(b.offset() - a.offset());
670     context_->UpdateNewConstraintsVariableUsage();
671     return RemoveConstraint(ct);
672   }
673 
674   // Cut everything above the max if possible.
675   // If one of the linear expression has many term and is above the max, we
676   // abort early since none of the other rule can be applied.
677   {
678     bool abort = false;
679     for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
680       const int64_t value_min = context_->MinOf(expr);
681       bool modified = false;
682       if (!context_->IntersectDomainWith(expr, Domain(value_min, target_max),
683                                          &modified)) {
684         return true;
685       }
686       if (modified) {
687         context_->UpdateRuleStats("lin_max: reduced expression domain.");
688       }
689       const int64_t value_max = context_->MaxOf(expr);
690       if (value_max > target_max) {
691         context_->UpdateRuleStats("TODO lin_max: linear expression above max.");
692         abort = true;
693       }
694     }
695     if (abort) return changed;
696   }
697 
698   // Deal with fixed target case.
699   if (target_min == target_max) {
700     bool all_booleans = true;
701     std::vector<int> literals;
702     const int64_t fixed_target = target_min;
703     for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
704       const int64_t value_min = context_->MinOf(expr);
705       const int64_t value_max = context_->MaxOf(expr);
706       CHECK_LE(value_max, fixed_target) << "Presolved above";
707       if (value_max < fixed_target) continue;
708 
709       if (value_min == value_max && value_max == fixed_target) {
710         context_->UpdateRuleStats("lin_max: always satisfied");
711         return RemoveConstraint(ct);
712       }
713       if (context_->ExpressionIsAffineBoolean(expr)) {
714         CHECK_EQ(value_max, fixed_target);
715         literals.push_back(context_->LiteralForExpressionMax(expr));
716       } else {
717         all_booleans = false;
718       }
719     }
720     if (all_booleans) {
721       if (literals.empty()) {
722         return MarkConstraintAsFalse(ct);
723       }
724 
725       // At least one true;
726       context_->UpdateRuleStats("lin_max: fixed target and all booleans");
727       for (const int lit : literals) {
728         ct->mutable_bool_or()->add_literals(lit);
729       }
730       return true;
731     }
732     return changed;
733   }
734 
735   // If everything is Boolean and affine, do not use a lin max!
736   if (context_->ExpressionIsAffineBoolean(target)) {
737     const int target_ref = context_->LiteralForExpressionMax(target);
738 
739     bool abort = false;
740 
741     bool min_is_reachable = false;
742     std::vector<int> min_literals;
743     std::vector<int> literals_above_min;
744     std::vector<int> max_literals;
745 
746     for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
747       const int64_t value_min = context_->MinOf(expr);
748       const int64_t value_max = context_->MaxOf(expr);
749 
750       // This shouldn't happen, but it document the fact.
751       if (value_min > target_min) {
752         context_->UpdateRuleStats("lin_max: fix target");
753         if (!context_->SetLiteralToTrue(target_ref)) return true;
754         abort = true;
755         break;
756       }
757 
758       // expr is fixed.
759       if (value_min == value_max) {
760         if (value_min == target_min) min_is_reachable = true;
761         continue;
762       }
763 
764       if (!context_->ExpressionIsAffineBoolean(expr)) {
765         abort = true;
766         break;
767       }
768 
769       const int ref = context_->LiteralForExpressionMax(expr);
770       CHECK_LE(value_min, target_min);
771       if (value_min == target_min) {
772         min_literals.push_back(NegatedRef(ref));
773       }
774 
775       CHECK_LE(value_max, target_max);
776       if (value_max == target_max) {
777         max_literals.push_back(ref);
778         literals_above_min.push_back(ref);
779       } else if (value_max > target_min) {
780         literals_above_min.push_back(ref);
781       } else if (value_max == target_min) {
782         min_literals.push_back(ref);
783       }
784     }
785     if (!abort) {
786       context_->UpdateRuleStats("lin_max: all Booleans.");
787 
788       // target_ref => at_least_one(max_literals);
789       ConstraintProto* clause = context_->working_model->add_constraints();
790       clause->add_enforcement_literal(target_ref);
791       clause->mutable_bool_or();
792       for (const int lit : max_literals) {
793         clause->mutable_bool_or()->add_literals(lit);
794       }
795 
796       // not(target_ref) => not(lit) for lit in literals_above_min
797       for (const int lit : literals_above_min) {
798         context_->AddImplication(lit, target_ref);
799       }
800 
801       if (!min_is_reachable) {
802         // not(target_ref) => at_least_one(min_literals).
803         ConstraintProto* clause = context_->working_model->add_constraints();
804         clause->add_enforcement_literal(NegatedRef(target_ref));
805         clause->mutable_bool_or();
806         for (const int lit : min_literals) {
807           clause->mutable_bool_or()->add_literals(lit);
808         }
809       }
810 
811       context_->UpdateNewConstraintsVariableUsage();
812       return RemoveConstraint(ct);
813     }
814   }
815 
816   return changed;
817 }
818 
819 // This presolve expect that the constraint only contains affine expressions.
820 bool CpModelPresolver::PresolveIntAbs(ConstraintProto* ct) {
821   CHECK_EQ(ct->enforcement_literal_size(), 0);
822   if (context_->ModelIsUnsat()) return false;
823   const LinearExpressionProto& target_expr = ct->lin_max().target();
824   const LinearExpressionProto& expr = ct->lin_max().exprs(0);
825   DCHECK_EQ(expr.vars_size(), 1);
826 
827   // Propagate domain from the expression to the target.
828   {
829     const Domain expr_domain = context_->DomainSuperSetOf(expr);
830     const Domain new_target_domain =
831         expr_domain.UnionWith(expr_domain.Negation())
832             .IntersectionWith({0, std::numeric_limits<int64_t>::max()});
833     bool target_domain_modified = false;
834     if (!context_->IntersectDomainWith(target_expr, new_target_domain,
835                                        &target_domain_modified)) {
836       return false;
837     }
838     if (expr_domain.IsFixed()) {
839       context_->UpdateRuleStats("int_abs: fixed expression");
840       return RemoveConstraint(ct);
841     }
842     if (target_domain_modified) {
843       context_->UpdateRuleStats("int_abs: propagate domain from x to abs(x)");
844     }
845   }
846 
847   // Propagate from target domain to variable.
848   {
849     const Domain target_domain =
850         context_->DomainSuperSetOf(target_expr)
851             .IntersectionWith(Domain(0, std::numeric_limits<int64_t>::max()));
852     const Domain new_expr_domain =
853         target_domain.UnionWith(target_domain.Negation());
854     bool expr_domain_modified = false;
855     if (!context_->IntersectDomainWith(expr, new_expr_domain,
856                                        &expr_domain_modified)) {
857       return true;
858     }
859     // This is the only reason why we don't support fully generic linear
860     // expression.
861     if (context_->IsFixed(target_expr)) {
862       context_->UpdateRuleStats("int_abs: fixed target");
863       return RemoveConstraint(ct);
864     }
865     if (expr_domain_modified) {
866       context_->UpdateRuleStats("int_abs: propagate domain from abs(x) to x");
867     }
868   }
869 
870   // Convert to equality if the sign of expr is fixed.
871   if (context_->MinOf(expr) >= 0) {
872     context_->UpdateRuleStats("int_abs: converted to equality");
873     ConstraintProto* new_ct = context_->working_model->add_constraints();
874     new_ct->set_name(ct->name());
875     auto* arg = new_ct->mutable_linear();
876     arg->add_domain(0);
877     arg->add_domain(0);
878     AddLinearExpressionToLinearConstraint(target_expr, 1, arg);
879     AddLinearExpressionToLinearConstraint(expr, -1, arg);
880     if (!CanonicalizeLinear(new_ct)) return false;
881     context_->UpdateNewConstraintsVariableUsage();
882     return RemoveConstraint(ct);
883   }
884 
885   if (context_->MaxOf(expr) <= 0) {
886     context_->UpdateRuleStats("int_abs: converted to equality");
887     ConstraintProto* new_ct = context_->working_model->add_constraints();
888     new_ct->set_name(ct->name());
889     auto* arg = new_ct->mutable_linear();
890     arg->add_domain(0);
891     arg->add_domain(0);
892     AddLinearExpressionToLinearConstraint(target_expr, 1, arg);
893     AddLinearExpressionToLinearConstraint(expr, 1, arg);
894     if (!CanonicalizeLinear(new_ct)) return false;
895     context_->UpdateNewConstraintsVariableUsage();
896     return RemoveConstraint(ct);
897   }
898 
899   // Remove the abs constraint if the target is removable and if domains have
900   // been propagated without loss.
901   // For now, we known that there is no loss if the target is a single ref.
902   // Since all the expression are affine, in this case we are fine.
903   if (ExpressionContainsSingleRef(target_expr) &&
904       context_->VariableIsUniqueAndRemovable(target_expr.vars(0))) {
905     context_->MarkVariableAsRemoved(target_expr.vars(0));
906     *context_->mapping_model->add_constraints() = *ct;
907     context_->UpdateRuleStats("int_abs: unused target");
908     return RemoveConstraint(ct);
909   }
910 
911   // Store the x == abs(y) relation if expr and target_expr can be cast into a
912   // ref.
913   // TODO(user): Support general affine expression in for expr in the Store
914   //                method call.
915   {
916     if (ExpressionContainsSingleRef(target_expr) &&
917         ExpressionContainsSingleRef(expr)) {
918       const int target_ref = GetSingleRefFromExpression(target_expr);
919       const int expr_ref = GetSingleRefFromExpression(expr);
920       if (context_->StoreAbsRelation(target_ref, expr_ref)) {
921         context_->UpdateRuleStats("int_abs: store abs(x) == y");
922       }
923     }
924   }
925 
926   return false;
927 }
928 
929 bool CpModelPresolver::PresolveIntProd(ConstraintProto* ct) {
930   if (context_->ModelIsUnsat()) return false;
931   if (HasEnforcementLiteral(*ct)) return false;
932 
933   LinearArgumentProto* proto = ct->mutable_int_prod();
934 
935   bool changed = CanonicalizeLinearExpression(*ct, proto->mutable_target());
936   for (LinearExpressionProto& exp : *(proto->mutable_exprs())) {
937     changed |= CanonicalizeLinearExpression(*ct, &exp);
938   }
939 
940   // Remove constant expressions.
941   int64_t constant_factor = 1;
942   int new_size = 0;
943   for (int i = 0; i < ct->int_prod().exprs().size(); ++i) {
944     LinearExpressionProto expr = ct->int_prod().exprs(i);
945     if (context_->IsFixed(expr)) {
946       context_->UpdateRuleStats("int_prod: removed constant expressions.");
947       changed = true;
948       constant_factor = CapProd(constant_factor, context_->FixedValue(expr));
949       continue;
950     } else {
951       const int64_t coeff = expr.coeffs(0);
952       const int64_t offset = expr.offset();
953       const int64_t gcd =
954           MathUtil::GCD64(static_cast<uint64_t>(std::abs(coeff)),
955                           static_cast<uint64_t>(std::abs(offset)));
956       if (gcd != 1) {
957         constant_factor = CapProd(constant_factor, gcd);
958         expr.set_coeffs(0, coeff / gcd);
959         expr.set_offset(offset / gcd);
960       }
961     }
962     *proto->mutable_exprs(new_size++) = expr;
963   }
964   proto->mutable_exprs()->erase(proto->mutable_exprs()->begin() + new_size,
965                                 proto->mutable_exprs()->end());
966 
967   if (ct->int_prod().exprs().empty()) {
968     if (!context_->IntersectDomainWith(ct->int_prod().target(),
969                                        Domain(constant_factor))) {
970       return false;
971     }
972     context_->UpdateRuleStats("int_prod: constant product");
973     return RemoveConstraint(ct);
974   }
975 
976   if (constant_factor == 0) {
977     context_->UpdateRuleStats("int_prod: multiplication by zero");
978     if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(0))) {
979       return false;
980     }
981     return RemoveConstraint(ct);
982   }
983 
984   // In this case, the only possible value that fit in the domains is zero.
985   // We will check for UNSAT if zero is not achievable by the rhs below.
986   if (constant_factor == std::numeric_limits<int64_t>::min() ||
987       constant_factor == std::numeric_limits<int64_t>::max()) {
988     context_->UpdateRuleStats("int_prod: overflow if non zero");
989     if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(0))) {
990       return false;
991     }
992     constant_factor = 1;
993   }
994 
995   // Replace by linear!
996   if (ct->int_prod().exprs().size() == 1) {
997     LinearConstraintProto* const lin =
998         context_->working_model->add_constraints()->mutable_linear();
999     lin->add_domain(0);
1000     lin->add_domain(0);
1001     AddLinearExpressionToLinearConstraint(ct->int_prod().target(), 1, lin);
1002     AddLinearExpressionToLinearConstraint(ct->int_prod().exprs(0),
1003                                           -constant_factor, lin);
1004     context_->UpdateNewConstraintsVariableUsage();
1005     context_->UpdateRuleStats("int_prod: linearize product by constant.");
1006     return RemoveConstraint(ct);
1007   }
1008 
1009   if (constant_factor != 1) {
1010     // Lets canonicalize the target by introducing a new variable if necessary.
1011     //
1012     // coeff * X + offset must be a multiple of constant_factor, so
1013     // we can rewrite X so that this property is clear.
1014     const LinearExpressionProto old_target = ct->int_prod().target();
1015     if (!context_->IsFixed(old_target)) {
1016       const int ref = old_target.vars(0);
1017       const int64_t coeff = old_target.coeffs(0);
1018       const int64_t offset = old_target.offset();
1019       if (!context_->CanonicalizeAffineVariable(ref, coeff, constant_factor,
1020                                                 -offset)) {
1021         return false;
1022       }
1023       if (context_->IsFixed(ref)) {
1024         changed = true;
1025       }
1026     }
1027 
1028     // This can happen during CanonicalizeAffineVariable().
1029     if (context_->IsFixed(old_target)) {
1030       const int64_t target_value = context_->FixedValue(old_target);
1031       if (target_value % constant_factor != 0) {
1032         return context_->NotifyThatModelIsUnsat(
1033             "int_prod: constant factor does not divide constant target");
1034       }
1035       proto->clear_target();
1036       proto->mutable_target()->set_offset(target_value / constant_factor);
1037       context_->UpdateRuleStats(
1038           "int_prod: divide product and fixed target by constant factor");
1039     } else {
1040       // We use absl::int128 to be resistant to overflow here.
1041       const AffineRelation::Relation r =
1042           context_->GetAffineRelation(old_target.vars(0));
1043       const absl::int128 temp_coeff =
1044           absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff);
1045       CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0);
1046       const absl::int128 temp_offset =
1047           absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) +
1048           absl::int128(old_target.offset());
1049       CHECK_EQ(temp_offset % absl::int128(constant_factor), 0);
1050       const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor);
1051       const absl::int128 new_offset =
1052           temp_offset / absl::int128(constant_factor);
1053 
1054       // TODO(user): We try to keep coeff/offset small, if this happens, it
1055       // probably means there is no feasible solution involving int64_t and that
1056       // do not causes overflow while evaluating it, but it is hard to be
1057       // exactly sure we are correct here since it depends on the evaluation
1058       // order. Similarly, by introducing intermediate variable we might loose
1059       // solution if this intermediate variable value do not fit on an int64_t.
1060       if (new_coeff > absl::int128(std::numeric_limits<int64_t>::max()) ||
1061           new_coeff < absl::int128(std::numeric_limits<int64_t>::min()) ||
1062           new_offset > absl::int128(std::numeric_limits<int64_t>::max()) ||
1063           new_offset < absl::int128(std::numeric_limits<int64_t>::min())) {
1064         return context_->NotifyThatModelIsUnsat(
1065             "int_prod: overflow during simplification.");
1066       }
1067 
1068       // Rewrite the target.
1069       proto->mutable_target()->set_coeffs(0, static_cast<int64_t>(new_coeff));
1070       proto->mutable_target()->set_vars(0, r.representative);
1071       proto->mutable_target()->set_offset(static_cast<int64_t>(new_offset));
1072       context_->UpdateRuleStats("int_prod: divide product by constant factor");
1073       changed = true;
1074     }
1075   }
1076 
1077   // Restrict the target domain if possible.
1078   Domain implied(1);
1079   for (const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1080     implied =
1081         implied.ContinuousMultiplicationBy(context_->DomainSuperSetOf(expr));
1082   }
1083   bool domain_modified = false;
1084   if (!context_->IntersectDomainWith(ct->int_prod().target(), implied,
1085                                      &domain_modified)) {
1086     return false;
1087   }
1088   if (domain_modified) {
1089     context_->UpdateRuleStats("int_prod: reduced target domain.");
1090   }
1091 
1092   if (ct->int_prod().exprs_size() == 2) {
1093     LinearExpressionProto a = ct->int_prod().exprs(0);
1094     LinearExpressionProto b = ct->int_prod().exprs(1);
1095     const LinearExpressionProto product = ct->int_prod().target();
1096     if (LinearExpressionProtosAreEqual(a, b) &&
1097         LinearExpressionProtosAreEqual(
1098             a, product)) {  // x = x * x, only true for {0, 1}.
1099       if (!context_->IntersectDomainWith(product, Domain(0, 1))) {
1100         return false;
1101       }
1102       context_->UpdateRuleStats("int_prod: fix variable to zero or one.");
1103       return RemoveConstraint(ct);
1104     }
1105   }
1106 
1107   // For now, we only presolve the case where all variables are Booleans.
1108   const LinearExpressionProto target_expr = ct->int_prod().target();
1109   int target;
1110   if (!context_->ExpressionIsALiteral(target_expr, &target)) {
1111     return changed;
1112   }
1113   std::vector<int> literals;
1114   for (const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1115     int lit;
1116     if (!context_->ExpressionIsALiteral(expr, &lit)) {
1117       return changed;
1118     }
1119     literals.push_back(lit);
1120   }
1121 
1122   // This is a bool constraint!
1123   context_->UpdateRuleStats("int_prod: all Boolean.");
1124   {
1125     ConstraintProto* new_ct = context_->working_model->add_constraints();
1126     new_ct->add_enforcement_literal(target);
1127     auto* arg = new_ct->mutable_bool_and();
1128     for (const int lit : literals) {
1129       arg->add_literals(lit);
1130     }
1131   }
1132   {
1133     ConstraintProto* new_ct = context_->working_model->add_constraints();
1134     auto* arg = new_ct->mutable_bool_or();
1135     arg->add_literals(target);
1136     for (const int lit : literals) {
1137       arg->add_literals(NegatedRef(lit));
1138     }
1139   }
1140   context_->UpdateNewConstraintsVariableUsage();
1141   return RemoveConstraint(ct);
1142 }
1143 
1144 bool CpModelPresolver::PresolveIntDiv(ConstraintProto* ct) {
1145   if (context_->ModelIsUnsat()) return false;
1146 
1147   bool changed = CanonicalizeLinearExpression(
1148       *ct, ct->mutable_int_div()->mutable_target());
1149   for (LinearExpressionProto& exp : *(ct->mutable_int_div()->mutable_exprs())) {
1150     changed |= CanonicalizeLinearExpression(*ct, &exp);
1151   }
1152 
1153   const LinearExpressionProto target = ct->int_div().target();
1154   const LinearExpressionProto expr = ct->int_div().exprs(0);
1155   const LinearExpressionProto div = ct->int_div().exprs(1);
1156 
1157   if (LinearExpressionProtosAreEqual(expr, div)) {
1158     if (!context_->IntersectDomainWith(target, Domain(1))) {
1159       return false;
1160     }
1161     context_->UpdateRuleStats("int_div: y = x / x");
1162     return RemoveConstraint(ct);
1163   } else if (LinearExpressionProtosAreEqual(expr, div, -1)) {
1164     if (!context_->IntersectDomainWith(target, Domain(-1))) {
1165       return false;
1166     }
1167     context_->UpdateRuleStats("int_div: y = - x / x");
1168     return RemoveConstraint(ct);
1169   }
1170 
1171   // For now, we only presolve the case where the divisor is constant.
1172   if (!context_->IsFixed(div)) return changed;
1173 
1174   const int64_t divisor = context_->FixedValue(div);
1175   if (divisor == 1) {
1176     LinearConstraintProto* const lin =
1177         context_->working_model->add_constraints()->mutable_linear();
1178     lin->add_domain(0);
1179     lin->add_domain(0);
1180     AddLinearExpressionToLinearConstraint(expr, 1, lin);
1181     AddLinearExpressionToLinearConstraint(target, -1, lin);
1182     context_->UpdateNewConstraintsVariableUsage();
1183     context_->UpdateRuleStats("int_div: rewrite to equality");
1184     return RemoveConstraint(ct);
1185   }
1186   bool domain_modified = false;
1187   if (!context_->IntersectDomainWith(
1188           target, context_->DomainSuperSetOf(expr).DivisionBy(divisor),
1189           &domain_modified)) {
1190     return false;
1191   }
1192   if (domain_modified) {
1193     context_->UpdateRuleStats(
1194         "int_div: updated domain of target in target = X / cte");
1195   }
1196 
1197   // Linearize if everything is positive.
1198   // TODO(user): Deal with other cases where there is no change of
1199   // sign. We can also deal with target = cte, div variable.
1200   if (context_->MinOf(target) >= 0 && context_->MinOf(expr) >= 0 &&
1201       divisor > 1) {
1202     LinearConstraintProto* const lin =
1203         context_->working_model->add_constraints()->mutable_linear();
1204     lin->add_domain(0);
1205     lin->add_domain(divisor - 1);
1206     AddLinearExpressionToLinearConstraint(expr, 1, lin);
1207     AddLinearExpressionToLinearConstraint(target, -divisor, lin);
1208     context_->UpdateNewConstraintsVariableUsage();
1209     context_->UpdateRuleStats(
1210         "int_div: linearize positive division with a constant divisor");
1211     return RemoveConstraint(ct);
1212   }
1213 
1214   // TODO(user): reduce the domain of X by introducing an
1215   // InverseDivisionOfSortedDisjointIntervals().
1216   return changed;
1217 }
1218 
1219 bool CpModelPresolver::PresolveIntMod(ConstraintProto* ct) {
1220   if (context_->ModelIsUnsat()) return false;
1221 
1222   bool changed = CanonicalizeLinearExpression(
1223       *ct, ct->mutable_int_mod()->mutable_target());
1224   for (LinearExpressionProto& exp : *(ct->mutable_int_mod()->mutable_exprs())) {
1225     changed |= CanonicalizeLinearExpression(*ct, &exp);
1226   }
1227 
1228   const LinearExpressionProto target = ct->int_mod().target();
1229   const LinearExpressionProto expr = ct->int_mod().exprs(0);
1230   const LinearExpressionProto mod = ct->int_mod().exprs(1);
1231 
1232   bool domain_changed = false;
1233   if (!context_->IntersectDomainWith(
1234           target,
1235           context_->DomainSuperSetOf(expr).PositiveModuloBySuperset(
1236               context_->DomainSuperSetOf(mod)),
1237           &domain_changed)) {
1238     return changed;
1239   }
1240 
1241   if (domain_changed) {
1242     context_->UpdateRuleStats("int_mod: reduce target domain");
1243   }
1244 
1245   return changed;
1246 }
1247 
1248 bool CpModelPresolver::ExploitEquivalenceRelations(int c, ConstraintProto* ct) {
1249   bool changed = false;
1250 
1251   // Optim: Special case for the linear constraint. We just remap the
1252   // enforcement literals, the normal variables will be replaced by their
1253   // representative in CanonicalizeLinear().
1254   if (ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
1255     for (int& ref : *ct->mutable_enforcement_literal()) {
1256       const int rep = this->context_->GetLiteralRepresentative(ref);
1257       if (rep != ref) {
1258         changed = true;
1259         ref = rep;
1260       }
1261     }
1262     return changed;
1263   }
1264 
1265   // Optim: This extra loop is a lot faster than reparsing the variable from the
1266   // proto when there is nothing to do, which is quite often.
1267   bool work_to_do = false;
1268   for (const int var : context_->ConstraintToVars(c)) {
1269     const AffineRelation::Relation r = context_->GetAffineRelation(var);
1270     if (r.representative != var) {
1271       work_to_do = true;
1272       break;
1273     }
1274   }
1275   if (!work_to_do) return false;
1276 
1277   // Remap equal and negated variables to their representative.
1278   ApplyToAllVariableIndices(
1279       [&changed, this](int* ref) {
1280         const int rep = context_->GetVariableRepresentative(*ref);
1281         if (rep != *ref) {
1282           changed = true;
1283           *ref = rep;
1284         }
1285       },
1286       ct);
1287 
1288   // Remap literal and negated literal to their representative.
1289   ApplyToAllLiteralIndices(
1290       [&changed, this](int* ref) {
1291         const int rep = this->context_->GetLiteralRepresentative(*ref);
1292         if (rep != *ref) {
1293           changed = true;
1294           *ref = rep;
1295         }
1296       },
1297       ct);
1298   return changed;
1299 }
1300 
1301 bool CpModelPresolver::DivideLinearByGcd(ConstraintProto* ct) {
1302   if (context_->ModelIsUnsat()) return false;
1303 
1304   // Compute the GCD of all coefficients.
1305   int64_t gcd = 0;
1306   const int num_vars = ct->linear().vars().size();
1307   for (int i = 0; i < num_vars; ++i) {
1308     const int64_t magnitude = std::abs(ct->linear().coeffs(i));
1309     gcd = MathUtil::GCD64(gcd, magnitude);
1310     if (gcd == 1) break;
1311   }
1312   if (gcd > 1) {
1313     context_->UpdateRuleStats("linear: divide by GCD");
1314     for (int i = 0; i < num_vars; ++i) {
1315       ct->mutable_linear()->set_coeffs(i, ct->linear().coeffs(i) / gcd);
1316     }
1317     const Domain rhs = ReadDomainFromProto(ct->linear());
1318     FillDomainInProto(rhs.InverseMultiplicationBy(gcd), ct->mutable_linear());
1319     if (ct->linear().domain_size() == 0) {
1320       return MarkConstraintAsFalse(ct);
1321     }
1322   }
1323   return false;
1324 }
1325 
1326 template <typename ProtoWithVarsAndCoeffs>
1327 bool CpModelPresolver::CanonicalizeLinearExpressionInternal(
1328     const ConstraintProto& ct, ProtoWithVarsAndCoeffs* proto, int64_t* offset) {
1329   // First regroup the terms on the same variables and sum the fixed ones.
1330   //
1331   // TODO(user): Add a quick pass to skip most of the work below if the
1332   // constraint is already in canonical form?
1333   tmp_terms_.clear();
1334   int64_t sum_of_fixed_terms = 0;
1335   bool remapped = false;
1336   const int old_size = proto->vars().size();
1337   DCHECK_EQ(old_size, proto->coeffs().size());
1338   for (int i = 0; i < old_size; ++i) {
1339     const int ref = proto->vars(i);
1340     const int var = PositiveRef(ref);
1341     const int64_t coeff =
1342         RefIsPositive(ref) ? proto->coeffs(i) : -proto->coeffs(i);
1343     if (coeff == 0) continue;
1344 
1345     if (context_->IsFixed(var)) {
1346       sum_of_fixed_terms += coeff * context_->MinOf(var);
1347       continue;
1348     }
1349 
1350     // TODO(user): Avoid the quadratic loop for the corner case of many
1351     // enforcement literal (this should be pretty rare though).
1352     bool removed = false;
1353     for (const int enf : ct.enforcement_literal()) {
1354       if (var == PositiveRef(enf)) {
1355         if (RefIsPositive(enf)) {
1356           // If the constraint is enforced, we can assume the variable is at 1.
1357           sum_of_fixed_terms += coeff;
1358         } else {
1359           // We can assume the variable is at zero.
1360         }
1361         removed = true;
1362         break;
1363       }
1364     }
1365     if (removed) {
1366       context_->UpdateRuleStats("linear: enforcement literal in expression");
1367       continue;
1368     }
1369 
1370     const AffineRelation::Relation r = context_->GetAffineRelation(var);
1371     if (r.representative != var) {
1372       remapped = true;
1373       sum_of_fixed_terms += coeff * r.offset;
1374     }
1375     tmp_terms_.push_back({r.representative, coeff * r.coeff});
1376   }
1377   proto->clear_vars();
1378   proto->clear_coeffs();
1379   std::sort(tmp_terms_.begin(), tmp_terms_.end());
1380   int current_var = 0;
1381   int64_t current_coeff = 0;
1382   for (const auto entry : tmp_terms_) {
1383     CHECK(RefIsPositive(entry.first));
1384     if (entry.first == current_var) {
1385       current_coeff += entry.second;
1386     } else {
1387       if (current_coeff != 0) {
1388         proto->add_vars(current_var);
1389         proto->add_coeffs(current_coeff);
1390       }
1391       current_var = entry.first;
1392       current_coeff = entry.second;
1393     }
1394   }
1395   if (current_coeff != 0) {
1396     proto->add_vars(current_var);
1397     proto->add_coeffs(current_coeff);
1398   }
1399   if (remapped) {
1400     context_->UpdateRuleStats("linear: remapped using affine relations");
1401   }
1402   if (proto->vars().size() < old_size) {
1403     context_->UpdateRuleStats("linear: fixed or dup variables");
1404   }
1405   *offset = sum_of_fixed_terms;
1406   return remapped || proto->vars().size() < old_size;
1407 }
1408 
1409 bool CpModelPresolver::CanonicalizeLinearExpression(
1410     const ConstraintProto& ct, LinearExpressionProto* exp) {
1411   int64_t offset = 0;
1412   const bool result = CanonicalizeLinearExpressionInternal(ct, exp, &offset);
1413   exp->set_offset(exp->offset() + offset);
1414   return result;
1415 }
1416 
1417 bool CpModelPresolver::CanonicalizeLinear(ConstraintProto* ct) {
1418   if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1419       context_->ModelIsUnsat()) {
1420     return false;
1421   }
1422 
1423   if (ct->linear().domain().empty()) {
1424     context_->UpdateRuleStats("linear: no domain");
1425     return MarkConstraintAsFalse(ct);
1426   }
1427 
1428   int64_t offset = 0;
1429   bool changed =
1430       CanonicalizeLinearExpressionInternal(*ct, ct->mutable_linear(), &offset);
1431   if (offset != 0) {
1432     FillDomainInProto(
1433         ReadDomainFromProto(ct->linear()).AdditionWith(Domain(-offset)),
1434         ct->mutable_linear());
1435   }
1436   changed |= DivideLinearByGcd(ct);
1437   return changed;
1438 }
1439 
1440 bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto* ct) {
1441   if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1442       context_->ModelIsUnsat()) {
1443     return false;
1444   }
1445 
1446   std::set<int> index_to_erase;
1447   const int num_vars = ct->linear().vars().size();
1448   Domain rhs = ReadDomainFromProto(ct->linear());
1449 
1450   // First pass. Process singleton column that are not in the objective. Note
1451   // that for postsolve, it is important that we process them in the same order
1452   // in which they will be removed.
1453   for (int i = 0; i < num_vars; ++i) {
1454     const int var = ct->linear().vars(i);
1455     const int64_t coeff = ct->linear().coeffs(i);
1456     CHECK(RefIsPositive(var));
1457     if (context_->VariableIsUniqueAndRemovable(var)) {
1458       bool exact;
1459       const auto term_domain =
1460           context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
1461       if (!exact) continue;
1462 
1463       // We do not do that if the domain of rhs becomes too complex.
1464       const Domain new_rhs = rhs.AdditionWith(term_domain);
1465       if (new_rhs.NumIntervals() > 100) continue;
1466 
1467       // Note that we can't do that if we loose information in the
1468       // multiplication above because the new domain might not be as strict
1469       // as the initial constraint otherwise. TODO(user): because of the
1470       // addition, it might be possible to cover more cases though.
1471       context_->UpdateRuleStats("linear: singleton column");
1472       index_to_erase.insert(i);
1473       rhs = new_rhs;
1474       continue;
1475     }
1476   }
1477 
1478   // If we didn't find any, look for the one appearing in the objective.
1479   if (index_to_erase.empty()) {
1480     // Note that we only do that if we have a non-reified equality.
1481     if (context_->params().presolve_substitution_level() <= 0) return false;
1482     if (!ct->enforcement_literal().empty()) return false;
1483 
1484     // If it is possible to do so, note that we can transform constraint into
1485     // equalities in PropagateDomainsInLinear().
1486     if (rhs.Min() != rhs.Max()) return false;
1487 
1488     for (int i = 0; i < num_vars; ++i) {
1489       const int var = ct->linear().vars(i);
1490       const int64_t coeff = ct->linear().coeffs(i);
1491       CHECK(RefIsPositive(var));
1492 
1493       // If the variable appear only in the objective and we have an equality,
1494       // we can transfer the cost to the rest of the linear expression, and
1495       // remove that variable. Note that this do not remove any feasible
1496       // solution and is not a "dual" reduction.
1497       //
1498       // Note that is similar to the substitution code in PresolveLinear() but
1499       // it doesn't require the variable to be implied free since we do not
1500       // remove the constraints afterwards, just the variable.
1501       if (!context_->VariableWithCostIsUnique(var)) continue;
1502       DCHECK(context_->ObjectiveMap().contains(var));
1503 
1504       // We only support substitution that does not require to multiply the
1505       // objective by some factor.
1506       //
1507       // TODO(user): If the objective is a single variable, we can actually
1508       // "absorb" any factor into the objective scaling.
1509       const int64_t objective_coeff = context_->ObjectiveMap().at(var);
1510       CHECK_NE(coeff, 0);
1511       if (objective_coeff % coeff != 0) continue;
1512 
1513       // We do not do that if the domain of rhs becomes too complex.
1514       bool exact;
1515       const auto term_domain =
1516           context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
1517       if (!exact) continue;
1518       const Domain new_rhs = rhs.AdditionWith(term_domain);
1519       if (new_rhs.NumIntervals() > 100) continue;
1520 
1521       // Special case: If the objective was a single variable, we can transfer
1522       // the domain of var to the objective, and just completely remove this
1523       // equality constraint like it is done in ExpandObjective().
1524       if (context_->ObjectiveMap().size() == 1) {
1525         if (!context_->IntersectDomainWith(
1526                 var, context_->ObjectiveDomain().InverseMultiplicationBy(
1527                          objective_coeff))) {
1528           return true;
1529         }
1530 
1531         // The intersection above might fix var, in which case, we just abort.
1532         if (context_->IsFixed(var)) continue;
1533 
1534         // This makes sure the domain of var is propagated back to the
1535         // objective.
1536         //
1537         // Tricky: We cannot "simplify" the domain of the objective using the
1538         // implied domain from the linear expression since we will substitute
1539         // the variable.
1540         //
1541         // TODO(user): Maybe if var has a complex domain, we might not want to
1542         // substitute it?
1543         if (!context_->CanonicalizeObjective(/*simplify_domain=*/false)) {
1544           return context_->NotifyThatModelIsUnsat();
1545         }
1546 
1547         // Normally, CanonicalizeObjective() shouldn't remove var because
1548         // we work on a linear constraint that has been canonicalized. We keep
1549         // the test here in case this ever happen so we are notified.
1550         if (!context_->ObjectiveMap().contains(var)) {
1551           LOG(WARNING) << "This was not supposed to happen and the presolve "
1552                           "could be improved.";
1553           continue;
1554         }
1555         if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
1556           if (context_->ModelIsUnsat()) return true;
1557           continue;
1558         }
1559 
1560         context_->UpdateRuleStats("linear: singleton column define objective.");
1561         context_->MarkVariableAsRemoved(var);
1562         *(context_->mapping_model->add_constraints()) = *ct;
1563         return RemoveConstraint(ct);
1564       }
1565 
1566       // Update the objective and remove the variable from its equality
1567       // constraint by expanding its rhs. This might fail if the new linear
1568       // objective expression can lead to overflow.
1569       if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
1570         if (context_->ModelIsUnsat()) return true;
1571         continue;
1572       }
1573 
1574       context_->UpdateRuleStats(
1575           "linear: singleton column in equality and in objective.");
1576       rhs = new_rhs;
1577       index_to_erase.insert(i);
1578       break;
1579     }
1580   }
1581   if (index_to_erase.empty()) return false;
1582 
1583   // Tricky: If we have a singleton variable in an enforced constraint, and at
1584   // postsolve the enforcement is false, we might just ignore the constraint.
1585   // This is fine, but we still need to assign any removed variable to a
1586   // feasible value, otherwise later postsolve rules might not work correctly.
1587   // Adding these linear1 achieve that.
1588   //
1589   // TODO(user): Alternatively, we could copy the constraint without the
1590   // enforcement to the mapping model, since singleton variable are supposed
1591   // to always have a feasible value anyway.
1592   if (!ct->enforcement_literal().empty()) {
1593     for (const int i : index_to_erase) {
1594       const int var = ct->linear().vars(i);
1595       auto* l = context_->mapping_model->add_constraints()->mutable_linear();
1596       l->add_vars(var);
1597       l->add_coeffs(1);
1598       FillDomainInProto(context_->DomainOf(var), l);
1599     }
1600   }
1601 
1602   // TODO(user): we could add the constraint to mapping_model only once
1603   // instead of adding a reduced version of it each time a new singleton
1604   // variable appear in the same constraint later. That would work but would
1605   // also force the postsolve to take search decisions...
1606   *context_->mapping_model->add_constraints() = *ct;
1607 
1608   int new_size = 0;
1609   for (int i = 0; i < num_vars; ++i) {
1610     if (index_to_erase.count(i)) {
1611       context_->MarkVariableAsRemoved(ct->linear().vars(i));
1612       continue;
1613     }
1614     ct->mutable_linear()->set_coeffs(new_size, ct->linear().coeffs(i));
1615     ct->mutable_linear()->set_vars(new_size, ct->linear().vars(i));
1616     ++new_size;
1617   }
1618   ct->mutable_linear()->mutable_vars()->Truncate(new_size);
1619   ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
1620   FillDomainInProto(rhs, ct->mutable_linear());
1621   DivideLinearByGcd(ct);
1622   return true;
1623 }
1624 
1625 // If the gcd of all but one term (with index target_index) is not one, we can
1626 // rewrite the last term using an affine representative.
1627 bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality(
1628     int target_index, ConstraintProto* ct) {
1629   int64_t gcd = 0;
1630   const int num_variables = ct->linear().vars().size();
1631   for (int i = 0; i < num_variables; ++i) {
1632     if (i == target_index) continue;
1633     const int64_t magnitude = std::abs(ct->linear().coeffs(i));
1634     gcd = MathUtil::GCD64(gcd, magnitude);
1635     if (gcd == 1) return false;
1636   }
1637 
1638   // If we take the constraint % gcd, we have
1639   // ref * coeff % gcd = rhs % gcd
1640   CHECK_GT(gcd, 1);
1641   const int ref = ct->linear().vars(target_index);
1642   const int64_t coeff = ct->linear().coeffs(target_index);
1643   const int64_t rhs = ct->linear().domain(0);
1644 
1645   // This should have been processed before by just dividing the whole
1646   // constraint by the gcd.
1647   if (coeff % gcd == 0) return false;
1648 
1649   if (!context_->CanonicalizeAffineVariable(ref, coeff, gcd, rhs)) {
1650     return false;
1651   }
1652 
1653   // We use the new variable in the constraint.
1654   // Note that we will divide everything by the gcd too.
1655   return CanonicalizeLinear(ct);
1656 }
1657 
1658 // Any equality must be true modulo n.
1659 //
1660 // If the gcd of all but one term is not one, we can rewrite the last term using
1661 // an affine representative by considering the equality modulo that gcd.
1662 // As an heuristic, we only test the smallest term or small primes 2, 3, and 5.
1663 //
1664 // We also handle the special case of having two non-zero literals modulo 2.
1665 bool CpModelPresolver::PresolveLinearEqualityWithModulo(ConstraintProto* ct) {
1666   if (context_->ModelIsUnsat()) return false;
1667   if (ct->constraint_case() != ConstraintProto::kLinear) return false;
1668   if (ct->linear().domain().size() != 2) return false;
1669   if (ct->linear().domain(0) != ct->linear().domain(1)) return false;
1670   if (!ct->enforcement_literal().empty()) return false;
1671 
1672   const int num_variables = ct->linear().vars().size();
1673   if (num_variables < 2) return false;
1674 
1675   std::vector<int> mod2_indices;
1676   std::vector<int> mod3_indices;
1677   std::vector<int> mod5_indices;
1678 
1679   int64_t min_magnitude;
1680   int num_smallest = 0;
1681   int smallest_index;
1682   for (int i = 0; i < num_variables; ++i) {
1683     const int64_t magnitude = std::abs(ct->linear().coeffs(i));
1684     if (num_smallest == 0 || magnitude < min_magnitude) {
1685       min_magnitude = magnitude;
1686       num_smallest = 1;
1687       smallest_index = i;
1688     } else if (magnitude == min_magnitude) {
1689       ++num_smallest;
1690     }
1691 
1692     if (magnitude % 2 != 0) mod2_indices.push_back(i);
1693     if (magnitude % 3 != 0) mod3_indices.push_back(i);
1694     if (magnitude % 5 != 0) mod5_indices.push_back(i);
1695   }
1696 
1697   if (mod2_indices.size() == 2) {
1698     bool ok = true;
1699     std::vector<int> literals;
1700     for (const int i : mod2_indices) {
1701       const int ref = ct->linear().vars(i);
1702       if (!context_->CanBeUsedAsLiteral(ref)) {
1703         ok = false;
1704         break;
1705       }
1706       literals.push_back(ref);
1707     }
1708     if (ok) {
1709       const int64_t rhs = std::abs(ct->linear().domain(0));
1710       context_->UpdateRuleStats("linear: only two odd Booleans in equality");
1711       if (rhs % 2) {
1712         context_->StoreBooleanEqualityRelation(literals[0],
1713                                                NegatedRef(literals[1]));
1714       } else {
1715         context_->StoreBooleanEqualityRelation(literals[0], literals[1]);
1716       }
1717     }
1718   }
1719 
1720   // TODO(user): More than one reduction might be possible, so we will need
1721   // to call this again if we apply any of these reduction.
1722   if (mod2_indices.size() == 1) {
1723     return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0], ct);
1724   }
1725   if (mod3_indices.size() == 1) {
1726     return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0], ct);
1727   }
1728   if (mod5_indices.size() == 1) {
1729     return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0], ct);
1730   }
1731   if (num_smallest == 1) {
1732     return AddVarAffineRepresentativeFromLinearEquality(smallest_index, ct);
1733   }
1734 
1735   return false;
1736 }
1737 
1738 bool CpModelPresolver::PresolveLinearOfSizeOne(ConstraintProto* ct) {
1739   DCHECK_EQ(ct->linear().vars().size(), 1);
1740 
1741   // Size one constraint with no enforcement?
1742   if (!HasEnforcementLiteral(*ct)) {
1743     const int64_t coeff = RefIsPositive(ct->linear().vars(0))
1744                               ? ct->linear().coeffs(0)
1745                               : -ct->linear().coeffs(0);
1746     context_->UpdateRuleStats("linear1: without enforcement");
1747     const int var = PositiveRef(ct->linear().vars(0));
1748     const Domain rhs = ReadDomainFromProto(ct->linear());
1749     if (!context_->IntersectDomainWith(var,
1750                                        rhs.InverseMultiplicationBy(coeff))) {
1751       return false;
1752     }
1753     return RemoveConstraint(ct);
1754   }
1755 
1756   // This is just an implication, lets convert it right away.
1757   if (context_->CanBeUsedAsLiteral(ct->linear().vars(0))) {
1758     const Domain rhs = ReadDomainFromProto(ct->linear());
1759     const bool zero_ok = rhs.Contains(0);
1760     const bool one_ok = rhs.Contains(ct->linear().coeffs(0));
1761     context_->UpdateRuleStats("linear1: is boolean implication");
1762     if (!zero_ok && !one_ok) {
1763       return MarkConstraintAsFalse(ct);
1764     }
1765     if (zero_ok && one_ok) {
1766       return RemoveConstraint(ct);
1767     }
1768     const int ref = ct->linear().vars(0);
1769     if (zero_ok) {
1770       ct->mutable_bool_and()->add_literals(NegatedRef(ref));
1771     } else {
1772       ct->mutable_bool_and()->add_literals(ref);
1773     }
1774 
1775     // No var <-> constraint graph changes.
1776     // But this is no longer a linear1.
1777     return true;
1778   }
1779 
1780   // If the constraint is literal => x in domain and x = abs(abs_arg), we can
1781   // replace x by abs_arg and hopefully remove the variable x later.
1782   int abs_arg;
1783   if (ct->linear().coeffs(0) == 1 &&
1784       context_->GetAbsRelation(ct->linear().vars(0), &abs_arg)) {
1785     // TODO(user): Deal with coeff = -1, here or during canonicalization.
1786     context_->UpdateRuleStats("linear1: remove abs from abs(x) in domain");
1787     const Domain implied_abs_target_domain =
1788         ReadDomainFromProto(ct->linear())
1789             .IntersectionWith({0, std::numeric_limits<int64_t>::max()})
1790             .IntersectionWith(context_->DomainOf(ct->linear().vars(0)));
1791 
1792     if (implied_abs_target_domain.IsEmpty()) {
1793       return MarkConstraintAsFalse(ct);
1794     }
1795 
1796     const Domain new_abs_var_domain =
1797         implied_abs_target_domain
1798             .UnionWith(implied_abs_target_domain.Negation())
1799             .IntersectionWith(context_->DomainOf(abs_arg));
1800 
1801     if (new_abs_var_domain.IsEmpty()) {
1802       return MarkConstraintAsFalse(ct);
1803     }
1804 
1805     ConstraintProto* new_ct = context_->working_model->add_constraints();
1806     new_ct->set_name(ct->name());
1807     *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
1808     auto* arg = new_ct->mutable_linear();
1809     arg->add_vars(abs_arg);
1810     arg->add_coeffs(1);
1811     FillDomainInProto(new_abs_var_domain, new_ct->mutable_linear());
1812     context_->UpdateNewConstraintsVariableUsage();
1813     return RemoveConstraint(ct);
1814   }
1815 
1816   // Detect encoding.
1817   if (ct->enforcement_literal_size() != 1 ||
1818       (ct->linear().coeffs(0) != 1 && ct->linear().coeffs(0) == -1)) {
1819     return false;
1820   }
1821 
1822   // Currently, we only use encoding during expansion, so when it is done,
1823   // there is no need to updates the maps.
1824   if (context_->ModelIsExpanded()) return false;
1825 
1826   const int literal = ct->enforcement_literal(0);
1827   const LinearConstraintProto& linear = ct->linear();
1828   const int ref = linear.vars(0);
1829   const int var = PositiveRef(ref);
1830   const int64_t coeff =
1831       RefIsPositive(ref) ? ct->linear().coeffs(0) : -ct->linear().coeffs(0);
1832 
1833   if (linear.domain_size() == 2 && linear.domain(0) == linear.domain(1)) {
1834     const int64_t value = RefIsPositive(ref) ? linear.domain(0) * coeff
1835                                              : -linear.domain(0) * coeff;
1836     if (!context_->DomainOf(var).Contains(value)) {
1837       if (!context_->SetLiteralToFalse(literal)) return false;
1838     } else if (context_->StoreLiteralImpliesVarEqValue(literal, var, value)) {
1839       // The domain is not actually modified, but we want to rescan the
1840       // constraints linked to this variable. See TODO below.
1841       context_->modified_domains.Set(var);
1842     }
1843   } else {
1844     const Domain complement = context_->DomainOf(ref).IntersectionWith(
1845         ReadDomainFromProto(linear).Complement());
1846     if (complement.Size() != 1) return false;
1847     const int64_t value = RefIsPositive(ref) ? complement.Min() * coeff
1848                                              : -complement.Min() * coeff;
1849     if (context_->StoreLiteralImpliesVarNEqValue(literal, var, value)) {
1850       // The domain is not actually modified, but we want to rescan the
1851       // constraints linked to this variable. See TODO below.
1852       context_->modified_domains.Set(var);
1853     }
1854   }
1855 
1856   // TODO(user): if we have l1 <=> x == value && l2 => x == value, we
1857   //     could rewrite the second constraint into l2 => l1.
1858   context_->UpdateNewConstraintsVariableUsage();
1859   return false;
1860 }
1861 
1862 bool CpModelPresolver::PresolveLinearOfSizeTwo(ConstraintProto* ct) {
1863   DCHECK_EQ(ct->linear().vars().size(), 2);
1864 
1865   const LinearConstraintProto& arg = ct->linear();
1866   const int var1 = arg.vars(0);
1867   const int var2 = arg.vars(1);
1868   const int64_t coeff1 = arg.coeffs(0);
1869   const int64_t coeff2 = arg.coeffs(1);
1870 
1871   // If it is not an equality, we only presolve the constraint if one of
1872   // the variable is Boolean. Note that if both are Boolean, then a similar
1873   // reduction is done by PresolveLinearOnBooleans(). If we have an equality,
1874   // then the code below will do something stronger than this.
1875   //
1876   // TODO(user): We should probably instead generalize the code of
1877   // ExtractEnforcementLiteralFromLinearConstraint(), or just temporary
1878   // propagate domain of enforced linear constraints, to detect Boolean that
1879   // must be true or false. This way we can do the same for longer constraints.
1880   const bool is_equality =
1881       arg.domain_size() == 2 && arg.domain(0) == arg.domain(1);
1882   if (!is_equality) {
1883     int lit, var;
1884     int64_t value_on_true, coeff;
1885     if (context_->CanBeUsedAsLiteral(var1)) {
1886       lit = var1;
1887       value_on_true = coeff1;
1888       var = var2;
1889       coeff = coeff2;
1890     } else if (context_->CanBeUsedAsLiteral(var2)) {
1891       lit = var2;
1892       value_on_true = coeff2;
1893       var = var1;
1894       coeff = coeff1;
1895     } else {
1896       return false;
1897     }
1898 
1899     const Domain rhs = ReadDomainFromProto(ct->linear());
1900     const Domain rhs_if_true =
1901         rhs.AdditionWith(Domain(-value_on_true)).InverseMultiplicationBy(coeff);
1902     const Domain rhs_if_false = rhs.InverseMultiplicationBy(coeff);
1903     const bool implied_false =
1904         context_->DomainOf(var).IntersectionWith(rhs_if_true).IsEmpty();
1905     const bool implied_true =
1906         context_->DomainOf(var).IntersectionWith(rhs_if_false).IsEmpty();
1907     if (implied_true && implied_false) {
1908       context_->UpdateRuleStats("linear2: infeasible.");
1909       return MarkConstraintAsFalse(ct);
1910     } else if (implied_true) {
1911       context_->UpdateRuleStats("linear2: Boolean with one feasible value.");
1912 
1913       // => true.
1914       ConstraintProto* new_ct = context_->working_model->add_constraints();
1915       *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
1916       new_ct->mutable_bool_and()->add_literals(lit);
1917       context_->UpdateNewConstraintsVariableUsage();
1918 
1919       // Rewrite to => var in rhs_if_true.
1920       ct->mutable_linear()->Clear();
1921       ct->mutable_linear()->add_vars(var);
1922       ct->mutable_linear()->add_coeffs(1);
1923       FillDomainInProto(rhs_if_true, ct->mutable_linear());
1924       return PresolveLinearOfSizeOne(ct) || true;
1925     } else if (implied_false) {
1926       context_->UpdateRuleStats("linear2: Boolean with one feasible value.");
1927 
1928       // => false.
1929       ConstraintProto* new_ct = context_->working_model->add_constraints();
1930       *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
1931       new_ct->mutable_bool_and()->add_literals(NegatedRef(lit));
1932       context_->UpdateNewConstraintsVariableUsage();
1933 
1934       // Rewrite to => var in rhs_if_false.
1935       ct->mutable_linear()->Clear();
1936       ct->mutable_linear()->add_vars(var);
1937       ct->mutable_linear()->add_coeffs(1);
1938       FillDomainInProto(rhs_if_false, ct->mutable_linear());
1939       return PresolveLinearOfSizeOne(ct) || true;
1940     } else {
1941       // TODO(user): We can expand this into two linear1 constraints, I am not
1942       // 100% sure it is always good, so for now we don't do it. Note that the
1943       // effect of doing it or not is not really visible on the bench. Some
1944       // problem are better with it some better without.
1945       context_->UpdateRuleStats("TODO linear2: contains a Boolean.");
1946       return false;
1947     }
1948   }
1949 
1950   // We have: enforcement => (coeff1 * v1 + coeff2 * v2 == rhs).
1951   const int64_t rhs = arg.domain(0);
1952   if (ct->enforcement_literal().empty()) {
1953     // Detect affine relation.
1954     //
1955     // TODO(user): it might be better to first add only the affine relation with
1956     // a coefficient of magnitude 1, and later the one with larger coeffs.
1957     bool added = false;
1958     if (coeff1 == 1) {
1959       added = context_->StoreAffineRelation(var1, var2, -coeff2, rhs);
1960     } else if (coeff2 == 1) {
1961       added = context_->StoreAffineRelation(var2, var1, -coeff1, rhs);
1962     } else if (coeff1 == -1) {
1963       added = context_->StoreAffineRelation(var1, var2, coeff2, -rhs);
1964     } else if (coeff2 == -1) {
1965       added = context_->StoreAffineRelation(var2, var1, coeff1, -rhs);
1966     } else {
1967       // In this case, we can solve the diophantine equation, and write
1968       // both x and y in term of a new affine representative z.
1969       //
1970       // Note that PresolveLinearEqualityWithModularInverse() will have the
1971       // same effect.
1972       //
1973       // We can also decide to fully expand the equality if the variables
1974       // are fully encoded.
1975       context_->UpdateRuleStats("TODO linear2: ax + by = cte");
1976     }
1977     if (added) return RemoveConstraint(ct);
1978   } else {
1979     // We look ahead to detect solutions to ax + by == cte.
1980     int64_t a = coeff1;
1981     int64_t b = coeff2;
1982     int64_t cte = rhs;
1983     int64_t x0 = 0;
1984     int64_t y0 = 0;
1985     if (!SolveDiophantineEquationOfSizeTwo(a, b, cte, x0, y0)) {
1986       context_->UpdateRuleStats(
1987           "linear2: implied ax + by = cte has no solutions");
1988       return MarkConstraintAsFalse(ct);
1989     }
1990     const Domain reduced_domain =
1991         context_->DomainOf(var1)
1992             .AdditionWith(Domain(-x0))
1993             .InverseMultiplicationBy(b)
1994             .IntersectionWith(context_->DomainOf(var2)
1995                                   .AdditionWith(Domain(-y0))
1996                                   .InverseMultiplicationBy(-a));
1997 
1998     if (reduced_domain.IsEmpty()) {  // no solution
1999       context_->UpdateRuleStats(
2000           "linear2: implied ax + by = cte has no solutions");
2001       return MarkConstraintAsFalse(ct);
2002     }
2003 
2004     if (reduced_domain.Size() == 1) {
2005       const int64_t z = reduced_domain.FixedValue();
2006       const int64_t value1 = x0 + b * z;
2007       const int64_t value2 = y0 - a * z;
2008 
2009       DCHECK(context_->DomainContains(var1, value1));
2010       DCHECK(context_->DomainContains(var2, value2));
2011       DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
2012 
2013       ConstraintProto* imply1 = context_->working_model->add_constraints();
2014       *imply1->mutable_enforcement_literal() = ct->enforcement_literal();
2015       imply1->mutable_linear()->add_vars(var1);
2016       imply1->mutable_linear()->add_coeffs(1);
2017       imply1->mutable_linear()->add_domain(value1);
2018       imply1->mutable_linear()->add_domain(value1);
2019 
2020       ConstraintProto* imply2 = context_->working_model->add_constraints();
2021       *imply2->mutable_enforcement_literal() = ct->enforcement_literal();
2022       imply2->mutable_linear()->add_vars(var2);
2023       imply2->mutable_linear()->add_coeffs(1);
2024       imply2->mutable_linear()->add_domain(value2);
2025       imply2->mutable_linear()->add_domain(value2);
2026       context_->UpdateRuleStats(
2027           "linear2: implied ax + by = cte has only one solution");
2028       context_->UpdateNewConstraintsVariableUsage();
2029       return RemoveConstraint(ct);
2030     }
2031   }
2032 
2033   return false;
2034 }
2035 
2036 bool CpModelPresolver::PresolveSmallLinear(ConstraintProto* ct) {
2037   if (ct->constraint_case() != ConstraintProto::kLinear) return false;
2038   if (context_->ModelIsUnsat()) return false;
2039 
2040   if (ct->linear().vars().empty()) {
2041     context_->UpdateRuleStats("linear: empty");
2042     const Domain rhs = ReadDomainFromProto(ct->linear());
2043     if (rhs.Contains(0)) {
2044       return RemoveConstraint(ct);
2045     } else {
2046       return MarkConstraintAsFalse(ct);
2047     }
2048   } else if (ct->linear().vars().size() == 1) {
2049     return PresolveLinearOfSizeOne(ct);
2050   } else if (ct->linear().vars().size() == 2) {
2051     return PresolveLinearOfSizeTwo(ct);
2052   }
2053 
2054   return false;
2055 }
2056 
2057 namespace {
2058 
2059 // Return true if the given domain only restrict the values with an upper bound.
2060 bool IsLeConstraint(const Domain& domain, const Domain& all_values) {
2061   return all_values
2062       .IntersectionWith(
2063           Domain(std::numeric_limits<int64_t>::min(), domain.Max()))
2064       .IsIncludedIn(domain);
2065 }
2066 
2067 // Same as IsLeConstraint() but in the other direction.
2068 bool IsGeConstraint(const Domain& domain, const Domain& all_values) {
2069   return all_values
2070       .IntersectionWith(
2071           Domain(domain.Min(), std::numeric_limits<int64_t>::max()))
2072       .IsIncludedIn(domain);
2073 }
2074 
2075 // In the equation terms + coeff * var_domain \included rhs, returns true if can
2076 // we always fix rhs to its min value for any value in terms. It is okay to
2077 // not be as generic as possible here.
2078 bool RhsCanBeFixedToMin(int64_t coeff, const Domain& var_domain,
2079                         const Domain& terms, const Domain& rhs) {
2080   if (var_domain.NumIntervals() != 1) return false;
2081   if (std::abs(coeff) != 1) return false;
2082 
2083   // If for all values in terms, there is one value below rhs.Min(), then
2084   // because we add only one integer interval, if there is a feasible value, it
2085   // can be at rhs.Min().
2086   //
2087   // TODO(user): generalize to larger coeff magnitude if rhs is also a multiple
2088   // or if terms is a multiple.
2089   if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
2090     return true;
2091   }
2092   if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
2093     return true;
2094   }
2095   return false;
2096 }
2097 
2098 bool RhsCanBeFixedToMax(int64_t coeff, const Domain& var_domain,
2099                         const Domain& terms, const Domain& rhs) {
2100   if (var_domain.NumIntervals() != 1) return false;
2101   if (std::abs(coeff) != 1) return false;
2102 
2103   if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
2104     return true;
2105   }
2106   if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
2107     return true;
2108   }
2109   return false;
2110 }
2111 
2112 // Remove from to_clear any entry not in current.
2113 void TakeIntersectionWith(const absl::flat_hash_set<int>& current,
2114                           absl::flat_hash_set<int>* to_clear) {
2115   std::vector<int> new_set;
2116   for (const int c : *to_clear) {
2117     if (current.contains(c)) new_set.push_back(c);
2118   }
2119   to_clear->clear();
2120   for (const int c : new_set) to_clear->insert(c);
2121 }
2122 
2123 }  // namespace
2124 
2125 bool CpModelPresolver::DetectAndProcessOneSidedLinearConstraint(
2126     int c, ConstraintProto* ct) {
2127   if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
2128     return false;
2129   }
2130   if (context_->ModelIsUnsat()) return false;
2131   if (context_->keep_all_feasible_solutions) return false;
2132 
2133   // TODO(user): There is a bit of code and effort duplication with
2134   // PropagateDomainsInLinear(). Try to remove that.
2135   Domain implied_rhs(0);
2136   const int num_vars = ct->linear().vars().size();
2137   for (int i = 0; i < num_vars; ++i) {
2138     const int ref = ct->linear().vars(i);
2139     const int64_t coeff = ct->linear().coeffs(i);
2140     implied_rhs =
2141         implied_rhs
2142             .AdditionWith(context_->DomainOf(ref).MultiplicationBy(coeff))
2143             .RelaxIfTooComplex();
2144   }
2145 
2146   // Abort if trivial.
2147   const Domain old_rhs = ReadDomainFromProto(ct->linear());
2148   if (implied_rhs.IsIncludedIn(old_rhs)) {
2149     context_->UpdateRuleStats("linear: always true");
2150     return RemoveConstraint(ct);
2151   }
2152 
2153   // Incorporate the implied rhs information.
2154   const Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
2155   if (rhs.IsEmpty()) {
2156     context_->UpdateRuleStats("linear: infeasible");
2157     return MarkConstraintAsFalse(ct);
2158   }
2159   if (rhs != old_rhs) {
2160     context_->UpdateRuleStats("linear: simplified rhs");
2161   }
2162   FillDomainInProto(rhs, ct->mutable_linear());
2163 
2164   // Detect if it is always good for a term of this constraint to move towards
2165   // its lower (resp. upper) bound. This is the same as saying that this
2166   // constraint only bound in one direction.
2167   const bool is_le_constraint = IsLeConstraint(rhs, implied_rhs);
2168   const bool is_ge_constraint = IsGeConstraint(rhs, implied_rhs);
2169   if (!is_le_constraint && !is_ge_constraint) return false;
2170   CHECK_NE(is_le_constraint, is_ge_constraint);
2171 
2172   bool recanonicalize = false;
2173   for (int i = 0; i < num_vars; ++i) {
2174     const int var = ct->linear().vars(i);
2175     const int64_t var_coeff = ct->linear().coeffs(i);
2176     CHECK(RefIsPositive(var));
2177 
2178     if ((var_coeff > 0) == is_ge_constraint) {
2179       context_->var_to_lb_only_constraints[var].insert(c);
2180     } else {
2181       context_->var_to_ub_only_constraints[var].insert(c);
2182     }
2183 
2184     // Simple dual fixing: If for any feasible solution, any solution with var
2185     // higher (resp. lower) is also valid, then we can fix that variable to
2186     // its bound if it also moves the objective in the good direction.
2187     const bool is_in_objective = context_->VarToConstraints(var).contains(-1);
2188     const int size =
2189         context_->VarToConstraints(var).size() - (is_in_objective ? 1 : 0);
2190     const int64_t obj_coeff =
2191         is_in_objective ? context_->ObjectiveMap().at(var) : 0;
2192 
2193     // We cannot fix anything if the domain of the objective is excluding
2194     // some objective values.
2195     if (obj_coeff != 0 && context_->ObjectiveDomainIsConstraining()) {
2196       continue;
2197     }
2198 
2199     if (obj_coeff <= 0 &&
2200         context_->var_to_lb_only_constraints[var].size() >= size) {
2201       TakeIntersectionWith(context_->VarToConstraints(var),
2202                            &(context_->var_to_lb_only_constraints[var]));
2203       if (context_->var_to_lb_only_constraints[var].size() >= size) {
2204         if (!context_->IntersectDomainWith(var, Domain(context_->MaxOf(var)))) {
2205           return false;
2206         }
2207         context_->UpdateRuleStats("linear: dual fixing");
2208         recanonicalize = true;
2209         continue;
2210       }
2211     }
2212 
2213     if (obj_coeff >= 0 &&
2214         context_->var_to_ub_only_constraints[var].size() >= size) {
2215       TakeIntersectionWith(context_->VarToConstraints(var),
2216                            &(context_->var_to_ub_only_constraints[var]));
2217       if (context_->var_to_ub_only_constraints[var].size() >= size) {
2218         if (!context_->IntersectDomainWith(var, Domain(context_->MinOf(var)))) {
2219           return false;
2220         }
2221         context_->UpdateRuleStats("linear: dual fixing");
2222         recanonicalize = true;
2223         continue;
2224       }
2225     }
2226   }
2227 
2228   if (recanonicalize) return CanonicalizeLinear(ct);
2229   return false;
2230 }
2231 
2232 bool CpModelPresolver::PropagateDomainsInLinear(int ct_index,
2233                                                 ConstraintProto* ct) {
2234   if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
2235       context_->ModelIsUnsat()) {
2236     return false;
2237   }
2238 
2239   // Compute the implied rhs bounds from the variable ones.
2240   auto& term_domains = context_->tmp_term_domains;
2241   auto& left_domains = context_->tmp_left_domains;
2242   const int num_vars = ct->linear().vars_size();
2243   term_domains.resize(num_vars + 1);
2244   left_domains.resize(num_vars + 1);
2245   left_domains[0] = Domain(0);
2246   for (int i = 0; i < num_vars; ++i) {
2247     const int var = ct->linear().vars(i);
2248     const int64_t coeff = ct->linear().coeffs(i);
2249     CHECK(RefIsPositive(var));
2250     term_domains[i] = context_->DomainOf(var).MultiplicationBy(coeff);
2251     left_domains[i + 1] =
2252         left_domains[i].AdditionWith(term_domains[i]).RelaxIfTooComplex();
2253   }
2254   const Domain& implied_rhs = left_domains[num_vars];
2255 
2256   // Abort if trivial.
2257   const Domain old_rhs = ReadDomainFromProto(ct->linear());
2258   if (implied_rhs.IsIncludedIn(old_rhs)) {
2259     context_->UpdateRuleStats("linear: always true");
2260     return RemoveConstraint(ct);
2261   }
2262 
2263   // Incorporate the implied rhs information.
2264   Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
2265   if (rhs.IsEmpty()) {
2266     context_->UpdateRuleStats("linear: infeasible");
2267     return MarkConstraintAsFalse(ct);
2268   }
2269   if (rhs != old_rhs) {
2270     context_->UpdateRuleStats("linear: simplified rhs");
2271   }
2272   FillDomainInProto(rhs, ct->mutable_linear());
2273 
2274   // Propagate the variable bounds.
2275   if (ct->enforcement_literal().size() > 1) return false;
2276 
2277   bool new_bounds = false;
2278   bool recanonicalize = false;
2279   Domain negated_rhs = rhs.Negation();
2280   Domain right_domain(0);
2281   Domain new_domain;
2282   Domain implied_term_domain;
2283   term_domains[num_vars] = Domain(0);
2284   for (int i = num_vars - 1; i >= 0; --i) {
2285     const int var = ct->linear().vars(i);
2286     const int64_t var_coeff = ct->linear().coeffs(i);
2287     right_domain =
2288         right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
2289     implied_term_domain = left_domains[i].AdditionWith(right_domain);
2290     new_domain = implied_term_domain.AdditionWith(negated_rhs)
2291                      .InverseMultiplicationBy(-var_coeff);
2292 
2293     if (ct->enforcement_literal().empty()) {
2294       // Push the new domain.
2295       if (!context_->IntersectDomainWith(var, new_domain, &new_bounds)) {
2296         return true;
2297       }
2298     } else if (ct->enforcement_literal().size() == 1) {
2299       // We cannot push the new domain, but we can add some deduction.
2300       CHECK(RefIsPositive(var));
2301       if (!context_->DomainOfVarIsIncludedIn(var, new_domain)) {
2302         context_->deductions.AddDeduction(ct->enforcement_literal(0), var,
2303                                           new_domain);
2304       }
2305     }
2306 
2307     if (context_->IsFixed(var)) {
2308       // This will make sure we remove that fixed variable from the constraint.
2309       recanonicalize = true;
2310       continue;
2311     }
2312 
2313     // The other transformations below require a non-reified constraint.
2314     if (!ct->enforcement_literal().empty()) continue;
2315 
2316     // Given a variable that only appear in one constraint and in the
2317     // objective, for any feasible solution, it will be always better to move
2318     // this singleton variable as much as possible towards its good objective
2319     // direction. Sometime_exprs, we can detect that we will always be able to
2320     // do this until the only constraint of this singleton variable is tight.
2321     //
2322     // When this happens, we can make the constraint an equality. Note that it
2323     // might not always be good to restrict constraint like this, but in this
2324     // case, the RemoveSingletonInLinear() code should be able to remove this
2325     // variable altogether.
2326     if (rhs.Min() != rhs.Max() &&
2327         context_->VariableWithCostIsUniqueAndRemovable(var)) {
2328       const int64_t obj_coeff = context_->ObjectiveMap().at(var);
2329       const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
2330       bool fixed = false;
2331       if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->DomainOf(var),
2332                                           implied_term_domain, rhs)) {
2333         rhs = Domain(rhs.Min());
2334         fixed = true;
2335       }
2336       if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->DomainOf(var),
2337                                            implied_term_domain, rhs)) {
2338         rhs = Domain(rhs.Max());
2339         fixed = true;
2340       }
2341       if (fixed) {
2342         context_->UpdateRuleStats("linear: tightened into equality");
2343         FillDomainInProto(rhs, ct->mutable_linear());
2344         negated_rhs = rhs.Negation();
2345 
2346         // Restart the loop.
2347         i = num_vars;
2348         right_domain = Domain(0);
2349         continue;
2350       }
2351     }
2352 
2353     // Can we perform some substitution?
2354     //
2355     // TODO(user): there is no guarantee we will not miss some since we might
2356     // not reprocess a constraint once other have been deleted.
2357 
2358     // Skip affine constraint. It is more efficient to substitute them lazily
2359     // when we process other constraints. Note that if we relax the fact that
2360     // we substitute only equalities, we can deal with inequality of size 2
2361     // here.
2362     if (ct->linear().vars().size() <= 2) continue;
2363 
2364     // TODO(user): We actually do not need a strict equality when
2365     // keep_all_feasible_solutions is false, but that simplifies things as the
2366     // SubstituteVariable() function cannot fail this way.
2367     if (rhs.Min() != rhs.Max()) continue;
2368 
2369     // Only consider "implied free" variables. Note that the coefficient of
2370     // magnitude 1 is important otherwise we can't easily remove the
2371     // constraint since the fact that the sum of the other terms must be a
2372     // multiple of coeff will not be enforced anymore.
2373     if (context_->DomainOf(var) != new_domain) continue;
2374     if (std::abs(var_coeff) != 1) continue;
2375     if (context_->params().presolve_substitution_level() <= 0) continue;
2376 
2377     // NOTE: The mapping doesn't allow us to remove a variable if
2378     // 'keep_all_feasible_solutions' is true.
2379     if (context_->keep_all_feasible_solutions) continue;
2380 
2381     bool is_in_objective = false;
2382     if (context_->VarToConstraints(var).contains(-1)) {
2383       is_in_objective = true;
2384       DCHECK(context_->ObjectiveMap().contains(var));
2385     }
2386 
2387     // Only consider low degree columns.
2388     int col_size = context_->VarToConstraints(var).size();
2389     if (is_in_objective) col_size--;
2390     const int row_size = ct->linear().vars_size();
2391 
2392     // This is actually an upper bound on the number of entries added since
2393     // some of them might already be present.
2394     const int num_entries_added = (row_size - 1) * (col_size - 1);
2395     const int num_entries_removed = col_size + row_size - 1;
2396 
2397     if (num_entries_added > num_entries_removed) {
2398       continue;
2399     }
2400 
2401     // Check pre-conditions on all the constraints in which this variable
2402     // appear. Basically they must all be linear.
2403     std::vector<int> others;
2404     bool abort = false;
2405     for (const int c : context_->VarToConstraints(var)) {
2406       if (c == kObjectiveConstraint) continue;
2407       if (c == kAffineRelationConstraint) {
2408         abort = true;
2409         break;
2410       }
2411       if (c == ct_index) continue;
2412       if (context_->working_model->constraints(c).constraint_case() !=
2413           ConstraintProto::ConstraintCase::kLinear) {
2414         abort = true;
2415         break;
2416       }
2417       for (const int ref :
2418            context_->working_model->constraints(c).enforcement_literal()) {
2419         if (PositiveRef(ref) == var) {
2420           abort = true;
2421           break;
2422         }
2423       }
2424       others.push_back(c);
2425     }
2426     if (abort) continue;
2427 
2428     // Do the actual substitution.
2429     for (const int c : others) {
2430       // TODO(user): In some corner cases, this might create integer overflow
2431       // issues. The danger is limited since the range of the linear
2432       // expression used in the definition do not exceed the domain of the
2433       // variable we substitute.
2434       const bool ok = SubstituteVariable(
2435           var, var_coeff, *ct, context_->working_model->mutable_constraints(c));
2436       if (!ok) {
2437         // This can happen if the constraint was not canonicalized and the
2438         // variable is actually not there (we have var - var for instance).
2439         CanonicalizeLinear(context_->working_model->mutable_constraints(c));
2440       }
2441 
2442       // TODO(user): We should re-enqueue these constraints for presolve.
2443       context_->UpdateConstraintVariableUsage(c);
2444     }
2445 
2446     // Substitute in objective.
2447     // This can only fail in corner cases.
2448     if (is_in_objective &&
2449         !context_->SubstituteVariableInObjective(var, var_coeff, *ct)) {
2450       continue;
2451     }
2452 
2453     context_->UpdateRuleStats(
2454         absl::StrCat("linear: variable substitution ", others.size()));
2455 
2456     // The variable now only appear in its definition and we can remove it
2457     // because it was implied free.
2458     //
2459     // Tricky: If the linear constraint contains other variables that are only
2460     // used here, then the postsolve needs more info. We do need to indicate
2461     // that whatever the value of those other variables, we will have a way to
2462     // assign var. We do that by putting it fist.
2463     CHECK_EQ(context_->VarToConstraints(var).size(), 1);
2464     context_->MarkVariableAsRemoved(var);
2465     const int ct_index = context_->mapping_model->constraints().size();
2466     *context_->mapping_model->add_constraints() = *ct;
2467     LinearConstraintProto* mapping_linear_ct =
2468         context_->mapping_model->mutable_constraints(ct_index)
2469             ->mutable_linear();
2470     std::swap(mapping_linear_ct->mutable_vars()->at(0),
2471               mapping_linear_ct->mutable_vars()->at(i));
2472     std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
2473               mapping_linear_ct->mutable_coeffs()->at(i));
2474     return RemoveConstraint(ct);
2475   }
2476   if (new_bounds) {
2477     context_->UpdateRuleStats("linear: reduced variable domains");
2478   }
2479   if (recanonicalize) return CanonicalizeLinear(ct);
2480   return false;
2481 }
2482 
2483 // Identify Boolean variable that makes the constraint always true when set to
2484 // true or false. Moves such literal to the constraint enforcement literals
2485 // list.
2486 //
2487 // We also generalize this to integer variable at one of their bound.
2488 //
2489 // This operation is similar to coefficient strengthening in the MIP world.
2490 void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
2491     int ct_index, ConstraintProto* ct) {
2492   if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
2493       context_->ModelIsUnsat()) {
2494     return;
2495   }
2496 
2497   const LinearConstraintProto& arg = ct->linear();
2498   const int num_vars = arg.vars_size();
2499 
2500   // No need to process size one constraints, they will be presolved separately.
2501   // We also do not want to split them in two.
2502   if (num_vars <= 1) return;
2503 
2504   int64_t min_sum = 0;
2505   int64_t max_sum = 0;
2506   int64_t max_coeff_magnitude = 0;
2507   for (int i = 0; i < num_vars; ++i) {
2508     const int ref = arg.vars(i);
2509     const int64_t coeff = arg.coeffs(i);
2510     const int64_t term_a = coeff * context_->MinOf(ref);
2511     const int64_t term_b = coeff * context_->MaxOf(ref);
2512     max_coeff_magnitude = std::max(max_coeff_magnitude, std::abs(coeff));
2513     min_sum += std::min(term_a, term_b);
2514     max_sum += std::max(term_a, term_b);
2515   }
2516 
2517   // We can only extract enforcement literals if the maximum coefficient
2518   // magnitude is large enough. Note that we handle complex domain.
2519   //
2520   // TODO(user): Depending on how we split below, the threshold are not the
2521   // same. This is maybe not too important, we just don't split as often as we
2522   // could, but it is still unclear if splitting is good.
2523   const auto& domain = ct->linear().domain();
2524   const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
2525   const int64_t lb_threshold = max_sum - domain[1];
2526   const Domain rhs_domain = ReadDomainFromProto(ct->linear());
2527   if (max_coeff_magnitude < std::max(ub_threshold, lb_threshold)) return;
2528 
2529   // We need the constraint to be only bounded on one side in order to extract
2530   // enforcement literal.
2531   //
2532   // If it is boxed and we know that some coefficient are big enough (see test
2533   // above), then we split the constraint in two. That might not seems always
2534   // good, but for the CP propagation engine, we don't loose anything by doing
2535   // so, and for the LP we will regroup the constraints if they still have the
2536   // exact same coeff after the presolve.
2537   //
2538   // TODO(user): Creating two new constraints and removing the current one might
2539   // not be the most efficient, but it simplify the presolve code by not having
2540   // to do anything special to trigger a new presolving of these constraints.
2541   // Try to improve if this becomes a problem.
2542   //
2543   // TODO(user): At the end of the presolve we should probably remerge any
2544   // identical linear constraints. That also cover the corner cases where
2545   // constraints are just redundant...
2546   const bool lower_bounded = min_sum < rhs_domain.Min();
2547   const bool upper_bounded = max_sum > rhs_domain.Max();
2548   if (!lower_bounded && !upper_bounded) return;
2549   if (lower_bounded && upper_bounded) {
2550     context_->UpdateRuleStats("linear: split boxed constraint");
2551     ConstraintProto* new_ct1 = context_->working_model->add_constraints();
2552     *new_ct1 = *ct;
2553     if (!ct->name().empty()) {
2554       new_ct1->set_name(absl::StrCat(ct->name(), " (part 1)"));
2555     }
2556     FillDomainInProto(Domain(min_sum, rhs_domain.Max()),
2557                       new_ct1->mutable_linear());
2558 
2559     ConstraintProto* new_ct2 = context_->working_model->add_constraints();
2560     *new_ct2 = *ct;
2561     if (!ct->name().empty()) {
2562       new_ct2->set_name(absl::StrCat(ct->name(), " (part 2)"));
2563     }
2564     FillDomainInProto(rhs_domain.UnionWith(Domain(rhs_domain.Max(), max_sum)),
2565                       new_ct2->mutable_linear());
2566 
2567     context_->UpdateNewConstraintsVariableUsage();
2568     return (void)RemoveConstraint(ct);
2569   }
2570 
2571   // Any coefficient greater than this will cause the constraint to be trivially
2572   // satisfied when the variable move away from its bound. Note that as we
2573   // remove coefficient, the threshold do not change!
2574   const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
2575 
2576   // Do we only extract Booleans?
2577   //
2578   // Note that for now the default is false, and also there are problem calling
2579   // GetOrCreateVarValueEncoding() after expansion because we might have removed
2580   // the variable used in the encoding.
2581   const bool only_booleans =
2582       !context_->params().presolve_extract_integer_enforcement() ||
2583       context_->ModelIsExpanded();
2584 
2585   // To avoid a quadratic loop, we will rewrite the linear expression at the
2586   // same time as we extract enforcement literals.
2587   int new_size = 0;
2588   int64_t rhs_offset = 0;
2589   bool some_integer_encoding_were_extracted = false;
2590   LinearConstraintProto* mutable_arg = ct->mutable_linear();
2591   for (int i = 0; i < arg.vars_size(); ++i) {
2592     int ref = arg.vars(i);
2593     int64_t coeff = arg.coeffs(i);
2594     if (coeff < 0) {
2595       ref = NegatedRef(ref);
2596       coeff = -coeff;
2597     }
2598 
2599     const bool is_boolean = context_->CanBeUsedAsLiteral(ref);
2600     if (context_->IsFixed(ref) || coeff < threshold ||
2601         (only_booleans && !is_boolean)) {
2602       // We keep this term.
2603       mutable_arg->set_vars(new_size, mutable_arg->vars(i));
2604       mutable_arg->set_coeffs(new_size, mutable_arg->coeffs(i));
2605       ++new_size;
2606       continue;
2607     }
2608 
2609     if (is_boolean) {
2610       context_->UpdateRuleStats("linear: extracted enforcement literal");
2611     } else {
2612       some_integer_encoding_were_extracted = true;
2613       context_->UpdateRuleStats(
2614           "linear: extracted integer enforcement literal");
2615     }
2616     if (lower_bounded) {
2617       ct->add_enforcement_literal(is_boolean
2618                                       ? NegatedRef(ref)
2619                                       : context_->GetOrCreateVarValueEncoding(
2620                                             ref, context_->MinOf(ref)));
2621       rhs_offset -= coeff * context_->MinOf(ref);
2622     } else {
2623       ct->add_enforcement_literal(is_boolean
2624                                       ? ref
2625                                       : context_->GetOrCreateVarValueEncoding(
2626                                             ref, context_->MaxOf(ref)));
2627       rhs_offset -= coeff * context_->MaxOf(ref);
2628     }
2629   }
2630   mutable_arg->mutable_vars()->Truncate(new_size);
2631   mutable_arg->mutable_coeffs()->Truncate(new_size);
2632   FillDomainInProto(rhs_domain.AdditionWith(Domain(rhs_offset)), mutable_arg);
2633   if (some_integer_encoding_were_extracted) {
2634     context_->UpdateNewConstraintsVariableUsage();
2635     context_->UpdateConstraintVariableUsage(ct_index);
2636   }
2637 }
2638 
2639 void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto* ct) {
2640   if (context_->ModelIsUnsat()) return;
2641   if (HasEnforcementLiteral(*ct)) return;
2642   const Domain rhs = ReadDomainFromProto(ct->linear());
2643 
2644   const LinearConstraintProto& arg = ct->linear();
2645   const int num_vars = arg.vars_size();
2646   int64_t min_sum = 0;
2647   int64_t max_sum = 0;
2648   for (int i = 0; i < num_vars; ++i) {
2649     const int ref = arg.vars(i);
2650     const int64_t coeff = arg.coeffs(i);
2651     const int64_t term_a = coeff * context_->MinOf(ref);
2652     const int64_t term_b = coeff * context_->MaxOf(ref);
2653     min_sum += std::min(term_a, term_b);
2654     max_sum += std::max(term_a, term_b);
2655   }
2656   for (const int type : {0, 1}) {
2657     std::vector<int> at_most_one;
2658     for (int i = 0; i < num_vars; ++i) {
2659       const int ref = arg.vars(i);
2660       const int64_t coeff = arg.coeffs(i);
2661       if (context_->MinOf(ref) != 0) continue;
2662       if (context_->MaxOf(ref) != 1) continue;
2663 
2664       if (type == 0) {
2665         // TODO(user): we could add one more Boolean with a lower coeff as long
2666         // as we have lower_coeff + min_of_other_coeff > rhs.Max().
2667         if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
2668           at_most_one.push_back(coeff > 0 ? ref : NegatedRef(ref));
2669         }
2670       } else {
2671         if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
2672           at_most_one.push_back(coeff > 0 ? NegatedRef(ref) : ref);
2673         }
2674       }
2675     }
2676     if (at_most_one.size() > 1) {
2677       if (type == 0) {
2678         context_->UpdateRuleStats("linear: extracted at most one (max).");
2679       } else {
2680         context_->UpdateRuleStats("linear: extracted at most one (min).");
2681       }
2682       ConstraintProto* new_ct = context_->working_model->add_constraints();
2683       new_ct->set_name(ct->name());
2684       for (const int ref : at_most_one) {
2685         new_ct->mutable_at_most_one()->add_literals(ref);
2686       }
2687       context_->UpdateNewConstraintsVariableUsage();
2688     }
2689   }
2690 }
2691 
2692 // Convert some linear constraint involving only Booleans to their Boolean
2693 // form.
2694 bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto* ct) {
2695   if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
2696       context_->ModelIsUnsat()) {
2697     return false;
2698   }
2699 
2700   const LinearConstraintProto& arg = ct->linear();
2701   const int num_vars = arg.vars_size();
2702   int64_t min_coeff = std::numeric_limits<int64_t>::max();
2703   int64_t max_coeff = 0;
2704   int64_t min_sum = 0;
2705   int64_t max_sum = 0;
2706   for (int i = 0; i < num_vars; ++i) {
2707     // We assume we already ran PresolveLinear().
2708     const int var = arg.vars(i);
2709     const int64_t coeff = arg.coeffs(i);
2710     CHECK(RefIsPositive(var));
2711     CHECK_NE(coeff, 0);
2712     if (context_->MinOf(var) != 0) return false;
2713     if (context_->MaxOf(var) != 1) return false;
2714 
2715     if (coeff > 0) {
2716       max_sum += coeff;
2717       min_coeff = std::min(min_coeff, coeff);
2718       max_coeff = std::max(max_coeff, coeff);
2719     } else {
2720       // We replace the Boolean ref, by a ref to its negation (1 - x).
2721       min_sum += coeff;
2722       min_coeff = std::min(min_coeff, -coeff);
2723       max_coeff = std::max(max_coeff, -coeff);
2724     }
2725   }
2726   CHECK_LE(min_coeff, max_coeff);
2727 
2728   // Detect trivially true/false constraints. Note that this is not necessarily
2729   // detected by PresolveLinear(). We do that here because we assume below
2730   // that this cannot happen.
2731   //
2732   // TODO(user): this could be generalized to constraint not containing only
2733   // Booleans.
2734   const Domain rhs_domain = ReadDomainFromProto(arg);
2735   if ((!rhs_domain.Contains(min_sum) &&
2736        min_sum + min_coeff > rhs_domain.Max()) ||
2737       (!rhs_domain.Contains(max_sum) &&
2738        max_sum - min_coeff < rhs_domain.Min())) {
2739     context_->UpdateRuleStats("linear: all booleans and trivially false");
2740     return MarkConstraintAsFalse(ct);
2741   }
2742   if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
2743     context_->UpdateRuleStats("linear: all booleans and trivially true");
2744     return RemoveConstraint(ct);
2745   }
2746 
2747   // Detect clauses, reified ands, at_most_one.
2748   //
2749   // TODO(user): split a == 1 constraint or similar into a clause and an at
2750   // most one constraint?
2751   DCHECK(!rhs_domain.IsEmpty());
2752   if (min_sum + min_coeff > rhs_domain.Max()) {
2753     // All Boolean are false if the reified literal is true.
2754     context_->UpdateRuleStats("linear: negative reified and");
2755     const auto copy = arg;
2756     ct->mutable_bool_and()->clear_literals();
2757     for (int i = 0; i < num_vars; ++i) {
2758       ct->mutable_bool_and()->add_literals(
2759           copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
2760     }
2761     PresolveBoolAnd(ct);
2762     return true;
2763   } else if (max_sum - min_coeff < rhs_domain.Min()) {
2764     // All Boolean are true if the reified literal is true.
2765     context_->UpdateRuleStats("linear: positive reified and");
2766     const auto copy = arg;
2767     ct->mutable_bool_and()->clear_literals();
2768     for (int i = 0; i < num_vars; ++i) {
2769       ct->mutable_bool_and()->add_literals(
2770           copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
2771     }
2772     PresolveBoolAnd(ct);
2773     return true;
2774   } else if (min_sum + min_coeff >= rhs_domain.Min() &&
2775              rhs_domain.front().end >= max_sum) {
2776     // At least one Boolean is true.
2777     context_->UpdateRuleStats("linear: positive clause");
2778     const auto copy = arg;
2779     ct->mutable_bool_or()->clear_literals();
2780     for (int i = 0; i < num_vars; ++i) {
2781       ct->mutable_bool_or()->add_literals(
2782           copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
2783     }
2784     PresolveBoolOr(ct);
2785     return true;
2786   } else if (max_sum - min_coeff <= rhs_domain.Max() &&
2787              rhs_domain.back().start <= min_sum) {
2788     // At least one Boolean is false.
2789     context_->UpdateRuleStats("linear: negative clause");
2790     const auto copy = arg;
2791     ct->mutable_bool_or()->clear_literals();
2792     for (int i = 0; i < num_vars; ++i) {
2793       ct->mutable_bool_or()->add_literals(
2794           copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
2795     }
2796     PresolveBoolOr(ct);
2797     return true;
2798   } else if (!HasEnforcementLiteral(*ct) &&
2799              min_sum + max_coeff <= rhs_domain.Max() &&
2800              min_sum + 2 * min_coeff > rhs_domain.Max() &&
2801              rhs_domain.back().start <= min_sum) {
2802     // At most one Boolean is true.
2803     context_->UpdateRuleStats("linear: positive at most one");
2804     const auto copy = arg;
2805     ct->mutable_at_most_one()->clear_literals();
2806     for (int i = 0; i < num_vars; ++i) {
2807       ct->mutable_at_most_one()->add_literals(
2808           copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
2809     }
2810     return true;
2811   } else if (!HasEnforcementLiteral(*ct) &&
2812              max_sum - max_coeff >= rhs_domain.Min() &&
2813              max_sum - 2 * min_coeff < rhs_domain.Min() &&
2814              rhs_domain.front().end >= max_sum) {
2815     // At most one Boolean is false.
2816     context_->UpdateRuleStats("linear: negative at most one");
2817     const auto copy = arg;
2818     ct->mutable_at_most_one()->clear_literals();
2819     for (int i = 0; i < num_vars; ++i) {
2820       ct->mutable_at_most_one()->add_literals(
2821           copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
2822     }
2823     return true;
2824   } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
2825              min_sum < rhs_domain.Min() &&
2826              min_sum + min_coeff >= rhs_domain.Min() &&
2827              min_sum + 2 * min_coeff > rhs_domain.Max() &&
2828              min_sum + max_coeff <= rhs_domain.Max()) {
2829     context_->UpdateRuleStats("linear: positive equal one");
2830     ConstraintProto* exactly_one = context_->working_model->add_constraints();
2831     exactly_one->set_name(ct->name());
2832     for (int i = 0; i < num_vars; ++i) {
2833       exactly_one->mutable_exactly_one()->add_literals(
2834           arg.coeffs(i) > 0 ? arg.vars(i) : NegatedRef(arg.vars(i)));
2835     }
2836     context_->UpdateNewConstraintsVariableUsage();
2837     return RemoveConstraint(ct);
2838   } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
2839              max_sum > rhs_domain.Max() &&
2840              max_sum - min_coeff <= rhs_domain.Max() &&
2841              max_sum - 2 * min_coeff < rhs_domain.Min() &&
2842              max_sum - max_coeff >= rhs_domain.Min()) {
2843     context_->UpdateRuleStats("linear: negative equal one");
2844     ConstraintProto* exactly_one = context_->working_model->add_constraints();
2845     exactly_one->set_name(ct->name());
2846     for (int i = 0; i < num_vars; ++i) {
2847       exactly_one->mutable_exactly_one()->add_literals(
2848           arg.coeffs(i) > 0 ? NegatedRef(arg.vars(i)) : arg.vars(i));
2849     }
2850     context_->UpdateNewConstraintsVariableUsage();
2851     return RemoveConstraint(ct);
2852   }
2853 
2854   // Expand small expression into clause.
2855   //
2856   // TODO(user): This is bad from a LP relaxation perspective. Do not do that
2857   // now? On another hand it is good for the SAT presolving.
2858   if (num_vars > 3) return false;
2859   context_->UpdateRuleStats("linear: small Boolean expression");
2860 
2861   // Enumerate all possible value of the Booleans and add a clause if constraint
2862   // is false. TODO(user): the encoding could be made better in some cases.
2863   const int max_mask = (1 << arg.vars_size());
2864   for (int mask = 0; mask < max_mask; ++mask) {
2865     int64_t value = 0;
2866     for (int i = 0; i < num_vars; ++i) {
2867       if ((mask >> i) & 1) value += arg.coeffs(i);
2868     }
2869     if (rhs_domain.Contains(value)) continue;
2870 
2871     // Add a new clause to exclude this bad assignment.
2872     ConstraintProto* new_ct = context_->working_model->add_constraints();
2873     auto* new_arg = new_ct->mutable_bool_or();
2874     if (HasEnforcementLiteral(*ct)) {
2875       *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2876     }
2877     for (int i = 0; i < num_vars; ++i) {
2878       new_arg->add_literals(((mask >> i) & 1) ? NegatedRef(arg.vars(i))
2879                                               : arg.vars(i));
2880     }
2881   }
2882 
2883   context_->UpdateNewConstraintsVariableUsage();
2884   return RemoveConstraint(ct);
2885 }
2886 
2887 bool CpModelPresolver::PresolveInterval(int c, ConstraintProto* ct) {
2888   if (context_->ModelIsUnsat()) return false;
2889   IntervalConstraintProto* interval = ct->mutable_interval();
2890 
2891   // If the size is < 0, then the interval cannot be performed.
2892   if (!ct->enforcement_literal().empty() && context_->SizeMax(c) < 0) {
2893     context_->UpdateRuleStats("interval: negative size implies unperformed");
2894     return MarkConstraintAsFalse(ct);
2895   }
2896 
2897   bool changed = false;
2898   if (ct->enforcement_literal().empty()) {
2899     // Size can't be negative.
2900     if (!context_->IntersectDomainWith(
2901             interval->size(), Domain(0, std::numeric_limits<int64_t>::max()),
2902             &changed)) {
2903       return false;
2904     }
2905     context_->UpdateRuleStats(
2906         "interval: performed intervals must have a positive size");
2907   }
2908 
2909   changed |= CanonicalizeLinearExpression(*ct, interval->mutable_start());
2910   changed |= CanonicalizeLinearExpression(*ct, interval->mutable_size());
2911   changed |= CanonicalizeLinearExpression(*ct, interval->mutable_end());
2912   return changed;
2913 }
2914 
2915 // TODO(user): avoid code duplication between expand and presolve.
2916 bool CpModelPresolver::PresolveInverse(ConstraintProto* ct) {
2917   const int size = ct->inverse().f_direct().size();
2918   bool changed = false;
2919 
2920   // Make sure the domains are included in [0, size - 1).
2921   for (const int ref : ct->inverse().f_direct()) {
2922     if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
2923       VLOG(1) << "Empty domain for a variable in ExpandInverse()";
2924       return false;
2925     }
2926   }
2927   for (const int ref : ct->inverse().f_inverse()) {
2928     if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
2929       VLOG(1) << "Empty domain for a variable in ExpandInverse()";
2930       return false;
2931     }
2932   }
2933 
2934   // Detect duplicated variable.
2935   // Even with negated variables, the reduced domain in [0..size - 1]
2936   // implies that the constraint is infeasible if ref and its negation
2937   // appear together.
2938   {
2939     absl::flat_hash_set<int> direct_vars;
2940     for (const int ref : ct->inverse().f_direct()) {
2941       const auto [it, inserted] = direct_vars.insert(PositiveRef(ref));
2942       if (!inserted) {
2943         return context_->NotifyThatModelIsUnsat("inverse: duplicated variable");
2944       }
2945     }
2946 
2947     absl::flat_hash_set<int> inverse_vars;
2948     for (const int ref : ct->inverse().f_inverse()) {
2949       const auto [it, inserted] = inverse_vars.insert(PositiveRef(ref));
2950       if (!inserted) {
2951         return context_->NotifyThatModelIsUnsat("inverse: duplicated variable");
2952       }
2953     }
2954   }
2955 
2956   // Propagate from one vector to its counterpart.
2957   // Note this reaches the fixpoint as there is a one to one mapping between
2958   // (variable-value) pairs in each vector.
2959   const auto filter_inverse_domain =
2960       [this, size, &changed](const auto& direct, const auto& inverse) {
2961         // Build the set of values in the inverse vector.
2962         std::vector<absl::flat_hash_set<int64_t>> inverse_values(size);
2963         for (int i = 0; i < size; ++i) {
2964           const Domain domain = context_->DomainOf(inverse[i]);
2965           for (const int64_t j : domain.Values()) {
2966             inverse_values[i].insert(j);
2967           }
2968         }
2969 
2970         // Propagate from the inverse vector to the direct vector. Reduce the
2971         // domains of each variable in the direct vector by checking that the
2972         // inverse value exists.
2973         std::vector<int64_t> possible_values;
2974         for (int i = 0; i < size; ++i) {
2975           possible_values.clear();
2976           const Domain domain = context_->DomainOf(direct[i]);
2977           bool removed_value = false;
2978           for (const int64_t j : domain.Values()) {
2979             if (inverse_values[j].contains(i)) {
2980               possible_values.push_back(j);
2981             } else {
2982               removed_value = true;
2983             }
2984           }
2985           if (removed_value) {
2986             changed = true;
2987             if (!context_->IntersectDomainWith(
2988                     direct[i], Domain::FromValues(possible_values))) {
2989               VLOG(1) << "Empty domain for a variable in ExpandInverse()";
2990               return false;
2991             }
2992           }
2993         }
2994         return true;
2995       };
2996 
2997   if (!filter_inverse_domain(ct->inverse().f_direct(),
2998                              ct->inverse().f_inverse())) {
2999     return false;
3000   }
3001 
3002   if (!filter_inverse_domain(ct->inverse().f_inverse(),
3003                              ct->inverse().f_direct())) {
3004     return false;
3005   }
3006 
3007   if (changed) {
3008     context_->UpdateRuleStats("inverse: reduce domains");
3009   }
3010 
3011   return false;
3012 }
3013 
3014 bool CpModelPresolver::PresolveElement(ConstraintProto* ct) {
3015   if (context_->ModelIsUnsat()) return false;
3016 
3017   if (ct->element().vars().empty()) {
3018     context_->UpdateRuleStats("element: empty array");
3019     return context_->NotifyThatModelIsUnsat();
3020   }
3021 
3022   const int index_ref = ct->element().index();
3023   const int target_ref = ct->element().target();
3024 
3025   // TODO(user): think about this once we do have such constraint.
3026   if (HasEnforcementLiteral(*ct)) return false;
3027 
3028   bool all_constants = true;
3029   absl::flat_hash_set<int64_t> constant_set;
3030   bool all_included_in_target_domain = true;
3031 
3032   {
3033     bool reduced_index_domain = false;
3034     if (!context_->IntersectDomainWith(index_ref,
3035                                        Domain(0, ct->element().vars_size() - 1),
3036                                        &reduced_index_domain)) {
3037       return false;
3038     }
3039 
3040     // Filter impossible index values if index == +/- target
3041     //
3042     // Note that this must be done before the unique_index/target rule.
3043     if (PositiveRef(target_ref) == PositiveRef(index_ref)) {
3044       std::vector<int64_t> possible_indices;
3045       const Domain& index_domain = context_->DomainOf(index_ref);
3046       for (const int64_t index_value : index_domain.Values()) {
3047         const int ref = ct->element().vars(index_value);
3048         const int64_t target_value =
3049             target_ref == index_ref ? index_value : -index_value;
3050         if (context_->DomainContains(ref, target_value)) {
3051           possible_indices.push_back(target_value);
3052         }
3053       }
3054       if (possible_indices.size() < index_domain.Size()) {
3055         if (!context_->IntersectDomainWith(
3056                 index_ref, Domain::FromValues(possible_indices))) {
3057           return true;
3058         }
3059         context_->UpdateRuleStats(
3060             "element: reduced index domain when target equals index");
3061       }
3062     }
3063 
3064     // Filter possible index values. Accumulate variable domains to build
3065     // a possible target domain.
3066     Domain infered_domain;
3067     const Domain& initial_index_domain = context_->DomainOf(index_ref);
3068     const Domain& target_domain = context_->DomainOf(target_ref);
3069     std::vector<int64_t> possible_indices;
3070     for (const int64_t value : initial_index_domain.Values()) {
3071       CHECK_GE(value, 0);
3072       CHECK_LT(value, ct->element().vars_size());
3073       const int ref = ct->element().vars(value);
3074       const Domain& domain = context_->DomainOf(ref);
3075       if (domain.IntersectionWith(target_domain).IsEmpty()) continue;
3076       possible_indices.push_back(value);
3077       if (domain.IsFixed()) {
3078         constant_set.insert(domain.Min());
3079       } else {
3080         all_constants = false;
3081       }
3082       if (!domain.IsIncludedIn(target_domain)) {
3083         all_included_in_target_domain = false;
3084       }
3085       infered_domain = infered_domain.UnionWith(domain);
3086     }
3087     if (possible_indices.size() < initial_index_domain.Size()) {
3088       if (!context_->IntersectDomainWith(
3089               index_ref, Domain::FromValues(possible_indices))) {
3090         return true;
3091       }
3092       context_->UpdateRuleStats("element: reduced index domain");
3093     }
3094     bool domain_modified = false;
3095     if (!context_->IntersectDomainWith(target_ref, infered_domain,
3096                                        &domain_modified)) {
3097       return true;
3098     }
3099     if (domain_modified) {
3100       context_->UpdateRuleStats("element: reduced target domain");
3101     }
3102   }
3103 
3104   // If the index is fixed, this is a equality constraint.
3105   if (context_->IsFixed(index_ref)) {
3106     const int var = ct->element().vars(context_->MinOf(index_ref));
3107     if (var != target_ref) {
3108       LinearConstraintProto* const lin =
3109           context_->working_model->add_constraints()->mutable_linear();
3110       lin->add_vars(var);
3111       lin->add_coeffs(-1);
3112       lin->add_vars(target_ref);
3113       lin->add_coeffs(1);
3114       lin->add_domain(0);
3115       lin->add_domain(0);
3116       context_->UpdateNewConstraintsVariableUsage();
3117     }
3118     context_->UpdateRuleStats("element: fixed index");
3119     return RemoveConstraint(ct);
3120   }
3121 
3122   // If the accessible part of the array is made of a single constant value,
3123   // then we do not care about the index. And, because of the previous target
3124   // domain reduction, the target is also fixed.
3125   if (all_constants && constant_set.size() == 1) {
3126     CHECK(context_->IsFixed(target_ref));
3127     context_->UpdateRuleStats("element: one value array");
3128     return RemoveConstraint(ct);
3129   }
3130 
3131   // Special case when the index is boolean, and the array does not contain
3132   // variables.
3133   if (context_->MinOf(index_ref) == 0 && context_->MaxOf(index_ref) == 1 &&
3134       all_constants) {
3135     const int64_t v0 = context_->MinOf(ct->element().vars(0));
3136     const int64_t v1 = context_->MinOf(ct->element().vars(1));
3137 
3138     LinearConstraintProto* const lin =
3139         context_->working_model->add_constraints()->mutable_linear();
3140     lin->add_vars(target_ref);
3141     lin->add_coeffs(1);
3142     lin->add_vars(index_ref);
3143     lin->add_coeffs(v0 - v1);
3144     lin->add_domain(v0);
3145     lin->add_domain(v0);
3146     context_->UpdateNewConstraintsVariableUsage();
3147     context_->UpdateRuleStats("element: linearize constant element of size 2");
3148     return RemoveConstraint(ct);
3149   }
3150 
3151   // If the index has a canonical affine representative, rewrite the element.
3152   const AffineRelation::Relation r_index =
3153       context_->GetAffineRelation(index_ref);
3154   if (r_index.representative != index_ref) {
3155     // Checks the domains are synchronized.
3156     if (context_->DomainOf(r_index.representative).Size() >
3157         context_->DomainOf(index_ref).Size()) {
3158       // Postpone, we will come back later when domains are synchronized.
3159       return true;
3160     }
3161     const int r_ref = r_index.representative;
3162     const int64_t r_min = context_->MinOf(r_ref);
3163     const int64_t r_max = context_->MaxOf(r_ref);
3164     const int array_size = ct->element().vars_size();
3165     if (r_min != 0) {
3166       context_->UpdateRuleStats("TODO element: representative has bad domain");
3167     } else if (r_index.offset >= 0 && r_index.offset < array_size &&
3168                r_index.offset + r_max * r_index.coeff >= 0 &&
3169                r_index.offset + r_max * r_index.coeff < array_size) {
3170       // This will happen eventually when domains are synchronized.
3171       ElementConstraintProto* const element =
3172           context_->working_model->add_constraints()->mutable_element();
3173       for (int64_t v = 0; v <= r_max; ++v) {
3174         const int64_t scaled_index = v * r_index.coeff + r_index.offset;
3175         CHECK_GE(scaled_index, 0);
3176         CHECK_LT(scaled_index, array_size);
3177         element->add_vars(ct->element().vars(scaled_index));
3178       }
3179       element->set_index(r_ref);
3180       element->set_target(target_ref);
3181 
3182       if (r_index.coeff == 1) {
3183         context_->UpdateRuleStats("element: shifed index ");
3184       } else {
3185         context_->UpdateRuleStats("element: scaled index");
3186       }
3187       context_->UpdateNewConstraintsVariableUsage();
3188       return RemoveConstraint(ct);
3189     }
3190   }
3191 
3192   // Should have been taken care of ealier.
3193   DCHECK(!context_->IsFixed(index_ref));
3194 
3195   // If a variable (target or index) appears only in this constraint, it does
3196   // not necessarily mean that we can remove the constraint, as the variable
3197   // can be used multiple time_exprs in the element. So let's count the local
3198   // uses of each variable.
3199   absl::flat_hash_map<int, int> local_var_occurrence_counter;
3200   local_var_occurrence_counter[PositiveRef(index_ref)]++;
3201   local_var_occurrence_counter[PositiveRef(target_ref)]++;
3202 
3203   for (const ClosedInterval interval : context_->DomainOf(index_ref)) {
3204     for (int64_t value = interval.start; value <= interval.end; ++value) {
3205       DCHECK_GE(value, 0);
3206       DCHECK_LT(value, ct->element().vars_size());
3207       const int ref = ct->element().vars(value);
3208       local_var_occurrence_counter[PositiveRef(ref)]++;
3209     }
3210   }
3211 
3212   if (context_->VariableIsUniqueAndRemovable(index_ref) &&
3213       local_var_occurrence_counter.at(PositiveRef(index_ref)) == 1) {
3214     if (all_constants) {
3215       // This constraint is just here to reduce the domain of the target! We can
3216       // add it to the mapping_model to reconstruct the index value during
3217       // postsolve and get rid of it now.
3218       context_->UpdateRuleStats("element: trivial target domain reduction");
3219       context_->MarkVariableAsRemoved(index_ref);
3220       *(context_->mapping_model->add_constraints()) = *ct;
3221       return RemoveConstraint(ct);
3222     } else {
3223       context_->UpdateRuleStats("TODO element: index not used elsewhere");
3224     }
3225   }
3226 
3227   if (!context_->IsFixed(target_ref) &&
3228       context_->VariableIsUniqueAndRemovable(target_ref) &&
3229       local_var_occurrence_counter.at(PositiveRef(target_ref)) == 1) {
3230     if (all_included_in_target_domain) {
3231       context_->UpdateRuleStats("element: trivial index domain reduction");
3232       context_->MarkVariableAsRemoved(target_ref);
3233       *(context_->mapping_model->add_constraints()) = *ct;
3234       return RemoveConstraint(ct);
3235     } else {
3236       context_->UpdateRuleStats("TODO element: target not used elsewhere");
3237     }
3238   }
3239 
3240   return false;
3241 }
3242 
3243 bool CpModelPresolver::PresolveTable(ConstraintProto* ct) {
3244   if (context_->ModelIsUnsat()) return false;
3245   if (HasEnforcementLiteral(*ct)) return false;
3246   if (ct->table().vars().empty()) {
3247     context_->UpdateRuleStats("table: empty constraint");
3248     return RemoveConstraint(ct);
3249   }
3250 
3251   const int initial_num_vars = ct->table().vars_size();
3252   bool changed = true;
3253 
3254   // Query existing affine relations.
3255   std::vector<AffineRelation::Relation> affine_relations;
3256   std::vector<int64_t> old_var_lb;
3257   std::vector<int64_t> old_var_ub;
3258   {
3259     for (int v = 0; v < initial_num_vars; ++v) {
3260       const int ref = ct->table().vars(v);
3261       AffineRelation::Relation r = context_->GetAffineRelation(ref);
3262       affine_relations.push_back(r);
3263       old_var_lb.push_back(context_->MinOf(ref));
3264       old_var_ub.push_back(context_->MaxOf(ref));
3265       if (r.representative != ref) {
3266         changed = true;
3267         ct->mutable_table()->set_vars(v, r.representative);
3268         context_->UpdateRuleStats(
3269             "table: replace variable by canonical affine one");
3270       }
3271     }
3272   }
3273 
3274   // Check for duplicate occurrences of variables.
3275   // If the ith index is -1, then the variable is not a duplicate of a smaller
3276   // index variable. It if is != from -1, then the values stored is the new
3277   // index of the first occurrence of the variable.
3278   std::vector<int> old_index_of_duplicate_to_new_index_of_first_occurrence(
3279       initial_num_vars, -1);
3280   // If == -1, then the variable is a duplicate of a smaller index variable.
3281   std::vector<int> old_index_to_new_index(initial_num_vars, -1);
3282   int num_vars = 0;
3283   {
3284     absl::flat_hash_map<int, int> first_visit;
3285     for (int p = 0; p < initial_num_vars; ++p) {
3286       const int ref = ct->table().vars(p);
3287       const int var = PositiveRef(ref);
3288       const auto& it = first_visit.find(var);
3289       if (it != first_visit.end()) {
3290         const int previous = it->second;
3291         old_index_of_duplicate_to_new_index_of_first_occurrence[p] = previous;
3292         context_->UpdateRuleStats("table: duplicate variables");
3293         changed = true;
3294       } else {
3295         ct->mutable_table()->set_vars(num_vars, ref);
3296         first_visit[var] = num_vars;
3297         old_index_to_new_index[p] = num_vars;
3298         num_vars++;
3299       }
3300     }
3301 
3302     if (num_vars < initial_num_vars) {
3303       ct->mutable_table()->mutable_vars()->Truncate(num_vars);
3304     }
3305   }
3306 
3307   // Check each tuple for validity w.r.t. affine relations, variable domains,
3308   // and consistency with duplicate variables. Reduce the size of the tuple in
3309   // case of duplicate variables.
3310   std::vector<std::vector<int64_t>> new_tuples;
3311   const int initial_num_tuples = ct->table().values_size() / initial_num_vars;
3312   std::vector<absl::flat_hash_set<int64_t>> new_domains(num_vars);
3313 
3314   {
3315     std::vector<int64_t> tuple(num_vars);
3316     new_tuples.reserve(initial_num_tuples);
3317     for (int i = 0; i < initial_num_tuples; ++i) {
3318       bool delete_row = false;
3319       std::string tmp;
3320       for (int j = 0; j < initial_num_vars; ++j) {
3321         const int64_t old_value = ct->table().values(i * initial_num_vars + j);
3322 
3323         // Corner case to avoid overflow, assuming the domain where already
3324         // propagated between a variable and its affine representative.
3325         if (old_value < old_var_lb[j] || old_value > old_var_ub[j]) {
3326           delete_row = true;
3327           break;
3328         }
3329 
3330         // Affine relations are defined on the initial variables.
3331         const AffineRelation::Relation& r = affine_relations[j];
3332         const int64_t value = (old_value - r.offset) / r.coeff;
3333         if (value * r.coeff + r.offset != old_value) {
3334           // Value not reachable by affine relation.
3335           delete_row = true;
3336           break;
3337         }
3338         const int mapped_position = old_index_to_new_index[j];
3339         if (mapped_position == -1) {  // The current variable is duplicate.
3340           const int new_index_of_first_occurrence =
3341               old_index_of_duplicate_to_new_index_of_first_occurrence[j];
3342           if (value != tuple[new_index_of_first_occurrence]) {
3343             delete_row = true;
3344             break;
3345           }
3346         } else {
3347           const int ref = ct->table().vars(mapped_position);
3348           if (!context_->DomainContains(ref, value)) {
3349             delete_row = true;
3350             break;
3351           }
3352           tuple[mapped_position] = value;
3353         }
3354       }
3355       if (delete_row) {
3356         changed = true;
3357         continue;
3358       }
3359       new_tuples.push_back(tuple);
3360       for (int j = 0; j < num_vars; ++j) {
3361         new_domains[j].insert(tuple[j]);
3362       }
3363     }
3364     gtl::STLSortAndRemoveDuplicates(&new_tuples);
3365     if (new_tuples.size() < initial_num_tuples) {
3366       context_->UpdateRuleStats("table: removed rows");
3367     }
3368   }
3369 
3370   // Update the list of tuples if needed.
3371   if (changed) {
3372     ct->mutable_table()->clear_values();
3373     for (const std::vector<int64_t>& t : new_tuples) {
3374       for (const int64_t v : t) {
3375         ct->mutable_table()->add_values(v);
3376       }
3377     }
3378   }
3379 
3380   // Nothing more to do for negated tables.
3381   if (ct->table().negated()) return changed;
3382 
3383   // Filter the variable domains.
3384   for (int j = 0; j < num_vars; ++j) {
3385     const int ref = ct->table().vars(j);
3386     if (!context_->IntersectDomainWith(
3387             PositiveRef(ref),
3388             Domain::FromValues(std::vector<int64_t>(new_domains[j].begin(),
3389                                                     new_domains[j].end())),
3390             &changed)) {
3391       return true;
3392     }
3393   }
3394   if (changed) {
3395     context_->UpdateRuleStats("table: reduced variable domains");
3396   }
3397   if (num_vars == 1) {
3398     // Now that we properly update the domain, we can remove the constraint.
3399     context_->UpdateRuleStats("table: only one column!");
3400     return RemoveConstraint(ct);
3401   }
3402 
3403   // Check that the table is not complete or just here to exclude a few tuples.
3404   double prod = 1.0;
3405   for (int j = 0; j < num_vars; ++j) prod *= new_domains[j].size();
3406   if (prod == new_tuples.size()) {
3407     context_->UpdateRuleStats("table: all tuples!");
3408     return RemoveConstraint(ct);
3409   }
3410 
3411   // Convert to the negated table if we gain a lot of entries by doing so.
3412   // Note however that currently the negated table do not propagate as much as
3413   // it could.
3414   if (new_tuples.size() > 0.7 * prod) {
3415     // Enumerate all tuples.
3416     std::vector<std::vector<int64_t>> var_to_values(num_vars);
3417     for (int j = 0; j < num_vars; ++j) {
3418       var_to_values[j].assign(new_domains[j].begin(), new_domains[j].end());
3419     }
3420     std::vector<std::vector<int64_t>> all_tuples(prod);
3421     for (int i = 0; i < prod; ++i) {
3422       all_tuples[i].resize(num_vars);
3423       int index = i;
3424       for (int j = 0; j < num_vars; ++j) {
3425         all_tuples[i][j] = var_to_values[j][index % var_to_values[j].size()];
3426         index /= var_to_values[j].size();
3427       }
3428     }
3429     gtl::STLSortAndRemoveDuplicates(&all_tuples);
3430 
3431     // Compute the complement of new_tuples.
3432     std::vector<std::vector<int64_t>> diff(prod - new_tuples.size());
3433     std::set_difference(all_tuples.begin(), all_tuples.end(),
3434                         new_tuples.begin(), new_tuples.end(), diff.begin());
3435 
3436     // Negate the constraint.
3437     ct->mutable_table()->set_negated(!ct->table().negated());
3438     ct->mutable_table()->clear_values();
3439     for (const std::vector<int64_t>& t : diff) {
3440       for (const int64_t v : t) ct->mutable_table()->add_values(v);
3441     }
3442     context_->UpdateRuleStats("table: negated");
3443   }
3444   return changed;
3445 }
3446 
3447 bool CpModelPresolver::PresolveAllDiff(ConstraintProto* ct) {
3448   if (context_->ModelIsUnsat()) return false;
3449   if (HasEnforcementLiteral(*ct)) return false;
3450 
3451   AllDifferentConstraintProto& all_diff = *ct->mutable_all_diff();
3452 
3453   bool constraint_has_changed = false;
3454   for (LinearExpressionProto& exp :
3455        *(ct->mutable_all_diff()->mutable_exprs())) {
3456     constraint_has_changed |= CanonicalizeLinearExpression(*ct, &exp);
3457   }
3458 
3459   for (;;) {
3460     const int size = all_diff.exprs_size();
3461     if (size == 0) {
3462       context_->UpdateRuleStats("all_diff: empty constraint");
3463       return RemoveConstraint(ct);
3464     }
3465     if (size == 1) {
3466       context_->UpdateRuleStats("all_diff: only one variable");
3467       return RemoveConstraint(ct);
3468     }
3469 
3470     bool something_was_propagated = false;
3471     std::vector<LinearExpressionProto> kept_expressions;
3472     for (int i = 0; i < size; ++i) {
3473       if (!context_->IsFixed(all_diff.exprs(i))) {
3474         kept_expressions.push_back(all_diff.exprs(i));
3475         continue;
3476       }
3477 
3478       const int64_t value = context_->MinOf(all_diff.exprs(i));
3479       bool propagated = false;
3480       for (int j = 0; j < size; ++j) {
3481         if (i == j) continue;
3482         if (context_->DomainContains(all_diff.exprs(j), value)) {
3483           if (!context_->IntersectDomainWith(all_diff.exprs(j),
3484                                              Domain(value).Complement())) {
3485             return true;
3486           }
3487           propagated = true;
3488         }
3489       }
3490       if (propagated) {
3491         context_->UpdateRuleStats("all_diff: propagated fixed expressions");
3492         something_was_propagated = true;
3493       }
3494     }
3495 
3496     // CanonicalizeLinearExpression() made sure that only positive variable
3497     // appears here, so this order will put expr and -expr one after the other.
3498     std::sort(
3499         kept_expressions.begin(), kept_expressions.end(),
3500         [](const LinearExpressionProto& expr_a,
3501            const LinearExpressionProto& expr_b) {
3502           DCHECK_EQ(expr_a.vars_size(), 1);
3503           DCHECK_EQ(expr_b.vars_size(), 1);
3504           const int ref_a = expr_a.vars(0);
3505           const int ref_b = expr_b.vars(0);
3506           const int64_t coeff_a = expr_a.coeffs(0);
3507           const int64_t coeff_b = expr_b.coeffs(0);
3508           const int64_t abs_coeff_a = std::abs(coeff_a);
3509           const int64_t abs_coeff_b = std::abs(coeff_b);
3510           const int64_t offset_a = expr_a.offset();
3511           const int64_t offset_b = expr_b.offset();
3512           const int64_t abs_offset_a = std::abs(offset_a);
3513           const int64_t abs_offset_b = std::abs(offset_b);
3514           return std::tie(ref_a, abs_coeff_a, coeff_a, abs_offset_a, offset_a) <
3515                  std::tie(ref_b, abs_coeff_b, coeff_b, abs_offset_b, offset_b);
3516         });
3517 
3518     // TODO(user): improve algorithm if of (a + offset) and (-a - offset)
3519     // might not be together if (a - offset) is present.
3520 
3521     for (int i = 1; i < kept_expressions.size(); ++i) {
3522       if (LinearExpressionProtosAreEqual(kept_expressions[i],
3523                                          kept_expressions[i - 1], 1)) {
3524         return context_->NotifyThatModelIsUnsat(
3525             "Duplicate variable in all_diff");
3526       }
3527       if (LinearExpressionProtosAreEqual(kept_expressions[i],
3528                                          kept_expressions[i - 1], -1)) {
3529         bool domain_modified = false;
3530         if (!context_->IntersectDomainWith(kept_expressions[i],
3531                                            Domain(0).Complement(),
3532                                            &domain_modified)) {
3533           return false;
3534         }
3535         if (domain_modified) {
3536           context_->UpdateRuleStats(
3537               "all_diff: remove 0 from expression appearing with its "
3538               "opposite.");
3539         }
3540       }
3541     }
3542 
3543     if (kept_expressions.size() < all_diff.exprs_size()) {
3544       all_diff.clear_exprs();
3545       for (const LinearExpressionProto& expr : kept_expressions) {
3546         *all_diff.add_exprs() = expr;
3547       }
3548       context_->UpdateRuleStats("all_diff: removed fixed variables");
3549       something_was_propagated = true;
3550       constraint_has_changed = true;
3551       if (kept_expressions.size() <= 1) continue;
3552     }
3553 
3554     // Propagate mandatory value if the all diff is actually a permutation.
3555     CHECK_GE(all_diff.exprs_size(), 2);
3556     Domain domain = context_->DomainSuperSetOf(all_diff.exprs(0));
3557     for (int i = 1; i < all_diff.exprs_size(); ++i) {
3558       domain = domain.UnionWith(context_->DomainSuperSetOf(all_diff.exprs(i)));
3559     }
3560     if (all_diff.exprs_size() == domain.Size()) {
3561       absl::flat_hash_map<int64_t, std::vector<LinearExpressionProto>>
3562           value_to_exprs;
3563       for (const LinearExpressionProto& expr : all_diff.exprs()) {
3564         for (const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
3565           value_to_exprs[expr.coeffs(0) * v + expr.offset()].push_back(expr);
3566         }
3567       }
3568       bool propagated = false;
3569       for (const auto& it : value_to_exprs) {
3570         if (it.second.size() == 1 && !context_->IsFixed(it.second.front())) {
3571           const LinearExpressionProto& expr = it.second.front();
3572           if (!context_->IntersectDomainWith(expr, Domain(it.first))) {
3573             return true;
3574           }
3575           propagated = true;
3576         }
3577       }
3578       if (propagated) {
3579         context_->UpdateRuleStats(
3580             "all_diff: propagated mandatory values in permutation");
3581         something_was_propagated = true;
3582       }
3583     }
3584     if (!something_was_propagated) break;
3585   }
3586 
3587   return constraint_has_changed;
3588 }
3589 
3590 namespace {
3591 
3592 // Returns the sorted list of literals for given bool_or or at_most_one
3593 // constraint.
3594 std::vector<int> GetLiteralsFromSetPPCConstraint(const ConstraintProto& ct) {
3595   std::vector<int> sorted_literals;
3596   if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
3597     for (const int literal : ct.at_most_one().literals()) {
3598       sorted_literals.push_back(literal);
3599     }
3600   } else if (ct.constraint_case() == ConstraintProto::kBoolOr) {
3601     for (const int literal : ct.bool_or().literals()) {
3602       sorted_literals.push_back(literal);
3603     }
3604   } else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
3605     for (const int literal : ct.exactly_one().literals()) {
3606       sorted_literals.push_back(literal);
3607     }
3608   }
3609   std::sort(sorted_literals.begin(), sorted_literals.end());
3610   return sorted_literals;
3611 }
3612 
3613 // Add the constraint (lhs => rhs) to the given proto. The hash map lhs ->
3614 // bool_and constraint index is used to merge implications with the same lhs.
3615 void AddImplication(int lhs, int rhs, CpModelProto* proto,
3616                     absl::flat_hash_map<int, int>* ref_to_bool_and) {
3617   if (ref_to_bool_and->contains(lhs)) {
3618     const int ct_index = (*ref_to_bool_and)[lhs];
3619     proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
3620   } else if (ref_to_bool_and->contains(NegatedRef(rhs))) {
3621     const int ct_index = (*ref_to_bool_and)[NegatedRef(rhs)];
3622     proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
3623         NegatedRef(lhs));
3624   } else {
3625     (*ref_to_bool_and)[lhs] = proto->constraints_size();
3626     ConstraintProto* ct = proto->add_constraints();
3627     ct->add_enforcement_literal(lhs);
3628     ct->mutable_bool_and()->add_literals(rhs);
3629   }
3630 }
3631 
3632 template <typename ClauseContainer>
3633 void ExtractClauses(bool use_bool_and, const ClauseContainer& container,
3634                     CpModelProto* proto) {
3635   // We regroup the "implication" into bool_and to have a more consise proto and
3636   // also for nicer information about the number of binary clauses.
3637   //
3638   // Important: however, we do not do that for the model used during presolving
3639   // since the order of the constraints might be important there depending on
3640   // how we perform the postsolve.
3641   absl::flat_hash_map<int, int> ref_to_bool_and;
3642   for (int i = 0; i < container.NumClauses(); ++i) {
3643     const std::vector<Literal>& clause = container.Clause(i);
3644     if (clause.empty()) continue;
3645 
3646     // bool_and.
3647     if (use_bool_and && clause.size() == 2) {
3648       const int a = clause[0].IsPositive()
3649                         ? clause[0].Variable().value()
3650                         : NegatedRef(clause[0].Variable().value());
3651       const int b = clause[1].IsPositive()
3652                         ? clause[1].Variable().value()
3653                         : NegatedRef(clause[1].Variable().value());
3654       AddImplication(NegatedRef(a), b, proto, &ref_to_bool_and);
3655       continue;
3656     }
3657 
3658     // bool_or.
3659     ConstraintProto* ct = proto->add_constraints();
3660     for (const Literal l : clause) {
3661       if (l.IsPositive()) {
3662         ct->mutable_bool_or()->add_literals(l.Variable().value());
3663       } else {
3664         ct->mutable_bool_or()->add_literals(NegatedRef(l.Variable().value()));
3665       }
3666     }
3667   }
3668 }
3669 
3670 }  // namespace
3671 
3672 bool CpModelPresolver::PresolveNoOverlap(ConstraintProto* ct) {
3673   if (context_->ModelIsUnsat()) return false;
3674   NoOverlapConstraintProto* proto = ct->mutable_no_overlap();
3675   bool changed = false;
3676 
3677   // Filter absent intervals.
3678   {
3679     const int initial_num_intervals = proto->intervals_size();
3680     int new_size = 0;
3681 
3682     for (int i = 0; i < initial_num_intervals; ++i) {
3683       const int interval_index = proto->intervals(i);
3684       if (context_->ConstraintIsInactive(interval_index)) {
3685         continue;
3686       }
3687 
3688       proto->set_intervals(new_size++, interval_index);
3689     }
3690 
3691     if (new_size < initial_num_intervals) {
3692       proto->mutable_intervals()->Truncate(new_size);
3693       context_->UpdateRuleStats("no_overlap: removed absent intervals");
3694       changed = true;
3695     }
3696   }
3697 
3698   // Split constraints in disjoint sets.
3699   if (proto->intervals_size() > 1) {
3700     std::vector<IndexedInterval> indexed_intervals;
3701     for (int i = 0; i < proto->intervals().size(); ++i) {
3702       const int index = proto->intervals(i);
3703       indexed_intervals.push_back({index,
3704                                    IntegerValue(context_->StartMin(index)),
3705                                    IntegerValue(context_->EndMax(index))});
3706     }
3707     std::vector<std::vector<int>> components;
3708     GetOverlappingIntervalComponents(&indexed_intervals, &components);
3709 
3710     if (components.size() > 1) {
3711       for (const std::vector<int>& intervals : components) {
3712         if (intervals.size() <= 1) continue;
3713 
3714         NoOverlapConstraintProto* new_no_overlap =
3715             context_->working_model->add_constraints()->mutable_no_overlap();
3716         // Fill in the intervals. Unfortunately, the Assign() method does not
3717         // compile in or-tools.
3718         for (const int i : intervals) {
3719           new_no_overlap->add_intervals(i);
3720         }
3721       }
3722       context_->UpdateNewConstraintsVariableUsage();
3723       context_->UpdateRuleStats("no_overlap: split into disjoint components");
3724       return RemoveConstraint(ct);
3725     }
3726   }
3727 
3728   std::vector<int> constant_intervals;
3729   int64_t size_min_of_non_constant_intervals =
3730       std::numeric_limits<int64_t>::max();
3731   for (int i = 0; i < proto->intervals_size(); ++i) {
3732     const int interval_index = proto->intervals(i);
3733     if (context_->IntervalIsConstant(interval_index)) {
3734       constant_intervals.push_back(interval_index);
3735     } else {
3736       size_min_of_non_constant_intervals =
3737           std::min(size_min_of_non_constant_intervals,
3738                    context_->SizeMin(interval_index));
3739     }
3740   }
3741 
3742   if (!constant_intervals.empty()) {
3743     // Sort constant_intervals by start min.
3744     std::sort(constant_intervals.begin(), constant_intervals.end(),
3745               [this](int i1, int i2) {
3746                 return context_->StartMin(i1) < context_->StartMin(i2);
3747               });
3748 
3749     // Check for overlapping constant intervals. We need to check feasibility
3750     // before we simplify the constraint, as we might remove conflicting
3751     // overlapping constant intervals.
3752     for (int i = 0; i + 1 < constant_intervals.size(); ++i) {
3753       if (context_->EndMax(constant_intervals[i]) >
3754           context_->StartMin(constant_intervals[i + 1])) {
3755         context_->UpdateRuleStats("no_overlap: constant intervals overlap");
3756         return context_->NotifyThatModelIsUnsat();
3757       }
3758     }
3759 
3760     if (constant_intervals.size() == proto->intervals_size()) {
3761       context_->UpdateRuleStats("no_overlap: no variable intervals");
3762       return RemoveConstraint(ct);
3763     }
3764 
3765     absl::flat_hash_set<int> intervals_to_remove;
3766 
3767     // If two constant intervals are separated by a gap smaller that the min
3768     // size of all non-constant intervals, then we can merge them.
3769     for (int i = 0; i + 1 < constant_intervals.size(); ++i) {
3770       const int start = i;
3771       while (i + 1 < constant_intervals.size() &&
3772              context_->StartMin(constant_intervals[i + 1]) -
3773                      context_->EndMax(constant_intervals[i]) <
3774                  size_min_of_non_constant_intervals) {
3775         i++;
3776       }
3777       if (i == start) continue;
3778       for (int j = start; j <= i; ++j) {
3779         intervals_to_remove.insert(constant_intervals[j]);
3780       }
3781       const int64_t new_start = context_->StartMin(constant_intervals[start]);
3782       const int64_t new_end = context_->EndMax(constant_intervals[i]);
3783       proto->add_intervals(context_->working_model->constraints_size());
3784       IntervalConstraintProto* new_interval =
3785           context_->working_model->add_constraints()->mutable_interval();
3786       new_interval->mutable_start()->set_offset(new_start);
3787       new_interval->mutable_size()->set_offset(new_end - new_start);
3788       new_interval->mutable_end()->set_offset(new_end);
3789     }
3790 
3791     // Cleanup the original proto.
3792     if (!intervals_to_remove.empty()) {
3793       int new_size = 0;
3794       const int old_size = proto->intervals_size();
3795       for (int i = 0; i < old_size; ++i) {
3796         const int interval_index = proto->intervals(i);
3797         if (intervals_to_remove.contains(interval_index)) {
3798           continue;
3799         }
3800         proto->set_intervals(new_size++, interval_index);
3801       }
3802       CHECK_LT(new_size, old_size);
3803       proto->mutable_intervals()->Truncate(new_size);
3804       context_->UpdateRuleStats(
3805           "no_overlap: merge constant contiguous intervals");
3806       intervals_to_remove.clear();
3807       constant_intervals.clear();
3808       changed = true;
3809       context_->UpdateNewConstraintsVariableUsage();
3810     }
3811   }
3812 
3813   if (proto->intervals_size() == 1) {
3814     context_->UpdateRuleStats("no_overlap: only one interval");
3815     return RemoveConstraint(ct);
3816   }
3817   if (proto->intervals().empty()) {
3818     context_->UpdateRuleStats("no_overlap: no intervals");
3819     return RemoveConstraint(ct);
3820   }
3821 
3822   return changed;
3823 }
3824 
3825 bool CpModelPresolver::PresolveNoOverlap2D(int c, ConstraintProto* ct) {
3826   if (context_->ModelIsUnsat()) {
3827     return false;
3828   }
3829 
3830   const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
3831   const int initial_num_boxes = proto.x_intervals_size();
3832 
3833   bool has_zero_sizes = false;
3834   bool x_constant = true;
3835   bool y_constant = true;
3836 
3837   // Filter absent boxes.
3838   int new_size = 0;
3839   std::vector<Rectangle> bounding_boxes;
3840   std::vector<int> active_boxes;
3841   for (int i = 0; i < proto.x_intervals_size(); ++i) {
3842     const int x_interval_index = proto.x_intervals(i);
3843     const int y_interval_index = proto.y_intervals(i);
3844 
3845     if (context_->ConstraintIsInactive(x_interval_index) ||
3846         context_->ConstraintIsInactive(y_interval_index)) {
3847       continue;
3848     }
3849 
3850     if (proto.boxes_with_null_area_can_overlap() &&
3851         (context_->SizeMax(x_interval_index) == 0 ||
3852          context_->SizeMax(y_interval_index) == 0)) {
3853       if (proto.boxes_with_null_area_can_overlap()) continue;
3854       has_zero_sizes = true;
3855     }
3856     ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index);
3857     ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index);
3858     bounding_boxes.push_back(
3859         {IntegerValue(context_->StartMin(x_interval_index)),
3860          IntegerValue(context_->EndMax(x_interval_index)),
3861          IntegerValue(context_->StartMin(y_interval_index)),
3862          IntegerValue(context_->EndMax(y_interval_index))});
3863     active_boxes.push_back(new_size);
3864     new_size++;
3865 
3866     if (x_constant && !context_->IntervalIsConstant(x_interval_index)) {
3867       x_constant = false;
3868     }
3869     if (y_constant && !context_->IntervalIsConstant(y_interval_index)) {
3870       y_constant = false;
3871     }
3872   }
3873 
3874   std::vector<absl::Span<int>> components = GetOverlappingRectangleComponents(
3875       bounding_boxes, absl::MakeSpan(active_boxes));
3876   if (components.size() > 1) {
3877     for (const absl::Span<int> boxes : components) {
3878       if (boxes.size() <= 1) continue;
3879 
3880       NoOverlap2DConstraintProto* new_no_overlap_2d =
3881           context_->working_model->add_constraints()->mutable_no_overlap_2d();
3882       for (const int b : boxes) {
3883         new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
3884         new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
3885       }
3886     }
3887     context_->UpdateNewConstraintsVariableUsage();
3888     context_->UpdateRuleStats("no_overlap_2d: split into disjoint components");
3889     return RemoveConstraint(ct);
3890   }
3891 
3892   if (!has_zero_sizes && (x_constant || y_constant)) {
3893     context_->UpdateRuleStats(
3894         "no_overlap_2d: a dimension is constant, splitting into many no "
3895         "overlaps");
3896     std::vector<IndexedInterval> indexed_intervals;
3897     for (int i = 0; i < new_size; ++i) {
3898       int x = proto.x_intervals(i);
3899       int y = proto.y_intervals(i);
3900       if (x_constant) std::swap(x, y);
3901       indexed_intervals.push_back({x, IntegerValue(context_->StartMin(y)),
3902                                    IntegerValue(context_->EndMax(y))});
3903     }
3904     std::vector<std::vector<int>> no_overlaps;
3905     ConstructOverlappingSets(/*already_sorted=*/false, &indexed_intervals,
3906                              &no_overlaps);
3907     for (const std::vector<int>& no_overlap : no_overlaps) {
3908       ConstraintProto* new_ct = context_->working_model->add_constraints();
3909       // Unfortunately, the Assign() method does not work in or-tools as the
3910       // protobuf int32_t type is not the int type.
3911       for (const int i : no_overlap) {
3912         new_ct->mutable_no_overlap()->add_intervals(i);
3913       }
3914     }
3915     context_->UpdateNewConstraintsVariableUsage();
3916     return RemoveConstraint(ct);
3917   }
3918 
3919   if (new_size < initial_num_boxes) {
3920     context_->UpdateRuleStats("no_overlap_2d: removed inactive boxes");
3921     ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
3922     ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
3923   }
3924 
3925   if (new_size == 0) {
3926     context_->UpdateRuleStats("no_overlap_2d: no boxes");
3927     return RemoveConstraint(ct);
3928   }
3929 
3930   if (new_size == 1) {
3931     context_->UpdateRuleStats("no_overlap_2d: only one box");
3932     return RemoveConstraint(ct);
3933   }
3934 
3935   return new_size < initial_num_boxes;
3936 }
3937 
3938 namespace {
3939 LinearExpressionProto ConstantExpressionProto(int64_t value) {
3940   LinearExpressionProto expr;
3941   expr.set_offset(value);
3942   return expr;
3943 }
3944 }  // namespace
3945 
3946 bool CpModelPresolver::PresolveCumulative(ConstraintProto* ct) {
3947   if (context_->ModelIsUnsat()) return false;
3948 
3949   CumulativeConstraintProto* proto = ct->mutable_cumulative();
3950 
3951   bool changed = CanonicalizeLinearExpression(*ct, proto->mutable_capacity());
3952   for (LinearExpressionProto& exp :
3953        *(ct->mutable_cumulative()->mutable_demands())) {
3954     changed |= CanonicalizeLinearExpression(*ct, &exp);
3955   }
3956 
3957   const int64_t capacity_max = context_->MaxOf(proto->capacity());
3958 
3959   // Checks the capacity of the constraint.
3960   {
3961     bool domain_changed = false;
3962     if (!context_->IntersectDomainWith(
3963             proto->capacity(), Domain(0, capacity_max), &domain_changed)) {
3964       return true;
3965     }
3966     if (domain_changed) {
3967       context_->UpdateRuleStats("cumulative: trimmed negative capacity");
3968     }
3969   }
3970 
3971   {
3972     // Filter absent intervals, or zero demands, or demand incompatible with the
3973     // capacity.
3974     int new_size = 0;
3975     int num_zero_demand_removed = 0;
3976     int num_zero_size_removed = 0;
3977     int num_incompatible_demands = 0;
3978     for (int i = 0; i < proto->intervals_size(); ++i) {
3979       if (context_->ConstraintIsInactive(proto->intervals(i))) continue;
3980 
3981       const LinearExpressionProto& demand_expr = proto->demands(i);
3982       const int64_t demand_max = context_->MaxOf(demand_expr);
3983       if (demand_max == 0) {
3984         num_zero_demand_removed++;
3985         continue;
3986       }
3987 
3988       if (context_->SizeMax(proto->intervals(i)) == 0) {
3989         // Size 0 intervals cannot contribute to a cumulative.
3990         num_zero_size_removed++;
3991         continue;
3992       }
3993 
3994       if (context_->MinOf(demand_expr) > capacity_max) {
3995         if (context_->ConstraintIsOptional(proto->intervals(i))) {
3996           ConstraintProto* interval_ct =
3997               context_->working_model->mutable_constraints(proto->intervals(i));
3998           DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
3999           const int literal = interval_ct->enforcement_literal(0);
4000           if (!context_->SetLiteralToFalse(literal)) {
4001             return true;
4002           }
4003           num_incompatible_demands++;
4004           continue;
4005         } else {  // Interval is performed.
4006           return context_->NotifyThatModelIsUnsat(
4007               "cumulative: performed demand exceeds capacity.");
4008         }
4009       }
4010 
4011       proto->set_intervals(new_size, proto->intervals(i));
4012       *proto->mutable_demands(new_size) = proto->demands(i);
4013       new_size++;
4014     }
4015 
4016     if (new_size < proto->intervals_size()) {
4017       changed = true;
4018       proto->mutable_intervals()->Truncate(new_size);
4019       proto->mutable_demands()->erase(
4020           proto->mutable_demands()->begin() + new_size,
4021           proto->mutable_demands()->end());
4022     }
4023 
4024     if (num_zero_demand_removed > 0) {
4025       context_->UpdateRuleStats(
4026           "cumulative: removed intervals with no demands");
4027     }
4028     if (num_zero_size_removed > 0) {
4029       context_->UpdateRuleStats(
4030           "cumulative: removed intervals with a size of zero");
4031     }
4032     if (num_incompatible_demands > 0) {
4033       context_->UpdateRuleStats(
4034           "cumulative: removed intervals demands greater than the capacity");
4035     }
4036   }
4037 
4038   // Checks the compatibility of demands w.r.t. the capacity.
4039   {
4040     for (int i = 0; i < proto->demands_size(); ++i) {
4041       const int interval = proto->intervals(i);
4042       const LinearExpressionProto& demand_expr = proto->demands(i);
4043       if (context_->ConstraintIsOptional(interval)) continue;
4044       bool domain_changed = false;
4045       if (!context_->IntersectDomainWith(demand_expr, {0, capacity_max},
4046                                          &domain_changed)) {
4047         return true;
4048       }
4049       if (domain_changed) {
4050         context_->UpdateRuleStats(
4051             "cumulative: fit demand in [0..capacity_max]");
4052       }
4053     }
4054   }
4055 
4056   // Split constraints in disjoint sets.
4057   //
4058   // TODO(user): This can be improved:
4059   // If we detect bridge nodes in the graph of overlapping components, we
4060   // can split the graph around the bridge and add the bridge node to both
4061   // side. Note that if it we take into account precedences between intervals,
4062   // we can detect more bridges.
4063   if (proto->intervals_size() > 1) {
4064     std::vector<IndexedInterval> indexed_intervals;
4065     for (int i = 0; i < proto->intervals().size(); ++i) {
4066       const int index = proto->intervals(i);
4067       indexed_intervals.push_back({i, IntegerValue(context_->StartMin(index)),
4068                                    IntegerValue(context_->EndMax(index))});
4069     }
4070     std::vector<std::vector<int>> components;
4071     GetOverlappingIntervalComponents(&indexed_intervals, &components);
4072 
4073     if (components.size() > 1) {
4074       for (const std::vector<int>& component : components) {
4075         CumulativeConstraintProto* new_cumulative =
4076             context_->working_model->add_constraints()->mutable_cumulative();
4077         for (const int i : component) {
4078           new_cumulative->add_intervals(proto->intervals(i));
4079           *new_cumulative->add_demands() = proto->demands(i);
4080         }
4081         *new_cumulative->mutable_capacity() = proto->capacity();
4082       }
4083       context_->UpdateNewConstraintsVariableUsage();
4084       context_->UpdateRuleStats("cumulative: split into disjoint components");
4085       return RemoveConstraint(ct);
4086     }
4087   }
4088 
4089   // TODO(user): move the algorithmic part of what we do below in a
4090   // separate function to unit test it more properly.
4091   {
4092     // Build max load profiles.
4093     std::map<int64_t, int64_t> time_to_demand_deltas;
4094     const int64_t capacity_min = context_->MinOf(proto->capacity());
4095     for (int i = 0; i < proto->intervals_size(); ++i) {
4096       const int interval_index = proto->intervals(i);
4097       const int64_t demand_max = context_->MaxOf(proto->demands(i));
4098       time_to_demand_deltas[context_->StartMin(interval_index)] += demand_max;
4099       time_to_demand_deltas[context_->EndMax(interval_index)] -= demand_max;
4100     }
4101 
4102     // We construct the profile which correspond to a set of [time, next_time)
4103     // to max_profile height. And for each time in our discrete set of
4104     // time_exprs (all the start_min and end_max) we count for how often the
4105     // height was above the capacity before this time.
4106     //
4107     // This rely on the iteration in sorted order.
4108     int num_possible_overloads = 0;
4109     int64_t current_load = 0;
4110     absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before;
4111     for (const auto& it : time_to_demand_deltas) {
4112       num_possible_overloads_before[it.first] = num_possible_overloads;
4113       current_load += it.second;
4114       if (current_load > capacity_min) {
4115         ++num_possible_overloads;
4116       }
4117     }
4118     CHECK_EQ(current_load, 0);
4119 
4120     // No possible overload with the min capacity.
4121     if (num_possible_overloads == 0) {
4122       context_->UpdateRuleStats(
4123           "cumulative: max profile is always under the min capacity");
4124       return RemoveConstraint(ct);
4125     }
4126 
4127     // An interval that does not intersect with the potential_overload_domains
4128     // cannot contribute to a conflict. We can safely remove them.
4129     //
4130     // This is an extension of the presolve rule from
4131     // "Presolving techniques and linear relaxations for cumulative
4132     // scheduling" PhD dissertation by Stefan Heinz, ZIB.
4133     int new_size = 0;
4134     for (int i = 0; i < proto->intervals_size(); ++i) {
4135       const int index = proto->intervals(i);
4136       const int64_t start_min = context_->StartMin(index);
4137       const int64_t end_max = context_->EndMax(index);
4138 
4139       // In the cumulative, if start_min == end_max, the interval is of size
4140       // zero and we can just ignore it. If the model is unsat or the interval
4141       // must be absent (start_min > end_max), this should be dealt with at
4142       // the interval constraint level and we can just remove it from here.
4143       //
4144       // Note that currently, the interpretation for interval of length zero
4145       // is different for the no-overlap constraint.
4146       if (start_min >= end_max) continue;
4147 
4148       // Note that by construction, both point are in the map. The formula
4149       // counts exactly for how many time_exprs in [start_min, end_max), we have
4150       // a point in our discrete set of time that exceeded the capacity. Because
4151       // we included all the relevant points, this works.
4152       const int num_diff = num_possible_overloads_before.at(end_max) -
4153                            num_possible_overloads_before.at(start_min);
4154       if (num_diff == 0) continue;
4155 
4156       proto->set_intervals(new_size, proto->intervals(i));
4157       *proto->mutable_demands(new_size) = proto->demands(i);
4158       new_size++;
4159     }
4160 
4161     if (new_size < proto->intervals_size()) {
4162       changed = true;
4163       proto->mutable_intervals()->Truncate(new_size);
4164       proto->mutable_demands()->erase(
4165           proto->mutable_demands()->begin() + new_size,
4166           proto->mutable_demands()->end());
4167       context_->UpdateRuleStats(
4168           "cumulative: remove never conflicting intervals.");
4169     }
4170   }
4171 
4172   if (proto->intervals().empty()) {
4173     context_->UpdateRuleStats("cumulative: no intervals");
4174     return RemoveConstraint(ct);
4175   }
4176 
4177   {
4178     int64_t max_of_performed_demand_mins = 0;
4179     int64_t sum_of_max_demands = 0;
4180     for (int i = 0; i < proto->intervals_size(); ++i) {
4181       const ConstraintProto& interval_ct =
4182           context_->working_model->constraints(proto->intervals(i));
4183 
4184       const LinearExpressionProto& demand_expr = proto->demands(i);
4185       sum_of_max_demands += context_->MaxOf(demand_expr);
4186 
4187       if (interval_ct.enforcement_literal().empty()) {
4188         max_of_performed_demand_mins = std::max(max_of_performed_demand_mins,
4189                                                 context_->MinOf(demand_expr));
4190       }
4191     }
4192 
4193     const LinearExpressionProto& capacity_expr = proto->capacity();
4194     if (max_of_performed_demand_mins > context_->MinOf(capacity_expr)) {
4195       context_->UpdateRuleStats("cumulative: propagate min capacity.");
4196       if (!context_->IntersectDomainWith(
4197               capacity_expr, Domain(max_of_performed_demand_mins,
4198                                     std::numeric_limits<int64_t>::max()))) {
4199         return true;
4200       }
4201     }
4202 
4203     if (max_of_performed_demand_mins > context_->MaxOf(capacity_expr)) {
4204       context_->UpdateRuleStats("cumulative: cannot fit performed demands");
4205       return context_->NotifyThatModelIsUnsat();
4206     }
4207 
4208     if (sum_of_max_demands <= context_->MinOf(capacity_expr)) {
4209       context_->UpdateRuleStats("cumulative: capacity exceeds sum of demands");
4210       return RemoveConstraint(ct);
4211     }
4212   }
4213 
4214   if (context_->IsFixed(proto->capacity())) {
4215     int64_t gcd = 0;
4216     for (int i = 0; i < ct->cumulative().demands_size(); ++i) {
4217       const LinearExpressionProto& demand_expr = ct->cumulative().demands(i);
4218       if (!context_->IsFixed(demand_expr)) {
4219         // Abort if the demand is not fixed.
4220         gcd = 1;
4221         break;
4222       }
4223       gcd = MathUtil::GCD64(gcd, context_->MinOf(demand_expr));
4224       if (gcd == 1) break;
4225     }
4226     if (gcd > 1) {
4227       changed = true;
4228       for (int i = 0; i < ct->cumulative().demands_size(); ++i) {
4229         const int64_t demand = context_->MinOf(ct->cumulative().demands(i));
4230         *proto->mutable_demands(i) = ConstantExpressionProto(demand / gcd);
4231       }
4232 
4233       const int64_t old_capacity = context_->MinOf(proto->capacity());
4234       *proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd);
4235       context_->UpdateRuleStats(
4236           "cumulative: divide demands and capacity by gcd");
4237     }
4238   }
4239 
4240   const int num_intervals = proto->intervals_size();
4241   const LinearExpressionProto& capacity_expr = proto->capacity();
4242 
4243   std::vector<LinearExpressionProto> start_exprs(num_intervals);
4244 
4245   int num_duration_one = 0;
4246   int num_greater_half_capacity = 0;
4247 
4248   bool has_optional_interval = false;
4249   bool all_starts_are_variables = true;
4250   for (int i = 0; i < num_intervals; ++i) {
4251     const int index = proto->intervals(i);
4252     // TODO(user): adapt in the presence of optional intervals.
4253     if (context_->ConstraintIsOptional(index)) has_optional_interval = true;
4254     const ConstraintProto& ct =
4255         context_->working_model->constraints(proto->intervals(i));
4256     const IntervalConstraintProto& interval = ct.interval();
4257     start_exprs[i] = interval.start();
4258 
4259     const LinearExpressionProto& demand_expr = proto->demands(i);
4260     if (context_->SizeMin(index) == 1 && context_->SizeMax(index) == 1) {
4261       num_duration_one++;
4262     }
4263     if (context_->SizeMin(index) == 0) {
4264       // The behavior for zero-duration interval is currently not the same in
4265       // the no-overlap and the cumulative constraint.
4266       return changed;
4267     }
4268     const int64_t demand_min = context_->MinOf(demand_expr);
4269     const int64_t demand_max = context_->MaxOf(demand_expr);
4270     if (demand_min > capacity_max / 2) {
4271       num_greater_half_capacity++;
4272     }
4273     if (demand_min > capacity_max) {
4274       context_->UpdateRuleStats("cumulative: demand_min exceeds capacity max");
4275       if (!context_->ConstraintIsOptional(index)) {
4276         return context_->NotifyThatModelIsUnsat();
4277       } else {
4278         CHECK_EQ(ct.enforcement_literal().size(), 1);
4279         if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
4280           return true;
4281         }
4282       }
4283       return changed;
4284     } else if (demand_max > capacity_max) {
4285       if (ct.enforcement_literal().empty()) {
4286         context_->UpdateRuleStats(
4287             "cumulative: demand_max exceeds capacity max.");
4288         if (!context_->IntersectDomainWith(
4289                 demand_expr,
4290                 Domain(std::numeric_limits<int64_t>::min(), capacity_max))) {
4291           return true;
4292         }
4293       } else {
4294         // TODO(user): we abort because we cannot convert this to a no_overlap
4295         // for instance.
4296         context_->UpdateRuleStats(
4297             "cumulative: demand_max of optional interval exceeds capacity.");
4298         return changed;
4299       }
4300     }
4301   }
4302   if (num_greater_half_capacity == num_intervals) {
4303     if (num_duration_one == num_intervals && !has_optional_interval &&
4304         all_starts_are_variables) {
4305       context_->UpdateRuleStats("cumulative: convert to all_different");
4306       ConstraintProto* new_ct = context_->working_model->add_constraints();
4307       auto* arg = new_ct->mutable_all_diff();
4308       for (const LinearExpressionProto& expr : start_exprs) {
4309         *arg->add_exprs() = expr;
4310       }
4311       context_->UpdateNewConstraintsVariableUsage();
4312       return RemoveConstraint(ct);
4313     } else {
4314       context_->UpdateRuleStats("cumulative: convert to no_overlap");
4315       // Before we remove the cumulative, add constraints to enforce that the
4316       // capacity is greater than the demand of any performed intervals.
4317       for (int i = 0; i < proto->demands_size(); ++i) {
4318         const LinearExpressionProto& demand_expr = proto->demands(i);
4319         const int64_t demand_max = context_->MaxOf(demand_expr);
4320         if (demand_max > context_->MinOf(capacity_expr)) {
4321           ConstraintProto* capacity_gt =
4322               context_->working_model->add_constraints();
4323           *capacity_gt->mutable_enforcement_literal() =
4324               context_->working_model->constraints(proto->intervals(i))
4325                   .enforcement_literal();
4326           capacity_gt->mutable_linear()->add_domain(0);
4327           capacity_gt->mutable_linear()->add_domain(
4328               std::numeric_limits<int64_t>::max());
4329           AddLinearExpressionToLinearConstraint(capacity_expr, 1,
4330                                                 capacity_gt->mutable_linear());
4331           AddLinearExpressionToLinearConstraint(demand_expr, -1,
4332                                                 capacity_gt->mutable_linear());
4333         }
4334       }
4335 
4336       ConstraintProto* new_ct = context_->working_model->add_constraints();
4337       auto* arg = new_ct->mutable_no_overlap();
4338       for (const int interval : proto->intervals()) {
4339         arg->add_intervals(interval);
4340       }
4341       context_->UpdateNewConstraintsVariableUsage();
4342       return RemoveConstraint(ct);
4343     }
4344   }
4345 
4346   return changed;
4347 }
4348 
4349 bool CpModelPresolver::PresolveRoutes(ConstraintProto* ct) {
4350   if (context_->ModelIsUnsat()) return false;
4351   if (HasEnforcementLiteral(*ct)) return false;
4352   RoutesConstraintProto& proto = *ct->mutable_routes();
4353 
4354   const int old_size = proto.literals_size();
4355   int new_size = 0;
4356   std::vector<bool> has_incoming_or_outgoing_arcs;
4357   const int num_arcs = proto.literals_size();
4358   for (int i = 0; i < num_arcs; ++i) {
4359     const int ref = proto.literals(i);
4360     const int tail = proto.tails(i);
4361     const int head = proto.heads(i);
4362     if (context_->LiteralIsFalse(ref)) {
4363       context_->UpdateRuleStats("routes: removed false arcs");
4364       continue;
4365     }
4366     proto.set_literals(new_size, ref);
4367     proto.set_tails(new_size, tail);
4368     proto.set_heads(new_size, head);
4369     ++new_size;
4370     if (tail >= has_incoming_or_outgoing_arcs.size()) {
4371       has_incoming_or_outgoing_arcs.resize(tail + 1, false);
4372     }
4373     if (head >= has_incoming_or_outgoing_arcs.size()) {
4374       has_incoming_or_outgoing_arcs.resize(head + 1, false);
4375     }
4376     has_incoming_or_outgoing_arcs[tail] = true;
4377     has_incoming_or_outgoing_arcs[head] = true;
4378   }
4379 
4380   if (old_size > 0 && new_size == 0) {
4381     // A routes constraint cannot have a self loop on 0. Therefore, if there
4382     // were arcs, it means it contains non zero nodes. Without arc, the
4383     // constraint is unfeasible.
4384     return context_->NotifyThatModelIsUnsat(
4385         "routes: graph with nodes and no arcs");
4386   }
4387 
4388   if (new_size < num_arcs) {
4389     proto.mutable_literals()->Truncate(new_size);
4390     proto.mutable_tails()->Truncate(new_size);
4391     proto.mutable_heads()->Truncate(new_size);
4392     return true;
4393   }
4394 
4395   // if a node misses an incomping or outgoing arc, the model is trivially
4396   // infeasible.
4397   for (int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) {
4398     if (!has_incoming_or_outgoing_arcs[n]) {
4399       return context_->NotifyThatModelIsUnsat(absl::StrCat(
4400           "routes: node ", n, " misses incoming or outgoing arcs"));
4401     }
4402   }
4403 
4404   return false;
4405 }
4406 
4407 bool CpModelPresolver::PresolveCircuit(ConstraintProto* ct) {
4408   if (context_->ModelIsUnsat()) return false;
4409   if (HasEnforcementLiteral(*ct)) return false;
4410   CircuitConstraintProto& proto = *ct->mutable_circuit();
4411 
4412   // The indexing might not be dense, so fix that first.
4413   ReindexArcs(ct->mutable_circuit()->mutable_tails(),
4414               ct->mutable_circuit()->mutable_heads());
4415 
4416   // Convert the flat structure to a graph, note that we includes all the arcs
4417   // here (even if they are at false).
4418   std::vector<std::vector<int>> incoming_arcs;
4419   std::vector<std::vector<int>> outgoing_arcs;
4420   int num_nodes = 0;
4421   const int num_arcs = proto.literals_size();
4422   for (int i = 0; i < num_arcs; ++i) {
4423     const int ref = proto.literals(i);
4424     const int tail = proto.tails(i);
4425     const int head = proto.heads(i);
4426     num_nodes = std::max(num_nodes, std::max(tail, head) + 1);
4427     if (std::max(tail, head) >= incoming_arcs.size()) {
4428       incoming_arcs.resize(std::max(tail, head) + 1);
4429       outgoing_arcs.resize(std::max(tail, head) + 1);
4430     }
4431     incoming_arcs[head].push_back(ref);
4432     outgoing_arcs[tail].push_back(ref);
4433   }
4434 
4435   // All the node must have some incoming and outgoing arcs.
4436   for (int i = 0; i < num_nodes; ++i) {
4437     if (incoming_arcs[i].empty() || outgoing_arcs[i].empty()) {
4438       return MarkConstraintAsFalse(ct);
4439     }
4440   }
4441 
4442   // Note that it is important to reach the fixed point here:
4443   // One arc at true, then all other arc at false. This is because we rely
4444   // on this in case the circuit is fully specified below.
4445   //
4446   // TODO(user): Use a better complexity if needed.
4447   bool loop_again = true;
4448   int num_fixed_at_true = 0;
4449   while (loop_again) {
4450     loop_again = false;
4451     for (const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
4452       for (const std::vector<int>& refs : *node_to_refs) {
4453         if (refs.size() == 1) {
4454           if (!context_->LiteralIsTrue(refs.front())) {
4455             ++num_fixed_at_true;
4456             if (!context_->SetLiteralToTrue(refs.front())) return true;
4457           }
4458           continue;
4459         }
4460 
4461         // At most one true, so if there is one, mark all the other to false.
4462         int num_true = 0;
4463         int true_ref;
4464         for (const int ref : refs) {
4465           if (context_->LiteralIsTrue(ref)) {
4466             ++num_true;
4467             true_ref = ref;
4468             break;
4469           }
4470         }
4471         if (num_true > 1) {
4472           return context_->NotifyThatModelIsUnsat();
4473         }
4474         if (num_true == 1) {
4475           for (const int ref : refs) {
4476             if (ref != true_ref) {
4477               if (!context_->IsFixed(ref)) {
4478                 context_->UpdateRuleStats("circuit: set literal to false.");
4479                 loop_again = true;
4480               }
4481               if (!context_->SetLiteralToFalse(ref)) return true;
4482             }
4483           }
4484         }
4485       }
4486     }
4487   }
4488   if (num_fixed_at_true > 0) {
4489     context_->UpdateRuleStats("circuit: fixed singleton arcs.");
4490   }
4491 
4492   // Remove false arcs.
4493   int new_size = 0;
4494   int num_true = 0;
4495   int circuit_start = -1;
4496   std::vector<int> next(num_nodes, -1);
4497   std::vector<int> new_in_degree(num_nodes, 0);
4498   std::vector<int> new_out_degree(num_nodes, 0);
4499   for (int i = 0; i < num_arcs; ++i) {
4500     const int ref = proto.literals(i);
4501     if (context_->LiteralIsFalse(ref)) continue;
4502     if (context_->LiteralIsTrue(ref)) {
4503       if (next[proto.tails(i)] != -1) {
4504         return context_->NotifyThatModelIsUnsat();
4505       }
4506       next[proto.tails(i)] = proto.heads(i);
4507       if (proto.tails(i) != proto.heads(i)) {
4508         circuit_start = proto.tails(i);
4509       }
4510       ++num_true;
4511     }
4512     ++new_out_degree[proto.tails(i)];
4513     ++new_in_degree[proto.heads(i)];
4514     proto.set_tails(new_size, proto.tails(i));
4515     proto.set_heads(new_size, proto.heads(i));
4516     proto.set_literals(new_size, proto.literals(i));
4517     ++new_size;
4518   }
4519 
4520   // Detect infeasibility due to a node having no more incoming or outgoing arc.
4521   // This is a bit tricky because for now the meaning of the constraint says
4522   // that all nodes that appear in at least one of the arcs must be in the
4523   // circuit or have a self-arc. So if any such node ends up with an incoming or
4524   // outgoing degree of zero once we remove false arcs then the constraint is
4525   // infeasible!
4526   for (int i = 0; i < num_nodes; ++i) {
4527     if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
4528       return context_->NotifyThatModelIsUnsat();
4529     }
4530   }
4531 
4532   // Test if a subcircuit is already present.
4533   if (circuit_start != -1) {
4534     std::vector<bool> visited(num_nodes, false);
4535     int current = circuit_start;
4536     while (current != -1 && !visited[current]) {
4537       visited[current] = true;
4538       current = next[current];
4539     }
4540     if (current == circuit_start) {
4541       // We have a sub-circuit! mark all other arc false except self-loop not in
4542       // circuit.
4543       std::vector<bool> has_self_arc(num_nodes, false);
4544       for (int i = 0; i < num_arcs; ++i) {
4545         if (visited[proto.tails(i)]) continue;
4546         if (proto.tails(i) == proto.heads(i)) {
4547           has_self_arc[proto.tails(i)] = true;
4548           if (!context_->SetLiteralToTrue(proto.literals(i))) return true;
4549         } else {
4550           if (!context_->SetLiteralToFalse(proto.literals(i))) return true;
4551         }
4552       }
4553       for (int n = 0; n < num_nodes; ++n) {
4554         if (!visited[n] && !has_self_arc[n]) {
4555           // We have a subircuit, but it doesn't cover all the mandatory nodes.
4556           return MarkConstraintAsFalse(ct);
4557         }
4558       }
4559       context_->UpdateRuleStats("circuit: fully specified.");
4560       return RemoveConstraint(ct);
4561     }
4562   } else {
4563     // All self loop?
4564     if (num_true == new_size) {
4565       context_->UpdateRuleStats("circuit: empty circuit.");
4566       return RemoveConstraint(ct);
4567     }
4568   }
4569 
4570   // Look for in/out-degree of two, this will imply that one of the indicator
4571   // Boolean is equal to the negation of the other.
4572   for (int i = 0; i < num_nodes; ++i) {
4573     for (const std::vector<int>* arc_literals :
4574          {&incoming_arcs[i], &outgoing_arcs[i]}) {
4575       std::vector<int> literals;
4576       for (const int ref : *arc_literals) {
4577         if (context_->LiteralIsFalse(ref)) continue;
4578         if (context_->LiteralIsTrue(ref)) {
4579           literals.clear();
4580           break;
4581         }
4582         literals.push_back(ref);
4583       }
4584       if (literals.size() == 2 && literals[0] != NegatedRef(literals[1])) {
4585         context_->UpdateRuleStats("circuit: degree 2");
4586         context_->StoreBooleanEqualityRelation(literals[0],
4587                                                NegatedRef(literals[1]));
4588       }
4589     }
4590   }
4591 
4592   // Truncate the circuit and return.
4593   if (new_size < num_arcs) {
4594     proto.mutable_tails()->Truncate(new_size);
4595     proto.mutable_heads()->Truncate(new_size);
4596     proto.mutable_literals()->Truncate(new_size);
4597     context_->UpdateRuleStats("circuit: removed false arcs.");
4598     return true;
4599   }
4600   return false;
4601 }
4602 
4603 bool CpModelPresolver::PresolveAutomaton(ConstraintProto* ct) {
4604   if (context_->ModelIsUnsat()) return false;
4605   if (HasEnforcementLiteral(*ct)) return false;
4606   AutomatonConstraintProto& proto = *ct->mutable_automaton();
4607   if (proto.vars_size() == 0 || proto.transition_label_size() == 0) {
4608     return false;
4609   }
4610 
4611   bool all_have_same_affine_relation = true;
4612   std::vector<AffineRelation::Relation> affine_relations;
4613   for (int v = 0; v < proto.vars_size(); ++v) {
4614     const int var = ct->automaton().vars(v);
4615     const AffineRelation::Relation r = context_->GetAffineRelation(var);
4616     affine_relations.push_back(r);
4617     if (r.representative == var) {
4618       all_have_same_affine_relation = false;
4619       break;
4620     }
4621     if (v > 0 && (r.coeff != affine_relations[v - 1].coeff ||
4622                   r.offset != affine_relations[v - 1].offset)) {
4623       all_have_same_affine_relation = false;
4624       break;
4625     }
4626   }
4627 
4628   if (all_have_same_affine_relation) {  // Unscale labels.
4629     for (int v = 0; v < proto.vars_size(); ++v) {
4630       proto.set_vars(v, affine_relations[v].representative);
4631     }
4632     const AffineRelation::Relation rep = affine_relations.front();
4633     int new_size = 0;
4634     for (int t = 0; t < proto.transition_tail_size(); ++t) {
4635       const int64_t label = proto.transition_label(t);
4636       int64_t inverse_label = (label - rep.offset) / rep.coeff;
4637       if (inverse_label * rep.coeff + rep.offset == label) {
4638         if (new_size != t) {
4639           proto.set_transition_tail(new_size, proto.transition_tail(t));
4640           proto.set_transition_head(new_size, proto.transition_head(t));
4641         }
4642         proto.set_transition_label(new_size, inverse_label);
4643         new_size++;
4644       }
4645     }
4646     if (new_size < proto.transition_tail_size()) {
4647       proto.mutable_transition_tail()->Truncate(new_size);
4648       proto.mutable_transition_label()->Truncate(new_size);
4649       proto.mutable_transition_head()->Truncate(new_size);
4650       context_->UpdateRuleStats("automaton: remove invalid transitions");
4651     }
4652     context_->UpdateRuleStats("automaton: unscale all affine labels");
4653     return true;
4654   }
4655 
4656   Domain hull = context_->DomainOf(proto.vars(0));
4657   for (int v = 1; v < proto.vars_size(); ++v) {
4658     hull = hull.UnionWith(context_->DomainOf(proto.vars(v)));
4659   }
4660 
4661   int new_size = 0;
4662   for (int t = 0; t < proto.transition_tail_size(); ++t) {
4663     const int64_t label = proto.transition_label(t);
4664     if (hull.Contains(label)) {
4665       if (new_size != t) {
4666         proto.set_transition_tail(new_size, proto.transition_tail(t));
4667         proto.set_transition_label(new_size, label);
4668         proto.set_transition_head(new_size, proto.transition_head(t));
4669       }
4670       new_size++;
4671     }
4672   }
4673   if (new_size < proto.transition_tail_size()) {
4674     proto.mutable_transition_tail()->Truncate(new_size);
4675     proto.mutable_transition_label()->Truncate(new_size);
4676     proto.mutable_transition_head()->Truncate(new_size);
4677     context_->UpdateRuleStats("automaton: remove invalid transitions");
4678     return false;
4679   }
4680 
4681   const int n = proto.vars_size();
4682   const std::vector<int> vars = {proto.vars().begin(), proto.vars().end()};
4683 
4684   // Compute the set of reachable state at each time point.
4685   std::vector<std::set<int64_t>> reachable_states(n + 1);
4686   reachable_states[0].insert(proto.starting_state());
4687   reachable_states[n] = {proto.final_states().begin(),
4688                          proto.final_states().end()};
4689 
4690   // Forward.
4691   for (int time = 0; time + 1 < n; ++time) {
4692     for (int t = 0; t < proto.transition_tail_size(); ++t) {
4693       const int64_t tail = proto.transition_tail(t);
4694       const int64_t label = proto.transition_label(t);
4695       const int64_t head = proto.transition_head(t);
4696       if (!gtl::ContainsKey(reachable_states[time], tail)) continue;
4697       if (!context_->DomainContains(vars[time], label)) continue;
4698       reachable_states[time + 1].insert(head);
4699     }
4700   }
4701 
4702   std::vector<std::set<int64_t>> reached_values(n);
4703 
4704   // Backward.
4705   for (int time = n - 1; time >= 0; --time) {
4706     std::set<int64_t> new_set;
4707     for (int t = 0; t < proto.transition_tail_size(); ++t) {
4708       const int64_t tail = proto.transition_tail(t);
4709       const int64_t label = proto.transition_label(t);
4710       const int64_t head = proto.transition_head(t);
4711 
4712       if (!gtl::ContainsKey(reachable_states[time], tail)) continue;
4713       if (!context_->DomainContains(vars[time], label)) continue;
4714       if (!gtl::ContainsKey(reachable_states[time + 1], head)) continue;
4715       new_set.insert(tail);
4716       reached_values[time].insert(label);
4717     }
4718     reachable_states[time].swap(new_set);
4719   }
4720 
4721   bool removed_values = false;
4722   for (int time = 0; time < n; ++time) {
4723     if (!context_->IntersectDomainWith(
4724             vars[time],
4725             Domain::FromValues(
4726                 {reached_values[time].begin(), reached_values[time].end()}),
4727             &removed_values)) {
4728       return false;
4729     }
4730   }
4731   if (removed_values) {
4732     context_->UpdateRuleStats("automaton: reduced variable domains");
4733   }
4734   return false;
4735 }
4736 
4737 bool CpModelPresolver::PresolveReservoir(ConstraintProto* ct) {
4738   if (context_->ModelIsUnsat()) return false;
4739   if (HasEnforcementLiteral(*ct)) return false;
4740 
4741   ReservoirConstraintProto& proto = *ct->mutable_reservoir();
4742   bool changed = false;
4743   for (LinearExpressionProto& exp : *(proto.mutable_time_exprs())) {
4744     changed |= CanonicalizeLinearExpression(*ct, &exp);
4745   }
4746 
4747   if (proto.active_literals().empty()) {
4748     const int true_literal = context_->GetOrCreateConstantVar(1);
4749     for (int i = 0; i < proto.time_exprs_size(); ++i) {
4750       proto.add_active_literals(true_literal);
4751     }
4752     changed = true;
4753   }
4754 
4755   const auto& demand_is_null = [&](int i) {
4756     return proto.level_changes(i) == 0 ||
4757            context_->LiteralIsFalse(proto.active_literals(i));
4758   };
4759 
4760   // Remove zero level_changes, and inactive events.
4761   int num_zeros = 0;
4762   for (int i = 0; i < proto.level_changes_size(); ++i) {
4763     if (demand_is_null(i)) num_zeros++;
4764   }
4765 
4766   if (num_zeros > 0) {  // Remove null events
4767     changed = true;
4768     int new_size = 0;
4769     for (int i = 0; i < proto.level_changes_size(); ++i) {
4770       if (demand_is_null(i)) continue;
4771       proto.set_level_changes(new_size, proto.level_changes(i));
4772       *proto.mutable_time_exprs(new_size) = proto.time_exprs(i);
4773       proto.set_active_literals(new_size, proto.active_literals(i));
4774       new_size++;
4775     }
4776 
4777     proto.mutable_level_changes()->Truncate(new_size);
4778     proto.mutable_time_exprs()->erase(
4779         proto.mutable_time_exprs()->begin() + new_size,
4780         proto.mutable_time_exprs()->end());
4781     proto.mutable_active_literals()->Truncate(new_size);
4782 
4783     context_->UpdateRuleStats(
4784         "reservoir: remove zero level_changes or inactive events.");
4785   }
4786 
4787   const int num_events = proto.level_changes_size();
4788   int64_t gcd =
4789       proto.level_changes().empty() ? 0 : std::abs(proto.level_changes(0));
4790   int num_positives = 0;
4791   int num_negatives = 0;
4792   int64_t max_sum_of_positive_level_changes = 0;
4793   int64_t min_sum_of_negative_level_changes = 0;
4794   for (int i = 0; i < num_events; ++i) {
4795     const int64_t demand = proto.level_changes(i);
4796     gcd = MathUtil::GCD64(gcd, std::abs(demand));
4797     if (demand > 0) {
4798       num_positives++;
4799       max_sum_of_positive_level_changes += demand;
4800     } else {
4801       DCHECK_LT(demand, 0);
4802       num_negatives++;
4803       min_sum_of_negative_level_changes += demand;
4804     }
4805   }
4806 
4807   if (min_sum_of_negative_level_changes >= proto.min_level() &&
4808       max_sum_of_positive_level_changes <= proto.max_level()) {
4809     context_->UpdateRuleStats("reservoir: always feasible");
4810     return RemoveConstraint(ct);
4811   }
4812 
4813   if (min_sum_of_negative_level_changes > proto.max_level() ||
4814       max_sum_of_positive_level_changes < proto.min_level()) {
4815     context_->UpdateRuleStats("reservoir: trivially infeasible");
4816     return context_->NotifyThatModelIsUnsat();
4817   }
4818 
4819   if (min_sum_of_negative_level_changes > proto.min_level()) {
4820     proto.set_min_level(min_sum_of_negative_level_changes);
4821     context_->UpdateRuleStats(
4822         "reservoir: increase min_level to reachable value");
4823   }
4824 
4825   if (max_sum_of_positive_level_changes < proto.max_level()) {
4826     proto.set_max_level(max_sum_of_positive_level_changes);
4827     context_->UpdateRuleStats("reservoir: reduce max_level to reachable value");
4828   }
4829 
4830   if (proto.min_level() <= 0 && proto.max_level() >= 0 &&
4831       (num_positives == 0 || num_negatives == 0)) {
4832     // If all level_changes have the same sign, and if the initial state is
4833     // always feasible, we do not care about the order, just the sum.
4834     auto* const sum =
4835         context_->working_model->add_constraints()->mutable_linear();
4836     int64_t fixed_contrib = 0;
4837     for (int i = 0; i < proto.level_changes_size(); ++i) {
4838       const int64_t demand = proto.level_changes(i);
4839       DCHECK_NE(demand, 0);
4840 
4841       const int active = proto.active_literals(i);
4842       if (RefIsPositive(active)) {
4843         sum->add_vars(active);
4844         sum->add_coeffs(demand);
4845       } else {
4846         sum->add_vars(PositiveRef(active));
4847         sum->add_coeffs(-demand);
4848         fixed_contrib += demand;
4849       }
4850     }
4851     sum->add_domain(proto.min_level() - fixed_contrib);
4852     sum->add_domain(proto.max_level() - fixed_contrib);
4853     context_->UpdateRuleStats("reservoir: converted to linear");
4854     return RemoveConstraint(ct);
4855   }
4856 
4857   if (gcd > 1) {
4858     for (int i = 0; i < proto.level_changes_size(); ++i) {
4859       proto.set_level_changes(i, proto.level_changes(i) / gcd);
4860     }
4861 
4862     // Adjust min and max levels.
4863     //   max level is always rounded down.
4864     //   min level is always rounded up.
4865     const Domain reduced_domain = Domain({proto.min_level(), proto.max_level()})
4866                                       .InverseMultiplicationBy(gcd);
4867     proto.set_min_level(reduced_domain.Min());
4868     proto.set_max_level(reduced_domain.Max());
4869     context_->UpdateRuleStats(
4870         "reservoir: simplify level_changes and levels by gcd.");
4871   }
4872 
4873   if (num_positives == 1 && num_negatives > 0) {
4874     context_->UpdateRuleStats(
4875         "TODO reservoir: one producer, multiple consumers.");
4876   }
4877 
4878   absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set;
4879   for (int i = 0; i < proto.level_changes_size(); ++i) {
4880     const LinearExpressionProto& time = proto.time_exprs(i);
4881     const int var = context_->IsFixed(time) ? std::numeric_limits<int>::min()
4882                                             : time.vars(0);
4883     const int64_t coeff = context_->IsFixed(time) ? 0 : time.coeffs(0);
4884     const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple(
4885         var, coeff,
4886         context_->IsFixed(time) ? context_->FixedValue(time) : time.offset(),
4887         proto.active_literals(i));
4888     if (time_active_set.contains(key)) {
4889       context_->UpdateRuleStats("TODO reservoir: merge synchronized events.");
4890       break;
4891     } else {
4892       time_active_set.insert(key);
4893     }
4894   }
4895 
4896   return changed;
4897 }
4898 
4899 // TODO(user): It is probably more efficient to keep all the bool_and in a
4900 // global place during all the presolve, and just output them at the end
4901 // rather than modifying more than once the proto.
4902 void CpModelPresolver::ExtractBoolAnd() {
4903   absl::flat_hash_map<int, int> ref_to_bool_and;
4904   const int num_constraints = context_->working_model->constraints_size();
4905   std::vector<int> to_remove;
4906   for (int c = 0; c < num_constraints; ++c) {
4907     const ConstraintProto& ct = context_->working_model->constraints(c);
4908     if (HasEnforcementLiteral(ct)) continue;
4909 
4910     if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr &&
4911         ct.bool_or().literals().size() == 2) {
4912       AddImplication(NegatedRef(ct.bool_or().literals(0)),
4913                      ct.bool_or().literals(1), context_->working_model,
4914                      &ref_to_bool_and);
4915       to_remove.push_back(c);
4916       continue;
4917     }
4918 
4919     if (ct.constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne &&
4920         ct.at_most_one().literals().size() == 2) {
4921       AddImplication(ct.at_most_one().literals(0),
4922                      NegatedRef(ct.at_most_one().literals(1)),
4923                      context_->working_model, &ref_to_bool_and);
4924       to_remove.push_back(c);
4925       continue;
4926     }
4927   }
4928 
4929   context_->UpdateNewConstraintsVariableUsage();
4930   for (const int c : to_remove) {
4931     ConstraintProto* ct = context_->working_model->mutable_constraints(c);
4932     CHECK(RemoveConstraint(ct));
4933     context_->UpdateConstraintVariableUsage(c);
4934   }
4935 }
4936 
4937 // TODO(user): It might make sense to run this in parallel. The same apply for
4938 // other expansive and self-contains steps like symmetry detection, etc...
4939 void CpModelPresolver::Probe() {
4940   Model model;
4941   if (!LoadModelForProbing(context_, &model)) return;
4942 
4943   // Probe.
4944   //
4945   // TODO(user): Compute the transitive reduction instead of just the
4946   // equivalences, and use the newly learned binary clauses?
4947   auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
4948   auto* sat_solver = model.GetOrCreate<SatSolver>();
4949   auto* mapping = model.GetOrCreate<CpModelMapping>();
4950   auto* prober = model.GetOrCreate<Prober>();
4951   prober->ProbeBooleanVariables(/*deterministic_time_limit=*/1.0);
4952   context_->time_limit()->AdvanceDeterministicTime(
4953       model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
4954   if (sat_solver->IsModelUnsat() || !implication_graph->DetectEquivalences()) {
4955     return (void)context_->NotifyThatModelIsUnsat("during probing");
4956   }
4957 
4958   // Update the presolve context with fixed Boolean variables.
4959   CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
4960   for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
4961     const Literal l = sat_solver->LiteralTrail()[i];
4962     const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
4963     if (var >= 0) {
4964       const int ref = l.IsPositive() ? var : NegatedRef(var);
4965       if (!context_->SetLiteralToTrue(ref)) return;
4966     }
4967   }
4968 
4969   const int num_variables = context_->working_model->variables().size();
4970   auto* integer_trail = model.GetOrCreate<IntegerTrail>();
4971   for (int var = 0; var < num_variables; ++var) {
4972     // Restrict IntegerVariable domain.
4973     // Note that Boolean are already dealt with above.
4974     if (!mapping->IsBoolean(var)) {
4975       if (!context_->IntersectDomainWith(
4976               var,
4977               integer_trail->InitialVariableDomain(mapping->Integer(var)))) {
4978         return;
4979       }
4980       continue;
4981     }
4982 
4983     // Add Boolean equivalence relations.
4984     const Literal l = mapping->Literal(var);
4985     const Literal r = implication_graph->RepresentativeOf(l);
4986     if (r != l) {
4987       const int r_var =
4988           mapping->GetProtoVariableFromBooleanVariable(r.Variable());
4989       CHECK_GE(r_var, 0);
4990       context_->StoreBooleanEqualityRelation(
4991           var, r.IsPositive() ? r_var : NegatedRef(r_var));
4992     }
4993   }
4994 }
4995 
4996 // TODO(user): What to do with the at_most_one/exactly_one constraints?
4997 // currently we do not take them into account here.
4998 void CpModelPresolver::PresolvePureSatPart() {
4999   // TODO(user): Reenable some SAT presolve with
5000   // keep_all_feasible_solutions set to true.
5001   if (context_->ModelIsUnsat() || context_->keep_all_feasible_solutions) return;
5002 
5003   const int num_variables = context_->working_model->variables_size();
5004   SatPostsolver sat_postsolver(num_variables);
5005   SatPresolver sat_presolver(&sat_postsolver, logger_);
5006   sat_presolver.SetNumVariables(num_variables);
5007   sat_presolver.SetTimeLimit(context_->time_limit());
5008 
5009   SatParameters params = context_->params();
5010 
5011   // The "full solver" postsolve does not support changing the value of a
5012   // variable from the solution of the presolved problem, and we do need this
5013   // for blocked clause. It should be possible to allow for this by adding extra
5014   // variable to the mapping model at presolve and some linking constraints, but
5015   // this is messy.
5016   if (params.debug_postsolve_with_full_solver()) {
5017     params.set_presolve_blocked_clause(false);
5018   }
5019 
5020   // TODO(user): BVA takes time_exprs and do not seems to help on the minizinc
5021   // benchmarks. That said, it was useful on pure sat problems, so we may
5022   // want to enable it.
5023   params.set_presolve_use_bva(false);
5024   sat_presolver.SetParameters(params);
5025 
5026   // Converts a cp_model literal ref to a sat::Literal used by SatPresolver.
5027   absl::flat_hash_set<int> used_variables;
5028   auto convert = [&used_variables](int ref) {
5029     used_variables.insert(PositiveRef(ref));
5030     if (RefIsPositive(ref)) return Literal(BooleanVariable(ref), true);
5031     return Literal(BooleanVariable(NegatedRef(ref)), false);
5032   };
5033 
5034   // We need all Boolean constraints to be presolved before loading them below.
5035   // Otherwise duplicate literals might result in a wrong outcome.
5036   //
5037   // TODO(user): Be a bit more efficient, and enforce this invariant before we
5038   // reach this point?
5039   for (int c = 0; c < context_->working_model->constraints_size(); ++c) {
5040     const ConstraintProto& ct = context_->working_model->constraints(c);
5041     if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
5042         ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
5043       if (PresolveOneConstraint(c)) {
5044         context_->UpdateConstraintVariableUsage(c);
5045       }
5046       if (context_->ModelIsUnsat()) return;
5047     }
5048   }
5049 
5050   // Load all Clauses into the presolver and remove them from the current model.
5051   //
5052   // TODO(user): The removing and adding back of the same clause when nothing
5053   // happens in the presolve "seems" bad. That said, complexity wise, it is
5054   // a lot faster that what happens in the presolve though.
5055   //
5056   // TODO(user): Add the "small" at most one constraints to the SAT presolver by
5057   // expanding them to implications? that could remove a lot of clauses. Do that
5058   // when we are sure we don't load duplicates at_most_one/implications in the
5059   // solver. Ideally, the pure sat presolve could be improved to handle at most
5060   // one, and we could merge this with what the ProcessSetPPC() is doing.
5061   std::vector<Literal> clause;
5062   int num_removed_constraints = 0;
5063   for (int i = 0; i < context_->working_model->constraints_size(); ++i) {
5064     const ConstraintProto& ct = context_->working_model->constraints(i);
5065 
5066     if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
5067       ++num_removed_constraints;
5068       clause.clear();
5069       for (const int ref : ct.bool_or().literals()) {
5070         clause.push_back(convert(ref));
5071       }
5072       for (const int ref : ct.enforcement_literal()) {
5073         clause.push_back(convert(ref).Negated());
5074       }
5075       sat_presolver.AddClause(clause);
5076 
5077       context_->working_model->mutable_constraints(i)->Clear();
5078       context_->UpdateConstraintVariableUsage(i);
5079       continue;
5080     }
5081 
5082     if (ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
5083       ++num_removed_constraints;
5084       std::vector<Literal> clause;
5085       for (const int ref : ct.enforcement_literal()) {
5086         clause.push_back(convert(ref).Negated());
5087       }
5088       clause.push_back(Literal(kNoLiteralIndex));  // will be replaced below.
5089       for (const int ref : ct.bool_and().literals()) {
5090         clause.back() = convert(ref);
5091         sat_presolver.AddClause(clause);
5092       }
5093 
5094       context_->working_model->mutable_constraints(i)->Clear();
5095       context_->UpdateConstraintVariableUsage(i);
5096       continue;
5097     }
5098   }
5099 
5100   // Abort early if there was no Boolean constraints.
5101   if (num_removed_constraints == 0) return;
5102 
5103   // Mark the variables appearing elsewhere or in the objective as non-removable
5104   // by the sat presolver.
5105   //
5106   // TODO(user): do not remove variable that appear in the decision heuristic?
5107   // TODO(user): We could go further for variable with only one polarity by
5108   // removing variable from the objective if they can be set to their "low"
5109   // objective value, and also removing enforcement literal that can be set to
5110   // false and don't appear elsewhere.
5111   std::vector<bool> can_be_removed(num_variables, false);
5112   for (int i = 0; i < num_variables; ++i) {
5113     if (context_->VarToConstraints(i).empty()) {
5114       can_be_removed[i] = true;
5115     }
5116 
5117     // Because we might not have reached the presove "fixed point" above, some
5118     // variable in the added clauses might be fixed. We need to indicate this to
5119     // the SAT presolver.
5120     if (used_variables.contains(i) && context_->IsFixed(i)) {
5121       if (context_->LiteralIsTrue(i)) {
5122         sat_presolver.AddClause({convert(i)});
5123       } else {
5124         sat_presolver.AddClause({convert(NegatedRef(i))});
5125       }
5126     }
5127   }
5128 
5129   // Run the presolve for a small number of passes.
5130   // TODO(user): Add probing like we do in the pure sat solver presolve loop?
5131   // TODO(user): Add a time limit, this can be slow on big SAT problem.
5132   const int num_passes = params.presolve_use_bva() ? 4 : 1;
5133   for (int i = 0; i < num_passes; ++i) {
5134     const int old_num_clause = sat_postsolver.NumClauses();
5135     if (!sat_presolver.Presolve(can_be_removed)) {
5136       VLOG(1) << "UNSAT during SAT presolve.";
5137       return (void)context_->NotifyThatModelIsUnsat();
5138     }
5139     if (old_num_clause == sat_postsolver.NumClauses()) break;
5140   }
5141 
5142   // Add any new variables to our internal structure.
5143   const int new_num_variables = sat_presolver.NumVariables();
5144   if (new_num_variables > context_->working_model->variables_size()) {
5145     VLOG(1) << "New variables added by the SAT presolver.";
5146     for (int i = context_->working_model->variables_size();
5147          i < new_num_variables; ++i) {
5148       IntegerVariableProto* var_proto =
5149           context_->working_model->add_variables();
5150       var_proto->add_domain(0);
5151       var_proto->add_domain(1);
5152     }
5153     context_->InitializeNewDomains();
5154   }
5155 
5156   // Add the presolver clauses back into the model.
5157   ExtractClauses(/*use_bool_and=*/true, sat_presolver, context_->working_model);
5158 
5159   // Update the constraints <-> variables graph.
5160   context_->UpdateNewConstraintsVariableUsage();
5161 
5162   // Add the sat_postsolver clauses to mapping_model.
5163   //
5164   // TODO(user): Mark removed variable as removed to detect any potential bugs.
5165   ExtractClauses(/*use_bool_and=*/false, sat_postsolver,
5166                  context_->mapping_model);
5167 }
5168 
5169 // TODO(user): The idea behind this was that it is better to have an objective
5170 // as spreaded as possible. However on some problems this have the opposite
5171 // effect. Like on a triangular matrix where each expansion reduced the size
5172 // of the objective by one. Investigate and fix?
5173 void CpModelPresolver::ExpandObjective() {
5174   if (context_->ModelIsUnsat()) return;
5175 
5176   // The objective is already loaded in the context, but we re-canonicalize
5177   // it with the latest information.
5178   if (!context_->CanonicalizeObjective()) {
5179     (void)context_->NotifyThatModelIsUnsat();
5180     return;
5181   }
5182 
5183   if (context_->time_limit()->LimitReached()) {
5184     context_->WriteObjectiveToProto();
5185     return;
5186   }
5187 
5188   // If the objective is a single variable, then we can usually remove this
5189   // variable if it is only used in one linear equality constraint and we do
5190   // just one expansion. This is because the domain of the variable will be
5191   // transferred to our objective_domain.
5192   int unique_expanded_constraint = -1;
5193   const bool objective_was_a_single_variable =
5194       context_->ObjectiveMap().size() == 1;
5195 
5196   // To avoid a bad complexity, we need to compute the number of relevant
5197   // constraints for each variables.
5198   const int num_variables = context_->working_model->variables_size();
5199   const int num_constraints = context_->working_model->constraints_size();
5200   absl::flat_hash_set<int> relevant_constraints;
5201   std::vector<int> var_to_num_relevant_constraints(num_variables, 0);
5202   for (int ct_index = 0; ct_index < num_constraints; ++ct_index) {
5203     const ConstraintProto& ct = context_->working_model->constraints(ct_index);
5204     // Skip everything that is not a linear equality constraint.
5205     if (!ct.enforcement_literal().empty() ||
5206         ct.constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
5207         ct.linear().domain().size() != 2 ||
5208         ct.linear().domain(0) != ct.linear().domain(1)) {
5209       continue;
5210     }
5211 
5212     relevant_constraints.insert(ct_index);
5213     const int num_terms = ct.linear().vars_size();
5214     for (int i = 0; i < num_terms; ++i) {
5215       var_to_num_relevant_constraints[PositiveRef(ct.linear().vars(i))]++;
5216     }
5217   }
5218 
5219   std::set<int> var_to_process;
5220   for (const auto entry : context_->ObjectiveMap()) {
5221     const int var = entry.first;
5222     CHECK(RefIsPositive(var));
5223     if (var_to_num_relevant_constraints[var] != 0) {
5224       var_to_process.insert(var);
5225     }
5226   }
5227 
5228   // We currently never expand a variable more than once.
5229   int num_expansions = 0;
5230   int last_expanded_objective_var;
5231   absl::flat_hash_set<int> processed_vars;
5232   std::vector<int> new_vars_in_objective;
5233   while (!relevant_constraints.empty()) {
5234     // Find a not yet expanded var.
5235     int objective_var = -1;
5236     while (!var_to_process.empty()) {
5237       const int var = *var_to_process.begin();
5238       CHECK(!processed_vars.contains(var));
5239       if (var_to_num_relevant_constraints[var] == 0) {
5240         processed_vars.insert(var);
5241         var_to_process.erase(var);
5242         continue;
5243       }
5244       if (!context_->ObjectiveMap().contains(var)) {
5245         // We do not mark it as processed in case it re-appear later.
5246         var_to_process.erase(var);
5247         continue;
5248       }
5249       objective_var = var;
5250       break;
5251     }
5252 
5253     if (objective_var == -1) break;
5254     CHECK(RefIsPositive(objective_var));
5255     processed_vars.insert(objective_var);
5256     var_to_process.erase(objective_var);
5257 
5258     int expanded_linear_index = -1;
5259     int64_t objective_coeff_in_expanded_constraint;
5260     int64_t size_of_expanded_constraint = 0;
5261     const auto& non_deterministic_list =
5262         context_->VarToConstraints(objective_var);
5263     std::vector<int> constraints_with_objective(non_deterministic_list.begin(),
5264                                                 non_deterministic_list.end());
5265     std::sort(constraints_with_objective.begin(),
5266               constraints_with_objective.end());
5267     for (const int ct_index : constraints_with_objective) {
5268       if (relevant_constraints.count(ct_index) == 0) continue;
5269       const ConstraintProto& ct =
5270           context_->working_model->constraints(ct_index);
5271 
5272       // This constraint is relevant now, but it will never be later because
5273       // it will contain the objective_var which is already processed!
5274       relevant_constraints.erase(ct_index);
5275       const int num_terms = ct.linear().vars_size();
5276       for (int i = 0; i < num_terms; ++i) {
5277         var_to_num_relevant_constraints[PositiveRef(ct.linear().vars(i))]--;
5278       }
5279 
5280       // Find the coefficient of objective_var in this constraint, and perform
5281       // various checks.
5282       //
5283       // TODO(user): This can crash the program if for some reason the linear
5284       // constraint was not canonicalized and contains the objective variable
5285       // twice. Currently this can only happen if the time limit was reached
5286       // before all constraints where processed, but because we abort at the
5287       // beginning of the function when this is the case we should be safe.
5288       // However, it might be more robust to just handle this case properly.
5289       bool is_present = false;
5290       int64_t objective_coeff;
5291       for (int i = 0; i < num_terms; ++i) {
5292         const int ref = ct.linear().vars(i);
5293         const int64_t coeff = ct.linear().coeffs(i);
5294         if (PositiveRef(ref) == objective_var) {
5295           CHECK(!is_present) << "Duplicate variables not supported.";
5296           is_present = true;
5297           objective_coeff = (ref == objective_var) ? coeff : -coeff;
5298         } else {
5299           // This is not possible since we only consider relevant constraints.
5300           CHECK(!processed_vars.contains(PositiveRef(ref)));
5301         }
5302       }
5303       CHECK(is_present);
5304 
5305       // We use the longest equality we can find.
5306       //
5307       // TODO(user): Deal with objective_coeff with a magnitude greater than
5308       // 1? This will only be possible if we change the objective coeff type
5309       // to double.
5310       if (std::abs(objective_coeff) == 1 &&
5311           num_terms > size_of_expanded_constraint) {
5312         expanded_linear_index = ct_index;
5313         size_of_expanded_constraint = num_terms;
5314         objective_coeff_in_expanded_constraint = objective_coeff;
5315       }
5316     }
5317 
5318     if (expanded_linear_index != -1) {
5319       // Update the objective map. Note that the division is possible because
5320       // currently we only expand with coeff with a magnitude of 1.
5321       CHECK_EQ(std::abs(objective_coeff_in_expanded_constraint), 1);
5322       const ConstraintProto& ct =
5323           context_->working_model->constraints(expanded_linear_index);
5324       if (!context_->SubstituteVariableInObjective(
5325               objective_var, objective_coeff_in_expanded_constraint, ct,
5326               &new_vars_in_objective)) {
5327         if (context_->ModelIsUnsat()) return;
5328         continue;
5329       }
5330 
5331       context_->UpdateRuleStats("objective: expanded objective constraint.");
5332 
5333       // Add not yet processed new variables.
5334       for (const int var : new_vars_in_objective) {
5335         if (!processed_vars.contains(var)) var_to_process.insert(var);
5336       }
5337 
5338       // If the objective variable wasn't used in other constraints and it can
5339       // be reconstructed whatever the value of the other variables, we can
5340       // remove the constraint.
5341       //
5342       // TODO(user): It should be possible to refactor the code so this is
5343       // automatically done by the linear constraint singleton presolve rule.
5344       if (context_->VarToConstraints(objective_var).size() == 1 &&
5345           !context_->keep_all_feasible_solutions) {
5346         // Compute implied domain on objective_var.
5347         Domain implied_domain = ReadDomainFromProto(ct.linear());
5348         for (int i = 0; i < size_of_expanded_constraint; ++i) {
5349           const int ref = ct.linear().vars(i);
5350           if (PositiveRef(ref) == objective_var) continue;
5351           implied_domain =
5352               implied_domain
5353                   .AdditionWith(context_->DomainOf(ref).MultiplicationBy(
5354                       -ct.linear().coeffs(i)))
5355                   .RelaxIfTooComplex();
5356         }
5357         implied_domain = implied_domain.InverseMultiplicationBy(
5358             objective_coeff_in_expanded_constraint);
5359 
5360         // Remove the constraint if the implied domain is included in the
5361         // domain of the objective_var term.
5362         if (implied_domain.IsIncludedIn(context_->DomainOf(objective_var))) {
5363           context_->MarkVariableAsRemoved(objective_var);
5364           context_->UpdateRuleStats("objective: removed objective constraint.");
5365           *(context_->mapping_model->add_constraints()) = ct;
5366           context_->working_model->mutable_constraints(expanded_linear_index)
5367               ->Clear();
5368           context_->UpdateConstraintVariableUsage(expanded_linear_index);
5369         } else {
5370           unique_expanded_constraint = expanded_linear_index;
5371         }
5372       }
5373 
5374       ++num_expansions;
5375       last_expanded_objective_var = objective_var;
5376     }
5377   }
5378 
5379   // Special case: If we just did one expansion of a single variable, then we
5380   // can remove the expanded constraints if the objective wasn't used elsewhere.
5381   if (num_expansions == 1 && objective_was_a_single_variable &&
5382       unique_expanded_constraint != -1) {
5383     context_->UpdateRuleStats(
5384         "objective: removed unique objective constraint.");
5385     ConstraintProto* mutable_ct = context_->working_model->mutable_constraints(
5386         unique_expanded_constraint);
5387     *(context_->mapping_model->add_constraints()) = *mutable_ct;
5388     mutable_ct->Clear();
5389     context_->MarkVariableAsRemoved(last_expanded_objective_var);
5390     context_->UpdateConstraintVariableUsage(unique_expanded_constraint);
5391   }
5392 
5393   // We re-do a canonicalization with the final linear expression.
5394   if (!context_->CanonicalizeObjective()) {
5395     (void)context_->NotifyThatModelIsUnsat();
5396     return;
5397   }
5398   context_->WriteObjectiveToProto();
5399 }
5400 
5401 void CpModelPresolver::MergeNoOverlapConstraints() {
5402   if (context_->ModelIsUnsat()) return;
5403 
5404   const int num_constraints = context_->working_model->constraints_size();
5405   int old_num_no_overlaps = 0;
5406   int old_num_intervals = 0;
5407 
5408   // Extract the no-overlap constraints.
5409   std::vector<int> disjunctive_index;
5410   std::vector<std::vector<Literal>> cliques;
5411   for (int c = 0; c < num_constraints; ++c) {
5412     const ConstraintProto& ct = context_->working_model->constraints(c);
5413     if (ct.constraint_case() != ConstraintProto::ConstraintCase::kNoOverlap) {
5414       continue;
5415     }
5416     std::vector<Literal> clique;
5417     for (const int i : ct.no_overlap().intervals()) {
5418       clique.push_back(Literal(BooleanVariable(i), true));
5419     }
5420     cliques.push_back(clique);
5421     disjunctive_index.push_back(c);
5422 
5423     old_num_no_overlaps++;
5424     old_num_intervals += clique.size();
5425   }
5426   if (old_num_no_overlaps == 0) return;
5427 
5428   // We reuse the max-clique code from sat.
5429   Model local_model;
5430   local_model.GetOrCreate<Trail>()->Resize(num_constraints);
5431   auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
5432   graph->Resize(num_constraints);
5433   for (const std::vector<Literal>& clique : cliques) {
5434     // All variables at false is always a valid solution of the local model,
5435     // so this should never return UNSAT.
5436     CHECK(graph->AddAtMostOne(clique));
5437   }
5438   CHECK(graph->DetectEquivalences());
5439   graph->TransformIntoMaxCliques(
5440       &cliques, context_->params().merge_no_overlap_work_limit());
5441 
5442   // Replace each no-overlap with an extended version, or remove if empty.
5443   int new_num_no_overlaps = 0;
5444   int new_num_intervals = 0;
5445   for (int i = 0; i < cliques.size(); ++i) {
5446     const int ct_index = disjunctive_index[i];
5447     ConstraintProto* ct =
5448         context_->working_model->mutable_constraints(ct_index);
5449     ct->Clear();
5450     if (cliques[i].empty()) continue;
5451     for (const Literal l : cliques[i]) {
5452       CHECK(l.IsPositive());
5453       ct->mutable_no_overlap()->add_intervals(l.Variable().value());
5454     }
5455     new_num_no_overlaps++;
5456     new_num_intervals += cliques[i].size();
5457   }
5458   if (old_num_intervals != new_num_intervals ||
5459       old_num_no_overlaps != new_num_no_overlaps) {
5460     VLOG(1) << absl::StrCat("Merged ", old_num_no_overlaps, " no-overlaps (",
5461                             old_num_intervals, " intervals) into ",
5462                             new_num_no_overlaps, " no-overlaps (",
5463                             new_num_intervals, " intervals).");
5464     context_->UpdateRuleStats("no_overlap: merged constraints");
5465   }
5466 }
5467 
5468 // TODO(user): Should we take into account the exactly_one constraints? note
5469 // that such constraint cannot be extended. If if a literal implies two literals
5470 // at one inside an exactly one constraint then it must be false. Similarly if
5471 // it implies all literals at zero inside the exactly one.
5472 void CpModelPresolver::TransformIntoMaxCliques() {
5473   if (context_->ModelIsUnsat()) return;
5474 
5475   auto convert = [](int ref) {
5476     if (RefIsPositive(ref)) return Literal(BooleanVariable(ref), true);
5477     return Literal(BooleanVariable(NegatedRef(ref)), false);
5478   };
5479   const int num_constraints = context_->working_model->constraints_size();
5480 
5481   // Extract the bool_and and at_most_one constraints.
5482   std::vector<std::vector<Literal>> cliques;
5483 
5484   for (int c = 0; c < num_constraints; ++c) {
5485     ConstraintProto* ct = context_->working_model->mutable_constraints(c);
5486     if (ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
5487       std::vector<Literal> clique;
5488       for (const int ref : ct->at_most_one().literals()) {
5489         clique.push_back(convert(ref));
5490       }
5491       cliques.push_back(clique);
5492       if (RemoveConstraint(ct)) {
5493         context_->UpdateConstraintVariableUsage(c);
5494       }
5495     } else if (ct->constraint_case() ==
5496                ConstraintProto::ConstraintCase::kBoolAnd) {
5497       if (ct->enforcement_literal().size() != 1) continue;
5498       const Literal enforcement = convert(ct->enforcement_literal(0));
5499       for (const int ref : ct->bool_and().literals()) {
5500         if (ref == ct->enforcement_literal(0)) continue;
5501         cliques.push_back({enforcement, convert(ref).Negated()});
5502       }
5503       if (RemoveConstraint(ct)) {
5504         context_->UpdateConstraintVariableUsage(c);
5505       }
5506     }
5507   }
5508 
5509   int64_t num_literals_before = 0;
5510   const int num_old_cliques = cliques.size();
5511 
5512   // We reuse the max-clique code from sat.
5513   Model local_model;
5514   const int num_variables = context_->working_model->variables().size();
5515   local_model.GetOrCreate<Trail>()->Resize(num_variables);
5516   auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
5517   graph->Resize(num_variables);
5518   for (const std::vector<Literal>& clique : cliques) {
5519     num_literals_before += clique.size();
5520     if (!graph->AddAtMostOne(clique)) {
5521       return (void)context_->NotifyThatModelIsUnsat();
5522     }
5523   }
5524   if (!graph->DetectEquivalences()) {
5525     return (void)context_->NotifyThatModelIsUnsat();
5526   }
5527   graph->TransformIntoMaxCliques(
5528       &cliques, context_->params().merge_at_most_one_work_limit());
5529 
5530   // Add the Boolean variable equivalence detected by DetectEquivalences().
5531   // Those are needed because TransformIntoMaxCliques() will replace all
5532   // variable by its representative.
5533   for (int var = 0; var < num_variables; ++var) {
5534     const Literal l = Literal(BooleanVariable(var), true);
5535     if (graph->RepresentativeOf(l) != l) {
5536       const Literal r = graph->RepresentativeOf(l);
5537       context_->StoreBooleanEqualityRelation(
5538           var, r.IsPositive() ? r.Variable().value()
5539                               : NegatedRef(r.Variable().value()));
5540     }
5541   }
5542 
5543   int num_new_cliques = 0;
5544   int64_t num_literals_after = 0;
5545   for (const std::vector<Literal>& clique : cliques) {
5546     if (clique.empty()) continue;
5547     num_new_cliques++;
5548     num_literals_after += clique.size();
5549     ConstraintProto* ct = context_->working_model->add_constraints();
5550     for (const Literal literal : clique) {
5551       if (literal.IsPositive()) {
5552         ct->mutable_at_most_one()->add_literals(literal.Variable().value());
5553       } else {
5554         ct->mutable_at_most_one()->add_literals(
5555             NegatedRef(literal.Variable().value()));
5556       }
5557     }
5558 
5559     // Make sure we do not have duplicate variable reference.
5560     PresolveAtMostOne(ct);
5561   }
5562   context_->UpdateNewConstraintsVariableUsage();
5563   if (num_new_cliques != num_old_cliques) {
5564     context_->UpdateRuleStats("at_most_one: transformed into max clique.");
5565   }
5566 
5567   if (num_old_cliques != num_new_cliques ||
5568       num_literals_before != num_literals_after) {
5569     SOLVER_LOG(logger_, "[MaxClique] Merged ", num_old_cliques, "(",
5570                num_literals_before, " literals) into ", num_new_cliques, "(",
5571                num_literals_after, " literals) at_most_ones.");
5572   }
5573 }
5574 
5575 namespace {
5576 
5577 bool IsAffineIntAbs(ConstraintProto* ct) {
5578   if (ct->constraint_case() != ConstraintProto::kLinMax ||
5579       ct->lin_max().exprs_size() != 2 ||
5580       ct->lin_max().target().vars_size() > 1 ||
5581       ct->lin_max().exprs(0).vars_size() != 1 ||
5582       ct->lin_max().exprs(1).vars_size() != 1) {
5583     return false;
5584   }
5585 
5586   const LinearArgumentProto& lin_max = ct->lin_max();
5587   if (lin_max.exprs(0).offset() != -lin_max.exprs(1).offset()) return false;
5588   if (PositiveRef(lin_max.exprs(0).vars(0)) !=
5589       PositiveRef(lin_max.exprs(1).vars(0))) {
5590     return false;
5591   }
5592 
5593   const int64_t left_coeff = RefIsPositive(lin_max.exprs(0).vars(0))
5594                                  ? lin_max.exprs(0).coeffs(0)
5595                                  : -lin_max.exprs(0).coeffs(0);
5596   const int64_t right_coeff = RefIsPositive(lin_max.exprs(1).vars(0))
5597                                   ? lin_max.exprs(1).coeffs(0)
5598                                   : -lin_max.exprs(1).coeffs(0);
5599   return left_coeff == -right_coeff;
5600 }
5601 
5602 }  // namespace
5603 
5604 bool CpModelPresolver::PresolveOneConstraint(int c) {
5605   if (context_->ModelIsUnsat()) return false;
5606   ConstraintProto* ct = context_->working_model->mutable_constraints(c);
5607 
5608   // Generic presolve to exploit variable/literal equivalence.
5609   if (ExploitEquivalenceRelations(c, ct)) {
5610     context_->UpdateConstraintVariableUsage(c);
5611   }
5612 
5613   // Generic presolve for reified constraint.
5614   if (PresolveEnforcementLiteral(ct)) {
5615     context_->UpdateConstraintVariableUsage(c);
5616   }
5617 
5618   // Call the presolve function for this constraint if any.
5619   switch (ct->constraint_case()) {
5620     case ConstraintProto::ConstraintCase::kBoolOr:
5621       return PresolveBoolOr(ct);
5622     case ConstraintProto::ConstraintCase::kBoolAnd:
5623       return PresolveBoolAnd(ct);
5624     case ConstraintProto::ConstraintCase::kAtMostOne:
5625       return PresolveAtMostOne(ct);
5626     case ConstraintProto::ConstraintCase::kExactlyOne:
5627       return PresolveExactlyOne(ct);
5628     case ConstraintProto::ConstraintCase::kBoolXor:
5629       return PresolveBoolXor(ct);
5630     case ConstraintProto::ConstraintCase::kLinMax:
5631       if (CanonicalizeLinMax(ct)) {
5632         context_->UpdateConstraintVariableUsage(c);
5633       }
5634       if (IsAffineIntAbs(ct)) {
5635         return PresolveIntAbs(ct);
5636       } else {
5637         return PresolveLinMax(ct);
5638       }
5639     case ConstraintProto::ConstraintCase::kIntProd:
5640       return PresolveIntProd(ct);
5641     case ConstraintProto::ConstraintCase::kIntDiv:
5642       return PresolveIntDiv(ct);
5643     case ConstraintProto::ConstraintCase::kIntMod:
5644       return PresolveIntMod(ct);
5645     case ConstraintProto::ConstraintCase::kLinear: {
5646       // In the presence of affine relation, it is possible that the sign of a
5647       // variable change during canonicalization, and a variable that could
5648       // freely move in one direction can no longer do so. So we make sure we
5649       // always remove c from all the maps before re-inserting it in
5650       // PropagateDomainsInLinear().
5651       //
5652       // TODO(user): The move in only one direction code is redundant with the
5653       // dual bound strengthening code. So maybe we don't need both.
5654       for (const int ref : ct->linear().vars()) {
5655         const int var = PositiveRef(ref);
5656         context_->var_to_lb_only_constraints[var].erase(c);
5657         context_->var_to_ub_only_constraints[var].erase(c);
5658       }
5659 
5660       if (CanonicalizeLinear(ct)) {
5661         context_->UpdateConstraintVariableUsage(c);
5662       }
5663       if (PropagateDomainsInLinear(c, ct)) {
5664         context_->UpdateConstraintVariableUsage(c);
5665       }
5666       if (PresolveSmallLinear(ct)) {
5667         context_->UpdateConstraintVariableUsage(c);
5668       }
5669       if (PresolveLinearEqualityWithModulo(ct)) {
5670         context_->UpdateConstraintVariableUsage(c);
5671       }
5672       // We first propagate the domains before calling this presolve rule.
5673       if (RemoveSingletonInLinear(ct)) {
5674         context_->UpdateConstraintVariableUsage(c);
5675 
5676         // There is no need to re-do a propagation here, but the constraint
5677         // size might have been reduced.
5678         if (PresolveSmallLinear(ct)) {
5679           context_->UpdateConstraintVariableUsage(c);
5680         }
5681       }
5682       if (PresolveSmallLinear(ct)) {
5683         context_->UpdateConstraintVariableUsage(c);
5684       }
5685       if (PresolveLinearOnBooleans(ct)) {
5686         context_->UpdateConstraintVariableUsage(c);
5687       }
5688       if (ct->constraint_case() == ConstraintProto::kLinear) {
5689         const int old_num_enforcement_literals = ct->enforcement_literal_size();
5690         ExtractEnforcementLiteralFromLinearConstraint(c, ct);
5691         if (ct->constraint_case() ==
5692             ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
5693           context_->UpdateConstraintVariableUsage(c);
5694           return true;
5695         }
5696         if (ct->enforcement_literal_size() > old_num_enforcement_literals &&
5697             PresolveSmallLinear(ct)) {
5698           context_->UpdateConstraintVariableUsage(c);
5699         }
5700       }
5701 
5702       // Note that it is important for this to be last, so that if a constraint
5703       // is marked as being in one direction, no other presolve is applied until
5704       // it is processed again and unmarked at the beginning of this case.
5705       if (ct->constraint_case() == ConstraintProto::kLinear &&
5706           DetectAndProcessOneSidedLinearConstraint(c, ct)) {
5707         context_->UpdateConstraintVariableUsage(c);
5708       }
5709       return false;
5710     }
5711     case ConstraintProto::ConstraintCase::kInterval:
5712       return PresolveInterval(c, ct);
5713     case ConstraintProto::ConstraintCase::kInverse:
5714       return PresolveInverse(ct);
5715     case ConstraintProto::ConstraintCase::kElement:
5716       return PresolveElement(ct);
5717     case ConstraintProto::ConstraintCase::kTable:
5718       return PresolveTable(ct);
5719     case ConstraintProto::ConstraintCase::kAllDiff:
5720       return PresolveAllDiff(ct);
5721     case ConstraintProto::ConstraintCase::kNoOverlap:
5722       return PresolveNoOverlap(ct);
5723     case ConstraintProto::ConstraintCase::kNoOverlap2D:
5724       return PresolveNoOverlap2D(c, ct);
5725     case ConstraintProto::ConstraintCase::kCumulative:
5726       return PresolveCumulative(ct);
5727     case ConstraintProto::ConstraintCase::kCircuit:
5728       return PresolveCircuit(ct);
5729     case ConstraintProto::ConstraintCase::kRoutes:
5730       return PresolveRoutes(ct);
5731     case ConstraintProto::ConstraintCase::kAutomaton:
5732       return PresolveAutomaton(ct);
5733     case ConstraintProto::ConstraintCase::kReservoir:
5734       return PresolveReservoir(ct);
5735     default:
5736       return false;
5737   }
5738 }
5739 
5740 // This is called with constraint c1 whose literals are included in the literals
5741 // of c2.
5742 //
5743 // Returns false iff the model is UNSAT.
5744 bool CpModelPresolver::ProcessSetPPCSubset(
5745     int c1, int c2, const std::vector<int>& c2_minus_c1,
5746     const std::vector<int>& original_constraint_index,
5747     std::vector<bool>* marked_for_removal) {
5748   if (context_->ModelIsUnsat()) return false;
5749 
5750   CHECK(!(*marked_for_removal)[c1]);
5751   CHECK(!(*marked_for_removal)[c2]);
5752 
5753   ConstraintProto* ct1 = context_->working_model->mutable_constraints(
5754       original_constraint_index[c1]);
5755   ConstraintProto* ct2 = context_->working_model->mutable_constraints(
5756       original_constraint_index[c2]);
5757 
5758   if ((ct1->constraint_case() == ConstraintProto::kBoolOr ||
5759        ct1->constraint_case() == ConstraintProto::kExactlyOne) &&
5760       (ct2->constraint_case() == ConstraintProto::kAtMostOne ||
5761        ct2->constraint_case() == ConstraintProto::kExactlyOne)) {
5762     context_->UpdateRuleStats("setppc: bool_or in at_most_one.");
5763 
5764     // Fix extras in c2 to 0, note that these will be removed from the
5765     // constraint later.
5766     for (const int literal : c2_minus_c1) {
5767       if (!context_->SetLiteralToFalse(literal)) return false;
5768       context_->UpdateRuleStats("setppc: fixed variables");
5769     }
5770 
5771     // Change c2 to exactly_one if not already.
5772     if (ct2->constraint_case() != ConstraintProto::kExactlyOne) {
5773       ConstraintProto copy = *ct2;
5774       (*ct2->mutable_exactly_one()->mutable_literals()) =
5775           copy.at_most_one().literals();
5776     }
5777 
5778     // Remove c1.
5779     (*marked_for_removal)[c1] = true;
5780     ct1->Clear();
5781     context_->UpdateConstraintVariableUsage(original_constraint_index[c1]);
5782     return true;
5783   }
5784 
5785   if ((ct1->constraint_case() == ConstraintProto::kBoolOr ||
5786        ct1->constraint_case() == ConstraintProto::kExactlyOne) &&
5787       ct2->constraint_case() == ConstraintProto::kBoolOr) {
5788     context_->UpdateRuleStats("setppc: removed dominated constraints");
5789 
5790     (*marked_for_removal)[c2] = true;
5791     ct2->Clear();
5792     context_->UpdateConstraintVariableUsage(original_constraint_index[c2]);
5793     return true;
5794   }
5795 
5796   if (ct1->constraint_case() == ConstraintProto::kAtMostOne &&
5797       (ct2->constraint_case() == ConstraintProto::kAtMostOne ||
5798        ct2->constraint_case() == ConstraintProto::kExactlyOne)) {
5799     context_->UpdateRuleStats("setppc: removed dominated constraints");
5800     (*marked_for_removal)[c1] = true;
5801     ct1->Clear();
5802     context_->UpdateConstraintVariableUsage(original_constraint_index[c1]);
5803     return true;
5804   }
5805 
5806   if (ct1->constraint_case() == ConstraintProto::kExactlyOne &&
5807       ct2->constraint_case() == ConstraintProto::kLinear) {
5808     // If we have an exactly one in a linear, we can shift the coefficients of
5809     // all these variables by any constant value. For now, since we only deal
5810     // with positive coefficient, we shift by the min.
5811     //
5812     // TODO(user): It might be more interesting to maximize the number of terms
5813     // that are shifted to zero to reduce the constraint size.
5814     absl::flat_hash_set<int> literals(ct1->exactly_one().literals().begin(),
5815                                       ct1->exactly_one().literals().end());
5816     int64_t min_coeff = std::numeric_limits<int64_t>::max();
5817     int num_matches = 0;
5818     for (int i = 0; i < ct2->linear().vars().size(); ++i) {
5819       const int var = ct2->linear().vars(i);
5820       if (literals.contains(var)) {
5821         ++num_matches;
5822         min_coeff = std::min(min_coeff, std::abs(ct2->linear().coeffs(i)));
5823       }
5824     }
5825 
5826     // If a linear constraint contains more than one at most one, after
5827     // processing one, we might no longer have an inclusion.
5828     if (num_matches != literals.size()) return true;
5829 
5830     // TODO(user): It would be cool to propagate other variable domains with
5831     // the knowledge that the partial sum in is [min_coeff, max_coeff]. I am
5832     // a bit relunctant to duplicate the code for that here.
5833     int new_size = 0;
5834     for (int i = 0; i < ct2->linear().vars().size(); ++i) {
5835       const int var = ct2->linear().vars(i);
5836       int64_t coeff = ct2->linear().coeffs(i);
5837       if (literals.contains(var)) {
5838         CHECK_GE(coeff, 0);
5839         if (coeff == min_coeff) continue;  // delete term.
5840         coeff -= min_coeff;
5841       }
5842       ct2->mutable_linear()->set_vars(new_size, var);
5843       ct2->mutable_linear()->set_coeffs(new_size, coeff);
5844       ++new_size;
5845     }
5846 
5847     ct2->mutable_linear()->mutable_vars()->Truncate(new_size);
5848     ct2->mutable_linear()->mutable_coeffs()->Truncate(new_size);
5849     FillDomainInProto(
5850         ReadDomainFromProto(ct2->linear()).AdditionWith(Domain(-min_coeff)),
5851         ct2->mutable_linear());
5852     context_->UpdateConstraintVariableUsage(original_constraint_index[c2]);
5853     context_->UpdateRuleStats("setppc: reduced linear coefficients.");
5854   }
5855 
5856   // We can't deduce anything in the last remaining case:
5857   // ct1->constraint_case() == ConstraintProto::kAtMostOne &&
5858   // ct2->constraint_case() == ConstraintProto::kBoolOr
5859 
5860   return true;
5861 }
5862 
5863 // TODO(user): TransformIntoMaxCliques() convert the bool_and to
5864 // at_most_one, but maybe also duplicating them into bool_or would allow this
5865 // function to do more presolving.
5866 bool CpModelPresolver::ProcessSetPPC() {
5867   const int num_constraints = context_->working_model->constraints_size();
5868 
5869   // Signatures of all the constraints. In the signature the bit i is 1 if it
5870   // contains a literal l such that l%64 = i.
5871   std::vector<uint64_t> signatures;
5872 
5873   // Graph of constraints to literals. constraint_literals[c] contains all the
5874   // literals in constraint indexed by 'c' in sorted order.
5875   std::vector<std::vector<int>> constraint_literals;
5876 
5877   // Graph of literals to constraints. literals_to_constraints[l] contains the
5878   // vector of constraint indices in which literal 'l' or 'neg(l)' appears.
5879   std::vector<std::vector<int>> literals_to_constraints;
5880 
5881   // vector of booleans indicating if the constraint was already removed.
5882   std::vector<bool> removed;
5883 
5884   // The containers above use the local indices for setppc constraints. We store
5885   // the original constraint indices corresponding to those local indices here.
5886   std::vector<int> original_constraint_index;
5887 
5888   // Fill the initial constraint <-> literal graph, compute signatures and
5889   // initialize other containers defined above.
5890   int num_setppc_constraints = 0;
5891   std::vector<int> temp_literals;
5892   if (context_->ModelIsUnsat()) return false;
5893   for (int c = 0; c < num_constraints; ++c) {
5894     ConstraintProto* ct = context_->working_model->mutable_constraints(c);
5895     if (ct->constraint_case() == ConstraintProto::kBoolOr ||
5896         ct->constraint_case() == ConstraintProto::kAtMostOne ||
5897         ct->constraint_case() == ConstraintProto::kExactlyOne) {
5898       // Because TransformIntoMaxCliques() can detect literal equivalence
5899       // relation, we make sure the constraints are presolved before being
5900       // inspected.
5901       if (PresolveOneConstraint(c)) {
5902         context_->UpdateConstraintVariableUsage(c);
5903       }
5904       if (context_->ModelIsUnsat()) return false;
5905       constraint_literals.push_back(GetLiteralsFromSetPPCConstraint(*ct));
5906     } else if (ct->constraint_case() == ConstraintProto::kLinear) {
5907       // We also want to test inclusion with the pseudo-Boolean part of
5908       // linear constraints of size at least 3. Exactly one of size two are
5909       // equivalent literals, and we already deal with this case.
5910       //
5911       // TODO(user): This is not ideal as we currently only process exactly one
5912       // included into linear, and we add overhead by detecting all the other
5913       // cases that we ignore later. That said, we could just propagate a bit
5914       // more the domain if we know at_least_one or at_most_one between literals
5915       // in a linear constraint.
5916       const int size = ct->linear().vars().size();
5917       if (size <= 2) continue;
5918 
5919       // TODO(user): We only deal with every literal having a positive coeff
5920       // here. We should probably do the same with all negative. We can also
5921       // consider NegatedRef(var).
5922       temp_literals.clear();
5923       for (int i = 0; i < size; ++i) {
5924         const int var = ct->linear().vars(i);
5925         const int64_t coeff = ct->linear().coeffs(i);
5926         if (!context_->CanBeUsedAsLiteral(var)) continue;
5927         if (!RefIsPositive(var)) continue;
5928         if (coeff < 0) continue;
5929         temp_literals.push_back(var);
5930       }
5931       if (temp_literals.size() <= 2) continue;
5932       constraint_literals.push_back(temp_literals);
5933     } else {
5934       continue;
5935     }
5936 
5937     uint64_t signature = 0;
5938     for (const int literal : constraint_literals.back()) {
5939       const int positive_literal = PositiveRef(literal);
5940       signature |= (int64_t{1} << (positive_literal % 64));
5941       DCHECK_GE(positive_literal, 0);
5942       if (positive_literal >= literals_to_constraints.size()) {
5943         literals_to_constraints.resize(positive_literal + 1);
5944       }
5945       literals_to_constraints[positive_literal].push_back(
5946           num_setppc_constraints);
5947     }
5948     signatures.push_back(signature);
5949     removed.push_back(false);
5950     original_constraint_index.push_back(c);
5951     num_setppc_constraints++;
5952   }
5953   VLOG(1) << "#setppc constraints: " << num_setppc_constraints;
5954 
5955   // Set of constraint pairs which are already compared.
5956   absl::flat_hash_set<std::pair<int, int>> compared_constraints;
5957   for (const std::vector<int>& literal_to_constraints :
5958        literals_to_constraints) {
5959     for (int index1 = 0; index1 < literal_to_constraints.size(); ++index1) {
5960       if (context_->time_limit()->LimitReached()) return true;
5961 
5962       const int c1 = literal_to_constraints[index1];
5963       if (removed[c1]) continue;
5964       const std::vector<int>& c1_literals = constraint_literals[c1];
5965 
5966       for (int index2 = index1 + 1; index2 < literal_to_constraints.size();
5967            ++index2) {
5968         const int c2 = literal_to_constraints[index2];
5969         if (removed[c2]) continue;
5970         if (removed[c1]) break;
5971 
5972         // TODO(user): This should not happen. Investigate.
5973         if (c1 == c2) continue;
5974 
5975         CHECK_LT(c1, c2);
5976         if (compared_constraints.contains(std::pair<int, int>(c1, c2))) {
5977           continue;
5978         }
5979         compared_constraints.insert({c1, c2});
5980 
5981         // Hard limit on number of comparisons to avoid spending too much time
5982         // here.
5983         if (compared_constraints.size() >= 50000) return true;
5984 
5985         const bool smaller = (signatures[c1] & ~signatures[c2]) == 0;
5986         const bool larger = (signatures[c2] & ~signatures[c1]) == 0;
5987         if (!(smaller || larger)) continue;
5988 
5989         // Check if literals in c1 is subset of literals in c2 or vice versa.
5990         const std::vector<int>& c2_literals = constraint_literals[c2];
5991 
5992         // TODO(user): Try avoiding computation of set differences if
5993         // possible.
5994         std::vector<int> c1_minus_c2;
5995         gtl::STLSetDifference(c1_literals, c2_literals, &c1_minus_c2);
5996         std::vector<int> c2_minus_c1;
5997         gtl::STLSetDifference(c2_literals, c1_literals, &c2_minus_c1);
5998 
5999         if (c1_minus_c2.empty()) {  // c1 included in c2.
6000           if (!ProcessSetPPCSubset(c1, c2, c2_minus_c1,
6001                                    original_constraint_index, &removed)) {
6002             return false;
6003           }
6004         } else if (c2_minus_c1.empty()) {  // c2 included in c1.
6005           if (!ProcessSetPPCSubset(c2, c1, c1_minus_c2,
6006                                    original_constraint_index, &removed)) {
6007             return false;
6008           }
6009         }
6010       }
6011     }
6012   }
6013 
6014   return true;
6015 }
6016 
6017 // Note that because we remove the linear constraint, this will not be called
6018 // often, so it is okay to use "heavy" data structure here.
6019 //
6020 // TODO(user): in the at most one case, consider always creating an associated
6021 // literal (l <=> var == rhs), and add the exactly_one = at_most_one U not(l)?
6022 // This constraint is implicit from what we create, however internally we will
6023 // not recover it easily, so we might not add the linear relaxation
6024 // corresponding to the constraint we just removed.
6025 bool CpModelPresolver::ProcessEncodingFromLinear(
6026     const int linear_encoding_ct_index,
6027     const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms,
6028     int64_t* num_multiple_terms) {
6029   // Preprocess exactly or at most one.
6030   bool in_exactly_one = false;
6031   absl::flat_hash_map<int, int> var_to_ref;
6032   if (at_most_or_exactly_one.constraint_case() == ConstraintProto::kAtMostOne) {
6033     for (const int ref : at_most_or_exactly_one.at_most_one().literals()) {
6034       CHECK(!var_to_ref.contains(PositiveRef(ref)));
6035       var_to_ref[PositiveRef(ref)] = ref;
6036     }
6037   } else {
6038     in_exactly_one = true;
6039     for (const int ref : at_most_or_exactly_one.exactly_one().literals()) {
6040       CHECK(!var_to_ref.contains(PositiveRef(ref)));
6041       var_to_ref[PositiveRef(ref)] = ref;
6042     }
6043   }
6044 
6045   // Preprocess the linear constraints.
6046   const ConstraintProto& linear_encoding =
6047       context_->working_model->constraints(linear_encoding_ct_index);
6048   int64_t rhs = linear_encoding.linear().domain(0);
6049   int target_ref = std::numeric_limits<int>::min();
6050   std::vector<std::pair<int, int64_t>> ref_to_coeffs;
6051   const int num_terms = linear_encoding.linear().vars().size();
6052   for (int i = 0; i < num_terms; ++i) {
6053     const int ref = linear_encoding.linear().vars(i);
6054     const int64_t coeff = linear_encoding.linear().coeffs(i);
6055     const auto it = var_to_ref.find(PositiveRef(ref));
6056 
6057     if (it == var_to_ref.end()) {
6058       CHECK_EQ(target_ref, std::numeric_limits<int>::min()) << "Uniqueness ";
6059       CHECK_EQ(std::abs(coeff), 1);
6060       target_ref = coeff == 1 ? ref : NegatedRef(ref);
6061       continue;
6062     }
6063 
6064     // We transform the constraint so that the Boolean reference match exactly
6065     // what is in the at most one.
6066     if (it->second == ref) {
6067       // The term in the constraint is the same as in the at_most_one.
6068       ref_to_coeffs.push_back({ref, coeff});
6069     } else {
6070       // We replace "coeff * ref" by "coeff - coeff * (1 - ref)"
6071       rhs -= coeff;
6072       ref_to_coeffs.push_back({NegatedRef(ref), -coeff});
6073     }
6074   }
6075   if (target_ref == std::numeric_limits<int>::min() ||
6076       context_->CanBeUsedAsLiteral(target_ref)) {
6077     // We didn't find the unique integer variable. This might have happenned
6078     // because by processing other encoding we might end up with a fully boolean
6079     // constraint. Just abort, it will be presolved later.
6080     context_->UpdateRuleStats("encoding: candidate linear is all Boolean now.");
6081     return true;
6082   }
6083 
6084   // Extract the encoding.
6085   std::vector<int64_t> all_values;
6086   std::map<int64_t, std::vector<int>> value_to_refs;
6087   for (const auto& [ref, coeff] : ref_to_coeffs) {
6088     const int64_t value = rhs - coeff;
6089     all_values.push_back(value);
6090     value_to_refs[value].push_back(ref);
6091     var_to_ref.erase(PositiveRef(ref));
6092   }
6093   // The one not used "encodes" the rhs value.
6094   for (const auto& [var, ref] : var_to_ref) {
6095     all_values.push_back(rhs);
6096     value_to_refs[rhs].push_back(ref);
6097   }
6098   if (!in_exactly_one) {
6099     // To cover the corner case when the inclusion is an equality. For an at
6100     // most one, the rhs should be always reachable when all Boolean are false.
6101     all_values.push_back(rhs);
6102   }
6103 
6104   // Make sure the target domain is up to date.
6105   const Domain new_domain = Domain::FromValues(all_values);
6106   bool domain_reduced = false;
6107   if (!context_->IntersectDomainWith(target_ref, new_domain, &domain_reduced)) {
6108     return false;
6109   }
6110   if (domain_reduced) {
6111     context_->UpdateRuleStats("encoding: reduced target domain");
6112   }
6113 
6114   if (context_->CanBeUsedAsLiteral(target_ref)) {
6115     // If target is now a literal, lets not process it here.
6116     context_->UpdateRuleStats("encoding: candidate linear is all Boolean now.");
6117     return true;
6118   }
6119 
6120   // Encode the encoding.
6121   absl::flat_hash_set<int64_t> value_set;
6122   for (const int64_t v : context_->DomainOf(target_ref).Values()) {
6123     value_set.insert(v);
6124   }
6125   for (const auto& [value, literals] : value_to_refs) {
6126     // If the value is not in the domain, just set all literal to false.
6127     if (!value_set.contains(value)) {
6128       for (const int lit : literals) {
6129         if (!context_->SetLiteralToFalse(lit)) return false;
6130       }
6131       continue;
6132     }
6133 
6134     if (literals.size() == 1 && (in_exactly_one || value != rhs)) {
6135       // Optimization if there is just one literal for this value.
6136       // Note that for the "at most one" case, we can't do that for the rhs.
6137       ++*num_unique_terms;
6138       if (!context_->InsertVarValueEncoding(literals[0], target_ref, value)) {
6139         return false;
6140       }
6141     } else {
6142       ++*num_multiple_terms;
6143       const int associated_lit =
6144           context_->GetOrCreateVarValueEncoding(target_ref, value);
6145       for (const int lit : literals) {
6146         context_->AddImplication(lit, associated_lit);
6147       }
6148 
6149       // All false means associated_lit is false too.
6150       // But not for the rhs case if we are not in exactly one.
6151       if (in_exactly_one || value != rhs) {
6152         // TODO(user): Insted of bool_or + implications, we could add an
6153         // exactly one! Experiment with this. In particular it might capture
6154         // more structure for later heuristic to add the exactly one instead.
6155         // This also applies to automata/table/element expansion.
6156         auto* bool_or =
6157             context_->working_model->add_constraints()->mutable_bool_or();
6158         for (const int lit : literals) bool_or->add_literals(lit);
6159         bool_or->add_literals(NegatedRef(associated_lit));
6160       }
6161     }
6162   }
6163 
6164   // Remove linear constraint now that it is fully encoded.
6165   context_->working_model->mutable_constraints(linear_encoding_ct_index)
6166       ->Clear();
6167   context_->UpdateNewConstraintsVariableUsage();
6168   context_->UpdateConstraintVariableUsage(linear_encoding_ct_index);
6169   return true;
6170 }
6171 
6172 namespace {
6173 
6174 // We want something that is order invariant and is compatible with inclusion.
6175 uint64_t ComputeSignature(const std::vector<int>& integers) {
6176   uint64_t signature = 0;
6177   for (const int i : integers) signature |= (int64_t{1} << (i & 63));
6178   return signature;
6179 }
6180 
6181 }  // namespace
6182 
6183 void CpModelPresolver::ExtractEncodingFromLinear() {
6184   if (context_->time_limit()->LimitReached()) return;
6185   if (context_->ModelIsUnsat()) return;
6186 
6187   WallTimer wall_timer;
6188   wall_timer.Start();
6189 
6190   struct Superset {
6191     int c;  // Index of the constraint.
6192     std::vector<int> vars;
6193     bool is_exactly_one;
6194   };
6195   std::vector<Superset> potential_supersets;
6196 
6197   struct Subset {
6198     int c;  // Index of the linear constraint.
6199     std::vector<int> vars;
6200   };
6201   std::vector<Subset> potential_subsets;
6202 
6203   // Loop over the constraints and fill the structures above.
6204   const int num_constraints = context_->working_model->constraints().size();
6205   for (int c = 0; c < num_constraints; ++c) {
6206     const ConstraintProto& ct = context_->working_model->constraints(c);
6207     switch (ct.constraint_case()) {
6208       case ConstraintProto::kAtMostOne: {
6209         std::vector<int> vars;
6210         for (const int ref : ct.at_most_one().literals()) {
6211           vars.push_back(PositiveRef(ref));
6212         }
6213         potential_supersets.push_back({c, std::move(vars), false});
6214         break;
6215       }
6216       case ConstraintProto::kExactlyOne: {
6217         std::vector<int> vars;
6218         for (const int ref : ct.exactly_one().literals()) {
6219           vars.push_back(PositiveRef(ref));
6220         }
6221         potential_supersets.push_back({c, std::move(vars), true});
6222         break;
6223       }
6224       case ConstraintProto::kLinear: {
6225         // We only consider equality with no enforcement.
6226         if (!ct.enforcement_literal().empty()) continue;
6227         if (ct.linear().domain().size() != 2) continue;
6228         if (ct.linear().domain(0) != ct.linear().domain(1)) continue;
6229 
6230         // We also want a single non-Boolean.
6231         // Note that this assume the constraint is canonicalized.
6232         std::vector<int> vars;
6233         bool is_candidate = true;
6234         int num_integers = 0;
6235         const int num_terms = ct.linear().vars().size();
6236         for (int i = 0; i < num_terms; ++i) {
6237           const int ref = ct.linear().vars(i);
6238           if (context_->CanBeUsedAsLiteral(ref)) {
6239             vars.push_back(PositiveRef(ref));
6240           } else {
6241             ++num_integers;
6242             if (std::abs(ct.linear().coeffs(i)) != 1) {
6243               is_candidate = false;
6244               break;
6245             }
6246             if (num_integers == 2) {
6247               is_candidate = false;
6248               break;
6249             }
6250           }
6251         }
6252 
6253         // We ignore cases with just one Boolean as this should be already dealt
6254         // with elsewhere.
6255         if (is_candidate && num_integers == 1 && vars.size() > 1) {
6256           potential_subsets.push_back({c, std::move(vars)});
6257         }
6258         break;
6259       }
6260       default:
6261         break;
6262     }
6263   }
6264 
6265   if (potential_supersets.empty()) return;
6266   if (potential_subsets.empty()) return;
6267 
6268   // Sort by size. We also want to process exactly_ones first in case a linear
6269   // constraint is also included in an at_most_one of the same size.
6270   std::sort(potential_supersets.begin(), potential_supersets.end(),
6271             [](const Superset& a, const Superset& b) {
6272               const int size_a = a.vars.size();
6273               const int size_b = b.vars.size();
6274               return std::tie(size_a, a.is_exactly_one) <
6275                      std::tie(size_b, b.is_exactly_one);
6276             });
6277   std::sort(potential_subsets.begin(), potential_subsets.end(),
6278             [](const Subset& a, const Subset& b) {
6279               return a.vars.size() < b.vars.size();
6280             });
6281 
6282   // Stats.
6283   int64_t num_exactly_one_encodings = 0;
6284   int64_t num_at_most_one_encodings = 0;
6285   int64_t num_literals = 0;
6286   int64_t num_unique_terms = 0;
6287   int64_t num_multiple_terms = 0;
6288 
6289   // Structure for efficient inclusion detection.
6290   //
6291   // TODO(user): Factor this and ComputeSignature() out and merge the 3 places
6292   // where we do something similar.
6293   int subset_index = 0;
6294   std::vector<uint64_t> signatures;
6295   std::vector<std::vector<int>> one_watcher;
6296   std::vector<bool> is_in_superset;
6297   for (const Superset& superset : potential_supersets) {
6298     // Include all the potential subset.
6299     const int superset_size = superset.vars.size();
6300     for (; subset_index < potential_subsets.size(); ++subset_index) {
6301       const std::vector<int>& vars = potential_subsets[subset_index].vars;
6302       if (vars.size() > superset_size) break;
6303 
6304       // Choose to watch the one with smallest list.
6305       int best_choice = -1;
6306       for (const int var : vars) {
6307         if (one_watcher.size() <= var) one_watcher.resize(var + 1);
6308         if (best_choice == -1 ||
6309             one_watcher[var].size() < one_watcher[best_choice].size()) {
6310           best_choice = var;
6311         }
6312       }
6313       one_watcher[best_choice].push_back(subset_index);
6314       CHECK_EQ(signatures.size(), subset_index);
6315       signatures.push_back(ComputeSignature(vars));
6316     }
6317 
6318     // Find any subset included in current superset.
6319     DCHECK(absl::c_all_of(is_in_superset, [](bool b) { return !b; }));
6320     for (const int var : superset.vars) {
6321       if (var >= is_in_superset.size()) {
6322         is_in_superset.resize(var + 1, false);
6323       }
6324       is_in_superset[var] = true;
6325     }
6326     {
6327       const int max_size = std::max(one_watcher.size(), is_in_superset.size());
6328       one_watcher.resize(max_size);
6329       is_in_superset.resize(max_size, false);
6330     }
6331 
6332     const uint64_t superset_signature = ComputeSignature(superset.vars);
6333     for (const int superset_var : superset.vars) {
6334       for (int i = 0; i < one_watcher[superset_var].size(); ++i) {
6335         const int subset_index = one_watcher[superset_var][i];
6336         const Subset& subset = potential_subsets[subset_index];
6337         CHECK_LE(subset.vars.size(), superset_size);
6338 
6339         // Quick check with signature.
6340         if ((signatures[subset_index] & ~superset_signature) != 0) continue;
6341 
6342         // Long check with bitset.
6343         bool is_included = true;
6344         for (const int subset_var : subset.vars) {
6345           if (!is_in_superset[subset_var]) {
6346             is_included = false;
6347             break;
6348           }
6349         }
6350         if (!is_included) continue;
6351 
6352         if (!superset.is_exactly_one) {
6353           ++num_at_most_one_encodings;
6354         } else {
6355           ++num_exactly_one_encodings;
6356         }
6357         num_literals += subset.vars.size();
6358         context_->UpdateRuleStats("encoding: extracted from linear");
6359 
6360         if (!ProcessEncodingFromLinear(
6361                 subset.c, context_->working_model->constraints(superset.c),
6362                 &num_unique_terms, &num_multiple_terms)) {
6363           // UNSAT, we return right away, no cleanup needed.
6364           return;
6365         }
6366 
6367         // Remove from the watcher list.
6368         std::swap(one_watcher[superset_var][i],
6369                   one_watcher[superset_var].back());
6370         one_watcher[superset_var].pop_back();
6371         --i;
6372       }
6373     }
6374 
6375     // Cleanup.
6376     for (const int var : superset.vars) {
6377       is_in_superset[var] = false;
6378     }
6379   }
6380 
6381   SOLVER_LOG(logger_, "[ExtractEncodingFromLinear]",
6382              " #potential_supersets=", potential_supersets.size(),
6383              " #potential_subsets=", potential_subsets.size(),
6384              " #at_most_one_encodings=", num_at_most_one_encodings,
6385              " #exactly_one_encodings=", num_exactly_one_encodings,
6386              " #unique_terms=", num_unique_terms,
6387              " #multiple_terms=", num_multiple_terms,
6388              " #literals=", num_literals, " time=", wall_timer.Get(), "s");
6389 }
6390 
6391 void CpModelPresolver::TryToSimplifyDomain(int var) {
6392   CHECK(RefIsPositive(var));
6393   CHECK(context_->ConstraintVariableGraphIsUpToDate());
6394   if (context_->ModelIsUnsat()) return;
6395   if (context_->IsFixed(var)) return;
6396   if (context_->VariableWasRemoved(var)) return;
6397   if (context_->VariableIsNotUsedAnymore(var)) return;
6398 
6399   const AffineRelation::Relation r = context_->GetAffineRelation(var);
6400   if (r.representative != var) return;
6401 
6402   // TODO(user): We can still remove the variable even if we want to keep
6403   // all feasible solutions for the cases when we have a full encoding.
6404   //
6405   // TODO(user): In fixed search, we disable this rule because we don't update
6406   // the search strategy, but for some strategy we could.
6407   //
6408   // TODO(user): The hint might get lost if the encoding was created during
6409   // the presolve.
6410   if (context_->VariableIsRemovable(var) &&
6411       !context_->CanBeUsedAsLiteral(var) &&
6412       context_->VariableIsOnlyUsedInEncodingAndMaybeInObjective(var) &&
6413       context_->params().search_branching() != SatParameters::FIXED_SEARCH) {
6414     // We can currently only deal with the case where all encoding constraint
6415     // are of the form literal => var ==/!= value.
6416     // If they are more complex linear1 involved, we just abort.
6417     //
6418     // TODO(user): Also deal with the case all >= or <= where we can add a
6419     // serie of implication between all involved literals.
6420     absl::flat_hash_set<int64_t> values_set;
6421     absl::flat_hash_map<int64_t, std::vector<int>> value_to_equal_literals;
6422     absl::flat_hash_map<int64_t, std::vector<int>> value_to_not_equal_literals;
6423     bool abort = false;
6424     for (const int c : context_->VarToConstraints(var)) {
6425       if (c < 0) continue;
6426       const ConstraintProto& ct = context_->working_model->constraints(c);
6427       CHECK_EQ(ct.constraint_case(), ConstraintProto::kLinear);
6428       CHECK_EQ(ct.linear().vars().size(), 1);
6429       int64_t coeff = ct.linear().coeffs(0);
6430       if (std::abs(coeff) != 1 || ct.enforcement_literal().size() != 1) {
6431         abort = true;
6432         break;
6433       }
6434       if (!RefIsPositive(ct.linear().vars(0))) coeff *= 1;
6435       const Domain rhs =
6436           ReadDomainFromProto(ct.linear()).InverseMultiplicationBy(coeff);
6437 
6438       if (rhs.IsFixed()) {
6439         if (!context_->DomainOf(var).Contains(rhs.FixedValue())) {
6440           if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
6441             return;
6442           }
6443         } else {
6444           values_set.insert(rhs.FixedValue());
6445           value_to_equal_literals[rhs.FixedValue()].push_back(
6446               ct.enforcement_literal(0));
6447         }
6448       } else {
6449         const Domain complement =
6450             context_->DomainOf(var).IntersectionWith(rhs.Complement());
6451         if (complement.IsEmpty()) {
6452           // TODO(user): This should be dealt with elsewhere.
6453           abort = true;
6454           break;
6455         }
6456         if (complement.IsFixed()) {
6457           if (context_->DomainOf(var).Contains(complement.FixedValue())) {
6458             values_set.insert(complement.FixedValue());
6459             value_to_not_equal_literals[complement.FixedValue()].push_back(
6460                 ct.enforcement_literal(0));
6461           }
6462         } else {
6463           abort = true;
6464           break;
6465         }
6466       }
6467     }
6468     if (abort) {
6469       context_->UpdateRuleStats("TODO variables: only used in linear1.");
6470     } else if (value_to_not_equal_literals.empty() &&
6471                value_to_equal_literals.empty()) {
6472       // This is just a variable not used anywhere, it should be removed by
6473       // another part of the presolve.
6474     } else {
6475       // For determinism, sort all the encoded values first.
6476       std::vector<int64_t> encoded_values(values_set.begin(), values_set.end());
6477       std::sort(encoded_values.begin(), encoded_values.end());
6478       CHECK(!encoded_values.empty());
6479       const bool is_fully_encoded =
6480           encoded_values.size() == context_->DomainOf(var).Size();
6481 
6482       // Link all Boolean in out linear1 to the encoding literals. Note that we
6483       // should hopefully already have detected such literal before and this
6484       // should add trivial implications.
6485       for (const int64_t v : encoded_values) {
6486         const int encoding_lit = context_->GetOrCreateVarValueEncoding(var, v);
6487         const auto eq_it = value_to_equal_literals.find(v);
6488         if (eq_it != value_to_equal_literals.end()) {
6489           for (const int lit : eq_it->second) {
6490             context_->AddImplication(lit, encoding_lit);
6491           }
6492         }
6493         const auto neq_it = value_to_not_equal_literals.find(v);
6494         if (neq_it != value_to_not_equal_literals.end()) {
6495           for (const int lit : neq_it->second) {
6496             context_->AddImplication(lit, NegatedRef(encoding_lit));
6497           }
6498         }
6499       }
6500       context_->UpdateNewConstraintsVariableUsage();
6501 
6502       // This is the set of other values.
6503       Domain other_values;
6504       if (!is_fully_encoded) {
6505         other_values = context_->DomainOf(var).IntersectionWith(
6506             Domain::FromValues(encoded_values).Complement());
6507       }
6508 
6509       // Update the objective if needed. Note that this operation can fail if
6510       // the new expression result in potential overflow.
6511       if (context_->VarToConstraints(var).contains(kObjectiveConstraint)) {
6512         int64_t min_value;
6513         const int64_t obj_coeff = context_->ObjectiveMap().at(var);
6514         if (is_fully_encoded) {
6515           // We substract the min_value from all coefficients.
6516           // This should reduce the objective size and helps with the bounds.
6517           min_value =
6518               obj_coeff > 0 ? encoded_values.front() : encoded_values.back();
6519         } else {
6520           // Tricky: If the variable is not fully encoded, then when all
6521           // partial encoding literal are false, it must take the "best" value
6522           // in other_values. That depend on the sign of the objective coeff.
6523           //
6524           // We also restrict other value so that the postsolve code below
6525           // will fix the variable to the correct value when this happen.
6526           other_values =
6527               Domain(obj_coeff > 0 ? other_values.Min() : other_values.Max());
6528           min_value = other_values.FixedValue();
6529         }
6530 
6531         // Checks for overflow before trying to substitute the variable in the
6532         // objective.
6533         int64_t accumulated = std::abs(min_value);
6534         for (const int64_t value : encoded_values) {
6535           accumulated = CapAdd(accumulated, std::abs(CapSub(value, min_value)));
6536           if (accumulated == std::numeric_limits<int64_t>::max()) {
6537             context_->UpdateRuleStats(
6538                 "TODO variables: only used in objective and in encoding");
6539             return;
6540           }
6541         }
6542 
6543         ConstraintProto encoding_ct;
6544         LinearConstraintProto* linear = encoding_ct.mutable_linear();
6545         const int64_t coeff_in_equality = -1;
6546         linear->add_vars(var);
6547         linear->add_coeffs(coeff_in_equality);
6548 
6549         linear->add_domain(-min_value);
6550         linear->add_domain(-min_value);
6551         for (const int64_t value : encoded_values) {
6552           if (value == min_value) continue;
6553           const int enf = context_->GetOrCreateVarValueEncoding(var, value);
6554           const int64_t coeff = value - min_value;
6555           if (RefIsPositive(enf)) {
6556             linear->add_vars(enf);
6557             linear->add_coeffs(coeff);
6558           } else {
6559             // (1 - var) * coeff;
6560             linear->set_domain(0, encoding_ct.linear().domain(0) - coeff);
6561             linear->set_domain(1, encoding_ct.linear().domain(1) - coeff);
6562             linear->add_vars(PositiveRef(enf));
6563             linear->add_coeffs(-coeff);
6564           }
6565         }
6566         if (!context_->SubstituteVariableInObjective(var, coeff_in_equality,
6567                                                      encoding_ct)) {
6568           context_->UpdateRuleStats(
6569               "TODO variables: only used in objective and in encoding");
6570           return;
6571         }
6572         context_->UpdateRuleStats(
6573             "variables: only used in objective and in encoding");
6574       } else {
6575         context_->UpdateRuleStats("variables: only used in encoding");
6576       }
6577 
6578       // Clear all involved constraint.
6579       auto copy = context_->VarToConstraints(var);
6580       for (const int c : copy) {
6581         if (c < 0) continue;
6582         context_->working_model->mutable_constraints(c)->Clear();
6583         context_->UpdateConstraintVariableUsage(c);
6584       }
6585 
6586       // Add enough constraints to the mapping model to recover a valid value
6587       // for var when all the booleans are fixed.
6588       for (const int64_t value : encoded_values) {
6589         const int enf = context_->GetOrCreateVarValueEncoding(var, value);
6590         ConstraintProto* ct = context_->mapping_model->add_constraints();
6591         ct->add_enforcement_literal(enf);
6592         ct->mutable_linear()->add_vars(var);
6593         ct->mutable_linear()->add_coeffs(1);
6594         ct->mutable_linear()->add_domain(value);
6595         ct->mutable_linear()->add_domain(value);
6596       }
6597 
6598       // This must be done after we removed all the constraint containing var.
6599       ConstraintProto* new_ct = context_->working_model->add_constraints();
6600       if (is_fully_encoded) {
6601         // The encoding is full: add an exactly one.
6602         for (const int64_t value : encoded_values) {
6603           new_ct->mutable_exactly_one()->add_literals(
6604               context_->GetOrCreateVarValueEncoding(var, value));
6605         }
6606         PresolveExactlyOne(new_ct);
6607       } else {
6608         // If all literal are false, then var must take one of the other values.
6609         ConstraintProto* mapping_ct =
6610             context_->mapping_model->add_constraints();
6611         mapping_ct->mutable_linear()->add_vars(var);
6612         mapping_ct->mutable_linear()->add_coeffs(1);
6613         FillDomainInProto(other_values, mapping_ct->mutable_linear());
6614 
6615         for (const int64_t value : encoded_values) {
6616           const int literal = context_->GetOrCreateVarValueEncoding(var, value);
6617           mapping_ct->add_enforcement_literal(NegatedRef(literal));
6618           new_ct->mutable_at_most_one()->add_literals(literal);
6619         }
6620         PresolveAtMostOne(new_ct);
6621       }
6622 
6623       context_->UpdateNewConstraintsVariableUsage();
6624       context_->MarkVariableAsRemoved(var);
6625       return;
6626     }
6627   }
6628 
6629   // Special case: if a literal l appear in exactly two constraints:
6630   // - l => var in domain1
6631   // - not(l) => var in domain2
6632   // then we know that domain(var) is included in domain1 U domain2,
6633   // and that the literal l can be removed (and determined at postsolve).
6634   //
6635   // TODO(user): This could be generalized further to linear of size > 1 if for
6636   // example the terms are the same.
6637   //
6638   // We wait for the model expansion to take place in order to avoid removing
6639   // encoding that will later be re-created during expansion.
6640   if (context_->ModelIsExpanded() && context_->CanBeUsedAsLiteral(var) &&
6641       context_->VariableIsRemovable(var) &&
6642       context_->VarToConstraints(var).size() == 2) {
6643     bool abort = false;
6644     int ct_var = -1;
6645     Domain union_of_domain;
6646     int num_positive = 0;
6647     std::vector<int> constraint_indices_to_remove;
6648     for (const int c : context_->VarToConstraints(var)) {
6649       if (c < 0) {
6650         abort = true;
6651         continue;
6652       }
6653       constraint_indices_to_remove.push_back(c);
6654       const ConstraintProto& ct = context_->working_model->constraints(c);
6655       if (ct.enforcement_literal().size() != 1 ||
6656           PositiveRef(ct.enforcement_literal(0)) != var ||
6657           ct.constraint_case() != ConstraintProto::kLinear ||
6658           ct.linear().vars().size() != 1) {
6659         abort = true;
6660         break;
6661       }
6662       if (ct.enforcement_literal(0) == var) ++num_positive;
6663       if (ct_var != -1 && PositiveRef(ct.linear().vars(0)) != ct_var) {
6664         abort = true;
6665         break;
6666       }
6667       ct_var = PositiveRef(ct.linear().vars(0));
6668       union_of_domain = union_of_domain.UnionWith(
6669           ReadDomainFromProto(ct.linear())
6670               .InverseMultiplicationBy(RefIsPositive(ct.linear().vars(0))
6671                                            ? ct.linear().coeffs(0)
6672                                            : -ct.linear().coeffs(0)));
6673     }
6674     if (!abort && num_positive == 1) {
6675       if (!context_->IntersectDomainWith(ct_var, union_of_domain)) {
6676         return;
6677       }
6678       context_->UpdateRuleStats("variables: removable enforcement literal");
6679       for (const int c : constraint_indices_to_remove) {
6680         *context_->mapping_model->add_constraints() =
6681             context_->working_model->constraints(c);
6682         context_->mapping_model
6683             ->mutable_constraints(
6684                 context_->mapping_model->constraints().size() - 1)
6685             ->set_name("here");
6686         context_->working_model->mutable_constraints(c)->Clear();
6687         context_->UpdateConstraintVariableUsage(c);
6688       }
6689       context_->MarkVariableAsRemoved(var);
6690       return;
6691     }
6692   }
6693 
6694   // Only process discrete domain.
6695   const Domain& domain = context_->DomainOf(var);
6696 
6697   // Special case for non-Boolean domain of size 2.
6698   if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
6699     context_->CanonicalizeDomainOfSizeTwo(var);
6700     return;
6701   }
6702 
6703   if (domain.NumIntervals() != domain.Size()) return;
6704 
6705   const int64_t var_min = domain.Min();
6706   int64_t gcd = domain[1].start - var_min;
6707   for (int index = 2; index < domain.NumIntervals(); ++index) {
6708     const ClosedInterval& i = domain[index];
6709     DCHECK_EQ(i.start, i.end);
6710     const int64_t shifted_value = i.start - var_min;
6711     DCHECK_GT(shifted_value, 0);
6712 
6713     gcd = MathUtil::GCD64(gcd, shifted_value);
6714     if (gcd == 1) break;
6715   }
6716   if (gcd == 1) return;
6717 
6718   // This does all the work since var * 1 % gcd = var_min % gcd.
6719   context_->CanonicalizeAffineVariable(var, 1, gcd, var_min);
6720 }
6721 
6722 // Adds all affine relations to our model for the variables that are still used.
6723 void CpModelPresolver::EncodeAllAffineRelations() {
6724   int64_t num_added = 0;
6725   for (int var = 0; var < context_->working_model->variables_size(); ++var) {
6726     if (context_->IsFixed(var)) continue;
6727 
6728     const AffineRelation::Relation r = context_->GetAffineRelation(var);
6729     if (r.representative == var) continue;
6730 
6731     if (!context_->keep_all_feasible_solutions) {
6732       // TODO(user): It seems some affine relation are still removable at this
6733       // stage even though they should be removed inside PresolveToFixPoint().
6734       // Investigate. For now, we just remove such relations.
6735       if (context_->VariableIsNotUsedAnymore(var)) continue;
6736       if (!PresolveAffineRelationIfAny(var)) break;
6737       if (context_->VariableIsNotUsedAnymore(var)) continue;
6738       if (context_->IsFixed(var)) continue;
6739     }
6740 
6741     ++num_added;
6742     ConstraintProto* ct = context_->working_model->add_constraints();
6743     auto* arg = ct->mutable_linear();
6744     arg->add_vars(var);
6745     arg->add_coeffs(1);
6746     arg->add_vars(r.representative);
6747     arg->add_coeffs(-r.coeff);
6748     arg->add_domain(r.offset);
6749     arg->add_domain(r.offset);
6750     context_->UpdateNewConstraintsVariableUsage();
6751   }
6752 
6753   // Now that we encoded all remaining affine relation with constraints, we
6754   // remove the special marker to have a proper constraint variable graph.
6755   context_->RemoveAllVariablesFromAffineRelationConstraint();
6756 
6757   if (num_added > 0) {
6758     SOLVER_LOG(logger_, num_added, " affine relations still in the model.");
6759   }
6760 }
6761 
6762 // Presolve a variable in relation with its representative.
6763 bool CpModelPresolver::PresolveAffineRelationIfAny(int var) {
6764   if (context_->VariableIsNotUsedAnymore(var)) return true;
6765 
6766   const AffineRelation::Relation r = context_->GetAffineRelation(var);
6767   if (r.representative == var) return true;
6768 
6769   // Propagate domains.
6770   if (!context_->PropagateAffineRelation(var)) return false;
6771 
6772   // Once an affine relation is detected, the variables should be added to
6773   // the kAffineRelationConstraint. The only way to be unmarked is if the
6774   // variable do not appear in any other constraint and is not a representative,
6775   // in which case it should never be added back.
6776   if (context_->IsFixed(var)) return true;
6777   CHECK(context_->VarToConstraints(var).contains(kAffineRelationConstraint));
6778   CHECK(!context_->VariableIsNotUsedAnymore(r.representative));
6779 
6780   // If var is no longer used, remove. Note that we can always do that since we
6781   // propagated the domain above and so we can find a feasible value for a for
6782   // any value of the representative.
6783   if (context_->VariableIsUniqueAndRemovable(var)) {
6784     // Add relation with current representative to the mapping model.
6785     ConstraintProto* ct = context_->mapping_model->add_constraints();
6786     auto* arg = ct->mutable_linear();
6787     arg->add_vars(var);
6788     arg->add_coeffs(1);
6789     arg->add_vars(r.representative);
6790     arg->add_coeffs(-r.coeff);
6791     arg->add_domain(r.offset);
6792     arg->add_domain(r.offset);
6793     context_->RemoveVariableFromAffineRelation(var);
6794   }
6795   return true;
6796 }
6797 
6798 void CpModelPresolver::PresolveToFixPoint() {
6799   if (context_->ModelIsUnsat()) return;
6800 
6801   // Limit on number of operations.
6802   const int64_t max_num_operations =
6803       context_->params().debug_max_num_presolve_operations() > 0
6804           ? context_->params().debug_max_num_presolve_operations()
6805           : std::numeric_limits<int64_t>::max();
6806 
6807   // This is used for constraint having unique variables in them (i.e. not
6808   // appearing anywhere else) to not call the presolve more than once for this
6809   // reason.
6810   absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
6811 
6812   TimeLimit* time_limit = context_->time_limit();
6813 
6814   // The queue of "active" constraints, initialized to the non-empty ones.
6815   std::vector<bool> in_queue(context_->working_model->constraints_size(),
6816                              false);
6817   std::deque<int> queue;
6818   for (int c = 0; c < in_queue.size(); ++c) {
6819     if (context_->working_model->constraints(c).constraint_case() !=
6820         ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
6821       in_queue[c] = true;
6822       queue.push_back(c);
6823     }
6824   }
6825 
6826   // When thinking about how the presolve works, it seems like a good idea to
6827   // process the "simple" constraints first in order to be more efficient.
6828   // In September 2019, experiment on the flatzinc problems shows no changes in
6829   // the results. We should actually count the number of rules triggered.
6830   if (context_->params().permute_presolve_constraint_order()) {
6831     std::shuffle(queue.begin(), queue.end(), *context_->random());
6832   } else {
6833     std::sort(queue.begin(), queue.end(), [this](int a, int b) {
6834       const int score_a = context_->ConstraintToVars(a).size();
6835       const int score_b = context_->ConstraintToVars(b).size();
6836       return score_a < score_b || (score_a == score_b && a < b);
6837     });
6838   }
6839 
6840   while (!queue.empty() && !context_->ModelIsUnsat()) {
6841     if (time_limit->LimitReached()) break;
6842     if (context_->num_presolve_operations > max_num_operations) break;
6843     while (!queue.empty() && !context_->ModelIsUnsat()) {
6844       if (time_limit->LimitReached()) break;
6845       if (context_->num_presolve_operations > max_num_operations) break;
6846       const int c = queue.front();
6847       in_queue[c] = false;
6848       queue.pop_front();
6849 
6850       const int old_num_constraint =
6851           context_->working_model->constraints_size();
6852       const bool changed = PresolveOneConstraint(c);
6853       if (context_->ModelIsUnsat()) {
6854         SOLVER_LOG(logger_, "Unsat after presolving constraint #", c,
6855                    " (warning, dump might be inconsistent): ",
6856                    context_->working_model->constraints(c).ShortDebugString());
6857       }
6858 
6859       // Add to the queue any newly created constraints.
6860       const int new_num_constraints =
6861           context_->working_model->constraints_size();
6862       if (new_num_constraints > old_num_constraint) {
6863         context_->UpdateNewConstraintsVariableUsage();
6864         in_queue.resize(new_num_constraints, true);
6865         for (int c = old_num_constraint; c < new_num_constraints; ++c) {
6866           queue.push_back(c);
6867         }
6868       }
6869 
6870       // TODO(user): Is seems safer to simply remove the changed Boolean.
6871       // We loose a bit of performance, but the code is simpler.
6872       if (changed) {
6873         context_->UpdateConstraintVariableUsage(c);
6874       }
6875     }
6876 
6877     // This is needed to remove variable with a different representative from
6878     // the objective. This allows to remove them completely in the loop below.
6879     if (context_->ModelIsUnsat()) return;
6880     if (!context_->CanonicalizeObjective()) return;
6881 
6882     // We also make sure all affine relations are propagated and any not
6883     // yet canonicalized domain is.
6884     //
6885     // TODO(user): maybe we can avoid iterating over all variables, but then
6886     // we already do that below.
6887     const int current_num_variables = context_->working_model->variables_size();
6888     for (int v = 0; v < current_num_variables; ++v) {
6889       if (context_->ModelIsUnsat()) return;
6890       if (!PresolveAffineRelationIfAny(v)) return;
6891 
6892       // Try to canonicalize the domain, note that we should have detected all
6893       // affine relations before, so we don't recreate "canononical" variables
6894       // if they already exist in the model.
6895       TryToSimplifyDomain(v);
6896       context_->UpdateNewConstraintsVariableUsage();
6897     }
6898 
6899     // Re-add to the queue constraints that have unique variables. Note that to
6900     // not enter an infinite loop, we call each (var, constraint) pair at most
6901     // once.
6902     const int num_vars = context_->working_model->variables_size();
6903     in_queue.resize(context_->working_model->constraints_size(), false);
6904     for (int v = 0; v < num_vars; ++v) {
6905       const auto& constraints = context_->VarToConstraints(v);
6906       if (constraints.size() != 1) continue;
6907       const int c = *constraints.begin();
6908       if (c < 0) continue;
6909 
6910       // Note that to avoid bad complexity in problem like a TSP with just one
6911       // big constraint. we mark all the singleton variables of a constraint
6912       // even if this constraint is already in the queue.
6913       if (var_constraint_pair_already_called.contains(
6914               std::pair<int, int>(v, c))) {
6915         continue;
6916       }
6917       var_constraint_pair_already_called.insert({v, c});
6918 
6919       if (!in_queue[c]) {
6920         in_queue[c] = true;
6921         queue.push_back(c);
6922       }
6923     }
6924 
6925     for (int i = 0; i < 2; ++i) {
6926       // Re-add to the queue the constraints that touch a variable that changed.
6927       //
6928       // TODO(user): Avoid reprocessing the constraints that changed the
6929       // variables with the use of time_exprstamp.
6930       if (context_->ModelIsUnsat()) return;
6931       in_queue.resize(context_->working_model->constraints_size(), false);
6932       for (const int v : context_->modified_domains.PositionsSetAtLeastOnce()) {
6933         if (context_->VariableIsNotUsedAnymore(v)) continue;
6934         if (context_->IsFixed(v)) context_->ExploitFixedDomain(v);
6935         for (const int c : context_->VarToConstraints(v)) {
6936           if (c >= 0 && !in_queue[c]) {
6937             in_queue[c] = true;
6938             queue.push_back(c);
6939           }
6940         }
6941       }
6942 
6943       // If we reach the end of the loop, try to detect dominance relations.
6944       if (!queue.empty() || i == 1) break;
6945 
6946       // Detect & exploit dominance between variables, or variables that can
6947       // move freely in one direction. Or variables that are just blocked by one
6948       // constraint in one direction.
6949       //
6950       // TODO(user): We can support assumptions but we need to not cut them out
6951       // of the feasible region.
6952       if (!context_->keep_all_feasible_solutions &&
6953           context_->working_model->assumptions().empty()) {
6954         VarDomination var_dom;
6955         DualBoundStrengthening dual_bound_strengthening;
6956         DetectDominanceRelations(*context_, &var_dom,
6957                                  &dual_bound_strengthening);
6958         if (!dual_bound_strengthening.Strengthen(context_)) return;
6959 
6960         // TODO(user): The Strengthen() function above might make some
6961         // inequality tight. Currently, because we only do that for implication,
6962         // this will not change who dominate who, but in general we should
6963         // process the new constraint direction before calling this.
6964         if (!ExploitDominanceRelations(var_dom, context_)) return;
6965       }
6966     }
6967 
6968     // Make sure the order is deterministic! because var_to_constraints[]
6969     // order changes from one run to the next.
6970     std::sort(queue.begin(), queue.end());
6971     context_->modified_domains.SparseClearAll();
6972   }
6973 
6974   if (context_->ModelIsUnsat()) return;
6975 
6976   // Second "pass" for transformation better done after all of the above and
6977   // that do not need a fix-point loop.
6978   //
6979   // TODO(user): Also add deductions achieved during probing!
6980   //
6981   // TODO(user): ideally we should "wake-up" any constraint that contains an
6982   // absent interval in the main propagation loop above. But we currently don't
6983   // maintain such list.
6984   const int num_constraints = context_->working_model->constraints_size();
6985   for (int c = 0; c < num_constraints; ++c) {
6986     ConstraintProto* ct = context_->working_model->mutable_constraints(c);
6987     switch (ct->constraint_case()) {
6988       case ConstraintProto::ConstraintCase::kNoOverlap:
6989         // Filter out absent intervals.
6990         if (PresolveNoOverlap(ct)) {
6991           context_->UpdateConstraintVariableUsage(c);
6992         }
6993         break;
6994       case ConstraintProto::ConstraintCase::kNoOverlap2D:
6995         // Filter out absent intervals.
6996         if (PresolveNoOverlap2D(c, ct)) {
6997           context_->UpdateConstraintVariableUsage(c);
6998         }
6999         break;
7000       case ConstraintProto::ConstraintCase::kCumulative:
7001         // Filter out absent intervals.
7002         if (PresolveCumulative(ct)) {
7003           context_->UpdateConstraintVariableUsage(c);
7004         }
7005         break;
7006       case ConstraintProto::ConstraintCase::kBoolOr: {
7007         // Try to infer domain reductions from clauses and the saved "implies in
7008         // domain" relations.
7009         for (const auto& pair :
7010              context_->deductions.ProcessClause(ct->bool_or().literals())) {
7011           bool modified = false;
7012           if (!context_->IntersectDomainWith(pair.first, pair.second,
7013                                              &modified)) {
7014             return;
7015           }
7016           if (modified) {
7017             context_->UpdateRuleStats("deductions: reduced variable domain");
7018           }
7019         }
7020         break;
7021       }
7022       default:
7023         break;
7024     }
7025   }
7026 
7027   context_->deductions.MarkProcessingAsDoneForNow();
7028 }
7029 
7030 ModelCopy::ModelCopy(PresolveContext* context) : context_(context) {}
7031 
7032 // TODO(user): Merge with the phase 1 of the presolve code.
7033 bool ModelCopy::ImportAndSimplifyConstraints(
7034     const CpModelProto& in_model, const std::vector<int>& ignored_constraints) {
7035   const absl::flat_hash_set<int> ignored_constraints_set(
7036       ignored_constraints.begin(), ignored_constraints.end());
7037   context_->InitializeNewDomains();
7038 
7039   starting_constraint_index_ = context_->working_model->constraints_size();
7040   for (int c = 0; c < in_model.constraints_size(); ++c) {
7041     if (ignored_constraints_set.contains(c)) continue;
7042 
7043     const ConstraintProto& ct = in_model.constraints(c);
7044     if (OneEnforcementLiteralIsFalse(ct) &&
7045         ct.constraint_case() != ConstraintProto::kInterval) {
7046       continue;
7047     }
7048     switch (ct.constraint_case()) {
7049       case ConstraintProto::CONSTRAINT_NOT_SET: {
7050         break;
7051       }
7052       case ConstraintProto::kBoolOr: {
7053         if (!CopyBoolOr(ct)) return CreateUnsatModel();
7054         break;
7055       }
7056       case ConstraintProto::kBoolAnd: {
7057         if (!CopyBoolAnd(ct)) return CreateUnsatModel();
7058         break;
7059       }
7060       case ConstraintProto::kLinear: {
7061         if (!CopyLinear(ct)) return CreateUnsatModel();
7062         break;
7063       }
7064       case ConstraintProto::kAtMostOne: {
7065         if (!CopyAtMostOne(ct)) return CreateUnsatModel();
7066         break;
7067       }
7068       case ConstraintProto::kExactlyOne: {
7069         if (!CopyExactlyOne(ct)) return CreateUnsatModel();
7070         break;
7071       }
7072       case ConstraintProto::kInterval: {
7073         if (!CopyInterval(ct, c)) return CreateUnsatModel();
7074         break;
7075       }
7076       default: {
7077         *context_->working_model->add_constraints() = ct;
7078       }
7079     }
7080   }
7081 
7082   // Re-map interval indices for new constraints.
7083   // TODO(user): Support removing unperformed intervals.
7084   for (int c = starting_constraint_index_;
7085        c < context_->working_model->constraints_size(); ++c) {
7086     ConstraintProto& ct_ref = *context_->working_model->mutable_constraints(c);
7087     ApplyToAllIntervalIndices(
7088         [this](int* ref) {
7089           const auto& it = interval_mapping_.find(*ref);
7090           if (it != interval_mapping_.end()) {
7091             *ref = it->second;
7092           }
7093         },
7094         &ct_ref);
7095   }
7096 
7097   return true;
7098 }
7099 
7100 void ModelCopy::CopyEnforcementLiterals(const ConstraintProto& orig,
7101                                         ConstraintProto* dest) {
7102   temp_enforcement_literals_.clear();
7103   for (const int lit : orig.enforcement_literal()) {
7104     if (context_->LiteralIsTrue(lit)) {
7105       skipped_non_zero_++;
7106       continue;
7107     }
7108     temp_enforcement_literals_.push_back(lit);
7109   }
7110   dest->mutable_enforcement_literal()->Add(temp_enforcement_literals_.begin(),
7111                                            temp_enforcement_literals_.end());
7112 }
7113 
7114 bool ModelCopy::OneEnforcementLiteralIsFalse(const ConstraintProto& ct) const {
7115   for (const int lit : ct.enforcement_literal()) {
7116     if (context_->LiteralIsFalse(lit)) {
7117       return true;
7118     }
7119   }
7120   return false;
7121 }
7122 
7123 bool ModelCopy::CopyBoolOr(const ConstraintProto& ct) {
7124   temp_literals_.clear();
7125   for (const int lit : ct.enforcement_literal()) {
7126     if (context_->LiteralIsTrue(lit)) continue;
7127     temp_literals_.push_back(NegatedRef(lit));
7128   }
7129   for (const int lit : ct.bool_or().literals()) {
7130     if (context_->LiteralIsTrue(lit)) {
7131       return true;
7132     }
7133     if (context_->LiteralIsFalse(lit)) {
7134       skipped_non_zero_++;
7135     } else {
7136       temp_literals_.push_back(lit);
7137     }
7138   }
7139 
7140   context_->working_model->add_constraints()
7141       ->mutable_bool_or()
7142       ->mutable_literals()
7143       ->Add(temp_literals_.begin(), temp_literals_.end());
7144   return !temp_literals_.empty();
7145 }
7146 
7147 bool ModelCopy::CopyBoolAnd(const ConstraintProto& ct) {
7148   bool at_least_one_false = false;
7149   int num_non_fixed_literals = 0;
7150   for (const int lit : ct.bool_and().literals()) {
7151     if (context_->LiteralIsFalse(lit)) {
7152       at_least_one_false = true;
7153       break;
7154     }
7155     if (!context_->LiteralIsTrue(lit)) {
7156       num_non_fixed_literals++;
7157     }
7158   }
7159 
7160   if (at_least_one_false) {
7161     ConstraintProto* new_ct = context_->working_model->add_constraints();
7162     BoolArgumentProto* bool_or = new_ct->mutable_bool_or();
7163 
7164     // One enforcement literal must be false.
7165     for (const int lit : ct.enforcement_literal()) {
7166       if (context_->LiteralIsTrue(lit)) {
7167         skipped_non_zero_++;
7168         continue;
7169       }
7170       bool_or->add_literals(NegatedRef(lit));
7171     }
7172     return !bool_or->literals().empty();
7173   } else if (num_non_fixed_literals > 0) {
7174     ConstraintProto* new_ct = context_->working_model->add_constraints();
7175     CopyEnforcementLiterals(ct, new_ct);
7176     BoolArgumentProto* bool_and = new_ct->mutable_bool_and();
7177     bool_and->mutable_literals()->Reserve(num_non_fixed_literals);
7178     for (const int lit : ct.bool_and().literals()) {
7179       if (context_->LiteralIsTrue(lit)) {
7180         skipped_non_zero_++;
7181         continue;
7182       }
7183       bool_and->add_literals(lit);
7184     }
7185   }
7186   return true;
7187 }
7188 
7189 bool ModelCopy::CopyLinear(const ConstraintProto& ct) {
7190   non_fixed_variables_.clear();
7191   non_fixed_coefficients_.clear();
7192   int64_t offset = 0;
7193   for (int i = 0; i < ct.linear().vars_size(); ++i) {
7194     const int ref = ct.linear().vars(i);
7195     const int64_t coeff = ct.linear().coeffs(i);
7196     if (context_->IsFixed(ref)) {
7197       offset += coeff * context_->MinOf(ref);
7198       skipped_non_zero_++;
7199     } else {
7200       non_fixed_variables_.push_back(ref);
7201       non_fixed_coefficients_.push_back(coeff);
7202     }
7203   }
7204 
7205   const Domain new_domain =
7206       ReadDomainFromProto(ct.linear()).AdditionWith(Domain(-offset));
7207   if (non_fixed_variables_.empty() && !new_domain.Contains(0)) {
7208     if (ct.enforcement_literal().empty()) {
7209       return false;
7210     }
7211     temp_literals_.clear();
7212     for (const int literal : ct.enforcement_literal()) {
7213       if (context_->LiteralIsTrue(literal)) {
7214         skipped_non_zero_++;
7215       } else {
7216         temp_literals_.push_back(NegatedRef(literal));
7217       }
7218     }
7219     context_->working_model->add_constraints()
7220         ->mutable_bool_or()
7221         ->mutable_literals()
7222         ->Add(temp_literals_.begin(), temp_literals_.end());
7223     return !temp_literals_.empty();
7224   }
7225 
7226   // TODO(user): Compute domain and avoid copying constraint that are always
7227   // true.
7228   ConstraintProto* new_ct = context_->working_model->add_constraints();
7229   CopyEnforcementLiterals(ct, new_ct);
7230   LinearConstraintProto* linear = new_ct->mutable_linear();
7231   linear->mutable_vars()->Add(non_fixed_variables_.begin(),
7232                               non_fixed_variables_.end());
7233   linear->mutable_coeffs()->Add(non_fixed_coefficients_.begin(),
7234                                 non_fixed_coefficients_.end());
7235   FillDomainInProto(new_domain, linear);
7236   return true;
7237 }
7238 
7239 bool ModelCopy::CopyAtMostOne(const ConstraintProto& ct) {
7240   int num_true = 0;
7241   temp_literals_.clear();
7242   for (const int lit : ct.at_most_one().literals()) {
7243     if (context_->LiteralIsFalse(lit)) {
7244       skipped_non_zero_++;
7245       continue;
7246     }
7247     temp_literals_.push_back(lit);
7248     if (context_->LiteralIsTrue(lit)) num_true++;
7249   }
7250 
7251   if (temp_literals_.size() <= 1) return true;
7252   if (num_true > 1) return false;
7253 
7254   // TODO(user): presolve if num_true == 1.
7255   ConstraintProto* new_ct = context_->working_model->add_constraints();
7256   CopyEnforcementLiterals(ct, new_ct);
7257   new_ct->mutable_at_most_one()->mutable_literals()->Add(temp_literals_.begin(),
7258                                                          temp_literals_.end());
7259   return true;
7260 }
7261 
7262 bool ModelCopy::CopyExactlyOne(const ConstraintProto& ct) {
7263   int num_true = 0;
7264   temp_literals_.clear();
7265   for (const int lit : ct.exactly_one().literals()) {
7266     if (context_->LiteralIsFalse(lit)) {
7267       skipped_non_zero_++;
7268       continue;
7269     }
7270     temp_literals_.push_back(lit);
7271     if (context_->LiteralIsTrue(lit)) num_true++;
7272   }
7273 
7274   if (temp_literals_.empty() || num_true > 1) return false;
7275   if (temp_literals_.size() == 1 && num_true == 1) return true;
7276 
7277   // TODO(user): presolve if num_true == 1 and not everything is false.
7278   ConstraintProto* new_ct = context_->working_model->add_constraints();
7279   CopyEnforcementLiterals(ct, new_ct);
7280   new_ct->mutable_exactly_one()->mutable_literals()->Add(temp_literals_.begin(),
7281                                                          temp_literals_.end());
7282   return true;
7283 }
7284 
7285 bool ModelCopy::CopyInterval(const ConstraintProto& ct, int c) {
7286   // TODO(user): remove non performed intervals.
7287   CHECK_EQ(starting_constraint_index_, 0)
7288       << "Adding new interval constraints to partially filled model is not "
7289          "supported.";
7290   interval_mapping_[c] = context_->working_model->constraints_size();
7291   *context_->working_model->add_constraints() = ct;
7292   return true;
7293 }
7294 
7295 bool ModelCopy::CreateUnsatModel() {
7296   context_->working_model->mutable_constraints()->Clear();
7297   context_->working_model->add_constraints()->mutable_bool_or();
7298   return false;
7299 }
7300 
7301 bool ImportConstraintsWithBasicPresolveIntoContext(const CpModelProto& in_model,
7302                                                    PresolveContext* context) {
7303   ModelCopy copier(context);
7304   if (copier.ImportAndSimplifyConstraints(in_model, {})) {
7305     CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(in_model,
7306                                                                  context);
7307     return true;
7308   }
7309   return context->NotifyThatModelIsUnsat();
7310 }
7311 
7312 void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(
7313     const CpModelProto& in_model, PresolveContext* context) {
7314   if (!in_model.name().empty()) {
7315     context->working_model->set_name(in_model.name());
7316   }
7317   if (in_model.has_objective()) {
7318     *context->working_model->mutable_objective() = in_model.objective();
7319   }
7320   if (in_model.has_floating_point_objective()) {
7321     *context->working_model->mutable_floating_point_objective() =
7322         in_model.floating_point_objective();
7323   }
7324   if (!in_model.search_strategy().empty()) {
7325     *context->working_model->mutable_search_strategy() =
7326         in_model.search_strategy();
7327   }
7328   if (!in_model.assumptions().empty()) {
7329     *context->working_model->mutable_assumptions() = in_model.assumptions();
7330   }
7331   if (in_model.has_symmetry()) {
7332     *context->working_model->mutable_symmetry() = in_model.symmetry();
7333   }
7334   if (in_model.has_solution_hint()) {
7335     *context->working_model->mutable_solution_hint() = in_model.solution_hint();
7336   }
7337 }
7338 
7339 // =============================================================================
7340 // Public API.
7341 // =============================================================================
7342 
7343 CpSolverStatus PresolveCpModel(PresolveContext* context,
7344                                std::vector<int>* postsolve_mapping) {
7345   CpModelPresolver presolver(context, postsolve_mapping);
7346   return presolver.Presolve();
7347 }
7348 
7349 CpModelPresolver::CpModelPresolver(PresolveContext* context,
7350                                    std::vector<int>* postsolve_mapping)
7351     : postsolve_mapping_(postsolve_mapping),
7352       context_(context),
7353       logger_(context->logger()) {}
7354 
7355 CpSolverStatus CpModelPresolver::InfeasibleStatus() {
7356   if (logger_->LoggingIsEnabled()) context_->LogInfo();
7357   return CpSolverStatus::INFEASIBLE;
7358 }
7359 
7360 // The presolve works as follow:
7361 //
7362 // First stage:
7363 // We will process all active constraints until a fix point is reached. During
7364 // this stage:
7365 // - Variable will never be deleted, but their domain will be reduced.
7366 // - Constraint will never be deleted (they will be marked as empty if needed).
7367 // - New variables and new constraints can be added after the existing ones.
7368 // - Constraints are added only when needed to the mapping_problem if they are
7369 //   needed during the postsolve.
7370 //
7371 // Second stage:
7372 // - All the variables domain will be copied to the mapping_model.
7373 // - Everything will be remapped so that only the variables appearing in some
7374 //   constraints will be kept and their index will be in [0, num_new_variables).
7375 CpSolverStatus CpModelPresolver::Presolve() {
7376   // TODO(user): move in the context.
7377   context_->keep_all_feasible_solutions =
7378       context_->params().keep_all_feasible_solutions_in_presolve() ||
7379       context_->params().enumerate_all_solutions() ||
7380       context_->params().fill_tightened_domains_in_response() ||
7381       !context_->working_model->assumptions().empty() ||
7382       !context_->params().cp_model_presolve();
7383 
7384   // We copy the search strategy to the mapping_model.
7385   for (const auto& decision_strategy :
7386        context_->working_model->search_strategy()) {
7387     *(context_->mapping_model->add_search_strategy()) = decision_strategy;
7388   }
7389 
7390   // Initialize the initial context.working_model domains.
7391   context_->InitializeNewDomains();
7392 
7393   // Before initializing the constraint <-> variable graph (which is costly), we
7394   // run a bunch of simple presolve rules. Note that these function should NOT
7395   // use the graph, or the part that uses it should properly check for
7396   // context_->ConstraintVariableGraphIsUpToDate() before doing anything that
7397   // depends on the graph.
7398   if (context_->params().cp_model_presolve()) {
7399     for (int c = 0; c < context_->working_model->constraints_size(); ++c) {
7400       ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7401       PresolveEnforcementLiteral(ct);
7402       switch (ct->constraint_case()) {
7403         case ConstraintProto::ConstraintCase::kBoolOr:
7404           PresolveBoolOr(ct);
7405           break;
7406         case ConstraintProto::ConstraintCase::kBoolAnd:
7407           PresolveBoolAnd(ct);
7408           break;
7409         case ConstraintProto::ConstraintCase::kAtMostOne:
7410           PresolveAtMostOne(ct);
7411           break;
7412         case ConstraintProto::ConstraintCase::kExactlyOne:
7413           PresolveExactlyOne(ct);
7414           break;
7415         case ConstraintProto::ConstraintCase::kLinear:
7416           CanonicalizeLinear(ct);
7417           break;
7418         default:
7419           break;
7420       }
7421       if (context_->ModelIsUnsat()) break;
7422     }
7423   }
7424   if (context_->ModelIsUnsat()) return InfeasibleStatus();
7425 
7426   // If the objective is a floating point one, we scale it.
7427   //
7428   // TODO(user): We should probably try to delay this even more. For that we
7429   // just need to isolate more the "dual" reduction that usually need to look at
7430   // the objective.
7431   if (context_->working_model->has_floating_point_objective()) {
7432     if (!context_->ScaleFloatingPointObjective()) {
7433       SOLVER_LOG(logger_,
7434                  "The floating point objective cannot be scaled with enough "
7435                  "precision");
7436       return CpSolverStatus::MODEL_INVALID;
7437     }
7438 
7439     // At this point, we didn't create any new variables, so the integer
7440     // objective is in term of the orinal problem variables. We save it so that
7441     // we can expose to the user what exact objective we are actually
7442     // optimizing.
7443     *context_->mapping_model->mutable_objective() =
7444         context_->working_model->objective();
7445   }
7446 
7447   // Initialize the objective and the constraint <-> variable graph.
7448   context_->ReadObjectiveFromProto();
7449   if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
7450   context_->UpdateNewConstraintsVariableUsage();
7451   context_->RegisterVariablesUsedInAssumptions();
7452   DCHECK(context_->ConstraintVariableUsageIsConsistent());
7453 
7454   // If presolve is false, just run expansion.
7455   if (!context_->params().cp_model_presolve()) {
7456     ExpandCpModel(context_);
7457     if (context_->ModelIsUnsat()) return InfeasibleStatus();
7458 
7459     // We need to append all the variable equivalence that are still used!
7460     EncodeAllAffineRelations();
7461     if (logger_->LoggingIsEnabled()) context_->LogInfo();
7462     return CpSolverStatus::UNKNOWN;
7463   }
7464 
7465   // Main propagation loop.
7466   for (int iter = 0; iter < context_->params().max_presolve_iterations();
7467        ++iter) {
7468     context_->UpdateRuleStats("presolve: iteration");
7469     // Save some quantities to decide if we abort early the iteration loop.
7470     const int64_t old_num_presolve_op = context_->num_presolve_operations;
7471     int old_num_non_empty_constraints = 0;
7472     for (int c = 0; c < context_->working_model->constraints_size(); ++c) {
7473       const auto type =
7474           context_->working_model->constraints(c).constraint_case();
7475       if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) continue;
7476       old_num_non_empty_constraints++;
7477     }
7478 
7479     // TODO(user): The presolve transformations we do after this is called might
7480     // result in even more presolve if we were to call this again! improve the
7481     // code. See for instance plusexample_6_sat.fzn were represolving the
7482     // presolved problem reduces it even more.
7483     PresolveToFixPoint();
7484 
7485     // Call expansion.
7486     if (!context_->ModelIsExpanded()) {
7487       ExtractEncodingFromLinear();
7488       ExpandCpModel(context_);
7489     }
7490     DCHECK(context_->ConstraintVariableUsageIsConsistent());
7491 
7492     // TODO(user): do that and the pure-SAT part below more than once.
7493     if (context_->params().cp_model_probing_level() > 0) {
7494       if (!context_->time_limit()->LimitReached()) {
7495         Probe();
7496         PresolveToFixPoint();
7497       }
7498     }
7499 
7500     // Runs SAT specific presolve on the pure-SAT part of the problem.
7501     // Note that because this can only remove/fix variable not used in the other
7502     // part of the problem, there is no need to redo more presolve afterwards.
7503     if (context_->params().cp_model_use_sat_presolve()) {
7504       if (!context_->time_limit()->LimitReached()) {
7505         PresolvePureSatPart();
7506       }
7507     }
7508 
7509     // Extract redundant at most one constraint form the linear ones.
7510     //
7511     // TODO(user): more generally if we do some probing, the same relation will
7512     // be detected (and more). Also add an option to turn this off?
7513     //
7514     // TODO(user): instead of extracting at most one, extract pairwise conflicts
7515     // and add them to bool_and clauses? this is some sort of small scale
7516     // probing, but good for sat presolve and clique later?
7517     if (!context_->ModelIsUnsat() && iter == 0) {
7518       const int old_size = context_->working_model->constraints_size();
7519       for (int c = 0; c < old_size; ++c) {
7520         ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7521         if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
7522           continue;
7523         }
7524         ExtractAtMostOneFromLinear(ct);
7525       }
7526       context_->UpdateNewConstraintsVariableUsage();
7527     }
7528 
7529     if (iter == 0) TransformIntoMaxCliques();
7530 
7531     // TODO(user): Decide where is the best place for this. Fow now we do it
7532     // after max clique to get all the bool_and converted to at most ones.
7533     if (context_->params().symmetry_level() > 0 && !context_->ModelIsUnsat() &&
7534         !context_->time_limit()->LimitReached() &&
7535         !context_->keep_all_feasible_solutions) {
7536       DetectAndExploitSymmetriesInPresolve(context_);
7537     }
7538 
7539     // Process set packing, partitioning and covering constraint.
7540     if (!context_->time_limit()->LimitReached()) {
7541       ProcessSetPPC();
7542       ExtractBoolAnd();
7543 
7544       // Call the main presolve to remove the fixed variables and do more
7545       // deductions.
7546       PresolveToFixPoint();
7547     }
7548 
7549     // Exit the loop if the reduction is not so large.
7550     // Hack: to facilitate experiments, if the requested number of iterations
7551     // is large, we always execute all of them.
7552     if (context_->params().max_presolve_iterations() >= 5) continue;
7553     if (context_->num_presolve_operations - old_num_presolve_op <
7554         0.8 * (context_->working_model->variables_size() +
7555                old_num_non_empty_constraints)) {
7556       break;
7557     }
7558   }
7559   if (context_->ModelIsUnsat()) return InfeasibleStatus();
7560 
7561   // Regroup no-overlaps into max-cliques.
7562   MergeNoOverlapConstraints();
7563   if (context_->ModelIsUnsat()) return InfeasibleStatus();
7564 
7565   // Tries to spread the objective amongst many variables.
7566   if (context_->working_model->has_objective()) {
7567     ExpandObjective();
7568     if (context_->ModelIsUnsat()) return InfeasibleStatus();
7569   }
7570 
7571   // Adds all needed affine relation to context_->working_model.
7572   EncodeAllAffineRelations();
7573   if (context_->ModelIsUnsat()) return InfeasibleStatus();
7574 
7575   // Remove duplicate constraints.
7576   //
7577   // TODO(user): We might want to do that earlier so that our count of variable
7578   // usage is not biased by duplicate constraints.
7579   const std::vector<std::pair<int, int>> duplicates =
7580       FindDuplicateConstraints(*context_->working_model);
7581   for (const auto [dup, rep] : duplicates) {
7582     // Note that it is important to look at the type of the representative in
7583     // case the constraint became empty.
7584     const int type =
7585         context_->working_model->constraints(rep).constraint_case();
7586 
7587     // TODO(user): we could delete duplicate identical interval, but we need
7588     // to make sure reference to them are updated.
7589     if (type == ConstraintProto::ConstraintCase::kInterval) {
7590       continue;
7591     }
7592 
7593     // For linear constraint, we merge their rhs since it was ignored in the
7594     // FindDuplicateConstraints() call.
7595     if (type == ConstraintProto::kLinear) {
7596       const Domain d1 = ReadDomainFromProto(
7597           context_->working_model->constraints(rep).linear());
7598       const Domain d2 = ReadDomainFromProto(
7599           context_->working_model->constraints(dup).linear());
7600       if (d1 != d2) {
7601         context_->UpdateRuleStats("duplicate: merged rhs of linear constraint");
7602         const Domain rhs = d1.IntersectionWith(d2);
7603         if (rhs.IsEmpty()) {
7604           if (!MarkConstraintAsFalse(
7605                   context_->working_model->mutable_constraints(rep))) {
7606             SOLVER_LOG(logger_, "Unsat after merging two linear constraints");
7607             break;
7608           }
7609 
7610           // The representative constraint is no longer a linear constraint,
7611           // so we will not enter this type case again and will just remove
7612           // all subsequent duplicate linear constraints.
7613           context_->UpdateConstraintVariableUsage(rep);
7614           continue;
7615         }
7616         FillDomainInProto(rhs, context_->working_model->mutable_constraints(rep)
7617                                    ->mutable_linear());
7618       }
7619     }
7620 
7621     context_->working_model->mutable_constraints(dup)->Clear();
7622     context_->UpdateConstraintVariableUsage(dup);
7623     context_->UpdateRuleStats("duplicate: removed constraint");
7624   }
7625 
7626   if (context_->ModelIsUnsat()) return InfeasibleStatus();
7627 
7628   // The strategy variable indices will be remapped in ApplyVariableMapping()
7629   // but first we use the representative of the affine relations for the
7630   // variables that are not present anymore.
7631   //
7632   // Note that we properly take into account the sign of the coefficient which
7633   // will result in the same domain reduction strategy. Moreover, if the
7634   // variable order is not CHOOSE_FIRST, then we also encode the associated
7635   // affine transformation in order to preserve the order.
7636   absl::flat_hash_set<int> used_variables;
7637   for (DecisionStrategyProto& strategy :
7638        *context_->working_model->mutable_search_strategy()) {
7639     DecisionStrategyProto copy = strategy;
7640     strategy.clear_variables();
7641     strategy.clear_transformations();
7642     for (const int ref : copy.variables()) {
7643       const int var = PositiveRef(ref);
7644 
7645       // Remove fixed variables.
7646       if (context_->IsFixed(var)) continue;
7647 
7648       // There is not point having a variable appear twice, so we only keep
7649       // the first occurrence in the first strategy in which it occurs.
7650       if (used_variables.contains(var)) continue;
7651       used_variables.insert(var);
7652 
7653       if (context_->VarToConstraints(var).empty()) {
7654         const AffineRelation::Relation r = context_->GetAffineRelation(var);
7655         if (!context_->VarToConstraints(r.representative).empty()) {
7656           const int rep = (r.coeff > 0) == RefIsPositive(ref)
7657                               ? r.representative
7658                               : NegatedRef(r.representative);
7659           if (strategy.variable_selection_strategy() !=
7660               DecisionStrategyProto::CHOOSE_FIRST) {
7661             DecisionStrategyProto::AffineTransformation* t =
7662                 strategy.add_transformations();
7663             t->set_index(strategy.variables_size());
7664             t->set_offset(r.offset);
7665             t->set_positive_coeff(std::abs(r.coeff));
7666           }
7667           strategy.add_variables(rep);
7668         } else {
7669           // TODO(user): this variable was removed entirely by the presolve (no
7670           // equivalent variable present). We simply ignore it entirely which
7671           // might result in a different search...
7672         }
7673       } else {
7674         strategy.add_variables(ref);
7675       }
7676     }
7677   }
7678 
7679   // Sync the domains.
7680   for (int i = 0; i < context_->working_model->variables_size(); ++i) {
7681     FillDomainInProto(context_->DomainOf(i),
7682                       context_->working_model->mutable_variables(i));
7683     DCHECK_GT(context_->working_model->variables(i).domain_size(), 0);
7684   }
7685 
7686   // Set the variables of the mapping_model.
7687   context_->mapping_model->mutable_variables()->CopyFrom(
7688       context_->working_model->variables());
7689 
7690   // Remove all the unused variables from the presolved model.
7691   postsolve_mapping_->clear();
7692   std::vector<int> mapping(context_->working_model->variables_size(), -1);
7693   int num_free_variables = 0;
7694   for (int i = 0; i < context_->working_model->variables_size(); ++i) {
7695     if (mapping[i] != -1) continue;  // Already mapped.
7696 
7697     if (context_->VariableWasRemoved(i)) {
7698       // Heuristic: If a variable is removed and has a representative that is
7699       // not, we "move" the representative to the spot of that variable in the
7700       // original order. This is to preserve any info encoded in the variable
7701       // order by the modeler.
7702       const int r = PositiveRef(context_->GetAffineRelation(i).representative);
7703       if (mapping[r] == -1 && !context_->VariableIsNotUsedAnymore(r)) {
7704         mapping[r] = postsolve_mapping_->size();
7705         postsolve_mapping_->push_back(r);
7706       }
7707       continue;
7708     }
7709 
7710     if (context_->VariableIsNotUsedAnymore(i) &&
7711         !context_->keep_all_feasible_solutions) {
7712       // Tricky. Variables that where not removed by a presolve rule should be
7713       // fixed first during postsolve, so that more complex postsolve rules
7714       // can use their values. One way to do that is to fix them here.
7715       //
7716       // We prefer to fix them to zero if possible.
7717       ++num_free_variables;
7718       FillDomainInProto(Domain(context_->DomainOf(i).SmallestValue()),
7719                         context_->mapping_model->mutable_variables(i));
7720       continue;
7721     }
7722 
7723     mapping[i] = postsolve_mapping_->size();
7724     postsolve_mapping_->push_back(i);
7725   }
7726   context_->UpdateRuleStats(absl::StrCat("presolve: ", num_free_variables,
7727                                          " unused variables removed."));
7728 
7729   if (context_->params().permute_variable_randomly()) {
7730     std::shuffle(postsolve_mapping_->begin(), postsolve_mapping_->end(),
7731                  *context_->random());
7732     for (int i = 0; i < postsolve_mapping_->size(); ++i) {
7733       mapping[(*postsolve_mapping_)[i]] = i;
7734     }
7735   }
7736 
7737   DCHECK(context_->ConstraintVariableUsageIsConsistent());
7738   ApplyVariableMapping(mapping, *context_);
7739 
7740   // Compact all non-empty constraint at the beginning.
7741   RemoveEmptyConstraints();
7742 
7743   // Hack to display the number of deductions stored.
7744   if (context_->deductions.NumDeductions() > 0) {
7745     context_->UpdateRuleStats(absl::StrCat(
7746         "deductions: ", context_->deductions.NumDeductions(), " stored"));
7747   }
7748 
7749   // Stats and checks.
7750   if (logger_->LoggingIsEnabled()) context_->LogInfo();
7751 
7752   // This is not supposed to happen, and is more indicative of an error than an
7753   // INVALID model. But for our no-overflow preconditions, we might run into bad
7754   // situation that causes the final model to be invalid.
7755   {
7756     const std::string error = ValidateCpModel(*context_->working_model);
7757     if (!error.empty()) {
7758       SOLVER_LOG(logger_, "Error while validating postsolved model: ", error);
7759       return CpSolverStatus::MODEL_INVALID;
7760     }
7761   }
7762   {
7763     const std::string error = ValidateCpModel(*context_->mapping_model);
7764     if (!error.empty()) {
7765       SOLVER_LOG(logger_,
7766                  "Error while validating mapping_model model: ", error);
7767       return CpSolverStatus::MODEL_INVALID;
7768     }
7769   }
7770 
7771   return CpSolverStatus::UNKNOWN;
7772 }
7773 
7774 void ApplyVariableMapping(const std::vector<int>& mapping,
7775                           const PresolveContext& context) {
7776   CpModelProto* proto = context.working_model;
7777 
7778   // Remap all the variable/literal references in the constraints and the
7779   // enforcement literals in the variables.
7780   auto mapping_function = [&mapping](int* ref) {
7781     const int image = mapping[PositiveRef(*ref)];
7782     CHECK_GE(image, 0);
7783     *ref = RefIsPositive(*ref) ? image : NegatedRef(image);
7784   };
7785   for (ConstraintProto& ct_ref : *proto->mutable_constraints()) {
7786     ApplyToAllVariableIndices(mapping_function, &ct_ref);
7787     ApplyToAllLiteralIndices(mapping_function, &ct_ref);
7788   }
7789 
7790   // Remap the objective variables.
7791   if (proto->has_objective()) {
7792     for (int& mutable_ref : *proto->mutable_objective()->mutable_vars()) {
7793       mapping_function(&mutable_ref);
7794     }
7795   }
7796 
7797   // Remap the assumptions.
7798   for (int& mutable_ref : *proto->mutable_assumptions()) {
7799     mapping_function(&mutable_ref);
7800   }
7801 
7802   // Remap the search decision heuristic.
7803   // Note that we delete any heuristic related to a removed variable.
7804   for (DecisionStrategyProto& strategy : *proto->mutable_search_strategy()) {
7805     const DecisionStrategyProto copy = strategy;
7806     strategy.clear_variables();
7807     std::vector<int> new_indices(copy.variables().size(), -1);
7808     for (int i = 0; i < copy.variables().size(); ++i) {
7809       const int ref = copy.variables(i);
7810       const int image = mapping[PositiveRef(ref)];
7811       if (image >= 0) {
7812         new_indices[i] = strategy.variables_size();
7813         strategy.add_variables(RefIsPositive(ref) ? image : NegatedRef(image));
7814       }
7815     }
7816     strategy.clear_transformations();
7817     for (const auto& transform : copy.transformations()) {
7818       CHECK_LT(transform.index(), new_indices.size());
7819       const int new_index = new_indices[transform.index()];
7820       if (new_index == -1) continue;
7821       auto* new_transform = strategy.add_transformations();
7822       *new_transform = transform;
7823       CHECK_LT(new_index, strategy.variables().size());
7824       new_transform->set_index(new_index);
7825     }
7826   }
7827 
7828   // Remap the solution hint. Note that after remapping, we may have duplicate
7829   // variable, so we only keep the first occurrence.
7830   if (proto->has_solution_hint()) {
7831     absl::flat_hash_set<int> used_vars;
7832     auto* mutable_hint = proto->mutable_solution_hint();
7833     int new_size = 0;
7834     for (int i = 0; i < mutable_hint->vars_size(); ++i) {
7835       const int old_ref = mutable_hint->vars(i);
7836       int64_t old_value = mutable_hint->values(i);
7837 
7838       // We always move a hint within bounds.
7839       // This also make sure a hint of INT_MIN or INT_MAX does not overflow.
7840       if (old_value < context.MinOf(old_ref)) {
7841         old_value = context.MinOf(old_ref);
7842       }
7843       if (old_value > context.MaxOf(old_ref)) {
7844         old_value = context.MaxOf(old_ref);
7845       }
7846 
7847       // Note that if (old_value - r.offset) is not divisible by r.coeff, then
7848       // the hint is clearly infeasible, but we still set it to a "close" value.
7849       const AffineRelation::Relation r = context.GetAffineRelation(old_ref);
7850       const int var = r.representative;
7851       const int64_t value = (old_value - r.offset) / r.coeff;
7852 
7853       const int image = mapping[var];
7854       if (image >= 0) {
7855         if (!used_vars.insert(image).second) continue;
7856         mutable_hint->set_vars(new_size, image);
7857         mutable_hint->set_values(new_size, value);
7858         ++new_size;
7859       }
7860     }
7861     if (new_size > 0) {
7862       mutable_hint->mutable_vars()->Truncate(new_size);
7863       mutable_hint->mutable_values()->Truncate(new_size);
7864     } else {
7865       proto->clear_solution_hint();
7866     }
7867   }
7868 
7869   // Move the variable definitions.
7870   std::vector<IntegerVariableProto> new_variables;
7871   for (int i = 0; i < mapping.size(); ++i) {
7872     const int image = mapping[i];
7873     if (image < 0) continue;
7874     if (image >= new_variables.size()) {
7875       new_variables.resize(image + 1, IntegerVariableProto());
7876     }
7877     new_variables[image].Swap(proto->mutable_variables(i));
7878   }
7879   proto->clear_variables();
7880   for (IntegerVariableProto& proto_ref : new_variables) {
7881     proto->add_variables()->Swap(&proto_ref);
7882   }
7883 
7884   // Check that all variables are used.
7885   for (const IntegerVariableProto& v : proto->variables()) {
7886     CHECK_GT(v.domain_size(), 0);
7887   }
7888 }
7889 
7890 namespace {
7891 ConstraintProto CopyConstraintForDuplicateDetection(const ConstraintProto& ct) {
7892   ConstraintProto copy = ct;
7893   copy.clear_name();
7894   if (ct.constraint_case() == ConstraintProto::kLinear) {
7895     copy.mutable_linear()->clear_domain();
7896   }
7897   return copy;
7898 }
7899 }  // namespace
7900 
7901 std::vector<std::pair<int, int>> FindDuplicateConstraints(
7902     const CpModelProto& model_proto) {
7903   std::vector<std::pair<int, int>> result;
7904 
7905   // We use a map hash: serialized_constraint_proto hash -> constraint index.
7906   ConstraintProto copy;
7907   absl::flat_hash_map<uint64_t, int> equiv_constraints;
7908 
7909   std::string s;
7910   const int num_constraints = model_proto.constraints().size();
7911   for (int c = 0; c < num_constraints; ++c) {
7912     if (model_proto.constraints(c).constraint_case() ==
7913         ConstraintProto::CONSTRAINT_NOT_SET) {
7914       continue;
7915     }
7916 
7917     // We ignore names when comparing constraints.
7918     //
7919     // TODO(user): This is not particularly efficient.
7920     copy = CopyConstraintForDuplicateDetection(model_proto.constraints(c));
7921     s = copy.SerializeAsString();
7922 
7923     const uint64_t hash = absl::Hash<std::string>()(s);
7924     const auto [it, inserted] = equiv_constraints.insert({hash, c});
7925     if (!inserted) {
7926       // Already present!
7927       const int other_c_with_same_hash = it->second;
7928       copy = CopyConstraintForDuplicateDetection(
7929           model_proto.constraints(other_c_with_same_hash));
7930       if (s == copy.SerializeAsString()) {
7931         result.push_back({c, other_c_with_same_hash});
7932       }
7933     }
7934   }
7935 
7936   return result;
7937 }
7938 
7939 }  // namespace sat
7940 }  // namespace operations_research
7941