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