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