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