OR-Tools  9.2
cp_model_presolve.cc
Go to the documentation of this file.
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
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"
43#include "ortools/sat/circuit.h"
54#include "ortools/sat/probing.h"
59
60namespace operations_research {
61namespace sat {
62
63bool 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.
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 =
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()) {
94 [&interval_mapping](int* ref) {
95 *ref = interval_mapping[*ref];
96 CHECK_NE(-1, *ref);
97 },
98 &ct_ref);
99 }
100}
101
102bool 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.
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
147bool 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).
221 } else { // a == b.
222 context_->StoreBooleanEqualityRelation(a, b);
223 }
225 context_->UpdateRuleStats("bool_xor: two active literals");
226 return RemoveConstraint(ct);
227 }
228
229 if (num_true_literals % 2 == 1) {
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
241bool 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.
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.
320ABSL_MUST_USE_RESULT bool CpModelPresolver::MarkConstraintAsFalse(
321 ConstraintProto* 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
336bool 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
403bool 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 &&
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");
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
521bool CpModelPresolver::PresolveAtMostOne(ConstraintProto* ct) {
522 if (context_->ModelIsUnsat()) return false;
523
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
544bool CpModelPresolver::PresolveExactlyOne(ConstraintProto* ct) {
545 if (context_->ModelIsUnsat()) return false;
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
574bool 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
589bool 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());
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
812 return RemoveConstraint(ct);
813 }
814 }
815
816 return changed;
817}
818
819// This presolve expect that the constraint only contains affine expressions.
820bool 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())
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)
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);
880 if (!CanonicalizeLinear(new_ct)) return false;
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);
894 if (!CanonicalizeLinear(new_ct)) return false;
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) &&
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
929bool 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 =
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);
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();
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 }
1141 return RemoveConstraint(ct);
1142}
1143
1144bool 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 =
1178 lin->add_domain(0);
1179 lin->add_domain(0);
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 =
1204 lin->add_domain(0);
1205 lin->add_domain(divisor - 1);
1207 AddLinearExpressionToLinearConstraint(target, -divisor, lin);
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
1219bool 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,
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
1248bool 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.
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.
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
1301bool 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
1326template <typename ProtoWithVarsAndCoeffs>
1327bool 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
1409bool 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
1417bool 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) {
1433 ReadDomainFromProto(ct->linear()).AdditionWith(Domain(-offset)),
1434 ct->mutable_linear());
1435 }
1436 changed |= DivideLinearByGcd(ct);
1437 return changed;
1438}
1439
1440bool 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);
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);
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(
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.
1627bool 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.
1665bool 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
1738bool 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())
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());
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;
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.
1859 return false;
1860}
1861
1862bool 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);
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));
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))
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");
2029 return RemoveConstraint(ct);
2030 }
2031 }
2032
2033 return false;
2034}
2035
2036bool 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
2057namespace {
2058
2059// Return true if the given domain only restrict the values with an upper bound.
2060bool 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.
2068bool 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.
2078bool 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
2098bool 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.
2113void 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
2125bool 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);
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
2232bool 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);
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.
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() &&
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 :
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.
2490void 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
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 =
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) {
2635 context_->UpdateConstraintVariableUsage(ct_index);
2636 }
2637}
2638
2639void 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 }
2688 }
2689 }
2690}
2691
2692// Convert some linear constraint involving only Booleans to their Boolean
2693// form.
2694bool 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);
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 }
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 }
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
2884 return RemoveConstraint(ct);
2885}
2886
2887bool 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.
2916bool 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
3014bool 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 =
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);
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 =
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);
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 =
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 }
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
3243bool 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 }
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 }
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
3447bool 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
3590namespace {
3591
3592// Returns the sorted list of literals for given bool_or or at_most_one
3593// constraint.
3594std::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.
3615void 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];
3620 } else if (ref_to_bool_and->contains(NegatedRef(rhs))) {
3621 const int ct_index = (*ref_to_bool_and)[NegatedRef(rhs)];
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
3632template <typename ClauseContainer>
3633void 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
3672bool 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 =
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 }
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 =
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 =
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;
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
3825bool 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 =
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 }
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 }
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
3938namespace {
3939LinearExpressionProto ConstantExpressionProto(int64_t value) {
3940 LinearExpressionProto expr;
3941 expr.set_offset(value);
3942 return expr;
3943}
3944} // namespace
3945
3946bool 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 =
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 }
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,
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 }
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))
4326 capacity_gt->mutable_linear()->add_domain(0);
4327 capacity_gt->mutable_linear()->add_domain(
4330 capacity_gt->mutable_linear());
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 }
4342 return RemoveConstraint(ct);
4343 }
4344 }
4345
4346 return changed;
4347}
4348
4349bool 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
4407bool 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
4603bool 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],
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
4737bool 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 =
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.
4902void 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
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...
4939void 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);
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);
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.
4998void 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.
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?
5173void 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;
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
5401void 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.
5472void 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);
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 }
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
5575namespace {
5576
5577bool 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
5605 if (context_->ModelIsUnsat()) return false;
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.
5744bool 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
5754 original_constraint_index[c1]);
5756 original_constraint_index[c2]);
5757
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.
5773 ConstraintProto copy = *ct2;
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
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
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
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);
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.
5866bool 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.
6025bool 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 =
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();
6168 context_->UpdateConstraintVariableUsage(linear_encoding_ct_index);
6169 return true;
6170}
6171
6172namespace {
6173
6174// We want something that is order invariant and is compatible with inclusion.
6175uint64_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
6183void CpModelPresolver::ExtractEncodingFromLinear() {
6184 if (context_->time_limit()->LimitReached()) return;
6185 if (context_->ModelIsUnsat()) return;
6186
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()) {
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 }
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 }
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
6391void CpModelPresolver::TryToSimplifyDomain(int var) {
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) &&
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 =
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 }
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) {
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
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
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)) {
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.
6723void 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);
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.
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.
6763bool 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;
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);
6794 }
6795 return true;
6796}
6797
6798void CpModelPresolver::PresolveToFixPoint() {
6799 if (context_->ModelIsUnsat()) return;
6800
6801 // Limit on number of operations.
6802 const int64_t max_num_operations =
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) {
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);
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
7028}
7029
7031
7032// TODO(user): Merge with the phase 1 of the presolve code.
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()) {
7050 break;
7051 }
7053 if (!CopyBoolOr(ct)) return CreateUnsatModel();
7054 break;
7055 }
7057 if (!CopyBoolAnd(ct)) return CreateUnsatModel();
7058 break;
7059 }
7061 if (!CopyLinear(ct)) return CreateUnsatModel();
7062 break;
7063 }
7065 if (!CopyAtMostOne(ct)) return CreateUnsatModel();
7066 break;
7067 }
7069 if (!CopyExactlyOne(ct)) return CreateUnsatModel();
7070 break;
7071 }
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);
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
7100void 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
7114bool 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
7123bool 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()
7143 ->Add(temp_literals_.begin(), temp_literals_.end());
7144 return !temp_literals_.empty();
7145}
7146
7147bool 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
7189bool 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()
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
7239bool 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
7262bool 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
7285bool 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
7295bool ModelCopy::CreateUnsatModel() {
7296 context_->working_model->mutable_constraints()->Clear();
7298 return false;
7299}
7300
7303 ModelCopy copier(context);
7304 if (copier.ImportAndSimplifyConstraints(in_model, {})) {
7306 context);
7307 return true;
7308 }
7309 return context->NotifyThatModelIsUnsat();
7310}
7311
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
7344 std::vector<int>* postsolve_mapping) {
7345 CpModelPresolver presolver(context, postsolve_mapping);
7346 return presolver.Presolve();
7347}
7348
7350 std::vector<int>* postsolve_mapping)
7351 : postsolve_mapping_(postsolve_mapping),
7352 context_(context),
7353 logger_(context->logger()) {}
7354
7355CpSolverStatus CpModelPresolver::InfeasibleStatus() {
7356 if (logger_->LoggingIsEnabled()) context_->LogInfo();
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).
7376 // TODO(user): move in the context.
7377 context_->keep_all_feasible_solutions =
7379 context_->params().enumerate_all_solutions() ||
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) {
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.
7432 if (!context_->ScaleFloatingPointObjective()) {
7433 SOLVER_LOG(logger_,
7434 "The floating point objective cannot be scaled with enough "
7435 "precision");
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();
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();
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 =
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 }
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) {
7521 if (ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
7522 continue;
7523 }
7524 ExtractAtMostOneFromLinear(ct);
7525 }
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) {
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 =
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 }
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
7659 if (strategy.variable_selection_strategy() !=
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.
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;
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
7738 ApplyVariableMapping(mapping, *context_);
7739
7740 // Compact all non-empty constraint at the beginning.
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);
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);
7768 }
7769 }
7770
7772}
7773
7774void 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.
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 {
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 }
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
7890namespace {
7891ConstraintProto 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
7901std::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) {
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
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:492
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:888
#define CHECK_LT(val1, val2)
Definition: base/logging.h:702
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:699
#define CHECK_GE(val1, val2)
Definition: base/logging.h:703
#define CHECK_GT(val1, val2)
Definition: base/logging.h:704
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:891
#define CHECK_NE(val1, val2)
Definition: base/logging.h:700
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:892
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:890
#define LOG(severity)
Definition: base/logging.h:417
#define DCHECK(condition)
Definition: base/logging.h:886
#define CHECK_LE(val1, val2)
Definition: base/logging.h:701
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:887
#define VLOG(verboselevel)
Definition: base/logging.h:980
void Start()
Definition: timer.h:31
double Get() const
Definition: timer.h:45
We call domain any subset of Int64 = [kint64min, kint64max].
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
bool Contains(int64_t value) const
Returns true iff value is in Domain.
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
ClosedInterval front() const
int64_t Size() const
Returns the number of elements in the domain.
Domain UnionWith(const Domain &domain) const
Returns the union of D and domain.
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
bool IsEmpty() const
Returns true if this is the empty set.
int64_t SmallestValue() const
Returns the value closest to zero.
Domain RelaxIfTooComplex() const
If NumIntervals() is too large, this return a superset of the domain.
static Domain FromValues(std::vector< int64_t > values)
Creates a domain from the union of an unsorted list of integer values.
Domain DivisionBy(int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e / coeff}.
DomainIteratorBeginEnd Values() const &
Domain PositiveModuloBySuperset(const Domain &modulo) const
Returns a superset of {x ∈ Int64, ∃ e ∈ D, ∃ m ∈ modulo, x = e % m }.
static int64_t GCD64(int64_t x, int64_t y)
Definition: mathutil.h:107
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:534
void AdvanceDeterministicTime(double deterministic_duration)
Advances the deterministic time.
Definition: time_limit.h:227
::operations_research::sat::LinearExpressionProto * add_exprs()
Definition: cp_model.pb.h:7472
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_literals()
Definition: cp_model.pb.h:7182
const ::operations_research::sat::BoolArgumentProto & at_most_one() const
Definition: cp_model.pb.h:9691
::operations_research::sat::IntervalConstraintProto * mutable_interval()
::operations_research::sat::NoOverlap2DConstraintProto * mutable_no_overlap_2d()
::operations_research::sat::ElementConstraintProto * mutable_element()
::operations_research::sat::NoOverlapConstraintProto * mutable_no_overlap()
const ::operations_research::sat::LinearConstraintProto & linear() const
void set_name(ArgT0 &&arg0, ArgT... args)
int32_t enforcement_literal(int index) const
Definition: cp_model.pb.h:9472
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_enforcement_literal()
Definition: cp_model.pb.h:9501
::operations_research::sat::BoolArgumentProto * mutable_bool_and()
Definition: cp_model.pb.h:9648
const ::operations_research::sat::BoolArgumentProto & exactly_one() const
Definition: cp_model.pb.h:9765
PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final
::operations_research::sat::AllDifferentConstraintProto * mutable_all_diff()
void Swap(ConstraintProto *other)
Definition: cp_model.pb.h:3852
::operations_research::sat::BoolArgumentProto * mutable_exactly_one()
Definition: cp_model.pb.h:9796
::operations_research::sat::LinearConstraintProto * mutable_linear()
::operations_research::sat::BoolArgumentProto * mutable_bool_or()
Definition: cp_model.pb.h:9574
::operations_research::sat::CumulativeConstraintProto * mutable_cumulative()
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_assumptions()
::operations_research::sat::DecisionStrategyProto * add_search_strategy()
const std::string & name() const
const ::operations_research::sat::CpObjectiveProto & objective() const
::operations_research::sat::DecisionStrategyProto * mutable_search_strategy(int index)
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
const ::operations_research::sat::DecisionStrategyProto & search_strategy(int index) const
const ::operations_research::sat::PartialVariableAssignment & solution_hint() const
::operations_research::sat::PartialVariableAssignment * mutable_solution_hint()
::operations_research::sat::ConstraintProto * mutable_constraints(int index)
const ::operations_research::sat::SymmetryProto & symmetry() const
::operations_research::sat::IntegerVariableProto * mutable_variables(int index)
::operations_research::sat::ConstraintProto * add_constraints()
const ::operations_research::sat::FloatObjectiveProto & floating_point_objective() const
::operations_research::sat::IntegerVariableProto * add_variables()
::operations_research::sat::CpObjectiveProto * mutable_objective()
int32_t assumptions(int index) const
const ::operations_research::sat::ConstraintProto & constraints(int index) const
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_vars()
static constexpr VariableSelectionStrategy CHOOSE_FIRST
Definition: cp_model.pb.h:5230
const ::operations_research::sat::DecisionStrategyProto_AffineTransformation & transformations(int index) const
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
void AddDeduction(int literal_ref, int var, Domain domain)
void Swap(IntegerVariableProto *other)
Definition: cp_model.pb.h:325
void CopyFrom(const IntegerVariableProto &from)
::operations_research::sat::LinearExpressionProto * mutable_start()
Definition: cp_model.pb.h:7788
::PROTOBUF_NAMESPACE_ID::RepeatedField< int64_t > * mutable_coeffs()
Definition: cp_model.pb.h:7576
void set_coeffs(int index, int64_t value)
Definition: cp_model.pb.h:7551
::PROTOBUF_NAMESPACE_ID::RepeatedField< int32_t > * mutable_vars()
Definition: cp_model.pb.h:7529
void set_vars(int index, int32_t value)
Definition: cp_model.pb.h:7504
bool ImportAndSimplifyConstraints(const CpModelProto &in_model, const std::vector< int > &ignored_constraints)
bool CanonicalizeAffineVariable(int ref, int64_t coeff, int64_t mod, int64_t rhs)
bool ExpressionIsALiteral(const LinearExpressionProto &expr, int *literal=nullptr) const
bool StoreAbsRelation(int target_ref, int ref)
const absl::flat_hash_set< int > & VarToConstraints(int var) const
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
std::vector< absl::flat_hash_set< int > > var_to_lb_only_constraints
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64_t value)
ABSL_MUST_USE_RESULT bool CanonicalizeObjective(bool simplify_domain=true)
bool StoreBooleanEqualityRelation(int ref_a, int ref_b)
bool DomainOfVarIsIncludedIn(int var, const Domain &domain)
bool VariableWithCostIsUniqueAndRemovable(int ref) const
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
ABSL_MUST_USE_RESULT bool ScaleFloatingPointObjective()
std::vector< absl::flat_hash_set< int > > var_to_ub_only_constraints
ABSL_MUST_USE_RESULT bool SubstituteVariableInObjective(int var_in_equality, int64_t coeff_in_equality, const ConstraintProto &equality, std::vector< int > *new_vars_in_objective=nullptr)
int GetOrCreateVarValueEncoding(int ref, int64_t value)
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(const std::string &message="")
bool InsertVarValueEncoding(int literal, int ref, int64_t value)
const std::vector< int > & ConstraintToVars(int c) const
const absl::flat_hash_map< int, int64_t > & ObjectiveMap() const
bool DomainContains(int ref, int64_t value) const
void UpdateRuleStats(const std::string &name, int num_times=1)
AffineRelation::Relation GetAffineRelation(int ref) const
bool StoreAffineRelation(int ref_x, int ref_y, int64_t coeff, int64_t offset, bool debug_no_recursion=false)
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
int LiteralForExpressionMax(const LinearExpressionProto &expr) const
const SatParameters & params() const
bool ExpressionIsAffineBoolean(const LinearExpressionProto &expr) const
bool ExploitExactlyOneInObjective(absl::Span< const int > exactly_one)
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
absl::flat_hash_set< int > tmp_literal_set
bool VariableIsOnlyUsedInEncodingAndMaybeInObjective(int ref) const
bool GetAbsRelation(int target_ref, int *ref)
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64_t value)
::operations_research::sat::SatParameters_SearchBranching search_branching() const
static constexpr SearchBranching FIXED_SEARCH
int64_t b
int64_t a
Block * next
CpModelProto proto
CpModelProto const * model_proto
WallTimer * wall_timer
ModelSharedTimeLimit * time_limit
const std::string name
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
GurobiMPCallbackContext * context
const int WARNING
Definition: log_severity.h:31
int64_t hash
Definition: matrix_utils.cc:61
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
void STLSetDifference(const In1 &a, const In2 &b, Out *out, Compare compare)
Definition: stl_util.h:595
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
bool DetectAndExploitSymmetriesInPresolve(PresolveContext *context)
bool RefIsPositive(int ref)
void GetOverlappingIntervalComponents(std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *components)
Definition: diffn_util.cc:395
const LiteralIndex kNoLiteralIndex(-1)
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(const std::vector< Rectangle > &rectangles, absl::Span< int > active_rectangles)
Definition: diffn_util.cc:26
bool HasEnforcementLiteral(const ConstraintProto &ct)
void ExpandCpModel(PresolveContext *context)
constexpr int kAffineRelationConstraint
void ApplyToAllLiteralIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
void DetectDominanceRelations(const PresolveContext &context, VarDomination *var_domination, DualBoundStrengthening *dual_bound_strengthening)
bool LinearExpressionProtosAreEqual(const LinearExpressionProto &a, const LinearExpressionProto &b, int64_t b_scaling)
void ApplyToAllIntervalIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
Definition: sat/util.cc:125
void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(const CpModelProto &in_model, PresolveContext *context)
int ReindexArcs(IntContainer *tails, IntContainer *heads)
Definition: circuit.h:168
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
bool ImportConstraintsWithBasicPresolveIntoContext(const CpModelProto &in_model, PresolveContext *context)
std::vector< std::pair< int, int > > FindDuplicateConstraints(const CpModelProto &model_proto)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void ApplyToAllVariableIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
void ConstructOverlappingSets(bool already_sorted, std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *result)
Definition: diffn_util.cc:346
constexpr int kObjectiveConstraint
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
int GetSingleRefFromExpression(const LinearExpressionProto &expr)
bool ExpressionContainsSingleRef(const LinearExpressionProto &expr)
void ApplyVariableMapping(const std::vector< int > &mapping, const PresolveContext &context)
std::string ValidateCpModel(const CpModelProto &model)
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
int64_t CapProd(int64_t x, int64_t y)
Literal literal
Definition: optimization.cc:85
int index
Definition: pack.cc:509
if(!yyg->yy_init)
Definition: parser.yy.cc:965
ColIndex representative
int64_t demand
Definition: resource.cc:125
int64_t time
Definition: resource.cc:1691
IntervalVar * interval
Definition: resource.cc:100
int64_t tail
int64_t head
Rev< int64_t > end_max
Rev< int64_t > start_min
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69