Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model_presolve.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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 <array>
18#include <cstdint>
19#include <cstdlib>
20#include <deque>
21#include <functional>
22#include <limits>
23#include <memory>
24#include <numeric>
25#include <optional>
26#include <string>
27#include <string_view>
28#include <tuple>
29#include <utility>
30#include <vector>
31
32#include "absl/algorithm/container.h"
33#include "absl/base/attributes.h"
34#include "absl/container/btree_map.h"
35#include "absl/container/btree_set.h"
36#include "absl/container/flat_hash_map.h"
37#include "absl/container/flat_hash_set.h"
38#include "absl/flags/flag.h"
39#include "absl/hash/hash.h"
40#include "absl/log/check.h"
41#include "absl/meta/type_traits.h"
42#include "absl/numeric/int128.h"
43#include "absl/random/distributions.h"
44#include "absl/status/statusor.h"
45#include "absl/strings/str_cat.h"
46#include "absl/types/span.h"
47#include "google/protobuf/arena.h"
48#include "google/protobuf/repeated_field.h"
49#include "google/protobuf/repeated_ptr_field.h"
55#include "ortools/base/timer.h"
60#include "ortools/sat/circuit.h"
61#include "ortools/sat/clause.h"
62#include "ortools/sat/cp_model.pb.h"
72#include "ortools/sat/integer.h"
74#include "ortools/sat/model.h"
77#include "ortools/sat/probing.h"
80#include "ortools/sat/sat_parameters.pb.h"
84#include "ortools/sat/util.h"
87#include "ortools/util/bitset.h"
93
94namespace operations_research {
95namespace sat {
96
97namespace {
98
99// TODO(user): Just make sure this invariant is enforced in all our linear
100// constraint after copy, and simplify the code!
101bool LinearConstraintIsClean(const LinearConstraintProto& linear) {
102 const int num_vars = linear.vars().size();
103 for (int i = 0; i < num_vars; ++i) {
104 if (!RefIsPositive(linear.vars(i))) return false;
105 if (linear.coeffs(i) == 0) return false;
106 }
107 return true;
108}
109
110} // namespace
111
112bool CpModelPresolver::RemoveConstraint(ConstraintProto* ct) {
113 ct->Clear();
114 return true;
115}
116
117// Remove all empty constraints and duplicated intervals. Note that we need to
118// remap the interval references.
119//
120// Now that they have served their purpose, we also remove dummy constraints,
121// otherwise that causes issue because our model are invalid in tests.
123 interval_representative_.clear();
124 std::vector<int> interval_mapping(context_->working_model->constraints_size(),
125 -1);
126 int new_num_constraints = 0;
127 const int old_num_non_empty_constraints =
128 context_->working_model->constraints_size();
129 for (int c = 0; c < old_num_non_empty_constraints; ++c) {
130 const auto type = context_->working_model->constraints(c).constraint_case();
131 if (type == ConstraintProto::CONSTRAINT_NOT_SET) continue;
132 if (type == ConstraintProto::kDummyConstraint) continue;
133 context_->working_model->mutable_constraints(new_num_constraints)
134 ->Swap(context_->working_model->mutable_constraints(c));
135 if (type == ConstraintProto::kInterval) {
136 // Warning: interval_representative_ holds a pointer to the working model
137 // to compute hashes, so we need to be careful about not changing a
138 // constraint after its index is added to the map.
139 const auto [it, inserted] = interval_representative_.insert(
140 {new_num_constraints, new_num_constraints});
141 interval_mapping[c] = it->second;
142 if (it->second != new_num_constraints) {
143 context_->UpdateRuleStats(
144 "intervals: change duplicate index across constraints");
145 continue;
146 }
147 }
148 new_num_constraints++;
149 }
151 context_->working_model->mutable_constraints(), new_num_constraints);
152 for (ConstraintProto& ct_ref :
153 *context_->working_model->mutable_constraints()) {
155 [&interval_mapping](int* ref) {
156 *ref = interval_mapping[*ref];
157 CHECK_NE(-1, *ref);
158 },
159 &ct_ref);
160 }
161}
162
163bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto* ct) {
164 if (context_->ModelIsUnsat()) return false;
165 if (!HasEnforcementLiteral(*ct)) return false;
166
167 int new_size = 0;
168 const int old_size = ct->enforcement_literal().size();
169 context_->tmp_literal_set.clear();
170 for (const int literal : ct->enforcement_literal()) {
171 if (context_->LiteralIsTrue(literal)) {
172 // We can remove a literal at true.
173 context_->UpdateRuleStats("enforcement: true literal");
174 continue;
175 }
176
177 if (context_->LiteralIsFalse(literal)) {
178 context_->UpdateRuleStats("enforcement: false literal");
179 return RemoveConstraint(ct);
180 }
181
182 if (context_->VariableIsUniqueAndRemovable(literal)) {
183 // We can simply set it to false and ignore the constraint in this case.
184 context_->UpdateRuleStats("enforcement: literal not used");
185 CHECK(context_->SetLiteralToFalse(literal));
186 return RemoveConstraint(ct);
187 }
188
189 // If the literal only appear in the objective, we might be able to fix it
190 // to false. TODO(user): generalize if the literal always appear with the
191 // same polarity.
192 if (context_->VariableWithCostIsUniqueAndRemovable(literal)) {
193 const int64_t obj_coeff =
194 context_->ObjectiveMap().at(PositiveRef(literal));
195 if (RefIsPositive(literal) == (obj_coeff > 0)) {
196 // It is just more advantageous to set it to false!
197 context_->UpdateRuleStats("enforcement: literal with unique direction");
198 CHECK(context_->SetLiteralToFalse(literal));
199 return RemoveConstraint(ct);
200 }
201 }
202
203 // Deals with duplicate literals.
204 //
205 // TODO(user): Ideally we could do that just once during the first copy,
206 // and later never create such constraint.
207 if (old_size > 1) {
208 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
209 if (!inserted) {
210 context_->UpdateRuleStats("enforcement: removed duplicate literal");
211 continue;
212 }
213 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
214 context_->UpdateRuleStats("enforcement: can never be true");
215 return RemoveConstraint(ct);
216 }
217 }
218
219 ct->set_enforcement_literal(new_size++, literal);
220 }
221 ct->mutable_enforcement_literal()->Truncate(new_size);
222 return new_size != old_size;
223}
224
225bool CpModelPresolver::PresolveBoolXor(ConstraintProto* ct) {
226 if (context_->ModelIsUnsat()) return false;
227 if (HasEnforcementLiteral(*ct)) return false;
228
229 int new_size = 0;
230 bool changed = false;
231 int num_true_literals = 0;
232 int true_literal = std::numeric_limits<int32_t>::min();
233 for (const int literal : ct->bool_xor().literals()) {
234 // TODO(user): More generally, if a variable appear in only bool xor
235 // constraints, we can simply eliminate it using linear algebra on Z/2Z.
236 // This should solve in polynomial time the parity-learning*.fzn problems
237 // for instance. This seems low priority, but it is also easy to do. Even
238 // better would be to have a dedicated propagator with all bool_xor
239 // constraints that do the necessary linear algebra.
240 if (context_->VariableIsUniqueAndRemovable(literal)) {
241 context_->UpdateRuleStats("TODO bool_xor: remove constraint");
242 }
243
244 if (context_->LiteralIsFalse(literal)) {
245 context_->UpdateRuleStats("bool_xor: remove false literal");
246 changed = true;
247 continue;
248 } else if (context_->LiteralIsTrue(literal)) {
249 true_literal = literal; // Keep if we need to put one back.
250 num_true_literals++;
251 continue;
252 }
253
254 ct->mutable_bool_xor()->set_literals(new_size++, literal);
255 }
256
257 if (new_size == 0) {
258 if (num_true_literals % 2 == 0) {
259 return context_->NotifyThatModelIsUnsat("bool_xor: always false");
260 } else {
261 context_->UpdateRuleStats("bool_xor: always true");
262 return RemoveConstraint(ct);
263 }
264 } else if (new_size == 1) { // We can fix the only active literal.
265 if (num_true_literals % 2 == 0) {
266 if (!context_->SetLiteralToTrue(ct->bool_xor().literals(0))) {
267 return context_->NotifyThatModelIsUnsat(
268 "bool_xor: cannot fix last literal");
269 }
270 } else {
271 if (!context_->SetLiteralToFalse(ct->bool_xor().literals(0))) {
272 return context_->NotifyThatModelIsUnsat(
273 "bool_xor: cannot fix last literal");
274 }
275 }
276 context_->UpdateRuleStats("bool_xor: one active literal");
277 return RemoveConstraint(ct);
278 } else if (new_size == 2) { // We can simplify the bool_xor.
279 const int a = ct->bool_xor().literals(0);
280 const int b = ct->bool_xor().literals(1);
281 if (a == b) {
282 if (num_true_literals % 2 == 0) {
283 return context_->NotifyThatModelIsUnsat("bool_xor: always false");
284 } else {
285 context_->UpdateRuleStats("bool_xor: always true");
286 return RemoveConstraint(ct);
287 }
288 }
289 if (a == NegatedRef(b)) {
290 if (num_true_literals % 2 == 1) {
291 return context_->NotifyThatModelIsUnsat("bool_xor: always false");
292 } else {
293 context_->UpdateRuleStats("bool_xor: always true");
294 return RemoveConstraint(ct);
295 }
296 }
297 if (num_true_literals % 2 == 0) { // a == not(b).
298 if (!context_->StoreBooleanEqualityRelation(a, NegatedRef(b))) {
299 return false;
300 }
301 } else { // a == b.
302 if (!context_->StoreBooleanEqualityRelation(a, b)) {
303 return false;
304 }
305 }
306 context_->UpdateNewConstraintsVariableUsage();
307 context_->UpdateRuleStats("bool_xor: two active literals");
308 return RemoveConstraint(ct);
309 }
310
311 if (num_true_literals % 2 == 1) {
312 CHECK_NE(true_literal, std::numeric_limits<int32_t>::min());
313 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
314 }
315 if (num_true_literals > 1) {
316 context_->UpdateRuleStats("bool_xor: remove even number of true literals");
317 changed = true;
318 }
319 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
320 return changed;
321}
322
323bool CpModelPresolver::PresolveBoolOr(ConstraintProto* ct) {
324 if (context_->ModelIsUnsat()) return false;
325
326 // Move the enforcement literal inside the clause if any. Note that we do not
327 // mark this as a change since the literal in the constraint are the same.
328 if (HasEnforcementLiteral(*ct)) {
329 context_->UpdateRuleStats("bool_or: removed enforcement literal");
330 for (const int literal : ct->enforcement_literal()) {
331 ct->mutable_bool_or()->add_literals(NegatedRef(literal));
332 }
333 ct->clear_enforcement_literal();
334 }
335
336 // Inspects the literals and deal with fixed ones.
337 //
338 // TODO(user): Because we remove literal on the first copy, maybe we can get
339 // rid of the set here. However we still need to be careful when remapping
340 // literals to their representatives.
341 bool changed = false;
342 context_->tmp_literals.clear();
343 context_->tmp_literal_set.clear();
344 for (const int literal : ct->bool_or().literals()) {
345 if (context_->LiteralIsFalse(literal)) {
346 changed = true;
347 continue;
348 }
349 if (context_->LiteralIsTrue(literal)) {
350 context_->UpdateRuleStats("bool_or: always true");
351 return RemoveConstraint(ct);
352 }
353 // We can just set the variable to true in this case since it is not
354 // used in any other constraint (note that we artificially bump the
355 // objective var usage by 1).
356 if (context_->VariableIsUniqueAndRemovable(literal)) {
357 context_->UpdateRuleStats("bool_or: singleton");
358 if (!context_->SetLiteralToTrue(literal)) return true;
359 return RemoveConstraint(ct);
360 }
361 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
362 context_->UpdateRuleStats("bool_or: always true");
363 return RemoveConstraint(ct);
364 }
365
366 if (context_->tmp_literal_set.contains(literal)) {
367 changed = true;
368 } else {
369 context_->tmp_literal_set.insert(literal);
370 context_->tmp_literals.push_back(literal);
371 }
372 }
373 context_->tmp_literal_set.clear();
374
375 if (context_->tmp_literals.empty()) {
376 context_->UpdateRuleStats("bool_or: empty");
377 return context_->NotifyThatModelIsUnsat();
378 }
379 if (context_->tmp_literals.size() == 1) {
380 context_->UpdateRuleStats("bool_or: only one literal");
381 if (!context_->SetLiteralToTrue(context_->tmp_literals[0])) return true;
382 return RemoveConstraint(ct);
383 }
384 if (context_->tmp_literals.size() == 2) {
385 // For consistency, we move all "implication" into half-reified bool_and.
386 // TODO(user): merge by enforcement literal and detect implication cycles.
387 context_->UpdateRuleStats("bool_or: implications");
388 ct->add_enforcement_literal(NegatedRef(context_->tmp_literals[0]));
389 ct->mutable_bool_and()->add_literals(context_->tmp_literals[1]);
390 return changed;
391 }
392
393 if (changed) {
394 context_->UpdateRuleStats("bool_or: fixed literals");
395 ct->mutable_bool_or()->mutable_literals()->Clear();
396 for (const int lit : context_->tmp_literals) {
397 ct->mutable_bool_or()->add_literals(lit);
398 }
399 }
400 return changed;
401}
402
403// Note this function does not update the constraint graph. It assumes this is
404// done elsewhere.
405ABSL_MUST_USE_RESULT bool CpModelPresolver::MarkConstraintAsFalse(
406 ConstraintProto* ct) {
407 if (HasEnforcementLiteral(*ct)) {
408 // Change the constraint to a bool_or.
409 ct->mutable_bool_or()->clear_literals();
410 for (const int lit : ct->enforcement_literal()) {
411 ct->mutable_bool_or()->add_literals(NegatedRef(lit));
412 }
413 ct->clear_enforcement_literal();
414 PresolveBoolOr(ct);
415 return true;
416 } else {
417 return context_->NotifyThatModelIsUnsat();
418 }
419}
420
421bool CpModelPresolver::PresolveBoolAnd(ConstraintProto* ct) {
422 if (context_->ModelIsUnsat()) return false;
423
424 if (!HasEnforcementLiteral(*ct)) {
425 context_->UpdateRuleStats("bool_and: non-reified.");
426 for (const int literal : ct->bool_and().literals()) {
427 if (!context_->SetLiteralToTrue(literal)) return true;
428 }
429 return RemoveConstraint(ct);
430 }
431
432 bool changed = false;
433 context_->tmp_literals.clear();
434 context_->tmp_literal_set.clear();
435 const absl::flat_hash_set<int> enforcement_literals_set(
436 ct->enforcement_literal().begin(), ct->enforcement_literal().end());
437 for (const int literal : ct->bool_and().literals()) {
438 if (context_->LiteralIsFalse(literal)) {
439 context_->UpdateRuleStats("bool_and: always false");
440 return MarkConstraintAsFalse(ct);
441 }
442 if (context_->LiteralIsTrue(literal)) {
443 changed = true;
444 continue;
445 }
446 if (enforcement_literals_set.contains(literal)) {
447 context_->UpdateRuleStats("bool_and: x => x");
448 changed = true;
449 continue;
450 }
451 if (enforcement_literals_set.contains(NegatedRef(literal))) {
452 context_->UpdateRuleStats("bool_and: x => not x");
453 return MarkConstraintAsFalse(ct);
454 }
455 if (context_->VariableIsUniqueAndRemovable(literal)) {
456 // This is a "dual" reduction.
457 changed = true;
458 context_->UpdateRuleStats(
459 "bool_and: setting unused literal in rhs to true");
460 if (!context_->SetLiteralToTrue(literal)) return true;
461 continue;
462 }
463
464 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
465 context_->UpdateRuleStats("bool_and: cannot be enforced");
466 return MarkConstraintAsFalse(ct);
467 }
468
469 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
470 if (inserted) {
471 context_->tmp_literals.push_back(literal);
472 } else {
473 changed = true;
474 context_->UpdateRuleStats("bool_and: removed duplicate literal");
475 }
476 }
477
478 // Note that this is not the same behavior as a bool_or:
479 // - bool_or means "at least one", so it is false if empty.
480 // - bool_and means "all literals inside true", so it is true if empty.
481 if (context_->tmp_literals.empty()) return RemoveConstraint(ct);
482
483 if (changed) {
484 ct->mutable_bool_and()->mutable_literals()->Clear();
485 for (const int lit : context_->tmp_literals) {
486 ct->mutable_bool_and()->add_literals(lit);
487 }
488 context_->UpdateRuleStats("bool_and: fixed literals");
489 }
490
491 // If a variable can move freely in one direction except for this constraint,
492 // we can make it an equality.
493 //
494 // TODO(user): also consider literal on the other side of the =>.
495 if (ct->enforcement_literal().size() == 1 &&
496 ct->bool_and().literals().size() == 1) {
497 const int enforcement = ct->enforcement_literal(0);
498 if (context_->VariableWithCostIsUniqueAndRemovable(enforcement)) {
499 int var = PositiveRef(enforcement);
500 int64_t obj_coeff = context_->ObjectiveMap().at(var);
501 if (!RefIsPositive(enforcement)) obj_coeff = -obj_coeff;
502
503 // The other case where the constraint is redundant is treated elsewhere.
504 if (obj_coeff < 0) {
505 context_->UpdateRuleStats("bool_and: dual equality.");
506 // Extending `ct` = "enforcement => implied_literal" to an equality can
507 // break the hint only if hint(implied_literal) = 1 and
508 // hint(enforcement) = 0. But in this case the `enforcement` hint can be
509 // increased to 1 to preserve the hint feasibility.
510 const int implied_literal = ct->bool_and().literals(0);
511 solution_crush_.SetLiteralToValueIf(enforcement, true, implied_literal);
512 if (!context_->StoreBooleanEqualityRelation(enforcement,
513 implied_literal)) {
514 return false;
515 }
516 }
517 }
518 }
519
520 return changed;
521}
522
523bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto* ct) {
524 bool is_at_most_one = ct->constraint_case() == ConstraintProto::kAtMostOne;
525 const std::string name = is_at_most_one ? "at_most_one: " : "exactly_one: ";
526 auto* literals = is_at_most_one
527 ? ct->mutable_at_most_one()->mutable_literals()
528 : ct->mutable_exactly_one()->mutable_literals();
529
530 // Having a canonical constraint is needed for duplicate detection.
531 // This also change how we regroup bool_and.
532 std::sort(literals->begin(), literals->end());
533
534 // Deal with duplicate variable reference.
535 context_->tmp_literal_set.clear();
536 for (const int literal : *literals) {
537 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
538 if (!inserted) {
539 if (!context_->SetLiteralToFalse(literal)) return false;
540 context_->UpdateRuleStats(absl::StrCat(name, "duplicate literals"));
541 }
542 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
543 int num_positive = 0;
544 int num_negative = 0;
545 for (const int other : *literals) {
546 if (PositiveRef(other) != PositiveRef(literal)) {
547 if (!context_->SetLiteralToFalse(other)) return false;
548 context_->UpdateRuleStats(absl::StrCat(name, "x and not(x)"));
549 } else {
550 if (other == literal) {
551 ++num_positive;
552 } else {
553 ++num_negative;
554 }
555 }
556 }
557
558 // This is tricky for the case where the at most one reduce to (lit,
559 // not(lit), not(lit)) for instance.
560 if (num_positive > 1 && !context_->SetLiteralToFalse(literal)) {
561 return false;
562 }
563 if (num_negative > 1 && !context_->SetLiteralToTrue(literal)) {
564 return false;
565 }
566 return RemoveConstraint(ct);
567 }
568 }
569
570 // We can always remove all singleton variables (with or without cost) in an
571 // at_most_one or exactly one. We collect them and deal with this at the end.
572 std::vector<std::pair<int, int64_t>> singleton_literal_with_cost;
573
574 // Remove fixed variables.
575 bool changed = false;
576 context_->tmp_literals.clear();
577 for (const int literal : *literals) {
578 if (context_->LiteralIsTrue(literal)) {
579 context_->UpdateRuleStats(absl::StrCat(name, "satisfied"));
580 for (const int other : *literals) {
581 if (other != literal) {
582 if (!context_->SetLiteralToFalse(other)) return false;
583 }
584 }
585 return RemoveConstraint(ct);
586 }
587
588 if (context_->LiteralIsFalse(literal)) {
589 changed = true;
590 continue;
591 }
592
593 // A singleton variable with or without cost can be removed. See below.
594 if (context_->VariableIsUniqueAndRemovable(literal)) {
595 singleton_literal_with_cost.push_back({literal, 0});
596 continue;
597 }
598 if (context_->VariableWithCostIsUniqueAndRemovable(literal)) {
599 const auto it = context_->ObjectiveMap().find(PositiveRef(literal));
600 DCHECK(it != context_->ObjectiveMap().end());
601 if (RefIsPositive(literal)) {
602 singleton_literal_with_cost.push_back({literal, it->second});
603 } else {
604 // Note that we actually just store the objective change if this literal
605 // is true compared to it being false.
606 singleton_literal_with_cost.push_back({literal, -it->second});
607 }
608 continue;
609 }
610
611 context_->tmp_literals.push_back(literal);
612 }
613
614 bool transform_to_at_most_one = false;
615 if (!singleton_literal_with_cost.empty()) {
616 changed = true;
617
618 // By domination argument, we can fix to false everything but the minimum.
619 if (singleton_literal_with_cost.size() > 1) {
620 std::stable_sort(
621 singleton_literal_with_cost.begin(),
622 singleton_literal_with_cost.end(),
623 [](const std::pair<int, int64_t>& a,
624 const std::pair<int, int64_t>& b) { return a.second < b.second; });
625 for (int i = 1; i < singleton_literal_with_cost.size(); ++i) {
626 context_->UpdateRuleStats("at_most_one: dominated singleton");
627 if (!context_->SetLiteralToFalse(
628 singleton_literal_with_cost[i].first)) {
629 return false;
630 }
631 }
632 singleton_literal_with_cost.resize(1);
633 }
634
635 const int literal = singleton_literal_with_cost[0].first;
636 const int64_t literal_cost = singleton_literal_with_cost[0].second;
637 if (is_at_most_one && literal_cost >= 0) {
638 // We can just always set it to false in this case.
639 context_->UpdateRuleStats("at_most_one: singleton");
640 if (!context_->SetLiteralToFalse(literal)) return false;
641 } else if (context_->ShiftCostInExactlyOne(*literals, literal_cost)) {
642 // We can make the constraint an exactly one if needed since it is always
643 // beneficial to set this literal to true if everything else is zero. Now
644 // that we have an exactly one, we can transfer the cost to the other
645 // terms. The objective of literal should become zero, and we can then
646 // decide its value at postsolve and just have an at most one on the other
647 // literals.
648 DCHECK(!context_->ObjectiveMap().contains(PositiveRef(literal)));
649
650 if (!is_at_most_one) transform_to_at_most_one = true;
651 is_at_most_one = true;
652
653 context_->UpdateRuleStats("exactly_one: singleton");
654 context_->MarkVariableAsRemoved(PositiveRef(literal));
655
656 // Put a constraint in the mapping proto for postsolve.
657 auto* mapping_exo = context_->NewMappingConstraint(__FILE__, __LINE__)
658 ->mutable_exactly_one();
659 for (const int lit : context_->tmp_literals) {
660 mapping_exo->add_literals(lit);
661 }
662 mapping_exo->add_literals(literal);
663 }
664 }
665
666 if (!is_at_most_one && !transform_to_at_most_one &&
667 context_->ExploitExactlyOneInObjective(context_->tmp_literals)) {
668 context_->UpdateRuleStats("exactly_one: simplified objective");
669 }
670
671 if (transform_to_at_most_one) {
672 CHECK(changed);
673 ct->Clear();
674 literals = ct->mutable_at_most_one()->mutable_literals();
675 }
676 if (changed) {
677 literals->Clear();
678 for (const int lit : context_->tmp_literals) {
679 literals->Add(lit);
680 }
681 context_->UpdateRuleStats(absl::StrCat(name, "removed literals"));
682 }
683 return changed;
684}
685
686bool CpModelPresolver::PresolveAtMostOne(ConstraintProto* ct) {
687 if (context_->ModelIsUnsat()) return false;
688
689 CHECK(!HasEnforcementLiteral(*ct));
690 const bool changed = PresolveAtMostOrExactlyOne(ct);
691 if (ct->constraint_case() != ConstraintProto::kAtMostOne) return changed;
692
693 // Size zero: ok.
694 const auto& literals = ct->at_most_one().literals();
695 if (literals.empty()) {
696 context_->UpdateRuleStats("at_most_one: empty or all false");
697 return RemoveConstraint(ct);
698 }
699
700 // Size one: always satisfied.
701 if (literals.size() == 1) {
702 context_->UpdateRuleStats("at_most_one: size one");
703 return RemoveConstraint(ct);
704 }
705
706 return changed;
707}
708
709bool CpModelPresolver::PresolveExactlyOne(ConstraintProto* ct) {
710 if (context_->ModelIsUnsat()) return false;
711 CHECK(!HasEnforcementLiteral(*ct));
712 const bool changed = PresolveAtMostOrExactlyOne(ct);
713 if (ct->constraint_case() != ConstraintProto::kExactlyOne) return changed;
714
715 // Size zero: UNSAT.
716 const auto& literals = ct->exactly_one().literals();
717 if (literals.empty()) {
718 return context_->NotifyThatModelIsUnsat("exactly_one: empty or all false");
719 }
720
721 // Size one: fix variable.
722 if (literals.size() == 1) {
723 context_->UpdateRuleStats("exactly_one: size one");
724 if (!context_->SetLiteralToTrue(literals[0])) return false;
725 return RemoveConstraint(ct);
726 }
727
728 // Size two: Equivalence.
729 if (literals.size() == 2) {
730 context_->UpdateRuleStats("exactly_one: size two");
731 if (!context_->StoreBooleanEqualityRelation(literals[0],
732 NegatedRef(literals[1]))) {
733 return false;
734 }
735 return RemoveConstraint(ct);
736 }
737
738 return changed;
739}
740
741bool CpModelPresolver::CanonicalizeLinearArgument(const ConstraintProto& ct,
742 LinearArgumentProto* proto) {
743 if (context_->ModelIsUnsat()) return false;
744
745 // Canonicalize all involved expression.
746 bool changed = CanonicalizeLinearExpression(ct, proto->mutable_target());
747 for (LinearExpressionProto& exp : *(proto->mutable_exprs())) {
748 changed |= CanonicalizeLinearExpression(ct, &exp);
749 }
750 return changed;
751}
752
753// Deal with X = lin_max(exprs) where all exprs are divisible by gcd.
754// X must be divisible also, and we can divide everything.
755bool CpModelPresolver::DivideLinMaxByGcd(int c, ConstraintProto* ct) {
756 LinearArgumentProto* lin_max = ct->mutable_lin_max();
757
758 // Compute gcd of exprs first.
759 int64_t gcd = 0;
760 for (const LinearExpressionProto& expr : lin_max->exprs()) {
761 gcd = LinearExpressionGcd(expr, gcd);
762 if (gcd == 1) break;
763 }
764 if (gcd <= 1) return true;
765
766 // TODO(user): deal with all UNSAT case.
767 // Also if the target is affine, we can canonicalize it.
768 const LinearExpressionProto& target = lin_max->target();
769 const int64_t old_gcd = gcd;
770 gcd = LinearExpressionGcd(target, gcd);
771 if (gcd != old_gcd) {
772 if (target.vars().empty()) {
773 return context_->NotifyThatModelIsUnsat("infeasible lin_max");
774 }
775
776 // If the target is affine, we can solve the diophantine equation and
777 // express the target in term of a new variable.
778 if (target.vars().size() == 1) {
779 gcd = old_gcd;
780 context_->UpdateRuleStats("lin_max: canonicalize target using gcd");
781 if (!context_->CanonicalizeAffineVariable(
782 target.vars(0), target.coeffs(0), gcd, -target.offset())) {
783 return false;
784 }
785 CanonicalizeLinearExpression(*ct, lin_max->mutable_target());
786 context_->UpdateConstraintVariableUsage(c);
787 CHECK_EQ(LinearExpressionGcd(target, gcd), gcd);
788 } else {
789 context_->UpdateRuleStats(
790 "TODO lin_max: lhs not trivially divisible by rhs gcd");
791 }
792 }
793 if (gcd <= 1) return true;
794
795 context_->UpdateRuleStats("lin_max: divising by gcd");
796 DivideLinearExpression(gcd, lin_max->mutable_target());
797 for (LinearExpressionProto& expr : *lin_max->mutable_exprs()) {
798 DivideLinearExpression(gcd, &expr);
799 }
800 return true;
801}
802
803namespace {
804
805int64_t EvaluateSingleVariableExpression(const LinearExpressionProto& expr,
806 int var, int64_t value) {
807 int64_t result = expr.offset();
808 for (int i = 0; i < expr.vars().size(); ++i) {
809 CHECK_EQ(expr.vars(i), var);
810 result += expr.coeffs(i) * value;
811 }
812 return result;
813}
814
815template <class ExpressionList>
816int GetFirstVar(ExpressionList exprs) {
817 for (const LinearExpressionProto& expr : exprs) {
818 for (const int var : expr.vars()) {
819 DCHECK(RefIsPositive(var));
820 return var;
821 }
822 }
823 return -1;
824}
825
826bool IsAffineIntAbs(const ConstraintProto& ct) {
827 if (ct.constraint_case() != ConstraintProto::kLinMax ||
828 ct.lin_max().exprs_size() != 2 || ct.lin_max().target().vars_size() > 1 ||
829 ct.lin_max().exprs(0).vars_size() != 1 ||
830 ct.lin_max().exprs(1).vars_size() != 1) {
831 return false;
832 }
833
834 const LinearArgumentProto& lin_max = ct.lin_max();
835 if (lin_max.exprs(0).offset() != -lin_max.exprs(1).offset()) return false;
836 if (PositiveRef(lin_max.exprs(0).vars(0)) !=
837 PositiveRef(lin_max.exprs(1).vars(0))) {
838 return false;
839 }
840
841 const int64_t left_coeff = RefIsPositive(lin_max.exprs(0).vars(0))
842 ? lin_max.exprs(0).coeffs(0)
843 : -lin_max.exprs(0).coeffs(0);
844 const int64_t right_coeff = RefIsPositive(lin_max.exprs(1).vars(0))
845 ? lin_max.exprs(1).coeffs(0)
846 : -lin_max.exprs(1).coeffs(0);
847 return left_coeff == -right_coeff;
848}
849
850} // namespace
851
852bool CpModelPresolver::PropagateAndReduceAffineMax(ConstraintProto* ct) {
853 // Get the unique variable appearing in the expressions.
854 const int unique_var = GetFirstVar(ct->lin_max().exprs());
855
856 const auto& lin_max = ct->lin_max();
857 const int num_exprs = lin_max.exprs_size();
858 const auto& target = lin_max.target();
859 std::vector<int> num_wins(num_exprs, 0);
860 std::vector<int64_t> reachable_target_values;
861 std::vector<int64_t> valid_variable_values;
862 std::vector<int64_t> tmp_values(num_exprs);
863
864 const bool target_has_same_unique_var =
865 target.vars_size() == 1 && target.vars(0) == unique_var;
866
867 CHECK_LE(context_->DomainOf(unique_var).Size(), 1000);
868
869 for (const int64_t value : context_->DomainOf(unique_var).Values()) {
870 int64_t current_max = std::numeric_limits<int64_t>::min();
871
872 // Fill tmp_values and compute current_max;
873 for (int i = 0; i < num_exprs; ++i) {
874 const int64_t v =
875 EvaluateSingleVariableExpression(lin_max.exprs(i), unique_var, value);
876 current_max = std::max(current_max, v);
877 tmp_values[i] = v;
878 }
879
880 // Check if any expr produced a value compatible with the target.
881 if (!context_->DomainContains(target, current_max)) continue;
882
883 // Special case: affine(x) == max(exprs(x)). We can check if the affine()
884 // and the max(exprs) are compatible.
885 if (target_has_same_unique_var &&
886 EvaluateSingleVariableExpression(target, unique_var, value) !=
887 current_max) {
888 continue;
889 }
890
891 valid_variable_values.push_back(value);
892 reachable_target_values.push_back(current_max);
893 for (int i = 0; i < num_exprs; ++i) {
894 DCHECK_LE(tmp_values[i], current_max);
895 if (tmp_values[i] == current_max) {
896 num_wins[i]++;
897 }
898 }
899 }
900
901 if (reachable_target_values.empty() || valid_variable_values.empty()) {
902 context_->UpdateRuleStats("lin_max: infeasible affine_max constraint");
903 return MarkConstraintAsFalse(ct);
904 }
905
906 {
907 bool reduced = false;
908 if (!context_->IntersectDomainWith(
909 target, Domain::FromValues(reachable_target_values), &reduced)) {
910 return true;
911 }
912 if (reduced) {
913 context_->UpdateRuleStats("lin_max: affine_max target domain reduced");
914 }
915 }
916
917 {
918 bool reduced = false;
919 if (!context_->IntersectDomainWith(
920 unique_var, Domain::FromValues(valid_variable_values), &reduced)) {
921 return true;
922 }
923 if (reduced) {
924 context_->UpdateRuleStats(
925 "lin_max: unique affine_max var domain reduced");
926 }
927 }
928
929 // If one expression always wins, even tied, we can eliminate all the others.
930 for (int i = 0; i < num_exprs; ++i) {
931 if (num_wins[i] == valid_variable_values.size()) {
932 const LinearExpressionProto winner_expr = lin_max.exprs(i);
933 ct->mutable_lin_max()->clear_exprs();
934 *ct->mutable_lin_max()->add_exprs() = winner_expr;
935 break;
936 }
937 }
938
939 bool changed = false;
940 if (ct->lin_max().exprs_size() > 1) {
941 int new_size = 0;
942 for (int i = 0; i < num_exprs; ++i) {
943 if (num_wins[i] == 0) continue;
944 *ct->mutable_lin_max()->mutable_exprs(new_size) = ct->lin_max().exprs(i);
945 new_size++;
946 }
947 if (new_size < ct->lin_max().exprs_size()) {
948 context_->UpdateRuleStats("lin_max: removed affine_max exprs");
949 google::protobuf::util::Truncate(ct->mutable_lin_max()->mutable_exprs(),
950 new_size);
951 changed = true;
952 }
953 }
954
955 if (context_->IsFixed(target)) {
956 context_->UpdateRuleStats("lin_max: fixed affine_max target");
957 return RemoveConstraint(ct);
958 }
959
960 if (target_has_same_unique_var) {
961 context_->UpdateRuleStats("lin_max: target_affine(x) = max(affine_i(x))");
962 return RemoveConstraint(ct);
963 }
964
965 // Remove the affine_max constraint if the target is removable and if domains
966 // have been propagated without loss. For now, we known that there is no loss
967 // if the target is a single ref. Since all the expression are affine, in this
968 // case we are fine.
969 if (ExpressionContainsSingleRef(target) &&
970 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
971 context_->MarkVariableAsRemoved(target.vars(0));
972 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
973 context_->UpdateRuleStats("lin_max: unused affine_max target");
974 return RemoveConstraint(ct);
975 }
976
977 return changed;
978}
979
980bool CpModelPresolver::PropagateAndReduceLinMax(ConstraintProto* ct) {
981 const LinearExpressionProto& target = ct->lin_max().target();
982
983 // Compute the infered min/max of the target.
984 // Update target domain (if it is not a complex expression).
985 {
986 int64_t infered_min = context_->MinOf(target);
987 int64_t infered_max = std::numeric_limits<int64_t>::min();
988 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
989 infered_min = std::max(infered_min, context_->MinOf(expr));
990 infered_max = std::max(infered_max, context_->MaxOf(expr));
991 }
992
993 if (target.vars().empty()) {
994 if (!Domain(infered_min, infered_max).Contains(target.offset())) {
995 context_->UpdateRuleStats("lin_max: infeasible");
996 return MarkConstraintAsFalse(ct);
997 }
998 }
999 if (target.vars().size() <= 1) { // Affine
1000 Domain rhs_domain;
1001 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1002 rhs_domain = rhs_domain.UnionWith(
1003 context_->DomainSuperSetOf(expr).IntersectionWith(
1004 {infered_min, infered_max}));
1005 }
1006 bool reduced = false;
1007 if (!context_->IntersectDomainWith(target, rhs_domain, &reduced)) {
1008 return true;
1009 }
1010 if (reduced) {
1011 context_->UpdateRuleStats("lin_max: target domain reduced");
1012 }
1013 }
1014 }
1015
1016 // Filter the expressions which are smaller than target_min.
1017 const int64_t target_min = context_->MinOf(target);
1018 bool changed = false;
1019 {
1020 // If one expression is >= target_min,
1021 // We can remove all the expression <= target min.
1022 //
1023 // Note that we must keep an expression >= target_min though, for corner
1024 // case like [2,3] = max([2], [0][3]);
1025 bool has_greater_or_equal_to_target_min = false;
1026 int64_t max_at_index_to_keep = std::numeric_limits<int64_t>::min();
1027 int index_to_keep = -1;
1028 for (int i = 0; i < ct->lin_max().exprs_size(); ++i) {
1029 const LinearExpressionProto& expr = ct->lin_max().exprs(i);
1030 if (context_->MinOf(expr) >= target_min) {
1031 const int64_t expr_max = context_->MaxOf(expr);
1032 if (expr_max > max_at_index_to_keep) {
1033 max_at_index_to_keep = expr_max;
1034 index_to_keep = i;
1035 }
1036 has_greater_or_equal_to_target_min = true;
1037 }
1038 }
1039
1040 int new_size = 0;
1041 for (int i = 0; i < ct->lin_max().exprs_size(); ++i) {
1042 const LinearExpressionProto& expr = ct->lin_max().exprs(i);
1043 const int64_t expr_max = context_->MaxOf(expr);
1044 // TODO(user): Also remove expression whose domain is incompatible with
1045 // the target even if the bounds are like [2] and [0][3]?
1046 if (expr_max < target_min) continue;
1047 if (expr_max == target_min && has_greater_or_equal_to_target_min &&
1048 i != index_to_keep) {
1049 continue;
1050 }
1051 *ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
1052 new_size++;
1053 }
1054 if (new_size < ct->lin_max().exprs_size()) {
1055 context_->UpdateRuleStats("lin_max: removed exprs");
1056 google::protobuf::util::Truncate(ct->mutable_lin_max()->mutable_exprs(),
1057 new_size);
1058 changed = true;
1059 }
1060 }
1061
1062 return changed;
1063}
1064
1065bool CpModelPresolver::PresolveLinMax(int c, ConstraintProto* ct) {
1066 if (context_->ModelIsUnsat()) return false;
1067 if (HasEnforcementLiteral(*ct)) return false;
1068 const LinearExpressionProto& target = ct->lin_max().target();
1069
1070 // x = max(x, xi...) => forall i, x >= xi.
1071 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1072 if (LinearExpressionProtosAreEqual(expr, target)) {
1073 for (const LinearExpressionProto& e : ct->lin_max().exprs()) {
1074 if (LinearExpressionProtosAreEqual(e, target)) continue;
1075 LinearConstraintProto* prec =
1076 context_->working_model->add_constraints()->mutable_linear();
1077 prec->add_domain(0);
1078 prec->add_domain(std::numeric_limits<int64_t>::max());
1081 }
1082 context_->UpdateRuleStats("lin_max: x = max(x, ...)");
1083 return RemoveConstraint(ct);
1084 }
1085 }
1086
1087 const bool is_one_var_affine_max =
1088 ExpressionsContainsOnlyOneVar(ct->lin_max().exprs()) &&
1089 ct->lin_max().target().vars_size() <= 1;
1090 bool unique_var_is_small_enough = false;
1091 const bool is_int_abs = IsAffineIntAbs(*ct);
1092
1093 if (is_one_var_affine_max) {
1094 const int unique_var = GetFirstVar(ct->lin_max().exprs());
1095 unique_var_is_small_enough = context_->DomainOf(unique_var).Size() <= 1000;
1096 }
1097
1098 // This is a test.12y
1099
1100 bool changed;
1101 if (is_one_var_affine_max && unique_var_is_small_enough) {
1102 changed = PropagateAndReduceAffineMax(ct);
1103 } else if (is_int_abs) {
1104 changed = PropagateAndReduceIntAbs(ct);
1105 } else {
1106 changed = PropagateAndReduceLinMax(ct);
1107 }
1108
1109 if (context_->ModelIsUnsat()) return false;
1110 if (ct->constraint_case() != ConstraintProto::kLinMax) {
1111 // The constraint was removed by the propagate helpers.
1112 return changed;
1113 }
1114
1115 if (ct->lin_max().exprs().empty()) {
1116 context_->UpdateRuleStats("lin_max: no exprs");
1117 return MarkConstraintAsFalse(ct);
1118 }
1119
1120 // If only one is left, we can convert to an equality. Note that we create a
1121 // new constraint otherwise it might not be processed again.
1122 if (ct->lin_max().exprs().size() == 1) {
1123 context_->UpdateRuleStats("lin_max: converted to equality");
1124 ConstraintProto* new_ct = context_->working_model->add_constraints();
1125 *new_ct = *ct; // copy name and potential reification.
1126 auto* arg = new_ct->mutable_linear();
1127 const LinearExpressionProto& a = ct->lin_max().target();
1128 const LinearExpressionProto& b = ct->lin_max().exprs(0);
1129 for (int i = 0; i < a.vars().size(); ++i) {
1130 arg->add_vars(a.vars(i));
1131 arg->add_coeffs(a.coeffs(i));
1132 }
1133 for (int i = 0; i < b.vars().size(); ++i) {
1134 arg->add_vars(b.vars(i));
1135 arg->add_coeffs(-b.coeffs(i));
1136 }
1137 arg->add_domain(b.offset() - a.offset());
1138 arg->add_domain(b.offset() - a.offset());
1139 context_->UpdateNewConstraintsVariableUsage();
1140 return RemoveConstraint(ct);
1141 }
1142
1143 if (!DivideLinMaxByGcd(c, ct)) return false;
1144
1145 // Cut everything above the max if possible.
1146 // If one of the linear expression has many term and is above the max, we
1147 // abort early since none of the other rule can be applied.
1148 const int64_t target_min = context_->MinOf(target);
1149 const int64_t target_max = context_->MaxOf(target);
1150 {
1151 bool abort = false;
1152 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1153 const int64_t value_min = context_->MinOf(expr);
1154 bool modified = false;
1155 if (!context_->IntersectDomainWith(expr, Domain(value_min, target_max),
1156 &modified)) {
1157 return true;
1158 }
1159 if (modified) {
1160 context_->UpdateRuleStats("lin_max: reduced expression domain.");
1161 }
1162 const int64_t value_max = context_->MaxOf(expr);
1163 if (value_max > target_max) {
1164 context_->UpdateRuleStats("TODO lin_max: linear expression above max.");
1165 abort = true;
1166 }
1167 }
1168 if (abort) return changed;
1169 }
1170
1171 // Checks if the affine target domain is constraining.
1172 bool linear_target_domain_contains_max_domain = false;
1173 if (ExpressionContainsSingleRef(target)) { // target = +/- var.
1174 int64_t infered_min = std::numeric_limits<int64_t>::min();
1175 int64_t infered_max = std::numeric_limits<int64_t>::min();
1176 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1177 infered_min = std::max(infered_min, context_->MinOf(expr));
1178 infered_max = std::max(infered_max, context_->MaxOf(expr));
1179 }
1180 Domain rhs_domain;
1181 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1182 rhs_domain = rhs_domain.UnionWith(
1183 context_->DomainSuperSetOf(expr).IntersectionWith(
1184 {infered_min, infered_max}));
1185 }
1186
1187 // Checks if all values from the max(exprs) belong in the domain of the
1188 // target.
1189 // Note that the target is +/-var.
1190 DCHECK_EQ(std::abs(target.coeffs(0)), 1);
1191 const Domain target_domain =
1192 target.coeffs(0) == 1 ? context_->DomainOf(target.vars(0))
1193 : context_->DomainOf(target.vars(0)).Negation();
1194 linear_target_domain_contains_max_domain =
1195 rhs_domain.IsIncludedIn(target_domain);
1196 }
1197
1198 // Avoid to remove the constraint for special cases:
1199 // affine(x) = max(expr(x, ...), ...);
1200 //
1201 // TODO(user): We could presolve this, but there are a few type of cases.
1202 // for example:
1203 // - x = max(x + 3, ...) : infeasible.
1204 // - x = max(x - 2, ...) : reduce arity: x = max(...)
1205 // - x = max(2x, ...) we have x <= 0
1206 // - etc...
1207 // Actually, I think for the expr=affine' case, it reduces to:
1208 // affine(x) >= affine'(x)
1209 // affine(x) = max(...);
1210 if (linear_target_domain_contains_max_domain) {
1211 const int target_var = target.vars(0);
1212 bool abort = false;
1213 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1214 for (const int var : expr.vars()) {
1215 if (var == target_var &&
1216 !LinearExpressionProtosAreEqual(expr, target)) {
1217 abort = true;
1218 break;
1219 }
1220 }
1221 if (abort) break;
1222 }
1223 if (abort) {
1224 // Actually the expression can be more than affine.
1225 // We only know that the target is affine here.
1226 context_->UpdateRuleStats(
1227 "TODO lin_max: affine(x) = max(affine'(x), ...) !!");
1228 linear_target_domain_contains_max_domain = false;
1229 }
1230 }
1231
1232 // If the target is not used, and safe, we can remove the constraint.
1233 if (linear_target_domain_contains_max_domain &&
1234 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
1235 context_->UpdateRuleStats("lin_max: unused affine target");
1236 context_->MarkVariableAsRemoved(target.vars(0));
1237 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1238 return RemoveConstraint(ct);
1239 }
1240
1241 // If the target is only used in the objective, and safe, we can simplify the
1242 // constraint.
1243 if (linear_target_domain_contains_max_domain &&
1244 context_->VariableWithCostIsUniqueAndRemovable(target.vars(0)) &&
1245 (target.coeffs(0) > 0) ==
1246 (context_->ObjectiveCoeff(target.vars(0)) > 0)) {
1247 context_->UpdateRuleStats("lin_max: rewrite with precedences");
1248 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1249 LinearConstraintProto* prec =
1250 context_->working_model->add_constraints()->mutable_linear();
1251 prec->add_domain(0);
1252 prec->add_domain(std::numeric_limits<int64_t>::max());
1255 }
1256 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1257 return RemoveConstraint(ct);
1258 }
1259
1260 // Deal with fixed target case.
1261 if (target_min == target_max) {
1262 bool all_booleans = true;
1263 std::vector<int> literals;
1264 const int64_t fixed_target = target_min;
1265 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1266 const int64_t value_min = context_->MinOf(expr);
1267 const int64_t value_max = context_->MaxOf(expr);
1268 CHECK_LE(value_max, fixed_target) << "Presolved above";
1269 if (value_max < fixed_target) continue;
1270
1271 if (value_min == value_max && value_max == fixed_target) {
1272 context_->UpdateRuleStats("lin_max: always satisfied");
1273 return RemoveConstraint(ct);
1274 }
1275 if (context_->ExpressionIsAffineBoolean(expr)) {
1276 CHECK_EQ(value_max, fixed_target);
1277 literals.push_back(context_->LiteralForExpressionMax(expr));
1278 } else {
1279 all_booleans = false;
1280 }
1281 }
1282 if (all_booleans) {
1283 if (literals.empty()) {
1284 return MarkConstraintAsFalse(ct);
1285 }
1286
1287 // At least one true;
1288 context_->UpdateRuleStats("lin_max: fixed target and all booleans");
1289 for (const int lit : literals) {
1290 ct->mutable_bool_or()->add_literals(lit);
1291 }
1292 return true;
1293 }
1294 return changed;
1295 }
1296
1297 changed |= PresolveLinMaxWhenAllBoolean(ct);
1298 return changed;
1299}
1300
1301// If everything is Boolean and affine, do not use a lin max!
1302bool CpModelPresolver::PresolveLinMaxWhenAllBoolean(ConstraintProto* ct) {
1303 if (context_->ModelIsUnsat()) return false;
1304 if (HasEnforcementLiteral(*ct)) return false;
1305
1306 const LinearExpressionProto& target = ct->lin_max().target();
1307 if (!context_->ExpressionIsAffineBoolean(target)) return false;
1308
1309 const int64_t target_min = context_->MinOf(target);
1310 const int64_t target_max = context_->MaxOf(target);
1311 const int target_ref = context_->LiteralForExpressionMax(target);
1312
1313 bool min_is_reachable = false;
1314 std::vector<int> min_literals;
1315 std::vector<int> literals_above_min;
1316 std::vector<int> max_literals;
1317
1318 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1319 if (!context_->ExpressionIsAffineBoolean(expr)) return false;
1320 const int64_t value_min = context_->MinOf(expr);
1321 const int64_t value_max = context_->MaxOf(expr);
1322 const int ref = context_->LiteralForExpressionMax(expr);
1323
1324 // Get corner case out of the way, and wait for the constraint to be
1325 // processed again in these case.
1326 if (value_min > target_min) {
1327 context_->UpdateRuleStats("lin_max: fix target");
1328 (void)context_->SetLiteralToTrue(target_ref);
1329 return false;
1330 }
1331 if (value_max > target_max) {
1332 context_->UpdateRuleStats("lin_max: fix bool expr");
1333 (void)context_->SetLiteralToFalse(ref);
1334 return false;
1335 }
1336
1337 // expr is fixed.
1338 if (value_min == value_max) {
1339 if (value_min == target_min) min_is_reachable = true;
1340 continue;
1341 }
1342
1343 CHECK_LE(value_min, target_min);
1344 if (value_min == target_min) {
1345 min_literals.push_back(NegatedRef(ref));
1346 }
1347
1348 CHECK_LE(value_max, target_max);
1349 if (value_max == target_max) {
1350 max_literals.push_back(ref);
1351 literals_above_min.push_back(ref);
1352 } else if (value_max > target_min) {
1353 literals_above_min.push_back(ref);
1354 } else if (value_max == target_min) {
1355 min_literals.push_back(ref);
1356 }
1357 }
1358
1359 context_->UpdateRuleStats("lin_max: all Booleans.");
1360
1361 // target_ref => at_least_one(max_literals);
1362 ConstraintProto* clause = context_->working_model->add_constraints();
1363 clause->add_enforcement_literal(target_ref);
1364 clause->mutable_bool_or();
1365 for (const int lit : max_literals) {
1366 clause->mutable_bool_or()->add_literals(lit);
1367 }
1368
1369 // not(target_ref) => not(lit) for lit in literals_above_min
1370 for (const int lit : literals_above_min) {
1371 context_->AddImplication(lit, target_ref);
1372 }
1373
1374 if (!min_is_reachable) {
1375 // not(target_ref) => at_least_one(min_literals).
1376 ConstraintProto* clause = context_->working_model->add_constraints();
1377 clause->add_enforcement_literal(NegatedRef(target_ref));
1378 clause->mutable_bool_or();
1379 for (const int lit : min_literals) {
1380 clause->mutable_bool_or()->add_literals(lit);
1381 }
1382 }
1383
1384 context_->UpdateNewConstraintsVariableUsage();
1385 return RemoveConstraint(ct);
1386}
1387
1388// This presolve expect that the constraint only contains 1-var affine
1389// expressions.
1390bool CpModelPresolver::PropagateAndReduceIntAbs(ConstraintProto* ct) {
1391 CHECK_EQ(ct->enforcement_literal_size(), 0);
1392 if (context_->ModelIsUnsat()) return false;
1393 const LinearExpressionProto& target_expr = ct->lin_max().target();
1394 const LinearExpressionProto& expr = ct->lin_max().exprs(0);
1395 DCHECK_EQ(expr.vars_size(), 1);
1396
1397 // Propagate domain from the expression to the target.
1398 {
1399 const Domain expr_domain = context_->DomainSuperSetOf(expr);
1400 const Domain new_target_domain =
1401 expr_domain.UnionWith(expr_domain.Negation())
1402 .IntersectionWith({0, std::numeric_limits<int64_t>::max()});
1403 bool target_domain_modified = false;
1404 if (!context_->IntersectDomainWith(target_expr, new_target_domain,
1405 &target_domain_modified)) {
1406 return false;
1407 }
1408 if (expr_domain.IsFixed()) {
1409 context_->UpdateRuleStats("lin_max: fixed expression in int_abs");
1410 return RemoveConstraint(ct);
1411 }
1412 if (target_domain_modified) {
1413 context_->UpdateRuleStats("lin_max: propagate domain from x to abs(x)");
1414 }
1415 }
1416
1417 // Propagate from target domain to variable.
1418 {
1419 const Domain target_domain =
1420 context_->DomainSuperSetOf(target_expr)
1421 .IntersectionWith(Domain(0, std::numeric_limits<int64_t>::max()));
1422 const Domain new_expr_domain =
1423 target_domain.UnionWith(target_domain.Negation());
1424 bool expr_domain_modified = false;
1425 if (!context_->IntersectDomainWith(expr, new_expr_domain,
1426 &expr_domain_modified)) {
1427 return true;
1428 }
1429 // This is the only reason why we don't support fully generic linear
1430 // expression.
1431 if (context_->IsFixed(target_expr)) {
1432 context_->UpdateRuleStats("lin_max: fixed abs target");
1433 return RemoveConstraint(ct);
1434 }
1435 if (expr_domain_modified) {
1436 context_->UpdateRuleStats("lin_max: propagate domain from abs(x) to x");
1437 }
1438 }
1439
1440 // Convert to equality if the sign of expr is fixed.
1441 if (context_->MinOf(expr) >= 0) {
1442 context_->UpdateRuleStats("lin_max: converted abs to equality");
1443 ConstraintProto* new_ct = context_->working_model->add_constraints();
1444 new_ct->set_name(ct->name());
1445 auto* arg = new_ct->mutable_linear();
1446 arg->add_domain(0);
1447 arg->add_domain(0);
1448 AddLinearExpressionToLinearConstraint(target_expr, 1, arg);
1450 CanonicalizeLinear(new_ct);
1451 context_->UpdateNewConstraintsVariableUsage();
1452 return RemoveConstraint(ct);
1453 }
1454
1455 if (context_->MaxOf(expr) <= 0) {
1456 context_->UpdateRuleStats("lin_max: converted abs to equality");
1457 ConstraintProto* new_ct = context_->working_model->add_constraints();
1458 new_ct->set_name(ct->name());
1459 auto* arg = new_ct->mutable_linear();
1460 arg->add_domain(0);
1461 arg->add_domain(0);
1462 AddLinearExpressionToLinearConstraint(target_expr, 1, arg);
1464 CanonicalizeLinear(new_ct);
1465 context_->UpdateNewConstraintsVariableUsage();
1466 return RemoveConstraint(ct);
1467 }
1468
1469 // Remove the abs constraint if the target is removable and if domains have
1470 // been propagated without loss.
1471 // For now, we known that there is no loss if the target is a single ref.
1472 // Since all the expression are affine, in this case we are fine.
1473 if (ExpressionContainsSingleRef(target_expr) &&
1474 context_->VariableIsUniqueAndRemovable(target_expr.vars(0))) {
1475 context_->MarkVariableAsRemoved(target_expr.vars(0));
1476 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1477 context_->UpdateRuleStats("lin_max: unused abs target");
1478 return RemoveConstraint(ct);
1479 }
1480
1481 return false;
1482}
1483
1484Domain EvaluateImpliedIntProdDomain(const LinearArgumentProto& expr,
1485 const PresolveContext& context) {
1486 if (expr.exprs().size() == 2) {
1487 const LinearExpressionProto& expr0 = expr.exprs(0);
1488 const LinearExpressionProto& expr1 = expr.exprs(1);
1489 if (LinearExpressionProtosAreEqual(expr0, expr1)) {
1490 return context.DomainSuperSetOf(expr0).SquareSuperset();
1491 }
1492 if (expr0.vars().size() == 1 && expr1.vars().size() == 1 &&
1493 expr0.vars(0) == expr1.vars(0)) {
1494 return context.DomainOf(expr0.vars(0))
1495 .QuadraticSuperset(expr0.coeffs(0), expr0.offset(), expr1.coeffs(0),
1496 expr1.offset());
1497 }
1498 }
1499
1500 Domain implied(1);
1501 for (const LinearExpressionProto& expr : expr.exprs()) {
1502 implied =
1503 implied.ContinuousMultiplicationBy(context.DomainSuperSetOf(expr));
1504 }
1505 return implied;
1506}
1507
1508bool CpModelPresolver::PresolveIntProd(ConstraintProto* ct) {
1509 if (context_->ModelIsUnsat()) return false;
1510 if (HasEnforcementLiteral(*ct)) return false;
1511
1512 // Start by restricting the domain of target. We will be more precise later.
1513 bool domain_modified = false;
1514 Domain implied_domain =
1515 EvaluateImpliedIntProdDomain(ct->int_prod(), *context_);
1516 if (!context_->IntersectDomainWith(ct->int_prod().target(), implied_domain,
1517 &domain_modified)) {
1518 return false;
1519 }
1520
1521 // Remove a constraint if the target only appears in the constraint. For this
1522 // to be correct some conditions must be met:
1523 // - The target is an affine linear with coefficient -1 or 1.
1524 // - The target does not appear in the rhs (no x = (a*x + b) * ...).
1525 // - The target domain covers all the possible range of the rhs.
1526 if (ExpressionContainsSingleRef(ct->int_prod().target()) &&
1527 context_->VariableIsUniqueAndRemovable(ct->int_prod().target().vars(0)) &&
1528 std::abs(ct->int_prod().target().coeffs(0)) == 1) {
1529 const LinearExpressionProto& target = ct->int_prod().target();
1530 if (!absl::c_any_of(ct->int_prod().exprs(),
1531 [&target](const LinearExpressionProto& expr) {
1532 return absl::c_linear_search(expr.vars(),
1533 target.vars(0));
1534 })) {
1535 const Domain target_domain =
1536 Domain(target.offset())
1537 .AdditionWith(context_->DomainOf(target.vars(0)));
1538 if (implied_domain.IsIncludedIn(target_domain)) {
1539 context_->MarkVariableAsRemoved(ct->int_prod().target().vars(0));
1540 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1541 context_->UpdateRuleStats("int_prod: unused affine target");
1542 return RemoveConstraint(ct);
1543 }
1544 }
1545 }
1546
1547 // Remove constant expressions and compute the product of the max positive
1548 // divisor of each term.
1549 int64_t constant_factor = 1;
1550 int new_size = 0;
1551 bool changed = false;
1552 LinearArgumentProto* proto = ct->mutable_int_prod();
1553 for (int i = 0; i < ct->int_prod().exprs().size(); ++i) {
1554 LinearExpressionProto expr = ct->int_prod().exprs(i);
1555 if (context_->IsFixed(expr)) {
1556 const int64_t expr_value = context_->FixedValue(expr);
1557 constant_factor = CapProd(constant_factor, expr_value);
1558 context_->UpdateRuleStats("int_prod: removed constant expressions.");
1559 changed = true;
1560 } else {
1561 const int64_t expr_divisor = LinearExpressionGcd(expr);
1562 DivideLinearExpression(expr_divisor, &expr);
1563 constant_factor = CapProd(constant_factor, expr_divisor);
1564 *proto->mutable_exprs(new_size++) = expr;
1565 }
1566 }
1567 proto->mutable_exprs()->erase(proto->mutable_exprs()->begin() + new_size,
1568 proto->mutable_exprs()->end());
1569
1570 if (ct->int_prod().exprs().empty() || constant_factor == 0) {
1571 if (!context_->IntersectDomainWith(ct->int_prod().target(),
1572 Domain(constant_factor))) {
1573 return false;
1574 }
1575 context_->UpdateRuleStats("int_prod: constant product");
1576 return RemoveConstraint(ct);
1577 }
1578
1579 // If target is fixed to zero, we can forget the constant factor.
1580 if (context_->IsFixed(ct->int_prod().target()) &&
1581 context_->FixedValue(ct->int_prod().target()) == 0 &&
1582 constant_factor != 1) {
1583 context_->UpdateRuleStats("int_prod: simplify by constant factor");
1584 constant_factor = 1;
1585 }
1586
1587 // In this case, the only possible value that fit in the domains is zero.
1588 // We will check for UNSAT if zero is not achievable by the rhs below.
1589 if (AtMinOrMaxInt64(constant_factor)) {
1590 context_->UpdateRuleStats("int_prod: overflow if non zero");
1591 if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(0))) {
1592 return false;
1593 }
1594 constant_factor = 1;
1595 }
1596
1597 // Replace by linear if it cannot overflow.
1598 if (ct->int_prod().exprs().size() == 1) {
1599 LinearExpressionProto* const target =
1600 ct->mutable_int_prod()->mutable_target();
1601 LinearConstraintProto* const lin =
1602 context_->working_model->add_constraints()->mutable_linear();
1603
1604 if (context_->IsFixed(*target)) {
1605 int64_t target_value = context_->FixedValue(*target);
1606 if (target_value % constant_factor != 0) {
1607 return context_->NotifyThatModelIsUnsat(
1608 "int_prod: product incompatible with fixed target");
1609 }
1610 // expression == target_value / constant_factor.
1611 lin->add_domain(target_value / constant_factor);
1612 lin->add_domain(target_value / constant_factor);
1613 AddLinearExpressionToLinearConstraint(ct->int_prod().exprs(0), 1, lin);
1614 context_->UpdateNewConstraintsVariableUsage();
1615 context_->UpdateRuleStats("int_prod: expression is constant.");
1616 return RemoveConstraint(ct);
1617 }
1618
1619 const int64_t target_divisor = LinearExpressionGcd(*target);
1620
1621 // Reduce coefficients.
1622 const int64_t gcd =
1623 std::gcd(static_cast<uint64_t>(std::abs(constant_factor)),
1624 static_cast<uint64_t>(std::abs(target_divisor)));
1625 if (gcd != 1) {
1626 constant_factor /= gcd;
1627 DivideLinearExpression(gcd, target);
1628 }
1629
1630 // expression * constant_factor = target.
1631 lin->add_domain(0);
1632 lin->add_domain(0);
1633 const bool overflow = !SafeAddLinearExpressionToLinearConstraint(
1634 ct->int_prod().target(), 1, lin) ||
1636 ct->int_prod().exprs(0), -constant_factor, lin);
1637
1638 // Check for overflow.
1639 if (overflow ||
1640 PossibleIntegerOverflow(*context_->working_model, lin->vars(),
1641 lin->coeffs(), lin->domain(0))) {
1642 context_->working_model->mutable_constraints()->RemoveLast();
1643 // The constant factor will be handled by the creation of an affine
1644 // relation below.
1645 } else { // Replace with a linear equation.
1646 context_->UpdateNewConstraintsVariableUsage();
1647 context_->UpdateRuleStats("int_prod: linearize product by constant.");
1648 return RemoveConstraint(ct);
1649 }
1650 }
1651
1652 if (constant_factor != 1) {
1653 // Lets canonicalize the target by introducing a new variable if necessary.
1654 //
1655 // coeff * X + offset must be a multiple of constant_factor, so
1656 // we can rewrite X so that this property is clear.
1657 //
1658 // Note(user): it is important for this to have a restricted target domain
1659 // so we can choose a better representative.
1660 const LinearExpressionProto old_target = ct->int_prod().target();
1661 if (!context_->IsFixed(old_target)) {
1662 if (CapProd(constant_factor, std::max(context_->MaxOf(old_target),
1663 -context_->MinOf(old_target))) >=
1664 std::numeric_limits<int64_t>::max() / 2) {
1665 // Re-add a new term with the constant factor.
1666 ct->mutable_int_prod()->add_exprs()->set_offset(constant_factor);
1667 context_->UpdateRuleStats(
1668 "int_prod: overflow prevented creating a affine relation.");
1669 return true;
1670 }
1671 const int ref = old_target.vars(0);
1672 const int64_t coeff = old_target.coeffs(0);
1673 const int64_t offset = old_target.offset();
1674 if (!context_->CanonicalizeAffineVariable(ref, coeff, constant_factor,
1675 -offset)) {
1676 return false;
1677 }
1678 if (context_->IsFixed(ref)) {
1679 changed = true;
1680 }
1681 }
1682
1683 // This can happen during CanonicalizeAffineVariable().
1684 if (context_->IsFixed(old_target)) {
1685 const int64_t target_value = context_->FixedValue(old_target);
1686 if (target_value % constant_factor != 0) {
1687 return context_->NotifyThatModelIsUnsat(
1688 "int_prod: constant factor does not divide constant target");
1689 }
1690 changed = true;
1691 proto->clear_target();
1692 proto->mutable_target()->set_offset(target_value / constant_factor);
1693 context_->UpdateRuleStats(
1694 "int_prod: divide product and fixed target by constant factor");
1695 } else {
1696 // We use absl::int128 to be resistant to overflow here.
1697 const AffineRelation::Relation r =
1698 context_->GetAffineRelation(old_target.vars(0));
1699 const absl::int128 temp_coeff =
1700 absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff);
1701 CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0);
1702 const absl::int128 temp_offset =
1703 absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) +
1704 absl::int128(old_target.offset());
1705 CHECK_EQ(temp_offset % absl::int128(constant_factor), 0);
1706 const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor);
1707 const absl::int128 new_offset =
1708 temp_offset / absl::int128(constant_factor);
1709
1710 // TODO(user): We try to keep coeff/offset small, if this happens, it
1711 // probably means there is no feasible solution involving int64_t and that
1712 // do not causes overflow while evaluating it, but it is hard to be
1713 // exactly sure we are correct here since it depends on the evaluation
1714 // order. Similarly, by introducing intermediate variable we might loose
1715 // solution if this intermediate variable value do not fit on an int64_t.
1716 if (new_coeff > absl::int128(std::numeric_limits<int64_t>::max()) ||
1717 new_coeff < absl::int128(std::numeric_limits<int64_t>::min()) ||
1718 new_offset > absl::int128(std::numeric_limits<int64_t>::max()) ||
1719 new_offset < absl::int128(std::numeric_limits<int64_t>::min())) {
1720 return context_->NotifyThatModelIsUnsat(
1721 "int_prod: overflow during simplification.");
1722 }
1723
1724 // Rewrite the target.
1725 proto->mutable_target()->set_coeffs(0, static_cast<int64_t>(new_coeff));
1726 proto->mutable_target()->set_vars(0, r.representative);
1727 proto->mutable_target()->set_offset(static_cast<int64_t>(new_offset));
1728 context_->UpdateRuleStats("int_prod: divide product by constant factor");
1729 changed = true;
1730 }
1731 }
1732
1733 // Restrict the target domain if possible.
1734 implied_domain = EvaluateImpliedIntProdDomain(ct->int_prod(), *context_);
1735 const bool is_square = ct->int_prod().exprs_size() == 2 &&
1737 ct->int_prod().exprs(0), ct->int_prod().exprs(1));
1738 if (!context_->IntersectDomainWith(ct->int_prod().target(), implied_domain,
1739 &domain_modified)) {
1740 return false;
1741 }
1742 if (domain_modified) {
1743 context_->UpdateRuleStats(absl::StrCat(
1744 is_square ? "int_square" : "int_prod", ": reduced target domain."));
1745 }
1746
1747 // y = x * x, we can reduce the domain of x from the domain of y.
1748 if (is_square) {
1749 const int64_t target_max = context_->MaxOf(ct->int_prod().target());
1750 DCHECK_GE(target_max, 0);
1751 const int64_t sqrt_max = FloorSquareRoot(target_max);
1752 bool expr_reduced = false;
1753 if (!context_->IntersectDomainWith(ct->int_prod().exprs(0),
1754 {-sqrt_max, sqrt_max}, &expr_reduced)) {
1755 return false;
1756 }
1757 if (expr_reduced) {
1758 context_->UpdateRuleStats("int_square: reduced expr domain.");
1759 }
1760 }
1761
1762 if (ct->int_prod().exprs_size() == 2) {
1763 LinearExpressionProto a = ct->int_prod().exprs(0);
1764 LinearExpressionProto b = ct->int_prod().exprs(1);
1765 const LinearExpressionProto product = ct->int_prod().target();
1768 a, product)) { // x = x * x, only true for {0, 1}.
1769 if (!context_->IntersectDomainWith(product, Domain(0, 1))) {
1770 return false;
1771 }
1772 context_->UpdateRuleStats("int_square: fix variable to zero or one.");
1773 return RemoveConstraint(ct);
1774 }
1775 }
1776
1777 if (ct->int_prod().exprs().size() == 2) {
1778 const auto is_boolean_affine =
1779 [context = context_](const LinearExpressionProto& expr) {
1780 return expr.vars().size() == 1 && context->MinOf(expr.vars(0)) == 0 &&
1781 context->MaxOf(expr.vars(0)) == 1;
1782 };
1783 const LinearExpressionProto* boolean_linear = nullptr;
1784 const LinearExpressionProto* other_linear = nullptr;
1785 if (is_boolean_affine(ct->int_prod().exprs(0))) {
1786 boolean_linear = &ct->int_prod().exprs(0);
1787 other_linear = &ct->int_prod().exprs(1);
1788 } else if (is_boolean_affine(ct->int_prod().exprs(1))) {
1789 boolean_linear = &ct->int_prod().exprs(1);
1790 other_linear = &ct->int_prod().exprs(0);
1791 }
1792 if (boolean_linear) {
1793 // We have:
1794 // (u + b * v) * other_expr = B, where `b` is a boolean variable.
1795 //
1796 // We can rewrite this as:
1797 // u * other_expr = B, if b = false;
1798 // (u + v) * other_expr = B, if b = true
1799 ConstraintProto* constraint_for_false =
1800 context_->working_model->add_constraints();
1801 ConstraintProto* constraint_for_true =
1802 context_->working_model->add_constraints();
1803 constraint_for_true->add_enforcement_literal(boolean_linear->vars(0));
1804 constraint_for_false->add_enforcement_literal(
1805 NegatedRef(boolean_linear->vars(0)));
1806 LinearConstraintProto* linear_for_false =
1807 constraint_for_false->mutable_linear();
1808 LinearConstraintProto* linear_for_true =
1809 constraint_for_true->mutable_linear();
1810
1811 linear_for_false->add_domain(0);
1812 linear_for_false->add_domain(0);
1814 *other_linear, boolean_linear->offset(), linear_for_false);
1815 AddLinearExpressionToLinearConstraint(ct->int_prod().target(), -1,
1816 linear_for_false);
1817
1818 linear_for_true->add_domain(0);
1819 linear_for_true->add_domain(0);
1821 *other_linear, boolean_linear->offset() + boolean_linear->coeffs(0),
1822 linear_for_true);
1823 AddLinearExpressionToLinearConstraint(ct->int_prod().target(), -1,
1824 linear_for_true);
1825 context_->CanonicalizeLinearConstraint(constraint_for_false);
1826 context_->CanonicalizeLinearConstraint(constraint_for_true);
1827 if (PossibleIntegerOverflow(*context_->working_model,
1828 linear_for_false->vars(),
1829 linear_for_false->coeffs()) ||
1830 PossibleIntegerOverflow(*context_->working_model,
1831 linear_for_true->vars(),
1832 linear_for_true->coeffs())) {
1833 context_->working_model->mutable_constraints()->RemoveLast();
1834 context_->working_model->mutable_constraints()->RemoveLast();
1835 } else {
1836 context_->UpdateRuleStats("int_prod: boolean affine term");
1837 context_->UpdateNewConstraintsVariableUsage();
1838 return RemoveConstraint(ct);
1839 }
1840 }
1841 }
1842
1843 // For now, we only presolve the case where all variables are Booleans.
1844 const LinearExpressionProto target_expr = ct->int_prod().target();
1845 int target;
1846 if (!context_->ExpressionIsALiteral(target_expr, &target)) {
1847 return changed;
1848 }
1849 std::vector<int> literals;
1850 for (const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1851 int lit;
1852 if (!context_->ExpressionIsALiteral(expr, &lit)) {
1853 return changed;
1854 }
1855 literals.push_back(lit);
1856 }
1857
1858 // This is a Boolean constraint!
1859 context_->UpdateRuleStats("int_prod: all Boolean.");
1860 {
1861 ConstraintProto* new_ct = context_->working_model->add_constraints();
1862 new_ct->add_enforcement_literal(target);
1863 auto* arg = new_ct->mutable_bool_and();
1864 for (const int lit : literals) {
1865 arg->add_literals(lit);
1866 }
1867 }
1868 {
1869 ConstraintProto* new_ct = context_->working_model->add_constraints();
1870 auto* arg = new_ct->mutable_bool_or();
1871 arg->add_literals(target);
1872 for (const int lit : literals) {
1873 arg->add_literals(NegatedRef(lit));
1874 }
1875 }
1876 context_->UpdateNewConstraintsVariableUsage();
1877 return RemoveConstraint(ct);
1878}
1879
1880bool CpModelPresolver::PresolveIntDiv(int c, ConstraintProto* ct) {
1881 if (context_->ModelIsUnsat()) return false;
1882
1883 const LinearExpressionProto target = ct->int_div().target();
1884 const LinearExpressionProto expr = ct->int_div().exprs(0);
1885 const LinearExpressionProto div = ct->int_div().exprs(1);
1886
1887 if (LinearExpressionProtosAreEqual(expr, div)) {
1888 if (!context_->IntersectDomainWith(target, Domain(1))) {
1889 return false;
1890 }
1891 context_->UpdateRuleStats("int_div: y = x / x");
1892 return RemoveConstraint(ct);
1893 } else if (LinearExpressionProtosAreEqual(expr, div, -1)) {
1894 if (!context_->IntersectDomainWith(target, Domain(-1))) {
1895 return false;
1896 }
1897 context_->UpdateRuleStats("int_div: y = - x / x");
1898 return RemoveConstraint(ct);
1899 }
1900
1901 // Sometimes we have only a single variable appearing in the whole constraint.
1902 // If the domain is small enough, we can just restrict the domain and remove
1903 // the constraint.
1904 if (ct->enforcement_literal().empty() &&
1905 context_->ConstraintToVars(c).size() == 1) {
1906 const int var = context_->ConstraintToVars(c)[0];
1907 if (context_->DomainOf(var).Size() >= 100) {
1908 context_->UpdateRuleStats(
1909 "TODO int_div: single variable with large domain");
1910 } else {
1911 std::vector<int64_t> possible_values;
1912 for (const int64_t v : context_->DomainOf(var).Values()) {
1913 const int64_t target_v =
1914 EvaluateSingleVariableExpression(target, var, v);
1915 const int64_t expr_v = EvaluateSingleVariableExpression(expr, var, v);
1916 const int64_t div_v = EvaluateSingleVariableExpression(div, var, v);
1917 if (div_v == 0) continue;
1918 if (target_v == expr_v / div_v) {
1919 possible_values.push_back(v);
1920 }
1921 }
1922 (void)context_->IntersectDomainWith(var,
1923 Domain::FromValues(possible_values));
1924 context_->UpdateRuleStats("int_div: single variable");
1925 return RemoveConstraint(ct);
1926 }
1927 }
1928
1929 // For now, we only presolve the case where the divisor is constant.
1930 if (!context_->IsFixed(div)) return false;
1931
1932 const int64_t divisor = context_->FixedValue(div);
1933
1934 // Trivial case one: target = expr / +/-1.
1935 if (divisor == 1 || divisor == -1) {
1936 LinearConstraintProto* const lin =
1937 context_->working_model->add_constraints()->mutable_linear();
1938 lin->add_domain(0);
1939 lin->add_domain(0);
1941 AddLinearExpressionToLinearConstraint(target, -divisor, lin);
1942 context_->UpdateNewConstraintsVariableUsage();
1943 context_->UpdateRuleStats("int_div: rewrite to equality");
1944 return RemoveConstraint(ct);
1945 }
1946
1947 // Reduce the domain of target.
1948 {
1949 bool domain_modified = false;
1950 const Domain target_implied_domain =
1951 context_->DomainSuperSetOf(expr).DivisionBy(divisor);
1952
1953 if (!context_->IntersectDomainWith(target, target_implied_domain,
1954 &domain_modified)) {
1955 return false;
1956 }
1957 if (domain_modified) {
1958 // Note: the case target is fixed has been processed before.
1959 if (target_implied_domain.IsFixed()) {
1960 context_->UpdateRuleStats(
1961 "int_div: target has been fixed by propagating X / cte");
1962 } else {
1963 context_->UpdateRuleStats(
1964 "int_div: updated domain of target in target = X / cte");
1965 }
1966 }
1967 }
1968
1969 // Trivial case three: fixed_target = expr / fixed_divisor.
1970 if (context_->IsFixed(target) &&
1971 CapAdd(1, CapProd(std::abs(divisor),
1972 1 + std::abs(context_->FixedValue(target)))) !=
1973 std::numeric_limits<int64_t>::max()) {
1974 int64_t t = context_->FixedValue(target);
1975 int64_t d = divisor;
1976 if (d < 0) {
1977 t = -t;
1978 d = -d;
1979 }
1980
1981 const Domain expr_implied_domain =
1982 t > 0
1983 ? Domain(t * d, (t + 1) * d - 1)
1984 : (t == 0 ? Domain(1 - d, d - 1) : Domain((t - 1) * d + 1, t * d));
1985 bool domain_modified = false;
1986 if (!context_->IntersectDomainWith(expr, expr_implied_domain,
1987 &domain_modified)) {
1988 return false;
1989 }
1990 if (domain_modified) {
1991 context_->UpdateRuleStats("int_div: target and divisor are fixed");
1992 } else {
1993 context_->UpdateRuleStats("int_div: always true");
1994 }
1995 return RemoveConstraint(ct);
1996 }
1997
1998 // Linearize if everything is positive, and we have no overflow.
1999 // TODO(user): Deal with other cases where there is no change of
2000 // sign. We can also deal with target = cte, div variable.
2001 if (context_->MinOf(target) >= 0 && context_->MinOf(expr) >= 0 &&
2002 divisor > 1 &&
2003 CapProd(divisor, context_->MaxOf(target)) !=
2004 std::numeric_limits<int64_t>::max()) {
2005 LinearConstraintProto* const lin =
2006 context_->working_model->add_constraints()->mutable_linear();
2007 lin->add_domain(0);
2008 lin->add_domain(divisor - 1);
2010 AddLinearExpressionToLinearConstraint(target, -divisor, lin);
2011 context_->UpdateNewConstraintsVariableUsage();
2012 context_->UpdateRuleStats(
2013 "int_div: linearize positive division with a constant divisor");
2014
2015 return RemoveConstraint(ct);
2016 }
2017
2018 // TODO(user): reduce the domain of X by introducing an
2019 // InverseDivisionOfSortedDisjointIntervals().
2020 return false;
2021}
2022
2023bool CpModelPresolver::PresolveIntMod(int c, ConstraintProto* ct) {
2024 if (context_->ModelIsUnsat()) return false;
2025
2026 // TODO(user): Presolve f(X) = g(X) % fixed_mod.
2027 const LinearExpressionProto target = ct->int_mod().target();
2028 const LinearExpressionProto expr = ct->int_mod().exprs(0);
2029 const LinearExpressionProto mod = ct->int_mod().exprs(1);
2030
2031 if (context_->MinOf(target) > 0) {
2032 bool domain_changed = false;
2033 if (!context_->IntersectDomainWith(
2034 expr, Domain(0, std::numeric_limits<int64_t>::max()),
2035 &domain_changed)) {
2036 return false;
2037 }
2038 if (domain_changed) {
2039 context_->UpdateRuleStats(
2040 "int_mod: non negative target implies positive expression");
2041 }
2042 }
2043
2044 if (context_->MinOf(target) >= context_->MaxOf(mod) ||
2045 context_->MaxOf(target) <= -context_->MaxOf(mod)) {
2046 return context_->NotifyThatModelIsUnsat(
2047 "int_mod: incompatible target and mod");
2048 }
2049
2050 if (context_->MaxOf(target) < 0) {
2051 bool domain_changed = false;
2052 if (!context_->IntersectDomainWith(
2053 expr, Domain(std::numeric_limits<int64_t>::min(), 0),
2054 &domain_changed)) {
2055 return false;
2056 }
2057 if (domain_changed) {
2058 context_->UpdateRuleStats(
2059 "int_mod: non positive target implies negative expression");
2060 }
2061 }
2062
2063 if (context_->IsFixed(target) && context_->IsFixed(mod) &&
2064 context_->FixedValue(mod) > 1 && ct->enforcement_literal().empty() &&
2065 expr.vars().size() == 1) {
2066 // We can intersect the domain of expr with {k * mod + target}.
2067 const int64_t fixed_mod = context_->FixedValue(mod);
2068 const int64_t fixed_target = context_->FixedValue(target);
2069
2070 if (!context_->CanonicalizeAffineVariable(expr.vars(0), expr.coeffs(0),
2071 fixed_mod,
2072 fixed_target - expr.offset())) {
2073 return false;
2074 }
2075
2076 context_->UpdateRuleStats("int_mod: fixed mod and target");
2077 return RemoveConstraint(ct);
2078 }
2079
2080 bool domain_changed = false;
2081 if (!context_->IntersectDomainWith(
2082 target,
2083 context_->DomainSuperSetOf(expr).PositiveModuloBySuperset(
2084 context_->DomainSuperSetOf(mod)),
2085 &domain_changed)) {
2086 return false;
2087 }
2088
2089 if (domain_changed) {
2090 context_->UpdateRuleStats("int_mod: reduce target domain");
2091 }
2092
2093 // Remove the constraint if the target is removable.
2094 // This is triggered on the flatzinc rotating-workforce problems.
2095 //
2096 // TODO(user): We can deal with more cases, sometime even if the domain of
2097 // expr.vars(0) is large, the implied domain is not too complex.
2098 if (target.vars().size() == 1 && expr.vars().size() == 1 &&
2099 context_->DomainOf(expr.vars(0)).Size() < 100 && context_->IsFixed(mod) &&
2100 context_->VariableIsUniqueAndRemovable(target.vars(0)) &&
2101 target.vars(0) != expr.vars(0)) {
2102 const int64_t fixed_mod = context_->FixedValue(mod);
2103 std::vector<int64_t> values;
2104 const Domain dom = context_->DomainOf(target.vars(0));
2105 for (const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
2106 const int64_t rhs = (v * expr.coeffs(0) + expr.offset()) % fixed_mod;
2107 const int64_t target_term = rhs - target.offset();
2108 if (target_term % target.coeffs(0) != 0) continue;
2109 if (dom.Contains(target_term / target.coeffs(0))) {
2110 values.push_back(v);
2111 }
2112 }
2113
2114 context_->UpdateRuleStats("int_mod: remove singleton target");
2115 if (!context_->IntersectDomainWith(expr.vars(0),
2116 Domain::FromValues(values))) {
2117 return false;
2118 }
2119 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2120 ct->Clear();
2121 context_->UpdateConstraintVariableUsage(c);
2122 context_->MarkVariableAsRemoved(target.vars(0));
2123 return true;
2124 }
2125
2126 return false;
2127}
2128
2129// TODO(user): Now that everything has affine relations, we should maybe
2130// canonicalize all linear subexpression in a generic way.
2131bool CpModelPresolver::ExploitEquivalenceRelations(int c, ConstraintProto* ct) {
2132 bool changed = false;
2133
2134 // Optim: Special case for the linear constraint. We just remap the
2135 // enforcement literals, the normal variables will be replaced by their
2136 // representative in CanonicalizeLinear().
2137 if (ct->constraint_case() == ConstraintProto::kLinear) {
2138 for (int& ref : *ct->mutable_enforcement_literal()) {
2139 const int rep = this->context_->GetLiteralRepresentative(ref);
2140 if (rep != ref) {
2141 changed = true;
2142 ref = rep;
2143 }
2144 }
2145 return changed;
2146 }
2147
2148 // Optim: This extra loop is a lot faster than reparsing the variable from the
2149 // proto when there is nothing to do, which is quite often.
2150 bool work_to_do = false;
2151 for (const int var : context_->ConstraintToVars(c)) {
2152 const AffineRelation::Relation r = context_->GetAffineRelation(var);
2153 if (r.representative != var) {
2154 work_to_do = true;
2155 break;
2156 }
2157 }
2158 if (!work_to_do) return false;
2159
2160 // Remap literal and negated literal to their representative.
2162 [&changed, this](int* ref) {
2163 const int rep = this->context_->GetLiteralRepresentative(*ref);
2164 if (rep != *ref) {
2165 changed = true;
2166 *ref = rep;
2167 }
2168 },
2169 ct);
2170 return changed;
2171}
2172
2173bool CpModelPresolver::DivideLinearByGcd(ConstraintProto* ct) {
2174 if (context_->ModelIsUnsat()) return false;
2175
2176 // Compute the GCD of all coefficients.
2177 int64_t gcd = 0;
2178 const int num_vars = ct->linear().vars().size();
2179 for (int i = 0; i < num_vars; ++i) {
2180 const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2181 gcd = std::gcd(gcd, magnitude);
2182 if (gcd == 1) break;
2183 }
2184 if (gcd > 1) {
2185 context_->UpdateRuleStats("linear: divide by GCD");
2186 for (int i = 0; i < num_vars; ++i) {
2187 ct->mutable_linear()->set_coeffs(i, ct->linear().coeffs(i) / gcd);
2188 }
2189 const Domain rhs = ReadDomainFromProto(ct->linear());
2190 FillDomainInProto(rhs.InverseMultiplicationBy(gcd), ct->mutable_linear());
2191 if (ct->linear().domain_size() == 0) {
2192 return MarkConstraintAsFalse(ct);
2193 }
2194 }
2195 return false;
2196}
2197
2198bool CpModelPresolver::CanonicalizeLinearExpression(
2199 const ConstraintProto& ct, LinearExpressionProto* exp) {
2200 return context_->CanonicalizeLinearExpression(ct.enforcement_literal(), exp);
2201}
2202
2203bool CpModelPresolver::CanonicalizeLinear(ConstraintProto* ct) {
2204 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
2205 if (context_->ModelIsUnsat()) return false;
2206
2207 if (ct->linear().domain().empty()) {
2208 context_->UpdateRuleStats("linear: no domain");
2209 return MarkConstraintAsFalse(ct);
2210 }
2211
2212 bool changed = context_->CanonicalizeLinearConstraint(ct);
2213 changed |= DivideLinearByGcd(ct);
2214
2215 // For duplicate detection, we always make the first coeff positive.
2216 //
2217 // TODO(user): Move that to context_->CanonicalizeLinearConstraint(), and do
2218 // the same for LinearExpressionProto.
2219 if (!ct->linear().coeffs().empty() && ct->linear().coeffs(0) < 0) {
2220 for (int64_t& ref_coeff : *ct->mutable_linear()->mutable_coeffs()) {
2221 ref_coeff = -ref_coeff;
2222 }
2224 ct->mutable_linear());
2225 }
2226
2227 return changed;
2228}
2229
2230bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto* ct) {
2231 if (ct->constraint_case() != ConstraintProto::kLinear ||
2232 context_->ModelIsUnsat()) {
2233 return false;
2234 }
2235
2236 absl::btree_set<int> index_to_erase;
2237 const int num_vars = ct->linear().vars().size();
2238 Domain rhs = ReadDomainFromProto(ct->linear());
2239
2240 // First pass. Process singleton column that are not in the objective. Note
2241 // that for postsolve, it is important that we process them in the same order
2242 // in which they will be removed.
2243 for (int i = 0; i < num_vars; ++i) {
2244 const int var = ct->linear().vars(i);
2245 const int64_t coeff = ct->linear().coeffs(i);
2246 CHECK(RefIsPositive(var));
2247 if (context_->VariableIsUniqueAndRemovable(var)) {
2248 // This is not needed for the code below, but in practice, removing
2249 // singleton with a large coefficient create holes in the constraint rhs
2250 // and we will need to add more variable to deal with that.
2251 // This works way better on timtab1CUTS.pb.gz for instance.
2252 if (std::abs(coeff) != 1) continue;
2253
2254 bool exact;
2255 const auto term_domain =
2256 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2257 if (!exact) continue;
2258
2259 // We do not do that if the domain of rhs becomes too complex.
2260 const Domain new_rhs = rhs.AdditionWith(term_domain);
2261 if (new_rhs.NumIntervals() > 100) continue;
2262
2263 // Note that we can't do that if we loose information in the
2264 // multiplication above because the new domain might not be as strict
2265 // as the initial constraint otherwise. TODO(user): because of the
2266 // addition, it might be possible to cover more cases though.
2267 context_->UpdateRuleStats("linear: singleton column");
2268 index_to_erase.insert(i);
2269 rhs = new_rhs;
2270 continue;
2271 }
2272 }
2273
2274 // If the whole linear is independent from the rest of the problem, we
2275 // can solve it now. If it is enforced, then each variable will have two
2276 // values: Its minimum one and one minimizing the objective under the
2277 // constraint. The switch can be controlled by a single Boolean.
2278 //
2279 // TODO(user): Cover more case like dedicated algorithm to solve for a small
2280 // number of variable that are faster than the DP we use here.
2281 if (index_to_erase.empty()) {
2282 int num_singletons = 0;
2283 for (const int var : ct->linear().vars()) {
2284 if (!RefIsPositive(var)) break;
2285 if (!context_->VariableWithCostIsUniqueAndRemovable(var) &&
2286 !context_->VariableIsUniqueAndRemovable(var)) {
2287 break;
2288 }
2289 ++num_singletons;
2290 }
2291 if (num_singletons == num_vars) {
2292 // Try to solve the equation.
2293 std::vector<Domain> domains;
2294 std::vector<int64_t> coeffs;
2295 std::vector<int64_t> costs;
2296 for (int i = 0; i < num_vars; ++i) {
2297 const int var = ct->linear().vars(i);
2298 CHECK(RefIsPositive(var));
2299 domains.push_back(context_->DomainOf(var));
2300 coeffs.push_back(ct->linear().coeffs(i));
2301 costs.push_back(context_->ObjectiveCoeff(var));
2302 }
2303 BasicKnapsackSolver solver;
2304 const auto& result = solver.Solve(domains, coeffs, costs,
2305 ReadDomainFromProto(ct->linear()));
2306 if (!result.solved) {
2307 context_->UpdateRuleStats(
2308 "TODO independent linear: minimize single linear constraint");
2309 } else if (result.infeasible) {
2310 context_->UpdateRuleStats(
2311 "independent linear: no DP solution to simple constraint");
2312 return MarkConstraintAsFalse(ct);
2313 } else {
2314 if (ct->enforcement_literal().empty()) {
2315 // Just fix everything.
2316 context_->UpdateRuleStats("independent linear: solved by DP");
2317 for (int i = 0; i < num_vars; ++i) {
2318 if (!context_->IntersectDomainWith(ct->linear().vars(i),
2319 Domain(result.solution[i]))) {
2320 return false;
2321 }
2322 }
2323 return RemoveConstraint(ct);
2324 }
2325
2326 // Each variable will take two values according to a single Boolean.
2327 int indicator;
2328 if (ct->enforcement_literal().size() == 1) {
2329 indicator = ct->enforcement_literal(0);
2330 } else {
2331 indicator =
2332 context_->NewBoolVarWithConjunction(ct->enforcement_literal());
2333 auto* new_ct = context_->working_model->add_constraints();
2334 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2335 new_ct->mutable_bool_or()->add_literals(indicator);
2336 context_->UpdateNewConstraintsVariableUsage();
2337 }
2338 for (int i = 0; i < num_vars; ++i) {
2339 const int64_t best_value =
2340 costs[i] > 0 ? domains[i].Min() : domains[i].Max();
2341 const int64_t other_value = result.solution[i];
2342 if (best_value == other_value) {
2343 if (!context_->IntersectDomainWith(ct->linear().vars(i),
2344 Domain(best_value))) {
2345 return false;
2346 }
2347 continue;
2348 }
2349 solution_crush_.SetVarToConditionalValue(
2350 ct->linear().vars(i), {indicator}, other_value, best_value);
2351 if (RefIsPositive(indicator)) {
2352 if (!context_->StoreAffineRelation(ct->linear().vars(i), indicator,
2353 other_value - best_value,
2354 best_value)) {
2355 return false;
2356 }
2357 } else {
2358 if (!context_->StoreAffineRelation(
2359 ct->linear().vars(i), PositiveRef(indicator),
2360 best_value - other_value, other_value)) {
2361 return false;
2362 }
2363 }
2364 }
2365 context_->UpdateRuleStats(
2366 "independent linear: with enforcement, but solved by DP");
2367 return RemoveConstraint(ct);
2368 }
2369 }
2370 }
2371
2372 // If we didn't find any, look for the one appearing in the objective.
2373 if (index_to_erase.empty()) {
2374 // Note that we only do that if we have a non-reified equality.
2375 if (context_->params().presolve_substitution_level() <= 0) return false;
2376 if (!ct->enforcement_literal().empty()) return false;
2377
2378 // If it is possible to do so, note that we can transform constraint into
2379 // equalities in PropagateDomainsInLinear().
2380 if (rhs.Min() != rhs.Max()) return false;
2381
2382 for (int i = 0; i < num_vars; ++i) {
2383 const int var = ct->linear().vars(i);
2384 const int64_t coeff = ct->linear().coeffs(i);
2385 CHECK(RefIsPositive(var));
2386
2387 // If the variable appear only in the objective and we have an equality,
2388 // we can transfer the cost to the rest of the linear expression, and
2389 // remove that variable. Note that this do not remove any feasible
2390 // solution and is not a "dual" reduction.
2391 //
2392 // Note that is similar to the substitution code in PresolveLinear() but
2393 // it doesn't require the variable to be implied free since we do not
2394 // remove the constraints afterwards, just the variable.
2395 if (!context_->VariableWithCostIsUnique(var)) continue;
2396 DCHECK(context_->ObjectiveMap().contains(var));
2397
2398 // We only support substitution that does not require to multiply the
2399 // objective by some factor.
2400 //
2401 // TODO(user): If the objective is a single variable, we can actually
2402 // "absorb" any factor into the objective scaling.
2403 const int64_t objective_coeff = context_->ObjectiveMap().at(var);
2404 CHECK_NE(coeff, 0);
2405 if (objective_coeff % coeff != 0) continue;
2406
2407 // TODO(user): We have an issue if objective coeff is not one, because
2408 // the RecomputeSingletonObjectiveDomain() do not properly put holes
2409 // in the objective domain, which might cause an issue. Note that this
2410 // presolve rule is actually almost never applied on the miplib.
2411 if (std::abs(objective_coeff) != 1) continue;
2412
2413 // We do not do that if the domain of rhs becomes too complex.
2414 bool exact;
2415 const auto term_domain =
2416 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2417 if (!exact) continue;
2418 const Domain new_rhs = rhs.AdditionWith(term_domain);
2419 if (new_rhs.NumIntervals() > 100) continue;
2420
2421 // Special case: If the objective was a single variable, we can transfer
2422 // the domain of var to the objective, and just completely remove this
2423 // equality constraint.
2424 //
2425 // TODO(user): Maybe if var has a complex domain, we might not want to
2426 // substitute it?
2427 if (context_->ObjectiveMap().size() == 1) {
2428 // This make sure the domain of var is restricted and the objective
2429 // domain updated.
2430 if (!context_->RecomputeSingletonObjectiveDomain()) {
2431 return true;
2432 }
2433
2434 // The function above might fix var, in which case, we just abort.
2435 if (context_->IsFixed(var)) continue;
2436
2437 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2438 if (context_->ModelIsUnsat()) return true;
2439 continue;
2440 }
2441
2442 context_->UpdateRuleStats("linear: singleton column define objective.");
2443 context_->MarkVariableAsRemoved(var);
2444 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2445 return RemoveConstraint(ct);
2446 }
2447
2448 // On supportcase20, this transformation make the LP relaxation way worse.
2449 // TODO(user): understand why.
2450 if (true) continue;
2451
2452 // Update the objective and remove the variable from its equality
2453 // constraint by expanding its rhs. This might fail if the new linear
2454 // objective expression can lead to overflow.
2455 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2456 if (context_->ModelIsUnsat()) return true;
2457 continue;
2458 }
2459
2460 context_->UpdateRuleStats(
2461 "linear: singleton column in equality and in objective.");
2462 rhs = new_rhs;
2463 index_to_erase.insert(i);
2464 break;
2465 }
2466 }
2467 if (index_to_erase.empty()) return false;
2468
2469 // Tricky: If we have a singleton variable in an enforced constraint, and at
2470 // postsolve the enforcement is false, we might just ignore the constraint.
2471 // This is fine, but we still need to assign any removed variable to a
2472 // feasible value, otherwise later postsolve rules might not work correctly.
2473 // Adding these linear1 achieve that.
2474 //
2475 // TODO(user): Alternatively, we could copy the constraint without the
2476 // enforcement to the mapping model, since singleton variable are supposed
2477 // to always have a feasible value anyway.
2478 if (!ct->enforcement_literal().empty()) {
2479 for (const int i : index_to_erase) {
2480 const int var = ct->linear().vars(i);
2481 auto* new_lin =
2482 context_->NewMappingConstraint(__FILE__, __LINE__)->mutable_linear();
2483 new_lin->add_vars(var);
2484 new_lin->add_coeffs(1);
2485 FillDomainInProto(context_->DomainOf(var), new_lin);
2486 }
2487 }
2488
2489 // TODO(user): we could add the constraint to mapping_model only once
2490 // instead of adding a reduced version of it each time a new singleton
2491 // variable appear in the same constraint later. That would work but would
2492 // also force the postsolve to take search decisions...
2493 if (absl::GetFlag(FLAGS_cp_model_debug_postsolve)) {
2494 auto* new_ct = context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2495 const std::string name = new_ct->name();
2496 *new_ct = *ct;
2497 new_ct->set_name(absl::StrCat(ct->name(), " copy ", name));
2498 } else {
2499 *context_->NewMappingConstraint(*ct, __FILE__, __LINE__) = *ct;
2500 }
2501
2502 int new_size = 0;
2503 for (int i = 0; i < num_vars; ++i) {
2504 if (index_to_erase.count(i)) {
2505 context_->MarkVariableAsRemoved(ct->linear().vars(i));
2506 continue;
2507 }
2508 ct->mutable_linear()->set_coeffs(new_size, ct->linear().coeffs(i));
2509 ct->mutable_linear()->set_vars(new_size, ct->linear().vars(i));
2510 ++new_size;
2511 }
2512 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
2513 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
2514 FillDomainInProto(rhs, ct->mutable_linear());
2515 DivideLinearByGcd(ct);
2516 return true;
2517}
2518
2519// If the gcd of all but one term (with index target_index) is not one, we can
2520// rewrite the last term using an affine representative.
2521bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality(
2522 int target_index, ConstraintProto* ct) {
2523 int64_t gcd = 0;
2524 const int num_variables = ct->linear().vars().size();
2525 for (int i = 0; i < num_variables; ++i) {
2526 if (i == target_index) continue;
2527 const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2528 gcd = std::gcd(gcd, magnitude);
2529 if (gcd == 1) return false;
2530 }
2531
2532 // If we take the constraint % gcd, we have
2533 // ref * coeff % gcd = rhs % gcd
2534 CHECK_GT(gcd, 1);
2535 const int ref = ct->linear().vars(target_index);
2536 const int64_t coeff = ct->linear().coeffs(target_index);
2537 const int64_t rhs = ct->linear().domain(0);
2538
2539 // This should have been processed before by just dividing the whole
2540 // constraint by the gcd.
2541 if (coeff % gcd == 0) return false;
2542
2543 if (!context_->CanonicalizeAffineVariable(ref, coeff, gcd, rhs)) {
2544 return false;
2545 }
2546
2547 // We use the new variable in the constraint.
2548 // Note that we will divide everything by the gcd too.
2549 return CanonicalizeLinear(ct);
2550}
2551
2552namespace {
2553
2554bool IsLinearEqualityConstraint(const ConstraintProto& ct) {
2555 return ct.constraint_case() == ConstraintProto::kLinear &&
2556 ct.linear().domain().size() == 2 &&
2557 ct.linear().domain(0) == ct.linear().domain(1) &&
2558 ct.enforcement_literal().empty();
2559}
2560
2561} // namespace
2562
2563// Any equality must be true modulo n.
2564//
2565// If the gcd of all but one term is not one, we can rewrite the last term using
2566// an affine representative by considering the equality modulo that gcd.
2567// As an heuristic, we only test the smallest term or small primes 2, 3, and 5.
2568//
2569// We also handle the special case of having two non-zero literals modulo 2.
2570//
2571// TODO(user): Use more complex algo to detect all the cases? By splitting the
2572// constraint in two, and computing the gcd of each halves, we can reduce the
2573// problem to two problem of half size. So at least we can do it in O(n log n).
2574bool CpModelPresolver::PresolveLinearEqualityWithModulo(ConstraintProto* ct) {
2575 if (context_->ModelIsUnsat()) return false;
2576 if (!IsLinearEqualityConstraint(*ct)) return false;
2577
2578 const int num_variables = ct->linear().vars().size();
2579 if (num_variables < 2) return false;
2580
2581 std::vector<int> mod2_indices;
2582 std::vector<int> mod3_indices;
2583 std::vector<int> mod5_indices;
2584
2585 int64_t min_magnitude;
2586 int num_smallest = 0;
2587 int smallest_index;
2588 for (int i = 0; i < num_variables; ++i) {
2589 const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2590 if (num_smallest == 0 || magnitude < min_magnitude) {
2591 min_magnitude = magnitude;
2592 num_smallest = 1;
2593 smallest_index = i;
2594 } else if (magnitude == min_magnitude) {
2595 ++num_smallest;
2596 }
2597
2598 if (magnitude % 2 != 0) mod2_indices.push_back(i);
2599 if (magnitude % 3 != 0) mod3_indices.push_back(i);
2600 if (magnitude % 5 != 0) mod5_indices.push_back(i);
2601 }
2602
2603 if (mod2_indices.size() == 2) {
2604 bool ok = true;
2605 std::vector<int> literals;
2606 for (const int i : mod2_indices) {
2607 const int ref = ct->linear().vars(i);
2608 if (!context_->CanBeUsedAsLiteral(ref)) {
2609 ok = false;
2610 break;
2611 }
2612 literals.push_back(ref);
2613 }
2614 if (ok) {
2615 const int64_t rhs = std::abs(ct->linear().domain(0));
2616 context_->UpdateRuleStats("linear: only two odd Booleans in equality");
2617 if (rhs % 2) {
2618 if (!context_->StoreBooleanEqualityRelation(literals[0],
2619 NegatedRef(literals[1]))) {
2620 return false;
2621 }
2622 } else {
2623 if (!context_->StoreBooleanEqualityRelation(literals[0], literals[1])) {
2624 return false;
2625 }
2626 }
2627 }
2628 }
2629
2630 // TODO(user): More than one reduction might be possible, so we will need
2631 // to call this again if we apply any of these reduction.
2632 if (mod2_indices.size() == 1) {
2633 return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0], ct);
2634 }
2635 if (mod3_indices.size() == 1) {
2636 return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0], ct);
2637 }
2638 if (mod5_indices.size() == 1) {
2639 return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0], ct);
2640 }
2641 if (num_smallest == 1) {
2642 return AddVarAffineRepresentativeFromLinearEquality(smallest_index, ct);
2643 }
2644
2645 return false;
2646}
2647
2648bool CpModelPresolver::PresolveLinearOfSizeOne(ConstraintProto* ct) {
2649 CHECK_EQ(ct->linear().vars().size(), 1);
2650 CHECK(RefIsPositive(ct->linear().vars(0)));
2651
2652 const int var = ct->linear().vars(0);
2653 const Domain var_domain = context_->DomainOf(var);
2654 const Domain rhs = ReadDomainFromProto(ct->linear())
2655 .InverseMultiplicationBy(ct->linear().coeffs(0))
2656 .IntersectionWith(var_domain);
2657 if (rhs.IsEmpty()) {
2658 context_->UpdateRuleStats("linear1: infeasible");
2659 return MarkConstraintAsFalse(ct);
2660 }
2661 if (rhs == var_domain) {
2662 context_->UpdateRuleStats("linear1: always true");
2663 return RemoveConstraint(ct);
2664 }
2665
2666 // We can always canonicalize the constraint to a coefficient of 1.
2667 // Note that this should never trigger as we usually divide by gcd already.
2668 if (ct->linear().coeffs(0) != 1) {
2669 context_->UpdateRuleStats("linear1: canonicalized");
2670 ct->mutable_linear()->set_coeffs(0, 1);
2671 FillDomainInProto(rhs, ct->mutable_linear());
2672 }
2673
2674 // Size one constraint with no enforcement?
2675 if (!HasEnforcementLiteral(*ct)) {
2676 context_->UpdateRuleStats("linear1: without enforcement");
2677 if (!context_->IntersectDomainWith(var, rhs)) return false;
2678 return RemoveConstraint(ct);
2679 }
2680
2681 // This is just an implication, lets convert it right away.
2682 if (context_->CanBeUsedAsLiteral(var)) {
2683 DCHECK(rhs.IsFixed());
2684 if (rhs.FixedValue() == 1) {
2685 ct->mutable_bool_and()->add_literals(var);
2686 } else {
2687 CHECK_EQ(rhs.FixedValue(), 0);
2688 ct->mutable_bool_and()->add_literals(NegatedRef(var));
2689 }
2690
2691 // No var <-> constraint graph changes.
2692 // But this is no longer a linear1.
2693 return true;
2694 }
2695
2696 // Detect encoding.
2697 bool changed = false;
2698 if (ct->enforcement_literal().size() == 1) {
2699 // If we already have an encoding literal, this constraint is really
2700 // an implication.
2701 int lit = ct->enforcement_literal(0);
2702
2703 // For correctness below, it is important lit is the canonical literal,
2704 // otherwise we might remove the constraint even though it is the one
2705 // defining an encoding literal.
2706 const int representative = context_->GetLiteralRepresentative(lit);
2707 if (lit != representative) {
2708 lit = representative;
2709 ct->set_enforcement_literal(0, lit);
2710 context_->UpdateRuleStats("linear1: remapped enforcement literal");
2711 changed = true;
2712 }
2713
2714 if (rhs.IsFixed()) {
2715 const int64_t value = rhs.FixedValue();
2716 int encoding_lit;
2717 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2718 if (lit == encoding_lit) return changed;
2719 context_->AddImplication(lit, encoding_lit);
2720 context_->UpdateNewConstraintsVariableUsage();
2721 ct->Clear();
2722 context_->UpdateRuleStats("linear1: transformed to implication");
2723 return true;
2724 } else {
2725 if (context_->StoreLiteralImpliesVarEqValue(lit, var, value)) {
2726 // The domain is not actually modified, but we want to rescan the
2727 // constraints linked to this variable.
2728 context_->modified_domains.Set(var);
2729 }
2730 context_->UpdateNewConstraintsVariableUsage();
2731 }
2732 return changed;
2733 }
2734
2735 const Domain complement = rhs.Complement().IntersectionWith(var_domain);
2736 if (complement.IsFixed()) {
2737 const int64_t value = complement.FixedValue();
2738 int encoding_lit;
2739 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2740 if (NegatedRef(lit) == encoding_lit) return changed;
2741 context_->AddImplication(lit, NegatedRef(encoding_lit));
2742 context_->UpdateNewConstraintsVariableUsage();
2743 ct->Clear();
2744 context_->UpdateRuleStats("linear1: transformed to implication");
2745 return true;
2746 } else {
2747 if (context_->StoreLiteralImpliesVarNEqValue(lit, var, value)) {
2748 // The domain is not actually modified, but we want to rescan the
2749 // constraints linked to this variable.
2750 context_->modified_domains.Set(var);
2751 }
2752 context_->UpdateNewConstraintsVariableUsage();
2753 }
2754 return changed;
2755 }
2756 }
2757
2758 return changed;
2759}
2760
2761bool CpModelPresolver::PresolveLinearOfSizeTwo(ConstraintProto* ct) {
2762 DCHECK_EQ(ct->linear().vars().size(), 2);
2763
2764 const LinearConstraintProto& arg = ct->linear();
2765 const int var1 = arg.vars(0);
2766 const int var2 = arg.vars(1);
2767 const int64_t coeff1 = arg.coeffs(0);
2768 const int64_t coeff2 = arg.coeffs(1);
2769
2770 // If it is not an equality, we only presolve the constraint if one of
2771 // the variable is Boolean. Note that if both are Boolean, then a similar
2772 // reduction is done by PresolveLinearOnBooleans(). If we have an equality,
2773 // then the code below will do something stronger than this.
2774 //
2775 // TODO(user): We should probably instead generalize the code of
2776 // ExtractEnforcementLiteralFromLinearConstraint(), or just temporary
2777 // propagate domain of enforced linear constraints, to detect Boolean that
2778 // must be true or false. This way we can do the same for longer constraints.
2779 const bool is_equality =
2780 arg.domain_size() == 2 && arg.domain(0) == arg.domain(1);
2781 if (!is_equality) {
2782 int lit, var;
2783 int64_t value_on_true, coeff;
2784 if (context_->CanBeUsedAsLiteral(var1)) {
2785 lit = var1;
2786 value_on_true = coeff1;
2787 var = var2;
2788 coeff = coeff2;
2789 } else if (context_->CanBeUsedAsLiteral(var2)) {
2790 lit = var2;
2791 value_on_true = coeff2;
2792 var = var1;
2793 coeff = coeff1;
2794 } else {
2795 return false;
2796 }
2797 if (!RefIsPositive(lit)) return false;
2798
2799 const Domain rhs = ReadDomainFromProto(ct->linear());
2800 const Domain rhs_if_true =
2801 rhs.AdditionWith(Domain(-value_on_true)).InverseMultiplicationBy(coeff);
2802 const Domain rhs_if_false = rhs.InverseMultiplicationBy(coeff);
2803 const bool implied_false =
2804 context_->DomainOf(var).IntersectionWith(rhs_if_true).IsEmpty();
2805 const bool implied_true =
2806 context_->DomainOf(var).IntersectionWith(rhs_if_false).IsEmpty();
2807 if (implied_true && implied_false) {
2808 context_->UpdateRuleStats("linear2: infeasible.");
2809 return MarkConstraintAsFalse(ct);
2810 } else if (implied_true) {
2811 context_->UpdateRuleStats("linear2: Boolean with one feasible value.");
2812
2813 // => true.
2814 ConstraintProto* new_ct = context_->working_model->add_constraints();
2815 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2816 new_ct->mutable_bool_and()->add_literals(lit);
2817 context_->UpdateNewConstraintsVariableUsage();
2818
2819 // Rewrite to => var in rhs_if_true.
2820 ct->mutable_linear()->Clear();
2821 ct->mutable_linear()->add_vars(var);
2822 ct->mutable_linear()->add_coeffs(1);
2823 FillDomainInProto(rhs_if_true, ct->mutable_linear());
2824 return PresolveLinearOfSizeOne(ct) || true;
2825 } else if (implied_false) {
2826 context_->UpdateRuleStats("linear2: Boolean with one feasible value.");
2827
2828 // => false.
2829 ConstraintProto* new_ct = context_->working_model->add_constraints();
2830 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2831 new_ct->mutable_bool_and()->add_literals(NegatedRef(lit));
2832 context_->UpdateNewConstraintsVariableUsage();
2833
2834 // Rewrite to => var in rhs_if_false.
2835 ct->mutable_linear()->Clear();
2836 ct->mutable_linear()->add_vars(var);
2837 ct->mutable_linear()->add_coeffs(1);
2838 FillDomainInProto(rhs_if_false, ct->mutable_linear());
2839 return PresolveLinearOfSizeOne(ct) || true;
2840 } else if (ct->enforcement_literal().empty() &&
2841 !context_->CanBeUsedAsLiteral(var)) {
2842 // We currently only do that if there are no enforcement and we don't have
2843 // two Booleans as this can be presolved differently. We expand it into
2844 // two linear1 constraint that have a chance to be merged with other
2845 // "encoding" constraints.
2846 context_->UpdateRuleStats("linear2: contains a Boolean.");
2847
2848 // lit => var \in rhs_if_true
2849 const Domain var_domain = context_->DomainOf(var);
2850 if (!var_domain.IsIncludedIn(rhs_if_true)) {
2851 ConstraintProto* new_ct = context_->working_model->add_constraints();
2852 new_ct->add_enforcement_literal(lit);
2853 new_ct->mutable_linear()->add_vars(var);
2854 new_ct->mutable_linear()->add_coeffs(1);
2855 FillDomainInProto(rhs_if_true.IntersectionWith(var_domain),
2856 new_ct->mutable_linear());
2857 }
2858
2859 // NegatedRef(lit) => var \in rhs_if_false
2860 if (!var_domain.IsIncludedIn(rhs_if_false)) {
2861 ConstraintProto* new_ct = context_->working_model->add_constraints();
2862 new_ct->add_enforcement_literal(NegatedRef(lit));
2863 new_ct->mutable_linear()->add_vars(var);
2864 new_ct->mutable_linear()->add_coeffs(1);
2865 FillDomainInProto(rhs_if_false.IntersectionWith(var_domain),
2866 new_ct->mutable_linear());
2867 }
2868
2869 context_->UpdateNewConstraintsVariableUsage();
2870 return RemoveConstraint(ct);
2871 }
2872
2873 // Code below require equality.
2874 context_->UpdateRuleStats("TODO linear2: contains a Boolean.");
2875 return false;
2876 }
2877
2878 // We have: enforcement => (coeff1 * v1 + coeff2 * v2 == rhs).
2879 const int64_t rhs = arg.domain(0);
2880 if (ct->enforcement_literal().empty()) {
2881 // Detect affine relation.
2882 //
2883 // TODO(user): it might be better to first add only the affine relation with
2884 // a coefficient of magnitude 1, and later the one with larger coeffs.
2885 bool added = false;
2886 if (coeff1 == 1) {
2887 added = context_->StoreAffineRelation(var1, var2, -coeff2, rhs);
2888 } else if (coeff2 == 1) {
2889 added = context_->StoreAffineRelation(var2, var1, -coeff1, rhs);
2890 } else if (coeff1 == -1) {
2891 added = context_->StoreAffineRelation(var1, var2, coeff2, -rhs);
2892 } else if (coeff2 == -1) {
2893 added = context_->StoreAffineRelation(var2, var1, coeff1, -rhs);
2894 } else {
2895 // In this case, we can solve the diophantine equation, and write
2896 // both x and y in term of a new affine representative z.
2897 //
2898 // Note that PresolveLinearEqualityWithModulo() will have the same effect.
2899 //
2900 // We can also decide to fully expand the equality if the variables
2901 // are fully encoded.
2902 context_->UpdateRuleStats("TODO linear2: ax + by = cte");
2903 }
2904 if (added) return RemoveConstraint(ct);
2905 } else {
2906 // We look ahead to detect solutions to ax + by == cte.
2907 int64_t a = coeff1;
2908 int64_t b = coeff2;
2909 int64_t cte = rhs;
2910 int64_t x0 = 0;
2911 int64_t y0 = 0;
2912 if (!SolveDiophantineEquationOfSizeTwo(a, b, cte, x0, y0)) {
2913 context_->UpdateRuleStats(
2914 "linear2: implied ax + by = cte has no solutions");
2915 return MarkConstraintAsFalse(ct);
2916 }
2917 const Domain reduced_domain =
2918 context_->DomainOf(var1)
2919 .AdditionWith(Domain(-x0))
2920 .InverseMultiplicationBy(b)
2921 .IntersectionWith(context_->DomainOf(var2)
2922 .AdditionWith(Domain(-y0))
2923 .InverseMultiplicationBy(-a));
2924
2925 if (reduced_domain.IsEmpty()) { // no solution
2926 context_->UpdateRuleStats(
2927 "linear2: implied ax + by = cte has no solutions");
2928 return MarkConstraintAsFalse(ct);
2929 }
2930
2931 if (reduced_domain.Size() == 1) {
2932 const int64_t z = reduced_domain.FixedValue();
2933 const int64_t value1 = x0 + b * z;
2934 const int64_t value2 = y0 - a * z;
2935
2936 DCHECK(context_->DomainContains(var1, value1));
2937 DCHECK(context_->DomainContains(var2, value2));
2938 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
2939
2940 ConstraintProto* imply1 = context_->working_model->add_constraints();
2941 *imply1->mutable_enforcement_literal() = ct->enforcement_literal();
2942 imply1->mutable_linear()->add_vars(var1);
2943 imply1->mutable_linear()->add_coeffs(1);
2944 imply1->mutable_linear()->add_domain(value1);
2945 imply1->mutable_linear()->add_domain(value1);
2946
2947 ConstraintProto* imply2 = context_->working_model->add_constraints();
2948 *imply2->mutable_enforcement_literal() = ct->enforcement_literal();
2949 imply2->mutable_linear()->add_vars(var2);
2950 imply2->mutable_linear()->add_coeffs(1);
2951 imply2->mutable_linear()->add_domain(value2);
2952 imply2->mutable_linear()->add_domain(value2);
2953 context_->UpdateRuleStats(
2954 "linear2: implied ax + by = cte has only one solution");
2955 context_->UpdateNewConstraintsVariableUsage();
2956 return RemoveConstraint(ct);
2957 }
2958 }
2959
2960 return false;
2961}
2962
2963bool CpModelPresolver::PresolveSmallLinear(ConstraintProto* ct) {
2964 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
2965 if (context_->ModelIsUnsat()) return false;
2966
2967 if (ct->linear().vars().empty()) {
2968 context_->UpdateRuleStats("linear: empty");
2969 const Domain rhs = ReadDomainFromProto(ct->linear());
2970 if (rhs.Contains(0)) {
2971 return RemoveConstraint(ct);
2972 } else {
2973 return MarkConstraintAsFalse(ct);
2974 }
2975 } else if (ct->linear().vars().size() == 1) {
2976 return PresolveLinearOfSizeOne(ct);
2977 } else if (ct->linear().vars().size() == 2) {
2978 return PresolveLinearOfSizeTwo(ct);
2979 }
2980
2981 return false;
2982}
2983
2984bool CpModelPresolver::PresolveDiophantine(ConstraintProto* ct) {
2985 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
2986 if (ct->linear().vars().size() <= 1) return false;
2987
2988 if (context_->ModelIsUnsat()) return false;
2989
2990 const LinearConstraintProto& linear_constraint = ct->linear();
2991 if (linear_constraint.domain_size() != 2) return false;
2992 if (linear_constraint.domain(0) != linear_constraint.domain(1)) return false;
2993
2994 std::vector<int64_t> lbs(linear_constraint.vars_size());
2995 std::vector<int64_t> ubs(linear_constraint.vars_size());
2996 for (int i = 0; i < linear_constraint.vars_size(); ++i) {
2997 lbs[i] = context_->MinOf(linear_constraint.vars(i));
2998 ubs[i] = context_->MaxOf(linear_constraint.vars(i));
2999 }
3000 DiophantineSolution diophantine_solution = SolveDiophantine(
3001 linear_constraint.coeffs(), linear_constraint.domain(0), lbs, ubs);
3002
3003 if (!diophantine_solution.has_solutions) {
3004 context_->UpdateRuleStats("diophantine: equality has no solutions");
3005 return MarkConstraintAsFalse(ct);
3006 }
3007 if (diophantine_solution.no_reformulation_needed) return false;
3008 // Only first coefficients of kernel_basis elements and special_solution could
3009 // overflow int64_t due to the reduction applied in SolveDiophantineEquation,
3010 for (const std::vector<absl::int128>& b : diophantine_solution.kernel_basis) {
3011 if (!IsNegatableInt64(b[0])) {
3012 context_->UpdateRuleStats(
3013 "diophantine: couldn't apply due to int64_t overflow");
3014 return false;
3015 }
3016 }
3017 if (!IsNegatableInt64(diophantine_solution.special_solution[0])) {
3018 context_->UpdateRuleStats(
3019 "diophantine: couldn't apply due to int64_t overflow");
3020 return false;
3021 }
3022
3023 const int num_replaced_variables =
3024 static_cast<int>(diophantine_solution.special_solution.size());
3025 const int num_new_variables =
3026 static_cast<int>(diophantine_solution.kernel_vars_lbs.size());
3027 DCHECK_EQ(num_new_variables + 1, num_replaced_variables);
3028 for (int i = 0; i < num_new_variables; ++i) {
3029 if (!IsNegatableInt64(diophantine_solution.kernel_vars_lbs[i]) ||
3030 !IsNegatableInt64(diophantine_solution.kernel_vars_ubs[i])) {
3031 context_->UpdateRuleStats(
3032 "diophantine: couldn't apply due to int64_t overflow");
3033 return false;
3034 }
3035 }
3036 // TODO(user): Make sure the newly generated linear constraint
3037 // satisfy our no-overflow precondition on the min/max activity.
3038 // We should check that the model still satisfy conditions in
3039 // 3/ortools/sat/cp_model_checker.cc;l=165;bpv=0
3040
3041 // Create new variables.
3042 std::vector<int> new_variables(num_new_variables);
3043 for (int i = 0; i < num_new_variables; ++i) {
3044 new_variables[i] = context_->working_model->variables_size();
3045 IntegerVariableProto* var = context_->working_model->add_variables();
3046 var->add_domain(
3047 static_cast<int64_t>(diophantine_solution.kernel_vars_lbs[i]));
3048 var->add_domain(
3049 static_cast<int64_t>(diophantine_solution.kernel_vars_ubs[i]));
3050 if (!ct->name().empty()) {
3051 var->set_name(absl::StrCat("u_diophantine_", ct->name(), "_", i));
3052 }
3053 }
3054
3055 // For i = 0, ..., num_replaced_variables - 1, creates
3056 // x[i] = special_solution[i]
3057 // + sum(kernel_basis[k][i]*y[k], max(1, i) <= k < vars.size - 1)
3058 // where:
3059 // y[k] is the newly created variable if 0 <= k < num_new_variables
3060 // y[k] = x[index_permutation[k + 1]] otherwise.
3061 for (int i = 0; i < num_replaced_variables; ++i) {
3062 ConstraintProto* identity = context_->working_model->add_constraints();
3063 LinearConstraintProto* lin = identity->mutable_linear();
3064 if (!ct->name().empty()) {
3065 identity->set_name(absl::StrCat("c_diophantine_", ct->name(), "_", i));
3066 }
3067 *identity->mutable_enforcement_literal() = ct->enforcement_literal();
3068 lin->add_vars(
3069 linear_constraint.vars(diophantine_solution.index_permutation[i]));
3070 lin->add_coeffs(1);
3071 lin->add_domain(
3072 static_cast<int64_t>(diophantine_solution.special_solution[i]));
3073 lin->add_domain(
3074 static_cast<int64_t>(diophantine_solution.special_solution[i]));
3075 for (int j = std::max(1, i); j < num_replaced_variables; ++j) {
3076 lin->add_vars(new_variables[j - 1]);
3077 lin->add_coeffs(
3078 -static_cast<int64_t>(diophantine_solution.kernel_basis[j - 1][i]));
3079 }
3080 for (int j = num_replaced_variables; j < linear_constraint.vars_size();
3081 ++j) {
3082 lin->add_vars(
3083 linear_constraint.vars(diophantine_solution.index_permutation[j]));
3084 lin->add_coeffs(
3085 -static_cast<int64_t>(diophantine_solution.kernel_basis[j - 1][i]));
3086 }
3087
3088 // TODO(user): The domain in the proto are not necessarily up to date so
3089 // this might be stricter than necessary. Fix? It shouldn't matter too much
3090 // though.
3091 if (PossibleIntegerOverflow(*(context_->working_model), lin->vars(),
3092 lin->coeffs())) {
3093 context_->UpdateRuleStats(
3094 "diophantine: couldn't apply due to overflowing activity of new "
3095 "constraints");
3096 // Cancel working_model changes.
3097 context_->working_model->mutable_constraints()->DeleteSubrange(
3098 context_->working_model->constraints_size() - i - 1, i + 1);
3099 context_->working_model->mutable_variables()->DeleteSubrange(
3100 context_->working_model->variables_size() - num_new_variables,
3101 num_new_variables);
3102 return false;
3103 }
3104 }
3105 context_->InitializeNewDomains();
3106 // Scan the new constraints added above in reverse order so that the hint of
3107 // `new_variables[k]` can be computed from the hint of the existing variables
3108 // and from the hints of `new_variables[k']`, with k' > k.
3109 const int num_constraints = context_->working_model->constraints_size();
3110 for (int i = 0; i < num_replaced_variables; ++i) {
3111 const LinearConstraintProto& linear =
3112 context_->working_model->constraints(num_constraints - 1 - i).linear();
3113 DCHECK(linear.domain_size() == 2 && linear.domain(0) == linear.domain(1));
3114 solution_crush_.SetVarToLinearConstraintSolution(
3115 std::nullopt, linear.vars(), linear.coeffs(), linear.domain(0));
3116 }
3117
3118 if (VLOG_IS_ON(2)) {
3119 std::string log_eq = absl::StrCat(linear_constraint.domain(0), " = ");
3120 const int terms_to_show = std::min<int>(15, linear_constraint.vars_size());
3121 for (int i = 0; i < terms_to_show; ++i) {
3122 if (i > 0) absl::StrAppend(&log_eq, " + ");
3123 absl::StrAppend(
3124 &log_eq,
3125 linear_constraint.coeffs(diophantine_solution.index_permutation[i]),
3126 " x",
3127 linear_constraint.vars(diophantine_solution.index_permutation[i]));
3128 }
3129 if (terms_to_show < linear_constraint.vars_size()) {
3130 absl::StrAppend(&log_eq, "+ ... (", linear_constraint.vars_size(),
3131 " terms)");
3132 }
3133 VLOG(2) << "[Diophantine] " << log_eq;
3134 }
3135
3136 context_->UpdateRuleStats("diophantine: reformulated equality");
3137 context_->UpdateNewConstraintsVariableUsage();
3138 return RemoveConstraint(ct);
3139}
3140
3141// This tries to decompose the constraint into coeff * part1 + part2 and show
3142// that the value that part2 take is not important, thus the constraint can
3143// only be transformed on a constraint on the first part.
3144//
3145// TODO(user): Improve !! we miss simple case like x + 47 y + 50 z >= 50
3146// for positive variables. We should remove x, and ideally we should rewrite
3147// this as y + 2z >= 2 if we can show that its relaxation is just better?
3148// We should at least see that it is the same as 47y + 50 z >= 48.
3149//
3150// TODO(user): One easy algo is to first remove all enforcement term (even
3151// non-Boolean one) before applying the algo here and then re-linearize the
3152// non-Boolean terms.
3153void CpModelPresolver::TryToReduceCoefficientsOfLinearConstraint(
3154 int c, ConstraintProto* ct) {
3155 if (ct->constraint_case() != ConstraintProto::kLinear) return;
3156 if (context_->ModelIsUnsat()) return;
3157
3158 // Only consider "simple" constraints.
3159 const LinearConstraintProto& lin = ct->linear();
3160 if (lin.domain().size() != 2) return;
3161 const Domain rhs = ReadDomainFromProto(lin);
3162
3163 // Precompute a bunch of quantities and "canonicalize" the constraint.
3164 int64_t lb_sum = 0;
3165 int64_t ub_sum = 0;
3166 int64_t max_variation = 0;
3167
3168 rd_entries_.clear();
3169 rd_magnitudes_.clear();
3170 rd_lbs_.clear();
3171 rd_ubs_.clear();
3172
3173 int64_t max_magnitude = 0;
3174 const int num_terms = lin.vars().size();
3175 for (int i = 0; i < num_terms; ++i) {
3176 const int64_t coeff = lin.coeffs(i);
3177 const int64_t magnitude = std::abs(lin.coeffs(i));
3178 if (magnitude == 0) continue;
3179 max_magnitude = std::max(max_magnitude, magnitude);
3180
3181 int64_t lb;
3182 int64_t ub;
3183 if (coeff > 0) {
3184 lb = context_->MinOf(lin.vars(i));
3185 ub = context_->MaxOf(lin.vars(i));
3186 } else {
3187 lb = -context_->MaxOf(lin.vars(i));
3188 ub = -context_->MinOf(lin.vars(i));
3189 }
3190 lb_sum += lb * magnitude;
3191 ub_sum += ub * magnitude;
3192
3193 // Abort if fixed term, that might mess up code below.
3194 if (lb == ub) return;
3195
3196 rd_lbs_.push_back(lb);
3197 rd_ubs_.push_back(ub);
3198 rd_magnitudes_.push_back(magnitude);
3199 rd_entries_.push_back({magnitude, magnitude * (ub - lb), i});
3200 max_variation += rd_entries_.back().max_variation;
3201 }
3202
3203 // Mark trivially false constraint as such. This should have been already
3204 // done, but we require non-negative quantity below.
3205 if (lb_sum > rhs.Max() || rhs.Min() > ub_sum) {
3206 (void)MarkConstraintAsFalse(ct);
3207 context_->UpdateConstraintVariableUsage(c);
3208 return;
3209 }
3210 const IntegerValue rhs_ub(CapSub(rhs.Max(), lb_sum));
3211 const IntegerValue rhs_lb(CapSub(ub_sum, rhs.Min()));
3212 const bool use_ub = max_variation > rhs_ub;
3213 const bool use_lb = max_variation > rhs_lb;
3214 if (!use_ub && !use_lb) {
3215 (void)RemoveConstraint(ct);
3216 context_->UpdateConstraintVariableUsage(c);
3217 return;
3218 }
3219
3220 // No point doing more work for constraint with all coeff at +/-1.
3221 if (max_magnitude <= 1) return;
3222
3223 // TODO(user): All the lb/ub_feasible/infeasible class are updated in
3224 // exactly the same way. Find a more efficient algo?
3225 if (use_lb) {
3226 lb_feasible_.Reset(rhs_lb.value());
3227 lb_infeasible_.Reset(rhs.Min() - lb_sum - 1);
3228 }
3229 if (use_ub) {
3230 ub_feasible_.Reset(rhs_ub.value());
3231 ub_infeasible_.Reset(ub_sum - rhs.Max() - 1);
3232 }
3233
3234 // Process entries by decreasing magnitude. Update max_error to correspond
3235 // only to the sum of the not yet processed terms.
3236 uint64_t gcd = 0;
3237 int64_t max_error = max_variation;
3238 std::stable_sort(rd_entries_.begin(), rd_entries_.end(),
3239 [](const RdEntry& a, const RdEntry& b) {
3240 return a.magnitude > b.magnitude;
3241 });
3242 int64_t range = 0;
3243 rd_divisors_.clear();
3244 for (int i = 0; i < rd_entries_.size(); ++i) {
3245 const RdEntry& e = rd_entries_[i];
3246 gcd = std::gcd(gcd, e.magnitude);
3247 max_error -= e.max_variation;
3248
3249 // We regroup all term with the same coefficient into one.
3250 //
3251 // TODO(user): I am not sure there is no possible simplification across two
3252 // term with the same coeff, but it should be rare if it ever happens.
3253 range += e.max_variation / e.magnitude;
3254 if (i + 1 < rd_entries_.size() &&
3255 e.magnitude == rd_entries_[i + 1].magnitude) {
3256 continue;
3257 }
3258 const int64_t saved_range = range;
3259 range = 0;
3260
3261 if (e.magnitude > 1) {
3262 if ((!use_ub ||
3263 max_error <= PositiveRemainder(rhs_ub, IntegerValue(e.magnitude))) &&
3264 (!use_lb ||
3265 max_error <= PositiveRemainder(rhs_lb, IntegerValue(e.magnitude)))) {
3266 rd_divisors_.push_back(e.magnitude);
3267 }
3268 }
3269
3270 bool simplify_lb = false;
3271 if (use_lb) {
3272 lb_feasible_.AddMultiples(e.magnitude, saved_range);
3273 lb_infeasible_.AddMultiples(e.magnitude, saved_range);
3274
3275 // For a <= constraint, the max_feasible + error is still feasible.
3276 if (CapAdd(lb_feasible_.CurrentMax(), max_error) <=
3277 lb_feasible_.Bound()) {
3278 simplify_lb = true;
3279 }
3280 // For a <= constraint describing the infeasible set, the max_infeasible +
3281 // error is still infeasible.
3282 if (CapAdd(lb_infeasible_.CurrentMax(), max_error) <=
3283 lb_infeasible_.Bound()) {
3284 simplify_lb = true;
3285 }
3286 } else {
3287 simplify_lb = true;
3288 }
3289 bool simplify_ub = false;
3290 if (use_ub) {
3291 ub_feasible_.AddMultiples(e.magnitude, saved_range);
3292 ub_infeasible_.AddMultiples(e.magnitude, saved_range);
3293 if (CapAdd(ub_feasible_.CurrentMax(), max_error) <=
3294 ub_feasible_.Bound()) {
3295 simplify_ub = true;
3296 }
3297 if (CapAdd(ub_infeasible_.CurrentMax(), max_error) <=
3298 ub_infeasible_.Bound()) {
3299 simplify_ub = true;
3300 }
3301 } else {
3302 simplify_ub = true;
3303 }
3304
3305 if (max_error == 0) break; // Last term.
3306 if (simplify_lb && simplify_ub) {
3307 // We have a simplification since the second part can be ignored.
3308 context_->UpdateRuleStats("linear: remove irrelevant part");
3309 int64_t shift_lb = 0;
3310 int64_t shift_ub = 0;
3311 rd_vars_.clear();
3312 rd_coeffs_.clear();
3313 for (int j = 0; j <= i; ++j) {
3314 const int index = rd_entries_[j].index;
3315 const int64_t m = rd_magnitudes_[index];
3316 shift_lb += rd_lbs_[index] * m;
3317 shift_ub += rd_ubs_[index] * m;
3318 rd_vars_.push_back(lin.vars(index));
3319 rd_coeffs_.push_back(lin.coeffs(index));
3320 }
3321 LinearConstraintProto* mut_lin = ct->mutable_linear();
3322 mut_lin->mutable_vars()->Assign(rd_vars_.begin(), rd_vars_.end());
3323 mut_lin->mutable_coeffs()->Assign(rd_coeffs_.begin(), rd_coeffs_.end());
3324
3325 // The constraint become:
3326 // sum ci (X - lb) <= rhs_ub
3327 // sum ci (ub - X) <= rhs_lb
3328 // sum ci ub - rhs_lb <= sum ci X <= rhs_ub + sum ci lb.
3329 const int64_t new_rhs_lb =
3330 use_lb ? shift_ub - lb_feasible_.CurrentMax() : shift_lb;
3331 const int64_t new_rhs_ub =
3332 use_ub ? shift_lb + ub_feasible_.CurrentMax() : shift_ub;
3333 if (new_rhs_lb > new_rhs_ub) {
3334 (void)MarkConstraintAsFalse(ct);
3335 context_->UpdateConstraintVariableUsage(c);
3336 return;
3337 }
3338 FillDomainInProto(Domain(new_rhs_lb, new_rhs_ub), mut_lin);
3339 DivideLinearByGcd(ct);
3340 context_->UpdateConstraintVariableUsage(c);
3341 return;
3342 }
3343 }
3344
3345 if (gcd > 1) {
3346 // This might happen as a result of extra reduction after we already tried
3347 // this reduction.
3348 if (DivideLinearByGcd(ct)) {
3349 context_->UpdateConstraintVariableUsage(c);
3350 }
3351 return;
3352 }
3353
3354 // We didn't remove any irrelevant part, but we might be able to tighten
3355 // the constraint bound.
3356 if ((use_lb && lb_feasible_.CurrentMax() < lb_feasible_.Bound()) ||
3357 (use_ub && ub_feasible_.CurrentMax() < ub_feasible_.Bound())) {
3358 context_->UpdateRuleStats("linear: reduce rhs with DP");
3359 const int64_t new_rhs_lb =
3360 use_lb ? ub_sum - lb_feasible_.CurrentMax() : lb_sum;
3361 const int64_t new_rhs_ub =
3362 use_ub ? lb_sum + ub_feasible_.CurrentMax() : ub_sum;
3363 if (new_rhs_lb > new_rhs_ub) {
3364 (void)MarkConstraintAsFalse(ct);
3365 context_->UpdateConstraintVariableUsage(c);
3366 return;
3367 }
3368 FillDomainInProto(Domain(new_rhs_lb, new_rhs_ub), ct->mutable_linear());
3369 }
3370
3371 // Limit the number of "divisor" we try for approximate gcd.
3372 if (rd_divisors_.size() > 3) rd_divisors_.resize(3);
3373 for (const int64_t divisor : rd_divisors_) {
3374 // Try the <= side first.
3375 int64_t new_ub;
3377 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, rhs.Max(), &new_ub)) {
3378 continue;
3379 }
3380
3381 // The other side.
3382 int64_t minus_new_lb;
3383 for (int i = 0; i < rd_lbs_.size(); ++i) {
3384 std::swap(rd_lbs_[i], rd_ubs_[i]);
3385 rd_lbs_[i] = -rd_lbs_[i];
3386 rd_ubs_[i] = -rd_ubs_[i];
3387 }
3389 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, -rhs.Min(),
3390 &minus_new_lb)) {
3391 for (int i = 0; i < rd_lbs_.size(); ++i) {
3392 std::swap(rd_lbs_[i], rd_ubs_[i]);
3393 rd_lbs_[i] = -rd_lbs_[i];
3394 rd_ubs_[i] = -rd_ubs_[i];
3395 }
3396 continue;
3397 }
3398
3399 // Rewrite the constraint !
3400 context_->UpdateRuleStats("linear: simplify using approximate gcd");
3401 int new_size = 0;
3402 LinearConstraintProto* mutable_linear = ct->mutable_linear();
3403 for (int i = 0; i < lin.coeffs().size(); ++i) {
3404 const int64_t new_coeff =
3405 ClosestMultiple(lin.coeffs(i), divisor) / divisor;
3406 if (new_coeff == 0) continue;
3407 mutable_linear->set_vars(new_size, lin.vars(i));
3408 mutable_linear->set_coeffs(new_size, new_coeff);
3409 ++new_size;
3410 }
3411 mutable_linear->mutable_vars()->Truncate(new_size);
3412 mutable_linear->mutable_coeffs()->Truncate(new_size);
3413 const Domain new_rhs = Domain(-minus_new_lb, new_ub);
3414 if (new_rhs.IsEmpty()) {
3415 (void)MarkConstraintAsFalse(ct);
3416 } else {
3417 FillDomainInProto(new_rhs, mutable_linear);
3418 }
3419 context_->UpdateConstraintVariableUsage(c);
3420 return;
3421 }
3422}
3423
3424namespace {
3425
3426// In the equation terms + coeff * var_domain \included rhs, returns true if can
3427// we always fix rhs to its min value for any value in terms. It is okay to
3428// not be as generic as possible here.
3429bool RhsCanBeFixedToMin(int64_t coeff, const Domain& var_domain,
3430 const Domain& terms, const Domain& rhs) {
3431 if (var_domain.NumIntervals() != 1) return false;
3432 if (std::abs(coeff) != 1) return false;
3433
3434 // If for all values in terms, there is one value below rhs.Min(), then
3435 // because we add only one integer interval, if there is a feasible value, it
3436 // can be at rhs.Min().
3437 //
3438 // TODO(user): generalize to larger coeff magnitude if rhs is also a multiple
3439 // or if terms is a multiple.
3440 if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
3441 return true;
3442 }
3443 if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
3444 return true;
3445 }
3446 return false;
3447}
3448
3449bool RhsCanBeFixedToMax(int64_t coeff, const Domain& var_domain,
3450 const Domain& terms, const Domain& rhs) {
3451 if (var_domain.NumIntervals() != 1) return false;
3452 if (std::abs(coeff) != 1) return false;
3453
3454 if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
3455 return true;
3456 }
3457 if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
3458 return true;
3459 }
3460 return false;
3461}
3462
3463int FixLiteralFromSet(const absl::flat_hash_set<int>& literals_at_true,
3464 LinearConstraintProto* linear) {
3465 int new_size = 0;
3466 int num_fixed = 0;
3467 const int num_terms = linear->vars().size();
3468 int64_t shift = 0;
3469 for (int i = 0; i < num_terms; ++i) {
3470 const int var = linear->vars(i);
3471 const int64_t coeff = linear->coeffs(i);
3472 if (literals_at_true.contains(var)) {
3473 // Var is at one.
3474 shift += coeff;
3475 ++num_fixed;
3476 } else if (!literals_at_true.contains(NegatedRef(var))) {
3477 linear->set_vars(new_size, var);
3478 linear->set_coeffs(new_size, coeff);
3479 ++new_size;
3480 } else {
3481 ++num_fixed;
3482 // Else the variable is at zero.
3483 }
3484 }
3485 linear->mutable_vars()->Truncate(new_size);
3486 linear->mutable_coeffs()->Truncate(new_size);
3487 if (shift != 0) {
3488 FillDomainInProto(ReadDomainFromProto(*linear).AdditionWith(Domain(-shift)),
3489 linear);
3490 }
3491 return num_fixed;
3492}
3493
3494} // namespace
3495
3496void CpModelPresolver::ProcessAtMostOneAndLinear() {
3497 if (time_limit_->LimitReached()) return;
3498 if (context_->ModelIsUnsat()) return;
3499 if (context_->params().presolve_inclusion_work_limit() == 0) return;
3500 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
3501
3502 ActivityBoundHelper amo_in_linear;
3503 amo_in_linear.AddAllAtMostOnes(*context_->working_model);
3504
3505 int num_changes = 0;
3506 const int num_constraints = context_->working_model->constraints_size();
3507 for (int c = 0; c < num_constraints; ++c) {
3508 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
3509 if (ct->constraint_case() != ConstraintProto::kLinear) continue;
3510
3511 // We loop if the constraint changed.
3512 for (int i = 0; i < 5; ++i) {
3513 const int old_size = ct->linear().vars().size();
3514 const int old_enf_size = ct->enforcement_literal().size();
3515 ProcessOneLinearWithAmo(c, ct, &amo_in_linear);
3516 if (context_->ModelIsUnsat()) return;
3517 if (ct->constraint_case() != ConstraintProto::kLinear) break;
3518 if (ct->linear().vars().size() == old_size &&
3519 ct->enforcement_literal().size() == old_enf_size) {
3520 break;
3521 }
3522 ++num_changes;
3523 }
3524 }
3525
3526 timer.AddCounter("num_changes", num_changes);
3527}
3528
3529// TODO(user): Similarly amo and bool_or intersection or amo and enforcement
3530// literals list can be presolved.
3531//
3532// TODO(user): This is stronger than the fully included case. Avoid having
3533// the second code?
3534void CpModelPresolver::ProcessOneLinearWithAmo(int ct_index,
3535 ConstraintProto* ct,
3536 ActivityBoundHelper* helper) {
3537 if (ct->constraint_case() != ConstraintProto::kLinear) return;
3538 if (ct->linear().vars().size() <= 1) return;
3539
3540 // TODO(user): It is possible in some corner-case that the linear constraint
3541 // is NOT canonicalized. This is because we might detect equivalence here and
3542 // we will continue with ProcessOneLinearWithAmo() in the parent loop.
3543 tmp_terms_.clear();
3544 temp_ct_.Clear();
3545 Domain non_boolean_domain(0);
3546 const int initial_size = ct->linear().vars().size();
3547 int64_t min_magnitude = std::numeric_limits<int64_t>::max();
3548 int64_t max_magnitude = 0;
3549 for (int i = 0; i < initial_size; ++i) {
3550 // TODO(user): Just do not use negative reference in linear!
3551 int ref = ct->linear().vars(i);
3552 int64_t coeff = ct->linear().coeffs(i);
3553 if (!RefIsPositive(ref)) {
3554 ref = NegatedRef(ref);
3555 coeff = -coeff;
3556 }
3557 if (context_->CanBeUsedAsLiteral(ref)) {
3558 tmp_terms_.push_back({ref, coeff});
3559 min_magnitude = std::min(min_magnitude, std::abs(coeff));
3560 max_magnitude = std::max(max_magnitude, std::abs(coeff));
3561 } else {
3562 non_boolean_domain =
3563 non_boolean_domain
3564 .AdditionWith(
3565 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
3566 .RelaxIfTooComplex();
3567 temp_ct_.mutable_linear()->add_vars(ref);
3568 temp_ct_.mutable_linear()->add_coeffs(coeff);
3569 }
3570 }
3571
3572 // Skip if there are no Booleans.
3573 if (tmp_terms_.empty()) return;
3574
3575 // Detect encoded AMO.
3576 //
3577 // TODO(user): Support more coefficient strengthening cases.
3578 // For instance on neos-954925.pb.gz we have stuff like:
3579 // 20 * (AMO1 + AMO2) - [coeff in 48 to 53] >= -15
3580 // this is really AMO1 + AMO2 - 2 * AMO3 >= 0.
3581 // Maybe if we reify the AMO to exactly one, this is visible since large
3582 // AMO can be rewriten with single variable (1 - extra var in exactly one).
3583 const Domain rhs = ReadDomainFromProto(ct->linear());
3584 if (non_boolean_domain == Domain(0) && rhs.NumIntervals() == 1 &&
3585 min_magnitude < max_magnitude) {
3586 int64_t min_activity = 0;
3587 int64_t max_activity = 0;
3588 for (const auto [ref, coeff] : tmp_terms_) {
3589 if (coeff > 0) {
3590 max_activity += coeff;
3591 } else {
3592 min_activity += coeff;
3593 }
3594 }
3595 const int64_t transformed_rhs = rhs.Max() - min_activity;
3596 if (min_activity >= rhs.Min() && max_magnitude <= transformed_rhs) {
3597 std::vector<int> literals;
3598 for (const auto [ref, coeff] : tmp_terms_) {
3599 if (coeff + min_magnitude > transformed_rhs) continue;
3600 literals.push_back(coeff > 0 ? ref : NegatedRef(ref));
3601 }
3602 if (helper->IsAmo(literals)) {
3603 // We actually have an at-most-one in disguise.
3604 context_->UpdateRuleStats("linear + amo: detect hidden AMO");
3605 int64_t shift = 0;
3606 for (int i = 0; i < initial_size; ++i) {
3607 CHECK(RefIsPositive(ct->linear().vars(i)));
3608 if (ct->linear().coeffs(i) > 0) {
3609 ct->mutable_linear()->set_coeffs(i, 1);
3610 } else {
3611 ct->mutable_linear()->set_coeffs(i, -1);
3612 shift -= 1;
3613 }
3614 }
3615 FillDomainInProto(Domain(shift, shift + 1), ct->mutable_linear());
3616 return;
3617 }
3618 }
3619 }
3620
3621 // Get more precise activity estimate based on at most one and heuristics.
3622 const int64_t min_bool_activity =
3623 helper->ComputeMinActivity(tmp_terms_, &conditional_mins_);
3624 const int64_t max_bool_activity =
3625 helper->ComputeMaxActivity(tmp_terms_, &conditional_maxs_);
3626
3627 // Detect trivially true/false constraint under these new bounds.
3628 // TODO(user): relax rhs if only one side is trivial.
3629 const Domain activity = non_boolean_domain.AdditionWith(
3630 Domain(min_bool_activity, max_bool_activity));
3631 if (activity.IntersectionWith(rhs).IsEmpty()) {
3632 // Note that this covers min_bool_activity > max_bool_activity.
3633 context_->UpdateRuleStats("linear + amo: infeasible linear constraint");
3634 (void)MarkConstraintAsFalse(ct);
3635 context_->UpdateConstraintVariableUsage(ct_index);
3636 return;
3637 } else if (activity.IsIncludedIn(rhs)) {
3638 context_->UpdateRuleStats("linear + amo: trivial linear constraint");
3639 ct->Clear();
3640 context_->UpdateConstraintVariableUsage(ct_index);
3641 return;
3642 }
3643
3644 // We can use the new bound to propagate other terms.
3645 if (ct->enforcement_literal().empty() && !temp_ct_.linear().vars().empty()) {
3647 rhs.AdditionWith(
3648 Domain(min_bool_activity, max_bool_activity).Negation()),
3649 temp_ct_.mutable_linear());
3650 if (!PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_)) {
3651 return;
3652 }
3653 if (context_->ModelIsUnsat()) return;
3654 }
3655
3656 // Extract enforcement or fix literal.
3657 //
3658 // TODO(user): Do not use domain fonction, can be slow.
3659 //
3660 // TODO(user): Actually we might make the linear relaxation worse by
3661 // extracting some of these enforcement, as those can be "lifted" booleans. We
3662 // currently deal with that in RemoveEnforcementThatMakesConstraintTrivial(),
3663 // but that might not be the most efficient.
3664 //
3665 // TODO(user): Another reason for making the LP worse is that if we replace
3666 // part of the constraint via FindBig*LinearOverlap() then our activity bounds
3667 // might not be as precise when we will linearize this constraint again.
3668 std::vector<int> new_enforcement;
3669 std::vector<int> must_be_true;
3670 for (int i = 0; i < tmp_terms_.size(); ++i) {
3671 const int ref = tmp_terms_[i].first;
3672
3673 const Domain bool0(conditional_mins_[i][0], conditional_maxs_[i][0]);
3674 const Domain activity0 = bool0.AdditionWith(non_boolean_domain);
3675 if (activity0.IntersectionWith(rhs).IsEmpty()) {
3676 // Must be 1.
3677 must_be_true.push_back(ref);
3678 } else if (activity0.IsIncludedIn(rhs)) {
3679 // Trivial constraint on 0.
3680 new_enforcement.push_back(ref);
3681 }
3682
3683 const Domain bool1(conditional_mins_[i][1], conditional_maxs_[i][1]);
3684 const Domain activity1 = bool1.AdditionWith(non_boolean_domain);
3685 if (activity1.IntersectionWith(rhs).IsEmpty()) {
3686 // Must be 0.
3687 must_be_true.push_back(NegatedRef(ref));
3688 } else if (activity1.IsIncludedIn(rhs)) {
3689 // Trivial constraint on 1.
3690 new_enforcement.push_back(NegatedRef(ref));
3691 }
3692 }
3693
3694 // Note that both list can be non empty, if for instance we have small * X +
3695 // big * Y + ... <= rhs and amo(X, Y). We could see that Y can never be true
3696 // and if X is true, then the constraint could be trivial.
3697 //
3698 // So we fix things first if we can.
3699 if (ct->enforcement_literal().empty() && !must_be_true.empty()) {
3700 // Note that our logic to do more presolve iteration depends on the
3701 // number of rule applied, so it is important to count this correctly.
3702 context_->UpdateRuleStats("linear + amo: fixed literal",
3703 must_be_true.size());
3704 for (const int lit : must_be_true) {
3705 if (!context_->SetLiteralToTrue(lit)) return;
3706 }
3707 CanonicalizeLinear(ct);
3708 context_->UpdateConstraintVariableUsage(ct_index);
3709 return;
3710 }
3711
3712 if (!new_enforcement.empty()) {
3713 context_->UpdateRuleStats("linear + amo: extracted enforcement literal",
3714 new_enforcement.size());
3715 for (const int ref : new_enforcement) {
3716 ct->add_enforcement_literal(ref);
3717 }
3718 }
3719
3720 if (!ct->enforcement_literal().empty()) {
3721 const int old_enf_size = ct->enforcement_literal().size();
3722 if (!helper->PresolveEnforcement(ct->linear().vars(), ct, &temp_set_)) {
3723 context_->UpdateRuleStats("linear + amo: infeasible enforcement");
3724 ct->Clear();
3725 context_->UpdateConstraintVariableUsage(ct_index);
3726 return;
3727 }
3728 if (ct->enforcement_literal().size() < old_enf_size) {
3729 context_->UpdateRuleStats("linear + amo: simplified enforcement list");
3730 context_->UpdateConstraintVariableUsage(ct_index);
3731 }
3732
3733 for (const int lit : must_be_true) {
3734 if (temp_set_.contains(NegatedRef(lit))) {
3735 // A literal must be true but is incompatible with what the enforcement
3736 // implies. The constraint must be false!
3737 context_->UpdateRuleStats(
3738 "linear + amo: advanced infeasible linear constraint");
3739 (void)MarkConstraintAsFalse(ct);
3740 context_->UpdateConstraintVariableUsage(ct_index);
3741 return;
3742 }
3743 }
3744
3745 // TODO(user): do that in more cases?
3746 if (ct->enforcement_literal().size() == 1 && !must_be_true.empty()) {
3747 // Add implication, and remove literal from the constraint in this case.
3748 // To remove them, we just add them to temp_set_ and FixLiteralFromSet()
3749 // will take care of it.
3750 context_->UpdateRuleStats("linear + amo: added implications");
3751 ConstraintProto* new_ct = context_->working_model->add_constraints();
3752 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
3753 for (const int lit : must_be_true) {
3754 new_ct->mutable_bool_and()->add_literals(lit);
3755 temp_set_.insert(lit);
3756 }
3757 context_->UpdateNewConstraintsVariableUsage();
3758 }
3759
3760 const int num_fixed = FixLiteralFromSet(temp_set_, ct->mutable_linear());
3761 if (num_fixed > new_enforcement.size()) {
3762 context_->UpdateRuleStats(
3763 "linear + amo: fixed literal implied by enforcement");
3764 }
3765 if (num_fixed > 0) {
3766 context_->UpdateConstraintVariableUsage(ct_index);
3767 }
3768 }
3769
3770 if (ct->linear().vars().empty()) {
3771 context_->UpdateRuleStats("linear + amo: empty after processing");
3772 PresolveSmallLinear(ct);
3773 context_->UpdateConstraintVariableUsage(ct_index);
3774 return;
3775 }
3776
3777 // If the constraint is of size 1 or 2, we re-presolve it right away.
3778 if (initial_size != ct->linear().vars().size() && PresolveSmallLinear(ct)) {
3779 context_->UpdateConstraintVariableUsage(ct_index);
3780 if (ct->constraint_case() != ConstraintProto::kLinear) return;
3781 }
3782
3783 // Detect enforcement literal that could actually be lifted, and as such can
3784 // just be removed from the enforcement list. Ideally, during relaxation we
3785 // would lift such Boolean again.
3786 //
3787 // Note that this code is independent from anything above.
3788 if (!ct->enforcement_literal().empty()) {
3789 // TODO(user): remove duplication with code above?
3790 tmp_terms_.clear();
3791 Domain non_boolean_domain(0);
3792 const int num_ct_terms = ct->linear().vars().size();
3793 for (int i = 0; i < num_ct_terms; ++i) {
3794 const int ref = ct->linear().vars(i);
3795 const int64_t coeff = ct->linear().coeffs(i);
3796 CHECK(RefIsPositive(ref));
3797 if (context_->CanBeUsedAsLiteral(ref)) {
3798 tmp_terms_.push_back({ref, coeff});
3799 } else {
3800 non_boolean_domain =
3801 non_boolean_domain
3802 .AdditionWith(
3803 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
3804 .RelaxIfTooComplex();
3805 }
3806 }
3807 const int num_removed = helper->RemoveEnforcementThatMakesConstraintTrivial(
3808 tmp_terms_, non_boolean_domain, ReadDomainFromProto(ct->linear()), ct);
3809 if (num_removed > 0) {
3810 context_->UpdateRuleStats("linear + amo: removed enforcement literal",
3811 num_removed);
3812 context_->UpdateConstraintVariableUsage(ct_index);
3813 }
3814 }
3815}
3816
3817bool CpModelPresolver::PropagateDomainsInLinear(int ct_index,
3818 ConstraintProto* ct) {
3819 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
3820 if (context_->ModelIsUnsat()) return false;
3821
3822 // For fast mode.
3823 int64_t min_activity;
3824 int64_t max_activity;
3825
3826 // For slow mode.
3827 const int num_vars = ct->linear().vars_size();
3828 auto& term_domains = context_->tmp_term_domains;
3829 auto& left_domains = context_->tmp_left_domains;
3830 const bool slow_mode = num_vars < 10;
3831
3832 // Compute the implied rhs bounds from the variable ones.
3833 if (slow_mode) {
3834 term_domains.resize(num_vars + 1);
3835 left_domains.resize(num_vars + 1);
3836 left_domains[0] = Domain(0);
3837 term_domains[num_vars] = Domain(0);
3838 for (int i = 0; i < num_vars; ++i) {
3839 const int var = ct->linear().vars(i);
3840 const int64_t coeff = ct->linear().coeffs(i);
3841 DCHECK(RefIsPositive(var));
3842 term_domains[i] = context_->DomainOf(var).MultiplicationBy(coeff);
3843 left_domains[i + 1] =
3844 left_domains[i].AdditionWith(term_domains[i]).RelaxIfTooComplex();
3845 }
3846 } else {
3847 std::tie(min_activity, max_activity) =
3848 context_->ComputeMinMaxActivity(ct->linear());
3849 }
3850 const Domain& implied_rhs =
3851 slow_mode ? left_domains[num_vars] : Domain(min_activity, max_activity);
3852
3853 // Abort if trivial.
3854 const Domain old_rhs = ReadDomainFromProto(ct->linear());
3855 if (implied_rhs.IsIncludedIn(old_rhs)) {
3856 if (ct_index != -1) context_->UpdateRuleStats("linear: always true");
3857 return RemoveConstraint(ct);
3858 }
3859
3860 // Incorporate the implied rhs information.
3861 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
3862 if (rhs.IsEmpty()) {
3863 context_->UpdateRuleStats("linear: infeasible");
3864 return MarkConstraintAsFalse(ct);
3865 }
3866 if (rhs != old_rhs) {
3867 if (ct_index != -1) context_->UpdateRuleStats("linear: simplified rhs");
3868 }
3869 FillDomainInProto(rhs, ct->mutable_linear());
3870
3871 // Propagate the variable bounds.
3872 if (ct->enforcement_literal().size() > 1) return false;
3873
3874 bool new_bounds = false;
3875 bool recanonicalize = false;
3876 Domain negated_rhs = rhs.Negation();
3877 Domain right_domain(0);
3878 Domain new_domain;
3879 Domain activity_minus_term;
3880 for (int i = num_vars - 1; i >= 0; --i) {
3881 const int var = ct->linear().vars(i);
3882 const int64_t var_coeff = ct->linear().coeffs(i);
3883
3884 if (slow_mode) {
3885 right_domain =
3886 right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
3887 activity_minus_term = left_domains[i].AdditionWith(right_domain);
3888 } else {
3889 int64_t min_term = var_coeff * context_->MinOf(var);
3890 int64_t max_term = var_coeff * context_->MaxOf(var);
3891 if (var_coeff < 0) std::swap(min_term, max_term);
3892 activity_minus_term =
3893 Domain(min_activity - min_term, max_activity - max_term);
3894 }
3895 new_domain = activity_minus_term.AdditionWith(negated_rhs)
3896 .InverseMultiplicationBy(-var_coeff);
3897
3898 if (ct->enforcement_literal().empty()) {
3899 // Push the new domain.
3900 if (!context_->IntersectDomainWith(var, new_domain, &new_bounds)) {
3901 return true;
3902 }
3903 } else if (ct->enforcement_literal().size() == 1) {
3904 // We cannot push the new domain, but we can add some deduction.
3905 CHECK(RefIsPositive(var));
3906 if (!context_->DomainOfVarIsIncludedIn(var, new_domain)) {
3907 context_->deductions.AddDeduction(ct->enforcement_literal(0), var,
3908 new_domain);
3909 }
3910 }
3911
3912 if (context_->IsFixed(var)) {
3913 // This will make sure we remove that fixed variable from the constraint.
3914 recanonicalize = true;
3915 continue;
3916 }
3917
3918 // The other transformations below require a non-reified constraint.
3919 if (ct_index == -1) continue;
3920 if (!ct->enforcement_literal().empty()) continue;
3921
3922 // Given a variable that only appear in one constraint and in the
3923 // objective, for any feasible solution, it will be always better to move
3924 // this singleton variable as much as possible towards its good objective
3925 // direction. Sometime, we can detect that we will always be able to
3926 // do this until the only constraint of this singleton variable is tight.
3927 //
3928 // When this happens, we can make the constraint an equality. Note that it
3929 // might not always be good to restrict constraint like this, but in this
3930 // case, the RemoveSingletonInLinear() code should be able to remove this
3931 // variable altogether.
3932 if (rhs.Min() != rhs.Max() &&
3933 context_->VariableWithCostIsUniqueAndRemovable(var)) {
3934 const int64_t obj_coeff = context_->ObjectiveMap().at(var);
3935 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
3936 bool fixed = false;
3937 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->DomainOf(var),
3938 activity_minus_term, rhs)) {
3939 rhs = Domain(rhs.Min());
3940 fixed = true;
3941 }
3942 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->DomainOf(var),
3943 activity_minus_term, rhs)) {
3944 rhs = Domain(rhs.Max());
3945 fixed = true;
3946 }
3947 if (fixed) {
3948 context_->UpdateRuleStats("linear: tightened into equality");
3949 // Compute a new `var` hint so that the lhs of `ct` is equal to `rhs`.
3950 solution_crush_.SetVarToLinearConstraintSolution(
3951 i, ct->linear().vars(), ct->linear().coeffs(), rhs.FixedValue());
3952 FillDomainInProto(rhs, ct->mutable_linear());
3953 negated_rhs = rhs.Negation();
3954
3955 // Restart the loop.
3956 i = num_vars;
3957 right_domain = Domain(0);
3958 continue;
3959 }
3960 }
3961
3962 // Can we perform some substitution?
3963 //
3964 // TODO(user): there is no guarantee we will not miss some since we might
3965 // not reprocess a constraint once other have been deleted.
3966
3967 // Skip affine constraint. It is more efficient to substitute them lazily
3968 // when we process other constraints. Note that if we relax the fact that
3969 // we substitute only equalities, we can deal with inequality of size 2
3970 // here.
3971 if (ct->linear().vars().size() <= 2) continue;
3972
3973 // TODO(user): We actually do not need a strict equality when
3974 // keep_all_feasible_solutions is false, but that simplifies things as the
3975 // SubstituteVariable() function cannot fail this way.
3976 if (rhs.Min() != rhs.Max()) continue;
3977
3978 // NOTE: The mapping doesn't allow us to remove a variable if
3979 // keep_all_feasible_solutions is true.
3980 //
3981 // TODO(user): This shouldn't be necessary, but caused some failure on
3982 // IntModExpandTest.FzTest. Fix.
3983 if (context_->params().keep_all_feasible_solutions_in_presolve()) continue;
3984
3985 // Only consider "implied free" variables. Note that the coefficient of
3986 // magnitude 1 is important otherwise we can't easily remove the
3987 // constraint since the fact that the sum of the other terms must be a
3988 // multiple of coeff will not be enforced anymore.
3989 if (std::abs(var_coeff) != 1) continue;
3990 if (context_->params().presolve_substitution_level() <= 0) continue;
3991
3992 // Only consider substitution that reduce the number of entries.
3993 const bool is_in_objective = context_->VarToConstraints(var).contains(-1);
3994 {
3995 int col_size = context_->VarToConstraints(var).size();
3996 if (is_in_objective) col_size--;
3997 const int row_size = ct->linear().vars_size();
3998
3999 // This is actually an upper bound on the number of entries added since
4000 // some of them might already be present.
4001 const int num_entries_added = (row_size - 1) * (col_size - 1);
4002 const int num_entries_removed = col_size + row_size - 1;
4003 if (num_entries_added > num_entries_removed) continue;
4004 }
4005
4006 // Check pre-conditions on all the constraints in which this variable
4007 // appear. Basically they must all be linear.
4008 std::vector<int> others;
4009 bool abort = false;
4010 for (const int c : context_->VarToConstraints(var)) {
4011 if (c == kObjectiveConstraint) continue;
4012 if (c == kAffineRelationConstraint) {
4013 abort = true;
4014 break;
4015 }
4016 if (c == ct_index) continue;
4017 if (context_->working_model->constraints(c).constraint_case() !=
4018 ConstraintProto::kLinear) {
4019 abort = true;
4020 break;
4021 }
4022 for (const int ref :
4023 context_->working_model->constraints(c).enforcement_literal()) {
4024 if (PositiveRef(ref) == var) {
4025 abort = true;
4026 break;
4027 }
4028 }
4029 if (abort) break;
4030 others.push_back(c);
4031 }
4032 if (abort) continue;
4033
4034 // If the domain implied by this constraint is the same as the current
4035 // domain of the variable, this variable is implied free. Otherwise, we
4036 // check if the intersection with the domain implied by another constraint
4037 // make it implied free.
4038 if (context_->DomainOf(var) != new_domain) {
4039 // We only do that for doubleton because we don't want the propagation to
4040 // be less strong. If we were to replace this variable in other constraint
4041 // the implied bound from the linear expression might not be as good.
4042 //
4043 // TODO(user): We still substitute even if this happens in the objective
4044 // though. Is that good?
4045 if (others.size() != 1) continue;
4046 const ConstraintProto& other_ct =
4047 context_->working_model->constraints(others.front());
4048 if (!other_ct.enforcement_literal().empty()) continue;
4049
4050 // Compute the implied domain using the other constraint.
4051 // We only do that if it is not too long to avoid quadratic worst case.
4052 const LinearConstraintProto& other_lin = other_ct.linear();
4053 if (other_lin.vars().size() > 100) continue;
4054 Domain implied = ReadDomainFromProto(other_lin);
4055 int64_t other_coeff = 0;
4056 for (int i = 0; i < other_lin.vars().size(); ++i) {
4057 const int v = other_lin.vars(i);
4058 const int64_t coeff = other_lin.coeffs(i);
4059 if (v == var) {
4060 // It is possible the constraint is not canonical if it wasn't
4061 // processed yet !
4062 other_coeff += coeff;
4063 } else {
4064 implied =
4065 implied
4066 .AdditionWith(context_->DomainOf(v).MultiplicationBy(-coeff))
4067 .RelaxIfTooComplex();
4068 }
4069 }
4070 if (other_coeff == 0) continue;
4071 implied = implied.InverseMultiplicationBy(other_coeff);
4072
4073 // Since we compute it, we can as well update the domain right now.
4074 // This is also needed for postsolve to have a tight domain.
4075 if (!context_->IntersectDomainWith(var, implied)) return false;
4076 if (context_->IsFixed(var)) continue;
4077 if (new_domain.IntersectionWith(implied) != context_->DomainOf(var)) {
4078 continue;
4079 }
4080
4081 context_->UpdateRuleStats("linear: doubleton free");
4082 }
4083
4084 // Substitute in objective.
4085 // This can fail in overflow corner cases, so we abort before doing any
4086 // actual changes.
4087 if (is_in_objective &&
4088 !context_->SubstituteVariableInObjective(var, var_coeff, *ct)) {
4089 continue;
4090 }
4091
4092 // Do the actual substitution.
4093 ConstraintProto copy_if_we_abort;
4094 absl::c_sort(others);
4095 for (const int c : others) {
4096 // TODO(user): The copy is needed to have a simpler overflow-checking
4097 // code were we check once the substitution is done. If needed we could
4098 // optimize that, but with more code.
4099 copy_if_we_abort = context_->working_model->constraints(c);
4100
4101 // In some corner cases, this might violate our overflow precondition or
4102 // even create an overflow. The danger is limited since the range of the
4103 // linear expression used in the definition do not exceed the domain of
4104 // the variable we substitute. But this is not the case for the doubleton
4105 // case above.
4106 if (!SubstituteVariable(
4107 var, var_coeff, *ct,
4108 context_->working_model->mutable_constraints(c))) {
4109 // The function above can fail because of overflow, but also if the
4110 // constraint was not canonicalized yet and the variable is actually not
4111 // there (we have var - var for instance).
4112 //
4113 // TODO(user): we canonicalize it right away, but I am not sure it is
4114 // really needed.
4115 if (CanonicalizeLinear(
4116 context_->working_model->mutable_constraints(c))) {
4117 context_->UpdateConstraintVariableUsage(c);
4118 }
4119 abort = true;
4120 break;
4121 }
4122
4124 *context_->working_model,
4125 context_->working_model->constraints(c).linear().vars(),
4126 context_->working_model->constraints(c).linear().coeffs())) {
4127 // Revert the change in this case.
4128 *context_->working_model->mutable_constraints(c) = copy_if_we_abort;
4129 abort = true;
4130 break;
4131 }
4132
4133 // TODO(user): We should re-enqueue these constraints for presolve.
4134 context_->UpdateConstraintVariableUsage(c);
4135 }
4136 if (abort) continue;
4137
4138 context_->UpdateRuleStats(
4139 absl::StrCat("linear: variable substitution ", others.size()));
4140
4141 // The variable now only appear in its definition and we can remove it
4142 // because it was implied free.
4143 //
4144 // Tricky: If the linear constraint contains other variables that are only
4145 // used here, then the postsolve needs more info. We do need to indicate
4146 // that whatever the value of those other variables, we will have a way to
4147 // assign var. We do that by putting it fist.
4148 CHECK_EQ(context_->VarToConstraints(var).size(), 1);
4149 context_->MarkVariableAsRemoved(var);
4150 ConstraintProto* mapping_ct =
4151 context_->NewMappingConstraint(__FILE__, __LINE__);
4152 *mapping_ct = *ct;
4153 LinearConstraintProto* mapping_linear_ct = mapping_ct->mutable_linear();
4154 std::swap(mapping_linear_ct->mutable_vars()->at(0),
4155 mapping_linear_ct->mutable_vars()->at(i));
4156 std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
4157 mapping_linear_ct->mutable_coeffs()->at(i));
4158 return RemoveConstraint(ct);
4159 }
4160
4161 // special case.
4162 if (ct_index == -1) {
4163 if (new_bounds) {
4164 context_->UpdateRuleStats(
4165 "linear: reduced variable domains in derived constraint");
4166 }
4167 return false;
4168 }
4169
4170 if (new_bounds) {
4171 context_->UpdateRuleStats("linear: reduced variable domains");
4172 }
4173 if (recanonicalize) return CanonicalizeLinear(ct);
4174 return false;
4175}
4176
4177// The constraint from its lower value is sum positive_coeff * X <= rhs.
4178// If from_lower_bound is false, then it is the constraint from its upper value.
4179void CpModelPresolver::LowerThanCoeffStrengthening(bool from_lower_bound,
4180 int64_t min_magnitude,
4181 int64_t rhs,
4182 ConstraintProto* ct) {
4183 const LinearConstraintProto& arg = ct->linear();
4184 const int64_t second_threshold = rhs - min_magnitude;
4185 const int num_vars = arg.vars_size();
4186
4187 // Special case:
4188 // - The terms above rhs must be fixed to zero.
4189 // - The terms in (second_threshold, rhs] can be fixed to rhs as
4190 // they will force all other terms to zero if not at zero themselves.
4191 // - If what is left can be simplified to a single coefficient, we can
4192 // put the constraint into a special form.
4193 //
4194 // TODO(user): More generally, if we ignore term that set everything else to
4195 // zero, we can preprocess the constraint left and then add them back. So we
4196 // can do all our other reduction like normal GCD or more advanced ones like
4197 // DP based or approximate GCD.
4198 if (min_magnitude <= second_threshold) {
4199 // Compute max_magnitude for the term <= second_threshold.
4200 int64_t max_magnitude_left = 0;
4201 int64_t max_activity_left = 0;
4202 int64_t activity_when_coeff_are_one = 0;
4203 int64_t gcd = 0;
4204 for (int i = 0; i < num_vars; ++i) {
4205 const int64_t magnitude = std::abs(arg.coeffs(i));
4206 if (magnitude <= second_threshold) {
4207 gcd = std::gcd(gcd, magnitude);
4208 max_magnitude_left = std::max(max_magnitude_left, magnitude);
4209 const int64_t bound_diff =
4210 context_->MaxOf(arg.vars(i)) - context_->MinOf(arg.vars(i));
4211 activity_when_coeff_are_one += bound_diff;
4212 max_activity_left += magnitude * bound_diff;
4213 }
4214 }
4215 CHECK_GT(min_magnitude, 0);
4216 CHECK_LE(min_magnitude, max_magnitude_left);
4217
4218 // Not considering the variable that set everyone at zero when true:
4219 int64_t new_rhs = 0;
4220 bool set_all_to_one = false;
4221 if (max_activity_left <= rhs) {
4222 // We are left with a trivial constraint.
4223 context_->UpdateRuleStats("linear with partial amo: trivial");
4224 new_rhs = activity_when_coeff_are_one;
4225 set_all_to_one = true;
4226 } else if (rhs / min_magnitude == rhs / max_magnitude_left) {
4227 // We are left with a sum <= new_rhs constraint.
4228 context_->UpdateRuleStats("linear with partial amo: constant coeff");
4229 new_rhs = rhs / min_magnitude;
4230 set_all_to_one = true;
4231 } else if (gcd > 1) {
4232 // We are left with a constraint that can be simplified by gcd.
4233 context_->UpdateRuleStats("linear with partial amo: gcd");
4234 new_rhs = rhs / gcd;
4235 }
4236
4237 if (new_rhs > 0) {
4238 int64_t rhs_offset = 0;
4239 for (int i = 0; i < num_vars; ++i) {
4240 const int ref = arg.vars(i);
4241 const int64_t coeff = from_lower_bound ? arg.coeffs(i) : -arg.coeffs(i);
4242
4243 int64_t new_coeff;
4244 const int64_t magnitude = std::abs(coeff);
4245 if (magnitude > rhs) {
4246 new_coeff = new_rhs + 1;
4247 } else if (magnitude > second_threshold) {
4248 new_coeff = new_rhs;
4249 } else {
4250 new_coeff = set_all_to_one ? 1 : magnitude / gcd;
4251 }
4252
4253 // In the transformed domain we will always have
4254 // magnitude * (var - lb) or magnitude * (ub - var)
4255 if (coeff > 0) {
4256 ct->mutable_linear()->set_coeffs(i, new_coeff);
4257 rhs_offset += new_coeff * context_->MinOf(ref);
4258 } else {
4259 ct->mutable_linear()->set_coeffs(i, -new_coeff);
4260 rhs_offset -= new_coeff * context_->MaxOf(ref);
4261 }
4262 }
4263 FillDomainInProto(Domain(rhs_offset, new_rhs + rhs_offset),
4264 ct->mutable_linear());
4265 return;
4266 }
4267 }
4268
4269 int64_t rhs_offset = 0;
4270 for (int i = 0; i < num_vars; ++i) {
4271 int ref = arg.vars(i);
4272 int64_t coeff = arg.coeffs(i);
4273 if (coeff < 0) {
4274 ref = NegatedRef(ref);
4275 coeff = -coeff;
4276 }
4277
4278 if (coeff > rhs) {
4279 if (ct->enforcement_literal().empty()) {
4280 // Shifted variable must be zero.
4281 //
4282 // TODO(user): Note that here IntersectDomainWith() can only return
4283 // false if for some reason this variable has an affine representative
4284 // for which this fail. Ideally we should always replace/merge
4285 // representative right away, but this is a bit difficult to enforce
4286 // currently.
4287 context_->UpdateRuleStats("linear: fix variable to its bound.");
4288 if (!context_->IntersectDomainWith(
4289 ref, Domain(from_lower_bound ? context_->MinOf(ref)
4290 : context_->MaxOf(ref)))) {
4291 return;
4292 }
4293 }
4294
4295 // TODO(user): What to do with the coeff if there is enforcement?
4296 continue;
4297 }
4298 if (coeff > second_threshold && coeff < rhs) {
4299 context_->UpdateRuleStats(
4300 "linear: coefficient strengthening by increasing it.");
4301 if (from_lower_bound) {
4302 // coeff * (X - LB + LB) -> rhs * (X - LB) + coeff * LB
4303 rhs_offset -= (coeff - rhs) * context_->MinOf(ref);
4304 } else {
4305 // coeff * (X - UB + UB) -> rhs * (X - UB) + coeff * UB
4306 rhs_offset -= (coeff - rhs) * context_->MaxOf(ref);
4307 }
4308 ct->mutable_linear()->set_coeffs(i, arg.coeffs(i) > 0 ? rhs : -rhs);
4309 }
4310 }
4311 if (rhs_offset != 0) {
4312 FillDomainInProto(ReadDomainFromProto(arg).AdditionWith(Domain(rhs_offset)),
4313 ct->mutable_linear());
4314 }
4315}
4316
4317// Identify Boolean variable that makes the constraint always true when set to
4318// true or false. Moves such literal to the constraint enforcement literals
4319// list.
4320//
4321// We also generalize this to integer variable at one of their bound.
4322//
4323// This operation is similar to coefficient strengthening in the MIP world.
4324void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
4325 int ct_index, ConstraintProto* ct) {
4326 if (ct->constraint_case() != ConstraintProto::kLinear) return;
4327 if (context_->ModelIsUnsat()) return;
4328
4329 const LinearConstraintProto& arg = ct->linear();
4330 const int num_vars = arg.vars_size();
4331
4332 // No need to process size one constraints, they will be presolved separately.
4333 // We also do not want to split them in two.
4334 if (num_vars <= 1) return;
4335
4336 int64_t min_sum = 0;
4337 int64_t max_sum = 0;
4338 int64_t max_coeff_magnitude = 0;
4339 int64_t min_coeff_magnitude = std::numeric_limits<int64_t>::max();
4340 for (int i = 0; i < num_vars; ++i) {
4341 const int ref = arg.vars(i);
4342 const int64_t coeff = arg.coeffs(i);
4343 if (coeff > 0) {
4344 max_coeff_magnitude = std::max(max_coeff_magnitude, coeff);
4345 min_coeff_magnitude = std::min(min_coeff_magnitude, coeff);
4346 min_sum += coeff * context_->MinOf(ref);
4347 max_sum += coeff * context_->MaxOf(ref);
4348 } else {
4349 max_coeff_magnitude = std::max(max_coeff_magnitude, -coeff);
4350 min_coeff_magnitude = std::min(min_coeff_magnitude, -coeff);
4351 min_sum += coeff * context_->MaxOf(ref);
4352 max_sum += coeff * context_->MinOf(ref);
4353 }
4354 }
4355 if (max_coeff_magnitude == 1) return;
4356
4357 // We can only extract enforcement literals if the maximum coefficient
4358 // magnitude is large enough. Note that we handle complex domain.
4359 //
4360 // TODO(user): Depending on how we split below, the threshold are not the
4361 // same. This is maybe not too important, we just don't split as often as we
4362 // could, but it is still unclear if splitting is good.
4363 const auto& domain = ct->linear().domain();
4364 const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
4365 const int64_t lb_threshold = max_sum - domain[1];
4366 if (max_coeff_magnitude + min_coeff_magnitude <
4367 std::max(ub_threshold, lb_threshold)) {
4368 // We also have other kind of coefficient strengthening.
4369 // In something like 3x + 5y <= 6, the coefficient 5 can be changed to 6.
4370 // And in 5x + 12y <= 12, the coeff 5 can be changed to 6 (not sure how to
4371 // generalize this one).
4372 if (domain.size() == 2 && min_coeff_magnitude > 1 &&
4373 min_coeff_magnitude < max_coeff_magnitude) {
4374 const int64_t rhs_min = domain[0];
4375 const int64_t rhs_max = domain[1];
4376 if (min_sum >= rhs_min &&
4377 max_coeff_magnitude + min_coeff_magnitude > rhs_max - min_sum) {
4378 LowerThanCoeffStrengthening(/*from_lower_bound=*/true,
4379 min_coeff_magnitude, rhs_max - min_sum, ct);
4380 return;
4381 }
4382 if (max_sum <= rhs_max &&
4383 max_coeff_magnitude + min_coeff_magnitude > max_sum - rhs_min) {
4384 LowerThanCoeffStrengthening(/*from_lower_bound=*/false,
4385 min_coeff_magnitude, max_sum - rhs_min, ct);
4386 return;
4387 }
4388 }
4389 }
4390
4391 // We need the constraint to be only bounded on one side in order to extract
4392 // enforcement literal.
4393 //
4394 // If it is boxed and we know that some coefficient are big enough (see test
4395 // above), then we split the constraint in two. That might not seems always
4396 // good, but for the CP propagation engine, we don't loose anything by doing
4397 // so, and for the LP we will regroup the constraints if they still have the
4398 // exact same coeff after the presolve.
4399 //
4400 // TODO(user): Creating two new constraints and removing the current one might
4401 // not be the most efficient, but it simplify the presolve code by not having
4402 // to do anything special to trigger a new presolving of these constraints.
4403 // Try to improve if this becomes a problem.
4404 const Domain rhs_domain = ReadDomainFromProto(ct->linear());
4405 const bool lower_bounded = min_sum < rhs_domain.Min();
4406 const bool upper_bounded = max_sum > rhs_domain.Max();
4407 if (!lower_bounded && !upper_bounded) return;
4408 if (lower_bounded && upper_bounded) {
4409 // We disable this for now.
4410 if (true) return;
4411
4412 // Lets not split except if we extract enforcement.
4413 if (max_coeff_magnitude < std::max(ub_threshold, lb_threshold)) return;
4414
4415 context_->UpdateRuleStats("linear: split boxed constraint");
4416 ConstraintProto* new_ct1 = context_->working_model->add_constraints();
4417 *new_ct1 = *ct;
4418 if (!ct->name().empty()) {
4419 new_ct1->set_name(absl::StrCat(ct->name(), " (part 1)"));
4420 }
4421 FillDomainInProto(Domain(min_sum, rhs_domain.Max()),
4422 new_ct1->mutable_linear());
4423
4424 ConstraintProto* new_ct2 = context_->working_model->add_constraints();
4425 *new_ct2 = *ct;
4426 if (!ct->name().empty()) {
4427 new_ct2->set_name(absl::StrCat(ct->name(), " (part 2)"));
4428 }
4429 FillDomainInProto(rhs_domain.UnionWith(Domain(rhs_domain.Max(), max_sum)),
4430 new_ct2->mutable_linear());
4431
4432 context_->UpdateNewConstraintsVariableUsage();
4433 ct->Clear();
4434 context_->UpdateConstraintVariableUsage(ct_index);
4435 return;
4436 }
4437
4438 // Any coefficient greater than this will cause the constraint to be trivially
4439 // satisfied when the variable move away from its bound. Note that as we
4440 // remove coefficient, the threshold do not change!
4441 const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
4442
4443 // All coeffs in [second_threshold, threshold) can be reduced to
4444 // second_threshold.
4445 //
4446 // TODO(user): If 2 * min_coeff_magnitude >= bound, then the constraint can
4447 // be completely rewriten to 2 * (enforcement_part) + sum var >= 2 which is
4448 // what happen eventually when bound is even, but not if it is odd currently.
4449 int64_t second_threshold =
4450 std::max(MathUtil::CeilOfRatio(threshold, int64_t{2}),
4451 threshold - min_coeff_magnitude);
4452
4453 // Tricky: The second threshold only work if the domain is simple. If the
4454 // domain has holes, changing the coefficient might change whether the
4455 // variable can be at one or not by herself.
4456 //
4457 // TODO(user): We could still reduce it to the smaller value with same
4458 // feasibility.
4459 if (rhs_domain.NumIntervals() > 1) {
4460 second_threshold = threshold; // Disable.
4461 }
4462
4463 // Do we only extract Booleans?
4464 //
4465 // Note that for now the default is false, and also there are problem calling
4466 // GetOrCreateVarValueEncoding() after expansion because we might have removed
4467 // the variable used in the encoding.
4468 const bool only_extract_booleans =
4469 !context_->params().presolve_extract_integer_enforcement() ||
4470 context_->ModelIsExpanded();
4471
4472 // To avoid a quadratic loop, we will rewrite the linear expression at the
4473 // same time as we extract enforcement literals.
4474 int new_size = 0;
4475 int64_t rhs_offset = 0;
4476 bool some_integer_encoding_were_extracted = false;
4477 LinearConstraintProto* mutable_arg = ct->mutable_linear();
4478 for (int i = 0; i < arg.vars_size(); ++i) {
4479 int ref = arg.vars(i);
4480 int64_t coeff = arg.coeffs(i);
4481 if (coeff < 0) {
4482 ref = NegatedRef(ref);
4483 coeff = -coeff;
4484 }
4485
4486 // TODO(user): If the encoding Boolean already exist, we could extract
4487 // the non-Boolean enforcement term.
4488 const bool is_boolean = context_->CanBeUsedAsLiteral(ref);
4489 if (context_->IsFixed(ref) || coeff < threshold ||
4490 (only_extract_booleans && !is_boolean)) {
4491 mutable_arg->set_vars(new_size, mutable_arg->vars(i));
4492
4493 int64_t new_magnitude = std::abs(arg.coeffs(i));
4494 if (coeff > threshold) {
4495 // We keep this term but reduces its coeff.
4496 // This is only for the case where only_extract_booleans == true.
4497 new_magnitude = threshold;
4498 context_->UpdateRuleStats("linear: coefficient strenghtening.");
4499 } else if (coeff > second_threshold && coeff < threshold) {
4500 // This cover the special case where one big + on small is enough
4501 // to satisfy the constraint, we can reduce the big.
4502 new_magnitude = second_threshold;
4503 context_->UpdateRuleStats(
4504 "linear: advanced coefficient strenghtening.");
4505 }
4506 if (coeff != new_magnitude) {
4507 if (lower_bounded) {
4508 // coeff * (X - LB + LB) -> new_magnitude * (X - LB) + coeff * LB
4509 rhs_offset -= (coeff - new_magnitude) * context_->MinOf(ref);
4510 } else {
4511 // coeff * (X - UB + UB) -> new_magnitude * (X - UB) + coeff * UB
4512 rhs_offset -= (coeff - new_magnitude) * context_->MaxOf(ref);
4513 }
4514 }
4515
4516 mutable_arg->set_coeffs(
4517 new_size, arg.coeffs(i) > 0 ? new_magnitude : -new_magnitude);
4518 ++new_size;
4519 continue;
4520 }
4521
4522 if (is_boolean) {
4523 context_->UpdateRuleStats("linear: extracted enforcement literal");
4524 } else {
4525 some_integer_encoding_were_extracted = true;
4526 context_->UpdateRuleStats(
4527 "linear: extracted integer enforcement literal");
4528 }
4529 if (lower_bounded) {
4530 ct->add_enforcement_literal(is_boolean
4531 ? NegatedRef(ref)
4532 : context_->GetOrCreateVarValueEncoding(
4533 ref, context_->MinOf(ref)));
4534 rhs_offset -= coeff * context_->MinOf(ref);
4535 } else {
4536 ct->add_enforcement_literal(is_boolean
4537 ? ref
4538 : context_->GetOrCreateVarValueEncoding(
4539 ref, context_->MaxOf(ref)));
4540 rhs_offset -= coeff * context_->MaxOf(ref);
4541 }
4542 }
4543 mutable_arg->mutable_vars()->Truncate(new_size);
4544 mutable_arg->mutable_coeffs()->Truncate(new_size);
4545 FillDomainInProto(rhs_domain.AdditionWith(Domain(rhs_offset)), mutable_arg);
4546 if (some_integer_encoding_were_extracted || new_size == 1) {
4547 context_->UpdateConstraintVariableUsage(ct_index);
4548 context_->UpdateNewConstraintsVariableUsage();
4549 }
4550}
4551
4552void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto* ct) {
4553 if (context_->ModelIsUnsat()) return;
4554 if (HasEnforcementLiteral(*ct)) return;
4555 const Domain rhs = ReadDomainFromProto(ct->linear());
4556
4557 const LinearConstraintProto& arg = ct->linear();
4558 const int num_vars = arg.vars_size();
4559 int64_t min_sum = 0;
4560 int64_t max_sum = 0;
4561 for (int i = 0; i < num_vars; ++i) {
4562 const int ref = arg.vars(i);
4563 const int64_t coeff = arg.coeffs(i);
4564 const int64_t term_a = coeff * context_->MinOf(ref);
4565 const int64_t term_b = coeff * context_->MaxOf(ref);
4566 min_sum += std::min(term_a, term_b);
4567 max_sum += std::max(term_a, term_b);
4568 }
4569 for (const int type : {0, 1}) {
4570 std::vector<int> at_most_one;
4571 for (int i = 0; i < num_vars; ++i) {
4572 const int ref = arg.vars(i);
4573 const int64_t coeff = arg.coeffs(i);
4574 if (context_->MinOf(ref) != 0) continue;
4575 if (context_->MaxOf(ref) != 1) continue;
4576
4577 if (type == 0) {
4578 // TODO(user): we could add one more Boolean with a lower coeff as long
4579 // as we have lower_coeff + min_of_other_coeff > rhs.Max().
4580 if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
4581 at_most_one.push_back(coeff > 0 ? ref : NegatedRef(ref));
4582 }
4583 } else {
4584 if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
4585 at_most_one.push_back(coeff > 0 ? NegatedRef(ref) : ref);
4586 }
4587 }
4588 }
4589 if (at_most_one.size() > 1) {
4590 if (type == 0) {
4591 context_->UpdateRuleStats("linear: extracted at most one (max).");
4592 } else {
4593 context_->UpdateRuleStats("linear: extracted at most one (min).");
4594 }
4595 ConstraintProto* new_ct = context_->working_model->add_constraints();
4596 new_ct->set_name(ct->name());
4597 for (const int ref : at_most_one) {
4598 new_ct->mutable_at_most_one()->add_literals(ref);
4599 }
4600 context_->UpdateNewConstraintsVariableUsage();
4601 }
4602 }
4603}
4604
4605// Convert some linear constraint involving only Booleans to their Boolean
4606// form.
4607bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto* ct) {
4608 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
4609 if (context_->ModelIsUnsat()) return false;
4610
4611 // For special kind of constraint detection.
4612 int64_t sum_of_coeffs = 0;
4613 int num_positive = 0;
4614 int num_negative = 0;
4615
4616 const LinearConstraintProto& arg = ct->linear();
4617 const int num_vars = arg.vars_size();
4618 int64_t min_coeff = std::numeric_limits<int64_t>::max();
4619 int64_t max_coeff = 0;
4620 int64_t min_sum = 0;
4621 int64_t max_sum = 0;
4622 for (int i = 0; i < num_vars; ++i) {
4623 // We assume we already ran PresolveLinear().
4624 const int var = arg.vars(i);
4625 const int64_t coeff = arg.coeffs(i);
4626 CHECK(RefIsPositive(var));
4627 CHECK_NE(coeff, 0);
4628 if (context_->MinOf(var) != 0) return false;
4629 if (context_->MaxOf(var) != 1) return false;
4630
4631 sum_of_coeffs += coeff;
4632 if (coeff > 0) {
4633 ++num_positive;
4634 max_sum += coeff;
4635 min_coeff = std::min(min_coeff, coeff);
4636 max_coeff = std::max(max_coeff, coeff);
4637 } else {
4638 // We replace the Boolean ref, by a ref to its negation (1 - x).
4639 ++num_negative;
4640 min_sum += coeff;
4641 min_coeff = std::min(min_coeff, -coeff);
4642 max_coeff = std::max(max_coeff, -coeff);
4643 }
4644 }
4645 CHECK_LE(min_coeff, max_coeff);
4646
4647 // Detect trivially true/false constraints. Note that this is not necessarily
4648 // detected by PresolveLinear(). We do that here because we assume below
4649 // that this cannot happen.
4650 //
4651 // TODO(user): this could be generalized to constraint not containing only
4652 // Booleans.
4653 const Domain rhs_domain = ReadDomainFromProto(arg);
4654 if ((!rhs_domain.Contains(min_sum) &&
4655 min_sum + min_coeff > rhs_domain.Max()) ||
4656 (!rhs_domain.Contains(max_sum) &&
4657 max_sum - min_coeff < rhs_domain.Min())) {
4658 context_->UpdateRuleStats("linear: all booleans and trivially false");
4659 return MarkConstraintAsFalse(ct);
4660 }
4661 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
4662 context_->UpdateRuleStats("linear: all booleans and trivially true");
4663 return RemoveConstraint(ct);
4664 }
4665
4666 // This discover cases like "A + B + C - 3*D = 0"
4667 // where all Booleans must be equivalent!
4668 // This happens a lot on woodlands09.mps for instance.
4669 //
4670 // TODO(user): generalize if enforced?
4671 // TODO(user): generalize to other variant! Use DP to identify constraint with
4672 // just one or two solutions? or a few solution with same variable values?
4673 if (ct->enforcement_literal().empty() && sum_of_coeffs == 0 &&
4674 (num_negative == 1 || num_positive == 1) && rhs_domain.IsFixed() &&
4675 rhs_domain.FixedValue() == 0) {
4676 // This forces either all variable at 1 or all at zero.
4677 context_->UpdateRuleStats("linear: all equivalent!");
4678 for (int i = 1; i < num_vars; ++i) {
4679 if (!context_->StoreBooleanEqualityRelation(ct->linear().vars(0),
4680 ct->linear().vars(i))) {
4681 return false;
4682 }
4683 }
4684 return RemoveConstraint(ct);
4685 }
4686
4687 // Detect clauses, reified ands, at_most_one.
4688 //
4689 // TODO(user): split a == 1 constraint or similar into a clause and an at
4690 // most one constraint?
4691 DCHECK(!rhs_domain.IsEmpty());
4692 if (min_sum + min_coeff > rhs_domain.Max()) {
4693 // All Boolean are false if the reified literal is true.
4694 context_->UpdateRuleStats("linear: negative reified and");
4695 const auto copy = arg;
4696 ct->mutable_bool_and()->clear_literals();
4697 for (int i = 0; i < num_vars; ++i) {
4698 ct->mutable_bool_and()->add_literals(
4699 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
4700 }
4701 PresolveBoolAnd(ct);
4702 return true;
4703 } else if (max_sum - min_coeff < rhs_domain.Min()) {
4704 // All Boolean are true if the reified literal is true.
4705 context_->UpdateRuleStats("linear: positive reified and");
4706 const auto copy = arg;
4707 ct->mutable_bool_and()->clear_literals();
4708 for (int i = 0; i < num_vars; ++i) {
4709 ct->mutable_bool_and()->add_literals(
4710 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
4711 }
4712 PresolveBoolAnd(ct);
4713 return true;
4714 } else if (min_sum + min_coeff >= rhs_domain.Min() &&
4715 rhs_domain.front().end >= max_sum) {
4716 // At least one Boolean is true.
4717 context_->UpdateRuleStats("linear: positive clause");
4718 const auto copy = arg;
4719 ct->mutable_bool_or()->clear_literals();
4720 for (int i = 0; i < num_vars; ++i) {
4721 ct->mutable_bool_or()->add_literals(
4722 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
4723 }
4724 PresolveBoolOr(ct);
4725 return true;
4726 } else if (max_sum - min_coeff <= rhs_domain.Max() &&
4727 rhs_domain.back().start <= min_sum) {
4728 // At least one Boolean is false.
4729 context_->UpdateRuleStats("linear: negative clause");
4730 const auto copy = arg;
4731 ct->mutable_bool_or()->clear_literals();
4732 for (int i = 0; i < num_vars; ++i) {
4733 ct->mutable_bool_or()->add_literals(
4734 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
4735 }
4736 PresolveBoolOr(ct);
4737 return true;
4738 } else if (!HasEnforcementLiteral(*ct) &&
4739 min_sum + max_coeff <= rhs_domain.Max() &&
4740 min_sum + 2 * min_coeff > rhs_domain.Max() &&
4741 rhs_domain.back().start <= min_sum) {
4742 // At most one Boolean is true.
4743 // TODO(user): Support enforced at most one.
4744 context_->UpdateRuleStats("linear: positive at most one");
4745 const auto copy = arg;
4746 ct->mutable_at_most_one()->clear_literals();
4747 for (int i = 0; i < num_vars; ++i) {
4748 ct->mutable_at_most_one()->add_literals(
4749 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
4750 }
4751 return true;
4752 } else if (!HasEnforcementLiteral(*ct) &&
4753 max_sum - max_coeff >= rhs_domain.Min() &&
4754 max_sum - 2 * min_coeff < rhs_domain.Min() &&
4755 rhs_domain.front().end >= max_sum) {
4756 // At most one Boolean is false.
4757 // TODO(user): Support enforced at most one.
4758 context_->UpdateRuleStats("linear: negative at most one");
4759 const auto copy = arg;
4760 ct->mutable_at_most_one()->clear_literals();
4761 for (int i = 0; i < num_vars; ++i) {
4762 ct->mutable_at_most_one()->add_literals(
4763 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
4764 }
4765 return true;
4766 } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
4767 min_sum < rhs_domain.Min() &&
4768 min_sum + min_coeff >= rhs_domain.Min() &&
4769 min_sum + 2 * min_coeff > rhs_domain.Max() &&
4770 min_sum + max_coeff <= rhs_domain.Max()) {
4771 // TODO(user): Support enforced exactly one.
4772 context_->UpdateRuleStats("linear: positive equal one");
4773 ConstraintProto* exactly_one = context_->working_model->add_constraints();
4774 exactly_one->set_name(ct->name());
4775 for (int i = 0; i < num_vars; ++i) {
4776 exactly_one->mutable_exactly_one()->add_literals(
4777 arg.coeffs(i) > 0 ? arg.vars(i) : NegatedRef(arg.vars(i)));
4778 }
4779 context_->UpdateNewConstraintsVariableUsage();
4780 return RemoveConstraint(ct);
4781 } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
4782 max_sum > rhs_domain.Max() &&
4783 max_sum - min_coeff <= rhs_domain.Max() &&
4784 max_sum - 2 * min_coeff < rhs_domain.Min() &&
4785 max_sum - max_coeff >= rhs_domain.Min()) {
4786 // TODO(user): Support enforced exactly one.
4787 context_->UpdateRuleStats("linear: negative equal one");
4788 ConstraintProto* exactly_one = context_->working_model->add_constraints();
4789 exactly_one->set_name(ct->name());
4790 for (int i = 0; i < num_vars; ++i) {
4791 exactly_one->mutable_exactly_one()->add_literals(
4792 arg.coeffs(i) > 0 ? NegatedRef(arg.vars(i)) : arg.vars(i));
4793 }
4794 context_->UpdateNewConstraintsVariableUsage();
4795 return RemoveConstraint(ct);
4796 }
4797
4798 // Expand small expression into clause.
4799 //
4800 // TODO(user): This is bad from a LP relaxation perspective. Do not do that
4801 // now? On another hand it is good for the SAT presolving.
4802 if (num_vars > 3) return false;
4803 context_->UpdateRuleStats("linear: small Boolean expression");
4804
4805 // Enumerate all possible value of the Booleans and add a clause if constraint
4806 // is false. TODO(user): the encoding could be made better in some cases.
4807 const int max_mask = (1 << arg.vars_size());
4808 for (int mask = 0; mask < max_mask; ++mask) {
4809 int64_t value = 0;
4810 for (int i = 0; i < num_vars; ++i) {
4811 if ((mask >> i) & 1) value += arg.coeffs(i);
4812 }
4813 if (rhs_domain.Contains(value)) continue;
4814
4815 // Add a new clause to exclude this bad assignment.
4816 ConstraintProto* new_ct = context_->working_model->add_constraints();
4817 auto* new_arg = new_ct->mutable_bool_or();
4818 if (HasEnforcementLiteral(*ct)) {
4819 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
4820 }
4821 for (int i = 0; i < num_vars; ++i) {
4822 new_arg->add_literals(((mask >> i) & 1) ? NegatedRef(arg.vars(i))
4823 : arg.vars(i));
4824 }
4825 }
4826
4827 context_->UpdateNewConstraintsVariableUsage();
4828 return RemoveConstraint(ct);
4829}
4830
4831bool CpModelPresolver::PresolveInterval(int c, ConstraintProto* ct) {
4832 if (context_->ModelIsUnsat()) return false;
4833 IntervalConstraintProto* interval = ct->mutable_interval();
4834
4835 // If the size is < 0, then the interval cannot be performed.
4836 if (!ct->enforcement_literal().empty() && context_->SizeMax(c) < 0) {
4837 context_->UpdateRuleStats("interval: negative size implies unperformed");
4838 return MarkConstraintAsFalse(ct);
4839 }
4840
4841 if (ct->enforcement_literal().empty()) {
4842 bool domain_changed = false;
4843 // Size can't be negative.
4844 if (!context_->IntersectDomainWith(
4845 interval->size(), Domain(0, std::numeric_limits<int64_t>::max()),
4846 &domain_changed)) {
4847 return false;
4848 }
4849 if (domain_changed) {
4850 context_->UpdateRuleStats(
4851 "interval: performed intervals must have a positive size");
4852 }
4853 }
4854
4855 // Note that the linear relation is stored elsewhere, so it is safe to just
4856 // remove such special interval constraint.
4857 if (context_->ConstraintVariableGraphIsUpToDate() &&
4858 context_->IntervalUsage(c) == 0) {
4859 context_->UpdateRuleStats("intervals: removed unused interval");
4860 return RemoveConstraint(ct);
4861 }
4862
4863 bool changed = false;
4864 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_start());
4865 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_size());
4866 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_end());
4867 return changed;
4868}
4869
4870// TODO(user): avoid code duplication between expand and presolve.
4871bool CpModelPresolver::PresolveInverse(ConstraintProto* ct) {
4872 const int size = ct->inverse().f_direct().size();
4873 bool changed = false;
4874
4875 // Make sure the domains are included in [0, size - 1).
4876 for (const int ref : ct->inverse().f_direct()) {
4877 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
4878 VLOG(1) << "Empty domain for a variable in ExpandInverse()";
4879 return false;
4880 }
4881 }
4882 for (const int ref : ct->inverse().f_inverse()) {
4883 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
4884 VLOG(1) << "Empty domain for a variable in ExpandInverse()";
4885 return false;
4886 }
4887 }
4888
4889 // Detect duplicated variable.
4890 // Even with negated variables, the reduced domain in [0..size - 1]
4891 // implies that the constraint is infeasible if ref and its negation
4892 // appear together.
4893 {
4894 absl::flat_hash_set<int> direct_vars;
4895 for (const int ref : ct->inverse().f_direct()) {
4896 const auto [it, inserted] = direct_vars.insert(PositiveRef(ref));
4897 if (!inserted) {
4898 return context_->NotifyThatModelIsUnsat("inverse: duplicated variable");
4899 }
4900 }
4901
4902 absl::flat_hash_set<int> inverse_vars;
4903 for (const int ref : ct->inverse().f_inverse()) {
4904 const auto [it, inserted] = inverse_vars.insert(PositiveRef(ref));
4905 if (!inserted) {
4906 return context_->NotifyThatModelIsUnsat("inverse: duplicated variable");
4907 }
4908 }
4909 }
4910
4911 // Propagate from one vector to its counterpart.
4912 // Note this reaches the fixpoint as there is a one to one mapping between
4913 // (variable-value) pairs in each vector.
4914 const auto filter_inverse_domain =
4915 [this, size, &changed](const auto& direct, const auto& inverse) {
4916 // Build the set of values in the inverse vector.
4917 std::vector<absl::flat_hash_set<int64_t>> inverse_values(size);
4918 for (int i = 0; i < size; ++i) {
4919 const Domain domain = context_->DomainOf(inverse[i]);
4920 for (const int64_t j : domain.Values()) {
4921 inverse_values[i].insert(j);
4922 }
4923 }
4924
4925 // Propagate from the inverse vector to the direct vector. Reduce the
4926 // domains of each variable in the direct vector by checking that the
4927 // inverse value exists.
4928 std::vector<int64_t> possible_values;
4929 for (int i = 0; i < size; ++i) {
4930 possible_values.clear();
4931 const Domain domain = context_->DomainOf(direct[i]);
4932 bool removed_value = false;
4933 for (const int64_t j : domain.Values()) {
4934 if (inverse_values[j].contains(i)) {
4935 possible_values.push_back(j);
4936 } else {
4937 removed_value = true;
4938 }
4939 }
4940 if (removed_value) {
4941 changed = true;
4942 if (!context_->IntersectDomainWith(
4943 direct[i], Domain::FromValues(possible_values))) {
4944 VLOG(1) << "Empty domain for a variable in ExpandInverse()";
4945 return false;
4946 }
4947 }
4948 }
4949 return true;
4950 };
4951
4952 if (!filter_inverse_domain(ct->inverse().f_direct(),
4953 ct->inverse().f_inverse())) {
4954 return false;
4955 }
4956
4957 if (!filter_inverse_domain(ct->inverse().f_inverse(),
4958 ct->inverse().f_direct())) {
4959 return false;
4960 }
4961
4962 if (changed) {
4963 context_->UpdateRuleStats("inverse: reduce domains");
4964 }
4965
4966 return false;
4967}
4968
4969bool CpModelPresolver::PresolveElement(int c, ConstraintProto* ct) {
4970 if (context_->ModelIsUnsat()) return false;
4971
4972 if (ct->element().exprs().empty()) {
4973 context_->UpdateRuleStats("element: empty array");
4974 return context_->NotifyThatModelIsUnsat();
4975 }
4976
4977 bool changed = false;
4978 changed |= CanonicalizeLinearExpression(
4979 *ct, ct->mutable_element()->mutable_linear_index());
4980 changed |= CanonicalizeLinearExpression(
4981 *ct, ct->mutable_element()->mutable_linear_target());
4982 for (int i = 0; i < ct->element().exprs_size(); ++i) {
4983 changed |= CanonicalizeLinearExpression(
4984 *ct, ct->mutable_element()->mutable_exprs(i));
4985 }
4986
4987 const LinearExpressionProto& index = ct->element().linear_index();
4988 const LinearExpressionProto& target = ct->element().linear_target();
4989
4990 // TODO(user): think about this once we do have such constraint.
4991 if (HasEnforcementLiteral(*ct)) return false;
4992
4993 // Reduce index domain from the array size.
4994 {
4995 bool index_modified = false;
4996 if (!context_->IntersectDomainWith(
4997 index, Domain(0, ct->element().exprs_size() - 1),
4998 &index_modified)) {
4999 return false;
5000 }
5001 if (index_modified) {
5002 context_->UpdateRuleStats(
5003 "element: reduced index domain from array size");
5004 }
5005 }
5006
5007 // Special case if the index is fixed.
5008 if (context_->IsFixed(index)) {
5009 const int64_t index_value = context_->FixedValue(index);
5010 ConstraintProto* new_ct = context_->working_model->add_constraints();
5011 new_ct->mutable_linear()->add_domain(0);
5012 new_ct->mutable_linear()->add_domain(0);
5013 AddLinearExpressionToLinearConstraint(target, 1, new_ct->mutable_linear());
5014 AddLinearExpressionToLinearConstraint(ct->element().exprs(index_value), -1,
5015 new_ct->mutable_linear());
5016 context_->CanonicalizeLinearConstraint(new_ct);
5017 context_->UpdateNewConstraintsVariableUsage();
5018 context_->UpdateRuleStats("element: fixed index");
5019 return RemoveConstraint(ct);
5020 }
5021
5022 // We know index is not fixed.
5023 const int index_var = index.vars(0);
5024
5025 {
5026 // Cleanup the array: if exprs[i] contains index_var, fix its value.
5027 const Domain& index_var_domain = context_->DomainOf(index_var);
5028 std::vector<int64_t> reached_indices(ct->element().exprs_size(), false);
5029 for (const int64_t index_var_value : index_var_domain.Values()) {
5030 const int64_t index_value =
5031 AffineExpressionValueAt(index, index_var_value);
5032 reached_indices[index_value] = true;
5033 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5034 if (expr.vars_size() == 1 && expr.vars(0) == index_var) {
5035 const int64_t expr_value =
5036 AffineExpressionValueAt(expr, index_var_value);
5037 ct->mutable_element()->mutable_exprs(index_value)->clear_vars();
5038 ct->mutable_element()->mutable_exprs(index_value)->clear_coeffs();
5039 ct->mutable_element()
5040 ->mutable_exprs(index_value)
5041 ->set_offset(expr_value);
5042 changed = true;
5043 context_->UpdateRuleStats(
5044 "element: fix expression depending on the index");
5045 }
5046 }
5047
5048 // Cleanup the array: clear unreached expressions.
5049 for (int i = 0; i < ct->element().exprs_size(); ++i) {
5050 if (!reached_indices[i]) {
5051 ct->mutable_element()->mutable_exprs(i)->Clear();
5052 changed = true;
5053 }
5054 }
5055 }
5056
5057 // Canonicalization and cleanups of the expressions could have messed up the
5058 // var-constraint graph.
5059 if (changed) context_->UpdateConstraintVariableUsage(c);
5060
5061 // Reduces the domain of the index.
5062 {
5063 const Domain& index_var_domain = context_->DomainOf(index_var);
5064 const Domain& target_domain = context_->DomainSuperSetOf(target);
5065 std::vector<int64_t> possible_index_var_values;
5066 for (const int64_t index_var_value : index_var_domain.Values()) {
5067 const int64_t index_value =
5068 AffineExpressionValueAt(index, index_var_value);
5069 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5070
5071 bool is_possible_index;
5072 if (target.vars_size() == 1 && target.vars(0) == index_var) {
5073 // The target domain can be reduced if it shares its variable with the
5074 // index.
5075 is_possible_index = context_->DomainContains(
5076 expr, AffineExpressionValueAt(target, index_var_value));
5077 } else {
5078 const Domain target_var_domain =
5079 target.vars_size() == 1 ? context_->DomainOf(target.vars(0))
5080 : Domain(0);
5081 const Domain expr_var_domain = expr.vars_size() == 1
5082 ? context_->DomainOf(expr.vars(0))
5083 : Domain(0);
5084 const int64_t target_coeff =
5085 target.vars_size() == 1 ? target.coeffs(0) : 0;
5086 const int64_t expr_coeff = expr.vars_size() == 1 ? expr.coeffs(0) : 0;
5088 target_var_domain, target_coeff, expr_var_domain, -expr_coeff,
5089 -target.offset() + expr.offset());
5090 }
5091
5092 if (is_possible_index) {
5093 possible_index_var_values.push_back(index_var_value);
5094 } else {
5095 ct->mutable_element()->mutable_exprs(index_value)->Clear();
5096 changed = true;
5097 }
5098 }
5099 if (possible_index_var_values.size() < index_var_domain.Size()) {
5100 if (!context_->IntersectDomainWith(
5101 index_var, Domain::FromValues(possible_index_var_values))) {
5102 return true;
5103 }
5104 context_->UpdateRuleStats("element: reduced index domain ");
5105 // If the index is fixed, this is a equality constraint.
5106 if (context_->IsFixed(index)) {
5107 ConstraintProto* const eq = context_->working_model->add_constraints();
5108 eq->mutable_linear()->add_domain(0);
5109 eq->mutable_linear()->add_domain(0);
5110 AddLinearExpressionToLinearConstraint(target, 1, eq->mutable_linear());
5112 ct->element().exprs(context_->FixedValue(index)), -1,
5113 eq->mutable_linear());
5114 context_->CanonicalizeLinearConstraint(eq);
5115 context_->UpdateNewConstraintsVariableUsage();
5116 context_->UpdateRuleStats("element: fixed index");
5117 return RemoveConstraint(ct);
5118 }
5119 }
5120 }
5121
5122 bool all_included_in_target_domain = true;
5123 {
5124 // Accumulate expressions domains to build a superset of the target domain.
5125 Domain infered_domain;
5126 const Domain& index_var_domain = context_->DomainOf(index_var);
5127 const Domain& target_domain = context_->DomainSuperSetOf(target);
5128 for (const int64_t index_var_value : index_var_domain.Values()) {
5129 const int64_t index_value =
5130 AffineExpressionValueAt(index, index_var_value);
5131 CHECK_GE(index_value, 0);
5132 CHECK_LT(index_value, ct->element().exprs_size());
5133 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5134 const Domain expr_domain = context_->DomainSuperSetOf(expr);
5135 if (!expr_domain.IsIncludedIn(target_domain)) {
5136 all_included_in_target_domain = false;
5137 }
5138 infered_domain = infered_domain.UnionWith(expr_domain);
5139 }
5140
5141 bool domain_modified = false;
5142 if (!context_->IntersectDomainWith(target, infered_domain,
5143 &domain_modified)) {
5144 return true;
5145 }
5146 if (domain_modified) {
5147 context_->UpdateRuleStats("element: reduce target domain");
5148 }
5149 }
5150
5151 bool all_constants = true;
5152 {
5153 const Domain& index_var_domain = context_->DomainOf(index_var);
5154 std::vector<int64_t> expr_constants;
5155
5156 for (const int64_t index_var_value : index_var_domain.Values()) {
5157 const int64_t index_value =
5158 AffineExpressionValueAt(index, index_var_value);
5159 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5160 if (context_->IsFixed(expr)) {
5161 expr_constants.push_back(context_->FixedValue(expr));
5162 } else {
5163 all_constants = false;
5164 break;
5165 }
5166 }
5167 }
5168
5169 // Detect is the element can be rewritten as a * target + b * index == c.
5170 if (all_constants) {
5171 if (context_->IsFixed(target)) {
5172 // If the accessible part of the array is made of a single constant
5173 // value, then we do not care about the index. And, because of the
5174 // previous target domain reduction, the target is also fixed.
5175 context_->UpdateRuleStats("element: one value array");
5176 return RemoveConstraint(ct);
5177 }
5178 int64_t first_index_var_value;
5179 int64_t first_target_var_value;
5180 int64_t d_index = 0;
5181 int64_t d_target = 0;
5182 int num_terms = 0;
5183 bool is_affine = true;
5184 const Domain& index_var_domain = context_->DomainOf(index_var);
5185 for (const int64_t index_var_value : index_var_domain.Values()) {
5186 ++num_terms;
5187 const int64_t index_value =
5188 AffineExpressionValueAt(index, index_var_value);
5189 const int64_t expr_value =
5190 context_->FixedValue(ct->element().exprs(index_value));
5191 const int64_t target_var_value = GetInnerVarValue(target, expr_value);
5192 if (num_terms == 1) {
5193 first_index_var_value = index_var_value;
5194 first_target_var_value = target_var_value;
5195 } else if (num_terms == 2) {
5196 d_index = index_var_value - first_index_var_value;
5197 d_target = target_var_value - first_target_var_value;
5198 const int64_t gcd = std::gcd(d_index, d_target);
5199 d_index /= gcd;
5200 d_target /= gcd;
5201 } else {
5202 const int64_t offset = CapSub(
5203 CapProd(d_index, CapSub(target_var_value, first_target_var_value)),
5204 CapProd(d_target, CapSub(index_var_value, first_index_var_value)));
5205 if (offset != 0) {
5206 is_affine = false;
5207 break;
5208 }
5209 }
5210 }
5211 if (is_affine) {
5212 const int64_t offset = CapSub(CapProd(first_target_var_value, d_index),
5213 CapProd(first_index_var_value, d_target));
5214 if (!AtMinOrMaxInt64(offset)) {
5215 ConstraintProto* const lin = context_->working_model->add_constraints();
5216 lin->mutable_linear()->add_vars(target.vars(0));
5217 lin->mutable_linear()->add_coeffs(d_index);
5218 lin->mutable_linear()->add_vars(index_var);
5219 lin->mutable_linear()->add_coeffs(-d_target);
5220 lin->mutable_linear()->add_domain(offset);
5221 lin->mutable_linear()->add_domain(offset);
5222 context_->CanonicalizeLinearConstraint(lin);
5223 context_->UpdateNewConstraintsVariableUsage();
5224 context_->UpdateRuleStats("element: rewrite as affine constraint");
5225 return RemoveConstraint(ct);
5226 }
5227 }
5228 }
5229
5230 // If a variable (target or index) appears only in this constraint, it does
5231 // not necessarily mean that we can remove the constraint, as the variable
5232 // can be used multiple times in the element. So let's count the local
5233 // uses of each variable.
5234 //
5235 // TODO(user): now that we used fixed values for these case, this is no longer
5236 // needed I think.
5237 absl::flat_hash_map<int, int> local_var_occurrence_counter;
5238 {
5239 auto count = [&local_var_occurrence_counter](
5240 const LinearExpressionProto& expr) mutable {
5241 for (const int var : expr.vars()) {
5242 local_var_occurrence_counter[var]++;
5243 }
5244 };
5245 count(index);
5246 count(target);
5247 for (const int64_t index_var_value :
5248 context_->DomainOf(index_var).Values()) {
5249 count(
5250 ct->element().exprs(AffineExpressionValueAt(index, index_var_value)));
5251 }
5252 }
5253
5254 if (context_->VariableIsUniqueAndRemovable(index_var) &&
5255 local_var_occurrence_counter.at(index_var) == 1) {
5256 if (all_constants) {
5257 // This constraint is just here to reduce the domain of the target! We can
5258 // add it to the mapping_model to reconstruct the index value during
5259 // postsolve and get rid of it now.
5260 context_->UpdateRuleStats(
5261 "element: removed as the index is not used elsewhere");
5262 context_->MarkVariableAsRemoved(index_var);
5263 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
5264 return RemoveConstraint(ct);
5265 } else {
5266 context_->UpdateRuleStats("TODO element: index not used elsewhere");
5267 }
5268 }
5269
5270 if (target.vars_size() == 1 && !context_->IsFixed(target.vars(0)) &&
5271 context_->VariableIsUniqueAndRemovable(target.vars(0)) &&
5272 local_var_occurrence_counter.at(target.vars(0)) == 1) {
5273 if (all_included_in_target_domain && std::abs(target.coeffs(0)) == 1) {
5274 context_->UpdateRuleStats(
5275 "element: removed as the target is not used elsewhere");
5276 context_->MarkVariableAsRemoved(target.vars(0));
5277 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
5278 return RemoveConstraint(ct);
5279 } else {
5280 context_->UpdateRuleStats("TODO element: target not used elsewhere");
5281 }
5282 }
5283
5284 return changed;
5285}
5286
5287bool CpModelPresolver::PresolveTable(ConstraintProto* ct) {
5288 if (context_->ModelIsUnsat()) return false;
5289
5290 bool changed = false;
5291 for (int i = 0; i < ct->table().exprs_size(); ++i) {
5292 changed |= CanonicalizeLinearExpression(
5293 *ct, ct->mutable_table()->mutable_exprs(i));
5294 }
5295
5296 const int initial_num_exprs = ct->table().exprs_size();
5297 if (initial_num_exprs > 0) CanonicalizeTable(context_, ct);
5298 changed |= (ct->table().exprs_size() != initial_num_exprs);
5299
5300 if (ct->table().exprs().empty()) {
5301 context_->UpdateRuleStats("table: no expressions");
5302 return RemoveConstraint(ct);
5303 }
5304
5305 if (ct->table().values().empty()) {
5306 if (ct->table().negated()) {
5307 context_->UpdateRuleStats("table: negative table without tuples");
5308 return RemoveConstraint(ct);
5309 } else {
5310 context_->UpdateRuleStats("table: positive table without tuples");
5311 return MarkConstraintAsFalse(ct);
5312 }
5313 }
5314
5315 int num_fixed_exprs = 0;
5316 for (const LinearExpressionProto& expr : ct->table().exprs()) {
5317 if (context_->IsFixed(expr)) ++num_fixed_exprs;
5318 }
5319 if (num_fixed_exprs == ct->table().exprs_size()) {
5320 context_->UpdateRuleStats("table: all expressions are fixed");
5321 DCHECK_LE(ct->table().values_size(), num_fixed_exprs);
5322 if (ct->table().negated() == ct->table().values().empty()) {
5323 context_->UpdateRuleStats("table: always true");
5324 return RemoveConstraint(ct);
5325 } else {
5326 context_->UpdateRuleStats("table: always false");
5327 return MarkConstraintAsFalse(ct);
5328 }
5329 return RemoveConstraint(ct);
5330 }
5331
5332 if (num_fixed_exprs > 0) {
5333 CanonicalizeTable(context_, ct);
5334 }
5335
5336 // Nothing more to do for negated tables.
5337 if (ct->table().negated()) return changed;
5338
5339 // And for constraints with enforcement literals.
5340 if (HasEnforcementLiteral(*ct)) return changed;
5341
5342 // Filter the variables domains.
5343 const int num_exprs = ct->table().exprs_size();
5344 const int num_tuples = ct->table().values_size() / num_exprs;
5345 std::vector<std::vector<int64_t>> new_domains(num_exprs);
5346 for (int e = 0; e < num_exprs; ++e) {
5347 const LinearExpressionProto& expr = ct->table().exprs(e);
5348 if (context_->IsFixed(expr)) {
5349 new_domains[e].push_back(context_->FixedValue(expr));
5350 continue;
5351 }
5352
5353 for (int t = 0; t < num_tuples; ++t) {
5354 new_domains[e].push_back(ct->table().values(t * num_exprs + e));
5355 }
5356 gtl::STLSortAndRemoveDuplicates(&new_domains[e]);
5357 DCHECK_EQ(1, expr.vars_size());
5358 DCHECK_EQ(1, expr.coeffs(0));
5359 DCHECK_EQ(0, expr.offset());
5360 const int var = expr.vars(0);
5361 bool domain_modified = false;
5362 if (!context_->IntersectDomainWith(var, Domain::FromValues(new_domains[e]),
5363 &domain_modified)) {
5364 return true;
5365 }
5366 if (domain_modified) {
5367 context_->UpdateRuleStats("table: reduce variable domain");
5368 }
5369 }
5370
5371 if (num_exprs == 1) {
5372 // Now that we have properly updated the domain, we can remove the
5373 // constraint.
5374 context_->UpdateRuleStats("table: only one column!");
5375 return RemoveConstraint(ct);
5376 }
5377
5378 // Check that the table is not complete or just here to exclude a few tuples.
5379 double prod = 1.0;
5380 for (int e = 0; e < num_exprs; ++e) prod *= new_domains[e].size();
5381 if (prod == static_cast<double>(num_tuples)) {
5382 context_->UpdateRuleStats("table: all tuples!");
5383 return RemoveConstraint(ct);
5384 }
5385
5386 // Convert to the negated table if we gain a lot of entries by doing so.
5387 // Note however that currently the negated table do not propagate as much as
5388 // it could.
5389 if (static_cast<double>(num_tuples) > 0.7 * prod) {
5390 std::vector<std::vector<int64_t>> current_tuples(num_tuples);
5391 for (int t = 0; t < num_tuples; ++t) {
5392 current_tuples[t].resize(num_exprs);
5393 for (int e = 0; e < num_exprs; ++e) {
5394 current_tuples[t][e] = ct->table().values(t * num_exprs + e);
5395 }
5396 }
5397
5398 // Enumerate all possible tuples.
5399 std::vector<std::vector<int64_t>> var_to_values(num_exprs);
5400 for (int e = 0; e < num_exprs; ++e) {
5401 var_to_values[e].assign(new_domains[e].begin(), new_domains[e].end());
5402 }
5403 std::vector<std::vector<int64_t>> all_tuples(prod);
5404 for (int i = 0; i < prod; ++i) {
5405 all_tuples[i].resize(num_exprs);
5406 int index = i;
5407 for (int j = 0; j < num_exprs; ++j) {
5408 all_tuples[i][j] = var_to_values[j][index % var_to_values[j].size()];
5409 index /= var_to_values[j].size();
5410 }
5411 }
5413
5414 // Compute the complement of new_tuples.
5415 std::vector<std::vector<int64_t>> diff(prod - num_tuples);
5416 std::set_difference(all_tuples.begin(), all_tuples.end(),
5417 current_tuples.begin(), current_tuples.end(),
5418 diff.begin());
5419
5420 // Negate the constraint.
5421 ct->mutable_table()->set_negated(!ct->table().negated());
5422 ct->mutable_table()->clear_values();
5423 for (const std::vector<int64_t>& t : diff) {
5424 for (const int64_t v : t) ct->mutable_table()->add_values(v);
5425 }
5426 context_->UpdateRuleStats("table: negated");
5427 }
5428
5429 return changed;
5430}
5431
5432bool CpModelPresolver::PresolveAllDiff(ConstraintProto* ct) {
5433 if (context_->ModelIsUnsat()) return false;
5434 if (HasEnforcementLiteral(*ct)) return false;
5435
5436 AllDifferentConstraintProto& all_diff = *ct->mutable_all_diff();
5437
5438 bool constraint_has_changed = false;
5439 for (LinearExpressionProto& exp :
5440 *(ct->mutable_all_diff()->mutable_exprs())) {
5441 constraint_has_changed |= CanonicalizeLinearExpression(*ct, &exp);
5442 }
5443
5444 for (;;) {
5445 const int size = all_diff.exprs_size();
5446 if (size == 0) {
5447 context_->UpdateRuleStats("all_diff: empty constraint");
5448 return RemoveConstraint(ct);
5449 }
5450 if (size == 1) {
5451 context_->UpdateRuleStats("all_diff: only one variable");
5452 return RemoveConstraint(ct);
5453 }
5454
5455 bool something_was_propagated = false;
5456 std::vector<LinearExpressionProto> kept_expressions;
5457 for (int i = 0; i < size; ++i) {
5458 if (!context_->IsFixed(all_diff.exprs(i))) {
5459 kept_expressions.push_back(all_diff.exprs(i));
5460 continue;
5461 }
5462
5463 const int64_t value = context_->MinOf(all_diff.exprs(i));
5464 bool propagated = false;
5465 for (int j = 0; j < size; ++j) {
5466 if (i == j) continue;
5467 if (context_->DomainContains(all_diff.exprs(j), value)) {
5468 if (!context_->IntersectDomainWith(all_diff.exprs(j),
5469 Domain(value).Complement())) {
5470 return true;
5471 }
5472 propagated = true;
5473 }
5474 }
5475 if (propagated) {
5476 context_->UpdateRuleStats("all_diff: propagated fixed expressions");
5477 something_was_propagated = true;
5478 }
5479 }
5480
5481 // CanonicalizeLinearExpression() made sure that only positive variable
5482 // appears here, so this order will put expr and -expr one after the other.
5483 std::sort(
5484 kept_expressions.begin(), kept_expressions.end(),
5485 [](const LinearExpressionProto& expr_a,
5486 const LinearExpressionProto& expr_b) {
5487 DCHECK_EQ(expr_a.vars_size(), 1);
5488 DCHECK_EQ(expr_b.vars_size(), 1);
5489 const int ref_a = expr_a.vars(0);
5490 const int ref_b = expr_b.vars(0);
5491 const int64_t coeff_a = expr_a.coeffs(0);
5492 const int64_t coeff_b = expr_b.coeffs(0);
5493 const int64_t abs_coeff_a = std::abs(coeff_a);
5494 const int64_t abs_coeff_b = std::abs(coeff_b);
5495 const int64_t offset_a = expr_a.offset();
5496 const int64_t offset_b = expr_b.offset();
5497 const int64_t abs_offset_a = std::abs(offset_a);
5498 const int64_t abs_offset_b = std::abs(offset_b);
5499 return std::tie(ref_a, abs_coeff_a, coeff_a, abs_offset_a, offset_a) <
5500 std::tie(ref_b, abs_coeff_b, coeff_b, abs_offset_b, offset_b);
5501 });
5502
5503 // TODO(user): improve algorithm if of (a + offset) and (-a - offset)
5504 // might not be together if (a - offset) is present.
5505
5506 for (int i = 1; i < kept_expressions.size(); ++i) {
5507 if (LinearExpressionProtosAreEqual(kept_expressions[i],
5508 kept_expressions[i - 1], 1)) {
5509 return context_->NotifyThatModelIsUnsat(
5510 "Duplicate variable in all_diff");
5511 }
5512 if (LinearExpressionProtosAreEqual(kept_expressions[i],
5513 kept_expressions[i - 1], -1)) {
5514 bool domain_modified = false;
5515 if (!context_->IntersectDomainWith(kept_expressions[i],
5516 Domain(0).Complement(),
5517 &domain_modified)) {
5518 return false;
5519 }
5520 if (domain_modified) {
5521 context_->UpdateRuleStats(
5522 "all_diff: remove 0 from expression appearing with its "
5523 "opposite.");
5524 }
5525 }
5526 }
5527
5528 if (kept_expressions.size() < all_diff.exprs_size()) {
5529 all_diff.clear_exprs();
5530 for (const LinearExpressionProto& expr : kept_expressions) {
5531 *all_diff.add_exprs() = expr;
5532 }
5533 context_->UpdateRuleStats("all_diff: removed fixed variables");
5534 something_was_propagated = true;
5535 constraint_has_changed = true;
5536 if (kept_expressions.size() <= 1) continue;
5537 }
5538
5539 // Propagate mandatory value if the all diff is actually a permutation.
5540 CHECK_GE(all_diff.exprs_size(), 2);
5541 Domain domain = context_->DomainSuperSetOf(all_diff.exprs(0));
5542 for (int i = 1; i < all_diff.exprs_size(); ++i) {
5543 domain = domain.UnionWith(context_->DomainSuperSetOf(all_diff.exprs(i)));
5544 }
5545 if (all_diff.exprs_size() == domain.Size()) {
5546 absl::flat_hash_map<int64_t, std::vector<LinearExpressionProto>>
5547 value_to_exprs;
5548 for (const LinearExpressionProto& expr : all_diff.exprs()) {
5549 for (const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
5550 value_to_exprs[expr.coeffs(0) * v + expr.offset()].push_back(expr);
5551 }
5552 }
5553 bool propagated = false;
5554 for (const auto& it : value_to_exprs) {
5555 if (it.second.size() == 1 && !context_->IsFixed(it.second.front())) {
5556 const LinearExpressionProto& expr = it.second.front();
5557 if (!context_->IntersectDomainWith(expr, Domain(it.first))) {
5558 return true;
5559 }
5560 propagated = true;
5561 }
5562 }
5563 if (propagated) {
5564 context_->UpdateRuleStats(
5565 "all_diff: propagated mandatory values in permutation");
5566 something_was_propagated = true;
5567 }
5568 }
5569 if (!something_was_propagated) break;
5570 }
5571
5572 return constraint_has_changed;
5573}
5574
5575namespace {
5576
5577// Add the constraint (lhs => rhs) to the given proto. The hash map lhs ->
5578// bool_and constraint index is used to merge implications with the same lhs.
5579void AddImplication(int lhs, int rhs, CpModelProto* proto,
5580 absl::flat_hash_map<int, int>* ref_to_bool_and) {
5581 if (ref_to_bool_and->contains(lhs)) {
5582 const int ct_index = (*ref_to_bool_and)[lhs];
5583 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
5584 } else if (ref_to_bool_and->contains(NegatedRef(rhs))) {
5585 const int ct_index = (*ref_to_bool_and)[NegatedRef(rhs)];
5586 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
5587 NegatedRef(lhs));
5588 } else {
5589 (*ref_to_bool_and)[lhs] = proto->constraints_size();
5590 ConstraintProto* ct = proto->add_constraints();
5591 ct->add_enforcement_literal(lhs);
5592 ct->mutable_bool_and()->add_literals(rhs);
5593 }
5594}
5595
5596template <typename ClauseContainer>
5597void ExtractClauses(bool merge_into_bool_and,
5598 absl::Span<const int> index_mapping,
5599 const ClauseContainer& container, CpModelProto* proto,
5600 std::string_view debug_name = "") {
5601 // We regroup the "implication" into bool_and to have a more concise proto and
5602 // also for nicer information about the number of binary clauses.
5603 //
5604 // Important: however, we do not do that for the model used during postsolving
5605 // since the order of the constraints might be important there depending on
5606 // how we perform the postsolve.
5607 absl::flat_hash_map<int, int> ref_to_bool_and;
5608 for (int i = 0; i < container.NumClauses(); ++i) {
5609 const std::vector<Literal>& clause = container.Clause(i);
5610 if (clause.empty()) continue;
5611
5612 // bool_and.
5613 //
5614 // TODO(user): Be smarter in how we regroup clause of size 2?
5615 if (merge_into_bool_and && clause.size() == 2) {
5616 const int var_a = index_mapping[clause[0].Variable().value()];
5617 const int var_b = index_mapping[clause[1].Variable().value()];
5618 const int ref_a = clause[0].IsPositive() ? var_a : NegatedRef(var_a);
5619 const int ref_b = clause[1].IsPositive() ? var_b : NegatedRef(var_b);
5620 AddImplication(NegatedRef(ref_a), ref_b, proto, &ref_to_bool_and);
5621 continue;
5622 }
5623
5624 // bool_or.
5625 ConstraintProto* ct = proto->add_constraints();
5626 if (!debug_name.empty()) {
5627 ct->set_name(std::string(debug_name));
5628 }
5629 ct->mutable_bool_or()->mutable_literals()->Reserve(clause.size());
5630 for (const Literal l : clause) {
5631 const int var = index_mapping[l.Variable().value()];
5632 if (l.IsPositive()) {
5633 ct->mutable_bool_or()->add_literals(var);
5634 } else {
5635 ct->mutable_bool_or()->add_literals(NegatedRef(var));
5636 }
5637 }
5638 }
5639}
5640
5641} // namespace
5642
5643bool CpModelPresolver::PresolveNoOverlap(ConstraintProto* ct) {
5644 if (context_->ModelIsUnsat()) return false;
5645 NoOverlapConstraintProto* proto = ct->mutable_no_overlap();
5646 bool changed = false;
5647
5648 // Filter out absent intervals. Process duplicate intervals.
5649 {
5650 // Collect duplicate intervals.
5651 absl::flat_hash_set<int> visited_intervals;
5652 absl::flat_hash_set<int> duplicate_intervals;
5653 for (const int interval_index : proto->intervals()) {
5654 if (context_->ConstraintIsInactive(interval_index)) continue;
5655 if (!visited_intervals.insert(interval_index).second) {
5656 duplicate_intervals.insert(interval_index);
5657 }
5658 }
5659
5660 const int initial_num_intervals = proto->intervals_size();
5661 int new_size = 0;
5662 visited_intervals.clear();
5663
5664 for (int i = 0; i < initial_num_intervals; ++i) {
5665 const int interval_index = proto->intervals(i);
5666 if (context_->ConstraintIsInactive(interval_index)) continue;
5667
5668 if (duplicate_intervals.contains(interval_index)) {
5669 // Once processed, we can always remove further duplicates.
5670 if (!visited_intervals.insert(interval_index).second) continue;
5671
5672 ConstraintProto* interval_ct =
5673 context_->working_model->mutable_constraints(interval_index);
5674
5675 // Case 1: size > 0. Interval must be unperformed.
5676 if (context_->SizeMin(interval_index) > 0) {
5677 if (!MarkConstraintAsFalse(interval_ct)) {
5678 return false;
5679 }
5680 context_->UpdateConstraintVariableUsage(interval_index);
5681 context_->UpdateRuleStats(
5682 "no_overlap: unperform duplicate non zero-sized intervals");
5683 // We can remove the interval from the no_overlap.
5684 continue;
5685 }
5686
5687 // No need to do anything if the size is 0.
5688 if (context_->SizeMax(interval_index) > 0) {
5689 // Case 2: interval is performed. Size must be set to 0.
5690 if (!context_->ConstraintIsOptional(interval_index)) {
5691 if (!context_->IntersectDomainWith(interval_ct->interval().size(),
5692 Domain(0))) {
5693 return false;
5694 }
5695 context_->UpdateRuleStats(
5696 "no_overlap: zero the size of performed duplicate intervals");
5697 // We still need to add the interval to the no_overlap as zero sized
5698 // intervals still cannot overlap with other intervals.
5699 } else { // Case 3: interval is optional and size can be > 0.
5700 const int performed_literal = interval_ct->enforcement_literal(0);
5701 ConstraintProto* size_eq_zero =
5702 context_->working_model->add_constraints();
5703 size_eq_zero->add_enforcement_literal(performed_literal);
5704 size_eq_zero->mutable_linear()->add_domain(0);
5705 size_eq_zero->mutable_linear()->add_domain(0);
5707 interval_ct->interval().size(), 1,
5708 size_eq_zero->mutable_linear());
5709 context_->UpdateRuleStats(
5710 "no_overlap: make duplicate intervals as unperformed or zero "
5711 "sized");
5712 context_->UpdateNewConstraintsVariableUsage();
5713 }
5714 }
5715 }
5716
5717 proto->set_intervals(new_size++, interval_index);
5718 }
5719
5720 if (new_size < initial_num_intervals) {
5721 proto->mutable_intervals()->Truncate(new_size);
5722 context_->UpdateRuleStats("no_overlap: removed absent intervals");
5723 changed = true;
5724 }
5725 }
5726
5727 // Split constraints in disjoint sets.
5728 if (proto->intervals_size() > 1) {
5729 std::vector<IndexedInterval> indexed_intervals;
5730 for (int i = 0; i < proto->intervals().size(); ++i) {
5731 const int index = proto->intervals(i);
5732 indexed_intervals.push_back({index,
5733 IntegerValue(context_->StartMin(index)),
5734 IntegerValue(context_->EndMax(index))});
5735 }
5736 std::vector<std::vector<int>> components;
5737 GetOverlappingIntervalComponents(&indexed_intervals, &components);
5738
5739 if (components.size() > 1) {
5740 for (const std::vector<int>& intervals : components) {
5741 if (intervals.size() <= 1) continue;
5742
5743 NoOverlapConstraintProto* new_no_overlap =
5744 context_->working_model->add_constraints()->mutable_no_overlap();
5745 // Fill in the intervals. Unfortunately, the Assign() method does not
5746 // compile in or-tools.
5747 for (const int i : intervals) {
5748 new_no_overlap->add_intervals(i);
5749 }
5750 }
5751 context_->UpdateNewConstraintsVariableUsage();
5752 context_->UpdateRuleStats("no_overlap: split into disjoint components");
5753 return RemoveConstraint(ct);
5754 }
5755 }
5756
5757 std::vector<int> constant_intervals;
5758 int64_t size_min_of_non_constant_intervals =
5759 std::numeric_limits<int64_t>::max();
5760 for (int i = 0; i < proto->intervals_size(); ++i) {
5761 const int interval_index = proto->intervals(i);
5762 if (context_->IntervalIsConstant(interval_index)) {
5763 constant_intervals.push_back(interval_index);
5764 } else {
5765 size_min_of_non_constant_intervals =
5766 std::min(size_min_of_non_constant_intervals,
5767 context_->SizeMin(interval_index));
5768 }
5769 }
5770
5771 bool move_constraint_last = false;
5772 if (!constant_intervals.empty()) {
5773 // Sort constant_intervals by start min.
5774 std::sort(constant_intervals.begin(), constant_intervals.end(),
5775 [this](int i1, int i2) {
5776 const int64_t s1 = context_->StartMin(i1);
5777 const int64_t e1 = context_->EndMax(i1);
5778 const int64_t s2 = context_->StartMin(i2);
5779 const int64_t e2 = context_->EndMax(i2);
5780 return std::tie(s1, e1) < std::tie(s2, e2);
5781 });
5782
5783 // Check for overlapping constant intervals. We need to check feasibility
5784 // before we simplify the constraint, as we might remove conflicting
5785 // overlapping constant intervals.
5786 for (int i = 0; i + 1 < constant_intervals.size(); ++i) {
5787 if (context_->EndMax(constant_intervals[i]) >
5788 context_->StartMin(constant_intervals[i + 1])) {
5789 context_->UpdateRuleStats("no_overlap: constant intervals overlap");
5790 return context_->NotifyThatModelIsUnsat();
5791 }
5792 }
5793
5794 if (constant_intervals.size() == proto->intervals_size()) {
5795 context_->UpdateRuleStats("no_overlap: no variable intervals");
5796 return RemoveConstraint(ct);
5797 }
5798
5799 absl::flat_hash_set<int> intervals_to_remove;
5800
5801 // If two constant intervals are separated by a gap smaller that the min
5802 // size of all non-constant intervals, then we can merge them.
5803 for (int i = 0; i + 1 < constant_intervals.size(); ++i) {
5804 const int start = i;
5805 while (i + 1 < constant_intervals.size() &&
5806 context_->StartMin(constant_intervals[i + 1]) -
5807 context_->EndMax(constant_intervals[i]) <
5808 size_min_of_non_constant_intervals) {
5809 i++;
5810 }
5811 if (i == start) continue;
5812 for (int j = start; j <= i; ++j) {
5813 intervals_to_remove.insert(constant_intervals[j]);
5814 }
5815 const int64_t new_start = context_->StartMin(constant_intervals[start]);
5816 const int64_t new_end = context_->EndMax(constant_intervals[i]);
5817 proto->add_intervals(context_->working_model->constraints_size());
5818 IntervalConstraintProto* new_interval =
5819 context_->working_model->add_constraints()->mutable_interval();
5820 new_interval->mutable_start()->set_offset(new_start);
5821 new_interval->mutable_size()->set_offset(new_end - new_start);
5822 new_interval->mutable_end()->set_offset(new_end);
5823 move_constraint_last = true;
5824 }
5825
5826 // Cleanup the original proto.
5827 if (!intervals_to_remove.empty()) {
5828 int new_size = 0;
5829 const int old_size = proto->intervals_size();
5830 for (int i = 0; i < old_size; ++i) {
5831 const int interval_index = proto->intervals(i);
5832 if (intervals_to_remove.contains(interval_index)) {
5833 continue;
5834 }
5835 proto->set_intervals(new_size++, interval_index);
5836 }
5837 CHECK_LT(new_size, old_size);
5838 proto->mutable_intervals()->Truncate(new_size);
5839 context_->UpdateRuleStats(
5840 "no_overlap: merge constant contiguous intervals");
5841 intervals_to_remove.clear();
5842 constant_intervals.clear();
5843 changed = true;
5844 context_->UpdateNewConstraintsVariableUsage();
5845 }
5846 }
5847
5848 if (proto->intervals_size() == 1) {
5849 context_->UpdateRuleStats("no_overlap: only one interval");
5850 return RemoveConstraint(ct);
5851 }
5852 if (proto->intervals().empty()) {
5853 context_->UpdateRuleStats("no_overlap: no intervals");
5854 return RemoveConstraint(ct);
5855 }
5856
5857 // Unfortunately, because we want all intervals to appear before a constraint
5858 // that uses them, we need to move the constraint last when we merged constant
5859 // intervals.
5860 if (move_constraint_last) {
5861 changed = true;
5862 *context_->working_model->add_constraints() = *ct;
5863 context_->UpdateNewConstraintsVariableUsage();
5864 return RemoveConstraint(ct);
5865 }
5866
5867 return changed;
5868}
5869
5870bool CpModelPresolver::PresolveNoOverlap2DFramed(
5871 absl::Span<const Rectangle> fixed_boxes,
5872 absl::Span<const RectangleInRange> non_fixed_boxes, ConstraintProto* ct) {
5873 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
5874
5875 DCHECK(!non_fixed_boxes.empty());
5876 Rectangle bounding_box = non_fixed_boxes[0].bounding_area;
5877 for (const RectangleInRange& box : non_fixed_boxes) {
5878 bounding_box.GrowToInclude(box.bounding_area);
5879 }
5880 std::vector<Rectangle> espace_for_single_box =
5881 FindEmptySpaces(bounding_box, {fixed_boxes.begin(), fixed_boxes.end()});
5882 // TODO(user): Find a faster way to see if fixed boxes are delimiting a
5883 // rectangle.
5884 std::vector<Rectangle> empty;
5885 ReduceNumberofBoxesGreedy(&espace_for_single_box, &empty);
5886 ReduceNumberOfBoxesExactMandatory(&espace_for_single_box, &empty);
5887 if (espace_for_single_box.size() != 1) {
5888 // Not a rectangular frame, since the inside is not a rectangle.
5889 return false;
5890 }
5891 Rectangle fixed_boxes_bb = fixed_boxes.front();
5892 for (const Rectangle& box : fixed_boxes) {
5893 fixed_boxes_bb.GrowToInclude(box);
5894 }
5895 const Rectangle framed_region = espace_for_single_box.front();
5896 for (const RectangleInRange& box : non_fixed_boxes) {
5897 if (!box.bounding_area.IsInsideOf(fixed_boxes_bb)) {
5898 // Something can be outside of the frame.
5899 return false;
5900 }
5901 if (non_fixed_boxes.size() > 1 &&
5902 (2 * box.x_size <= framed_region.SizeX() ||
5903 2 * box.y_size <= framed_region.SizeY())) {
5904 // We can fit two boxes in the delimited space between the fixed boxes, so
5905 // we cannot replace it by an at-most-one.
5906 return false;
5907 }
5908 const int x_interval_index = proto.x_intervals(box.box_index);
5909 const int y_interval_index = proto.y_intervals(box.box_index);
5910 if (!context_->working_model->constraints(x_interval_index)
5911 .enforcement_literal()
5912 .empty() &&
5913 !context_->working_model->constraints(y_interval_index)
5914 .enforcement_literal()
5915 .empty()) {
5916 if (context_->working_model->constraints(x_interval_index)
5917 .enforcement_literal(0) !=
5918 context_->working_model->constraints(y_interval_index)
5919 .enforcement_literal(0)) {
5920 // Two different enforcement literals.
5921 return false;
5922 }
5923 }
5924 }
5925 // All this no_overlap_2d constraint is doing is forcing at most one of
5926 // the non-fixed boxes to be in the `framed_region` rectangle. A
5927 // better representation of this is to simply enforce that the items fit
5928 // that rectangle with linear constraints and add a at-most-one constraint.
5929 std::vector<int> enforcement_literals_for_amo;
5930 bool has_mandatory = false;
5931 for (const RectangleInRange& box : non_fixed_boxes) {
5932 const int box_index = box.box_index;
5933 const int x_interval_index = proto.x_intervals(box_index);
5934 const int y_interval_index = proto.y_intervals(box_index);
5935 const ConstraintProto& x_interval_ct =
5936 context_->working_model->constraints(x_interval_index);
5937 const ConstraintProto& y_interval_ct =
5938 context_->working_model->constraints(y_interval_index);
5939 if (x_interval_ct.enforcement_literal().empty() &&
5940 y_interval_ct.enforcement_literal().empty()) {
5941 // Mandatory box, update the domains.
5942 if (has_mandatory) {
5943 return context_->NotifyThatModelIsUnsat(
5944 "Two mandatory boxes in the same space");
5945 }
5946 has_mandatory = true;
5947 if (!context_->IntersectDomainWith(x_interval_ct.interval().start(),
5948 Domain(framed_region.x_min.value(),
5949 framed_region.x_max.value()))) {
5950 return true;
5951 }
5952 if (!context_->IntersectDomainWith(x_interval_ct.interval().end(),
5953 Domain(framed_region.x_min.value(),
5954 framed_region.x_max.value()))) {
5955 return true;
5956 }
5957 if (!context_->IntersectDomainWith(y_interval_ct.interval().start(),
5958 Domain(framed_region.y_min.value(),
5959 framed_region.y_max.value()))) {
5960 return true;
5961 }
5962 if (!context_->IntersectDomainWith(y_interval_ct.interval().end(),
5963 Domain(framed_region.y_min.value(),
5964 framed_region.y_max.value()))) {
5965 return true;
5966 }
5967 } else {
5968 auto add_linear_constraint = [&](const ConstraintProto& interval_ct,
5969 int enforcement_literal,
5970 IntegerValue min, IntegerValue max) {
5971 // TODO(user): If size is constant add only one linear constraint
5972 // instead of two.
5973 context_->AddImplyInDomain(enforcement_literal,
5974 interval_ct.interval().start(),
5975 Domain(min.value(), max.value()));
5976 context_->AddImplyInDomain(enforcement_literal,
5977 interval_ct.interval().end(),
5978 Domain(min.value(), max.value()));
5979 };
5980 const int enforcement_literal =
5981 x_interval_ct.enforcement_literal().empty()
5982 ? y_interval_ct.enforcement_literal(0)
5983 : x_interval_ct.enforcement_literal(0);
5984 enforcement_literals_for_amo.push_back(enforcement_literal);
5985 add_linear_constraint(x_interval_ct, enforcement_literal,
5986 framed_region.x_min, framed_region.x_max);
5987 add_linear_constraint(y_interval_ct, enforcement_literal,
5988 framed_region.y_min, framed_region.y_max);
5989 }
5990 }
5991 if (has_mandatory) {
5992 for (const int lit : enforcement_literals_for_amo) {
5993 if (!context_->SetLiteralToFalse(lit)) {
5994 return true;
5995 }
5996 }
5997 } else if (enforcement_literals_for_amo.size() > 1) {
5998 context_->working_model->add_constraints()
5999 ->mutable_at_most_one()
6000 ->mutable_literals()
6001 ->Add(enforcement_literals_for_amo.begin(),
6002 enforcement_literals_for_amo.end());
6003 }
6004 context_->UpdateRuleStats("no_overlap_2d: at most one rectangle in region");
6005 context_->UpdateNewConstraintsVariableUsage();
6006 return RemoveConstraint(ct);
6007}
6008
6009bool CpModelPresolver::ExpandEncoded2DBinPacking(
6010 absl::Span<const Rectangle> fixed_boxes,
6011 absl::Span<const RectangleInRange> non_fixed_boxes, ConstraintProto* ct) {
6012 const Disjoint2dPackingResult disjoint_packing_presolve_result =
6014 non_fixed_boxes, fixed_boxes,
6015 context_->params()
6016 .maximum_regions_to_split_in_disconnected_no_overlap_2d());
6017 if (disjoint_packing_presolve_result.bins.empty()) return false;
6018
6019 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6020 std::vector<SolutionCrush::BoxInAreaLiteral> box_in_area_lits;
6021 absl::flat_hash_map<int, std::vector<int>> box_to_presence_literal;
6022 // For the boxes that are optional, add a presence literal for each box in a
6023 // fake "absent" bin.
6024 for (int idx = 0; idx < non_fixed_boxes.size(); ++idx) {
6025 const int b = non_fixed_boxes[idx].box_index;
6026 const ConstraintProto& x_interval_ct =
6027 context_->working_model->constraints(proto.x_intervals(b));
6028 const ConstraintProto& y_interval_ct =
6029 context_->working_model->constraints(proto.y_intervals(b));
6030 if (x_interval_ct.enforcement_literal().empty() &&
6031 y_interval_ct.enforcement_literal().empty()) {
6032 // Mandatory box, cannot be in the "absent" bin -1.
6033 continue;
6034 }
6035 int enforcement_literal = x_interval_ct.enforcement_literal().empty()
6036 ? y_interval_ct.enforcement_literal(0)
6037 : x_interval_ct.enforcement_literal(0);
6038 int potentially_other_enforcement_literal =
6039 y_interval_ct.enforcement_literal().empty()
6040 ? x_interval_ct.enforcement_literal(0)
6041 : y_interval_ct.enforcement_literal(0);
6042
6043 if (enforcement_literal == potentially_other_enforcement_literal) {
6044 // The box is in the "absent" bin -1.
6045 box_to_presence_literal[idx].push_back(NegatedRef(enforcement_literal));
6046 } else {
6047 const int interval_is_absent_literal =
6048 context_->NewBoolVarWithConjunction(
6049 {enforcement_literal, potentially_other_enforcement_literal});
6050
6051 BoolArgumentProto* bool_or =
6052 context_->working_model->add_constraints()->mutable_bool_or();
6053 bool_or->add_literals(NegatedRef(interval_is_absent_literal));
6054 for (const int lit :
6055 {enforcement_literal, potentially_other_enforcement_literal}) {
6056 context_->AddImplication(NegatedRef(interval_is_absent_literal), lit);
6057 bool_or->add_literals(NegatedRef(lit));
6058 }
6059 box_to_presence_literal[idx].push_back(interval_is_absent_literal);
6060 }
6061 }
6062 // Now create the literals "item i in bin j".
6063 for (int bin_index = 0;
6064 bin_index < disjoint_packing_presolve_result.bins.size(); ++bin_index) {
6065 const Disjoint2dPackingResult::Bin& bin =
6066 disjoint_packing_presolve_result.bins[bin_index];
6067 NoOverlap2DConstraintProto new_no_overlap_2d;
6068 for (const Rectangle& ret : bin.fixed_boxes) {
6069 new_no_overlap_2d.add_x_intervals(
6070 context_->working_model->constraints_size());
6071 new_no_overlap_2d.add_y_intervals(
6072 context_->working_model->constraints_size() + 1);
6073 IntervalConstraintProto* new_interval =
6074 context_->working_model->add_constraints()->mutable_interval();
6075 new_interval->mutable_start()->set_offset(ret.x_min.value());
6076 new_interval->mutable_size()->set_offset(ret.SizeX().value());
6077 new_interval->mutable_end()->set_offset(ret.x_max.value());
6078
6079 new_interval =
6080 context_->working_model->add_constraints()->mutable_interval();
6081 new_interval->mutable_start()->set_offset(ret.y_min.value());
6082 new_interval->mutable_size()->set_offset(ret.SizeY().value());
6083 new_interval->mutable_end()->set_offset(ret.y_max.value());
6084 }
6085 for (const int idx : bin.non_fixed_box_indexes) {
6086 int presence_in_box_lit = context_->NewBoolVar("binpacking");
6087 box_to_presence_literal[idx].push_back(presence_in_box_lit);
6088 const int b = non_fixed_boxes[idx].box_index;
6089 box_in_area_lits.push_back({.box_index = b,
6090 .area_index = bin_index,
6091 .literal = presence_in_box_lit});
6092 const ConstraintProto& x_interval_ct =
6093 context_->working_model->constraints(proto.x_intervals(b));
6094 const ConstraintProto& y_interval_ct =
6095 context_->working_model->constraints(proto.y_intervals(b));
6096 ConstraintProto* new_interval_x =
6097 context_->working_model->add_constraints();
6098 *new_interval_x = x_interval_ct;
6099 new_interval_x->clear_enforcement_literal();
6100 new_interval_x->add_enforcement_literal(presence_in_box_lit);
6101 ConstraintProto* new_interval_y =
6102 context_->working_model->add_constraints();
6103 *new_interval_y = y_interval_ct;
6104 new_interval_y->clear_enforcement_literal();
6105 new_interval_y->add_enforcement_literal(presence_in_box_lit);
6106 new_no_overlap_2d.add_x_intervals(
6107 context_->working_model->constraints_size() - 2);
6108 new_no_overlap_2d.add_y_intervals(
6109 context_->working_model->constraints_size() - 1);
6110 }
6111 context_->working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
6112 &new_no_overlap_2d);
6113 }
6114
6115 // Each box is in exactly one bin (including the fake "absent" bin).
6116 for (int box_index = 0; box_index < non_fixed_boxes.size(); ++box_index) {
6117 const std::vector<int>& presence_literals =
6118 box_to_presence_literal[box_index];
6119 if (presence_literals.empty()) {
6120 return context_->NotifyThatModelIsUnsat(
6121 "A mandatory box cannot be placed in any position");
6122 }
6123 auto* exactly_one =
6124 context_->working_model->add_constraints()->mutable_exactly_one();
6125 for (const int presence_literal : presence_literals) {
6126 exactly_one->add_literals(presence_literal);
6127 }
6128 }
6129 CompactVectorVector<int, Rectangle> areas;
6130 for (int bin_index = 0;
6131 bin_index < disjoint_packing_presolve_result.bins.size(); ++bin_index) {
6132 areas.Add(disjoint_packing_presolve_result.bins[bin_index].bin_area);
6133 }
6134 solution_crush_.AssignVariableToPackingArea(
6135 areas, *context_->working_model, proto.x_intervals(), proto.y_intervals(),
6136 box_in_area_lits);
6137 context_->UpdateNewConstraintsVariableUsage();
6138 context_->UpdateRuleStats(
6139 "no_overlap_2d: fixed boxes partition available space, converted "
6140 "to optional regions");
6141 return RemoveConstraint(ct);
6142}
6143
6144bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
6145 if (context_->ModelIsUnsat()) {
6146 return false;
6147 }
6148
6149 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6150 const int initial_num_boxes = proto.x_intervals_size();
6151
6152 bool x_constant = true;
6153 bool y_constant = true;
6154 bool has_zero_sized_interval = false;
6155 bool has_potential_zero_sized_interval = false;
6156
6157 // Filter absent boxes.
6158 int new_size = 0;
6159 std::vector<Rectangle> bounding_boxes, fixed_boxes, non_fixed_bounding_boxes;
6160 std::vector<RectangleInRange> non_fixed_boxes;
6161 absl::flat_hash_set<int> fixed_item_indexes;
6162 for (int i = 0; i < proto.x_intervals_size(); ++i) {
6163 const int x_interval_index = proto.x_intervals(i);
6164 const int y_interval_index = proto.y_intervals(i);
6165
6166 ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index);
6167 ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index);
6168
6169 // We don't want to fully presolve the intervals (intervals have their own
6170 // presolve), but we don't want to bother with negative sizes downstream in
6171 // this function.
6172 for (const int interval_index : {x_interval_index, y_interval_index}) {
6173 if (context_->StartMin(interval_index) >
6174 context_->EndMax(interval_index)) {
6175 const ConstraintProto* interval_ct =
6176 context_->working_model->mutable_constraints(interval_index);
6177 if (interval_ct->enforcement_literal_size() == 1) {
6178 const int literal = interval_ct->enforcement_literal(0);
6179 if (!context_->SetLiteralToFalse(literal)) {
6180 return true;
6181 }
6182 } else {
6183 return context_->NotifyThatModelIsUnsat(
6184 "no_overlap_2d: impossible interval.");
6185 }
6186 }
6187 }
6188
6189 if (context_->ConstraintIsInactive(x_interval_index) ||
6190 context_->ConstraintIsInactive(y_interval_index)) {
6191 continue;
6192 }
6193
6194 bounding_boxes.push_back(
6195 {IntegerValue(context_->StartMin(x_interval_index)),
6196 IntegerValue(context_->EndMax(x_interval_index)),
6197 IntegerValue(context_->StartMin(y_interval_index)),
6198 IntegerValue(context_->EndMax(y_interval_index))});
6199 if (context_->IntervalIsConstant(x_interval_index) &&
6200 context_->IntervalIsConstant(y_interval_index) &&
6201 context_->SizeMax(x_interval_index) > 0 &&
6202 context_->SizeMax(y_interval_index) > 0) {
6203 fixed_boxes.push_back(bounding_boxes.back());
6204 fixed_item_indexes.insert(new_size);
6205 } else {
6206 non_fixed_bounding_boxes.push_back(bounding_boxes.back());
6207 non_fixed_boxes.push_back(
6208 {.box_index = new_size,
6209 .bounding_area = bounding_boxes.back(),
6210 .x_size = context_->SizeMin(x_interval_index),
6211 .y_size = context_->SizeMin(y_interval_index)});
6212 }
6213 new_size++;
6214
6215 if (x_constant && !context_->IntervalIsConstant(x_interval_index)) {
6216 x_constant = false;
6217 }
6218 if (y_constant && !context_->IntervalIsConstant(y_interval_index)) {
6219 y_constant = false;
6220 }
6221 if (context_->SizeMax(x_interval_index) == 0 ||
6222 context_->SizeMax(y_interval_index) == 0) {
6223 has_zero_sized_interval = true;
6224 }
6225 if (context_->SizeMin(x_interval_index) == 0 ||
6226 context_->SizeMin(y_interval_index) == 0) {
6227 has_potential_zero_sized_interval = true;
6228 }
6229 }
6230
6231 if (new_size < initial_num_boxes) {
6232 context_->UpdateRuleStats("no_overlap_2d: removed inactive boxes");
6233 ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
6234 ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
6235 }
6236
6237 if (new_size == 0) {
6238 context_->UpdateRuleStats("no_overlap_2d: no boxes");
6239 return RemoveConstraint(ct);
6240 }
6241
6242 if (new_size == 1) {
6243 context_->UpdateRuleStats("no_overlap_2d: only one box");
6244 return RemoveConstraint(ct);
6245 }
6246
6247 const CompactVectorVector<int> components =
6248 GetOverlappingRectangleComponents(bounding_boxes);
6249 if (components.size() > 1) {
6250 for (int i = 0; i < components.size(); ++i) {
6251 absl::Span<const int> boxes = components[i];
6252 if (boxes.size() <= 1) continue;
6253
6254 NoOverlap2DConstraintProto* new_no_overlap_2d =
6255 context_->working_model->add_constraints()->mutable_no_overlap_2d();
6256 for (const int b : boxes) {
6257 new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
6258 new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
6259 }
6260 }
6261 context_->UpdateNewConstraintsVariableUsage();
6262 context_->UpdateRuleStats("no_overlap_2d: split into disjoint components");
6263 return RemoveConstraint(ct);
6264 }
6265
6266 // TODO(user): handle this case. See issue #4068.
6267 if (!has_zero_sized_interval && (x_constant || y_constant)) {
6268 context_->UpdateRuleStats(
6269 "no_overlap_2d: a dimension is constant, splitting into many "
6270 "no_overlaps");
6271 std::vector<IndexedInterval> indexed_intervals;
6272 for (int i = 0; i < new_size; ++i) {
6273 int x = proto.x_intervals(i);
6274 int y = proto.y_intervals(i);
6275 if (x_constant) std::swap(x, y);
6276 indexed_intervals.push_back({x, IntegerValue(context_->StartMin(y)),
6277 IntegerValue(context_->EndMax(y))});
6278 }
6279 CompactVectorVector<int> no_overlaps;
6280 absl::c_sort(indexed_intervals, IndexedInterval::ComparatorByStart());
6281 ConstructOverlappingSets(absl::MakeSpan(indexed_intervals), &no_overlaps);
6282 for (int i = 0; i < no_overlaps.size(); ++i) {
6283 ConstraintProto* new_ct = context_->working_model->add_constraints();
6284 // Unfortunately, the Assign() method does not work in or-tools as the
6285 // protobuf int32_t type is not the int type.
6286 for (const int i : no_overlaps[i]) {
6287 new_ct->mutable_no_overlap()->add_intervals(i);
6288 }
6289 }
6290 context_->UpdateNewConstraintsVariableUsage();
6291 return RemoveConstraint(ct);
6292 }
6293
6294 // We check if the fixed boxes are not overlapping so downstream code can
6295 // assume it to be true.
6296 if (!FindPartialRectangleIntersections(fixed_boxes).empty()) {
6297 return context_->NotifyThatModelIsUnsat(
6298 "Two fixed boxes in no_overlap_2d overlap");
6299 }
6300
6301 if (non_fixed_bounding_boxes.empty()) {
6302 context_->UpdateRuleStats("no_overlap_2d: all boxes are fixed");
6303 return RemoveConstraint(ct);
6304 }
6305
6306 // TODO(user): presolve the zero-size fixed items so they are disjoint from
6307 // the other fixed items. Then the following presolve is still valid. On the
6308 // other hand, we cannot do much with non-fixed zero-size items.
6309 if (!has_potential_zero_sized_interval && !fixed_boxes.empty()) {
6310 const bool presolved =
6311 PresolveFixed2dRectangles(non_fixed_boxes, &fixed_boxes);
6312 if (presolved) {
6313 NoOverlap2DConstraintProto new_no_overlap_2d;
6314
6315 // Replace the old fixed intervals by the new ones.
6316 const int old_size = proto.x_intervals_size();
6317 for (int i = 0; i < old_size; ++i) {
6318 if (fixed_item_indexes.contains(i)) {
6319 continue;
6320 }
6321 new_no_overlap_2d.add_x_intervals(proto.x_intervals(i));
6322 new_no_overlap_2d.add_y_intervals(proto.y_intervals(i));
6323 }
6324 for (const Rectangle& fixed_box : fixed_boxes) {
6325 const int item_x_interval =
6326 context_->working_model->constraints().size();
6327 IntervalConstraintProto* new_interval =
6328 context_->working_model->add_constraints()->mutable_interval();
6329 new_interval->mutable_start()->set_offset(fixed_box.x_min.value());
6330 new_interval->mutable_size()->set_offset(fixed_box.SizeX().value());
6331 new_interval->mutable_end()->set_offset(fixed_box.x_max.value());
6332
6333 const int item_y_interval =
6334 context_->working_model->constraints().size();
6335 new_interval =
6336 context_->working_model->add_constraints()->mutable_interval();
6337 new_interval->mutable_start()->set_offset(fixed_box.y_min.value());
6338 new_interval->mutable_size()->set_offset(fixed_box.SizeY().value());
6339 new_interval->mutable_end()->set_offset(fixed_box.y_max.value());
6340
6341 new_no_overlap_2d.add_x_intervals(item_x_interval);
6342 new_no_overlap_2d.add_y_intervals(item_y_interval);
6343 }
6344 context_->working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
6345 &new_no_overlap_2d);
6346 context_->UpdateNewConstraintsVariableUsage();
6347 context_->UpdateRuleStats("no_overlap_2d: presolved fixed rectangles");
6348 return RemoveConstraint(ct);
6349 }
6350 }
6351
6352 if (!fixed_boxes.empty() && fixed_boxes.size() <= 4 &&
6353 !non_fixed_boxes.empty() && !has_potential_zero_sized_interval) {
6354 if (PresolveNoOverlap2DFramed(fixed_boxes, non_fixed_boxes, ct)) {
6355 return true;
6356 }
6357 }
6358
6359 // If the non-fixed boxes are disjoint but connected by fixed boxes, we can
6360 // split the constraint and duplicate the fixed boxes. To avoid duplicating
6361 // too many fixed boxes, we do this after we we applied the presolve reducing
6362 // their number to as few as possible.
6363 const CompactVectorVector<int> non_fixed_components =
6364 GetOverlappingRectangleComponents(non_fixed_bounding_boxes);
6365 if (non_fixed_components.size() > 1) {
6366 for (int i = 0; i < non_fixed_components.size(); ++i) {
6367 // Note: we care about components of size 1 because they might be
6368 // overlapping with the fixed boxes.
6369 absl::Span<const int> indexes = non_fixed_components[i];
6370
6371 NoOverlap2DConstraintProto* new_no_overlap_2d =
6372 context_->working_model->add_constraints()->mutable_no_overlap_2d();
6373 for (const int idx : indexes) {
6374 const int b = non_fixed_boxes[idx].box_index;
6375 new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
6376 new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
6377 }
6378 for (const int b : fixed_item_indexes) {
6379 new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
6380 new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
6381 }
6382 }
6383 context_->UpdateNewConstraintsVariableUsage();
6384 context_->UpdateRuleStats(
6385 "no_overlap_2d: split into disjoint components duplicating fixed "
6386 "boxes");
6387 return RemoveConstraint(ct);
6388 }
6389
6390 if (!has_potential_zero_sized_interval) {
6391 if (ExpandEncoded2DBinPacking(fixed_boxes, non_fixed_boxes, ct)) {
6392 return true;
6393 }
6394 }
6395 RunPropagatorsForConstraint(*ct);
6396 return new_size < initial_num_boxes;
6397}
6398
6399namespace {
6400LinearExpressionProto ConstantExpressionProto(int64_t value) {
6401 LinearExpressionProto expr;
6402 expr.set_offset(value);
6403 return expr;
6404}
6405} // namespace
6406
6407void CpModelPresolver::DetectDuplicateIntervals(
6408 int c, google::protobuf::RepeatedField<int32_t>* intervals) {
6409 interval_representative_.clear();
6410 bool changed = false;
6411 const int size = intervals->size();
6412 for (int i = 0; i < size; ++i) {
6413 const int index = (*intervals)[i];
6414 const auto [it, inserted] = interval_representative_.insert({index, index});
6415 if (it->second != index) {
6416 changed = true;
6417 intervals->Set(i, it->second);
6418 context_->UpdateRuleStats(
6419 "intervals: change duplicate index inside constraint");
6420 }
6421 }
6422 if (changed) context_->UpdateConstraintVariableUsage(c);
6423}
6424
6425bool CpModelPresolver::PresolveCumulative(ConstraintProto* ct) {
6426 if (context_->ModelIsUnsat()) return false;
6427
6428 CumulativeConstraintProto* proto = ct->mutable_cumulative();
6429
6430 bool changed = CanonicalizeLinearExpression(*ct, proto->mutable_capacity());
6431 for (LinearExpressionProto& exp :
6432 *(ct->mutable_cumulative()->mutable_demands())) {
6433 changed |= CanonicalizeLinearExpression(*ct, &exp);
6434 }
6435
6436 const int64_t capacity_max = context_->MaxOf(proto->capacity());
6437
6438 // Checks the capacity of the constraint.
6439 {
6440 bool domain_changed = false;
6441 if (!context_->IntersectDomainWith(
6442 proto->capacity(), Domain(0, capacity_max), &domain_changed)) {
6443 return true;
6444 }
6445 if (domain_changed) {
6446 context_->UpdateRuleStats("cumulative: trimmed negative capacity");
6447 }
6448 }
6449
6450 // Merge identical intervals if the demand can be merged and is still affine.
6451 //
6452 // TODO(user): We could also merge if the first entry is constant instead of
6453 // the second one. Or if the variable used for the demand is the same.
6454 {
6455 absl::flat_hash_map<int, int> interval_to_i;
6456 int new_size = 0;
6457 for (int i = 0; i < proto->intervals_size(); ++i) {
6458 const auto [it, inserted] =
6459 interval_to_i.insert({proto->intervals(i), new_size});
6460 if (!inserted) {
6461 if (context_->IsFixed(proto->demands(i))) {
6462 const int old_index = it->second;
6463 proto->mutable_demands(old_index)->set_offset(
6464 proto->demands(old_index).offset() +
6465 context_->FixedValue(proto->demands(i)));
6466 context_->UpdateRuleStats(
6467 "cumulative: merged demand of identical interval");
6468 continue;
6469 } else {
6470 context_->UpdateRuleStats(
6471 "TODO cumulative: merged demand of identical interval");
6472 }
6473 }
6474 proto->set_intervals(new_size, proto->intervals(i));
6475 *proto->mutable_demands(new_size) = proto->demands(i);
6476 ++new_size;
6477 }
6478 if (new_size < proto->intervals_size()) {
6479 changed = true;
6480 proto->mutable_intervals()->Truncate(new_size);
6481 proto->mutable_demands()->erase(
6482 proto->mutable_demands()->begin() + new_size,
6483 proto->mutable_demands()->end());
6484 }
6485 }
6486
6487 // Filter absent intervals, or zero demands, or demand incompatible with the
6488 // capacity.
6489 {
6490 int new_size = 0;
6491 int num_zero_demand_removed = 0;
6492 int num_zero_size_removed = 0;
6493 int num_incompatible_intervals = 0;
6494 for (int i = 0; i < proto->intervals_size(); ++i) {
6495 if (context_->ConstraintIsInactive(proto->intervals(i))) continue;
6496
6497 const LinearExpressionProto& demand_expr = proto->demands(i);
6498 const int64_t demand_max = context_->MaxOf(demand_expr);
6499 if (demand_max == 0) {
6500 num_zero_demand_removed++;
6501 continue;
6502 }
6503
6504 const int interval_index = proto->intervals(i);
6505 if (context_->SizeMax(interval_index) == 0) {
6506 // Size 0 intervals cannot contribute to a cumulative.
6507 num_zero_size_removed++;
6508 continue;
6509 }
6510
6511 // Inconsistent intervals cannot be performed.
6512 const int64_t start_min = context_->StartMin(interval_index);
6513 const int64_t end_max = context_->EndMax(interval_index);
6514 if (start_min > end_max) {
6515 if (context_->ConstraintIsOptional(interval_index)) {
6516 ConstraintProto* interval_ct =
6517 context_->working_model->mutable_constraints(interval_index);
6518 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
6519 const int literal = interval_ct->enforcement_literal(0);
6520 if (!context_->SetLiteralToFalse(literal)) {
6521 return true;
6522 }
6523 num_incompatible_intervals++;
6524 continue;
6525 } else {
6526 return context_->NotifyThatModelIsUnsat(
6527 "cumulative: inconsistent intervals cannot be performed.");
6528 }
6529 }
6530
6531 if (context_->MinOf(demand_expr) > capacity_max) {
6532 if (context_->ConstraintIsOptional(interval_index)) {
6533 if (context_->SizeMin(interval_index) > 0) {
6534 ConstraintProto* interval_ct =
6535 context_->working_model->mutable_constraints(interval_index);
6536 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
6537 const int literal = interval_ct->enforcement_literal(0);
6538 if (!context_->SetLiteralToFalse(literal)) {
6539 return true;
6540 }
6541 num_incompatible_intervals++;
6542 continue;
6543 }
6544 } else { // Interval performed.
6545 // Try to set the size to 0.
6546 const ConstraintProto& interval_ct =
6547 context_->working_model->constraints(interval_index);
6548 if (!context_->IntersectDomainWith(interval_ct.interval().size(),
6549 {0, 0})) {
6550 return true;
6551 }
6552 context_->UpdateRuleStats(
6553 "cumulative: zero size of performed demand that exceeds "
6554 "capacity");
6555 ++num_zero_demand_removed;
6556 continue;
6557 }
6558 }
6559
6560 proto->set_intervals(new_size, interval_index);
6561 *proto->mutable_demands(new_size) = proto->demands(i);
6562 new_size++;
6563 }
6564
6565 if (new_size < proto->intervals_size()) {
6566 changed = true;
6567 proto->mutable_intervals()->Truncate(new_size);
6568 proto->mutable_demands()->erase(
6569 proto->mutable_demands()->begin() + new_size,
6570 proto->mutable_demands()->end());
6571 }
6572
6573 if (num_zero_demand_removed > 0) {
6574 context_->UpdateRuleStats(
6575 "cumulative: removed intervals with no demands");
6576 }
6577 if (num_zero_size_removed > 0) {
6578 context_->UpdateRuleStats(
6579 "cumulative: removed intervals with a size of zero");
6580 }
6581 if (num_incompatible_intervals > 0) {
6582 context_->UpdateRuleStats(
6583 "cumulative: removed intervals that can't be performed");
6584 }
6585 }
6586
6587 // Checks the compatibility of demands w.r.t. the capacity.
6588 {
6589 for (int i = 0; i < proto->demands_size(); ++i) {
6590 const int interval = proto->intervals(i);
6591 const LinearExpressionProto& demand_expr = proto->demands(i);
6592 if (context_->ConstraintIsOptional(interval)) continue;
6593 if (context_->SizeMin(interval) == 0) continue;
6594 bool domain_changed = false;
6595 if (!context_->IntersectDomainWith(demand_expr, {0, capacity_max},
6596 &domain_changed)) {
6597 return true;
6598 }
6599 if (domain_changed) {
6600 context_->UpdateRuleStats(
6601 "cumulative: fit demand in [0..capacity_max]");
6602 }
6603 }
6604 }
6605
6606 // Split constraints in disjoint sets.
6607 //
6608 // TODO(user): This can be improved:
6609 // If we detect bridge nodes in the graph of overlapping components, we
6610 // can split the graph around the bridge and add the bridge node to both
6611 // side. Note that if it we take into account precedences between intervals,
6612 // we can detect more bridges.
6613 if (proto->intervals_size() > 1) {
6614 std::vector<IndexedInterval> indexed_intervals;
6615 for (int i = 0; i < proto->intervals().size(); ++i) {
6616 const int index = proto->intervals(i);
6617 indexed_intervals.push_back({i, IntegerValue(context_->StartMin(index)),
6618 IntegerValue(context_->EndMax(index))});
6619 }
6620 std::vector<std::vector<int>> components;
6621 GetOverlappingIntervalComponents(&indexed_intervals, &components);
6622
6623 if (components.size() > 1) {
6624 for (const std::vector<int>& component : components) {
6625 CumulativeConstraintProto* new_cumulative =
6626 context_->working_model->add_constraints()->mutable_cumulative();
6627 for (const int i : component) {
6628 new_cumulative->add_intervals(proto->intervals(i));
6629 *new_cumulative->add_demands() = proto->demands(i);
6630 }
6631 *new_cumulative->mutable_capacity() = proto->capacity();
6632 }
6633 context_->UpdateNewConstraintsVariableUsage();
6634 context_->UpdateRuleStats("cumulative: split into disjoint components");
6635 return RemoveConstraint(ct);
6636 }
6637 }
6638
6639 // TODO(user): move the algorithmic part of what we do below in a
6640 // separate function to unit test it more properly.
6641 {
6642 // Build max load profiles.
6643 absl::btree_map<int64_t, int64_t> time_to_demand_deltas;
6644 const int64_t capacity_min = context_->MinOf(proto->capacity());
6645 for (int i = 0; i < proto->intervals_size(); ++i) {
6646 const int interval_index = proto->intervals(i);
6647 const int64_t demand_max = context_->MaxOf(proto->demands(i));
6648 time_to_demand_deltas[context_->StartMin(interval_index)] += demand_max;
6649 time_to_demand_deltas[context_->EndMax(interval_index)] -= demand_max;
6650 }
6651
6652 // We construct the profile which correspond to a set of [time, next_time)
6653 // to max_profile height. And for each time in our discrete set of
6654 // time_exprs (all the start_min and end_max) we count for how often the
6655 // height was above the capacity before this time.
6656 //
6657 // This rely on the iteration in sorted order.
6658 int num_possible_overloads = 0;
6659 int64_t current_load = 0;
6660 absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before;
6661 for (const auto& it : time_to_demand_deltas) {
6662 num_possible_overloads_before[it.first] = num_possible_overloads;
6663 current_load += it.second;
6664 if (current_load > capacity_min) {
6665 ++num_possible_overloads;
6666 }
6667 }
6668 CHECK_EQ(current_load, 0);
6669
6670 // No possible overload with the min capacity.
6671 if (num_possible_overloads == 0) {
6672 context_->UpdateRuleStats(
6673 "cumulative: max profile is always under the min capacity");
6674 return RemoveConstraint(ct);
6675 }
6676
6677 // An interval that does not intersect with the potential_overload_domains
6678 // cannot contribute to a conflict. We can safely remove them.
6679 //
6680 // This is an extension of the presolve rule from
6681 // "Presolving techniques and linear relaxations for cumulative
6682 // scheduling" PhD dissertation by Stefan Heinz, ZIB.
6683 int new_size = 0;
6684 for (int i = 0; i < proto->intervals_size(); ++i) {
6685 const int index = proto->intervals(i);
6686 const int64_t start_min = context_->StartMin(index);
6687 const int64_t end_max = context_->EndMax(index);
6688
6689 // In the cumulative, if start_min == end_max, the interval is of size
6690 // zero and we can just ignore it. If the model is unsat or the interval
6691 // must be absent (start_min > end_max), this should be dealt with at
6692 // the interval constraint level and we can just remove it from here.
6693 //
6694 // Note that currently, the interpretation for interval of length zero
6695 // is different for the no-overlap constraint.
6696 if (start_min >= end_max) continue;
6697
6698 // Note that by construction, both point are in the map. The formula
6699 // counts exactly for how many time_exprs in [start_min, end_max), we have
6700 // a point in our discrete set of time that exceeded the capacity. Because
6701 // we included all the relevant points, this works.
6702 const int num_diff = num_possible_overloads_before.at(end_max) -
6703 num_possible_overloads_before.at(start_min);
6704 if (num_diff == 0) continue;
6705
6706 proto->set_intervals(new_size, proto->intervals(i));
6707 *proto->mutable_demands(new_size) = proto->demands(i);
6708 new_size++;
6709 }
6710
6711 if (new_size < proto->intervals_size()) {
6712 changed = true;
6713 proto->mutable_intervals()->Truncate(new_size);
6714 proto->mutable_demands()->erase(
6715 proto->mutable_demands()->begin() + new_size,
6716 proto->mutable_demands()->end());
6717 context_->UpdateRuleStats(
6718 "cumulative: remove never conflicting intervals.");
6719 }
6720 }
6721
6722 if (proto->intervals().empty()) {
6723 context_->UpdateRuleStats("cumulative: no intervals");
6724 return RemoveConstraint(ct);
6725 }
6726
6727 {
6728 int64_t max_of_performed_demand_mins = 0;
6729 int64_t sum_of_max_demands = 0;
6730 for (int i = 0; i < proto->intervals_size(); ++i) {
6731 const int interval_index = proto->intervals(i);
6732 const ConstraintProto& interval_ct =
6733 context_->working_model->constraints(interval_index);
6734
6735 const LinearExpressionProto& demand_expr = proto->demands(i);
6736 sum_of_max_demands += context_->MaxOf(demand_expr);
6737
6738 if (interval_ct.enforcement_literal().empty() &&
6739 context_->SizeMin(interval_index) > 0) {
6740 max_of_performed_demand_mins = std::max(max_of_performed_demand_mins,
6741 context_->MinOf(demand_expr));
6742 }
6743 }
6744
6745 const LinearExpressionProto& capacity_expr = proto->capacity();
6746 if (max_of_performed_demand_mins > context_->MinOf(capacity_expr)) {
6747 context_->UpdateRuleStats("cumulative: propagate min capacity.");
6748 if (!context_->IntersectDomainWith(
6749 capacity_expr, Domain(max_of_performed_demand_mins,
6750 std::numeric_limits<int64_t>::max()))) {
6751 return true;
6752 }
6753 }
6754
6755 if (max_of_performed_demand_mins > context_->MaxOf(capacity_expr)) {
6756 context_->UpdateRuleStats("cumulative: cannot fit performed demands");
6757 return context_->NotifyThatModelIsUnsat();
6758 }
6759
6760 if (sum_of_max_demands <= context_->MinOf(capacity_expr)) {
6761 context_->UpdateRuleStats("cumulative: capacity exceeds sum of demands");
6762 return RemoveConstraint(ct);
6763 }
6764 }
6765
6766 if (context_->IsFixed(proto->capacity())) {
6767 int64_t gcd = 0;
6768 for (int i = 0; i < ct->cumulative().demands_size(); ++i) {
6769 const LinearExpressionProto& demand_expr = ct->cumulative().demands(i);
6770 if (!context_->IsFixed(demand_expr)) {
6771 // Abort if the demand is not fixed.
6772 gcd = 1;
6773 break;
6774 }
6775 gcd = std::gcd(gcd, context_->MinOf(demand_expr));
6776 if (gcd == 1) break;
6777 }
6778 if (gcd > 1) {
6779 changed = true;
6780 for (int i = 0; i < ct->cumulative().demands_size(); ++i) {
6781 const int64_t demand = context_->MinOf(ct->cumulative().demands(i));
6782 *proto->mutable_demands(i) = ConstantExpressionProto(demand / gcd);
6783 }
6784
6785 const int64_t old_capacity = context_->MinOf(proto->capacity());
6786 *proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd);
6787 context_->UpdateRuleStats(
6788 "cumulative: divide demands and capacity by gcd");
6789 }
6790 }
6791
6792 const int num_intervals = proto->intervals_size();
6793 const LinearExpressionProto& capacity_expr = proto->capacity();
6794
6795 std::vector<LinearExpressionProto> start_exprs(num_intervals);
6796
6797 int num_duration_one = 0;
6798 int num_greater_half_capacity = 0;
6799
6800 bool has_optional_interval = false;
6801 for (int i = 0; i < num_intervals; ++i) {
6802 const int index = proto->intervals(i);
6803 // TODO(user): adapt in the presence of optional intervals.
6804 if (context_->ConstraintIsOptional(index)) has_optional_interval = true;
6805 const ConstraintProto& ct =
6806 context_->working_model->constraints(proto->intervals(i));
6807 const IntervalConstraintProto& interval = ct.interval();
6808 start_exprs[i] = interval.start();
6809
6810 const LinearExpressionProto& demand_expr = proto->demands(i);
6811 if (context_->SizeMin(index) == 1 && context_->SizeMax(index) == 1) {
6812 num_duration_one++;
6813 }
6814 if (context_->SizeMin(index) == 0) {
6815 // The behavior for zero-duration interval is currently not the same in
6816 // the no-overlap and the cumulative constraint.
6817 return changed;
6818 }
6819
6820 const int64_t demand_min = context_->MinOf(demand_expr);
6821 const int64_t demand_max = context_->MaxOf(demand_expr);
6822 if (demand_min > capacity_max / 2) {
6823 num_greater_half_capacity++;
6824 }
6825 if (demand_min > capacity_max) {
6826 context_->UpdateRuleStats("cumulative: demand_min exceeds capacity max");
6827 if (!context_->ConstraintIsOptional(index)) {
6828 return context_->NotifyThatModelIsUnsat();
6829 } else {
6830 CHECK_EQ(ct.enforcement_literal().size(), 1);
6831 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
6832 return true;
6833 }
6834 }
6835 return changed;
6836 } else if (demand_max > capacity_max) {
6837 if (ct.enforcement_literal().empty()) {
6838 context_->UpdateRuleStats(
6839 "cumulative: demand_max exceeds capacity max.");
6840 if (!context_->IntersectDomainWith(
6841 demand_expr,
6842 Domain(std::numeric_limits<int64_t>::min(), capacity_max))) {
6843 return true;
6844 }
6845 } else {
6846 // TODO(user): we abort because we cannot convert this to a no_overlap
6847 // for instance.
6848 context_->UpdateRuleStats(
6849 "cumulative: demand_max of optional interval exceeds capacity.");
6850 return changed;
6851 }
6852 }
6853 }
6854 if (num_greater_half_capacity == num_intervals) {
6855 if (num_duration_one == num_intervals && !has_optional_interval) {
6856 context_->UpdateRuleStats("cumulative: convert to all_different");
6857 ConstraintProto* new_ct = context_->working_model->add_constraints();
6858 auto* arg = new_ct->mutable_all_diff();
6859 for (const LinearExpressionProto& expr : start_exprs) {
6860 *arg->add_exprs() = expr;
6861 }
6862 if (!context_->IsFixed(capacity_expr)) {
6863 const int64_t capacity_min = context_->MinOf(capacity_expr);
6864 for (const LinearExpressionProto& expr : proto->demands()) {
6865 if (capacity_min >= context_->MaxOf(expr)) continue;
6866 LinearConstraintProto* fit =
6867 context_->working_model->add_constraints()->mutable_linear();
6868 fit->add_domain(0);
6869 fit->add_domain(std::numeric_limits<int64_t>::max());
6870 AddLinearExpressionToLinearConstraint(capacity_expr, 1, fit);
6872 }
6873 }
6874 context_->UpdateNewConstraintsVariableUsage();
6875 return RemoveConstraint(ct);
6876 } else {
6877 context_->UpdateRuleStats("cumulative: convert to no_overlap");
6878 // Before we remove the cumulative, add constraints to enforce that the
6879 // capacity is greater than the demand of any performed intervals.
6880 for (int i = 0; i < proto->demands_size(); ++i) {
6881 const LinearExpressionProto& demand_expr = proto->demands(i);
6882 const int64_t demand_max = context_->MaxOf(demand_expr);
6883 if (demand_max > context_->MinOf(capacity_expr)) {
6884 ConstraintProto* capacity_gt =
6885 context_->working_model->add_constraints();
6886 *capacity_gt->mutable_enforcement_literal() =
6887 context_->working_model->constraints(proto->intervals(i))
6888 .enforcement_literal();
6889 capacity_gt->mutable_linear()->add_domain(0);
6890 capacity_gt->mutable_linear()->add_domain(
6891 std::numeric_limits<int64_t>::max());
6893 capacity_gt->mutable_linear());
6895 capacity_gt->mutable_linear());
6896 }
6897 }
6898
6899 ConstraintProto* new_ct = context_->working_model->add_constraints();
6900 auto* arg = new_ct->mutable_no_overlap();
6901 for (const int interval : proto->intervals()) {
6902 arg->add_intervals(interval);
6903 }
6904 context_->UpdateNewConstraintsVariableUsage();
6905 return RemoveConstraint(ct);
6906 }
6907 }
6908
6909 RunPropagatorsForConstraint(*ct);
6910 return changed;
6911}
6912
6913bool CpModelPresolver::PresolveRoutes(ConstraintProto* ct) {
6914 if (context_->ModelIsUnsat()) return false;
6915 if (HasEnforcementLiteral(*ct)) return false;
6916 RoutesConstraintProto& proto = *ct->mutable_routes();
6917
6918 const int old_size = proto.literals_size();
6919 int new_size = 0;
6920 std::vector<bool> has_incoming_or_outgoing_arcs;
6921 const int num_arcs = proto.literals_size();
6922 for (int i = 0; i < num_arcs; ++i) {
6923 const int ref = proto.literals(i);
6924 const int tail = proto.tails(i);
6925 const int head = proto.heads(i);
6926
6927 if (tail >= has_incoming_or_outgoing_arcs.size()) {
6928 has_incoming_or_outgoing_arcs.resize(tail + 1, false);
6929 }
6930 if (head >= has_incoming_or_outgoing_arcs.size()) {
6931 has_incoming_or_outgoing_arcs.resize(head + 1, false);
6932 }
6933
6934 if (context_->LiteralIsFalse(ref)) {
6935 context_->UpdateRuleStats("routes: removed false arcs");
6936 continue;
6937 }
6938 proto.set_literals(new_size, ref);
6939 proto.set_tails(new_size, tail);
6940 proto.set_heads(new_size, head);
6941 ++new_size;
6942 has_incoming_or_outgoing_arcs[tail] = true;
6943 has_incoming_or_outgoing_arcs[head] = true;
6944 }
6945
6946 if (old_size > 0 && new_size == 0) {
6947 // A routes constraint cannot have a self loop on 0. Therefore, if there
6948 // were arcs, it means it contains non zero nodes. Without arc, the
6949 // constraint is unfeasible.
6950 return context_->NotifyThatModelIsUnsat(
6951 "routes: graph with nodes and no arcs");
6952 }
6953
6954 // if a node misses an incomping or outgoing arc, the model is trivially
6955 // infeasible.
6956 for (int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) {
6957 if (!has_incoming_or_outgoing_arcs[n]) {
6958 return context_->NotifyThatModelIsUnsat(absl::StrCat(
6959 "routes: node ", n, " misses incoming or outgoing arcs"));
6960 }
6961 }
6962
6963 if (new_size < num_arcs) {
6964 proto.mutable_literals()->Truncate(new_size);
6965 proto.mutable_tails()->Truncate(new_size);
6966 proto.mutable_heads()->Truncate(new_size);
6967 return true;
6968 }
6969
6970 RunPropagatorsForConstraint(*ct);
6971 return false;
6972}
6973
6974bool CpModelPresolver::PresolveCircuit(ConstraintProto* ct) {
6975 if (context_->ModelIsUnsat()) return false;
6976 if (HasEnforcementLiteral(*ct)) return false;
6977 CircuitConstraintProto& proto = *ct->mutable_circuit();
6978
6979 // The indexing might not be dense, so fix that first.
6980 ReindexArcs(ct->mutable_circuit()->mutable_tails(),
6981 ct->mutable_circuit()->mutable_heads());
6982
6983 // Convert the flat structure to a graph, note that we includes all the arcs
6984 // here (even if they are at false).
6985 std::vector<std::vector<int>> incoming_arcs;
6986 std::vector<std::vector<int>> outgoing_arcs;
6987 int num_nodes = 0;
6988 const int num_arcs = proto.literals_size();
6989 for (int i = 0; i < num_arcs; ++i) {
6990 const int ref = proto.literals(i);
6991 const int tail = proto.tails(i);
6992 const int head = proto.heads(i);
6993 num_nodes = std::max(num_nodes, std::max(tail, head) + 1);
6994 if (std::max(tail, head) >= incoming_arcs.size()) {
6995 incoming_arcs.resize(std::max(tail, head) + 1);
6996 outgoing_arcs.resize(std::max(tail, head) + 1);
6997 }
6998 incoming_arcs[head].push_back(ref);
6999 outgoing_arcs[tail].push_back(ref);
7000 }
7001
7002 // All the node must have some incoming and outgoing arcs.
7003 for (int i = 0; i < num_nodes; ++i) {
7004 if (incoming_arcs[i].empty() || outgoing_arcs[i].empty()) {
7005 return MarkConstraintAsFalse(ct);
7006 }
7007 }
7008
7009 // Note that it is important to reach the fixed point here:
7010 // One arc at true, then all other arc at false. This is because we rely
7011 // on this in case the circuit is fully specified below.
7012 //
7013 // TODO(user): Use a better complexity if needed.
7014 bool loop_again = true;
7015 int num_fixed_at_true = 0;
7016 while (loop_again) {
7017 loop_again = false;
7018 for (const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
7019 for (const std::vector<int>& refs : *node_to_refs) {
7020 if (refs.size() == 1) {
7021 if (!context_->LiteralIsTrue(refs.front())) {
7022 ++num_fixed_at_true;
7023 if (!context_->SetLiteralToTrue(refs.front())) return true;
7024 }
7025 continue;
7026 }
7027
7028 // At most one true, so if there is one, mark all the other to false.
7029 int num_true = 0;
7030 int true_ref;
7031 for (const int ref : refs) {
7032 if (context_->LiteralIsTrue(ref)) {
7033 ++num_true;
7034 true_ref = ref;
7035 break;
7036 }
7037 }
7038 if (num_true > 1) {
7039 return context_->NotifyThatModelIsUnsat();
7040 }
7041 if (num_true == 1) {
7042 for (const int ref : refs) {
7043 if (ref != true_ref) {
7044 if (!context_->IsFixed(ref)) {
7045 context_->UpdateRuleStats("circuit: set literal to false.");
7046 loop_again = true;
7047 }
7048 if (!context_->SetLiteralToFalse(ref)) return true;
7049 }
7050 }
7051 }
7052 }
7053 }
7054 }
7055 if (num_fixed_at_true > 0) {
7056 context_->UpdateRuleStats("circuit: fixed singleton arcs.");
7057 }
7058
7059 // Remove false arcs.
7060 int new_size = 0;
7061 int num_true = 0;
7062 int circuit_start = -1;
7063 std::vector<int> next(num_nodes, -1);
7064 std::vector<int> new_in_degree(num_nodes, 0);
7065 std::vector<int> new_out_degree(num_nodes, 0);
7066 for (int i = 0; i < num_arcs; ++i) {
7067 const int ref = proto.literals(i);
7068 if (context_->LiteralIsFalse(ref)) continue;
7069 if (context_->LiteralIsTrue(ref)) {
7070 if (next[proto.tails(i)] != -1) {
7071 return context_->NotifyThatModelIsUnsat();
7072 }
7073 next[proto.tails(i)] = proto.heads(i);
7074 if (proto.tails(i) != proto.heads(i)) {
7075 circuit_start = proto.tails(i);
7076 }
7077 ++num_true;
7078 }
7079 ++new_out_degree[proto.tails(i)];
7080 ++new_in_degree[proto.heads(i)];
7081 proto.set_tails(new_size, proto.tails(i));
7082 proto.set_heads(new_size, proto.heads(i));
7083 proto.set_literals(new_size, ref);
7084 ++new_size;
7085 }
7086
7087 // Detect infeasibility due to a node having no more incoming or outgoing arc.
7088 // This is a bit tricky because for now the meaning of the constraint says
7089 // that all nodes that appear in at least one of the arcs must be in the
7090 // circuit or have a self-arc. So if any such node ends up with an incoming or
7091 // outgoing degree of zero once we remove false arcs then the constraint is
7092 // infeasible!
7093 for (int i = 0; i < num_nodes; ++i) {
7094 if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
7095 return context_->NotifyThatModelIsUnsat();
7096 }
7097 }
7098
7099 // Test if a subcircuit is already present.
7100 if (circuit_start != -1) {
7101 std::vector<bool> visited(num_nodes, false);
7102 int current = circuit_start;
7103 while (current != -1 && !visited[current]) {
7104 visited[current] = true;
7105 current = next[current];
7106 }
7107 if (current == circuit_start) {
7108 // We have a sub-circuit! mark all other arc false except self-loop not in
7109 // circuit.
7110 std::vector<bool> has_self_arc(num_nodes, false);
7111 for (int i = 0; i < num_arcs; ++i) {
7112 if (visited[proto.tails(i)]) continue;
7113 if (proto.tails(i) == proto.heads(i)) {
7114 has_self_arc[proto.tails(i)] = true;
7115 if (!context_->SetLiteralToTrue(proto.literals(i))) return true;
7116 } else {
7117 if (!context_->SetLiteralToFalse(proto.literals(i))) return true;
7118 }
7119 }
7120 for (int n = 0; n < num_nodes; ++n) {
7121 if (!visited[n] && !has_self_arc[n]) {
7122 // We have a subircuit, but it doesn't cover all the mandatory nodes.
7123 return MarkConstraintAsFalse(ct);
7124 }
7125 }
7126 context_->UpdateRuleStats("circuit: fully specified.");
7127 return RemoveConstraint(ct);
7128 }
7129 } else {
7130 // All self loop?
7131 if (num_true == new_size) {
7132 context_->UpdateRuleStats("circuit: empty circuit.");
7133 return RemoveConstraint(ct);
7134 }
7135 }
7136
7137 // Look for in/out-degree of two, this will imply that one of the indicator
7138 // Boolean is equal to the negation of the other.
7139 for (int i = 0; i < num_nodes; ++i) {
7140 for (const std::vector<int>* arc_literals :
7141 {&incoming_arcs[i], &outgoing_arcs[i]}) {
7142 std::vector<int> literals;
7143 for (const int ref : *arc_literals) {
7144 if (context_->LiteralIsFalse(ref)) continue;
7145 if (context_->LiteralIsTrue(ref)) {
7146 literals.clear();
7147 break;
7148 }
7149 literals.push_back(ref);
7150 }
7151 if (literals.size() == 2 && literals[0] != NegatedRef(literals[1])) {
7152 context_->UpdateRuleStats("circuit: degree 2");
7153 if (!context_->StoreBooleanEqualityRelation(literals[0],
7154 NegatedRef(literals[1]))) {
7155 return true;
7156 }
7157 }
7158 }
7159 }
7160
7161 // Truncate the circuit and return.
7162 if (new_size < num_arcs) {
7163 proto.mutable_tails()->Truncate(new_size);
7164 proto.mutable_heads()->Truncate(new_size);
7165 proto.mutable_literals()->Truncate(new_size);
7166 context_->UpdateRuleStats("circuit: removed false arcs.");
7167 return true;
7168 }
7169 RunPropagatorsForConstraint(*ct);
7170 return false;
7171}
7172
7173bool CpModelPresolver::PresolveAutomaton(ConstraintProto* ct) {
7174 if (context_->ModelIsUnsat()) return false;
7175 if (HasEnforcementLiteral(*ct)) return false;
7176
7177 AutomatonConstraintProto* proto = ct->mutable_automaton();
7178 if (proto->exprs_size() == 0 || proto->transition_label_size() == 0) {
7179 return false;
7180 }
7181
7182 bool changed = false;
7183 for (int i = 0; i < proto->exprs_size(); ++i) {
7184 changed |= CanonicalizeLinearExpression(*ct, proto->mutable_exprs(i));
7185 }
7186
7187 std::vector<absl::flat_hash_set<int64_t>> reachable_states;
7188 std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
7189 PropagateAutomaton(*proto, *context_, &reachable_states, &reachable_labels);
7190
7191 // Filter domains and compute the union of all relevant labels.
7192 for (int time = 0; time < reachable_labels.size(); ++time) {
7193 const LinearExpressionProto& expr = proto->exprs(time);
7194 if (context_->IsFixed(expr)) {
7195 if (!reachable_labels[time].contains(context_->FixedValue(expr))) {
7196 return MarkConstraintAsFalse(ct);
7197 }
7198 } else {
7199 std::vector<int64_t> unscaled_reachable_labels;
7200 for (const int64_t label : reachable_labels[time]) {
7201 unscaled_reachable_labels.push_back(GetInnerVarValue(expr, label));
7202 }
7203 bool removed_values = false;
7204 if (!context_->IntersectDomainWith(
7205 expr.vars(0), Domain::FromValues(unscaled_reachable_labels),
7206 &removed_values)) {
7207 return true;
7208 }
7209 if (removed_values) {
7210 context_->UpdateRuleStats("automaton: reduce variable domain");
7211 }
7212 }
7213 }
7214
7215 return changed;
7216}
7217
7218bool CpModelPresolver::PresolveReservoir(ConstraintProto* ct) {
7219 if (context_->ModelIsUnsat()) return false;
7220 if (HasEnforcementLiteral(*ct)) return false;
7221
7222 ReservoirConstraintProto& proto = *ct->mutable_reservoir();
7223 bool changed = false;
7224 for (LinearExpressionProto& exp : *(proto.mutable_time_exprs())) {
7225 changed |= CanonicalizeLinearExpression(*ct, &exp);
7226 }
7227 for (LinearExpressionProto& exp : *(proto.mutable_level_changes())) {
7228 changed |= CanonicalizeLinearExpression(*ct, &exp);
7229 }
7230
7231 if (proto.active_literals().empty()) {
7232 const int true_literal = context_->GetTrueLiteral();
7233 for (int i = 0; i < proto.time_exprs_size(); ++i) {
7234 proto.add_active_literals(true_literal);
7235 }
7236 changed = true;
7237 }
7238
7239 const auto& demand_is_null = [&](int i) {
7240 return (context_->IsFixed(proto.level_changes(i)) &&
7241 context_->FixedValue(proto.level_changes(i)) == 0) ||
7242 context_->LiteralIsFalse(proto.active_literals(i));
7243 };
7244
7245 // Remove zero level_changes, and inactive events.
7246 int num_zeros = 0;
7247 for (int i = 0; i < proto.level_changes_size(); ++i) {
7248 if (demand_is_null(i)) num_zeros++;
7249 }
7250
7251 if (num_zeros > 0) { // Remove null events
7252 changed = true;
7253 int new_size = 0;
7254 for (int i = 0; i < proto.level_changes_size(); ++i) {
7255 if (demand_is_null(i)) continue;
7256 *proto.mutable_level_changes(new_size) = proto.level_changes(i);
7257 *proto.mutable_time_exprs(new_size) = proto.time_exprs(i);
7258 proto.set_active_literals(new_size, proto.active_literals(i));
7259 new_size++;
7260 }
7261
7262 proto.mutable_level_changes()->erase(
7263 proto.mutable_level_changes()->begin() + new_size,
7264 proto.mutable_level_changes()->end());
7265 proto.mutable_time_exprs()->erase(
7266 proto.mutable_time_exprs()->begin() + new_size,
7267 proto.mutable_time_exprs()->end());
7268 proto.mutable_active_literals()->Truncate(new_size);
7269
7270 context_->UpdateRuleStats(
7271 "reservoir: remove zero level_changes or inactive events.");
7272 }
7273
7274 // The rest of the presolve only applies if all demands are fixed.
7275 for (const LinearExpressionProto& level_change : proto.level_changes()) {
7276 if (!context_->IsFixed(level_change)) return changed;
7277 }
7278
7279 const int num_events = proto.level_changes_size();
7280 int64_t gcd = proto.level_changes().empty()
7281 ? 0
7282 : std::abs(context_->FixedValue(proto.level_changes(0)));
7283 int num_positives = 0;
7284 int num_negatives = 0;
7285 int64_t max_sum_of_positive_level_changes = 0;
7286 int64_t min_sum_of_negative_level_changes = 0;
7287 for (int i = 0; i < num_events; ++i) {
7288 const int64_t demand = context_->FixedValue(proto.level_changes(i));
7289 gcd = std::gcd(gcd, std::abs(demand));
7290 if (demand > 0) {
7291 num_positives++;
7292 max_sum_of_positive_level_changes += demand;
7293 } else {
7294 DCHECK_LT(demand, 0);
7295 num_negatives++;
7296 min_sum_of_negative_level_changes += demand;
7297 }
7298 }
7299
7300 if (min_sum_of_negative_level_changes >= proto.min_level() &&
7301 max_sum_of_positive_level_changes <= proto.max_level()) {
7302 context_->UpdateRuleStats("reservoir: always feasible");
7303 return RemoveConstraint(ct);
7304 }
7305
7306 if (min_sum_of_negative_level_changes > proto.max_level() ||
7307 max_sum_of_positive_level_changes < proto.min_level()) {
7308 context_->UpdateRuleStats("reservoir: trivially infeasible");
7309 return context_->NotifyThatModelIsUnsat();
7310 }
7311
7312 if (min_sum_of_negative_level_changes > proto.min_level()) {
7313 proto.set_min_level(min_sum_of_negative_level_changes);
7314 context_->UpdateRuleStats(
7315 "reservoir: increase min_level to reachable value");
7316 }
7317
7318 if (max_sum_of_positive_level_changes < proto.max_level()) {
7319 proto.set_max_level(max_sum_of_positive_level_changes);
7320 context_->UpdateRuleStats("reservoir: reduce max_level to reachable value");
7321 }
7322
7323 if (proto.min_level() <= 0 && proto.max_level() >= 0 &&
7324 (num_positives == 0 || num_negatives == 0)) {
7325 // If all level_changes have the same sign, and if the initial state is
7326 // always feasible, we do not care about the order, just the sum.
7327 auto* const sum_ct = context_->working_model->add_constraints();
7328 auto* const sum = sum_ct->mutable_linear();
7329 int64_t fixed_contrib = 0;
7330 for (int i = 0; i < proto.level_changes_size(); ++i) {
7331 const int64_t demand = context_->FixedValue(proto.level_changes(i));
7332 DCHECK_NE(demand, 0);
7333
7334 const int active = proto.active_literals(i);
7335 if (RefIsPositive(active)) {
7336 sum->add_vars(active);
7337 sum->add_coeffs(demand);
7338 } else {
7339 sum->add_vars(PositiveRef(active));
7340 sum->add_coeffs(-demand);
7341 fixed_contrib += demand;
7342 }
7343 }
7344 sum->add_domain(proto.min_level() - fixed_contrib);
7345 sum->add_domain(proto.max_level() - fixed_contrib);
7346 CanonicalizeLinear(sum_ct);
7347 context_->UpdateRuleStats("reservoir: converted to linear");
7348 return RemoveConstraint(ct);
7349 }
7350
7351 if (gcd > 1) {
7352 for (int i = 0; i < proto.level_changes_size(); ++i) {
7353 proto.mutable_level_changes(i)->set_offset(
7354 context_->FixedValue(proto.level_changes(i)) / gcd);
7355 proto.mutable_level_changes(i)->clear_vars();
7356 proto.mutable_level_changes(i)->clear_coeffs();
7357 }
7358
7359 // Adjust min and max levels.
7360 // max level is always rounded down.
7361 // min level is always rounded up.
7362 const Domain reduced_domain = Domain({proto.min_level(), proto.max_level()})
7363 .InverseMultiplicationBy(gcd);
7364 proto.set_min_level(reduced_domain.Min());
7365 proto.set_max_level(reduced_domain.Max());
7366 context_->UpdateRuleStats(
7367 "reservoir: simplify level_changes and levels by gcd.");
7368 }
7369
7370 if (num_positives == 1 && num_negatives > 0) {
7371 context_->UpdateRuleStats(
7372 "TODO reservoir: one producer, multiple consumers.");
7373 }
7374
7375 absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set;
7376 for (int i = 0; i < proto.level_changes_size(); ++i) {
7377 const LinearExpressionProto& time = proto.time_exprs(i);
7378 const int var = context_->IsFixed(time) ? std::numeric_limits<int>::min()
7379 : time.vars(0);
7380 const int64_t coeff = context_->IsFixed(time) ? 0 : time.coeffs(0);
7381 const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple(
7382 var, coeff,
7383 context_->IsFixed(time) ? context_->FixedValue(time) : time.offset(),
7384 proto.active_literals(i));
7385 if (time_active_set.contains(key)) {
7386 context_->UpdateRuleStats("TODO reservoir: merge synchronized events.");
7387 break;
7388 } else {
7389 time_active_set.insert(key);
7390 }
7391 }
7392
7393 RunPropagatorsForConstraint(*ct);
7394 return changed;
7395}
7396
7397// TODO(user): It is probably more efficient to keep all the bool_and in a
7398// global place during all the presolve, and just output them at the end
7399// rather than modifying more than once the proto.
7400void CpModelPresolver::ConvertToBoolAnd() {
7401 absl::flat_hash_map<int, int> ref_to_bool_and;
7402 const int num_constraints = context_->working_model->constraints_size();
7403 std::vector<int> to_remove;
7404 for (int c = 0; c < num_constraints; ++c) {
7405 const ConstraintProto& ct = context_->working_model->constraints(c);
7406 if (HasEnforcementLiteral(ct)) continue;
7407
7408 if (ct.constraint_case() == ConstraintProto::kBoolOr &&
7409 ct.bool_or().literals().size() == 2) {
7410 AddImplication(NegatedRef(ct.bool_or().literals(0)),
7411 ct.bool_or().literals(1), context_->working_model,
7412 &ref_to_bool_and);
7413 to_remove.push_back(c);
7414 continue;
7415 }
7416
7417 if (ct.constraint_case() == ConstraintProto::kAtMostOne &&
7418 ct.at_most_one().literals().size() == 2) {
7419 AddImplication(ct.at_most_one().literals(0),
7420 NegatedRef(ct.at_most_one().literals(1)),
7421 context_->working_model, &ref_to_bool_and);
7422 to_remove.push_back(c);
7423 continue;
7424 }
7425 }
7426
7427 context_->UpdateNewConstraintsVariableUsage();
7428 for (const int c : to_remove) {
7429 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7430 CHECK(RemoveConstraint(ct));
7431 context_->UpdateConstraintVariableUsage(c);
7432 }
7433}
7434
7435void CpModelPresolver::RunPropagatorsForConstraint(const ConstraintProto& ct) {
7436 if (context_->ModelIsUnsat()) return;
7437
7438 Model model;
7439
7440 // Enable as many propagators as possible. We do not care if some propagator
7441 // is a bit slow or if the explanation is too big: anything that improves our
7442 // bounds is an improvement.
7443 SatParameters local_params;
7444 local_params.set_use_try_edge_reasoning_in_no_overlap_2d(true);
7445 local_params.set_exploit_all_precedences(true);
7446 local_params.set_use_hard_precedences_in_cumulative(true);
7447 local_params.set_max_num_intervals_for_timetable_edge_finding(1000);
7448 local_params.set_use_overload_checker_in_cumulative(true);
7449 local_params.set_use_strong_propagation_in_disjunctive(true);
7450 local_params.set_use_timetable_edge_finding_in_cumulative(true);
7451 local_params.set_max_pairs_pairwise_reasoning_in_no_overlap_2d(50000);
7452 local_params.set_use_timetabling_in_no_overlap_2d(true);
7453 local_params.set_use_energetic_reasoning_in_no_overlap_2d(true);
7454 local_params.set_use_area_energetic_reasoning_in_no_overlap_2d(true);
7455 local_params.set_use_conservative_scale_overload_checker(true);
7456 local_params.set_use_dual_scheduling_heuristics(true);
7457
7458 model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
7459 std::vector<int> variable_mapping;
7460 CreateValidModelWithSingleConstraint(ct, context_, &variable_mapping,
7461 &tmp_model_);
7462 DCHECK_EQ(ValidateCpModel(tmp_model_, false), "");
7463 if (!LoadModelForPresolve(tmp_model_, std::move(local_params), context_,
7464 &model, "single constraint")) {
7465 return;
7466 }
7467
7468 time_limit_->AdvanceDeterministicTime(
7469 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
7470 auto* mapping = model.GetOrCreate<CpModelMapping>();
7471 auto* integer_trail = model.GetOrCreate<IntegerTrail>();
7472 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
7473 auto* trail = model.GetOrCreate<Trail>();
7474
7475 int num_equiv = 0;
7476 int num_changed_bounds = 0;
7477 int num_fixed_bools = 0;
7478 for (int var = 0; var < variable_mapping.size(); ++var) {
7479 const int proto_var = variable_mapping[var];
7480 if (mapping->IsBoolean(var)) {
7481 const Literal l = mapping->Literal(var);
7482 if (trail->Assignment().LiteralIsFalse(l)) {
7483 if (!context_->SetLiteralToFalse(proto_var)) return;
7484 ++num_fixed_bools;
7485 continue;
7486 } else if (trail->Assignment().LiteralIsTrue(l)) {
7487 if (!context_->SetLiteralToTrue(proto_var)) return;
7488 ++num_fixed_bools;
7489 continue;
7490 }
7491 // Add Boolean equivalence relations.
7492 const Literal r = implication_graph->RepresentativeOf(l);
7493 if (r != l) {
7494 ++num_equiv;
7495 const int r_var =
7496 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
7497 if (r_var < 0) continue;
7498 if (!context_->StoreBooleanEqualityRelation(
7499 proto_var, r.IsPositive() ? r_var : NegatedRef(r_var))) {
7500 return;
7501 }
7502 }
7503 } else {
7504 // Restrict variable domain.
7505 bool changed = false;
7506 if (!context_->IntersectDomainWith(
7507 proto_var,
7508 integer_trail->InitialVariableDomain(mapping->Integer(var)),
7509 &changed)) {
7510 return;
7511 }
7512 if (changed) ++num_changed_bounds;
7513 }
7514 }
7515 if (num_changed_bounds > 0) {
7516 context_->UpdateRuleStats("propagators: changed bounds",
7517 num_changed_bounds);
7518 }
7519 if (num_fixed_bools > 0) {
7520 context_->UpdateRuleStats("propagators: fixed booleans", num_fixed_bools);
7521 }
7522}
7523
7524// TODO(user): It might make sense to run this in parallel. The same apply for
7525// other expansive and self-contains steps like symmetry detection, etc...
7526void CpModelPresolver::Probe() {
7527 auto probing_timer =
7528 std::make_unique<PresolveTimer>(__FUNCTION__, logger_, time_limit_);
7529
7530 Model model;
7531 if (!LoadModelForProbing(context_, &model)) return;
7532
7533 // Probe.
7534 //
7535 // TODO(user): Compute the transitive reduction instead of just the
7536 // equivalences, and use the newly learned binary clauses?
7537 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
7538 auto* sat_solver = model.GetOrCreate<SatSolver>();
7539 auto* mapping = model.GetOrCreate<CpModelMapping>();
7540 auto* prober = model.GetOrCreate<Prober>();
7541
7542 // Try to detect trivial clauses thanks to implications.
7543 // This can be slow, so we bound the amount of work done.
7544 //
7545 // Idea: If we have l1, l2 in a bool_or and not(l1) => l2, the constraint is
7546 // always true.
7547 //
7548 // Correctness: Note that we always replace a clause with another one that
7549 // subsumes it. So we are correct even if new clauses are learned and used
7550 // for propagation along the way.
7551 //
7552 // TODO(user): Improve the algo?
7553 const auto& assignment = sat_solver->Assignment();
7554 prober->SetPropagationCallback([&](Literal decision) {
7555 if (probing_timer->WorkLimitIsReached()) return;
7556 const int decision_var =
7557 mapping->GetProtoVariableFromBooleanVariable(decision.Variable());
7558 if (decision_var < 0) return;
7559 probing_timer->TrackSimpleLoop(
7560 context_->VarToConstraints(decision_var).size());
7561 std::vector<int> to_update;
7562 for (const int c : context_->VarToConstraints(decision_var)) {
7563 if (c < 0) continue;
7564 const ConstraintProto& ct = context_->working_model->constraints(c);
7565 if (ct.enforcement_literal().size() > 2) {
7566 // Any l for which decision => l can be removed.
7567 //
7568 // If decision => not(l), constraint can never be satisfied. However
7569 // because we don't know if this constraint was part of the
7570 // propagation we replace it by an implication.
7571 //
7572 // TODO(user): remove duplication with code below.
7573 // TODO(user): If decision appear positively, we could potentially
7574 // remove a bunch of terms (all the ones involving variables implied
7575 // by the decision) from the inner constraint, especially in the
7576 // linear case.
7577 int decision_ref;
7578 int false_ref;
7579 bool decision_is_positive = false;
7580 bool has_false_literal = false;
7581 bool simplification_possible = false;
7582 probing_timer->TrackSimpleLoop(ct.enforcement_literal().size());
7583 for (const int ref : ct.enforcement_literal()) {
7584 const Literal lit = mapping->Literal(ref);
7585 if (PositiveRef(ref) == decision_var) {
7586 decision_ref = ref;
7587 decision_is_positive = assignment.LiteralIsTrue(lit);
7588 if (!decision_is_positive) break;
7589 continue;
7590 }
7591 if (assignment.LiteralIsFalse(lit)) {
7592 false_ref = ref;
7593 has_false_literal = true;
7594 } else if (assignment.LiteralIsTrue(lit)) {
7595 // If decision => l, we can remove l from the list.
7596 simplification_possible = true;
7597 }
7598 }
7599 if (!decision_is_positive) continue;
7600
7601 if (has_false_literal) {
7602 // Reduce to implication.
7603 auto* mutable_ct = context_->working_model->mutable_constraints(c);
7604 mutable_ct->Clear();
7605 mutable_ct->add_enforcement_literal(decision_ref);
7606 mutable_ct->mutable_bool_and()->add_literals(NegatedRef(false_ref));
7607 context_->UpdateRuleStats(
7608 "probing: reduced enforced constraint to implication.");
7609 to_update.push_back(c);
7610 continue;
7611 }
7612
7613 if (simplification_possible) {
7614 int new_size = 0;
7615 auto* mutable_enforcements =
7616 context_->working_model->mutable_constraints(c)
7617 ->mutable_enforcement_literal();
7618 for (const int ref : ct.enforcement_literal()) {
7619 if (PositiveRef(ref) != decision_var &&
7620 assignment.LiteralIsTrue(mapping->Literal(ref))) {
7621 continue;
7622 }
7623 mutable_enforcements->Set(new_size++, ref);
7624 }
7625 mutable_enforcements->Truncate(new_size);
7626 context_->UpdateRuleStats("probing: simplified enforcement list.");
7627 to_update.push_back(c);
7628 }
7629 continue;
7630 }
7631
7632 if (ct.constraint_case() != ConstraintProto::kBoolOr) continue;
7633 if (ct.bool_or().literals().size() <= 2) continue;
7634
7635 int decision_ref;
7636 int true_ref;
7637 bool decision_is_negative = false;
7638 bool has_true_literal = false;
7639 bool simplification_possible = false;
7640 probing_timer->TrackSimpleLoop(ct.bool_or().literals().size());
7641 for (const int ref : ct.bool_or().literals()) {
7642 const Literal lit = mapping->Literal(ref);
7643 if (PositiveRef(ref) == decision_var) {
7644 decision_ref = ref;
7645 decision_is_negative = assignment.LiteralIsFalse(lit);
7646 if (!decision_is_negative) break;
7647 continue;
7648 }
7649 if (assignment.LiteralIsTrue(lit)) {
7650 true_ref = ref;
7651 has_true_literal = true;
7652 } else if (assignment.LiteralIsFalse(lit)) {
7653 // If not(l1) => not(l2), we can remove l2 from the clause.
7654 simplification_possible = true;
7655 }
7656 }
7657 if (!decision_is_negative) continue;
7658
7659 if (has_true_literal) {
7660 // This will later be merged with the current implications and removed
7661 // if it is a duplicate.
7662 auto* mutable_bool_or =
7663 context_->working_model->mutable_constraints(c)->mutable_bool_or();
7664 mutable_bool_or->mutable_literals()->Clear();
7665 mutable_bool_or->add_literals(decision_ref);
7666 mutable_bool_or->add_literals(true_ref);
7667 context_->UpdateRuleStats("probing: bool_or reduced to implication");
7668 to_update.push_back(c);
7669 continue;
7670 }
7671
7672 if (simplification_possible) {
7673 int new_size = 0;
7674 auto* mutable_bool_or =
7675 context_->working_model->mutable_constraints(c)->mutable_bool_or();
7676 for (const int ref : ct.bool_or().literals()) {
7677 if (PositiveRef(ref) != decision_var &&
7678 assignment.LiteralIsFalse(mapping->Literal(ref))) {
7679 continue;
7680 }
7681 mutable_bool_or->set_literals(new_size++, ref);
7682 }
7683 mutable_bool_or->mutable_literals()->Truncate(new_size);
7684 context_->UpdateRuleStats("probing: simplified clauses.");
7685 to_update.push_back(c);
7686 }
7687 }
7688
7689 absl::c_sort(to_update);
7690 for (const int c : to_update) {
7691 context_->UpdateConstraintVariableUsage(c);
7692 }
7693 });
7694
7695 prober->ProbeBooleanVariables(
7696 context_->params().probing_deterministic_time_limit());
7697
7698 probing_timer->AddCounter("probed", prober->num_decisions());
7699 probing_timer->AddToWork(
7700 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
7701 if (sat_solver->ModelIsUnsat() || !implication_graph->DetectEquivalences()) {
7702 return (void)context_->NotifyThatModelIsUnsat("during probing");
7703 }
7704
7705 // Update the presolve context with fixed Boolean variables.
7706 int num_fixed = 0;
7707 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
7708 for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
7709 const Literal l = sat_solver->LiteralTrail()[i];
7710 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
7711 if (var >= 0) {
7712 const int ref = l.IsPositive() ? var : NegatedRef(var);
7713 if (context_->IsFixed(ref)) continue;
7714 ++num_fixed;
7715 if (!context_->SetLiteralToTrue(ref)) return;
7716 }
7717 }
7718 probing_timer->AddCounter("fixed_bools", num_fixed);
7719
7720 int num_equiv = 0;
7721 int num_changed_bounds = 0;
7722 const int num_variables = context_->working_model->variables().size();
7723 auto* integer_trail = model.GetOrCreate<IntegerTrail>();
7724 for (int var = 0; var < num_variables; ++var) {
7725 // Restrict IntegerVariable domain.
7726 // Note that Boolean are already dealt with above.
7727 if (!mapping->IsBoolean(var)) {
7728 bool changed = false;
7729 if (!context_->IntersectDomainWith(
7730 var, integer_trail->InitialVariableDomain(mapping->Integer(var)),
7731 &changed)) {
7732 return;
7733 }
7734 if (changed) ++num_changed_bounds;
7735 continue;
7736 }
7737
7738 // Add Boolean equivalence relations.
7739 const Literal l = mapping->Literal(var);
7740 const Literal r = implication_graph->RepresentativeOf(l);
7741 if (r != l) {
7742 ++num_equiv;
7743 const int r_var =
7744 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
7745 CHECK_GE(r_var, 0);
7746 if (!context_->StoreBooleanEqualityRelation(
7747 var, r.IsPositive() ? r_var : NegatedRef(r_var))) {
7748 return;
7749 }
7750 }
7751 }
7752 probing_timer->AddCounter("new_bounds", num_changed_bounds);
7753 probing_timer->AddCounter("equiv", num_equiv);
7754 probing_timer->AddCounter("new_binary_clauses",
7755 prober->num_new_binary_clauses());
7756
7757 // Note that we prefer to run this after we exported all equivalence to the
7758 // context, so that our enforcement list can be presolved to the best of our
7759 // knowledge.
7760 DetectDuplicateConstraintsWithDifferentEnforcements(
7761 mapping, implication_graph, model.GetOrCreate<Trail>());
7762
7763 // Stop probing timer now and display info.
7764 probing_timer.reset();
7765
7766 // Run clique merging using detected implications from probing.
7767 if (context_->params().merge_at_most_one_work_limit() > 0.0) {
7768 PresolveTimer timer("MaxClique", logger_, time_limit_);
7769 std::vector<std::vector<Literal>> cliques;
7770 std::vector<int> clique_ct_index;
7771
7772 // TODO(user): On large model, most of the time is spend in this copy,
7773 // clearing and updating the constraint variable graph...
7774 int64_t num_literals_before = 0;
7775 const int num_constraints = context_->working_model->constraints_size();
7776 for (int c = 0; c < num_constraints; ++c) {
7777 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7778 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
7779 std::vector<Literal> clique;
7780 for (const int ref : ct->at_most_one().literals()) {
7781 clique.push_back(mapping->Literal(ref));
7782 }
7783 num_literals_before += clique.size();
7784 cliques.push_back(clique);
7785 ct->Clear();
7786 context_->UpdateConstraintVariableUsage(c);
7787 } else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
7788 if (ct->enforcement_literal().size() != 1) continue;
7789 const Literal enforcement =
7790 mapping->Literal(ct->enforcement_literal(0));
7791 for (const int ref : ct->bool_and().literals()) {
7792 if (ref == ct->enforcement_literal(0)) continue;
7793 num_literals_before += 2;
7794 cliques.push_back({enforcement, mapping->Literal(ref).Negated()});
7795 }
7796 ct->Clear();
7797 context_->UpdateConstraintVariableUsage(c);
7798 }
7799 }
7800 const int64_t num_old_cliques = cliques.size();
7801
7802 // We adapt the limit if there is a lot of literals in amo/implications.
7803 // Usually we can have big reduction on large problem so it seems
7804 // worthwhile.
7805 double limit = context_->params().merge_at_most_one_work_limit();
7806 if (num_literals_before > 1e6) {
7807 limit *= num_literals_before / 1e6;
7808 }
7809
7810 double dtime = 0.0;
7811 implication_graph->MergeAtMostOnes(absl::MakeSpan(cliques),
7812 SafeDoubleToInt64(limit), &dtime);
7813 timer.AddToWork(dtime);
7814
7815 // Note that because TransformIntoMaxCliques() extend cliques, we are ok
7816 // to ignore any unmapped literal. In case of equivalent literal, we always
7817 // use the smaller indices as a representative, so we should be good.
7818 int num_new_cliques = 0;
7819 int64_t num_literals_after = 0;
7820 for (const std::vector<Literal>& clique : cliques) {
7821 if (clique.empty()) continue;
7822 num_new_cliques++;
7823 num_literals_after += clique.size();
7824 ConstraintProto* ct = context_->working_model->add_constraints();
7825 for (const Literal literal : clique) {
7826 const int var =
7827 mapping->GetProtoVariableFromBooleanVariable(literal.Variable());
7828 if (var < 0) continue;
7829 if (literal.IsPositive()) {
7830 ct->mutable_at_most_one()->add_literals(var);
7831 } else {
7832 ct->mutable_at_most_one()->add_literals(NegatedRef(var));
7833 }
7834 }
7835
7836 // Make sure we do not have duplicate variable reference.
7837 PresolveAtMostOne(ct);
7838 }
7839 context_->UpdateNewConstraintsVariableUsage();
7840 if (num_new_cliques != num_old_cliques) {
7841 context_->UpdateRuleStats("at_most_one: transformed into max clique.");
7842 }
7843
7844 if (num_old_cliques != num_new_cliques ||
7845 num_literals_before != num_literals_after) {
7846 timer.AddMessage(absl::StrCat(
7847 "Merged ", FormatCounter(num_old_cliques), "(",
7848 FormatCounter(num_literals_before), " literals) into ",
7849 FormatCounter(num_new_cliques), "(",
7850 FormatCounter(num_literals_after), " literals) at_most_ones. "));
7851 }
7852 }
7853}
7854
7855namespace {
7856
7857bool FixFromAssignment(const VariablesAssignment& assignment,
7858 absl::Span<const int> var_mapping,
7859 PresolveContext* context) {
7860 const int num_vars = assignment.NumberOfVariables();
7861 for (int i = 0; i < num_vars; ++i) {
7862 const Literal lit(BooleanVariable(i), true);
7863 const int ref = var_mapping[i];
7864 if (assignment.LiteralIsTrue(lit)) {
7865 if (!context->SetLiteralToTrue(ref)) return false;
7866 } else if (assignment.LiteralIsFalse(lit)) {
7867 if (!context->SetLiteralToFalse(ref)) return false;
7868 }
7869 }
7870 return true;
7871}
7872
7873} // namespace
7874
7875// TODO(user): What to do with the at_most_one/exactly_one constraints?
7876// currently we do not take them into account here.
7877bool CpModelPresolver::PresolvePureSatPart() {
7878 // TODO(user): Reenable some SAT presolve with
7879 // keep_all_feasible_solutions set to true.
7880 if (context_->ModelIsUnsat()) return true;
7881 if (context_->params().keep_all_feasible_solutions_in_presolve()) return true;
7882
7883 // Compute a dense re-indexing for the Booleans of the problem.
7884 int num_variables = 0;
7885 int num_ignored_variables = 0;
7886 const int total_num_vars = context_->working_model->variables().size();
7887 std::vector<int> new_index(total_num_vars, -1);
7888 std::vector<int> new_to_old_index;
7889 for (int i = 0; i < total_num_vars; ++i) {
7890 if (!context_->CanBeUsedAsLiteral(i)) {
7891 ++num_ignored_variables;
7892 continue;
7893 }
7894
7895 // This is important to not assign variable in equivalence to random values.
7896 if (context_->VarToConstraints(i).empty()) continue;
7897
7898 new_to_old_index.push_back(i);
7899 new_index[i] = num_variables++;
7900 DCHECK_EQ(num_variables, new_to_old_index.size());
7901 }
7902
7903 // The conversion from proto index to remapped Literal.
7904 auto convert = [&new_index](int ref) {
7905 const int index = new_index[PositiveRef(ref)];
7906 DCHECK_NE(index, -1);
7907 return Literal(BooleanVariable(index), RefIsPositive(ref));
7908 };
7909
7910 // Load the pure-SAT part in a fresh Model.
7911 //
7912 // TODO(user): The removing and adding back of the same clause when nothing
7913 // happens in the presolve "seems" bad. That said, complexity wise, it is
7914 // a lot faster that what happens in the presolve though.
7915 //
7916 // TODO(user): Add the "small" at most one constraints to the SAT presolver by
7917 // expanding them to implications? that could remove a lot of clauses. Do that
7918 // when we are sure we don't load duplicates at_most_one/implications in the
7919 // solver. Ideally, the pure sat presolve could be improved to handle at most
7920 // one, and we could merge this with what the ProcessSetPPC() is doing.
7921 Model local_model;
7922 local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
7923 auto* sat_solver = local_model.GetOrCreate<SatSolver>();
7924 sat_solver->SetNumVariables(num_variables);
7925
7926 // Fix variables if any. Because we might not have reached the presove "fixed
7927 // point" above, some variable in the added clauses might be fixed. We need to
7928 // indicate this to the SAT presolver.
7929 for (const int var : new_to_old_index) {
7930 if (context_->IsFixed(var)) {
7931 if (context_->LiteralIsTrue(var)) {
7932 if (!sat_solver->AddUnitClause({convert(var)})) return false;
7933 } else {
7934 if (!sat_solver->AddUnitClause({convert(NegatedRef(var))})) {
7935 return false;
7936 }
7937 }
7938 }
7939 }
7940
7941 std::vector<Literal> clause;
7942 int num_removed_constraints = 0;
7943 int num_ignored_constraints = 0;
7944 for (int i = 0; i < context_->working_model->constraints_size(); ++i) {
7945 const ConstraintProto& ct = context_->working_model->constraints(i);
7946
7947 if (ct.constraint_case() == ConstraintProto::kBoolOr) {
7948 ++num_removed_constraints;
7949 clause.clear();
7950 for (const int ref : ct.bool_or().literals()) {
7951 clause.push_back(convert(ref));
7952 }
7953 for (const int ref : ct.enforcement_literal()) {
7954 clause.push_back(convert(ref).Negated());
7955 }
7956 sat_solver->AddProblemClause(clause);
7957
7958 context_->working_model->mutable_constraints(i)->Clear();
7959 context_->UpdateConstraintVariableUsage(i);
7960 continue;
7961 }
7962
7963 if (ct.constraint_case() == ConstraintProto::kBoolAnd) {
7964 // We currently do not expand "complex" bool_and that would result
7965 // in too many literals.
7966 const int left_size = ct.enforcement_literal().size();
7967 const int right_size = ct.bool_and().literals().size();
7968 if (left_size > 1 && right_size > 1 &&
7969 (left_size + 1) * right_size > 10'000) {
7970 ++num_ignored_constraints;
7971 continue;
7972 }
7973
7974 ++num_removed_constraints;
7975 std::vector<Literal> clause;
7976 for (const int ref : ct.enforcement_literal()) {
7977 clause.push_back(convert(ref).Negated());
7978 }
7979 clause.push_back(Literal(kNoLiteralIndex)); // will be replaced below.
7980 for (const int ref : ct.bool_and().literals()) {
7981 clause.back() = convert(ref);
7982 sat_solver->AddProblemClause(clause);
7983 }
7984
7985 context_->working_model->mutable_constraints(i)->Clear();
7986 context_->UpdateConstraintVariableUsage(i);
7987 continue;
7988 }
7989
7990 if (ct.constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
7991 continue;
7992 }
7993
7994 ++num_ignored_constraints;
7995 }
7996 if (sat_solver->ModelIsUnsat()) return false;
7997
7998 // Abort early if there was no Boolean constraints.
7999 if (num_removed_constraints == 0) return true;
8000
8001 // Mark the variables appearing elsewhere or in the objective as non-removable
8002 // by the sat presolver.
8003 //
8004 // TODO(user): do not remove variable that appear in the decision heuristic?
8005 // TODO(user): We could go further for variable with only one polarity by
8006 // removing variable from the objective if they can be set to their "low"
8007 // objective value, and also removing enforcement literal that can be set to
8008 // false and don't appear elsewhere.
8009 int num_in_extra_constraints = 0;
8010 std::vector<bool> can_be_removed(num_variables, false);
8011 for (int i = 0; i < num_variables; ++i) {
8012 const int var = new_to_old_index[i];
8013 if (context_->VarToConstraints(var).empty()) {
8014 can_be_removed[i] = true;
8015 } else {
8016 // That might correspond to the objective or a variable with an affine
8017 // relation that is still in the model.
8018 ++num_in_extra_constraints;
8019 }
8020 }
8021
8022 // The "full solver" postsolve does not support changing the value of a
8023 // variable from the solution of the presolved problem, and we do need this
8024 // for blocked clause. It should be possible to allow for this by adding extra
8025 // variable to the mapping model at presolve and some linking constraints, but
8026 // this is messy.
8027 //
8028 // We also disable this if the user asked for tightened domain as this might
8029 // fix variable to a potentially infeasible value, and just correct them later
8030 // during postsolve of a particular solution.
8031 SatParameters params = context_->params();
8032 if (params.debug_postsolve_with_full_solver() ||
8033 params.fill_tightened_domains_in_response()) {
8034 params.set_presolve_blocked_clause(false);
8035 }
8036
8037 SatPostsolver sat_postsolver(num_variables);
8038
8039 // If the problem is a pure-SAT problem, we run the new SAT presolver.
8040 // This takes more time but it is usually worthwile
8041 //
8042 // Note that the probing that it does is faster than the
8043 // ProbeAndFindEquivalentLiteral() call below, but does not do equivalence
8044 // detection as completely, so we still apply the other "probing" code
8045 // afterwards even if it will not fix more literals, but it will do one pass
8046 // of proper equivalence detection.
8047 util_intops::StrongVector<LiteralIndex, LiteralIndex> equiv_map;
8048 if (!context_->params().debug_postsolve_with_full_solver() &&
8049 num_ignored_variables == 0 && num_ignored_constraints == 0 &&
8050 num_in_extra_constraints == 0) {
8051 // Some problems are formulated in such a way that our SAT heuristics
8052 // simply works without conflict. Get them out of the way first because it
8053 // is possible that the presolve lose this "lucky" ordering. This is in
8054 // particular the case on the SAT14.crafted.complete-xxx-... problems.
8055 if (!LookForTrivialSatSolution(/*deterministic_time_limit=*/1.0,
8056 &local_model, logger_)) {
8057 return false;
8058 }
8059 if (sat_solver->LiteralTrail().Index() == num_variables) {
8060 // Problem solved! We should be able to assign the solution.
8061 CHECK(FixFromAssignment(sat_solver->Assignment(), new_to_old_index,
8062 context_));
8063 return true;
8064 }
8065
8066 SatPresolveOptions options;
8067 options.log_info = true; // log_info;
8068 options.extract_binary_clauses_in_probing = false;
8069 options.use_transitive_reduction = false;
8070 options.deterministic_time_limit =
8071 context_->params().presolve_probing_deterministic_time_limit();
8072
8073 auto* inprocessing = local_model.GetOrCreate<Inprocessing>();
8074 inprocessing->ProvideLogger(logger_);
8075 if (!inprocessing->PresolveLoop(options)) return false;
8076 for (const auto& c : local_model.GetOrCreate<PostsolveClauses>()->clauses) {
8077 sat_postsolver.Add(c[0], c);
8078 }
8079
8080 // Probe + find equivalent literals.
8081 // TODO(user): Use a derived time limit in the probing phase.
8082 ProbeAndFindEquivalentLiteral(sat_solver, &sat_postsolver,
8083 /*drat_proof_handler=*/nullptr, &equiv_map,
8084 logger_);
8085 if (sat_solver->ModelIsUnsat()) return false;
8086 } else {
8087 // TODO(user): BVA takes time and does not seems to help on the minizinc
8088 // benchmarks. So we currently disable it, except if we are on a pure-SAT
8089 // problem, where we follow the default (true) or the user specified value.
8090 params.set_presolve_use_bva(false);
8091 }
8092
8093 // Disable BVA if we want to keep the symmetries.
8094 //
8095 // TODO(user): We could still do it, we just need to do in a symmetric way
8096 // and also update the generators to take into account the new variables. This
8097 // do not seems that easy.
8098 if (context_->params().keep_symmetry_in_presolve()) {
8099 params.set_presolve_use_bva(false);
8100 }
8101
8102 // Update the time limit of the initial propagation.
8103 if (!sat_solver->ResetToLevelZero()) return false;
8104 time_limit_->AdvanceDeterministicTime(
8105 local_model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
8106
8107 // Apply the "old" SAT presolve.
8108 SatPresolver sat_presolver(&sat_postsolver, logger_);
8109 sat_presolver.SetNumVariables(num_variables);
8110 if (!equiv_map.empty()) {
8111 sat_presolver.SetEquivalentLiteralMapping(equiv_map);
8112 }
8113 sat_presolver.SetTimeLimit(time_limit_);
8114 sat_presolver.SetParameters(params);
8115
8116 // Load in the presolver.
8117 // Register the fixed variables with the postsolver.
8118 for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
8119 sat_postsolver.FixVariable(sat_solver->LiteralTrail()[i]);
8120 }
8121 sat_solver->ExtractClauses(&sat_presolver);
8122
8123 // Run the presolve for a small number of passes.
8124 // TODO(user): Add a local time limit? this can be slow on big SAT problem.
8125 for (int i = 0; i < 1; ++i) {
8126 const int old_num_clause = sat_postsolver.NumClauses();
8127 if (!sat_presolver.Presolve(can_be_removed)) return false;
8128 if (old_num_clause == sat_postsolver.NumClauses()) break;
8129 }
8130
8131 // Add any new variables to our internal structure.
8132 const int new_num_variables = sat_presolver.NumVariables();
8133 if (new_num_variables > num_variables) {
8134 VLOG(1) << "New variables added by the SAT presolver.";
8135 for (int i = num_variables; i < new_num_variables; ++i) {
8136 new_to_old_index.push_back(context_->working_model->variables().size());
8137 IntegerVariableProto* var_proto =
8138 context_->working_model->add_variables();
8139 var_proto->add_domain(0);
8140 var_proto->add_domain(1);
8141 }
8142 context_->InitializeNewDomains();
8143 }
8144
8145 // Fix variables if any.
8146 if (!FixFromAssignment(sat_postsolver.assignment(), new_to_old_index,
8147 context_)) {
8148 return false;
8149 }
8150
8151 // Add the presolver clauses back into the model.
8152 ExtractClauses(/*merge_into_bool_and=*/true, new_to_old_index, sat_presolver,
8153 context_->working_model);
8154
8155 // Update the constraints <-> variables graph.
8156 context_->UpdateNewConstraintsVariableUsage();
8157
8158 // We mark as removed any variables removed by the pure SAT presolve.
8159 // This is mainly to discover or avoid bug as we might have stale entries
8160 // in our encoding hash-map for instance.
8161 for (int i = 0; i < num_variables; ++i) {
8162 const int var = new_to_old_index[i];
8163 if (context_->VarToConstraints(var).empty()) {
8164 // Such variable needs to be fixed to some value for the SAT postsolve to
8165 // work.
8166 if (!context_->IsFixed(var)) {
8167 CHECK(context_->IntersectDomainWith(
8168 var, Domain(context_->DomainOf(var).SmallestValue())));
8169 }
8170 context_->MarkVariableAsRemoved(var);
8171 }
8172 }
8173
8174 // Add the sat_postsolver clauses to mapping_model.
8175 const std::string name =
8176 absl::GetFlag(FLAGS_cp_model_debug_postsolve) ? "sat_postsolver" : "";
8177 ExtractClauses(/*merge_into_bool_and=*/false, new_to_old_index,
8178 sat_postsolver, context_->mapping_model, name);
8179 return true;
8180}
8181
8182void CpModelPresolver::ShiftObjectiveWithExactlyOnes() {
8183 if (context_->ModelIsUnsat()) return;
8184
8185 // The objective is already loaded in the context, but we re-canonicalize
8186 // it with the latest information.
8187 if (!context_->CanonicalizeObjective()) {
8188 return;
8189 }
8190
8191 std::vector<int> exos;
8192 const int num_constraints = context_->working_model->constraints_size();
8193 for (int c = 0; c < num_constraints; ++c) {
8194 const ConstraintProto& ct = context_->working_model->constraints(c);
8195 if (!ct.enforcement_literal().empty()) continue;
8196 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8197 exos.push_back(c);
8198 }
8199 }
8200
8201 // This is not the same from what we do in ExpandObjective() because we do not
8202 // make the minimum cost zero but the second minimum. Note that when we do
8203 // that, we still do not degrade the trivial objective bound as we would if we
8204 // went any further.
8205 //
8206 // One reason why this might be beneficial is that it lower the maximum cost
8207 // magnitude, making more Booleans with the same cost and thus simplifying
8208 // the core optimizer job. I am not 100% sure.
8209 //
8210 // TODO(user): We need to loop a few time to reach a fixed point. Understand
8211 // exactly if there is a fixed-point and how to reach it in a nicer way.
8212 int num_shifts = 0;
8213 for (int i = 0; i < 3; ++i) {
8214 for (const int c : exos) {
8215 const ConstraintProto& ct = context_->working_model->constraints(c);
8216 const int num_terms = ct.exactly_one().literals().size();
8217 if (num_terms <= 1) continue;
8218 int64_t min_obj = std::numeric_limits<int64_t>::max();
8219 int64_t second_min = std::numeric_limits<int64_t>::max();
8220 for (int i = 0; i < num_terms; ++i) {
8221 const int literal = ct.exactly_one().literals(i);
8222 const int64_t var_obj = context_->ObjectiveCoeff(PositiveRef(literal));
8223 const int64_t obj = RefIsPositive(literal) ? var_obj : -var_obj;
8224 if (obj < min_obj) {
8225 second_min = min_obj;
8226 min_obj = obj;
8227 } else if (obj < second_min) {
8228 second_min = obj;
8229 }
8230 }
8231 if (second_min == 0) continue;
8232 ++num_shifts;
8233 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
8234 second_min)) {
8235 if (context_->ModelIsUnsat()) return;
8236 continue;
8237 }
8238 }
8239 }
8240 if (num_shifts > 0) {
8241 context_->UpdateRuleStats("objective: shifted cost with exactly ones",
8242 num_shifts);
8243 }
8244}
8245
8246bool CpModelPresolver::PropagateObjective() {
8247 if (!context_->working_model->has_objective()) return true;
8248 if (context_->ModelIsUnsat()) return false;
8249 context_->WriteObjectiveToProto();
8250
8251 int64_t min_activity = 0;
8252 int64_t max_variation = 0;
8253 const CpObjectiveProto& objective = context_->working_model->objective();
8254 const int num_terms = objective.vars().size();
8255 for (int i = 0; i < num_terms; ++i) {
8256 const int var = objective.vars(i);
8257 const int64_t coeff = objective.coeffs(i);
8258 CHECK(RefIsPositive(var));
8259 CHECK_NE(coeff, 0);
8260
8261 const int64_t domain_min = context_->MinOf(var);
8262 const int64_t domain_max = context_->MaxOf(var);
8263 if (coeff > 0) {
8264 min_activity += coeff * domain_min;
8265 } else {
8266 min_activity += coeff * domain_max;
8267 }
8268 const int64_t variation = std::abs(coeff) * (domain_max - domain_min);
8269 max_variation = std::max(max_variation, variation);
8270 }
8271
8272 // Infeasible ?
8273 const int64_t slack =
8274 CapSub(ReadDomainFromProto(objective).Max(), min_activity);
8275 if (slack < 0) {
8276 return context_->NotifyThatModelIsUnsat(
8277 "infeasible while propagating objective");
8278 }
8279
8280 // No propagation ?
8281 if (max_variation <= slack) return true;
8282
8283 int num_propagations = 0;
8284 for (int i = 0; i < num_terms; ++i) {
8285 const int var = objective.vars(i);
8286 const int64_t coeff = objective.coeffs(i);
8287 const int64_t domain_min = context_->MinOf(var);
8288 const int64_t domain_max = context_->MaxOf(var);
8289
8290 const int64_t new_diff = slack / std::abs(coeff);
8291 if (new_diff >= domain_max - domain_min) continue;
8292
8293 ++num_propagations;
8294 if (coeff > 0) {
8295 if (!context_->IntersectDomainWith(
8296 var, Domain(domain_min, domain_min + new_diff))) {
8297 return false;
8298 }
8299 } else {
8300 if (!context_->IntersectDomainWith(
8301 var, Domain(domain_max - new_diff, domain_max))) {
8302 return false;
8303 }
8304 }
8305 }
8306 CHECK_GT(num_propagations, 0);
8307
8308 context_->UpdateRuleStats("objective: restricted var domains by propagation",
8309 num_propagations);
8310 return true;
8311}
8312
8313// Expand the objective expression in some easy cases.
8314//
8315// The ideas is to look at all the "tight" equality constraints. These should
8316// give a topological order on the variable in which we can perform
8317// substitution.
8318//
8319// Basically, we will only use constraints of the form X' = sum ci * Xi' with ci
8320// > 0 and the variable X' being shifted version >= 0. Note that if there is a
8321// cycle with these constraints, all variables involved must be equal to each
8322// other and likely zero. Otherwise, we can express everything in terms of the
8323// leaves.
8324//
8325// This assumes we are more or less at the propagation fix point, even if we
8326// try to address cases where we are not.
8327void CpModelPresolver::ExpandObjective() {
8328 if (time_limit_->LimitReached()) return;
8329 if (context_->ModelIsUnsat()) return;
8330 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
8331
8332 // The objective is already loaded in the context, but we re-canonicalize
8333 // it with the latest information.
8334 if (!context_->CanonicalizeObjective()) {
8335 return;
8336 }
8337
8338 const int num_variables = context_->working_model->variables_size();
8339 const int num_constraints = context_->working_model->constraints_size();
8340
8341 // We consider two types of shifted variables (X - LB(X)) and (UB(X) - X).
8342 const auto get_index = [](int var, bool to_lb) {
8343 return 2 * var + (to_lb ? 0 : 1);
8344 };
8345 const auto get_lit_index = [](int lit) {
8346 return RefIsPositive(lit) ? 2 * lit : 2 * PositiveRef(lit) + 1;
8347 };
8348 const int num_nodes = 2 * num_variables;
8349 std::vector<std::vector<int>> index_graph(num_nodes);
8350
8351 // TODO(user): instead compute how much each constraint can be further
8352 // expanded?
8353 std::vector<int> index_to_best_c(num_nodes, -1);
8354 std::vector<int> index_to_best_size(num_nodes, 0);
8355
8356 // Lets see first if there are "tight" constraint and for which variables.
8357 // We stop processing constraint if we have too many entries.
8358 int num_entries = 0;
8359 int num_propagations = 0;
8360 int num_tight_variables = 0;
8361 int num_tight_constraints = 0;
8362 const int kNumEntriesThreshold = 1e8;
8363 for (int c = 0; c < num_constraints; ++c) {
8364 if (num_entries > kNumEntriesThreshold) break;
8365
8366 const ConstraintProto& ct = context_->working_model->constraints(c);
8367 if (!ct.enforcement_literal().empty()) continue;
8368
8369 // Deal with exactly one.
8370 // An exactly one is always tight on the upper bound of one term.
8371 //
8372 // Note(user): This code assume there is no fixed variable in the exactly
8373 // one. We thus make sure the constraint is re-presolved if for some reason
8374 // we didn't reach the fixed point before calling this code.
8375 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8376 if (PresolveExactlyOne(context_->working_model->mutable_constraints(c))) {
8377 context_->UpdateConstraintVariableUsage(c);
8378 }
8379 }
8380 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8381 const int num_terms = ct.exactly_one().literals().size();
8382 ++num_tight_constraints;
8383 num_tight_variables += num_terms;
8384 for (int i = 0; i < num_terms; ++i) {
8385 if (num_entries > kNumEntriesThreshold) break;
8386 const int neg_index = get_lit_index(ct.exactly_one().literals(i)) ^ 1;
8387
8388 const int old_c = index_to_best_c[neg_index];
8389 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
8390 index_to_best_c[neg_index] = c;
8391 index_to_best_size[neg_index] = num_terms;
8392 }
8393
8394 for (int j = 0; j < num_terms; ++j) {
8395 if (j == i) continue;
8396 const int other_index = get_lit_index(ct.exactly_one().literals(j));
8397 ++num_entries;
8398 index_graph[neg_index].push_back(other_index);
8399 }
8400 }
8401 continue;
8402 }
8403
8404 // Skip everything that is not a linear equality constraint.
8405 if (!IsLinearEqualityConstraint(ct)) continue;
8406
8407 // Let see for which variable is it "tight". We need a coeff of 1, and that
8408 // the implied bounds match exactly.
8409 const auto [min_activity, max_activity] =
8410 context_->ComputeMinMaxActivity(ct.linear());
8411
8412 bool is_tight = false;
8413 const int64_t rhs = ct.linear().domain(0);
8414 const int num_terms = ct.linear().vars_size();
8415 for (int i = 0; i < num_terms; ++i) {
8416 const int var = ct.linear().vars(i);
8417 const int64_t coeff = ct.linear().coeffs(i);
8418 if (std::abs(coeff) != 1) continue;
8419 if (num_entries > kNumEntriesThreshold) break;
8420
8421 const int index = get_index(var, coeff > 0);
8422
8423 const int64_t var_range = context_->MaxOf(var) - context_->MinOf(var);
8424 const int64_t implied_shifted_ub = rhs - min_activity;
8425 if (implied_shifted_ub <= var_range) {
8426 if (implied_shifted_ub < var_range) ++num_propagations;
8427 is_tight = true;
8428 ++num_tight_variables;
8429
8430 const int neg_index = index ^ 1;
8431 const int old_c = index_to_best_c[neg_index];
8432 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
8433 index_to_best_c[neg_index] = c;
8434 index_to_best_size[neg_index] = num_terms;
8435 }
8436
8437 for (int j = 0; j < num_terms; ++j) {
8438 if (j == i) continue;
8439 const int other_index =
8440 get_index(ct.linear().vars(j), ct.linear().coeffs(j) > 0);
8441 ++num_entries;
8442 index_graph[neg_index].push_back(other_index);
8443 }
8444 }
8445 const int64_t implied_shifted_lb = max_activity - rhs;
8446 if (implied_shifted_lb <= var_range) {
8447 if (implied_shifted_lb < var_range) ++num_propagations;
8448 is_tight = true;
8449 ++num_tight_variables;
8450
8451 const int old_c = index_to_best_c[index];
8452 if (old_c == -1 || num_terms > index_to_best_size[index]) {
8453 index_to_best_c[index] = c;
8454 index_to_best_size[index] = num_terms;
8455 }
8456
8457 for (int j = 0; j < num_terms; ++j) {
8458 if (j == i) continue;
8459 const int other_index =
8460 get_index(ct.linear().vars(j), ct.linear().coeffs(j) < 0);
8461 ++num_entries;
8462 index_graph[index].push_back(other_index);
8463 }
8464 }
8465 }
8466 if (is_tight) ++num_tight_constraints;
8467 }
8468
8469 // Note(user): We assume the fixed point was already reached by the linear
8470 // presolve, so we don't add extra code here for that. But we still abort if
8471 // some are left to cover corner cases were linear a still not propagated.
8472 if (num_propagations > 0) {
8473 context_->UpdateRuleStats("TODO objective: propagation possible!");
8474 return;
8475 }
8476
8477 // In most cases, we should have no cycle and thus a topo order.
8478 //
8479 // In case there is a cycle, then all member of a strongly connected component
8480 // must be equivalent, this is because from X to Y, if we follow the chain we
8481 // will have X = non_negative_sum + Y and Y = non_negative_sum + X.
8482 //
8483 // Moreover, many shifted variables will need to be zero once we start to have
8484 // equivalence.
8485 //
8486 // TODO(user): Make the fixing to zero? or at least when this happen redo
8487 // a presolve pass?
8488 //
8489 // TODO(user): Densify index to only look at variable that can be substituted
8490 // further.
8491 const auto topo_order = util::graph::FastTopologicalSort(index_graph);
8492 if (!topo_order.ok()) {
8493 // Tricky: We need to cache all domains to derive the proper relations.
8494 // This is because StoreAffineRelation() might propagate them.
8495 std::vector<int64_t> var_min(num_variables);
8496 std::vector<int64_t> var_max(num_variables);
8497 for (int var = 0; var < num_variables; ++var) {
8498 var_min[var] = context_->MinOf(var);
8499 var_max[var] = context_->MaxOf(var);
8500 }
8501
8502 std::vector<std::vector<int>> components;
8503 FindStronglyConnectedComponents(static_cast<int>(index_graph.size()),
8504 index_graph, &components);
8505 for (const std::vector<int>& compo : components) {
8506 if (compo.size() == 1) continue;
8507
8508 const int rep_var = compo[0] / 2;
8509 const bool rep_to_lp = (compo[0] % 2) == 0;
8510 for (int i = 1; i < compo.size(); ++i) {
8511 const int var = compo[i] / 2;
8512 const bool to_lb = (compo[i] % 2) == 0;
8513
8514 // (rep - rep_lb) | (rep_ub - rep) == (var - var_lb) | (var_ub - var)
8515 // +/- rep = +/- var + offset.
8516 const int64_t rep_coeff = rep_to_lp ? 1 : -1;
8517 const int64_t var_coeff = to_lb ? 1 : -1;
8518 const int64_t offset =
8519 (to_lb ? -var_min[var] : var_max[var]) -
8520 (rep_to_lp ? -var_min[rep_var] : var_max[rep_var]);
8521 if (!context_->StoreAffineRelation(rep_var, var, rep_coeff * var_coeff,
8522 rep_coeff * offset)) {
8523 return;
8524 }
8525 }
8526 context_->UpdateRuleStats("objective: detected equivalence",
8527 compo.size() - 1);
8528 }
8529 return;
8530 }
8531
8532 // If the removed variable is now unique, we could remove it if it is implied
8533 // free. But this should already be done by RemoveSingletonInLinear(), so we
8534 // don't redo it here.
8535 int num_expands = 0;
8536 int num_issues = 0;
8537 for (const int index : *topo_order) {
8538 if (index_graph[index].empty()) continue;
8539
8540 const int var = index / 2;
8541 const int64_t obj_coeff = context_->ObjectiveCoeff(var);
8542 if (obj_coeff == 0) continue;
8543
8544 const bool to_lb = (index % 2) == 0;
8545 if (obj_coeff > 0 == to_lb) {
8546 const ConstraintProto& ct =
8547 context_->working_model->constraints(index_to_best_c[index]);
8548 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8549 int64_t shift = 0;
8550 for (const int lit : ct.exactly_one().literals()) {
8551 if (PositiveRef(lit) == var) {
8552 shift = RefIsPositive(lit) ? obj_coeff : -obj_coeff;
8553 break;
8554 }
8555 }
8556 if (shift == 0) {
8557 ++num_issues;
8558 continue;
8559 }
8560 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
8561 shift)) {
8562 if (context_->ModelIsUnsat()) return;
8563 ++num_issues;
8564 continue;
8565 }
8566 CHECK_EQ(context_->ObjectiveCoeff(var), 0);
8567 ++num_expands;
8568 continue;
8569 }
8570
8571 int64_t objective_coeff_in_expanded_constraint = 0;
8572 const int num_terms = ct.linear().vars().size();
8573 for (int i = 0; i < num_terms; ++i) {
8574 if (ct.linear().vars(i) == var) {
8575 objective_coeff_in_expanded_constraint = ct.linear().coeffs(i);
8576 break;
8577 }
8578 }
8579 if (objective_coeff_in_expanded_constraint == 0) {
8580 ++num_issues;
8581 continue;
8582 }
8583
8584 if (!context_->SubstituteVariableInObjective(
8585 var, objective_coeff_in_expanded_constraint, ct)) {
8586 if (context_->ModelIsUnsat()) return;
8587 ++num_issues;
8588 continue;
8589 }
8590
8591 ++num_expands;
8592 }
8593 }
8594
8595 if (num_expands > 0) {
8596 context_->UpdateRuleStats("objective: expanded via tight equality",
8597 num_expands);
8598 }
8599
8600 timer.AddCounter("propagations", num_propagations);
8601 timer.AddCounter("entries", num_entries);
8602 timer.AddCounter("tight_variables", num_tight_variables);
8603 timer.AddCounter("tight_constraints", num_tight_constraints);
8604 timer.AddCounter("expands", num_expands);
8605 timer.AddCounter("issues", num_issues);
8606}
8607
8608void CpModelPresolver::MergeNoOverlapConstraints() {
8609 if (context_->ModelIsUnsat()) return;
8610
8611 const int num_constraints = context_->working_model->constraints_size();
8612 int old_num_no_overlaps = 0;
8613 int old_num_intervals = 0;
8614
8615 // Extract the no-overlap constraints.
8616 std::vector<int> disjunctive_index;
8617 std::vector<std::vector<Literal>> cliques;
8618 for (int c = 0; c < num_constraints; ++c) {
8619 const ConstraintProto& ct = context_->working_model->constraints(c);
8620 if (ct.constraint_case() != ConstraintProto::kNoOverlap) continue;
8621 std::vector<Literal> clique;
8622 for (const int i : ct.no_overlap().intervals()) {
8623 clique.push_back(Literal(BooleanVariable(i), true));
8624 }
8625 cliques.push_back(clique);
8626 disjunctive_index.push_back(c);
8627
8628 old_num_no_overlaps++;
8629 old_num_intervals += clique.size();
8630 }
8631 if (old_num_no_overlaps == 0) return;
8632
8633 // We reuse the max-clique code from sat.
8634 Model local_model;
8635 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
8636 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
8637 graph->Resize(num_constraints);
8638 for (const std::vector<Literal>& clique : cliques) {
8639 // All variables at false is always a valid solution of the local model,
8640 // so this should never return UNSAT.
8641 CHECK(graph->AddAtMostOne(clique));
8642 }
8643 CHECK(graph->DetectEquivalences());
8644 graph->TransformIntoMaxCliques(
8645 &cliques,
8646 SafeDoubleToInt64(context_->params().merge_no_overlap_work_limit()));
8647
8648 // Replace each no-overlap with an extended version, or remove if empty.
8649 int new_num_no_overlaps = 0;
8650 int new_num_intervals = 0;
8651 for (int i = 0; i < cliques.size(); ++i) {
8652 const int ct_index = disjunctive_index[i];
8653 ConstraintProto* ct =
8654 context_->working_model->mutable_constraints(ct_index);
8655 ct->Clear();
8656 if (cliques[i].empty()) continue;
8657 for (const Literal l : cliques[i]) {
8658 CHECK(l.IsPositive());
8659 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
8660 }
8661 new_num_no_overlaps++;
8662 new_num_intervals += cliques[i].size();
8663 }
8664 if (old_num_intervals != new_num_intervals ||
8665 old_num_no_overlaps != new_num_no_overlaps) {
8666 VLOG(1) << absl::StrCat("Merged ", old_num_no_overlaps, " no-overlaps (",
8667 old_num_intervals, " intervals) into ",
8668 new_num_no_overlaps, " no-overlaps (",
8669 new_num_intervals, " intervals).");
8670 context_->UpdateRuleStats("no_overlap: merged constraints");
8671 }
8672}
8673
8674// TODO(user): Should we take into account the exactly_one constraints? note
8675// that such constraint cannot be extended. If if a literal implies two literals
8676// at one inside an exactly one constraint then it must be false. Similarly if
8677// it implies all literals at zero inside the exactly one.
8678void CpModelPresolver::TransformIntoMaxCliques() {
8679 if (context_->ModelIsUnsat()) return;
8680 if (context_->params().merge_at_most_one_work_limit() <= 0.0) return;
8681
8682 auto convert = [](int ref) {
8683 if (RefIsPositive(ref)) return Literal(BooleanVariable(ref), true);
8684 return Literal(BooleanVariable(NegatedRef(ref)), false);
8685 };
8686 const int num_constraints = context_->working_model->constraints_size();
8687
8688 // Extract the bool_and and at_most_one constraints.
8689 // TODO(user): use probing info?
8690 std::vector<std::vector<Literal>> cliques;
8691
8692 for (int c = 0; c < num_constraints; ++c) {
8693 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8694 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
8695 std::vector<Literal> clique;
8696 for (const int ref : ct->at_most_one().literals()) {
8697 clique.push_back(convert(ref));
8698 }
8699 cliques.push_back(clique);
8700 if (RemoveConstraint(ct)) {
8701 context_->UpdateConstraintVariableUsage(c);
8702 }
8703 } else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
8704 if (ct->enforcement_literal().size() != 1) continue;
8705 const Literal enforcement = convert(ct->enforcement_literal(0));
8706 for (const int ref : ct->bool_and().literals()) {
8707 if (ref == ct->enforcement_literal(0)) continue;
8708 cliques.push_back({enforcement, convert(ref).Negated()});
8709 }
8710 if (RemoveConstraint(ct)) {
8711 context_->UpdateConstraintVariableUsage(c);
8712 }
8713 }
8714 }
8715
8716 int64_t num_literals_before = 0;
8717 const int num_old_cliques = cliques.size();
8718
8719 // We reuse the max-clique code from sat.
8720 Model local_model;
8721 const int num_variables = context_->working_model->variables().size();
8722 local_model.GetOrCreate<Trail>()->Resize(num_variables);
8723 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
8724 graph->Resize(num_variables);
8725 for (const std::vector<Literal>& clique : cliques) {
8726 num_literals_before += clique.size();
8727 if (!graph->AddAtMostOne(clique)) {
8728 return (void)context_->NotifyThatModelIsUnsat();
8729 }
8730 }
8731 if (!graph->DetectEquivalences()) {
8732 return (void)context_->NotifyThatModelIsUnsat();
8733 }
8734 graph->MergeAtMostOnes(
8735 absl::MakeSpan(cliques),
8736 SafeDoubleToInt64(context_->params().merge_at_most_one_work_limit()));
8737
8738 // Add the Boolean variable equivalence detected by DetectEquivalences().
8739 // Those are needed because TransformIntoMaxCliques() will replace all
8740 // variable by its representative.
8741 for (int var = 0; var < num_variables; ++var) {
8742 const Literal l = Literal(BooleanVariable(var), true);
8743 if (graph->RepresentativeOf(l) != l) {
8744 const Literal r = graph->RepresentativeOf(l);
8745 if (!context_->StoreBooleanEqualityRelation(
8746 var, r.IsPositive() ? r.Variable().value()
8747 : NegatedRef(r.Variable().value()))) {
8748 return;
8749 }
8750 }
8751 }
8752
8753 int num_new_cliques = 0;
8754 int64_t num_literals_after = 0;
8755 for (const std::vector<Literal>& clique : cliques) {
8756 if (clique.empty()) continue;
8757 num_new_cliques++;
8758 num_literals_after += clique.size();
8759 ConstraintProto* ct = context_->working_model->add_constraints();
8760 for (const Literal literal : clique) {
8761 if (literal.IsPositive()) {
8762 ct->mutable_at_most_one()->add_literals(literal.Variable().value());
8763 } else {
8764 ct->mutable_at_most_one()->add_literals(
8765 NegatedRef(literal.Variable().value()));
8766 }
8767 }
8768
8769 // Make sure we do not have duplicate variable reference.
8770 PresolveAtMostOne(ct);
8771 }
8772 context_->UpdateNewConstraintsVariableUsage();
8773 if (num_new_cliques != num_old_cliques) {
8774 context_->UpdateRuleStats("at_most_one: transformed into max clique.");
8775 }
8776
8777 if (num_old_cliques != num_new_cliques ||
8778 num_literals_before != num_literals_after) {
8779 SOLVER_LOG(logger_, "[MaxClique] Merged ", num_old_cliques, "(",
8780 num_literals_before, " literals) into ", num_new_cliques, "(",
8781 num_literals_after, " literals) at_most_ones.");
8782 }
8783}
8784
8786 if (context_->ModelIsUnsat()) return false;
8787 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8788
8789 // Generic presolve to exploit variable/literal equivalence.
8790 if (ExploitEquivalenceRelations(c, ct)) {
8791 context_->UpdateConstraintVariableUsage(c);
8792 }
8793
8794 // Generic presolve for reified constraint.
8795 if (PresolveEnforcementLiteral(ct)) {
8796 context_->UpdateConstraintVariableUsage(c);
8797 }
8798
8799 // Call the presolve function for this constraint if any.
8800 switch (ct->constraint_case()) {
8801 case ConstraintProto::kBoolOr:
8802 return PresolveBoolOr(ct);
8803 case ConstraintProto::kBoolAnd:
8804 return PresolveBoolAnd(ct);
8805 case ConstraintProto::kAtMostOne:
8806 return PresolveAtMostOne(ct);
8807 case ConstraintProto::kExactlyOne:
8808 return PresolveExactlyOne(ct);
8809 case ConstraintProto::kBoolXor:
8810 return PresolveBoolXor(ct);
8811 case ConstraintProto::kLinMax:
8812 if (CanonicalizeLinearArgument(*ct, ct->mutable_lin_max())) {
8813 context_->UpdateConstraintVariableUsage(c);
8814 }
8815 return PresolveLinMax(c, ct);
8816 case ConstraintProto::kIntProd:
8817 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_prod())) {
8818 context_->UpdateConstraintVariableUsage(c);
8819 }
8820 return PresolveIntProd(ct);
8821 case ConstraintProto::kIntDiv:
8822 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_div())) {
8823 context_->UpdateConstraintVariableUsage(c);
8824 }
8825 return PresolveIntDiv(c, ct);
8826 case ConstraintProto::kIntMod:
8827 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_mod())) {
8828 context_->UpdateConstraintVariableUsage(c);
8829 }
8830 return PresolveIntMod(c, ct);
8831 case ConstraintProto::kLinear: {
8832 if (CanonicalizeLinear(ct)) {
8833 context_->UpdateConstraintVariableUsage(c);
8834 }
8835 if (PropagateDomainsInLinear(c, ct)) {
8836 context_->UpdateConstraintVariableUsage(c);
8837 }
8838 if (PresolveSmallLinear(ct)) {
8839 context_->UpdateConstraintVariableUsage(c);
8840 }
8841 if (PresolveLinearEqualityWithModulo(ct)) {
8842 context_->UpdateConstraintVariableUsage(c);
8843 }
8844 // We first propagate the domains before calling this presolve rule.
8845 if (RemoveSingletonInLinear(ct)) {
8846 context_->UpdateConstraintVariableUsage(c);
8847
8848 // There is no need to re-do a propagation here, but the constraint
8849 // size might have been reduced.
8850 if (PresolveSmallLinear(ct)) {
8851 context_->UpdateConstraintVariableUsage(c);
8852 }
8853 }
8854 if (PresolveSmallLinear(ct)) {
8855 context_->UpdateConstraintVariableUsage(c);
8856 }
8857 if (PresolveLinearOnBooleans(ct)) {
8858 context_->UpdateConstraintVariableUsage(c);
8859 }
8860
8861 // If we extracted some enforcement, we redo some presolve.
8862 const int old_num_enforcement_literals = ct->enforcement_literal_size();
8863 ExtractEnforcementLiteralFromLinearConstraint(c, ct);
8864 if (context_->ModelIsUnsat()) return false;
8865 if (ct->enforcement_literal_size() > old_num_enforcement_literals) {
8866 if (DivideLinearByGcd(ct)) {
8867 context_->UpdateConstraintVariableUsage(c);
8868 }
8869 if (PresolveSmallLinear(ct)) {
8870 context_->UpdateConstraintVariableUsage(c);
8871 }
8872 }
8873
8874 if (PresolveDiophantine(ct)) {
8875 context_->UpdateConstraintVariableUsage(c);
8876 }
8877
8878 TryToReduceCoefficientsOfLinearConstraint(c, ct);
8879 return false;
8880 }
8881 case ConstraintProto::kInterval:
8882 return PresolveInterval(c, ct);
8883 case ConstraintProto::kInverse:
8884 return PresolveInverse(ct);
8885 case ConstraintProto::kElement:
8886 return PresolveElement(c, ct);
8887 case ConstraintProto::kTable:
8888 return PresolveTable(ct);
8889 case ConstraintProto::kAllDiff:
8890 return PresolveAllDiff(ct);
8891 case ConstraintProto::kNoOverlap:
8892 DetectDuplicateIntervals(c,
8893 ct->mutable_no_overlap()->mutable_intervals());
8894 return PresolveNoOverlap(ct);
8895 case ConstraintProto::kNoOverlap2D: {
8896 const bool changed = PresolveNoOverlap2D(c, ct);
8897 if (ct->constraint_case() == ConstraintProto::kNoOverlap2D) {
8898 // For 2D, we don't exploit index duplication between x/y so it is not
8899 // important to do it beforehand. Moreover in some situation
8900 // PresolveNoOverlap2D() remove a lot of interval, so better to do it
8901 // afterwards.
8902 DetectDuplicateIntervals(
8903 c, ct->mutable_no_overlap_2d()->mutable_x_intervals());
8904 DetectDuplicateIntervals(
8905 c, ct->mutable_no_overlap_2d()->mutable_y_intervals());
8906 }
8907 return changed;
8908 }
8909 case ConstraintProto::kCumulative:
8910 DetectDuplicateIntervals(c,
8911 ct->mutable_cumulative()->mutable_intervals());
8912 return PresolveCumulative(ct);
8913 case ConstraintProto::kCircuit:
8914 return PresolveCircuit(ct);
8915 case ConstraintProto::kRoutes:
8916 return PresolveRoutes(ct);
8917 case ConstraintProto::kAutomaton:
8918 return PresolveAutomaton(ct);
8919 case ConstraintProto::kReservoir:
8920 return PresolveReservoir(ct);
8921 default:
8922 return false;
8923 }
8924}
8925
8926// Returns false iff the model is UNSAT.
8927bool CpModelPresolver::ProcessSetPPCSubset(int subset_c, int superset_c,
8928 absl::flat_hash_set<int>* tmp_set,
8929 bool* remove_subset,
8930 bool* remove_superset,
8931 bool* stop_processing_superset) {
8932 ConstraintProto* subset_ct =
8933 context_->working_model->mutable_constraints(subset_c);
8934 ConstraintProto* superset_ct =
8935 context_->working_model->mutable_constraints(superset_c);
8936
8937 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
8938 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
8939 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
8940 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
8941 context_->UpdateRuleStats("setppc: bool_or in at_most_one.");
8942
8943 tmp_set->clear();
8944 if (subset_ct->constraint_case() == ConstraintProto::kBoolOr) {
8945 tmp_set->insert(subset_ct->bool_or().literals().begin(),
8946 subset_ct->bool_or().literals().end());
8947 } else {
8948 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
8949 subset_ct->exactly_one().literals().end());
8950 }
8951
8952 // Fix extras in superset_c to 0, note that these will be removed from the
8953 // constraint later.
8954 for (const int literal :
8955 superset_ct->constraint_case() == ConstraintProto::kAtMostOne
8956 ? superset_ct->at_most_one().literals()
8957 : superset_ct->exactly_one().literals()) {
8958 if (tmp_set->contains(literal)) continue;
8959 if (!context_->SetLiteralToFalse(literal)) return false;
8960 context_->UpdateRuleStats("setppc: fixed variables");
8961 }
8962
8963 // Change superset_c to exactly_one if not already.
8964 if (superset_ct->constraint_case() != ConstraintProto::kExactlyOne) {
8965 ConstraintProto copy = *superset_ct;
8966 (*superset_ct->mutable_exactly_one()->mutable_literals()) =
8967 copy.at_most_one().literals();
8968 }
8969
8970 *remove_subset = true;
8971 return true;
8972 }
8973
8974 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
8975 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
8976 superset_ct->constraint_case() == ConstraintProto::kBoolOr) {
8977 context_->UpdateRuleStats("setppc: removed dominated constraints");
8978 *remove_superset = true;
8979 return true;
8980 }
8981
8982 if (subset_ct->constraint_case() == ConstraintProto::kAtMostOne &&
8983 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
8984 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
8985 context_->UpdateRuleStats("setppc: removed dominated constraints");
8986 *remove_subset = true;
8987 return true;
8988 }
8989
8990 // Note(user): Only the exactly one should really be needed, the intersection
8991 // is taken care of by ProcessAtMostOneAndLinear() in a better way.
8992 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne &&
8993 superset_ct->constraint_case() == ConstraintProto::kLinear) {
8994 tmp_set->clear();
8995 int64_t min_sum = std::numeric_limits<int64_t>::max();
8996 int64_t max_sum = std::numeric_limits<int64_t>::min();
8997 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
8998 subset_ct->exactly_one().literals().end());
8999
9000 // Compute the min/max on the subset of the sum that correspond the exo.
9001 int num_matches = 0;
9002 temp_ct_.Clear();
9003 Domain reachable(0);
9004 std::vector<std::pair<int64_t, int>> coeff_counts;
9005 for (int i = 0; i < superset_ct->linear().vars().size(); ++i) {
9006 const int var = superset_ct->linear().vars(i);
9007 const int64_t coeff = superset_ct->linear().coeffs(i);
9008 if (tmp_set->contains(var)) {
9009 ++num_matches;
9010 min_sum = std::min(min_sum, coeff);
9011 max_sum = std::max(max_sum, coeff);
9012 coeff_counts.push_back({superset_ct->linear().coeffs(i), 1});
9013 } else {
9014 reachable =
9015 reachable
9016 .AdditionWith(
9017 context_->DomainOf(var).ContinuousMultiplicationBy(coeff))
9018 .RelaxIfTooComplex();
9019 temp_ct_.mutable_linear()->add_vars(var);
9020 temp_ct_.mutable_linear()->add_coeffs(coeff);
9021 }
9022 }
9023
9024 // If a linear constraint contains more than one at_most_one or exactly_one,
9025 // after processing one, we might no longer have an inclusion.
9026 //
9027 // TODO(user): If we have multiple disjoint inclusion, we can propagate
9028 // more. For instance on neos-1593097.mps we basically have a
9029 // weighted_sum_over_at_most_one1 >= weighted_sum_over_at_most_one2.
9030 if (num_matches != tmp_set->size()) return true;
9031 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne) {
9032 context_->UpdateRuleStats("setppc: exactly_one included in linear");
9033 } else {
9034 context_->UpdateRuleStats("setppc: at_most_one included in linear");
9035 }
9036
9037 reachable = reachable.AdditionWith(Domain(min_sum, max_sum));
9038 const Domain superset_rhs = ReadDomainFromProto(superset_ct->linear());
9039 if (reachable.IsIncludedIn(superset_rhs)) {
9040 // The constraint is trivial !
9041 context_->UpdateRuleStats("setppc: removed trivial linear constraint");
9042 *remove_superset = true;
9043 return true;
9044 }
9045 if (reachable.IntersectionWith(superset_rhs).IsEmpty()) {
9046 // TODO(user): constraint might become bool_or.
9047 context_->UpdateRuleStats("setppc: removed infeasible linear constraint");
9048 *stop_processing_superset = true;
9049 return MarkConstraintAsFalse(superset_ct);
9050 }
9051
9052 // We reuse the normal linear constraint code to propagate domains of
9053 // the other variable using the inclusion information.
9054 if (superset_ct->enforcement_literal().empty()) {
9055 CHECK_GT(num_matches, 0);
9056 FillDomainInProto(ReadDomainFromProto(superset_ct->linear())
9057 .AdditionWith(Domain(-max_sum, -min_sum)),
9058 temp_ct_.mutable_linear());
9059 PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_);
9060 }
9061
9062 // If we have an exactly one in a linear, we can shift the coefficients of
9063 // all these variables by any constant value. We select a value that reduces
9064 // the number of terms the most.
9065 std::sort(coeff_counts.begin(), coeff_counts.end());
9066 int new_size = 0;
9067 for (int i = 0; i < coeff_counts.size(); ++i) {
9068 if (new_size > 0 &&
9069 coeff_counts[i].first == coeff_counts[new_size - 1].first) {
9070 coeff_counts[new_size - 1].second++;
9071 continue;
9072 }
9073 coeff_counts[new_size++] = coeff_counts[i];
9074 }
9075 coeff_counts.resize(new_size);
9076 int64_t best = 0;
9077 int64_t best_count = 0;
9078 for (const auto [coeff, count] : coeff_counts) {
9079 if (count > best_count) {
9080 best = coeff;
9081 best_count = count;
9082 }
9083 }
9084 if (best != 0) {
9085 LinearConstraintProto new_ct = superset_ct->linear();
9086 int new_size = 0;
9087 for (int i = 0; i < new_ct.vars().size(); ++i) {
9088 const int var = new_ct.vars(i);
9089 int64_t coeff = new_ct.coeffs(i);
9090 if (tmp_set->contains(var)) {
9091 if (coeff == best) continue; // delete term.
9092 coeff -= best;
9093 }
9094 new_ct.set_vars(new_size, var);
9095 new_ct.set_coeffs(new_size, coeff);
9096 ++new_size;
9097 }
9098
9099 new_ct.mutable_vars()->Truncate(new_size);
9100 new_ct.mutable_coeffs()->Truncate(new_size);
9101 FillDomainInProto(ReadDomainFromProto(new_ct).AdditionWith(Domain(-best)),
9102 &new_ct);
9103 if (!PossibleIntegerOverflow(*context_->working_model, new_ct.vars(),
9104 new_ct.coeffs())) {
9105 *superset_ct->mutable_linear() = std::move(new_ct);
9106 context_->UpdateConstraintVariableUsage(superset_c);
9107 context_->UpdateRuleStats("setppc: reduced linear coefficients");
9108 }
9109 }
9110
9111 return true;
9112 }
9113
9114 // We can't deduce anything in the last remaining cases, like an at most one
9115 // in an at least one.
9116 return true;
9117}
9118
9119// TODO(user): TransformIntoMaxCliques() convert the bool_and to
9120// at_most_one, but maybe also duplicating them into bool_or would allow this
9121// function to do more presolving.
9122void CpModelPresolver::ProcessSetPPC() {
9123 if (time_limit_->LimitReached()) return;
9124 if (context_->ModelIsUnsat()) return;
9125 if (context_->params().presolve_inclusion_work_limit() == 0) return;
9126 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9127
9128 // TODO(user): compute on the fly instead of temporary storing variables?
9129 CompactVectorVector<int> storage;
9130 InclusionDetector detector(storage, time_limit_);
9131 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
9132
9133 // We use an encoding of literal that allows to index arrays.
9134 std::vector<int> temp_literals;
9135 const int num_constraints = context_->working_model->constraints_size();
9136 std::vector<int> relevant_constraints;
9137 for (int c = 0; c < num_constraints; ++c) {
9138 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9139 const auto type = ct->constraint_case();
9140 if (type == ConstraintProto::kBoolOr ||
9141 type == ConstraintProto::kAtMostOne ||
9142 type == ConstraintProto::kExactlyOne) {
9143 // Because TransformIntoMaxCliques() can detect literal equivalence
9144 // relation, we make sure the constraints are presolved before being
9145 // inspected.
9146 if (PresolveOneConstraint(c)) {
9147 context_->UpdateConstraintVariableUsage(c);
9148 }
9149 if (context_->ModelIsUnsat()) return;
9150
9151 temp_literals.clear();
9152 for (const int ref :
9153 type == ConstraintProto::kAtMostOne ? ct->at_most_one().literals()
9154 : type == ConstraintProto::kBoolOr ? ct->bool_or().literals()
9155 : ct->exactly_one().literals()) {
9156 temp_literals.push_back(
9157 Literal(BooleanVariable(PositiveRef(ref)), RefIsPositive(ref))
9158 .Index()
9159 .value());
9160 }
9161 relevant_constraints.push_back(c);
9162 detector.AddPotentialSet(storage.Add(temp_literals));
9163 } else if (type == ConstraintProto::kLinear) {
9164 // We also want to test inclusion with the pseudo-Boolean part of
9165 // linear constraints of size at least 3. Exactly one of size two are
9166 // equivalent literals, and we already deal with this case.
9167 //
9168 // TODO(user): This is not ideal as we currently only process exactly one
9169 // included into linear, and we add overhead by detecting all the other
9170 // cases that we ignore later. That said, we could just propagate a bit
9171 // more the domain if we know at_least_one or at_most_one between literals
9172 // in a linear constraint.
9173 const int size = ct->linear().vars().size();
9174 if (size <= 2) continue;
9175
9176 // TODO(user): We only deal with positive var here. Ideally we should
9177 // match the VARIABLES of the at_most_one/exactly_one with the VARIABLES
9178 // of the linear, and complement all variable to have a literal inclusion.
9179 temp_literals.clear();
9180 for (int i = 0; i < size; ++i) {
9181 const int var = ct->linear().vars(i);
9182 if (!context_->CanBeUsedAsLiteral(var)) continue;
9183 if (!RefIsPositive(var)) continue;
9184 temp_literals.push_back(
9185 Literal(BooleanVariable(var), true).Index().value());
9186 }
9187 if (temp_literals.size() > 2) {
9188 // Note that we only care about the linear being the superset.
9189 relevant_constraints.push_back(c);
9190 detector.AddPotentialSuperset(storage.Add(temp_literals));
9191 }
9192 }
9193 }
9194
9195 absl::flat_hash_set<int> tmp_set;
9196 int64_t num_inclusions = 0;
9197 detector.DetectInclusions([&](int subset, int superset) {
9198 ++num_inclusions;
9199 bool remove_subset = false;
9200 bool remove_superset = false;
9201 bool stop_processing_superset = false;
9202 const int subset_c = relevant_constraints[subset];
9203 const int superset_c = relevant_constraints[superset];
9204 detector.IncreaseWorkDone(storage[subset].size());
9205 detector.IncreaseWorkDone(storage[superset].size());
9206 if (!ProcessSetPPCSubset(subset_c, superset_c, &tmp_set, &remove_subset,
9207 &remove_superset, &stop_processing_superset)) {
9208 detector.Stop();
9209 return;
9210 }
9211 if (remove_subset) {
9212 context_->working_model->mutable_constraints(subset_c)->Clear();
9213 context_->UpdateConstraintVariableUsage(subset_c);
9214 detector.StopProcessingCurrentSubset();
9215 }
9216 if (remove_superset) {
9217 context_->working_model->mutable_constraints(superset_c)->Clear();
9218 context_->UpdateConstraintVariableUsage(superset_c);
9219 detector.StopProcessingCurrentSuperset();
9220 }
9221 if (stop_processing_superset) {
9222 context_->UpdateConstraintVariableUsage(superset_c);
9223 detector.StopProcessingCurrentSuperset();
9224 }
9225 });
9226
9227 timer.AddToWork(detector.work_done() * 1e-9);
9228 timer.AddCounter("relevant_constraints", relevant_constraints.size());
9229 timer.AddCounter("num_inclusions", num_inclusions);
9230}
9231
9232void CpModelPresolver::DetectIncludedEnforcement() {
9233 if (time_limit_->LimitReached()) return;
9234 if (context_->ModelIsUnsat()) return;
9235 if (context_->params().presolve_inclusion_work_limit() == 0) return;
9236 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9237
9238 // TODO(user): compute on the fly instead of temporary storing variables?
9239 std::vector<int> relevant_constraints;
9240 CompactVectorVector<int> storage;
9241 InclusionDetector detector(storage, time_limit_);
9242 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
9243
9244 std::vector<int> temp_literals;
9245 const int num_constraints = context_->working_model->constraints_size();
9246 for (int c = 0; c < num_constraints; ++c) {
9247 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9248 if (ct->enforcement_literal().size() <= 1) continue;
9249
9250 // Make sure there is no x => x.
9251 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
9252 if (PresolveOneConstraint(c)) {
9253 context_->UpdateConstraintVariableUsage(c);
9254 }
9255 if (context_->ModelIsUnsat()) return;
9256 }
9257
9258 // We use an encoding of literal that allows to index arrays.
9259 temp_literals.clear();
9260 for (const int ref : ct->enforcement_literal()) {
9261 temp_literals.push_back(
9262 Literal(BooleanVariable(PositiveRef(ref)), RefIsPositive(ref))
9263 .Index()
9264 .value());
9265 }
9266 relevant_constraints.push_back(c);
9267
9268 // We only deal with bool_and included in other. Not the other way around,
9269 // Altough linear enforcement included in bool_and does happen.
9270 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
9271 detector.AddPotentialSet(storage.Add(temp_literals));
9272 } else {
9273 detector.AddPotentialSuperset(storage.Add(temp_literals));
9274 }
9275 }
9276
9277 int64_t num_inclusions = 0;
9278 detector.DetectInclusions([&](int subset, int superset) {
9279 ++num_inclusions;
9280 const int subset_c = relevant_constraints[subset];
9281 const int superset_c = relevant_constraints[superset];
9282 ConstraintProto* subset_ct =
9283 context_->working_model->mutable_constraints(subset_c);
9284 ConstraintProto* superset_ct =
9285 context_->working_model->mutable_constraints(superset_c);
9286 if (subset_ct->constraint_case() != ConstraintProto::kBoolAnd) return;
9287
9288 context_->tmp_literal_set.clear();
9289 for (const int ref : subset_ct->bool_and().literals()) {
9290 context_->tmp_literal_set.insert(ref);
9291 }
9292
9293 // Filter superset enforcement.
9294 {
9295 int new_size = 0;
9296 for (const int ref : superset_ct->enforcement_literal()) {
9297 if (context_->tmp_literal_set.contains(ref)) {
9298 context_->UpdateRuleStats("bool_and: filtered enforcement");
9299 } else if (context_->tmp_literal_set.contains(NegatedRef(ref))) {
9300 context_->UpdateRuleStats("bool_and: never enforced");
9301 superset_ct->Clear();
9302 context_->UpdateConstraintVariableUsage(superset_c);
9303 detector.StopProcessingCurrentSuperset();
9304 return;
9305 } else {
9306 superset_ct->set_enforcement_literal(new_size++, ref);
9307 }
9308 }
9309 if (new_size < superset_ct->bool_and().literals().size()) {
9310 context_->UpdateConstraintVariableUsage(superset_c);
9311 superset_ct->mutable_enforcement_literal()->Truncate(new_size);
9312 }
9313 }
9314
9315 if (superset_ct->constraint_case() == ConstraintProto::kBoolAnd) {
9316 int new_size = 0;
9317 for (const int ref : superset_ct->bool_and().literals()) {
9318 if (context_->tmp_literal_set.contains(ref)) {
9319 context_->UpdateRuleStats("bool_and: filtered literal");
9320 } else if (context_->tmp_literal_set.contains(NegatedRef(ref))) {
9321 context_->UpdateRuleStats("bool_and: must be false");
9322 if (!MarkConstraintAsFalse(superset_ct)) return;
9323 context_->UpdateConstraintVariableUsage(superset_c);
9324 detector.StopProcessingCurrentSuperset();
9325 return;
9326 } else {
9327 superset_ct->mutable_bool_and()->set_literals(new_size++, ref);
9328 }
9329 }
9330 if (new_size < superset_ct->bool_and().literals().size()) {
9331 context_->UpdateConstraintVariableUsage(superset_c);
9332 superset_ct->mutable_bool_and()->mutable_literals()->Truncate(new_size);
9333 }
9334 }
9335
9336 if (superset_ct->constraint_case() == ConstraintProto::kLinear) {
9337 context_->UpdateRuleStats("TODO bool_and enforcement in linear enf");
9338 }
9339 });
9340
9341 timer.AddToWork(1e-9 * static_cast<double>(detector.work_done()));
9342 timer.AddCounter("relevant_constraints", relevant_constraints.size());
9343 timer.AddCounter("num_inclusions", num_inclusions);
9344}
9345
9346// Note that because we remove the linear constraint, this will not be called
9347// often, so it is okay to use "heavy" data structure here.
9348//
9349// TODO(user): in the at most one case, consider always creating an associated
9350// literal (l <=> var == rhs), and add the exactly_one = at_most_one U not(l)?
9351// This constraint is implicit from what we create, however internally we will
9352// not recover it easily, so we might not add the linear relaxation
9353// corresponding to the constraint we just removed.
9354bool CpModelPresolver::ProcessEncodingFromLinear(
9355 const int linear_encoding_ct_index,
9356 const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms,
9357 int64_t* num_multiple_terms) {
9358 // Preprocess exactly or at most one.
9359 bool in_exactly_one = false;
9360 absl::flat_hash_map<int, int> var_to_ref;
9361 if (at_most_or_exactly_one.constraint_case() == ConstraintProto::kAtMostOne) {
9362 for (const int ref : at_most_or_exactly_one.at_most_one().literals()) {
9363 CHECK(!var_to_ref.contains(PositiveRef(ref)));
9364 var_to_ref[PositiveRef(ref)] = ref;
9365 }
9366 } else {
9367 CHECK_EQ(at_most_or_exactly_one.constraint_case(),
9368 ConstraintProto::kExactlyOne);
9369 in_exactly_one = true;
9370 for (const int ref : at_most_or_exactly_one.exactly_one().literals()) {
9371 CHECK(!var_to_ref.contains(PositiveRef(ref)));
9372 var_to_ref[PositiveRef(ref)] = ref;
9373 }
9374 }
9375
9376 // Preprocess the linear constraints.
9377 const ConstraintProto& linear_encoding =
9378 context_->working_model->constraints(linear_encoding_ct_index);
9379 int64_t rhs = linear_encoding.linear().domain(0);
9380 int target_ref = std::numeric_limits<int>::min();
9381 std::vector<std::pair<int, int64_t>> ref_to_coeffs;
9382 const int num_terms = linear_encoding.linear().vars().size();
9383 for (int i = 0; i < num_terms; ++i) {
9384 const int ref = linear_encoding.linear().vars(i);
9385 const int64_t coeff = linear_encoding.linear().coeffs(i);
9386 const auto it = var_to_ref.find(PositiveRef(ref));
9387
9388 if (it == var_to_ref.end()) {
9389 CHECK_EQ(target_ref, std::numeric_limits<int>::min()) << "Uniqueness";
9390 CHECK_EQ(std::abs(coeff), 1);
9391 target_ref = coeff == 1 ? ref : NegatedRef(ref);
9392 continue;
9393 }
9394
9395 // We transform the constraint so that the Boolean reference match exactly
9396 // what is in the at most one.
9397 if (it->second == ref) {
9398 // The term in the constraint is the same as in the at_most_one.
9399 ref_to_coeffs.push_back({ref, coeff});
9400 } else {
9401 // We replace "coeff * ref" by "coeff - coeff * (1 - ref)"
9402 rhs -= coeff;
9403 ref_to_coeffs.push_back({NegatedRef(ref), -coeff});
9404 }
9405 }
9406 if (target_ref == std::numeric_limits<int>::min() ||
9407 context_->CanBeUsedAsLiteral(target_ref)) {
9408 // We didn't find the unique integer variable. This might have happenned
9409 // because by processing other encoding we might end up with a fully boolean
9410 // constraint. Just abort, it will be presolved later.
9411 context_->UpdateRuleStats("encoding: candidate linear is all Boolean now.");
9412 return true;
9413 }
9414
9415 // Extract the encoding.
9416 std::vector<int64_t> all_values;
9417 absl::btree_map<int64_t, std::vector<int>> value_to_refs;
9418 for (const auto& [ref, coeff] : ref_to_coeffs) {
9419 const int64_t value = rhs - coeff;
9420 all_values.push_back(value);
9421 value_to_refs[value].push_back(ref);
9422 var_to_ref.erase(PositiveRef(ref));
9423 }
9424 // The one not used "encodes" the rhs value.
9425 for (const auto& [var, ref] : var_to_ref) {
9426 all_values.push_back(rhs);
9427 value_to_refs[rhs].push_back(ref);
9428 }
9429 if (!in_exactly_one) {
9430 // To cover the corner case when the inclusion is an equality. For an at
9431 // most one, the rhs should be always reachable when all Boolean are false.
9432 all_values.push_back(rhs);
9433 }
9434
9435 // Make sure the target domain is up to date.
9436 const Domain new_domain = Domain::FromValues(all_values);
9437 bool domain_reduced = false;
9438 if (!context_->IntersectDomainWith(target_ref, new_domain, &domain_reduced)) {
9439 return false;
9440 }
9441 if (domain_reduced) {
9442 context_->UpdateRuleStats("encoding: reduced target domain");
9443 }
9444
9445 if (context_->CanBeUsedAsLiteral(target_ref)) {
9446 // If target is now a literal, lets not process it here.
9447 context_->UpdateRuleStats("encoding: candidate linear is all Boolean now.");
9448 return true;
9449 }
9450
9451 // Encode the encoding.
9452 absl::flat_hash_set<int64_t> value_set;
9453 for (const int64_t v : context_->DomainOf(target_ref).Values()) {
9454 value_set.insert(v);
9455 }
9456 for (auto& [value, literals] : value_to_refs) {
9457 // For determinism.
9458 absl::c_sort(literals);
9459
9460 // If the value is not in the domain, just set all literal to false.
9461 if (!value_set.contains(value)) {
9462 for (const int lit : literals) {
9463 if (!context_->SetLiteralToFalse(lit)) return false;
9464 }
9465 continue;
9466 }
9467
9468 if (literals.size() == 1 && (in_exactly_one || value != rhs)) {
9469 // Optimization if there is just one literal for this value.
9470 // Note that for the "at most one" case, we can't do that for the rhs.
9471 ++*num_unique_terms;
9472 if (!context_->InsertVarValueEncoding(literals[0], target_ref, value)) {
9473 return false;
9474 }
9475 } else {
9476 ++*num_multiple_terms;
9477 const int associated_lit =
9478 context_->GetOrCreateVarValueEncoding(target_ref, value);
9479 for (const int lit : literals) {
9480 context_->AddImplication(lit, associated_lit);
9481 }
9482
9483 // All false means associated_lit is false too.
9484 // But not for the rhs case if we are not in exactly one.
9485 if (in_exactly_one || value != rhs) {
9486 // TODO(user): Instead of bool_or + implications, we could add an
9487 // exactly one! Experiment with this. In particular it might capture
9488 // more structure for later heuristic to add the exactly one instead.
9489 // This also applies to automata/table/element expansion.
9490 auto* bool_or =
9491 context_->working_model->add_constraints()->mutable_bool_or();
9492 for (const int lit : literals) bool_or->add_literals(lit);
9493 bool_or->add_literals(NegatedRef(associated_lit));
9494 }
9495 }
9496 }
9497
9498 // Remove linear constraint now that it is fully encoded.
9499 context_->working_model->mutable_constraints(linear_encoding_ct_index)
9500 ->Clear();
9501 context_->UpdateNewConstraintsVariableUsage();
9502 context_->UpdateConstraintVariableUsage(linear_encoding_ct_index);
9503 return true;
9504}
9505
9508 CompactVectorVector<int, std::pair<int, int64_t>>* _column)
9509 : column(_column) {}
9510 std::size_t operator()(int c) const { return absl::HashOf((*column)[c]); }
9511
9513};
9514
9517 CompactVectorVector<int, std::pair<int, int64_t>>* _column)
9518 : column(_column) {}
9519 bool operator()(int a, int b) const {
9520 if (a == b) return true;
9521
9522 // We use absl::span<> comparison.
9523 return (*column)[a] == (*column)[b];
9524 }
9525
9527};
9528
9529// Note that our symmetry-detector will also identify full permutation group
9530// for these columns, but it is better to handle that even before. We can
9531// also detect variable with different domains but with indentical columns.
9533 if (time_limit_->LimitReached()) return;
9534 if (context_->ModelIsUnsat()) return;
9535 if (context_->params().keep_all_feasible_solutions_in_presolve()) return;
9536 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9537
9538 const int num_vars = context_->working_model->variables().size();
9539 const int num_constraints = context_->working_model->constraints().size();
9540
9541 // Our current implementation require almost a full copy.
9542 // First construct a transpose var to columns (constraint_index, coeff).
9543 std::vector<int> flat_vars;
9544 std::vector<std::pair<int, int64_t>> flat_terms;
9546
9547 // We will only support columns that include:
9548 // - objective
9549 // - linear (non-enforced part)
9550 // - at_most_one/exactly_one/clauses (but with positive variable only).
9551 //
9552 // TODO(user): deal with enforcement_literal, especially bool_and. It is a bit
9553 // annoying to have to deal with all kind of constraints. Maybe convert
9554 // bool_and to at_most_one first? We already do that in other places. Note
9555 // however that an at most one of size 2 means at most 2 columns can be
9556 // identical. If we have a bool and with many term on the left, all column
9557 // could be indentical, but we have to linearize the constraint first.
9558 std::vector<bool> appear_in_amo(num_vars, false);
9559 std::vector<bool> appear_in_bool_constraint(num_vars, false);
9560 for (int c = 0; c < num_constraints; ++c) {
9561 const ConstraintProto& ct = context_->working_model->constraints(c);
9562 absl::Span<const int> literals;
9563
9564 bool is_amo = false;
9565 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
9566 is_amo = true;
9567 literals = ct.at_most_one().literals();
9568 } else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
9569 is_amo = true; // That works here.
9570 literals = ct.exactly_one().literals();
9571 } else if (ct.constraint_case() == ConstraintProto::kBoolOr) {
9572 literals = ct.bool_or().literals();
9573 }
9574
9575 if (!literals.empty()) {
9576 for (const int lit : literals) {
9577 // It is okay to ignore terms (the columns will not be full).
9578 if (!RefIsPositive(lit)) continue;
9579 if (is_amo) appear_in_amo[lit] = true;
9580 appear_in_bool_constraint[lit] = true;
9581 flat_vars.push_back(lit);
9582 flat_terms.push_back({c, 1});
9583 }
9584 continue;
9585 }
9586
9587 if (ct.constraint_case() == ConstraintProto::kLinear) {
9588 const int num_terms = ct.linear().vars().size();
9589 for (int i = 0; i < num_terms; ++i) {
9590 const int var = ct.linear().vars(i);
9591 const int64_t coeff = ct.linear().coeffs(i);
9592 flat_vars.push_back(var);
9593 flat_terms.push_back({c, coeff});
9594 }
9595 continue;
9596 }
9597 }
9598
9599 // Use kObjectiveConstraint (-1) for the objective.
9600 //
9601 // TODO(user): deal with equivalent column with different objective value.
9602 // It might not be easy to presolve, but we can at least have a single
9603 // variable = sum of var appearing only in objective. And we can transfer the
9604 // min cost.
9605 if (context_->working_model->has_objective()) {
9606 context_->WriteObjectiveToProto();
9607 const int num_terms = context_->working_model->objective().vars().size();
9608 for (int i = 0; i < num_terms; ++i) {
9609 const int var = context_->working_model->objective().vars(i);
9610 const int64_t coeff = context_->working_model->objective().coeffs(i);
9611 flat_vars.push_back(var);
9612 flat_terms.push_back({kObjectiveConstraint, coeff});
9613 }
9614 }
9615
9616 // Now construct the graph.
9617 var_to_columns.ResetFromFlatMapping(flat_vars, flat_terms);
9618
9619 // Find duplicate columns using an hash map.
9620 // We only consider "full" columns.
9621 // var -> var_representative using columns hash/comparison.
9622 absl::flat_hash_map<int, int, ColumnHashForDuplicateDetection,
9624 duplicates(
9625 /*capacity=*/num_vars,
9626 ColumnHashForDuplicateDetection(&var_to_columns),
9627 ColumnEqForDuplicateDetection(&var_to_columns));
9628 std::vector<int> flat_duplicates;
9629 std::vector<int> flat_representatives;
9630 for (int var = 0; var < var_to_columns.size(); ++var) {
9631 const int size_seen = var_to_columns[var].size();
9632 if (size_seen == 0) continue;
9633 if (size_seen != context_->VarToConstraints(var).size()) continue;
9634
9635 // TODO(user): If we have duplicate columns appearing in Boolean constraint
9636 // we can only easily substitute if the sum of columns is a Boolean (i.e. if
9637 // it appear in an at most one or exactly one). Otherwise we will need to
9638 // transform such constraint to linear, do that?
9639 if (appear_in_bool_constraint[var] && !appear_in_amo[var]) {
9640 context_->UpdateRuleStats(
9641 "TODO duplicate: duplicate columns in Boolean constraints");
9642 continue;
9643 }
9644
9645 const auto [it, inserted] = duplicates.insert({var, var});
9646 if (!inserted) {
9647 flat_duplicates.push_back(var);
9648 flat_representatives.push_back(it->second);
9649 }
9650 }
9651
9652 // Process duplicates.
9653 int num_equivalent_classes = 0;
9655 rep_to_dups.ResetFromFlatMapping(flat_representatives, flat_duplicates);
9656 std::vector<std::pair<int, int64_t>> definition;
9657 std::vector<int> var_to_remove;
9658 std::vector<int> var_to_rep(num_vars, -1);
9659 for (int var = 0; var < rep_to_dups.size(); ++var) {
9660 if (rep_to_dups[var].empty()) continue;
9661
9662 // Since columns are the same, we can introduce a new variable = sum all
9663 // columns. Note that the linear expression will not overflow, but the
9664 // overflow check also requires that max_sum < int_max/2, which might
9665 // happen.
9666 //
9667 // In the corner case where there is a lot of holes in the domain, and the
9668 // sum domain is too complex, we skip. Hopefully this should be rare.
9669 definition.clear();
9670 definition.push_back({var, 1});
9671 Domain domain = context_->DomainOf(var);
9672 for (const int other_var : rep_to_dups[var]) {
9673 definition.push_back({other_var, 1});
9674 domain = domain.AdditionWith(context_->DomainOf(other_var));
9675 if (domain.NumIntervals() > 100) break;
9676 }
9677 if (domain.NumIntervals() > 100) {
9678 context_->UpdateRuleStats(
9679 "TODO duplicate: domain of the sum is too complex");
9680 continue;
9681 }
9682 if (appear_in_amo[var]) {
9683 domain = domain.IntersectionWith(Domain(0, 1));
9684 }
9685 const int new_var = context_->NewIntVarWithDefinition(
9686 domain, definition, /*append_constraint_to_mapping_model=*/true);
9687 if (new_var == -1) {
9688 context_->UpdateRuleStats("TODO duplicate: possible overflow");
9689 continue;
9690 }
9691
9692 var_to_remove.push_back(var);
9693 CHECK_EQ(var_to_rep[var], -1);
9694 var_to_rep[var] = new_var;
9695 for (const int other_var : rep_to_dups[var]) {
9696 var_to_remove.push_back(other_var);
9697 CHECK_EQ(var_to_rep[other_var], -1);
9698 var_to_rep[other_var] = new_var;
9699 }
9700
9701 // Deal with objective right away.
9702 const int64_t obj_coeff = context_->ObjectiveCoeff(var);
9703 if (obj_coeff != 0) {
9704 context_->RemoveVariableFromObjective(var);
9705 for (const int other_var : rep_to_dups[var]) {
9706 CHECK_EQ(context_->ObjectiveCoeff(other_var), obj_coeff);
9707 context_->RemoveVariableFromObjective(other_var);
9708 }
9709 context_->AddToObjective(new_var, obj_coeff);
9710 }
9711
9712 num_equivalent_classes++;
9713 }
9714
9715 // Lets rescan the model, and remove all variables, replacing them by
9716 // the sum. We do that in one O(model size) pass.
9717 if (!var_to_remove.empty()) {
9718 absl::flat_hash_set<int> seen;
9719 std::vector<std::pair<int, int64_t>> new_terms;
9720 for (int c = 0; c < num_constraints; ++c) {
9721 ConstraintProto* mutable_ct =
9722 context_->working_model->mutable_constraints(c);
9723
9724 seen.clear();
9725 new_terms.clear();
9726
9727 // Deal with bool case.
9728 // TODO(user): maybe converting to linear + single code is better?
9729 BoolArgumentProto* mutable_arg = nullptr;
9730 if (mutable_ct->constraint_case() == ConstraintProto::kAtMostOne) {
9731 mutable_arg = mutable_ct->mutable_at_most_one();
9732 } else if (mutable_ct->constraint_case() ==
9733 ConstraintProto::kExactlyOne) {
9734 mutable_arg = mutable_ct->mutable_exactly_one();
9735 } else if (mutable_ct->constraint_case() == ConstraintProto::kBoolOr) {
9736 mutable_arg = mutable_ct->mutable_bool_or();
9737 }
9738 if (mutable_arg != nullptr) {
9739 int new_size = 0;
9740 const int num_terms = mutable_arg->literals().size();
9741 for (int i = 0; i < num_terms; ++i) {
9742 const int lit = mutable_arg->literals(i);
9743 const int rep = var_to_rep[PositiveRef(lit)];
9744 if (rep != -1) {
9745 CHECK(RefIsPositive(lit));
9746 const auto [_, inserted] = seen.insert(rep);
9747 if (inserted) new_terms.push_back({rep, 1});
9748 continue;
9749 }
9750 mutable_arg->set_literals(new_size, lit);
9751 ++new_size;
9752 }
9753 if (new_size == num_terms) continue; // skip.
9754
9755 // TODO(user): clear amo/exo of size 1.
9756 mutable_arg->mutable_literals()->Truncate(new_size);
9757 for (const auto [var, coeff] : new_terms) {
9758 mutable_arg->add_literals(var);
9759 }
9760 context_->UpdateConstraintVariableUsage(c);
9761 continue;
9762 }
9763
9764 // Deal with linear case.
9765 if (mutable_ct->constraint_case() == ConstraintProto::kLinear) {
9766 int new_size = 0;
9767 LinearConstraintProto* mutable_linear = mutable_ct->mutable_linear();
9768 const int num_terms = mutable_linear->vars().size();
9769 for (int i = 0; i < num_terms; ++i) {
9770 const int var = mutable_linear->vars(i);
9771 const int64_t coeff = mutable_linear->coeffs(i);
9772 const int rep = var_to_rep[var];
9773 if (rep != -1) {
9774 const auto [_, inserted] = seen.insert(rep);
9775 if (inserted) new_terms.push_back({rep, coeff});
9776 continue;
9777 }
9778 mutable_linear->set_vars(new_size, var);
9779 mutable_linear->set_coeffs(new_size, coeff);
9780 ++new_size;
9781 }
9782 if (new_size == num_terms) continue; // skip.
9783
9784 mutable_linear->mutable_vars()->Truncate(new_size);
9785 mutable_linear->mutable_coeffs()->Truncate(new_size);
9786 for (const auto [var, coeff] : new_terms) {
9787 mutable_linear->add_vars(var);
9788 mutable_linear->add_coeffs(coeff);
9789 }
9790 context_->UpdateConstraintVariableUsage(c);
9791 continue;
9792 }
9793 }
9794 }
9795
9796 // We removed all occurrence of "var_to_remove" so we can remove them now.
9797 // Note that since we introduce a new variable per equivalence class, we
9798 // remove one less for each equivalent class.
9799 const int num_var_reduction = var_to_remove.size() - num_equivalent_classes;
9800 for (const int var : var_to_remove) {
9801 CHECK(context_->VarToConstraints(var).empty());
9802 context_->MarkVariableAsRemoved(var);
9803 }
9804 if (num_var_reduction > 0) {
9805 context_->UpdateRuleStats("duplicate: removed duplicated column",
9806 num_var_reduction);
9807 }
9808
9809 timer.AddCounter("num_equiv_classes", num_equivalent_classes);
9810 timer.AddCounter("num_removed_vars", num_var_reduction);
9811}
9812
9813void CpModelPresolver::DetectDuplicateConstraints() {
9814 if (time_limit_->LimitReached()) return;
9815 if (context_->ModelIsUnsat()) return;
9816 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9817
9818 // We need the objective written for this.
9819 if (context_->working_model->has_objective()) {
9820 if (!context_->CanonicalizeObjective()) return;
9821 context_->WriteObjectiveToProto();
9822 }
9823
9824 // Remove duplicate constraints.
9825 // Note that at this point the objective in the proto should be up to date.
9826 //
9827 // TODO(user): We might want to do that earlier so that our count of variable
9828 // usage is not biased by duplicate constraints.
9829 const std::vector<std::pair<int, int>> duplicates =
9831 timer.AddCounter("duplicates", duplicates.size());
9832 for (const auto& [dup, rep] : duplicates) {
9833 // Note that it is important to look at the type of the representative in
9834 // case the constraint became empty.
9835 DCHECK_LT(kObjectiveConstraint, 0);
9836 const int type =
9839 : context_->working_model->constraints(rep).constraint_case();
9840
9841 // For linear constraint, we merge their rhs since it was ignored in the
9842 // FindDuplicateConstraints() call.
9843 if (type == ConstraintProto::kLinear) {
9844 const Domain rep_domain = ReadDomainFromProto(
9845 context_->working_model->constraints(rep).linear());
9846 const Domain d = ReadDomainFromProto(
9847 context_->working_model->constraints(dup).linear());
9848 if (rep_domain != d) {
9849 context_->UpdateRuleStats("duplicate: merged rhs of linear constraint");
9850 const Domain rhs = rep_domain.IntersectionWith(d);
9851 if (rhs.IsEmpty()) {
9852 if (!MarkConstraintAsFalse(
9853 context_->working_model->mutable_constraints(rep))) {
9854 SOLVER_LOG(logger_, "Unsat after merging two linear constraints");
9855 return;
9856 }
9857
9858 // The representative constraint is no longer a linear constraint,
9859 // so we will not enter this type case again and will just remove
9860 // all subsequent duplicate linear constraints.
9861 context_->UpdateConstraintVariableUsage(rep);
9862 continue;
9863 }
9864 FillDomainInProto(rhs, context_->working_model->mutable_constraints(rep)
9865 ->mutable_linear());
9866 }
9867 }
9868
9869 if (type == kObjectiveConstraint) {
9870 context_->UpdateRuleStats(
9871 "duplicate: linear constraint parallel to objective");
9872 const Domain objective_domain =
9873 ReadDomainFromProto(context_->working_model->objective());
9874 const Domain d = ReadDomainFromProto(
9875 context_->working_model->constraints(dup).linear());
9876 if (objective_domain != d) {
9877 context_->UpdateRuleStats("duplicate: updated objective domain");
9878 const Domain new_domain = objective_domain.IntersectionWith(d);
9879 if (new_domain.IsEmpty()) {
9880 return (void)context_->NotifyThatModelIsUnsat(
9881 "Constraint parallel to the objective makes the objective domain "
9882 "empty.");
9883 }
9884 FillDomainInProto(new_domain,
9885 context_->working_model->mutable_objective());
9886
9887 // TODO(user): this write/read is a bit unclean, but needed.
9888 context_->ReadObjectiveFromProto();
9889 }
9890 }
9891 context_->working_model->mutable_constraints(dup)->Clear();
9892 context_->UpdateConstraintVariableUsage(dup);
9893 context_->UpdateRuleStats("duplicate: removed constraint");
9894 }
9895}
9896
9897void CpModelPresolver::DetectDuplicateConstraintsWithDifferentEnforcements(
9898 const CpModelMapping* mapping, BinaryImplicationGraph* implication_graph,
9899 Trail* trail) {
9900 if (time_limit_->LimitReached()) return;
9901 if (context_->ModelIsUnsat()) return;
9902 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9903
9904 // We need the objective written for this.
9905 if (context_->working_model->has_objective()) {
9906 if (!context_->CanonicalizeObjective()) return;
9907 context_->WriteObjectiveToProto();
9908 }
9909
9910 absl::flat_hash_set<Literal> enforcement_vars;
9911 std::vector<std::pair<Literal, Literal>> implications_used;
9912 // TODO(user): We can also do similar stuff to linear constraint that just
9913 // differ at a singleton variable. Or that are equalities. Like if expr + X =
9914 // cte and expr + Y = other_cte, we can see that X is in affine relation with
9915 // Y.
9916 const std::vector<std::pair<int, int>> duplicates_without_enforcement =
9917 FindDuplicateConstraints(*context_->working_model, true);
9918 timer.AddCounter("without_enforcements",
9919 duplicates_without_enforcement.size());
9920 for (const auto& [dup, rep] : duplicates_without_enforcement) {
9921 auto* dup_ct = context_->working_model->mutable_constraints(dup);
9922 auto* rep_ct = context_->working_model->mutable_constraints(rep);
9923
9924 // Make sure our enforcement list are up to date: nothing fixed and that
9925 // its uses the literal representatives.
9926 if (PresolveEnforcementLiteral(dup_ct)) {
9927 context_->UpdateConstraintVariableUsage(dup);
9928 }
9929 if (PresolveEnforcementLiteral(rep_ct)) {
9930 context_->UpdateConstraintVariableUsage(rep);
9931 }
9932
9933 // Skip this pair if one of the constraint was simplified
9934 if (rep_ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET ||
9935 dup_ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
9936 continue;
9937 }
9938
9939 // If one of them has no enforcement, then the other can be ignored.
9940 // We always keep rep, but clear its enforcement if any.
9941 if (dup_ct->enforcement_literal().empty() ||
9942 rep_ct->enforcement_literal().empty()) {
9943 context_->UpdateRuleStats("duplicate: removed enforced constraint");
9944 rep_ct->mutable_enforcement_literal()->Clear();
9945 context_->UpdateConstraintVariableUsage(rep);
9946 dup_ct->Clear();
9947 context_->UpdateConstraintVariableUsage(dup);
9948 continue;
9949 }
9950
9951 const int a = rep_ct->enforcement_literal(0);
9952 const int b = dup_ct->enforcement_literal(0);
9953
9954 if (a == NegatedRef(b) && rep_ct->enforcement_literal().size() == 1 &&
9955 dup_ct->enforcement_literal().size() == 1) {
9956 context_->UpdateRuleStats(
9957 "duplicate: both with enforcement and its negation");
9958 rep_ct->mutable_enforcement_literal()->Clear();
9959 context_->UpdateConstraintVariableUsage(rep);
9960 dup_ct->Clear();
9961 context_->UpdateConstraintVariableUsage(dup);
9962 continue;
9963 }
9964
9965 // Special case. This looks specific but users might reify with a cost
9966 // a duplicate constraint. In this case, no need to have two variables,
9967 // we can make them equal by duality argument.
9968 //
9969 // TODO(user): Deal with more general situation? Note that we already
9970 // do something similar in dual_bound_strengthening.Strengthen() were we
9971 // are more general as we just require an unique blocking constraint rather
9972 // than a singleton variable.
9973 //
9974 // But we could detect that "a <=> constraint" and "b <=> constraint", then
9975 // we can also add the equality. Alternatively, we can just introduce a new
9976 // variable and merge all duplicate constraint into 1 + bunch of boolean
9977 // constraints liking enforcements.
9978 if (context_->VariableWithCostIsUniqueAndRemovable(a) &&
9979 context_->VariableWithCostIsUniqueAndRemovable(b)) {
9980 // Both these case should be presolved before, but it is easy to deal with
9981 // if we encounter them here in some corner cases. And the code after
9982 // 'continue' uses this, in particular to update the hint.
9983 bool skip = false;
9984 if (RefIsPositive(a) == context_->ObjectiveCoeff(PositiveRef(a)) > 0) {
9985 context_->UpdateRuleStats("duplicate: dual fixing enforcement.");
9986 if (!context_->SetLiteralToFalse(a)) return;
9987 skip = true;
9988 }
9989 if (RefIsPositive(b) == context_->ObjectiveCoeff(PositiveRef(b)) > 0) {
9990 context_->UpdateRuleStats("duplicate: dual fixing enforcement.");
9991 if (!context_->SetLiteralToFalse(b)) return;
9992 skip = true;
9993 }
9994 if (skip) continue;
9995
9996 // If there are more than one enforcement literal, then the Booleans
9997 // are not necessarily equivalent: if a constraint is disabled by other
9998 // literal, we don't want to put a or b at 1 and pay an extra cost.
9999 //
10000 // TODO(user): If a is alone, then b==1 can implies a == 1.
10001 // We can also replace [(b, others) => constraint] with (b, others) <=> a.
10002 //
10003 // TODO(user): If the other enforcements are the same, we can also add
10004 // the equivalence and remove the duplicate constraint.
10005 if (rep_ct->enforcement_literal().size() > 1 ||
10006 dup_ct->enforcement_literal().size() > 1) {
10007 context_->UpdateRuleStats(
10008 "TODO duplicate: identical constraint with unique enforcement "
10009 "cost");
10010 continue;
10011 }
10012
10013 // Sign is correct, i.e. ignoring the constraint is expensive.
10014 // The two enforcement can be made equivalent.
10015 context_->UpdateRuleStats("duplicate: dual equivalence of enforcement");
10016 // If `a` and `b` hints are different then the whole hint satisfies
10017 // the enforced constraint. We can thus change them to true (this cannot
10018 // increase the objective value thanks to the `skip` test above -- the
10019 // objective domain is non-constraining, but this only guarantees that
10020 // singleton variables can freely *decrease* the objective).
10021 solution_crush_.UpdateLiteralsToFalseIfDifferent(NegatedRef(a),
10022 NegatedRef(b));
10023 if (!context_->StoreBooleanEqualityRelation(a, b)) return;
10024
10025 // We can also remove duplicate constraint now. It will be done later but
10026 // it seems more efficient to just do it now.
10027 if (dup_ct->enforcement_literal().size() == 1 &&
10028 rep_ct->enforcement_literal().size() == 1) {
10029 dup_ct->Clear();
10030 context_->UpdateConstraintVariableUsage(dup);
10031 continue;
10032 }
10033 }
10034
10035 // Check if the enforcement of one constraint implies the ones of the other.
10036 if (implication_graph != nullptr && mapping != nullptr &&
10037 trail != nullptr) {
10038 for (int i = 0; i < 2; i++) {
10039 // When A and B only differ on their enforcement literals and the
10040 // enforcements of constraint A implies the enforcements of constraint
10041 // B, then constraint A is redundant and we can remove it.
10042 const int c_a = i == 0 ? dup : rep;
10043 const int c_b = i == 0 ? rep : dup;
10044 const auto& ct_a = context_->working_model->constraints(c_a);
10045 const auto& ct_b = context_->working_model->constraints(c_b);
10046
10047 enforcement_vars.clear();
10048 implications_used.clear();
10049 for (const int proto_lit : ct_b.enforcement_literal()) {
10050 const Literal lit = mapping->Literal(proto_lit);
10051 DCHECK(!trail->Assignment().LiteralIsAssigned(lit));
10052 enforcement_vars.insert(lit);
10053 }
10054 for (const int proto_lit : ct_a.enforcement_literal()) {
10055 const Literal lit = mapping->Literal(proto_lit);
10056 DCHECK(!trail->Assignment().LiteralIsAssigned(lit));
10057 for (const Literal implication_lit :
10058 implication_graph->DirectImplications(lit)) {
10059 auto extracted = enforcement_vars.extract(implication_lit);
10060 if (!extracted.empty() && lit != implication_lit) {
10061 implications_used.push_back({lit, implication_lit});
10062 }
10063 }
10064 }
10065 if (enforcement_vars.empty()) {
10066 // Tricky: Because we keep track of literal <=> var == value, we
10067 // cannot easily simplify linear1 here. This is because a scenario
10068 // like this can happen:
10069 //
10070 // We have registered the fact that a <=> X=1 because we saw two
10071 // constraints a => X=1 and not(a) => X!= 1
10072 //
10073 // Now, we are here and we have:
10074 // a => X=1, b => X=1, a => b
10075 // So we rewrite this as
10076 // a => b, b => X=1
10077 //
10078 // But later, the PresolveLinearOfSizeOne() see
10079 // b => X=1 and just rewrite this as b => a since (a <=> X=1).
10080 // This is wrong because the constraint "b => X=1" is needed for the
10081 // equivalence (a <=> X=1), but we lost that fact.
10082 //
10083 // Note(user): In the scenario above we can see that a <=> b, and if
10084 // we know that fact, then the transformation is correctly handled.
10085 // The bug was triggered when the Probing finished early due to time
10086 // limit and we never detected that equivalence.
10087 //
10088 // TODO(user): Try to find a cleaner way to handle this. We could
10089 // query our HasVarValueEncoding() directly here and directly detect a
10090 // <=> b. However we also need to figure the case of
10091 // half-implications.
10092 {
10093 if (ct_a.constraint_case() == ConstraintProto::kLinear &&
10094 ct_a.linear().vars().size() == 1 &&
10095 ct_a.enforcement_literal().size() == 1) {
10096 const int var = ct_a.linear().vars(0);
10097 const Domain var_domain = context_->DomainOf(var);
10098 const Domain rhs =
10099 ReadDomainFromProto(ct_a.linear())
10100 .InverseMultiplicationBy(ct_a.linear().coeffs(0))
10101 .IntersectionWith(var_domain);
10102
10103 // IsFixed() do not work on empty domain.
10104 if (rhs.IsEmpty()) {
10105 context_->UpdateRuleStats("duplicate: linear1 infeasible");
10106 if (!MarkConstraintAsFalse(rep_ct)) return;
10107 if (!MarkConstraintAsFalse(dup_ct)) return;
10108 context_->UpdateConstraintVariableUsage(rep);
10109 context_->UpdateConstraintVariableUsage(dup);
10110 continue;
10111 }
10112 if (rhs == var_domain) {
10113 context_->UpdateRuleStats("duplicate: linear1 always true");
10114 rep_ct->Clear();
10115 dup_ct->Clear();
10116 context_->UpdateConstraintVariableUsage(rep);
10117 context_->UpdateConstraintVariableUsage(dup);
10118 continue;
10119 }
10120
10121 // We skip if it is a var == value or var != value constraint.
10122 if (rhs.IsFixed() ||
10123 rhs.Complement().IntersectionWith(var_domain).IsFixed()) {
10124 context_->UpdateRuleStats(
10125 "TODO duplicate: skipped identical encoding constraints");
10126 continue;
10127 }
10128 }
10129 }
10130
10131 context_->UpdateRuleStats(
10132 "duplicate: identical constraint with implied enforcements");
10133 if (c_a == rep) {
10134 // We don't want to remove the representative element of the
10135 // duplicates detection, so swap the constraints.
10136 rep_ct->Swap(dup_ct);
10137 context_->UpdateConstraintVariableUsage(rep);
10138 }
10139 dup_ct->Clear();
10140 context_->UpdateConstraintVariableUsage(dup);
10141 // Subtle point: we need to add the implications we used back to the
10142 // graph. This is because in some case the implications are only true
10143 // in the presence of the "duplicated" constraints.
10144 for (const auto& [a, b] : implications_used) {
10145 const int proto_lit_a = mapping->GetProtoLiteralFromLiteral(a);
10146 const int proto_lit_b = mapping->GetProtoLiteralFromLiteral(b);
10147 context_->AddImplication(proto_lit_a, proto_lit_b);
10148 }
10149 context_->UpdateNewConstraintsVariableUsage();
10150 break;
10151 }
10152 }
10153 }
10154 }
10155}
10156
10158 if (time_limit_->LimitReached()) return;
10159 if (context_->ModelIsUnsat()) return;
10160 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10161
10162 // List the variable that are pairwise different, also store in offset[x, y]
10163 // the offsets such that x >= y + offset.second OR y >= x + offset.first.
10164 std::vector<std::pair<int, int>> different_vars;
10165 absl::flat_hash_map<std::pair<int, int>, std::pair<int64_t, int64_t>> offsets;
10166
10167 // Process the fact "v1 - v2 \in Domain".
10168 const auto process_difference = [&different_vars, &offsets](int v1, int v2,
10169 Domain d) {
10170 Domain exclusion = d.Complement().PartAroundZero();
10171 if (exclusion.IsEmpty()) return;
10172 if (v1 == v2) return;
10173 std::pair<int, int> key = {v1, v2};
10174 if (v1 > v2) {
10175 std::swap(key.first, key.second);
10176 exclusion = exclusion.Negation();
10177 }
10178
10179 // We have x - y not in exclusion,
10180 // so x - y > exclusion.Max() --> x > y + exclusion.Max();
10181 // OR x - y < exclusion.Min() --> y > x - exclusion.Min();
10182 different_vars.push_back(key);
10183 offsets[key] = {exclusion.Min() == std::numeric_limits<int64_t>::min()
10184 ? std::numeric_limits<int64_t>::max()
10185 : CapAdd(-exclusion.Min(), 1),
10186 CapAdd(exclusion.Max(), 1)};
10187 };
10188
10189 // Try to find identical linear constraint with incompatible domains.
10190 // This works really well on neos16.mps.gz where we have
10191 // a <=> x <= y
10192 // b <=> x >= y
10193 // and a => not(b),
10194 // Because of this presolve, we detect that not(a) => b and thus that a and
10195 // not(b) are equivalent. We can thus simplify the problem to just
10196 // a => x < y
10197 // not(a) => x > y
10198 //
10199 // TODO(user): On that same problem, we could actually just have x != y and
10200 // remove the enforcement literal that is just used for that. But then we
10201 // will just re-create it, since we don't have a native way to handle x != y.
10202 //
10203 // TODO(user): Again on neos16.mps, we actually have cliques of x != y so we
10204 // end up with a bunch of groups of 7 variables in [0, 6] that are all
10205 // different. If we can detect that, then we close the problem quickly instead
10206 // of not closing it.
10207 bool has_all_diff = false;
10208 bool has_no_overlap = false;
10209 std::vector<std::pair<uint64_t, int>> hashes;
10210 const int num_constraints = context_->working_model->constraints_size();
10211 for (int c = 0; c < num_constraints; ++c) {
10212 const ConstraintProto& ct = context_->working_model->constraints(c);
10213 if (ct.constraint_case() == ConstraintProto::kAllDiff) {
10214 has_all_diff = true;
10215 continue;
10216 }
10217 if (ct.constraint_case() == ConstraintProto::kNoOverlap) {
10218 has_no_overlap = true;
10219 continue;
10220 }
10221 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
10222 if (ct.linear().vars().size() == 1) continue;
10223
10224 // Detect direct encoding of x != y. Note that we also see that from x > y
10225 // and related.
10226 if (ct.linear().vars().size() == 2 && ct.enforcement_literal().empty() &&
10227 ct.linear().coeffs(0) == -ct.linear().coeffs(1)) {
10228 // We assume the constraint was already divided by its gcd.
10229 if (ct.linear().coeffs(0) == 1) {
10230 process_difference(ct.linear().vars(0), ct.linear().vars(1),
10231 ReadDomainFromProto(ct.linear()));
10232 } else if (ct.linear().coeffs(0) == -1) {
10233 process_difference(ct.linear().vars(0), ct.linear().vars(1),
10234 ReadDomainFromProto(ct.linear()).Negation());
10235 }
10236 }
10237
10238 // TODO(user): Handle this case?
10239 if (ct.enforcement_literal().size() > 1) continue;
10240
10241 uint64_t hash = kDefaultFingerprintSeed;
10242 hash = FingerprintRepeatedField(ct.linear().vars(), hash);
10243 hash = FingerprintRepeatedField(ct.linear().coeffs(), hash);
10244 hashes.push_back({hash, c});
10245 }
10246 std::sort(hashes.begin(), hashes.end());
10247 for (int next, start = 0; start < hashes.size(); start = next) {
10248 next = start + 1;
10249 while (next < hashes.size() && hashes[next].first == hashes[start].first) {
10250 ++next;
10251 }
10252 absl::Span<const std::pair<uint64_t, int>> range(&hashes[start],
10253 next - start);
10254 if (range.size() <= 1) continue;
10255 if (range.size() > 10) continue;
10256
10257 for (int i = 0; i < range.size(); ++i) {
10258 const ConstraintProto& ct1 =
10259 context_->working_model->constraints(range[i].second);
10260 const int num_terms = ct1.linear().vars().size();
10261 for (int j = i + 1; j < range.size(); ++j) {
10262 const ConstraintProto& ct2 =
10263 context_->working_model->constraints(range[j].second);
10264 if (ct2.linear().vars().size() != num_terms) continue;
10265 if (!ReadDomainFromProto(ct1.linear())
10267 .IsEmpty()) {
10268 continue;
10269 }
10270 if (absl::MakeSpan(ct1.linear().vars().data(), num_terms) !=
10271 absl::MakeSpan(ct2.linear().vars().data(), num_terms)) {
10272 continue;
10273 }
10274 if (absl::MakeSpan(ct1.linear().coeffs().data(), num_terms) !=
10275 absl::MakeSpan(ct2.linear().coeffs().data(), num_terms)) {
10276 continue;
10277 }
10278
10279 if (ct1.enforcement_literal().empty() &&
10280 ct2.enforcement_literal().empty()) {
10281 (void)context_->NotifyThatModelIsUnsat(
10282 "two incompatible linear constraint");
10283 return;
10284 }
10285 if (ct1.enforcement_literal().empty()) {
10286 context_->UpdateRuleStats(
10287 "incompatible linear: set enforcement to false");
10288 if (!context_->SetLiteralToFalse(ct2.enforcement_literal(0))) {
10289 return;
10290 }
10291 continue;
10292 }
10293 if (ct2.enforcement_literal().empty()) {
10294 context_->UpdateRuleStats(
10295 "incompatible linear: set enforcement to false");
10296 if (!context_->SetLiteralToFalse(ct1.enforcement_literal(0))) {
10297 return;
10298 }
10299 continue;
10300 }
10301
10302 const int lit1 = ct1.enforcement_literal(0);
10303 const int lit2 = ct2.enforcement_literal(0);
10304
10305 // Detect x != y via lit => x > y && not(lit) => x < y.
10306 if (ct1.linear().vars().size() == 2 &&
10307 ct1.linear().coeffs(0) == -ct1.linear().coeffs(1) &&
10308 lit1 == NegatedRef(lit2)) {
10309 // We have x - y in domain1 or in domain2, so it must be in the union.
10310 Domain union_of_domain =
10311 ReadDomainFromProto(ct1.linear())
10312 .UnionWith(ReadDomainFromProto(ct2.linear()));
10313
10314 // We assume the constraint was already divided by its gcd.
10315 if (ct1.linear().coeffs(0) == 1) {
10316 process_difference(ct1.linear().vars(0), ct1.linear().vars(1),
10317 std::move(union_of_domain));
10318 } else if (ct1.linear().coeffs(0) == -1) {
10319 process_difference(ct1.linear().vars(0), ct1.linear().vars(1),
10320 union_of_domain.Negation());
10321 }
10322 }
10323
10324 if (lit1 != NegatedRef(lit2)) {
10325 context_->UpdateRuleStats("incompatible linear: add implication");
10326 context_->AddImplication(lit1, NegatedRef(lit2));
10327 }
10328 }
10329 }
10330 }
10331
10332 // Detect all_different cliques.
10333 // We reuse the max-clique code from sat.
10334 //
10335 // TODO(user): To avoid doing that more than once, we only run it if there
10336 // is no all-diff in the model already. This is not perfect.
10337 //
10338 // Note(user): The all diff added here will not be expanded since we run this
10339 // after expansion. This is fragile though. Not even sure this is what we
10340 // want.
10341 //
10342 // TODO(user): Start with the existing all diff and expand them rather than
10343 // not running this if there are all_diff present.
10344 //
10345 // TODO(user): Only add them at the end of the presolve! it hurt our presolve
10346 // (like probing is slower) and only serve for linear relaxation.
10347 if (context_->params().infer_all_diffs() && !has_all_diff &&
10348 !has_no_overlap && different_vars.size() > 2) {
10349 WallTimer local_time;
10350 local_time.Start();
10351
10352 std::vector<std::vector<Literal>> cliques;
10353 absl::flat_hash_set<int> used_var;
10354
10355 Model local_model;
10356 const int num_variables = context_->working_model->variables().size();
10357 local_model.GetOrCreate<Trail>()->Resize(num_variables);
10358 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
10359 graph->Resize(num_variables);
10360 for (const auto [var1, var2] : different_vars) {
10361 if (!RefIsPositive(var1)) continue;
10362 if (!RefIsPositive(var2)) continue;
10363 if (var1 == var2) {
10364 (void)context_->NotifyThatModelIsUnsat("x != y with x == y");
10365 return;
10366 }
10367 // All variables at false is always a valid solution of the local model,
10368 // so this should never return UNSAT.
10369 CHECK(graph->AddAtMostOne({Literal(BooleanVariable(var1), true),
10370 Literal(BooleanVariable(var2), true)}));
10371 if (!used_var.contains(var1)) {
10372 used_var.insert(var1);
10373 cliques.push_back({Literal(BooleanVariable(var1), true),
10374 Literal(BooleanVariable(var2), true)});
10375 }
10376 if (!used_var.contains(var2)) {
10377 used_var.insert(var2);
10378 cliques.push_back({Literal(BooleanVariable(var1), true),
10379 Literal(BooleanVariable(var2), true)});
10380 }
10381 }
10382 CHECK(graph->DetectEquivalences());
10383 graph->TransformIntoMaxCliques(&cliques, 1e8);
10384
10385 int num_cliques = 0;
10386 int64_t cumulative_size = 0;
10387 for (std::vector<Literal>& clique : cliques) {
10388 if (clique.size() <= 2) continue;
10389
10390 ++num_cliques;
10391 cumulative_size += clique.size();
10392 std::sort(clique.begin(), clique.end());
10393
10394 // We have an all-diff, but inspect the offsets to see if we have a
10395 // disjunctive ! Note that this is quadratic, but no more complex than the
10396 // scan of the model we just did above, since we had one linear constraint
10397 // per entry.
10398 const int num_terms = clique.size();
10399 std::vector<int64_t> sizes(num_terms,
10400 std::numeric_limits<int64_t>::max());
10401 for (int i = 0; i < num_terms; ++i) {
10402 const int v1 = clique[i].Variable().value();
10403 for (int j = i + 1; j < num_terms; ++j) {
10404 const int v2 = clique[j].Variable().value();
10405 const auto [o1, o2] = offsets.at({v1, v2});
10406 sizes[i] = std::min(sizes[i], o1);
10407 sizes[j] = std::min(sizes[j], o2);
10408 }
10409 }
10410
10411 int num_greater_than_one = 0;
10412 int64_t issue = 0;
10413 for (int i = 0; i < num_terms; ++i) {
10414 CHECK_GE(sizes[i], 1);
10415 if (sizes[i] > 1) ++num_greater_than_one;
10416
10417 // When this happens, it means this interval can never be before
10418 // any other. We should probably handle this case better, but for now we
10419 // abort.
10420 issue = CapAdd(issue, sizes[i]);
10421 if (issue == std::numeric_limits<int64_t>::max()) {
10422 context_->UpdateRuleStats("TODO no_overlap: with task always last");
10423 num_greater_than_one = 0;
10424 break;
10425 }
10426 }
10427
10428 if (num_greater_than_one > 0) {
10429 // We have one size greater than 1, lets add a no_overlap!
10430 //
10431 // TODO(user): try to remove all the quadratic boolean and their
10432 // corresponding linear2 ? Any Boolean not used elsewhere could be
10433 // removed.
10434 context_->UpdateRuleStats(
10435 "no_overlap: inferred from x != y constraints");
10436
10437 std::vector<int> intervals;
10438 for (int i = 0; i < num_terms; ++i) {
10439 intervals.push_back(context_->working_model->constraints().size());
10440 auto* new_interval =
10441 context_->working_model->add_constraints()->mutable_interval();
10442 new_interval->mutable_start()->set_offset(0);
10443 new_interval->mutable_start()->add_coeffs(1);
10444 new_interval->mutable_start()->add_vars(clique[i].Variable().value());
10445
10446 new_interval->mutable_size()->set_offset(sizes[i]);
10447
10448 new_interval->mutable_end()->set_offset(sizes[i]);
10449 new_interval->mutable_end()->add_coeffs(1);
10450 new_interval->mutable_end()->add_vars(clique[i].Variable().value());
10451 }
10452 auto* new_ct =
10453 context_->working_model->add_constraints()->mutable_no_overlap();
10454 for (const int interval : intervals) {
10455 new_ct->add_intervals(interval);
10456 }
10457 } else {
10458 context_->UpdateRuleStats("all_diff: inferred from x != y constraints");
10459 auto* new_ct =
10460 context_->working_model->add_constraints()->mutable_all_diff();
10461 for (const Literal l : clique) {
10462 auto* expr = new_ct->add_exprs();
10463 expr->add_vars(l.Variable().value());
10464 expr->add_coeffs(1);
10465 }
10466 }
10467 }
10468
10469 timer.AddCounter("different", different_vars.size());
10470 timer.AddCounter("cliques", num_cliques);
10471 timer.AddCounter("size", cumulative_size);
10472 }
10473
10474 context_->UpdateNewConstraintsVariableUsage();
10475}
10476
10477namespace {
10478
10479// Add factor * subset_ct to the given superset_ct.
10480void Substitute(int64_t factor,
10481 const absl::flat_hash_map<int, int64_t>& subset_coeff_map,
10482 const Domain& subset_rhs, const Domain& superset_rhs,
10483 LinearConstraintProto* mutable_linear) {
10484 int new_size = 0;
10485 const int old_size = mutable_linear->vars().size();
10486 for (int i = 0; i < old_size; ++i) {
10487 const int var = mutable_linear->vars(i);
10488 int64_t coeff = mutable_linear->coeffs(i);
10489 const auto it = subset_coeff_map.find(var);
10490 if (it != subset_coeff_map.end()) {
10491 coeff += factor * it->second;
10492 if (coeff == 0) continue;
10493 }
10494
10495 mutable_linear->set_vars(new_size, var);
10496 mutable_linear->set_coeffs(new_size, coeff);
10497 ++new_size;
10498 }
10499 mutable_linear->mutable_vars()->Truncate(new_size);
10500 mutable_linear->mutable_coeffs()->Truncate(new_size);
10502 superset_rhs.AdditionWith(subset_rhs.MultiplicationBy(factor)),
10503 mutable_linear);
10504}
10505
10506} // namespace
10507
10508void CpModelPresolver::DetectDominatedLinearConstraints() {
10509 if (time_limit_->LimitReached()) return;
10510 if (context_->ModelIsUnsat()) return;
10511 if (context_->params().presolve_inclusion_work_limit() == 0) return;
10512 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10513
10514 // Because we only deal with linear constraint and we want to ignore the
10515 // enforcement part, we reuse the variable list in the inclusion detector.
10516 // Note that we ignore "unclean" constraint, so we only have positive
10517 // reference there.
10518 class Storage {
10519 public:
10520 explicit Storage(CpModelProto* proto) : proto_(*proto) {}
10521 int size() const { return static_cast<int>(proto_.constraints().size()); }
10522 absl::Span<const int> operator[](int c) const {
10523 return absl::MakeSpan(proto_.constraints(c).linear().vars());
10524 }
10525
10526 private:
10527 const CpModelProto& proto_;
10528 };
10529 Storage storage(context_->working_model);
10530 InclusionDetector detector(storage, time_limit_);
10531 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
10532
10533 // Because we use the constraint <-> variable graph, we cannot modify it
10534 // during DetectInclusions(). So we delay the update of the graph.
10535 std::vector<int> constraint_indices_to_clean;
10536
10537 // Cache the linear expression domain.
10538 // TODO(user): maybe we should store this instead of recomputing it.
10539 absl::flat_hash_map<int, Domain> cached_expr_domain;
10540
10541 const int num_constraints = context_->working_model->constraints().size();
10542 for (int c = 0; c < num_constraints; ++c) {
10543 const ConstraintProto& ct = context_->working_model->constraints(c);
10544 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
10545
10546 // We only look at long enforced constraint to avoid all the linear of size
10547 // one or two which can be numerous.
10548 if (!ct.enforcement_literal().empty()) {
10549 if (ct.linear().vars().size() < 3) continue;
10550 }
10551
10552 if (!LinearConstraintIsClean(ct.linear())) {
10553 // This shouldn't happen except in potential corner cases were the
10554 // constraints were not canonicalized before this point. We just skip
10555 // such constraint.
10556 continue;
10557 }
10558
10559 detector.AddPotentialSet(c);
10560
10561 const auto [min_activity, max_activity] =
10562 context_->ComputeMinMaxActivity(ct.linear());
10563 cached_expr_domain[c] = Domain(min_activity, max_activity);
10564 }
10565
10566 int64_t num_inclusions = 0;
10567 absl::flat_hash_map<int, int64_t> coeff_map;
10568 detector.DetectInclusions([&](int subset_c, int superset_c) {
10569 ++num_inclusions;
10570
10571 // Store the coeff of the subset linear constraint in a map.
10572 const ConstraintProto& subset_ct =
10573 context_->working_model->constraints(subset_c);
10574 const LinearConstraintProto& subset_lin = subset_ct.linear();
10575 coeff_map.clear();
10576 detector.IncreaseWorkDone(subset_lin.vars().size());
10577 for (int i = 0; i < subset_lin.vars().size(); ++i) {
10578 coeff_map[subset_lin.vars(i)] = subset_lin.coeffs(i);
10579 }
10580
10581 // We have a perfect match if 'factor_a * subset == factor_b * superset' on
10582 // the common positions. Note that assuming subset has been gcd reduced,
10583 // there is not point considering factor_b != 1.
10584 bool perfect_match = true;
10585
10586 // Find interesting factor of the subset that cancels terms of the superset.
10587 int64_t factor = 0;
10588 int64_t min_pos_factor = std::numeric_limits<int64_t>::max();
10589 int64_t max_neg_factor = std::numeric_limits<int64_t>::min();
10590
10591 // Lets compute the implied domain of the linear expression
10592 // "superset - subset". Note that we actually do not need exact inclusion
10593 // for this algorithm to work, but it is an heuristic to not try it with
10594 // all pair of constraints.
10595 const ConstraintProto& superset_ct =
10596 context_->working_model->constraints(superset_c);
10597 const LinearConstraintProto& superset_lin = superset_ct.linear();
10598 int64_t diff_min_activity = 0;
10599 int64_t diff_max_activity = 0;
10600 detector.IncreaseWorkDone(superset_lin.vars().size());
10601 for (int i = 0; i < superset_lin.vars().size(); ++i) {
10602 const int var = superset_lin.vars(i);
10603 int64_t coeff = superset_lin.coeffs(i);
10604 const auto it = coeff_map.find(var);
10605
10606 if (it != coeff_map.end()) {
10607 const int64_t subset_coeff = it->second;
10608
10609 const int64_t div = coeff / subset_coeff;
10610 if (div > 0) {
10611 min_pos_factor = std::min(div, min_pos_factor);
10612 } else {
10613 max_neg_factor = std::max(div, max_neg_factor);
10614 }
10615
10616 if (perfect_match) {
10617 if (coeff % subset_coeff == 0) {
10618 if (factor == 0) {
10619 // Note that factor can be negative.
10620 factor = div;
10621 } else if (factor != div) {
10622 perfect_match = false;
10623 }
10624 } else {
10625 perfect_match = false;
10626 }
10627 }
10628
10629 // TODO(user): compute the factor first in case it is != 1 ?
10630 coeff -= subset_coeff;
10631 }
10632 if (coeff == 0) continue;
10633 context_->CappedUpdateMinMaxActivity(var, coeff, &diff_min_activity,
10634 &diff_max_activity);
10635 }
10636
10637 const Domain diff_domain(diff_min_activity, diff_max_activity);
10638 const Domain subset_rhs = ReadDomainFromProto(subset_lin);
10639 const Domain superset_rhs = ReadDomainFromProto(superset_lin);
10640
10641 // Case 1: superset is redundant.
10642 // We process this one first as it let us remove the longest constraint.
10643 //
10644 // Important: because of how we computed the inclusion, the diff_domain is
10645 // only valid if none of the enforcement appear in the subset.
10646 //
10647 // TODO(user): Compute the correct infered domain in this case.
10648 if (subset_ct.enforcement_literal().empty()) {
10649 const Domain implied_superset_domain =
10650 subset_rhs.AdditionWith(diff_domain)
10651 .IntersectionWith(cached_expr_domain[superset_c]);
10652 if (implied_superset_domain.IsIncludedIn(superset_rhs)) {
10653 context_->UpdateRuleStats(
10654 "linear inclusion: redundant containing constraint");
10655 context_->working_model->mutable_constraints(superset_c)->Clear();
10656 constraint_indices_to_clean.push_back(superset_c);
10657 detector.StopProcessingCurrentSuperset();
10658 return;
10659 }
10660 }
10661
10662 // Case 2: subset is redundant.
10663 if (superset_ct.enforcement_literal().empty()) {
10664 const Domain implied_subset_domain =
10665 superset_rhs.AdditionWith(diff_domain.Negation())
10666 .IntersectionWith(cached_expr_domain[subset_c]);
10667 if (implied_subset_domain.IsIncludedIn(subset_rhs)) {
10668 context_->UpdateRuleStats(
10669 "linear inclusion: redundant included constraint");
10670 context_->working_model->mutable_constraints(subset_c)->Clear();
10671 constraint_indices_to_clean.push_back(subset_c);
10672 detector.StopProcessingCurrentSubset();
10673 return;
10674 }
10675 }
10676
10677 // If the subset is an equality, and we can add a factor of it to the
10678 // superset so that the activity range is guaranteed to be tighter, we
10679 // always do it. This should both sparsify the problem but also lead to
10680 // tighter propagation.
10681 if (subset_rhs.IsFixed() && subset_ct.enforcement_literal().empty()) {
10682 const int64_t best_factor =
10683 max_neg_factor > -min_pos_factor ? max_neg_factor : min_pos_factor;
10684
10685 // Compute the activity range before and after. Because our pos/neg factor
10686 // are the smallest possible, if one is undefined then we are guaranteed
10687 // to be tighter, and do not need to compute this.
10688 //
10689 // TODO(user): can we compute the best factor that make this as tight as
10690 // possible instead? that looks doable.
10691 bool is_tigher = true;
10692 if (min_pos_factor != std::numeric_limits<int64_t>::max() &&
10693 max_neg_factor != std::numeric_limits<int64_t>::min()) {
10694 int64_t min_before = 0;
10695 int64_t max_before = 0;
10696 int64_t min_after = CapProd(best_factor, subset_rhs.FixedValue());
10697 int64_t max_after = min_after;
10698 for (int i = 0; i < superset_lin.vars().size(); ++i) {
10699 const int var = superset_lin.vars(i);
10700 const auto it = coeff_map.find(var);
10701 if (it == coeff_map.end()) continue;
10702
10703 const int64_t coeff_before = superset_lin.coeffs(i);
10704 const int64_t coeff_after = coeff_before - best_factor * it->second;
10705 context_->CappedUpdateMinMaxActivity(var, coeff_before, &min_before,
10706 &max_before);
10707 context_->CappedUpdateMinMaxActivity(var, coeff_after, &min_after,
10708 &max_after);
10709 }
10710 is_tigher = min_after >= min_before && max_after <= max_before;
10711 }
10712 if (is_tigher) {
10713 context_->UpdateRuleStats("linear inclusion: sparsify superset");
10714 Substitute(-best_factor, coeff_map, subset_rhs, superset_rhs,
10715 context_->working_model->mutable_constraints(superset_c)
10716 ->mutable_linear());
10717 constraint_indices_to_clean.push_back(superset_c);
10718 detector.StopProcessingCurrentSuperset();
10719 return;
10720 }
10721 }
10722
10723 // We do a bit more if we have an exact match and factor * subset is exactly
10724 // a subpart of the superset constraint.
10725 if (perfect_match && subset_ct.enforcement_literal().empty() &&
10726 superset_ct.enforcement_literal().empty()) {
10727 CHECK_NE(factor, 0);
10728
10729 // Propagate domain on the superset - subset variables.
10730 // TODO(user): We can probably still do that if the inclusion is not
10731 // perfect.
10732 temp_ct_.Clear();
10733 auto* mutable_linear = temp_ct_.mutable_linear();
10734 for (int i = 0; i < superset_lin.vars().size(); ++i) {
10735 const int var = superset_lin.vars(i);
10736 const int64_t coeff = superset_lin.coeffs(i);
10737 const auto it = coeff_map.find(var);
10738 if (it != coeff_map.end()) continue;
10739 mutable_linear->add_vars(var);
10740 mutable_linear->add_coeffs(coeff);
10741 }
10743 superset_rhs.AdditionWith(subset_rhs.MultiplicationBy(-factor)),
10744 mutable_linear);
10745 PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_);
10746 if (context_->ModelIsUnsat()) detector.Stop();
10747
10748 if (superset_rhs.IsFixed()) {
10749 if (subset_lin.vars().size() + 1 == superset_lin.vars().size()) {
10750 // Because we propagated the equation on the singleton variable above,
10751 // and we have an equality, the subset is redundant!
10752 context_->UpdateRuleStats(
10753 "linear inclusion: subset + singleton is equality");
10754 context_->working_model->mutable_constraints(subset_c)->Clear();
10755 constraint_indices_to_clean.push_back(subset_c);
10756 detector.StopProcessingCurrentSubset();
10757 return;
10758 }
10759
10760 // This one could make sense if subset is large vs superset.
10761 context_->UpdateRuleStats(
10762 "TODO linear inclusion: superset is equality");
10763 }
10764 }
10765 });
10766
10767 for (const int c : constraint_indices_to_clean) {
10768 context_->UpdateConstraintVariableUsage(c);
10769 }
10770
10771 timer.AddToWork(1e-9 * static_cast<double>(detector.work_done()));
10772 timer.AddCounter("relevant_constraints", detector.num_potential_supersets());
10773 timer.AddCounter("num_inclusions", num_inclusions);
10774 timer.AddCounter("num_redundant", constraint_indices_to_clean.size());
10775}
10776
10777// TODO(user): Also substitute if this appear in the objective?
10778// TODO(user): In some case we only need common_part <= new_var.
10779bool CpModelPresolver::RemoveCommonPart(
10780 const absl::flat_hash_map<int, int64_t>& common_var_coeff_map,
10781 absl::Span<const std::pair<int, int64_t>> block,
10782 ActivityBoundHelper* helper) {
10783 int new_var;
10784 int64_t gcd = 0;
10785 int64_t offset = 0;
10786
10787 // If the common part is expressable via one of the constraint in the block as
10788 // == gcd * X + offset, we can just use this variable instead of creating a
10789 // new variable.
10790 int definiting_equation = -1;
10791 for (const auto [c, multiple] : block) {
10792 const ConstraintProto& ct = context_->working_model->constraints(c);
10793 if (std::abs(multiple) != 1) continue;
10794 if (!IsLinearEqualityConstraint(ct)) continue;
10795 if (ct.linear().vars().size() != common_var_coeff_map.size() + 1) continue;
10796
10797 context_->UpdateRuleStats(
10798 "linear matrix: defining equation for common rectangle");
10799 definiting_equation = c;
10800
10801 // Find the missing term and its coefficient.
10802 int64_t coeff = 0;
10803 const int num_terms = ct.linear().vars().size();
10804 for (int k = 0; k < num_terms; ++k) {
10805 if (common_var_coeff_map.contains(ct.linear().vars(k))) continue;
10806 new_var = ct.linear().vars(k);
10807 coeff = ct.linear().coeffs(k);
10808 break;
10809 }
10810 CHECK_NE(coeff, 0);
10811
10812 // We have multiple * common + coeff * X = constant.
10813 // So common = multiple^-1 * constant - multiple^-1 * coeff * X;
10814 gcd = -multiple * coeff;
10815 offset = multiple * ct.linear().domain(0);
10816 break;
10817 }
10818
10819 // We need a new variable and defining equation.
10820 if (definiting_equation == -1) {
10821 offset = 0;
10822 int64_t min_activity = 0;
10823 int64_t max_activity = 0;
10824 tmp_terms_.clear();
10825 std::vector<std::pair<int, int64_t>> common_part;
10826 for (const auto [var, coeff] : common_var_coeff_map) {
10827 common_part.push_back({var, coeff});
10828 gcd = std::gcd(gcd, std::abs(coeff));
10829 if (context_->CanBeUsedAsLiteral(var) && !context_->IsFixed(var)) {
10830 tmp_terms_.push_back({var, coeff});
10831 continue;
10832 }
10833 if (coeff > 0) {
10834 min_activity += coeff * context_->MinOf(var);
10835 max_activity += coeff * context_->MaxOf(var);
10836 } else {
10837 min_activity += coeff * context_->MaxOf(var);
10838 max_activity += coeff * context_->MinOf(var);
10839 }
10840 }
10841
10842 // We isolated the Boolean in tmp_terms_, use the helper to get
10843 // more precise activity bounds. Note that while tmp_terms_ was built from
10844 // a hash map and is in an unspecified order, the Compute*Activity() helpers
10845 // will still return a deterministic result.
10846 if (!tmp_terms_.empty()) {
10847 min_activity += helper->ComputeMinActivity(tmp_terms_);
10848 max_activity += helper->ComputeMaxActivity(tmp_terms_);
10849 }
10850
10851 if (gcd > 1) {
10852 min_activity /= gcd;
10853 max_activity /= gcd;
10854 for (int i = 0; i < common_part.size(); ++i) {
10855 common_part[i].second /= gcd;
10856 }
10857 }
10858
10859 // Create new variable.
10860 std::sort(common_part.begin(), common_part.end());
10861 new_var = context_->NewIntVarWithDefinition(
10862 Domain(min_activity, max_activity), common_part);
10863 if (new_var == -1) return false;
10864 }
10865
10866 // Replace in each constraint the common part by gcd * multiple * new_var !
10867 for (const auto [c, multiple] : block) {
10868 if (c == definiting_equation) continue;
10869
10870 auto* mutable_linear =
10871 context_->working_model->mutable_constraints(c)->mutable_linear();
10872 const int num_terms = mutable_linear->vars().size();
10873 int new_size = 0;
10874 bool new_var_already_seen = false;
10875 for (int k = 0; k < num_terms; ++k) {
10876 if (common_var_coeff_map.contains(mutable_linear->vars(k))) {
10877 CHECK_EQ(common_var_coeff_map.at(mutable_linear->vars(k)) * multiple,
10878 mutable_linear->coeffs(k));
10879 continue;
10880 }
10881
10882 // Tricky: the new variable can already be present in this expression!
10883 int64_t new_coeff = mutable_linear->coeffs(k);
10884 if (mutable_linear->vars(k) == new_var) {
10885 new_var_already_seen = true;
10886 new_coeff += gcd * multiple;
10887 if (new_coeff == 0) continue;
10888 }
10889
10890 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
10891 mutable_linear->set_coeffs(new_size, new_coeff);
10892 ++new_size;
10893 }
10894 mutable_linear->mutable_vars()->Truncate(new_size);
10895 mutable_linear->mutable_coeffs()->Truncate(new_size);
10896 if (!new_var_already_seen) {
10897 mutable_linear->add_vars(new_var);
10898 mutable_linear->add_coeffs(gcd * multiple);
10899 }
10900 if (offset != 0) {
10901 FillDomainInProto(ReadDomainFromProto(*mutable_linear)
10902 .AdditionWith(Domain(-offset * multiple)),
10903 mutable_linear);
10904 }
10905 context_->UpdateConstraintVariableUsage(c);
10906 }
10907 return true;
10908}
10909
10910namespace {
10911
10912int64_t FindVarCoeff(int var, const ConstraintProto& ct) {
10913 const int num_terms = ct.linear().vars().size();
10914 for (int k = 0; k < num_terms; ++k) {
10915 if (ct.linear().vars(k) == var) return ct.linear().coeffs(k);
10916 }
10917 return 0;
10918}
10919
10920int64_t ComputeNonZeroReduction(size_t block_size, size_t common_part_size) {
10921 // We replace the block by a column of new variable.
10922 // But we also need to define this new variable.
10923 return static_cast<int64_t>(block_size * (common_part_size - 1) -
10924 common_part_size - 1);
10925}
10926
10927} // namespace
10928
10929// The idea is to find a set of literal in AMO relationship that appear in
10930// many linear constraints. If this is the case, we can create a new variable to
10931// make an exactly one constraint, and replace it in the linear.
10932void CpModelPresolver::FindBigAtMostOneAndLinearOverlap(
10933 ActivityBoundHelper* helper) {
10934 if (time_limit_->LimitReached()) return;
10935 if (context_->ModelIsUnsat()) return;
10936 if (context_->params().presolve_inclusion_work_limit() == 0) return;
10937 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10938
10939 int64_t num_blocks = 0;
10940 int64_t nz_reduction = 0;
10941 std::vector<int> amo_cts;
10942 std::vector<int> amo_literals;
10943
10944 std::vector<int> common_part;
10945 std::vector<int> best_common_part;
10946
10947 std::vector<bool> common_part_sign;
10948 std::vector<bool> best_common_part_sign;
10949
10950 // We store for each var if the literal was positive or not.
10951 absl::flat_hash_map<int, bool> var_in_amo;
10952
10953 for (int x = 0; x < context_->working_model->variables().size(); ++x) {
10954 // We pick a variable x that appear in some AMO.
10955 if (time_limit_->LimitReached()) break;
10956 if (timer.WorkLimitIsReached()) break;
10957 if (helper->NumAmoForVariable(x) == 0) continue;
10958
10959 amo_cts.clear();
10960 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
10961 for (const int c : context_->VarToConstraints(x)) {
10962 if (c < 0) continue;
10963 const ConstraintProto& ct = context_->working_model->constraints(c);
10964 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
10965 amo_cts.push_back(c);
10966 } else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
10967 amo_cts.push_back(c);
10968 }
10969 }
10970 if (amo_cts.empty()) continue;
10971
10972 // Pick a random AMO containing x.
10973 //
10974 // TODO(user): better algo!
10975 //
10976 // Note that we don't care about the polarity, for each linear constraint,
10977 // if the coeff magnitude are the same, we will just have two values
10978 // controlled by whether the AMO (or EXO subset) is at one or zero.
10979 var_in_amo.clear();
10980 amo_literals.clear();
10981 common_part.clear();
10982 common_part_sign.clear();
10983 int base_ct_index;
10984 {
10985 // For determinism.
10986 std::sort(amo_cts.begin(), amo_cts.end());
10987 const int random_c =
10988 absl::Uniform<int>(*context_->random(), 0, amo_cts.size());
10989 base_ct_index = amo_cts[random_c];
10990 const ConstraintProto& ct =
10991 context_->working_model->constraints(base_ct_index);
10992 const auto& literals = ct.constraint_case() == ConstraintProto::kAtMostOne
10993 ? ct.at_most_one().literals()
10994 : ct.exactly_one().literals();
10995 timer.TrackSimpleLoop(5 * literals.size()); // hash insert are slow.
10996 for (const int literal : literals) {
10997 amo_literals.push_back(literal);
10998 common_part.push_back(PositiveRef(literal));
10999 common_part_sign.push_back(RefIsPositive(literal));
11000 const auto [_, inserted] =
11001 var_in_amo.insert({PositiveRef(literal), RefIsPositive(literal)});
11002 CHECK(inserted);
11003 }
11004 }
11005
11006 const int64_t x_multiplier = var_in_amo.at(x) ? 1 : -1;
11007
11008 // Collect linear constraints with at least two Boolean terms in var_in_amo
11009 // with the same coefficient than x.
11010 std::vector<int> block_cts;
11011 std::vector<int> linear_cts;
11012 int max_common_part = 0;
11013 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11014 for (const int c : context_->VarToConstraints(x)) {
11015 if (c < 0) continue;
11016 const ConstraintProto& ct = context_->working_model->constraints(c);
11017 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
11018 const int num_terms = ct.linear().vars().size();
11019 if (num_terms < 2) continue;
11020
11021 timer.TrackSimpleLoop(2 * num_terms);
11022 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
11023 if (x_coeff == 0) continue; // could be in enforcement.
11024
11025 int num_in_amo = 0;
11026 for (int k = 0; k < num_terms; ++k) {
11027 const int var = ct.linear().vars(k);
11028 if (!RefIsPositive(var)) {
11029 num_in_amo = 0; // Abort.
11030 break;
11031 }
11032 const auto it = var_in_amo.find(var);
11033 if (it == var_in_amo.end()) continue;
11034 int64_t coeff = ct.linear().coeffs(k);
11035 if (!it->second) coeff = -coeff;
11036 if (coeff != x_coeff) continue;
11037 ++num_in_amo;
11038 }
11039 if (num_in_amo < 2) continue;
11040
11041 max_common_part += num_in_amo;
11042 if (num_in_amo == common_part.size()) {
11043 // This is a perfect match!
11044 block_cts.push_back(c);
11045 } else {
11046 linear_cts.push_back(c);
11047 }
11048 }
11049 if (linear_cts.empty() && block_cts.empty()) continue;
11050 if (max_common_part < 100) continue;
11051
11052 // Remember the best block encountered in the greedy algo below.
11053 // Note that we always start with the current perfect match.
11054 best_common_part = common_part;
11055 best_common_part_sign = common_part_sign;
11056 int best_block_size = block_cts.size();
11057 int best_saved_nz =
11058 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
11059
11060 // For determinism.
11061 std::sort(block_cts.begin(), block_cts.end());
11062 std::sort(linear_cts.begin(), linear_cts.end());
11063
11064 // We will just greedily compute a big block with a random order.
11065 // TODO(user): We could sort by match with the full constraint instead.
11066 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
11067 for (const int c : linear_cts) {
11068 const ConstraintProto& ct = context_->working_model->constraints(c);
11069 const int num_terms = ct.linear().vars().size();
11070 timer.TrackSimpleLoop(2 * num_terms);
11071 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
11072 CHECK_NE(x_coeff, 0);
11073
11074 common_part.clear();
11075 common_part_sign.clear();
11076 for (int k = 0; k < num_terms; ++k) {
11077 const int var = ct.linear().vars(k);
11078 const auto it = var_in_amo.find(var);
11079 if (it == var_in_amo.end()) continue;
11080 int64_t coeff = ct.linear().coeffs(k);
11081 if (!it->second) coeff = -coeff;
11082 if (coeff != x_coeff) continue;
11083 common_part.push_back(var);
11084 common_part_sign.push_back(it->second);
11085 }
11086 if (common_part.size() < 2) continue;
11087
11088 // Change var_in_amo;
11089 block_cts.push_back(c);
11090 if (common_part.size() < var_in_amo.size()) {
11091 var_in_amo.clear();
11092 for (int i = 0; i < common_part.size(); ++i) {
11093 var_in_amo[common_part[i]] = common_part_sign[i];
11094 }
11095 }
11096
11097 // We have a block that can be replaced with a single new boolean +
11098 // defining exo constraint. Note that we can also replace in the base
11099 // constraint, hence the +1 to the block size.
11100 const int64_t saved_nz =
11101 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
11102 if (saved_nz > best_saved_nz) {
11103 best_block_size = block_cts.size();
11104 best_saved_nz = saved_nz;
11105 best_common_part = common_part;
11106 best_common_part_sign = common_part_sign;
11107 }
11108 }
11109 if (best_saved_nz < 100) continue;
11110
11111 // Use the best rectangle.
11112 // We start with the full match.
11113 // TODO(user): maybe we should always just use this if it is large enough?
11114 block_cts.resize(best_block_size);
11115 var_in_amo.clear();
11116 for (int i = 0; i < best_common_part.size(); ++i) {
11117 var_in_amo[best_common_part[i]] = best_common_part_sign[i];
11118 }
11119
11120 ++num_blocks;
11121 nz_reduction += best_saved_nz;
11122 context_->UpdateRuleStats("linear matrix: common amo rectangle");
11123
11124 // First filter the amo.
11125 int new_size = 0;
11126 for (const int lit : amo_literals) {
11127 if (!var_in_amo.contains(PositiveRef(lit))) continue;
11128 amo_literals[new_size++] = lit;
11129 }
11130 if (new_size == amo_literals.size()) {
11131 const ConstraintProto& ct =
11132 context_->working_model->constraints(base_ct_index);
11133 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
11134 context_->UpdateRuleStats("TODO linear matrix: constant rectangle!");
11135 } else {
11136 context_->UpdateRuleStats(
11137 "TODO linear matrix: reuse defining constraint");
11138 }
11139 } else if (new_size + 1 == amo_literals.size()) {
11140 const ConstraintProto& ct =
11141 context_->working_model->constraints(base_ct_index);
11142 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
11143 context_->UpdateRuleStats("TODO linear matrix: reuse exo constraint");
11144 }
11145 }
11146 amo_literals.resize(new_size);
11147
11148 // Create a new literal that is one iff one of the literal in AMO is one.
11149 const int new_var = context_->NewBoolVarWithClause(amo_literals);
11150 {
11151 auto* new_exo =
11152 context_->working_model->add_constraints()->mutable_exactly_one();
11153 new_exo->mutable_literals()->Reserve(amo_literals.size() + 1);
11154 for (const int lit : amo_literals) {
11155 new_exo->add_literals(lit);
11156 }
11157 new_exo->add_literals(NegatedRef(new_var));
11158 context_->UpdateNewConstraintsVariableUsage();
11159 }
11160
11161 // Filter the base amo/exo.
11162 {
11163 ConstraintProto* ct =
11164 context_->working_model->mutable_constraints(base_ct_index);
11165 auto* mutable_literals =
11166 ct->constraint_case() == ConstraintProto::kAtMostOne
11167 ? ct->mutable_at_most_one()->mutable_literals()
11168 : ct->mutable_exactly_one()->mutable_literals();
11169 int new_size = 0;
11170 for (const int lit : *mutable_literals) {
11171 if (var_in_amo.contains(PositiveRef(lit))) continue;
11172 (*mutable_literals)[new_size++] = lit;
11173 }
11174 (*mutable_literals)[new_size++] = new_var;
11175 mutable_literals->Truncate(new_size);
11176 context_->UpdateConstraintVariableUsage(base_ct_index);
11177 }
11178
11179 // Use this Boolean in all the linear constraints.
11180 for (const int c : block_cts) {
11181 auto* mutable_linear =
11182 context_->working_model->mutable_constraints(c)->mutable_linear();
11183
11184 // The removed expression will be (offset + coeff_x * new_bool).
11185 int64_t offset = 0;
11186 int64_t coeff_x = 0;
11187
11188 int new_size = 0;
11189 const int num_terms = mutable_linear->vars().size();
11190 for (int k = 0; k < num_terms; ++k) {
11191 const int var = mutable_linear->vars(k);
11192 CHECK(RefIsPositive(var));
11193 int64_t coeff = mutable_linear->coeffs(k);
11194 const auto it = var_in_amo.find(var);
11195 if (it != var_in_amo.end()) {
11196 if (it->second) {
11197 // default is zero, amo at one means we add coeff.
11198 } else {
11199 // term is -coeff * (1 - var) + coeff.
11200 // default is coeff, amo at 1 means we remove coeff.
11201 offset += coeff;
11202 coeff = -coeff;
11203 }
11204 if (coeff_x == 0) coeff_x = coeff;
11205 CHECK_EQ(coeff, coeff_x);
11206 continue;
11207 }
11208 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
11209 mutable_linear->set_coeffs(new_size, coeff);
11210 ++new_size;
11211 }
11212
11213 // Add the new term.
11214 mutable_linear->set_vars(new_size, new_var);
11215 mutable_linear->set_coeffs(new_size, coeff_x);
11216 ++new_size;
11217
11218 mutable_linear->mutable_vars()->Truncate(new_size);
11219 mutable_linear->mutable_coeffs()->Truncate(new_size);
11220 if (offset != 0) {
11222 ReadDomainFromProto(*mutable_linear).AdditionWith(Domain(-offset)),
11223 mutable_linear);
11224 }
11225 context_->UpdateConstraintVariableUsage(c);
11226 }
11227 }
11228
11229 timer.AddCounter("blocks", num_blocks);
11230 timer.AddCounter("saved_nz", nz_reduction);
11231 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11232}
11233
11234// This helps on neos-5045105-creuse.pb.gz for instance.
11235void CpModelPresolver::FindBigVerticalLinearOverlap(
11236 ActivityBoundHelper* helper) {
11237 if (time_limit_->LimitReached()) return;
11238 if (context_->ModelIsUnsat()) return;
11239 if (context_->params().presolve_inclusion_work_limit() == 0) return;
11240 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11241
11242 int64_t num_blocks = 0;
11243 int64_t nz_reduction = 0;
11244 absl::flat_hash_map<int, int64_t> coeff_map;
11245 for (int x = 0; x < context_->working_model->variables().size(); ++x) {
11246 if (timer.WorkLimitIsReached()) break;
11247
11248 bool in_enforcement = false;
11249 std::vector<int> linear_cts;
11250 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11251 for (const int c : context_->VarToConstraints(x)) {
11252 if (c < 0) continue;
11253 const ConstraintProto& ct = context_->working_model->constraints(c);
11254 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
11255
11256 const int num_terms = ct.linear().vars().size();
11257 if (num_terms < 2) continue;
11258 bool is_canonical = true;
11259 timer.TrackSimpleLoop(num_terms);
11260 for (int k = 0; k < num_terms; ++k) {
11261 if (!RefIsPositive(ct.linear().vars(k))) {
11262 is_canonical = false;
11263 break;
11264 }
11265 }
11266 if (!is_canonical) continue;
11267
11268 // We don't care about enforcement literal, but we don't want x inside.
11269 timer.TrackSimpleLoop(ct.enforcement_literal().size());
11270 for (const int lit : ct.enforcement_literal()) {
11271 if (PositiveRef(lit) == x) {
11272 in_enforcement = true;
11273 break;
11274 }
11275 }
11276
11277 // Note(user): We will actually abort right away in this case, but we
11278 // want work_done to be deterministic! so we do the work anyway.
11279 if (in_enforcement) continue;
11280 linear_cts.push_back(c);
11281 }
11282
11283 // If a Boolean is used in enforcement, we prefer not to combine it with
11284 // others. TODO(user): more generally ignore Boolean or only replace if
11285 // there is a big non-zero improvement.
11286 if (in_enforcement) continue;
11287 if (linear_cts.size() < 10) continue;
11288
11289 // For determinism.
11290 std::sort(linear_cts.begin(), linear_cts.end());
11291 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
11292
11293 // Now it is almost the same algo as for FindBigHorizontalLinearOverlap().
11294 // We greedely compute a "common" rectangle using the first constraint
11295 // as a "base" one. Note that if a aX + bY appear in the majority of
11296 // constraint, we have a good chance to find this block since we start by
11297 // a random constraint.
11298 coeff_map.clear();
11299
11300 std::vector<std::pair<int, int64_t>> block;
11301 std::vector<std::pair<int, int64_t>> common_part;
11302 for (const int c : linear_cts) {
11303 const ConstraintProto& ct = context_->working_model->constraints(c);
11304 const int num_terms = ct.linear().vars().size();
11305 timer.TrackSimpleLoop(num_terms);
11306
11307 // Compute the coeff of x.
11308 const int64_t x_coeff = FindVarCoeff(x, ct);
11309 if (x_coeff == 0) continue;
11310
11311 if (block.empty()) {
11312 // This is our base constraint.
11313 coeff_map.clear();
11314 for (int k = 0; k < num_terms; ++k) {
11315 coeff_map[ct.linear().vars(k)] = ct.linear().coeffs(k);
11316 }
11317 if (coeff_map.size() < 2) continue;
11318 block.push_back({c, x_coeff});
11319 continue;
11320 }
11321
11322 // We are looking for a common divisor of coeff_map and this constraint.
11323 const int64_t gcd =
11324 std::gcd(std::abs(coeff_map.at(x)), std::abs(x_coeff));
11325 const int64_t multiple_base = coeff_map.at(x) / gcd;
11326 const int64_t multiple_ct = x_coeff / gcd;
11327 common_part.clear();
11328 for (int k = 0; k < num_terms; ++k) {
11329 const int64_t coeff = ct.linear().coeffs(k);
11330 if (coeff % multiple_ct != 0) continue;
11331
11332 const auto it = coeff_map.find(ct.linear().vars(k));
11333 if (it == coeff_map.end()) continue;
11334 if (it->second % multiple_base != 0) continue;
11335 if (it->second / multiple_base != coeff / multiple_ct) continue;
11336
11337 common_part.push_back({ct.linear().vars(k), coeff / multiple_ct});
11338 }
11339
11340 // Skip bad constraint.
11341 if (common_part.size() < 2) continue;
11342
11343 // Update coeff_map.
11344 block.push_back({c, x_coeff});
11345 coeff_map.clear();
11346 for (const auto [var, coeff] : common_part) {
11347 coeff_map[var] = coeff;
11348 }
11349 }
11350
11351 // We have a candidate.
11352 const int64_t saved_nz =
11353 ComputeNonZeroReduction(block.size(), coeff_map.size());
11354 if (saved_nz < 30) continue;
11355
11356 // Fix multiples, currently this contain the coeff of x for each constraint.
11357 const int64_t base_x = coeff_map.at(x);
11358 for (auto& [c, multipier] : block) {
11359 CHECK_EQ(multipier % base_x, 0);
11360 multipier /= base_x;
11361 }
11362
11363 // Introduce new_var = coeff_map and perform the substitution.
11364 if (!RemoveCommonPart(coeff_map, block, helper)) continue;
11365 ++num_blocks;
11366 nz_reduction += saved_nz;
11367 context_->UpdateRuleStats("linear matrix: common vertical rectangle");
11368 }
11369
11370 timer.AddCounter("blocks", num_blocks);
11371 timer.AddCounter("saved_nz", nz_reduction);
11372 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11373}
11374
11375// Note that internally, we already split long linear into smaller chunk, so
11376// it should be beneficial to identify common part between many linear
11377// constraint.
11378//
11379// Note(user): This was made to work on var-smallemery-m6j6.pb.gz, but applies
11380// to quite a few miplib problem. Try to improve the heuristics and algorithm to
11381// be faster and detect larger block.
11382void CpModelPresolver::FindBigHorizontalLinearOverlap(
11383 ActivityBoundHelper* helper) {
11384 if (time_limit_->LimitReached()) return;
11385 if (context_->ModelIsUnsat()) return;
11386 if (context_->params().presolve_inclusion_work_limit() == 0) return;
11387 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11388
11389 const int num_constraints = context_->working_model->constraints_size();
11390 std::vector<std::pair<int, int>> to_sort;
11391 for (int c = 0; c < num_constraints; ++c) {
11392 const ConstraintProto& ct = context_->working_model->constraints(c);
11393 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
11394 const int size = ct.linear().vars().size();
11395 if (size < 5) continue;
11396 to_sort.push_back({-size, c});
11397 }
11398 std::sort(to_sort.begin(), to_sort.end());
11399
11400 std::vector<int> sorted_linear;
11401 for (int i = 0; i < to_sort.size(); ++i) {
11402 sorted_linear.push_back(to_sort[i].second);
11403 }
11404
11405 // On large problem, using and hash_map can be slow, so we use the vector
11406 // version and for now fill the map only when doing the change.
11407 std::vector<int> var_to_coeff_non_zeros;
11408 std::vector<int64_t> var_to_coeff(context_->working_model->variables_size(),
11409 0);
11410
11411 int64_t num_blocks = 0;
11412 int64_t nz_reduction = 0;
11413 for (int i = 0; i < sorted_linear.size(); ++i) {
11414 const int c = sorted_linear[i];
11415 if (c < 0) continue;
11416 if (timer.WorkLimitIsReached()) break;
11417
11418 for (const int var : var_to_coeff_non_zeros) {
11419 var_to_coeff[var] = 0;
11420 }
11421 var_to_coeff_non_zeros.clear();
11422 {
11423 const ConstraintProto& ct = context_->working_model->constraints(c);
11424 const int num_terms = ct.linear().vars().size();
11425 timer.TrackSimpleLoop(num_terms);
11426 for (int k = 0; k < num_terms; ++k) {
11427 const int var = ct.linear().vars(k);
11428 var_to_coeff[var] = ct.linear().coeffs(k);
11429 var_to_coeff_non_zeros.push_back(var);
11430 }
11431 }
11432
11433 // Look for an initial overlap big enough.
11434 //
11435 // Note that because we construct it incrementally, we need the first two
11436 // constraint to have an overlap of at least half this.
11437 int saved_nz = 100;
11438 std::vector<int> used_sorted_linear = {i};
11439 std::vector<std::pair<int, int64_t>> block = {{c, 1}};
11440 std::vector<std::pair<int, int64_t>> common_part;
11441 std::vector<std::pair<int, int>> old_matches;
11442
11443 for (int j = 0; j < sorted_linear.size(); ++j) {
11444 if (i == j) continue;
11445 const int other_c = sorted_linear[j];
11446 if (other_c < 0) continue;
11447 const ConstraintProto& ct = context_->working_model->constraints(other_c);
11448
11449 // No need to continue if linear is not large enough.
11450 const int num_terms = ct.linear().vars().size();
11451 const int best_saved_nz =
11452 ComputeNonZeroReduction(block.size() + 1, num_terms);
11453 if (best_saved_nz <= saved_nz) break;
11454
11455 // This is the hot loop here.
11456 timer.TrackSimpleLoop(num_terms);
11457 common_part.clear();
11458 for (int k = 0; k < num_terms; ++k) {
11459 const int var = ct.linear().vars(k);
11460 if (var_to_coeff[var] == ct.linear().coeffs(k)) {
11461 common_part.push_back({var, ct.linear().coeffs(k)});
11462 }
11463 }
11464
11465 // We replace (new_block_size) * (common_size) by
11466 // 1/ and equation of size common_size + 1
11467 // 2/ new_block_size variable
11468 // So new_block_size * common_size - common_size - 1 - new_block_size
11469 // which is (new_block_size - 1) * (common_size - 1) - 2;
11470 const int64_t new_saved_nz =
11471 ComputeNonZeroReduction(block.size() + 1, common_part.size());
11472 if (new_saved_nz > saved_nz) {
11473 saved_nz = new_saved_nz;
11474 used_sorted_linear.push_back(j);
11475 block.push_back({other_c, 1});
11476
11477 // Rebuild the map.
11478 // TODO(user): We could only clear the non-common part.
11479 for (const int var : var_to_coeff_non_zeros) {
11480 var_to_coeff[var] = 0;
11481 }
11482 var_to_coeff_non_zeros.clear();
11483 for (const auto [var, coeff] : common_part) {
11484 var_to_coeff[var] = coeff;
11485 var_to_coeff_non_zeros.push_back(var);
11486 }
11487 } else {
11488 if (common_part.size() > 1) {
11489 old_matches.push_back({j, common_part.size()});
11490 }
11491 }
11492 }
11493
11494 // Introduce a new variable = common_part.
11495 // Use it in all linear constraint.
11496 if (block.size() > 1) {
11497 // Try to extend with exact matches that were skipped.
11498 const int match_size = var_to_coeff_non_zeros.size();
11499 for (const auto [index, old_match_size] : old_matches) {
11500 if (old_match_size < match_size) continue;
11501
11502 int new_match_size = 0;
11503 const int other_c = sorted_linear[index];
11504 const ConstraintProto& ct =
11505 context_->working_model->constraints(other_c);
11506 const int num_terms = ct.linear().vars().size();
11507 for (int k = 0; k < num_terms; ++k) {
11508 if (var_to_coeff[ct.linear().vars(k)] == ct.linear().coeffs(k)) {
11509 ++new_match_size;
11510 }
11511 }
11512 if (new_match_size == match_size) {
11513 context_->UpdateRuleStats(
11514 "linear matrix: common horizontal rectangle extension");
11515 used_sorted_linear.push_back(index);
11516 block.push_back({other_c, 1});
11517 }
11518 }
11519
11520 // TODO(user): avoid creating the map? this is not visible in profile
11521 // though since we only do it when a reduction is performed.
11522 absl::flat_hash_map<int, int64_t> coeff_map;
11523 for (const int var : var_to_coeff_non_zeros) {
11524 coeff_map[var] = var_to_coeff[var];
11525 }
11526 if (!RemoveCommonPart(coeff_map, block, helper)) continue;
11527
11528 ++num_blocks;
11529 nz_reduction += ComputeNonZeroReduction(block.size(), coeff_map.size());
11530 context_->UpdateRuleStats("linear matrix: common horizontal rectangle");
11531 for (const int i : used_sorted_linear) sorted_linear[i] = -1;
11532 }
11533 }
11534
11535 timer.AddCounter("blocks", num_blocks);
11536 timer.AddCounter("saved_nz", nz_reduction);
11537 timer.AddCounter("linears", sorted_linear.size());
11538 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11539}
11540
11541// Find two linear constraints of the form:
11542// - term1 + identical_terms = rhs1
11543// - term2 + identical_terms = rhs2
11544// This allows to infer an affine relation, and remove one constraint and one
11545// variable.
11546void CpModelPresolver::FindAlmostIdenticalLinearConstraints() {
11547 if (time_limit_->LimitReached()) return;
11548 if (context_->ModelIsUnsat()) return;
11549
11550 // Work tracking is required, since in the worst case (n identical
11551 // constraints), we are in O(n^3). In practice we are way faster though. And
11552 // identical constraints should have already be removed when we call this.
11553 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11554
11555 // Only keep non-enforced linear equality of size > 2. Sort by size.
11556 std::vector<std::pair<int, int>> to_sort;
11557 const int num_constraints = context_->working_model->constraints_size();
11558 for (int c = 0; c < num_constraints; ++c) {
11559 const ConstraintProto& ct = context_->working_model->constraints(c);
11560 if (!IsLinearEqualityConstraint(ct)) continue;
11561 if (ct.linear().vars().size() <= 2) continue;
11562
11563 // Our canonicalization should sort constraints, we skip non-canonical ones.
11564 if (!std::is_sorted(ct.linear().vars().begin(), ct.linear().vars().end())) {
11565 continue;
11566 }
11567
11568 to_sort.push_back({ct.linear().vars().size(), c});
11569 }
11570 std::sort(to_sort.begin(), to_sort.end());
11571
11572 // One watcher data structure.
11573 // This is similar to what is used by the inclusion detector.
11574 std::vector<int> var_to_clear;
11575 std::vector<std::vector<std::pair<int, int64_t>>> var_to_ct_coeffs_;
11576 const int num_variables = context_->working_model->variables_size();
11577 var_to_ct_coeffs_.resize(num_variables);
11578
11579 int end;
11580 int num_tested_pairs = 0;
11581 int num_affine_relations = 0;
11582 for (int start = 0; start < to_sort.size(); start = end) {
11583 // Split by identical size.
11584 end = start + 1;
11585 const int length = to_sort[start].first;
11586 for (; end < to_sort.size(); ++end) {
11587 if (to_sort[end].first != length) break;
11588 }
11589 const int span_size = end - start;
11590 if (span_size == 1) continue;
11591
11592 // Watch one term of each constraint randomly.
11593 for (const int var : var_to_clear) var_to_ct_coeffs_[var].clear();
11594 var_to_clear.clear();
11595 for (int i = start; i < end; ++i) {
11596 const int c = to_sort[i].second;
11597 const LinearConstraintProto& lin =
11598 context_->working_model->constraints(c).linear();
11599 const int index =
11600 absl::Uniform<int>(*context_->random(), 0, lin.vars().size());
11601 const int var = lin.vars(index);
11602 if (var_to_ct_coeffs_[var].empty()) var_to_clear.push_back(var);
11603 var_to_ct_coeffs_[var].push_back({c, lin.coeffs(index)});
11604 }
11605
11606 // For each constraint, try other constraints that have at least one term in
11607 // common with the same coeff. Note that for two constraint of size 3, we
11608 // will miss a working pair only if we both watch the variable that is
11609 // different. So only with a probability (1/3)^2. Since we call this more
11610 // than once per presolve, we should be mostly good. For larger constraint,
11611 // we shouldn't miss much.
11612 for (int i1 = start; i1 < end; ++i1) {
11613 if (timer.WorkLimitIsReached()) break;
11614 const int c1 = to_sort[i1].second;
11615 const LinearConstraintProto& lin1 =
11616 context_->working_model->constraints(c1).linear();
11617 bool skip = false;
11618 for (int i = 0; !skip && i < lin1.vars().size(); ++i) {
11619 for (const auto [c2, coeff2] : var_to_ct_coeffs_[lin1.vars(i)]) {
11620 if (c2 == c1) continue;
11621
11622 // TODO(user): we could easily deal with * -1 or other multiples.
11623 if (coeff2 != lin1.coeffs(i)) continue;
11624 if (timer.WorkLimitIsReached()) break;
11625
11626 // Skip if we processed this earlier and deleted it.
11627 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
11628 if (ct2.constraint_case() != ConstraintProto::kLinear) continue;
11629 const LinearConstraintProto& lin2 =
11630 context_->working_model->constraints(c2).linear();
11631 if (lin2.vars().size() != length) continue;
11632
11633 // TODO(user): In practice LinearsDifferAtOneTerm() will abort
11634 // early if the constraints differ early, so we are even faster than
11635 // this.
11636 timer.TrackSimpleLoop(length);
11637
11638 ++num_tested_pairs;
11639 if (LinearsDifferAtOneTerm(lin1, lin2)) {
11640 // The two equalities only differ at one term !
11641 // do c1 -= c2 and presolve c1 right away.
11642 // We should detect new affine relation and remove it.
11643 auto* to_modify = context_->working_model->mutable_constraints(c1);
11645 -1, context_->working_model->constraints(c2), to_modify)) {
11646 continue;
11647 }
11648
11649 // Affine will be of size 2, but we might also have the same
11650 // variable with different coeff in both constraint, in which case
11651 // the linear will be of size 1.
11652 DCHECK_LE(to_modify->linear().vars().size(), 2);
11653
11654 ++num_affine_relations;
11655 context_->UpdateRuleStats(
11656 "linear: advanced affine relation from 2 constraints.");
11657
11658 // We should stop processing c1 since it should be empty afterward.
11659 DivideLinearByGcd(to_modify);
11660 PresolveSmallLinear(to_modify);
11661 context_->UpdateConstraintVariableUsage(c1);
11662 skip = true;
11663 break;
11664 }
11665 }
11666 }
11667 }
11668 }
11669
11670 timer.AddCounter("num_tested_pairs", num_tested_pairs);
11671 timer.AddCounter("found", num_affine_relations);
11672 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11673}
11674
11675void CpModelPresolver::ExtractEncodingFromLinear() {
11676 if (time_limit_->LimitReached()) return;
11677 if (context_->ModelIsUnsat()) return;
11678 if (context_->params().presolve_inclusion_work_limit() == 0) return;
11679 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11680
11681 // TODO(user): compute on the fly instead of temporary storing variables?
11682 std::vector<int> relevant_constraints;
11683 CompactVectorVector<int> storage;
11684 InclusionDetector detector(storage, time_limit_);
11685 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
11686
11687 // Loop over the constraints and fill the structures above.
11688 //
11689 // TODO(user): Ideally we want to process exactly_one first in case a
11690 // linear constraint is both included in an at_most_one and an exactly_one.
11691 std::vector<int> vars;
11692 const int num_constraints = context_->working_model->constraints().size();
11693 for (int c = 0; c < num_constraints; ++c) {
11694 const ConstraintProto& ct = context_->working_model->constraints(c);
11695 switch (ct.constraint_case()) {
11696 case ConstraintProto::kAtMostOne: {
11697 vars.clear();
11698 for (const int ref : ct.at_most_one().literals()) {
11699 vars.push_back(PositiveRef(ref));
11700 }
11701 relevant_constraints.push_back(c);
11702 detector.AddPotentialSuperset(storage.Add(vars));
11703 break;
11704 }
11705 case ConstraintProto::kExactlyOne: {
11706 vars.clear();
11707 for (const int ref : ct.exactly_one().literals()) {
11708 vars.push_back(PositiveRef(ref));
11709 }
11710 relevant_constraints.push_back(c);
11711 detector.AddPotentialSuperset(storage.Add(vars));
11712 break;
11713 }
11714 case ConstraintProto::kLinear: {
11715 // We only consider equality with no enforcement.
11716 if (!IsLinearEqualityConstraint(ct)) continue;
11717
11718 // We also want a single non-Boolean.
11719 // Note that this assume the constraint is canonicalized.
11720 bool is_candidate = true;
11721 int num_integers = 0;
11722 vars.clear();
11723 const int num_terms = ct.linear().vars().size();
11724 for (int i = 0; i < num_terms; ++i) {
11725 const int ref = ct.linear().vars(i);
11726 if (context_->CanBeUsedAsLiteral(ref)) {
11727 vars.push_back(PositiveRef(ref));
11728 } else {
11729 ++num_integers;
11730 if (std::abs(ct.linear().coeffs(i)) != 1) {
11731 is_candidate = false;
11732 break;
11733 }
11734 if (num_integers == 2) {
11735 is_candidate = false;
11736 break;
11737 }
11738 }
11739 }
11740
11741 // We ignore cases with just one Boolean as this should be already dealt
11742 // with elsewhere.
11743 if (is_candidate && num_integers == 1 && vars.size() > 1) {
11744 relevant_constraints.push_back(c);
11745 detector.AddPotentialSubset(storage.Add(vars));
11746 }
11747 break;
11748 }
11749 default:
11750 break;
11751 }
11752 }
11753
11754 // Stats.
11755 int64_t num_exactly_one_encodings = 0;
11756 int64_t num_at_most_one_encodings = 0;
11757 int64_t num_literals = 0;
11758 int64_t num_unique_terms = 0;
11759 int64_t num_multiple_terms = 0;
11760
11761 detector.DetectInclusions([&](int subset, int superset) {
11762 const int subset_c = relevant_constraints[subset];
11763 const int superset_c = relevant_constraints[superset];
11764 const ConstraintProto& superset_ct =
11765 context_->working_model->constraints(superset_c);
11766 if (superset_ct.constraint_case() == ConstraintProto::kAtMostOne) {
11767 ++num_at_most_one_encodings;
11768 } else {
11769 ++num_exactly_one_encodings;
11770 }
11771 num_literals += storage[subset].size();
11772 context_->UpdateRuleStats("encoding: extracted from linear");
11773
11774 if (!ProcessEncodingFromLinear(subset_c, superset_ct, &num_unique_terms,
11775 &num_multiple_terms)) {
11776 detector.Stop(); // UNSAT.
11777 }
11778
11779 detector.StopProcessingCurrentSubset();
11780 });
11781
11782 timer.AddCounter("potential_supersets", detector.num_potential_supersets());
11783 timer.AddCounter("potential_subsets", detector.num_potential_subsets());
11784 timer.AddCounter("amo_encodings", num_at_most_one_encodings);
11785 timer.AddCounter("exo_encodings", num_exactly_one_encodings);
11786 timer.AddCounter("unique_terms", num_unique_terms);
11787 timer.AddCounter("multiple_terms", num_multiple_terms);
11788 timer.AddCounter("literals", num_literals);
11789}
11790
11791// Special case: if a literal l appear in exactly two constraints:
11792// - l => var in domain1
11793// - not(l) => var in domain2
11794// then we know that domain(var) is included in domain1 U domain2,
11795// and that the literal l can be removed (and determined at postsolve).
11796//
11797// TODO(user): This could be generalized further to linear of size > 1 if for
11798// example the terms are the same.
11799//
11800// We wait for the model expansion to take place in order to avoid removing
11801// encoding that will later be re-created during expansion.
11802void CpModelPresolver::LookAtVariableWithDegreeTwo(int var) {
11803 CHECK(RefIsPositive(var));
11804 CHECK(context_->ConstraintVariableGraphIsUpToDate());
11805 if (context_->ModelIsUnsat()) return;
11806 if (context_->params().keep_all_feasible_solutions_in_presolve()) return;
11807 if (context_->IsFixed(var)) return;
11808 if (!context_->ModelIsExpanded()) return;
11809 if (!context_->CanBeUsedAsLiteral(var)) return;
11810
11811 // TODO(user): If var is in objective, we might be able to tighten domains.
11812 // ex: enf => x \in [0, 1]
11813 // not(enf) => x \in [1, 2]
11814 // The x can be removed from one place. Maybe just do <=> not in [0,1] with
11815 // dual code?
11816 if (context_->VarToConstraints(var).size() != 2) return;
11817
11818 bool abort = false;
11819 int ct_var = -1;
11820 Domain union_of_domain;
11821 int num_positive = 0;
11822 std::vector<int> constraint_indices_to_remove;
11823 for (const int c : context_->VarToConstraints(var)) {
11824 if (c < 0) {
11825 abort = true;
11826 break;
11827 }
11828 constraint_indices_to_remove.push_back(c);
11829 const ConstraintProto& ct = context_->working_model->constraints(c);
11830 if (ct.enforcement_literal().size() != 1 ||
11831 PositiveRef(ct.enforcement_literal(0)) != var ||
11832 ct.constraint_case() != ConstraintProto::kLinear ||
11833 ct.linear().vars().size() != 1) {
11834 abort = true;
11835 break;
11836 }
11837 if (ct.enforcement_literal(0) == var) ++num_positive;
11838 if (ct_var != -1 && PositiveRef(ct.linear().vars(0)) != ct_var) {
11839 abort = true;
11840 break;
11841 }
11842 ct_var = PositiveRef(ct.linear().vars(0));
11843 union_of_domain = union_of_domain.UnionWith(
11844 ReadDomainFromProto(ct.linear())
11845 .InverseMultiplicationBy(RefIsPositive(ct.linear().vars(0))
11846 ? ct.linear().coeffs(0)
11847 : -ct.linear().coeffs(0)));
11848 }
11849 if (abort) return;
11850 if (num_positive != 1) return;
11851 if (!context_->IntersectDomainWith(ct_var, union_of_domain)) return;
11852
11853 context_->UpdateRuleStats("variables: removable enforcement literal");
11854 absl::c_sort(constraint_indices_to_remove); // For determinism
11855
11856 // Note(user): Only one constraint should be enough given how the postsolve
11857 // work. However that will not work for the case where we postsolve by solving
11858 // the mapping model (debug_postsolve_with_full_solver:true).
11859 for (const int c : constraint_indices_to_remove) {
11860 context_->NewMappingConstraint(context_->working_model->constraints(c),
11861 __FILE__, __LINE__);
11862 context_->working_model->mutable_constraints(c)->Clear();
11863 context_->UpdateConstraintVariableUsage(c);
11864 }
11865 context_->MarkVariableAsRemoved(var);
11866}
11867
11868namespace {
11869
11870absl::Span<const int> AtMostOneOrExactlyOneLiterals(const ConstraintProto& ct) {
11871 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
11872 return {ct.at_most_one().literals()};
11873 } else {
11874 return {ct.exactly_one().literals()};
11875 }
11876}
11877
11878} // namespace
11879
11880void CpModelPresolver::ProcessVariableInTwoAtMostOrExactlyOne(int var) {
11881 DCHECK(RefIsPositive(var));
11882 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
11883 if (context_->ModelIsUnsat()) return;
11884 if (context_->IsFixed(var)) return;
11885 if (context_->VariableWasRemoved(var)) return;
11886 if (!context_->ModelIsExpanded()) return;
11887 if (!context_->CanBeUsedAsLiteral(var)) return;
11888
11889 int64_t cost = 0;
11890 if (context_->VarToConstraints(var).contains(kObjectiveConstraint)) {
11891 if (context_->VarToConstraints(var).size() != 3) return;
11892 cost = context_->ObjectiveMap().at(var);
11893 } else {
11894 if (context_->VarToConstraints(var).size() != 2) return;
11895 }
11896
11897 // We have a variable with a cost (or without) that appear in two constraints.
11898 // We want two at_most_one or exactly_one.
11899 // TODO(user): Also deal with bool_and.
11900 int c1 = -1;
11901 int c2 = -1;
11902 for (const int c : context_->VarToConstraints(var)) {
11903 if (c < 0) continue;
11904 const ConstraintProto& ct = context_->working_model->constraints(c);
11905 if (ct.constraint_case() != ConstraintProto::kAtMostOne &&
11906 ct.constraint_case() != ConstraintProto::kExactlyOne) {
11907 return;
11908 }
11909 if (c1 == -1) {
11910 c1 = c;
11911 } else {
11912 c2 = c;
11913 }
11914 }
11915
11916 // This can happen for variable in a kAffineRelationConstraint.
11917 if (c1 == -1 || c2 == -1) return;
11918
11919 // Tricky: We iterate on a map above, so the order is non-deterministic, we
11920 // do not want that, so we re-order the constraints.
11921 if (c1 > c2) std::swap(c1, c2);
11922
11923 // We can always sum the two constraints.
11924 // If var appear in one and not(var) in the other, the two term cancel out to
11925 // one, so we still have an <= 1 (or eventually a ==1 (see below).
11926 //
11927 // Note that if the constraint are of size one, they can just be preprocessed
11928 // individually and just be removed. So we abort here as the code below
11929 // is incorrect if new_ct is an empty constraint.
11930 context_->tmp_literals.clear();
11931 int c1_ref = std::numeric_limits<int>::min();
11932 const ConstraintProto& ct1 = context_->working_model->constraints(c1);
11933 if (AtMostOneOrExactlyOneLiterals(ct1).size() <= 1) return;
11934 for (const int lit : AtMostOneOrExactlyOneLiterals(ct1)) {
11935 if (PositiveRef(lit) == var) {
11936 c1_ref = lit;
11937 } else {
11938 context_->tmp_literals.push_back(lit);
11939 }
11940 }
11941 int c2_ref = std::numeric_limits<int>::min();
11942 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
11943 if (AtMostOneOrExactlyOneLiterals(ct2).size() <= 1) return;
11944 for (const int lit : AtMostOneOrExactlyOneLiterals(ct2)) {
11945 if (PositiveRef(lit) == var) {
11946 c2_ref = lit;
11947 } else {
11948 context_->tmp_literals.push_back(lit);
11949 }
11950 }
11951 DCHECK_NE(c1_ref, std::numeric_limits<int>::min());
11952 DCHECK_NE(c2_ref, std::numeric_limits<int>::min());
11953 if (c1_ref != NegatedRef(c2_ref)) return;
11954
11955 // If the cost is non-zero, we can use an exactly one to make it zero.
11956 // Use that exactly one in the postsolve to recover the value of var.
11957 int64_t cost_shift = 0;
11958 absl::Span<const int> literals;
11959 if (ct1.constraint_case() == ConstraintProto::kExactlyOne) {
11960 cost_shift = RefIsPositive(c1_ref) ? cost : -cost;
11961 literals = ct1.exactly_one().literals();
11962 } else if (ct2.constraint_case() == ConstraintProto::kExactlyOne) {
11963 cost_shift = RefIsPositive(c2_ref) ? cost : -cost;
11964 literals = ct2.exactly_one().literals();
11965 } else {
11966 // Dual argument. The one with a negative cost can be transformed to
11967 // an exactly one.
11968 // Tricky: if there is a cost, we don't want the objective to be
11969 // constraining to be able to do that.
11970 if (context_->params().keep_all_feasible_solutions_in_presolve()) return;
11971 if (context_->params().keep_symmetry_in_presolve()) return;
11972 if (cost != 0 && context_->ObjectiveDomainIsConstraining()) return;
11973
11974 if (RefIsPositive(c1_ref) == (cost < 0)) {
11975 cost_shift = RefIsPositive(c1_ref) ? cost : -cost;
11976 literals = ct1.at_most_one().literals();
11977 } else {
11978 cost_shift = RefIsPositive(c2_ref) ? cost : -cost;
11979 literals = ct2.at_most_one().literals();
11980 }
11981 }
11982
11983 if (!context_->ShiftCostInExactlyOne(literals, cost_shift)) return;
11984 DCHECK(!context_->ObjectiveMap().contains(var));
11985 context_->NewMappingConstraint(__FILE__, __LINE__)
11986 ->mutable_exactly_one()
11987 ->mutable_literals()
11988 ->Assign(literals.begin(), literals.end());
11989
11990 // We can now replace the two constraint by a single one, and delete var!
11991 const int new_ct_index = context_->working_model->constraints().size();
11992 ConstraintProto* new_ct = context_->working_model->add_constraints();
11993 if (ct1.constraint_case() == ConstraintProto::kExactlyOne &&
11994 ct2.constraint_case() == ConstraintProto::kExactlyOne) {
11995 for (const int lit : context_->tmp_literals) {
11996 new_ct->mutable_exactly_one()->add_literals(lit);
11997 }
11998 } else {
11999 // At most one here is enough: if all zero, we can satisfy one of the
12000 // two exactly one at postsolve.
12001 for (const int lit : context_->tmp_literals) {
12002 new_ct->mutable_at_most_one()->add_literals(lit);
12003 }
12004 }
12005
12006 context_->UpdateNewConstraintsVariableUsage();
12007 context_->working_model->mutable_constraints(c1)->Clear();
12008 context_->UpdateConstraintVariableUsage(c1);
12009 context_->working_model->mutable_constraints(c2)->Clear();
12010 context_->UpdateConstraintVariableUsage(c2);
12011
12012 context_->UpdateRuleStats(
12013 "at_most_one: resolved two constraints with opposite literal");
12014 context_->MarkVariableAsRemoved(var);
12015
12016 // TODO(user): If the merged list contains duplicates or literal that are
12017 // negation of other, we need to deal with that right away. For some reason
12018 // something is not robust to that it seems. Investigate & fix!
12019 DCHECK_NE(new_ct->constraint_case(), ConstraintProto::CONSTRAINT_NOT_SET);
12020 if (PresolveAtMostOrExactlyOne(new_ct)) {
12021 context_->UpdateConstraintVariableUsage(new_ct_index);
12022 }
12023}
12024
12025// If we have a bunch of constraint of the form literal => Y \in domain and
12026// another constraint Y = f(X), we can remove Y, that constraint, and transform
12027// all linear1 from constraining Y to constraining X.
12028//
12029// We can for instance do it for Y = abs(X) or Y = X^2 easily. More complex
12030// function might be trickier.
12031//
12032// Note that we can't always do it in the reverse direction though!
12033// If we have l => X = -1, we can't transfer that to abs(X) for instance, since
12034// X=1 will also map to abs(-1). We can only do it if for all implied domain D
12035// we have f^-1(f(D)) = D, which is not easy to check.
12036void CpModelPresolver::MaybeTransferLinear1ToAnotherVariable(int var) {
12037 // Find the extra constraint and do basic CHECKs.
12038 int other_c;
12039 int num_others = 0;
12040 std::vector<int> to_rewrite;
12041 for (const int c : context_->VarToConstraints(var)) {
12042 if (c >= 0) {
12043 const ConstraintProto& ct = context_->working_model->constraints(c);
12044 if (ct.constraint_case() == ConstraintProto::kLinear &&
12045 ct.linear().vars().size() == 1) {
12046 to_rewrite.push_back(c);
12047 continue;
12048 }
12049 }
12050 ++num_others;
12051 other_c = c;
12052 }
12053 if (num_others != 1) return;
12054 if (other_c < 0) return;
12055
12056 // In general constraint with more than two variable can't be removed.
12057 // Similarly for linear2 with non-fixed rhs as we would need to check the form
12058 // of all implied domain.
12059 const auto& other_ct = context_->working_model->constraints(other_c);
12060 if (context_->ConstraintToVars(other_c).size() != 2 ||
12061 !other_ct.enforcement_literal().empty() ||
12062 other_ct.constraint_case() == ConstraintProto::kLinear) {
12063 return;
12064 }
12065
12066 // This will be the rewriting function. It takes the implied domain of var
12067 // from linear1, and return a pair {new_var, new_var_implied_domain}.
12068 std::function<std::pair<int, Domain>(const Domain& implied)> transfer_f =
12069 nullptr;
12070
12071 // We only support a few cases.
12072 //
12073 // TODO(user): implement more! Note that the linear2 case was tempting, but if
12074 // we don't have an equality, we can't transfer, and if we do, we actually
12075 // have affine equivalence already.
12076 if (other_ct.constraint_case() == ConstraintProto::kLinMax &&
12077 other_ct.lin_max().target().vars().size() == 1 &&
12078 other_ct.lin_max().target().vars(0) == var &&
12079 std::abs(other_ct.lin_max().target().coeffs(0)) == 1 &&
12080 IsAffineIntAbs(other_ct)) {
12081 context_->UpdateRuleStats("linear1: transferred from abs(X) to X");
12082 const LinearExpressionProto& target = other_ct.lin_max().target();
12083 const LinearExpressionProto& expr = other_ct.lin_max().exprs(0);
12084 transfer_f = [target = target, expr = expr](const Domain& implied) {
12085 Domain target_domain =
12086 implied.ContinuousMultiplicationBy(target.coeffs(0))
12087 .AdditionWith(Domain(target.offset()));
12088 target_domain = target_domain.IntersectionWith(
12089 Domain(0, std::numeric_limits<int64_t>::max()));
12090
12091 // We have target = abs(expr).
12092 const Domain expr_domain =
12093 target_domain.UnionWith(target_domain.Negation());
12094 const Domain new_domain = expr_domain.AdditionWith(Domain(-expr.offset()))
12095 .InverseMultiplicationBy(expr.coeffs(0));
12096 return std::make_pair(expr.vars(0), new_domain);
12097 };
12098 }
12099
12100 if (transfer_f == nullptr) {
12101 context_->UpdateRuleStats(
12102 "TODO linear1: appear in only one extra 2-var constraint");
12103 return;
12104 }
12105
12106 // Applies transfer_f to all linear1.
12107 std::sort(to_rewrite.begin(), to_rewrite.end());
12108 const Domain var_domain = context_->DomainOf(var);
12109 for (const int c : to_rewrite) {
12110 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12111 if (ct->linear().vars(0) != var || ct->linear().coeffs(0) != 1) {
12112 // This shouldn't happen.
12113 LOG(INFO) << "Aborted in MaybeTransferLinear1ToAnotherVariable()";
12114 return;
12115 }
12116
12117 const Domain implied =
12118 var_domain.IntersectionWith(ReadDomainFromProto(ct->linear()));
12119 auto [new_var, new_domain] = transfer_f(implied);
12120 const Domain current = context_->DomainOf(new_var);
12121 new_domain = new_domain.IntersectionWith(current);
12122 if (new_domain.IsEmpty()) {
12123 if (!MarkConstraintAsFalse(ct)) return;
12124 } else if (new_domain == current) {
12125 ct->Clear();
12126 } else {
12127 ct->mutable_linear()->set_vars(0, new_var);
12128 FillDomainInProto(new_domain, ct->mutable_linear());
12129 }
12130 context_->UpdateConstraintVariableUsage(c);
12131 }
12132
12133 // Copy other_ct to the mapping model and delete var!
12134 context_->NewMappingConstraint(other_ct, __FILE__, __LINE__);
12135 context_->working_model->mutable_constraints(other_c)->Clear();
12136 context_->UpdateConstraintVariableUsage(other_c);
12137 context_->MarkVariableAsRemoved(var);
12138}
12139
12140// TODO(user): We can still remove the variable even if we want to keep
12141// all feasible solutions for the cases when we have a full encoding.
12142// Similarly this shouldn't break symmetry, but we do need to do it for all
12143// symmetric variable at once.
12144//
12145// TODO(user): In fixed search, we disable this rule because we don't update
12146// the search strategy, but for some strategy we could.
12147//
12148// TODO(user): The hint might get lost if the encoding was created during
12149// the presolve.
12150void CpModelPresolver::ProcessVariableOnlyUsedInEncoding(int var) {
12151 if (context_->ModelIsUnsat()) return;
12152 if (context_->params().keep_all_feasible_solutions_in_presolve()) return;
12153 if (context_->params().keep_symmetry_in_presolve()) return;
12154 if (context_->IsFixed(var)) return;
12155 if (context_->VariableWasRemoved(var)) return;
12156 if (context_->CanBeUsedAsLiteral(var)) return;
12157 if (context_->params().search_branching() == SatParameters::FIXED_SEARCH) {
12158 return;
12159 }
12160
12161 if (!context_->VariableIsOnlyUsedInEncodingAndMaybeInObjective(var)) {
12162 if (context_->VariableIsOnlyUsedInLinear1AndOneExtraConstraint(var)) {
12163 MaybeTransferLinear1ToAnotherVariable(var);
12164 return;
12165 }
12166 return;
12167 }
12168
12169 // If a variable var only appear in enf => var \in domain and in the
12170 // objective, we can remove its costs and the variable/constraint by
12171 // transferring part of the cost to the enforcement.
12172 //
12173 // More generally, we can reduce the domain to just two values. Later this
12174 // will be replaced by a Boolean, and the equivalence to the enforcement
12175 // literal will be added if it is unique.
12176 //
12177 // TODO(user): maybe we should do more here rather than delaying some
12178 // reduction. But then it is more code.
12179 if (context_->VariableWithCostIsUniqueAndRemovable(var)) {
12180 int unique_c = -1;
12181 for (const int c : context_->VarToConstraints(var)) {
12182 if (c < 0) continue;
12183 CHECK_EQ(unique_c, -1);
12184 unique_c = c;
12185 }
12186 CHECK_NE(unique_c, -1);
12187 const ConstraintProto& ct = context_->working_model->constraints(unique_c);
12188 const int64_t cost = context_->ObjectiveCoeff(var);
12189 if (ct.linear().vars(0) == var) {
12190 const Domain implied = ReadDomainFromProto(ct.linear())
12191 .InverseMultiplicationBy(ct.linear().coeffs(0))
12192 .IntersectionWith(context_->DomainOf(var));
12193 if (implied.IsEmpty()) {
12194 if (!MarkConstraintAsFalse(
12195 context_->working_model->mutable_constraints(unique_c))) {
12196 return;
12197 }
12198 context_->UpdateConstraintVariableUsage(unique_c);
12199 return;
12200 }
12201
12202 int64_t value1, value2;
12203 if (cost == 0) {
12204 context_->UpdateRuleStats("variables: fix singleton var in linear1");
12205 return (void)context_->IntersectDomainWith(var, Domain(implied.Min()));
12206 } else if (cost > 0) {
12207 value1 = context_->MinOf(var);
12208 value2 = implied.Min();
12209 } else {
12210 value1 = context_->MaxOf(var);
12211 value2 = implied.Max();
12212 }
12213
12214 // Nothing else to do in this case, the constraint will be reduced to
12215 // a pure Boolean constraint later.
12216 context_->UpdateRuleStats("variables: reduced domain to two values");
12217 // If the hint enforces `ct`, then the hint of `var` must be in the
12218 // implied domain. In any case its new value must not increase the
12219 // objective value (the objective domain is non-constraining, but this
12220 // only guarantees that `var` can freely *decrease* the objective). The
12221 // code below ensures this (`value2` is the 'cheapest' value the implied
12222 // domain, and `value1` the cheapest value in the variable's domain).
12223 solution_crush_.SetVarToConditionalValue(var, ct.enforcement_literal(),
12224 value2, value1);
12225 return (void)context_->IntersectDomainWith(
12226 var, Domain::FromValues({value1, value2}));
12227 }
12228 }
12229
12230 // We can currently only deal with the case where all encoding constraint
12231 // are of the form literal => var ==/!= value.
12232 // If they are more complex linear1 involved, we just abort.
12233 //
12234 // TODO(user): Also deal with the case all >= or <= where we can add a
12235 // serie of implication between all involved literals.
12236 absl::flat_hash_set<int64_t> values_set;
12237 absl::flat_hash_map<int64_t, std::vector<int>> value_to_equal_literals;
12238 absl::flat_hash_map<int64_t, std::vector<int>> value_to_not_equal_literals;
12239 bool abort = false;
12240 for (const int c : context_->VarToConstraints(var)) {
12241 if (c < 0) continue;
12242 const ConstraintProto& ct = context_->working_model->constraints(c);
12243 CHECK_EQ(ct.constraint_case(), ConstraintProto::kLinear);
12244 CHECK_EQ(ct.linear().vars().size(), 1);
12245 int64_t coeff = ct.linear().coeffs(0);
12246 if (std::abs(coeff) != 1 || ct.enforcement_literal().size() != 1) {
12247 abort = true;
12248 break;
12249 }
12250 if (!RefIsPositive(ct.linear().vars(0))) coeff *= 1;
12251 const int var = PositiveRef(ct.linear().vars(0));
12252 const Domain var_domain = context_->DomainOf(var);
12253 const Domain rhs = ReadDomainFromProto(ct.linear())
12255 .IntersectionWith(var_domain);
12256 if (rhs.IsEmpty()) {
12257 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
12258 return;
12259 }
12260 return;
12261 } else if (rhs.IsFixed()) {
12262 if (!var_domain.Contains(rhs.FixedValue())) {
12263 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
12264 return;
12265 }
12266 } else {
12267 values_set.insert(rhs.FixedValue());
12268 value_to_equal_literals[rhs.FixedValue()].push_back(
12269 ct.enforcement_literal(0));
12270 }
12271 } else {
12272 const Domain complement = var_domain.IntersectionWith(rhs.Complement());
12273 if (complement.IsEmpty()) {
12274 // TODO(user): This should be dealt with elsewhere.
12275 abort = true;
12276 break;
12277 }
12278 if (complement.IsFixed()) {
12279 if (var_domain.Contains(complement.FixedValue())) {
12280 values_set.insert(complement.FixedValue());
12281 value_to_not_equal_literals[complement.FixedValue()].push_back(
12282 ct.enforcement_literal(0));
12283 }
12284 } else {
12285 abort = true;
12286 break;
12287 }
12288 }
12289 }
12290 if (abort) {
12291 context_->UpdateRuleStats("TODO variables: only used in linear1.");
12292 return;
12293 } else if (value_to_not_equal_literals.empty() &&
12294 value_to_equal_literals.empty()) {
12295 // This is just a variable not used anywhere, it should be removed by
12296 // another part of the presolve.
12297 return;
12298 }
12299
12300 // For determinism, sort all the encoded values first.
12301 std::vector<int64_t> encoded_values(values_set.begin(), values_set.end());
12302 std::sort(encoded_values.begin(), encoded_values.end());
12303 CHECK(!encoded_values.empty());
12304 const bool is_fully_encoded =
12305 encoded_values.size() == context_->DomainOf(var).Size();
12306
12307 // Link all Boolean in our linear1 to the encoding literals. Note that we
12308 // should hopefully already have detected such literal before and this
12309 // should add trivial implications.
12310 for (const int64_t v : encoded_values) {
12311 const int encoding_lit = context_->GetOrCreateVarValueEncoding(var, v);
12312 const auto eq_it = value_to_equal_literals.find(v);
12313 if (eq_it != value_to_equal_literals.end()) {
12314 absl::c_sort(eq_it->second);
12315 for (const int lit : eq_it->second) {
12316 context_->AddImplication(lit, encoding_lit);
12317 }
12318 }
12319 const auto neq_it = value_to_not_equal_literals.find(v);
12320 if (neq_it != value_to_not_equal_literals.end()) {
12321 absl::c_sort(neq_it->second);
12322 for (const int lit : neq_it->second) {
12323 context_->AddImplication(lit, NegatedRef(encoding_lit));
12324 }
12325 }
12326 }
12327 context_->UpdateNewConstraintsVariableUsage();
12328
12329 // This is the set of other values.
12330 Domain other_values;
12331 if (!is_fully_encoded) {
12332 other_values = context_->DomainOf(var).IntersectionWith(
12333 Domain::FromValues(encoded_values).Complement());
12334 }
12335
12336 // Update the objective if needed. Note that this operation can fail if
12337 // the new expression result in potential overflow.
12338 if (context_->VarToConstraints(var).contains(kObjectiveConstraint)) {
12339 int64_t min_value;
12340 const int64_t obj_coeff = context_->ObjectiveMap().at(var);
12341 if (is_fully_encoded) {
12342 // We substract the min_value from all coefficients.
12343 // This should reduce the objective size and helps with the bounds.
12344 min_value =
12345 obj_coeff > 0 ? encoded_values.front() : encoded_values.back();
12346 } else {
12347 // Tricky: We cannot just choose an arbitrary value if the objective has
12348 // a restrictive domain!
12349 if (context_->ObjectiveDomainIsConstraining() &&
12350 !other_values.IsFixed()) {
12351 context_->UpdateRuleStats(
12352 "TODO variables: only used in objective and in encoding");
12353 return;
12354 }
12355
12356 // Tricky: If the variable is not fully encoded, then when all
12357 // partial encoding literal are false, it must take the "best" value
12358 // in other_values. That depend on the sign of the objective coeff.
12359 //
12360 // We also restrict other value so that the postsolve code below
12361 // will fix the variable to the correct value when this happen.
12362 other_values =
12363 Domain(obj_coeff > 0 ? other_values.Min() : other_values.Max());
12364 min_value = other_values.FixedValue();
12365 }
12366
12367 // Checks for overflow before trying to substitute the variable in the
12368 // objective.
12369 int64_t accumulated = std::abs(min_value);
12370 for (const int64_t value : encoded_values) {
12371 accumulated = CapAdd(accumulated, std::abs(CapSub(value, min_value)));
12372 if (accumulated == std::numeric_limits<int64_t>::max()) {
12373 context_->UpdateRuleStats(
12374 "TODO variables: only used in objective and in encoding");
12375 return;
12376 }
12377 }
12378
12379 ConstraintProto encoding_ct;
12380 LinearConstraintProto* linear = encoding_ct.mutable_linear();
12381 const int64_t coeff_in_equality = -1;
12382 linear->add_vars(var);
12383 linear->add_coeffs(coeff_in_equality);
12384
12385 linear->add_domain(-min_value);
12386 linear->add_domain(-min_value);
12387 for (const int64_t value : encoded_values) {
12388 if (value == min_value) continue;
12389 const int enf = context_->GetOrCreateVarValueEncoding(var, value);
12390 const int64_t coeff = value - min_value;
12391 if (RefIsPositive(enf)) {
12392 linear->add_vars(enf);
12393 linear->add_coeffs(coeff);
12394 } else {
12395 // (1 - var) * coeff;
12396 linear->set_domain(0, encoding_ct.linear().domain(0) - coeff);
12397 linear->set_domain(1, encoding_ct.linear().domain(1) - coeff);
12398 linear->add_vars(PositiveRef(enf));
12399 linear->add_coeffs(-coeff);
12400 }
12401 }
12402 if (!context_->SubstituteVariableInObjective(var, coeff_in_equality,
12403 encoding_ct)) {
12404 context_->UpdateRuleStats(
12405 "TODO variables: only used in objective and in encoding");
12406 return;
12407 }
12408 context_->UpdateRuleStats(
12409 "variables: only used in objective and in encoding");
12410 } else {
12411 context_->UpdateRuleStats("variables: only used in encoding");
12412 }
12413
12414 // Clear all involved constraint.
12415 {
12416 std::vector<int> to_clear;
12417 for (const int c : context_->VarToConstraints(var)) {
12418 if (c >= 0) to_clear.push_back(c);
12419 }
12420 absl::c_sort(to_clear);
12421 for (const int c : to_clear) {
12422 if (c < 0) continue;
12423 context_->working_model->mutable_constraints(c)->Clear();
12424 context_->UpdateConstraintVariableUsage(c);
12425 }
12426 }
12427
12428 // See below. This is used for the mapping constraint.
12429 int64_t special_value = 0;
12430
12431 // This must be done after we removed all the constraint containing var.
12432 ConstraintProto* new_ct = context_->working_model->add_constraints();
12433 if (is_fully_encoded) {
12434 // The encoding is full: add an exactly one.
12435 for (const int64_t value : encoded_values) {
12436 new_ct->mutable_exactly_one()->add_literals(
12437 context_->GetOrCreateVarValueEncoding(var, value));
12438 }
12439 PresolveExactlyOne(new_ct);
12440 } else {
12441 // Add an at most one.
12442 for (const int64_t value : encoded_values) {
12443 new_ct->mutable_at_most_one()->add_literals(
12444 context_->GetOrCreateVarValueEncoding(var, value));
12445 }
12446 PresolveAtMostOne(new_ct);
12447
12448 // Pick a "special_value" that our variable can take when all the bi are
12449 // false.
12450 special_value = other_values.SmallestValue();
12451 }
12452 if (context_->ModelIsUnsat()) return;
12453
12454 // To simplify the postsolve, we output a single constraint to infer X from
12455 // the bi: X = sum bi * (Vi - special_value) + special_value
12456 ConstraintProto* mapping_ct =
12457 context_->NewMappingConstraint(__FILE__, __LINE__);
12458 mapping_ct->mutable_linear()->add_vars(var);
12459 mapping_ct->mutable_linear()->add_coeffs(1);
12460 int64_t offset = special_value;
12461 for (const int64_t value : encoded_values) {
12462 const int literal = context_->GetOrCreateVarValueEncoding(var, value);
12463 const int64_t coeff = (value - special_value);
12464 if (RefIsPositive(literal)) {
12465 mapping_ct->mutable_linear()->add_vars(literal);
12466 mapping_ct->mutable_linear()->add_coeffs(-coeff);
12467 } else {
12468 offset += coeff;
12469 mapping_ct->mutable_linear()->add_vars(PositiveRef(literal));
12470 mapping_ct->mutable_linear()->add_coeffs(coeff);
12471 }
12472 }
12473 mapping_ct->mutable_linear()->add_domain(offset);
12474 mapping_ct->mutable_linear()->add_domain(offset);
12475
12476 context_->UpdateNewConstraintsVariableUsage();
12477 context_->MarkVariableAsRemoved(var);
12478}
12479
12480void CpModelPresolver::TryToSimplifyDomain(int var) {
12481 DCHECK(RefIsPositive(var));
12482 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
12483 if (context_->ModelIsUnsat()) return;
12484 if (context_->IsFixed(var)) return;
12485 if (context_->VariableWasRemoved(var)) return;
12486 if (context_->VariableIsNotUsedAnymore(var)) return;
12487
12488 const AffineRelation::Relation r = context_->GetAffineRelation(var);
12489 if (r.representative != var) return;
12490
12491 // Only process discrete domain.
12492 const Domain& domain = context_->DomainOf(var);
12493
12494 // Special case for non-Boolean domain of size 2.
12495 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
12496 context_->CanonicalizeDomainOfSizeTwo(var);
12497 return;
12498 }
12499
12500 if (domain.NumIntervals() != domain.Size()) return;
12501
12502 const int64_t var_min = domain.Min();
12503 int64_t gcd = domain[1].start - var_min;
12504 for (int index = 2; index < domain.NumIntervals(); ++index) {
12505 const ClosedInterval& i = domain[index];
12506 DCHECK_EQ(i.start, i.end);
12507 const int64_t shifted_value = i.start - var_min;
12508 DCHECK_GT(shifted_value, 0);
12509
12510 gcd = std::gcd(gcd, shifted_value);
12511 if (gcd == 1) break;
12512 }
12513 if (gcd == 1) return;
12514
12515 // This does all the work since var * 1 % gcd = var_min % gcd.
12516 context_->CanonicalizeAffineVariable(var, 1, gcd, var_min);
12517}
12518
12519// Adds all affine relations to our model for the variables that are still used.
12520void CpModelPresolver::EncodeAllAffineRelations() {
12521 int64_t num_added = 0;
12522 for (int var = 0; var < context_->working_model->variables_size(); ++var) {
12523 if (context_->IsFixed(var)) continue;
12524
12525 const AffineRelation::Relation r = context_->GetAffineRelation(var);
12526 if (r.representative == var) continue;
12527
12528 // TODO(user): It seems some affine relation are still removable at this
12529 // stage even though they should be removed inside PresolveToFixPoint().
12530 // Investigate. For now, we just remove such relations.
12531 if (context_->VariableIsNotUsedAnymore(var)) continue;
12532 if (!PresolveAffineRelationIfAny(var)) break;
12533 if (context_->VariableIsNotUsedAnymore(var)) continue;
12534 if (context_->IsFixed(var)) continue;
12535
12536 ++num_added;
12537 ConstraintProto* ct = context_->working_model->add_constraints();
12538 auto* arg = ct->mutable_linear();
12539 arg->add_vars(var);
12540 arg->add_coeffs(1);
12541 arg->add_vars(r.representative);
12542 arg->add_coeffs(-r.coeff);
12543 arg->add_domain(r.offset);
12544 arg->add_domain(r.offset);
12545 context_->UpdateNewConstraintsVariableUsage();
12546 }
12547
12548 // Now that we encoded all remaining affine relation with constraints, we
12549 // remove the special marker to have a proper constraint variable graph.
12550 context_->RemoveAllVariablesFromAffineRelationConstraint();
12551
12552 if (num_added > 0) {
12553 SOLVER_LOG(logger_, num_added, " affine relations still in the model.");
12554 }
12555}
12556
12557// Presolve a variable in relation with its representative.
12558bool CpModelPresolver::PresolveAffineRelationIfAny(int var) {
12559 const AffineRelation::Relation r = context_->GetAffineRelation(var);
12560 if (r.representative == var) return true;
12561
12562 // Propagate domains.
12563 if (!context_->PropagateAffineRelation(var)) return false;
12564
12565 // Once an affine relation is detected, the variables should be added to
12566 // the kAffineRelationConstraint. The only way to be unmarked is if the
12567 // variable do not appear in any other constraint and is not a representative,
12568 // in which case it should never be added back.
12569 if (context_->IsFixed(var)) return true;
12570 DCHECK(context_->VarToConstraints(var).contains(kAffineRelationConstraint));
12571 DCHECK(!context_->VariableIsNotUsedAnymore(r.representative));
12572
12573 // If var is no longer used, remove. Note that we can always do that since we
12574 // propagated the domain above and so we can find a feasible value for a for
12575 // any value of the representative.
12576 context_->RemoveNonRepresentativeAffineVariableIfUnused(var);
12577 return true;
12578}
12579
12580// Re-add to the queue the constraints that touch a variable that changed.
12581bool CpModelPresolver::ProcessChangedVariables(std::vector<bool>* in_queue,
12582 std::deque<int>* queue) {
12583 // TODO(user): Avoid reprocessing the constraints that changed the domain?
12584 if (context_->ModelIsUnsat()) return false;
12585 if (time_limit_->LimitReached()) return false;
12586 in_queue->resize(context_->working_model->constraints_size(), false);
12587 const auto& vector_that_can_grow_during_iter =
12588 context_->modified_domains.PositionsSetAtLeastOnce();
12589 for (int i = 0; i < vector_that_can_grow_during_iter.size(); ++i) {
12590 const int v = vector_that_can_grow_during_iter[i];
12591 context_->modified_domains.Clear(v);
12592 if (context_->VariableIsNotUsedAnymore(v)) continue;
12593 if (context_->ModelIsUnsat()) return false;
12594 if (!PresolveAffineRelationIfAny(v)) return false;
12595 if (context_->VariableIsNotUsedAnymore(v)) continue;
12596
12597 TryToSimplifyDomain(v);
12598
12599 // TODO(user): Integrate these with TryToSimplifyDomain().
12600 if (context_->ModelIsUnsat()) return false;
12601 context_->UpdateNewConstraintsVariableUsage();
12602
12603 if (!context_->CanonicalizeOneObjectiveVariable(v)) return false;
12604
12605 in_queue->resize(context_->working_model->constraints_size(), false);
12606 for (const int c : context_->VarToConstraints(v)) {
12607 if (c >= 0 && !(*in_queue)[c]) {
12608 (*in_queue)[c] = true;
12609 queue->push_back(c);
12610 }
12611 }
12612 }
12613 context_->modified_domains.SparseClearAll();
12614
12615 // Make sure the order is deterministic! because var_to_constraints[]
12616 // order changes from one run to the next.
12617 std::sort(queue->begin(), queue->end());
12618 return !queue->empty();
12619}
12620
12621void CpModelPresolver::PresolveToFixPoint() {
12622 if (time_limit_->LimitReached()) return;
12623 if (context_->ModelIsUnsat()) return;
12624 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12625
12626 // We do at most 2 tests per PresolveToFixPoint() call since this can be slow.
12627 int num_dominance_tests = 0;
12628 int num_dual_strengthening = 0;
12629
12630 // Limit on number of operations.
12631 const int64_t max_num_operations =
12632 context_->params().debug_max_num_presolve_operations() > 0
12633 ? context_->params().debug_max_num_presolve_operations()
12634 : std::numeric_limits<int64_t>::max();
12635
12636 // This is used for constraint having unique variables in them (i.e. not
12637 // appearing anywhere else) to not call the presolve more than once for this
12638 // reason.
12639 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
12640
12641 // The queue of "active" constraints, initialized to the non-empty ones.
12642 std::vector<bool> in_queue(context_->working_model->constraints_size(),
12643 false);
12644 std::deque<int> queue;
12645 for (int c = 0; c < in_queue.size(); ++c) {
12646 if (context_->working_model->constraints(c).constraint_case() !=
12647 ConstraintProto::CONSTRAINT_NOT_SET) {
12648 in_queue[c] = true;
12649 queue.push_back(c);
12650 }
12651 }
12652
12653 // When thinking about how the presolve works, it seems like a good idea to
12654 // process the "simple" constraints first in order to be more efficient.
12655 // In September 2019, experiment on the flatzinc problems shows no changes in
12656 // the results. We should actually count the number of rules triggered.
12657 if (context_->params().permute_presolve_constraint_order()) {
12658 std::shuffle(queue.begin(), queue.end(), *context_->random());
12659 } else {
12660 std::sort(queue.begin(), queue.end(), [this](int a, int b) {
12661 const int score_a = context_->ConstraintToVars(a).size();
12662 const int score_b = context_->ConstraintToVars(b).size();
12663 return score_a < score_b || (score_a == score_b && a < b);
12664 });
12665 }
12666
12667 // We put a hard limit on the number of loop to prevent some corner case with
12668 // propagation loops. Note that the limit is quite high so it shouldn't really
12669 // be reached in most situation.
12670 int num_loops = 0;
12671 constexpr int kMaxNumLoops = 1000;
12672 for (; num_loops < kMaxNumLoops && !queue.empty(); ++num_loops) {
12673 if (time_limit_->LimitReached()) break;
12674 if (context_->ModelIsUnsat()) break;
12675 if (context_->num_presolve_operations > max_num_operations) break;
12676
12677 // Empty the queue of single constraint presolve.
12678 while (!queue.empty() && !context_->ModelIsUnsat()) {
12679 if (time_limit_->LimitReached()) break;
12680 if (context_->num_presolve_operations > max_num_operations) break;
12681 const int c = queue.front();
12682 in_queue[c] = false;
12683 queue.pop_front();
12684
12685 const int old_num_constraint =
12686 context_->working_model->constraints_size();
12687 const bool changed = PresolveOneConstraint(c);
12688 if (context_->ModelIsUnsat()) {
12689 SOLVER_LOG(
12690 logger_, "Unsat after presolving constraint #", c,
12691 " (warning, dump might be inconsistent): ",
12692 ProtobufShortDebugString(context_->working_model->constraints(c)));
12693 }
12694
12695 // Add to the queue any newly created constraints.
12696 const int new_num_constraints =
12697 context_->working_model->constraints_size();
12698 if (new_num_constraints > old_num_constraint) {
12699 context_->UpdateNewConstraintsVariableUsage();
12700 in_queue.resize(new_num_constraints, true);
12701 for (int c = old_num_constraint; c < new_num_constraints; ++c) {
12702 queue.push_back(c);
12703 }
12704 }
12705
12706 // TODO(user): Is seems safer to remove the changed Boolean and maybe
12707 // just compare the number of applied "rules" before/after.
12708 if (changed) {
12709 context_->UpdateConstraintVariableUsage(c);
12710 }
12711 }
12712
12713 if (context_->ModelIsUnsat()) return;
12714
12715 in_queue.resize(context_->working_model->constraints_size(), false);
12716 const auto& vector_that_can_grow_during_iter =
12717 context_->var_with_reduced_small_degree.PositionsSetAtLeastOnce();
12718 for (int i = 0; i < vector_that_can_grow_during_iter.size(); ++i) {
12719 const int v = vector_that_can_grow_during_iter[i];
12720 if (context_->VariableIsNotUsedAnymore(v)) continue;
12721
12722 // Remove the variable from the set to allow it to be pushed again.
12723 // This is necessary since a few affine logic needs to add the same
12724 // variable back to a second pass of processing.
12725 context_->var_with_reduced_small_degree.Clear(v);
12726
12727 // Make sure all affine relations are propagated.
12728 // This also remove the relation if the degree is now one.
12729 if (context_->ModelIsUnsat()) return;
12730 if (!PresolveAffineRelationIfAny(v)) return;
12731
12732 const int degree = context_->VarToConstraints(v).size();
12733 if (degree == 0) continue;
12734 if (degree == 2) LookAtVariableWithDegreeTwo(v);
12735 if (degree == 2 || degree == 3) {
12736 // Tricky: this function can add new constraint.
12737 ProcessVariableInTwoAtMostOrExactlyOne(v);
12738 in_queue.resize(context_->working_model->constraints_size(), false);
12739 continue;
12740 }
12741
12742 // Re-add to the queue constraints that have unique variables. Note that
12743 // to not enter an infinite loop, we call each (var, constraint) pair at
12744 // most once.
12745 if (degree != 1) continue;
12746 const int c = *context_->VarToConstraints(v).begin();
12747 if (c < 0) continue;
12748
12749 // Note that to avoid bad complexity in problem like a TSP with just one
12750 // big constraint. we mark all the singleton variables of a constraint
12751 // even if this constraint is already in the queue.
12752 if (var_constraint_pair_already_called.contains(
12753 std::pair<int, int>(v, c))) {
12754 continue;
12755 }
12756 var_constraint_pair_already_called.insert({v, c});
12757
12758 if (!in_queue[c]) {
12759 in_queue[c] = true;
12760 queue.push_back(c);
12761 }
12762 }
12763 context_->var_with_reduced_small_degree.SparseClearAll();
12764
12765 if (ProcessChangedVariables(&in_queue, &queue)) continue;
12766
12767 DCHECK(!context_->HasUnusedAffineVariable());
12768
12769 // Deal with integer variable only appearing in an encoding.
12770 for (int v = 0; v < context_->working_model->variables().size(); ++v) {
12771 ProcessVariableOnlyUsedInEncoding(v);
12772 }
12773 if (ProcessChangedVariables(&in_queue, &queue)) continue;
12774
12775 // Perform dual reasoning.
12776 //
12777 // TODO(user): We can support assumptions but we need to not cut them out
12778 // of the feasible region.
12779 if (context_->params().keep_all_feasible_solutions_in_presolve()) break;
12780 if (!context_->working_model->assumptions().empty()) break;
12781
12782 // Starts by the "faster" algo that exploit variables that can move freely
12783 // in one direction. Or variables that are just blocked by one constraint in
12784 // one direction.
12785 for (int i = 0; i < 10; ++i) {
12786 if (context_->ModelIsUnsat()) return;
12787 ++num_dual_strengthening;
12788 DualBoundStrengthening dual_bound_strengthening;
12789 ScanModelForDualBoundStrengthening(*context_, &dual_bound_strengthening);
12790
12791 // TODO(user): Make sure that if we fix one variable, we fix its full
12792 // symmetric orbit. There should be no reason that we don't do that
12793 // though.
12794 if (!dual_bound_strengthening.Strengthen(context_)) return;
12795 if (ProcessChangedVariables(&in_queue, &queue)) break;
12796
12797 // It is possible we deleted some constraint, but the queue is empty.
12798 // In this case we redo a pass of dual bound strenghtening as we might
12799 // perform more reduction.
12800 //
12801 // TODO(user): maybe we could reach fix point directly?
12802 if (dual_bound_strengthening.NumDeletedConstraints() == 0) break;
12803 }
12804 if (!queue.empty()) continue;
12805
12806 // Dominance reasoning will likely break symmetry.
12807 // TODO(user): We can apply the one that do not break any though, or the
12808 // operations that are safe.
12809 if (context_->params().keep_symmetry_in_presolve()) break;
12810
12811 // Detect & exploit dominance between variables.
12812 // TODO(user): This can be slow, remove from fix-pint loop?
12813 if (num_dominance_tests++ < 2) {
12814 if (context_->ModelIsUnsat()) return;
12815 PresolveTimer timer("DetectDominanceRelations", logger_, time_limit_);
12816 VarDomination var_dom;
12817 ScanModelForDominanceDetection(*context_, &var_dom);
12818 if (!ExploitDominanceRelations(var_dom, context_)) return;
12819 if (ProcessChangedVariables(&in_queue, &queue)) continue;
12820 }
12821 }
12822
12823 if (context_->ModelIsUnsat()) return;
12824
12825 // Second "pass" for transformation better done after all of the above and
12826 // that do not need a fix-point loop.
12827 //
12828 // TODO(user): Also add deductions achieved during probing!
12829 //
12830 // TODO(user): ideally we should "wake-up" any constraint that contains an
12831 // absent interval in the main propagation loop above. But we currently don't
12832 // maintain such list.
12833 const int num_constraints = context_->working_model->constraints_size();
12834 for (int c = 0; c < num_constraints; ++c) {
12835 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12836 switch (ct->constraint_case()) {
12837 case ConstraintProto::kNoOverlap:
12838 // Filter out absent intervals.
12839 if (PresolveNoOverlap(ct)) {
12840 context_->UpdateConstraintVariableUsage(c);
12841 }
12842 break;
12843 case ConstraintProto::kNoOverlap2D:
12844 // Filter out absent intervals.
12845 if (PresolveNoOverlap2D(c, ct)) {
12846 context_->UpdateConstraintVariableUsage(c);
12847 }
12848 break;
12849 case ConstraintProto::kCumulative:
12850 // Filter out absent intervals.
12851 if (PresolveCumulative(ct)) {
12852 context_->UpdateConstraintVariableUsage(c);
12853 }
12854 break;
12855 case ConstraintProto::kBoolOr: {
12856 // Try to infer domain reductions from clauses and the saved "implies in
12857 // domain" relations.
12858 for (const auto& pair :
12859 context_->deductions.ProcessClause(ct->bool_or().literals())) {
12860 bool modified = false;
12861 if (!context_->IntersectDomainWith(pair.first, pair.second,
12862 &modified)) {
12863 return;
12864 }
12865 if (modified) {
12866 context_->UpdateRuleStats("deductions: reduced variable domain");
12867 }
12868 }
12869 break;
12870 }
12871 default:
12872 break;
12873 }
12874 }
12875
12876 timer.AddCounter("num_loops", num_loops);
12877 timer.AddCounter("num_dual_strengthening", num_dual_strengthening);
12878 context_->deductions.MarkProcessingAsDoneForNow();
12879}
12880
12881// TODO(user): Use better heuristic?
12882//
12883// TODO(user): This is similar to what Bounded variable addition (BVA) does.
12884// By adding a new variable, enforcement => literals becomes
12885// enforcement => x => literals, and we have one clause + #literals implication
12886// instead of #literals clauses. What BVA does in addition is to use the same
12887// x for other enforcement list if the rhs literals are shared.
12888void CpModelPresolver::MergeClauses() {
12889 if (context_->ModelIsUnsat()) return;
12890 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12891
12892 // Constraint index that changed.
12893 std::vector<int> to_clean;
12894
12895 // Keep a map from negation of enforcement_literal => bool_and ct index.
12896 absl::flat_hash_map<uint64_t, int> bool_and_map;
12897
12898 // First loop over the constraint:
12899 // - Register already existing bool_and.
12900 // - score at_most_ones literals.
12901 // - Record bool_or.
12902 const int num_variables = context_->working_model->variables_size();
12903 std::vector<int> bool_or_indices;
12904 std::vector<int64_t> literal_score(2 * num_variables, 0);
12905 const auto get_index = [](int ref) {
12906 return 2 * PositiveRef(ref) + (RefIsPositive(ref) ? 0 : 1);
12907 };
12908
12909 int64_t num_collisions = 0;
12910 int64_t num_merges = 0;
12911 int64_t num_saved_literals = 0;
12912 ClauseWithOneMissingHasher hasher(*context_->random());
12913 const int num_constraints = context_->working_model->constraints_size();
12914 for (int c = 0; c < num_constraints; ++c) {
12915 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12916 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
12917 if (ct->enforcement_literal().size() > 1) {
12918 // We need to sort the negated literals.
12919 std::sort(ct->mutable_enforcement_literal()->begin(),
12920 ct->mutable_enforcement_literal()->end(),
12921 std::greater<int>());
12922 const auto [it, inserted] = bool_and_map.insert(
12923 {hasher.HashOfNegatedLiterals(ct->enforcement_literal()), c});
12924 if (inserted) {
12925 to_clean.push_back(c);
12926 } else {
12927 // See if this is a true duplicate. If yes, merge rhs.
12928 ConstraintProto* other_ct =
12929 context_->working_model->mutable_constraints(it->second);
12930 const absl::Span<const int> s1(ct->enforcement_literal());
12931 const absl::Span<const int> s2(other_ct->enforcement_literal());
12932 if (s1 == s2) {
12933 context_->UpdateRuleStats(
12934 "bool_and: merged constraints with same enforcement");
12935 other_ct->mutable_bool_and()->mutable_literals()->Add(
12936 ct->bool_and().literals().begin(),
12937 ct->bool_and().literals().end());
12938 ct->Clear();
12939 context_->UpdateConstraintVariableUsage(c);
12940 }
12941 }
12942 }
12943 continue;
12944 }
12945 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
12946 const int size = ct->at_most_one().literals().size();
12947 for (const int ref : ct->at_most_one().literals()) {
12948 literal_score[get_index(ref)] += size;
12949 }
12950 continue;
12951 }
12952 if (ct->constraint_case() == ConstraintProto::kExactlyOne) {
12953 const int size = ct->exactly_one().literals().size();
12954 for (const int ref : ct->exactly_one().literals()) {
12955 literal_score[get_index(ref)] += size;
12956 }
12957 continue;
12958 }
12959
12960 if (ct->constraint_case() != ConstraintProto::kBoolOr) continue;
12961
12962 // Both of these test shouldn't happen, but we have them to be safe.
12963 if (!ct->enforcement_literal().empty()) continue;
12964 if (ct->bool_or().literals().size() <= 2) continue;
12965
12966 std::sort(ct->mutable_bool_or()->mutable_literals()->begin(),
12967 ct->mutable_bool_or()->mutable_literals()->end());
12968 hasher.RegisterClause(c, ct->bool_or().literals());
12969 bool_or_indices.push_back(c);
12970 }
12971
12972 for (const int c : bool_or_indices) {
12973 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12974
12975 bool merged = false;
12976 timer.TrackSimpleLoop(ct->bool_or().literals().size());
12977 if (timer.WorkLimitIsReached()) break;
12978 for (const int ref : ct->bool_or().literals()) {
12979 const uint64_t hash = hasher.HashWithout(c, ref);
12980 const auto it = bool_and_map.find(hash);
12981 if (it != bool_and_map.end()) {
12982 ++num_collisions;
12983 const int base_c = it->second;
12984 auto* and_ct = context_->working_model->mutable_constraints(base_c);
12986 ct->bool_or().literals(), and_ct->enforcement_literal(), ref)) {
12987 ++num_merges;
12988 num_saved_literals += ct->bool_or().literals().size() - 1;
12989 merged = true;
12990 and_ct->mutable_bool_and()->add_literals(ref);
12991 ct->Clear();
12992 context_->UpdateConstraintVariableUsage(c);
12993 break;
12994 }
12995 }
12996 }
12997
12998 if (!merged) {
12999 // heuristic: take first literal whose negation has highest score.
13000 int best_ref = ct->bool_or().literals(0);
13001 int64_t best_score = literal_score[get_index(NegatedRef(best_ref))];
13002 for (const int ref : ct->bool_or().literals()) {
13003 const int64_t score = literal_score[get_index(NegatedRef(ref))];
13004 if (score > best_score) {
13005 best_ref = ref;
13006 best_score = score;
13007 }
13008 }
13009
13010 const uint64_t hash = hasher.HashWithout(c, best_ref);
13011 const auto [_, inserted] = bool_and_map.insert({hash, c});
13012 if (inserted) {
13013 to_clean.push_back(c);
13014 context_->tmp_literals.clear();
13015 for (const int lit : ct->bool_or().literals()) {
13016 if (lit == best_ref) continue;
13017 context_->tmp_literals.push_back(NegatedRef(lit));
13018 }
13019 ct->Clear();
13020 ct->mutable_enforcement_literal()->Assign(
13021 context_->tmp_literals.begin(), context_->tmp_literals.end());
13022 ct->mutable_bool_and()->add_literals(best_ref);
13023 }
13024 }
13025 }
13026
13027 // Retransform to bool_or bool_and with a single rhs.
13028 for (const int c : to_clean) {
13029 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13030 if (ct->bool_and().literals().size() > 1) {
13031 context_->UpdateConstraintVariableUsage(c);
13032 continue;
13033 }
13034
13035 // We have a single bool_and, lets transform it back to single bool_or.
13036 context_->tmp_literals.clear();
13037 context_->tmp_literals.push_back(ct->bool_and().literals(0));
13038 for (const int ref : ct->enforcement_literal()) {
13039 context_->tmp_literals.push_back(NegatedRef(ref));
13040 }
13041 ct->Clear();
13042 ct->mutable_bool_or()->mutable_literals()->Assign(
13043 context_->tmp_literals.begin(), context_->tmp_literals.end());
13044 }
13045
13046 timer.AddCounter("num_collisions", num_collisions);
13047 timer.AddCounter("num_merges", num_merges);
13048 timer.AddCounter("num_saved_literals", num_saved_literals);
13049}
13050
13051// =============================================================================
13052// Public API.
13053// =============================================================================
13054
13055CpSolverStatus PresolveCpModel(PresolveContext* context,
13056 std::vector<int>* postsolve_mapping) {
13057 CpModelPresolver presolver(context, postsolve_mapping);
13058 return presolver.Presolve();
13059}
13060
13061CpModelPresolver::CpModelPresolver(PresolveContext* context,
13062 std::vector<int>* postsolve_mapping)
13063 : postsolve_mapping_(postsolve_mapping),
13064 context_(context),
13065 solution_crush_(context->solution_crush()),
13066 logger_(context->logger()),
13067 time_limit_(context->time_limit()),
13068 interval_representative_(context->working_model->constraints_size(),
13069 IntervalConstraintHash{context->working_model},
13070 IntervalConstraintEq{context->working_model}) {}
13071
13072CpSolverStatus CpModelPresolver::InfeasibleStatus() {
13073 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13074 return CpSolverStatus::INFEASIBLE;
13075}
13076
13077// At the end of presolve, the mapping model is initialized to contains all
13078// the variable from the original model + the one created during presolve
13079// expand. It also contains the tightened domains.
13080namespace {
13081void InitializeMappingModelVariables(absl::Span<const Domain> domains,
13082 std::vector<int>* fixed_postsolve_mapping,
13083 CpModelProto* mapping_proto) {
13084 // Extend the fixed mapping to take into account all newly created variable
13085 // since the time it was constructed.
13086 int old_num_variables = mapping_proto->variables().size();
13087 while (fixed_postsolve_mapping->size() < domains.size()) {
13088 mapping_proto->add_variables();
13089 fixed_postsolve_mapping->push_back(old_num_variables++);
13090 DCHECK_EQ(old_num_variables, mapping_proto->variables().size());
13091 }
13092
13093 // Overwrite the domains.
13094 //
13095 // Note that if the fixed_postsolve_mapping was not null, the mapping model
13096 // should contains the original variable domains at the time the fixed mapping
13097 // was computed.
13098 for (int i = 0; i < domains.size(); ++i) {
13099 FillDomainInProto(domains[i], mapping_proto->mutable_variables(
13100 (*fixed_postsolve_mapping)[i]));
13101 }
13102
13103 // Remap the mapping proto.
13104 // We only deal with constraint here, do not touch the rest.
13105 //
13106 // TODO(user): Maybe we should have a real "postsolve" proto so we can
13107 // interleave postsolve "constraint" and remapping phase. This would allow to
13108 // do that in the middle of the presolve. But maybe this is not as impactful.
13109 auto mapping_function = [fixed_postsolve_mapping](int* ref) {
13110 const int image = (*fixed_postsolve_mapping)[PositiveRef(*ref)];
13111 CHECK_GE(image, 0);
13112 *ref = RefIsPositive(*ref) ? image : NegatedRef(image);
13113 };
13114 for (ConstraintProto& ct_ref : *mapping_proto->mutable_constraints()) {
13115 ApplyToAllVariableIndices(mapping_function, &ct_ref);
13116 ApplyToAllLiteralIndices(mapping_function, &ct_ref);
13117 }
13118}
13119} // namespace
13120
13121void CpModelPresolver::ExpandCpModelAndCanonicalizeConstraints() {
13122 const int num_constraints_before_expansion =
13123 context_->working_model->constraints_size();
13124 ExpandCpModel(context_);
13125 if (context_->ModelIsUnsat()) return;
13126
13127 // TODO(user): Make sure we can't have duplicate in these constraint.
13128 // These are due to ExpandCpModel() were we create such constraint with
13129 // duplicate. The problem is that some code assumes these are presolved
13130 // before being called.
13131 const int num_constraints = context_->working_model->constraints().size();
13132 for (int c = num_constraints_before_expansion; c < num_constraints; ++c) {
13133 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13134 const auto type = ct->constraint_case();
13135 if (type == ConstraintProto::kAtMostOne ||
13136 type == ConstraintProto::kExactlyOne) {
13137 if (PresolveOneConstraint(c)) {
13138 context_->UpdateConstraintVariableUsage(c);
13139 }
13140 if (context_->ModelIsUnsat()) return;
13141 } else if (type == ConstraintProto::kLinear) {
13142 if (CanonicalizeLinear(ct)) {
13143 context_->UpdateConstraintVariableUsage(c);
13144 }
13145 }
13146 }
13147}
13148
13149namespace {
13150
13151// Updates the solution hint in the proto with the crushed solution values.
13152void UpdateHintInProto(PresolveContext* context) {
13153 CpModelProto* proto = context->working_model;
13154 if (!proto->has_solution_hint()) return;
13155 if (context->ModelIsUnsat()) return;
13156
13157 SolutionCrush& crush = context->solution_crush();
13158 const int num_vars = context->working_model->variables().size();
13159 for (int i = 0; i < num_vars; ++i) {
13160 // If the initial hint is incomplete or infeasible, the crushed hint might
13161 // contain values outside of their respective domains (see SolutionCrush).
13162 crush.SetOrUpdateVarToDomain(i, context->DomainOf(i));
13163 }
13164 // If the time limit is reached, the presolved model might still contain
13165 // non-representative "affine" variables.
13166 for (int i = 0; i < num_vars; ++i) {
13167 const auto relation = context->GetAffineRelation(i);
13168 if (relation.representative != i) {
13169 crush.SetVarToLinearExpression(
13170 i, {{relation.representative, relation.coeff}}, relation.offset);
13171 }
13172 }
13173 crush.StoreSolutionAsHint(*proto);
13174}
13175
13176} // namespace
13177
13178// The presolve works as follow:
13179//
13180// First stage:
13181// We will process all active constraints until a fix point is reached. During
13182// this stage:
13183// - Variable will never be deleted, but their domain will be reduced.
13184// - Constraint will never be deleted (they will be marked as empty if needed).
13185// - New variables and new constraints can be added after the existing ones.
13186// - Constraints are added only when needed to the mapping_problem if they are
13187// needed during the postsolve.
13188//
13189// Second stage:
13190// - All the variables domain will be copied to the mapping_model.
13191// - Everything will be remapped so that only the variables appearing in some
13192// constraints will be kept and their index will be in [0, num_new_variables).
13193CpSolverStatus CpModelPresolver::Presolve() {
13194 context_->InitializeNewDomains();
13196 // If the objective is a floating point one, we scale it.
13197 //
13198 // TODO(user): We should probably try to delay this even more. For that we
13199 // just need to isolate more the "dual" reduction that usually need to look at
13200 // the objective.
13201 if (context_->working_model->has_floating_point_objective()) {
13202 context_->WriteVariableDomainsToProto();
13203 if (!ScaleFloatingPointObjective(context_->params(), logger_,
13204 context_->working_model)) {
13205 SOLVER_LOG(logger_,
13206 "The floating point objective cannot be scaled with enough "
13207 "precision");
13208 return CpSolverStatus::MODEL_INVALID;
13209 }
13210
13211 // At this point, we didn't create any new variables, so the integer
13212 // objective is in term of the orinal problem variables. We save it so that
13213 // we can expose to the user what exact objective we are actually
13214 // optimizing.
13215 *context_->mapping_model->mutable_objective() =
13216 context_->working_model->objective();
13217 }
13218
13219 // If there is a large proprotion of fixed variable, lets remap the model
13220 // before we start the actual presolve. This is useful for LNS in particular.
13221 //
13222 // fixed_postsolve_mapping[i] will contains the original index of the variable
13223 // that will be at position i after MaybeRemoveFixedVariables(). If the
13224 // mapping is left empty, it will be set to the identity mapping later by
13225 // InitializeMappingModelVariables().
13226 std::vector<int> fixed_postsolve_mapping;
13227 if (!MaybeRemoveFixedVariables(&fixed_postsolve_mapping)) {
13228 return InfeasibleStatus();
13229 }
13230
13231 // Initialize the initial context.working_model domains.
13232 // Initialize the objective and the constraint <-> variable graph.
13233 //
13234 // Note that we did some basic presolving during the first copy of the model.
13235 // This is important has initializing the constraint <-> variable graph can
13236 // be costly, so better to remove trivially feasible constraint for instance.
13237 context_->InitializeNewDomains();
13238 context_->LoadSolutionHint();
13239 context_->ReadObjectiveFromProto();
13240 if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
13241
13244 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13245
13246 // If presolve is false, just run expansion.
13247 if (!context_->params().cp_model_presolve()) {
13248 for (ConstraintProto& ct :
13249 *context_->working_model->mutable_constraints()) {
13250 if (ct.constraint_case() == ConstraintProto::kLinear) {
13251 context_->CanonicalizeLinearConstraint(&ct);
13252 }
13253 }
13254
13255 if (!solution_crush_.SolutionIsLoaded()) {
13256 context_->LoadSolutionHint();
13257 }
13258 ExpandCpModelAndCanonicalizeConstraints();
13259 UpdateHintInProto(context_);
13260 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13261
13262 // We still write back the canonical objective has we don't deal well
13263 // with uninitialized domain or duplicate variables.
13264 if (context_->working_model->has_objective()) {
13265 context_->WriteObjectiveToProto();
13266 }
13267
13268 // We need to append all the variable equivalence that are still used!
13269 EncodeAllAffineRelations();
13270
13271 // Make sure we also have an initialized mapping model as we use this for
13272 // filling the tightened variables. Even without presolve, we do some
13273 // trivial presolving during the initial copy of the model, and expansion
13274 // might do more.
13275 context_->WriteVariableDomainsToProto();
13276 InitializeMappingModelVariables(context_->AllDomains(),
13277 &fixed_postsolve_mapping,
13278 context_->mapping_model);
13279
13280 // We don't want to run postsolve when the presolve is disabled, but the
13281 // expansion might have added some constraints to the mapping model. To
13282 // restore correctness, we merge them with the working model.
13283 if (!context_->mapping_model->constraints().empty()) {
13284 context_->UpdateRuleStats(
13285 "TODO: mapping model not empty with presolve disabled");
13286 context_->working_model->mutable_constraints()->MergeFrom(
13287 context_->mapping_model->constraints());
13288 context_->mapping_model->clear_constraints();
13289 }
13290
13291 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13292 return CpSolverStatus::UNKNOWN;
13293 }
13294
13295 // Presolve all variable domain once. The PresolveToFixPoint() function will
13296 // only reprocess domain that changed.
13297 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13298 for (int var = 0; var < context_->working_model->variables().size(); ++var) {
13299 if (context_->VariableIsNotUsedAnymore(var)) continue;
13300 if (!PresolveAffineRelationIfAny(var)) return InfeasibleStatus();
13301
13302 // Try to canonicalize the domain, note that we should have detected all
13303 // affine relations before, so we don't recreate "canononical" variables
13304 // if they already exist in the model.
13305 TryToSimplifyDomain(var);
13306 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13307 context_->UpdateNewConstraintsVariableUsage();
13308 }
13309 if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
13310
13311 // Main propagation loop.
13312 for (int iter = 0; iter < context_->params().max_presolve_iterations();
13313 ++iter) {
13314 if (time_limit_->LimitReached()) break;
13315 context_->UpdateRuleStats("presolve: iteration");
13316 const int64_t old_num_presolve_op = context_->num_presolve_operations;
13317
13318 // Propagate the objective.
13319 if (!PropagateObjective()) return InfeasibleStatus();
13320
13321 // TODO(user): The presolve transformations we do after this is called might
13322 // result in even more presolve if we were to call this again! improve the
13323 // code. See for instance plusexample_6_sat.fzn were represolving the
13324 // presolved problem reduces it even more.
13325 PresolveToFixPoint();
13326
13327 // Call expansion.
13328 if (!context_->ModelIsExpanded()) {
13329 ExtractEncodingFromLinear();
13330 ExpandCpModelAndCanonicalizeConstraints();
13331 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13332 // We need to re-evaluate the degree because some presolve rule only
13333 // run after expansion.
13334 const int num_vars = context_->working_model->variables().size();
13335 for (int var = 0; var < num_vars; ++var) {
13336 if (context_->VarToConstraints(var).size() <= 3) {
13337 context_->var_with_reduced_small_degree.Set(var);
13338 }
13339 }
13340 }
13341 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13342
13343 // We run the symmetry before more complex presolve rules as many of them
13344 // are heuristic based and might break the symmetry present in the original
13345 // problems. This happens for example on the flatzinc wordpress problem.
13346 //
13347 // TODO(user): Decide where is the best place for this.
13348 //
13349 // TODO(user): try not to break symmetry in our clique extension or other
13350 // more advanced presolve rule? Ideally we could even exploit them. But in
13351 // this case, it is still good to compute them early.
13352 if (context_->params().symmetry_level() > 0 && !context_->ModelIsUnsat() &&
13353 !time_limit_->LimitReached()) {
13354 // Both kind of duplications might introduce a lot of symmetries and we
13355 // want to do that before we even compute them.
13357 DetectDuplicateConstraints();
13358 if (context_->params().keep_symmetry_in_presolve()) {
13359 // If the presolve always keep symmetry, we compute it once and for all.
13360 if (!context_->working_model->has_symmetry()) {
13361 DetectAndAddSymmetryToProto(context_->params(),
13362 context_->working_model, logger_);
13363 }
13364
13365 // We distinguish an empty symmetry message meaning that symmetry were
13366 // computed and there is none, and the absence of symmetry message
13367 // meaning we don't know.
13368 //
13369 // TODO(user): Maybe this is a bit brittle. Also move this logic to
13370 // DetectAndAddSymmetryToProto() ?
13371 if (!context_->working_model->has_symmetry()) {
13372 context_->working_model->mutable_symmetry()->Clear();
13373 }
13374 } else if (!context_->params()
13375 .keep_all_feasible_solutions_in_presolve()) {
13377 }
13378 }
13379
13380 // Runs SAT specific presolve on the pure-SAT part of the problem.
13381 // Note that because this can only remove/fix variable not used in the other
13382 // part of the problem, there is no need to redo more presolve afterwards.
13383 if (context_->params().cp_model_use_sat_presolve()) {
13384 if (!time_limit_->LimitReached()) {
13385 if (!PresolvePureSatPart()) {
13386 (void)context_->NotifyThatModelIsUnsat(
13387 "Proven Infeasible during SAT presolve");
13388 return InfeasibleStatus();
13389 }
13390 }
13391 }
13392
13393 // Extract redundant at most one constraint from the linear ones.
13394 //
13395 // TODO(user): more generally if we do some probing, the same relation will
13396 // be detected (and more). Also add an option to turn this off?
13397 //
13398 // TODO(user): instead of extracting at most one, extract pairwise conflicts
13399 // and add them to bool_and clauses? this is some sort of small scale
13400 // probing, but good for sat presolve and clique later?
13401 if (!context_->ModelIsUnsat() && iter == 0) {
13402 const int old_size = context_->working_model->constraints_size();
13403 for (int c = 0; c < old_size; ++c) {
13404 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13405 if (ct->constraint_case() != ConstraintProto::kLinear) continue;
13406 ExtractAtMostOneFromLinear(ct);
13407 }
13408 context_->UpdateNewConstraintsVariableUsage();
13409 }
13410
13411 if (context_->params().cp_model_probing_level() > 0) {
13412 if (!time_limit_->LimitReached()) {
13413 Probe();
13414 PresolveToFixPoint();
13415 }
13416 } else {
13417 TransformIntoMaxCliques();
13418 }
13419
13420 // Deal with pair of constraints.
13421 //
13422 // TODO(user): revisit when different transformation appear.
13423 // TODO(user): merge these code instead of doing many passes?
13424 ProcessAtMostOneAndLinear();
13425 DetectDuplicateConstraints();
13426 DetectDuplicateConstraintsWithDifferentEnforcements();
13427 DetectDominatedLinearConstraints();
13429 ProcessSetPPC();
13430
13431 // These operations might break symmetry. Or at the very least, the newly
13432 // created variable must be incorporated in the generators.
13433 if (context_->params().find_big_linear_overlap() &&
13434 !context_->params().keep_symmetry_in_presolve()) {
13435 FindAlmostIdenticalLinearConstraints();
13436
13437 ActivityBoundHelper activity_amo_helper;
13438 activity_amo_helper.AddAllAtMostOnes(*context_->working_model);
13439 FindBigAtMostOneAndLinearOverlap(&activity_amo_helper);
13440
13441 // Heuristic: vertical introduce smaller defining constraint and appear in
13442 // many constraints, so might be more constrained. We might also still
13443 // make horizontal rectangle with the variable introduced.
13444 FindBigVerticalLinearOverlap(&activity_amo_helper);
13445 FindBigHorizontalLinearOverlap(&activity_amo_helper);
13446 }
13447 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13448
13449 // We do that after the duplicate, SAT and SetPPC constraints.
13450 if (!time_limit_->LimitReached()) {
13451 // Merge clauses that differ in just one literal.
13452 // Heuristic use at_most_one to try to tighten the initial LP Relaxation.
13453 MergeClauses();
13454 if (/*DISABLES CODE*/ (false)) DetectIncludedEnforcement();
13455 }
13456
13457 // The TransformIntoMaxCliques() call above transform all bool and into
13458 // at most one of size 2. This does the reverse and merge them.
13459 ConvertToBoolAnd();
13460
13461 // Call the main presolve to remove the fixed variables and do more
13462 // deductions.
13463 PresolveToFixPoint();
13464
13465 // Exit the loop if no operations were performed.
13466 //
13467 // TODO(user): try to be smarter and avoid looping again if little changed.
13468 const int64_t num_ops =
13469 context_->num_presolve_operations - old_num_presolve_op;
13470 if (num_ops == 0) break;
13471 }
13472 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13473
13474 // Regroup no-overlaps into max-cliques.
13475 MergeNoOverlapConstraints();
13476 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13477
13478 // Tries to spread the objective amongst many variables.
13479 // We re-do a canonicalization with the final linear expression.
13480 if (context_->working_model->has_objective()) {
13481 if (!context_->params().keep_symmetry_in_presolve()) {
13482 ExpandObjective();
13483 if (!context_->modified_domains.PositionsSetAtLeastOnce().empty()) {
13484 // If we have fixed variables or created new affine relations, there
13485 // might be more things to presolve.
13486 PresolveToFixPoint();
13487 }
13488 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13489 ShiftObjectiveWithExactlyOnes();
13490 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13491 }
13492
13493 // We re-do a canonicalization with the final linear expression.
13494 if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
13495 context_->WriteObjectiveToProto();
13496 }
13497
13498 // Now that everything that could possibly be fixed was fixed, make sure we
13499 // don't leave any linear constraint with fixed variables.
13500 for (int c = 0; c < context_->working_model->constraints_size(); ++c) {
13501 ConstraintProto& ct = *context_->working_model->mutable_constraints(c);
13502 bool need_canonicalize = false;
13503 if (ct.constraint_case() == ConstraintProto::kLinear) {
13504 for (const int v : ct.linear().vars()) {
13505 if (context_->IsFixed(v)) {
13506 need_canonicalize = true;
13507 break;
13508 }
13509 }
13510 }
13511 if (need_canonicalize) {
13512 if (CanonicalizeLinear(&ct)) {
13513 context_->UpdateConstraintVariableUsage(c);
13514 }
13515 }
13516 }
13517
13518 // Take care of linear constraint with a complex rhs.
13520
13521 // Adds all needed affine relation to context_->working_model.
13522 EncodeAllAffineRelations();
13523 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13524
13525 // If we have symmetry information, lets filter it.
13526 if (context_->working_model->has_symmetry()) {
13528 context_->working_model->mutable_symmetry(), context_)) {
13529 return InfeasibleStatus();
13530 }
13531 }
13532
13533 // The strategy variable indices will be remapped in ApplyVariableMapping()
13534 // but first we use the representative of the affine relations for the
13535 // variables that are not present anymore.
13536 //
13537 // Note that we properly take into account the sign of the coefficient which
13538 // will result in the same domain reduction strategy. Moreover, if the
13539 // variable order is not CHOOSE_FIRST, then we also encode the associated
13540 // affine transformation in order to preserve the order.
13541 absl::flat_hash_set<int> used_variables;
13542 for (DecisionStrategyProto& strategy :
13543 *context_->working_model->mutable_search_strategy()) {
13544 CHECK(strategy.variables().empty());
13545 if (strategy.exprs().empty()) continue;
13546
13547 // Canonicalize each expression to use affine representative.
13548 ConstraintProto empy_enforcement;
13549 for (LinearExpressionProto& expr : *strategy.mutable_exprs()) {
13550 CanonicalizeLinearExpression(empy_enforcement, &expr);
13551 }
13552
13553 // Remove fixed expression and affine corresponding to same variables.
13554 int new_size = 0;
13555 for (const LinearExpressionProto& expr : strategy.exprs()) {
13556 if (context_->IsFixed(expr)) continue;
13557
13558 const auto [_, inserted] = used_variables.insert(expr.vars(0));
13559 if (!inserted) continue;
13560
13561 *strategy.mutable_exprs(new_size++) = expr;
13562 }
13563 google::protobuf::util::Truncate(strategy.mutable_exprs(), new_size);
13564 }
13565
13566 // Sync the domains and initialize the mapping model variables.
13567 context_->WriteVariableDomainsToProto();
13568 InitializeMappingModelVariables(context_->AllDomains(),
13569 &fixed_postsolve_mapping,
13570 context_->mapping_model);
13571
13572 // Remove all the unused variables from the presolved model.
13573 postsolve_mapping_->clear();
13574 std::vector<int> mapping(context_->working_model->variables_size(), -1);
13575 absl::flat_hash_map<int64_t, int> constant_to_index;
13576 int num_unused_variables = 0;
13577 for (int i = 0; i < context_->working_model->variables_size(); ++i) {
13578 if (mapping[i] != -1) continue; // Already mapped.
13579
13580 if (context_->VariableWasRemoved(i)) {
13581 // Heuristic: If a variable is removed and has a representative that is
13582 // not, we "move" the representative to the spot of that variable in the
13583 // original order. This is to preserve any info encoded in the variable
13584 // order by the modeler.
13585 const int r = PositiveRef(context_->GetAffineRelation(i).representative);
13586 if (mapping[r] == -1 && !context_->VariableIsNotUsedAnymore(r)) {
13587 mapping[r] = postsolve_mapping_->size();
13588 postsolve_mapping_->push_back(fixed_postsolve_mapping[r]);
13589 }
13590 continue;
13591 }
13592
13593 // Deal with unused variables.
13594 //
13595 // If the variable is not fixed, we have multiple feasible solution for
13596 // this variable, so we can't remove it if we want all of them.
13597 if (context_->VariableIsNotUsedAnymore(i) &&
13598 (!context_->params().keep_all_feasible_solutions_in_presolve() ||
13599 context_->IsFixed(i))) {
13600 // Tricky. Variables that where not removed by a presolve rule should be
13601 // fixed first during postsolve, so that more complex postsolve rules
13602 // can use their values. One way to do that is to fix them here.
13603 //
13604 // We prefer to fix them to zero if possible.
13605 ++num_unused_variables;
13606 FillDomainInProto(Domain(context_->DomainOf(i).SmallestValue()),
13607 context_->mapping_model->mutable_variables(
13608 fixed_postsolve_mapping[i]));
13609 continue;
13610 }
13611
13612 // Merge identical constant. Note that the only place were constant are
13613 // still left are in the circuit and route constraint for fixed arcs.
13614 if (context_->IsFixed(i)) {
13615 auto [it, inserted] = constant_to_index.insert(
13616 {context_->FixedValue(i), postsolve_mapping_->size()});
13617 if (!inserted) {
13618 mapping[i] = it->second;
13619 continue;
13620 }
13621 }
13622
13623 mapping[i] = postsolve_mapping_->size();
13624 postsolve_mapping_->push_back(fixed_postsolve_mapping[i]);
13625 }
13626 context_->UpdateRuleStats(absl::StrCat("presolve: ", num_unused_variables,
13627 " unused variables removed."));
13628
13629 if (context_->params().permute_variable_randomly()) {
13630 // The mapping might merge variable, so we have to be careful here.
13631 const int n = postsolve_mapping_->size();
13632 std::vector<int> perm(n);
13633 std::iota(perm.begin(), perm.end(), 0);
13634 std::shuffle(perm.begin(), perm.end(), *context_->random());
13635 for (int i = 0; i < context_->working_model->variables_size(); ++i) {
13636 if (mapping[i] != -1) mapping[i] = perm[mapping[i]];
13637 }
13638 std::vector<int> new_postsolve_mapping(n);
13639 for (int i = 0; i < n; ++i) {
13640 new_postsolve_mapping[perm[i]] = (*postsolve_mapping_)[i];
13641 }
13642 *postsolve_mapping_ = std::move(new_postsolve_mapping);
13643 }
13644
13645 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13646 UpdateHintInProto(context_);
13647 const int old_size = postsolve_mapping_->size();
13648 ApplyVariableMapping(absl::MakeSpan(mapping), postsolve_mapping_,
13649 context_->working_model);
13650 CHECK_EQ(old_size, postsolve_mapping_->size());
13651
13652 // Compact all non-empty constraint at the beginning.
13654
13655 // Hack to display the number of deductions stored.
13656 if (context_->deductions.NumDeductions() > 0) {
13657 context_->UpdateRuleStats(absl::StrCat(
13658 "deductions: ", context_->deductions.NumDeductions(), " stored"));
13659 }
13660
13661 // Stats and checks.
13662 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13663
13664 // This is not supposed to happen, and is more indicative of an error than an
13665 // INVALID model. But for our no-overflow preconditions, we might run into bad
13666 // situation that causes the final model to be invalid.
13667 {
13668 const std::string error =
13669 ValidateCpModel(*context_->working_model, /*after_presolve=*/true);
13670 if (!error.empty()) {
13671 SOLVER_LOG(logger_, "Error while validating postsolved model: ", error);
13672 return CpSolverStatus::MODEL_INVALID;
13673 }
13674 }
13675 {
13676 const std::string error = ValidateCpModel(*context_->mapping_model);
13677 if (!error.empty()) {
13678 SOLVER_LOG(logger_,
13679 "Error while validating mapping_model model: ", error);
13680 return CpSolverStatus::MODEL_INVALID;
13681 }
13682 }
13683
13684 return CpSolverStatus::UNKNOWN;
13685}
13686
13687void ApplyVariableMapping(absl::Span<int> mapping,
13688 std::vector<int>* reverse_mapping,
13689 CpModelProto* proto) {
13690 // Remap all the variable/literal references in the constraints and the
13691 // enforcement literals in the variables.
13692 auto mapping_function = [mapping, reverse_mapping](int* ref) mutable {
13693 const int var = PositiveRef(*ref);
13694 int image = mapping[var];
13695 if (image < 0) {
13696 // We extend the mapping if this variable is still used.
13697 image = mapping[var] = reverse_mapping->size();
13698 reverse_mapping->push_back(var);
13699 }
13700 *ref = RefIsPositive(*ref) ? image : NegatedRef(image);
13701 };
13702 for (ConstraintProto& ct_ref : *proto->mutable_constraints()) {
13703 ApplyToAllVariableIndices(mapping_function, &ct_ref);
13704 ApplyToAllLiteralIndices(mapping_function, &ct_ref);
13705 }
13706
13707 // Remap the objective variables.
13708 if (proto->has_objective()) {
13709 for (int& mutable_ref : *proto->mutable_objective()->mutable_vars()) {
13710 mapping_function(&mutable_ref);
13711 }
13712 }
13713
13714 // Remap the assumptions.
13715 for (int& mutable_ref : *proto->mutable_assumptions()) {
13716 mapping_function(&mutable_ref);
13717 }
13718
13719 // Remap the symmetries. Note that we should have properly dealt with fixed
13720 // orbit and such in FilterOrbitOnUnusedOrFixedVariables().
13721 if (proto->has_symmetry()) {
13722 for (SparsePermutationProto& generator :
13723 *proto->mutable_symmetry()->mutable_permutations()) {
13724 for (int& var : *generator.mutable_support()) {
13725 mapping_function(&var);
13726 }
13727 }
13728
13729 // We clear the orbitope info (we don't really use it after presolve).
13730 proto->mutable_symmetry()->clear_orbitopes();
13731 }
13732
13733 // Note: For the rest of the mapping, if mapping[i] is -1, we can just ignore
13734 // the variable instead of trying to map it.
13735
13736 // Remap the search decision heuristic.
13737 // Note that we delete any heuristic related to a removed variable.
13738 for (DecisionStrategyProto& strategy : *proto->mutable_search_strategy()) {
13739 int new_size = 0;
13740 for (LinearExpressionProto expr : strategy.exprs()) {
13741 DCHECK_EQ(expr.vars().size(), 1);
13742 const int image = mapping[expr.vars(0)];
13743 if (image >= 0) {
13744 expr.set_vars(0, image);
13745 *strategy.mutable_exprs(new_size++) = expr;
13746 }
13747 }
13748 google::protobuf::util::Truncate(strategy.mutable_exprs(), new_size);
13749 }
13750
13751 // Remove strategy with empty affine expression.
13752 {
13753 int new_size = 0;
13754 for (const DecisionStrategyProto& strategy : proto->search_strategy()) {
13755 if (strategy.exprs().empty()) continue;
13756 *proto->mutable_search_strategy(new_size++) = strategy;
13757 }
13758 google::protobuf::util::Truncate(proto->mutable_search_strategy(),
13759 new_size);
13760 }
13761
13762 // Remap the solution hint.
13763 if (proto->has_solution_hint()) {
13764 auto* mutable_hint = proto->mutable_solution_hint();
13765
13766 // Note that after remapping, we may have duplicate variables. For instance,
13767 // identical constant variables are mapped to a single one. So we make sure
13768 // we don't output duplicates here and just keep the first occurrence.
13769 absl::flat_hash_set<int> hinted_images;
13770
13771 int new_size = 0;
13772 const int old_size = mutable_hint->vars().size();
13773 for (int i = 0; i < old_size; ++i) {
13774 const int hinted_var = mutable_hint->vars(i);
13775 const int64_t hinted_value = mutable_hint->values(i);
13776 const int image = mapping[hinted_var];
13777 if (image >= 0) {
13778 if (!hinted_images.insert(image).second) continue;
13779 mutable_hint->set_vars(new_size, image);
13780 mutable_hint->set_values(new_size, hinted_value);
13781 ++new_size;
13782 }
13783 }
13784 mutable_hint->mutable_vars()->Truncate(new_size);
13785 mutable_hint->mutable_values()->Truncate(new_size);
13786 }
13787
13788 // Move the variable definitions.
13789 google::protobuf::RepeatedPtrField<IntegerVariableProto>
13790 new_variables_storage;
13791 google::protobuf::RepeatedPtrField<IntegerVariableProto>* new_variables;
13792 if (proto->GetArena() == nullptr) {
13793 new_variables = &new_variables_storage;
13794 } else {
13795 new_variables = google::protobuf::Arena::Create<
13796 google::protobuf::RepeatedPtrField<IntegerVariableProto>>(
13797 proto->GetArena());
13798 }
13799 for (int i = 0; i < mapping.size(); ++i) {
13800 const int image = mapping[i];
13801 if (image < 0) continue;
13802 while (image >= new_variables->size()) {
13803 new_variables->Add();
13804 }
13805 (*new_variables)[image].Swap(proto->mutable_variables(i));
13806 }
13807 proto->mutable_variables()->Swap(new_variables);
13808
13809 // Check that all variables have a non-empty domain.
13810 for (const IntegerVariableProto& v : proto->variables()) {
13811 CHECK_GT(v.domain_size(), 0);
13812 }
13813}
13814
13815bool CpModelPresolver::MaybeRemoveFixedVariables(
13816 std::vector<int>* postsolve_mapping) {
13817 postsolve_mapping->clear();
13818 if (!context_->params().remove_fixed_variables_early()) return true;
13819 if (!context_->params().cp_model_presolve()) return true;
13820
13821 // This is supposed to be already called, but it is a no-opt if this was the
13822 // case, and it comment nicely that we do require domains to be up to date
13823 // in the context.
13824 context_->InitializeNewDomains();
13825 if (context_->ModelIsUnsat()) return false;
13826
13827 // Initialize the mapping to remove all fixed variables.
13828 const int num_vars = context_->working_model->variables().size();
13829 std::vector<int> mapping(num_vars, -1);
13830 for (int i = 0; i < num_vars; ++i) {
13831 if (context_->IsFixed(i)) continue;
13832 mapping[i] = postsolve_mapping->size();
13833 postsolve_mapping->push_back(i);
13834 }
13835
13836 // Lets only do this if the proportion of fixed variables is large enough.
13837 const int num_fixed = num_vars - postsolve_mapping->size();
13838 if (num_fixed < 1000 || num_fixed * 2 <= num_vars) {
13839 postsolve_mapping->clear();
13840 return true;
13841 }
13842
13843 // TODO(user): Right now the copy do not remove fixed variable from the
13844 // objective, but ReadObjectiveFromProto() does it. Maybe we should just not
13845 // copy them in the first place.
13846 if (context_->working_model->has_objective()) {
13847 context_->ReadObjectiveFromProto();
13848 if (!context_->CanonicalizeObjective()) return false;
13849 if (!PropagateObjective()) return false;
13850 if (context_->ModelIsUnsat()) return false;
13851 context_->WriteObjectiveToProto();
13852 }
13853
13854 // Copy the current domains into the mapping model.
13855 // Note that we are not sure the domain where properly written.
13856 context_->WriteVariableDomainsToProto();
13857 *context_->mapping_model->mutable_variables() =
13858 context_->working_model->variables();
13859
13860 // Reset some part of the context, it will re-read the new domains below.
13861 context_->ResetAfterCopy();
13862
13863 SOLVER_LOG(logger_, "Large number of fixed variables ",
13864 FormatCounter(num_fixed), " / ", FormatCounter(num_vars),
13865 ", doing a first remapping phase to go down to ",
13866 FormatCounter(postsolve_mapping->size()), " variables.");
13867
13868 // Perform the actual mapping.
13869 // Note that this might re-add fixed variable that are still used.
13870 const int old_size = postsolve_mapping->size();
13871 ApplyVariableMapping(absl::MakeSpan(mapping), postsolve_mapping,
13872 context_->working_model);
13873 if (postsolve_mapping->size() > old_size) {
13874 const int new_extra = postsolve_mapping->size() - old_size;
13875 SOLVER_LOG(logger_, "TODO: ", new_extra,
13876 " fixed variables still required in the model!");
13877 }
13878 return true;
13879}
13880
13881namespace {
13882
13883// We ignore all the fields but the linear expression.
13884ConstraintProto CopyObjectiveForDuplicateDetection(
13885 const CpObjectiveProto& objective) {
13886 ConstraintProto copy;
13887 *copy.mutable_linear()->mutable_vars() = objective.vars();
13888 *copy.mutable_linear()->mutable_coeffs() = objective.coeffs();
13889 return copy;
13890}
13891
13892struct ConstraintHashForDuplicateDetection {
13893 const CpModelProto* working_model;
13894 bool ignore_enforcement;
13895 ConstraintProto objective_constraint;
13896
13897 ConstraintHashForDuplicateDetection(const CpModelProto* working_model,
13898 bool ignore_enforcement)
13899 : working_model(working_model),
13900 ignore_enforcement(ignore_enforcement),
13901 objective_constraint(
13902 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
13903
13904 // We hash our mostly frequently used constraint directly without extra memory
13905 // allocation. We revert to a generic code using proto serialization for the
13906 // others.
13907 std::size_t operator()(int ct_idx) const {
13908 const ConstraintProto& ct = ct_idx == kObjectiveConstraint
13909 ? objective_constraint
13910 : working_model->constraints(ct_idx);
13911 const std::pair<ConstraintProto::ConstraintCase, absl::Span<const int>>
13912 type_and_enforcement = {ct.constraint_case(),
13913 ignore_enforcement
13914 ? absl::Span<const int>()
13915 : absl::MakeSpan(ct.enforcement_literal())};
13916 switch (ct.constraint_case()) {
13917 case ConstraintProto::kLinear:
13918 if (ignore_enforcement) {
13919 return absl::HashOf(type_and_enforcement,
13920 absl::MakeSpan(ct.linear().vars()),
13921 absl::MakeSpan(ct.linear().coeffs()),
13922 absl::MakeSpan(ct.linear().domain()));
13923 } else {
13924 // We ignore domain for linear constraint, because if the rest of the
13925 // constraint is the same we can just intersect them.
13926 return absl::HashOf(type_and_enforcement,
13927 absl::MakeSpan(ct.linear().vars()),
13928 absl::MakeSpan(ct.linear().coeffs()));
13929 }
13930 case ConstraintProto::kBoolAnd:
13931 return absl::HashOf(type_and_enforcement,
13932 absl::MakeSpan(ct.bool_and().literals()));
13933 case ConstraintProto::kBoolOr:
13934 return absl::HashOf(type_and_enforcement,
13935 absl::MakeSpan(ct.bool_or().literals()));
13936 case ConstraintProto::kAtMostOne:
13937 return absl::HashOf(type_and_enforcement,
13938 absl::MakeSpan(ct.at_most_one().literals()));
13939 case ConstraintProto::kExactlyOne:
13940 return absl::HashOf(type_and_enforcement,
13941 absl::MakeSpan(ct.exactly_one().literals()));
13942 default:
13943 ConstraintProto copy = ct;
13944 copy.clear_name();
13945 if (ignore_enforcement) {
13946 copy.mutable_enforcement_literal()->Clear();
13947 }
13948 return absl::HashOf(copy.SerializeAsString());
13949 }
13950 }
13951};
13952
13953struct ConstraintEqForDuplicateDetection {
13954 const CpModelProto* working_model;
13955 bool ignore_enforcement;
13956 ConstraintProto objective_constraint;
13957
13958 ConstraintEqForDuplicateDetection(const CpModelProto* working_model,
13959 bool ignore_enforcement)
13960 : working_model(working_model),
13961 ignore_enforcement(ignore_enforcement),
13962 objective_constraint(
13963 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
13964
13965 bool operator()(int a, int b) const {
13966 if (a == b) {
13967 return true;
13968 }
13969 const ConstraintProto& ct_a = a == kObjectiveConstraint
13970 ? objective_constraint
13971 : working_model->constraints(a);
13972 const ConstraintProto& ct_b = b == kObjectiveConstraint
13973 ? objective_constraint
13974 : working_model->constraints(b);
13975
13976 if (ct_a.constraint_case() != ct_b.constraint_case()) return false;
13977 if (!ignore_enforcement) {
13978 if (absl::MakeSpan(ct_a.enforcement_literal()) !=
13979 absl::MakeSpan(ct_b.enforcement_literal())) {
13980 return false;
13981 }
13982 }
13983 switch (ct_a.constraint_case()) {
13984 case ConstraintProto::kLinear:
13985 // As above, we ignore domain for linear constraint, because if the rest
13986 // of the constraint is the same we can just intersect them.
13987 if (ignore_enforcement && absl::MakeSpan(ct_a.linear().domain()) !=
13988 absl::MakeSpan(ct_b.linear().domain())) {
13989 return false;
13990 }
13991 return absl::MakeSpan(ct_a.linear().vars()) ==
13992 absl::MakeSpan(ct_b.linear().vars()) &&
13993 absl::MakeSpan(ct_a.linear().coeffs()) ==
13994 absl::MakeSpan(ct_b.linear().coeffs());
13995 case ConstraintProto::kBoolAnd:
13996 return absl::MakeSpan(ct_a.bool_and().literals()) ==
13997 absl::MakeSpan(ct_b.bool_and().literals());
13998 case ConstraintProto::kBoolOr:
13999 return absl::MakeSpan(ct_a.bool_or().literals()) ==
14000 absl::MakeSpan(ct_b.bool_or().literals());
14001 case ConstraintProto::kAtMostOne:
14002 return absl::MakeSpan(ct_a.at_most_one().literals()) ==
14003 absl::MakeSpan(ct_b.at_most_one().literals());
14004 case ConstraintProto::kExactlyOne:
14005 return absl::MakeSpan(ct_a.exactly_one().literals()) ==
14006 absl::MakeSpan(ct_b.exactly_one().literals());
14007 default:
14008 // Slow (hopefully comparably rare) path.
14009 ConstraintProto copy_a = ct_a;
14010 ConstraintProto copy_b = ct_b;
14011 copy_a.clear_name();
14012 copy_b.clear_name();
14013 if (ignore_enforcement) {
14014 copy_a.mutable_enforcement_literal()->Clear();
14015 copy_b.mutable_enforcement_literal()->Clear();
14016 }
14017 return copy_a.SerializeAsString() == copy_b.SerializeAsString();
14018 }
14019 }
14020};
14021
14022} // namespace
14023
14024std::vector<std::pair<int, int>> FindDuplicateConstraints(
14025 const CpModelProto& model_proto, bool ignore_enforcement) {
14026 std::vector<std::pair<int, int>> result;
14027
14028 // We use a map hash that uses the underlying constraint to compute the hash
14029 // and the equality for the indices.
14030 absl::flat_hash_map<int, int, ConstraintHashForDuplicateDetection,
14031 ConstraintEqForDuplicateDetection>
14032 equiv_constraints(
14033 model_proto.constraints_size(),
14034 ConstraintHashForDuplicateDetection{&model_proto, ignore_enforcement},
14035 ConstraintEqForDuplicateDetection{&model_proto, ignore_enforcement});
14036
14037 // Create a special representative for the linear objective.
14038 if (model_proto.has_objective() && !ignore_enforcement) {
14039 equiv_constraints[kObjectiveConstraint] = kObjectiveConstraint;
14040 }
14041
14042 const int num_constraints = model_proto.constraints().size();
14043 for (int c = 0; c < num_constraints; ++c) {
14044 const auto type = model_proto.constraints(c).constraint_case();
14045 if (type == ConstraintProto::CONSTRAINT_NOT_SET) continue;
14046
14047 // TODO(user): we could delete duplicate identical interval, but we need
14048 // to make sure reference to them are updated.
14049 if (type == ConstraintProto::kInterval) continue;
14050
14051 // Nothing we will presolve in this case.
14052 if (ignore_enforcement && type == ConstraintProto::kBoolAnd) continue;
14053
14054 const auto [it, inserted] = equiv_constraints.insert({c, c});
14055 if (it->second != c) {
14056 // Already present!
14057 result.push_back({c, it->second});
14058 }
14059 }
14060
14061 return result;
14062}
14063
14064namespace {
14065bool SimpleLinearExprEq(const LinearExpressionProto& a,
14066 const LinearExpressionProto& b) {
14067 return absl::MakeSpan(a.vars()) == absl::MakeSpan(b.vars()) &&
14068 absl::MakeSpan(a.coeffs()) == absl::MakeSpan(b.coeffs()) &&
14069 a.offset() == b.offset();
14070}
14071
14072std::size_t LinearExpressionHash(const LinearExpressionProto& expr) {
14073 return absl::HashOf(absl::MakeSpan(expr.vars()),
14074 absl::MakeSpan(expr.coeffs()), expr.offset());
14075}
14076
14077} // namespace
14078
14079bool CpModelPresolver::IntervalConstraintEq::operator()(int a, int b) const {
14080 const ConstraintProto& ct_a = working_model->constraints(a);
14081 const ConstraintProto& ct_b = working_model->constraints(b);
14082 return absl::MakeSpan(ct_a.enforcement_literal()) ==
14083 absl::MakeSpan(ct_b.enforcement_literal()) &&
14084 SimpleLinearExprEq(ct_a.interval().start(), ct_b.interval().start()) &&
14085 SimpleLinearExprEq(ct_a.interval().size(), ct_b.interval().size()) &&
14086 SimpleLinearExprEq(ct_a.interval().end(), ct_b.interval().end());
14087}
14088
14089std::size_t CpModelPresolver::IntervalConstraintHash::operator()(
14090 int ct_idx) const {
14091 const ConstraintProto& ct = working_model->constraints(ct_idx);
14092 return absl::HashOf(absl::MakeSpan(ct.enforcement_literal()),
14093 LinearExpressionHash(ct.interval().start()),
14094 LinearExpressionHash(ct.interval().size()),
14095 LinearExpressionHash(ct.interval().end()));
14096}
14097
14098} // namespace sat
14099} // namespace operations_research
const std::vector< Variable * > & variables() const
--— Accessors and mutators --—
Definition model.h:372
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:32
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
static Domain FromValues(std::vector< int64_t > values)
Domain IntersectionWith(const Domain &domain) const
Domain QuadraticSuperset(int64_t a, int64_t b, int64_t c, int64_t d) const
Domain ContinuousMultiplicationBy(int64_t coeff) const
Domain AdditionWith(const Domain &domain) const
bool IsIncludedIn(const Domain &domain) const
Domain UnionWith(const Domain &domain) const
Domain InverseMultiplicationBy(int64_t coeff) const
static IntegralType CeilOfRatio(IntegralType numerator, IntegralType denominator)
Definition mathutil.h:40
bool LoggingIsEnabled() const
Returns true iff logging is enabled.
Definition logging.h:49
void Resize(int num_variables)
Resizes the data structure.
Definition clause.cc:539
void ResetFromFlatMapping(Keys keys, Values values)
Definition util.h:843
size_t size() const
Size of the "key" space, always in [0, size()).
Definition util.h:832
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
void DetectDifferentVariables()
Detects variable that must take different values.
bool PresolveOneConstraint(int c)
Executes presolve method for the given constraint. Public for testing only.
void RemoveEmptyConstraints()
Visible for testing.
ABSL_MUST_USE_RESULT bool CanonicalizeObjective(bool simplify_domain=true)
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
Returns false if the 'lit' doesn't have the desired value in the domain.
void LogInfo()
Logs stats to the logger.
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
Return a super-set of the domain of the linear expression.
void InitializeNewDomains()
Creates the internal structure for any new variables in working_model.
ConstraintProto * NewMappingConstraint(absl::string_view file, int line)
void UpdateRuleStats(const std::string &name, int num_times=1)
void UpdateNewConstraintsVariableUsage()
Calls UpdateConstraintVariableUsage() on all newly created constraints.
bool CanonicalizeLinearConstraint(ConstraintProto *ct)
void AddCounter(std::string name, int64_t count)
void StoreSolutionAsHint(CpModelProto &model) const
Stores the solution as a hint in the given model.
time_limit
Definition solve.cc:22
void Truncate(RepeatedPtrField< T > *array, int new_size)
RepeatedPtrField version.
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
bool ReduceNumberOfBoxesExactMandatory(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
constexpr uint64_t kDefaultFingerprintSeed
Default seed for fingerprints.
uint64_t FingerprintRepeatedField(const google::protobuf::RepeatedField< T > &sequence, uint64_t seed)
void ApplyVariableMapping(absl::Span< int > mapping, std::vector< int > *reverse_mapping, CpModelProto *proto)
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
const LiteralIndex kNoLiteralIndex(-1)
std::vector< std::pair< int, int > > FindDuplicateConstraints(const CpModelProto &model_proto, bool ignore_enforcement)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
void ExpandCpModel(PresolveContext *context)
bool DetectAndExploitSymmetriesInPresolve(PresolveContext *context)
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
Definition util.cc:204
CompactVectorVector< int > GetOverlappingRectangleComponents(absl::Span< const Rectangle > rectangles)
Domain EvaluateImpliedIntProdDomain(const LinearArgumentProto &expr, const PresolveContext &context)
int64_t FloorSquareRoot(int64_t a)
The argument must be non-negative.
Definition util.cc:300
bool HasEnforcementLiteral(const ConstraintProto &ct)
Small utility functions to deal with half-reified constraints.
DiophantineSolution SolveDiophantine(absl::Span< const int64_t > coeffs, int64_t rhs, absl::Span< const int64_t > var_lbs, absl::Span< const int64_t > var_ubs)
bool ClauseIsEnforcementImpliesLiteral(absl::Span< const int > clause, absl::Span< const int > enforcement, int literal)
void ApplyToAllIntervalIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
bool SafeAddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
Same method, but returns if the addition was possible without overflowing.
bool IsNegatableInt64(absl::int128 x)
Tells whether a int128 can be casted to a int64_t that can be negated.
Definition util.h:740
int64_t GetInnerVarValue(const LinearExpressionProto &expr, int64_t value)
std::vector< Rectangle > FindEmptySpaces(const Rectangle &bounding_box, std::vector< Rectangle > ocupied_rectangles)
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
Convenient wrapper to call the full presolve.
bool ScaleFloatingPointObjective(const SatParameters &params, SolverLogger *logger, CpModelProto *proto)
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
bool FilterOrbitOnUnusedOrFixedVariables(SymmetryProto *symmetry, PresolveContext *context)
void ProbeAndFindEquivalentLiteral(SatSolver *solver, SatPostsolver *postsolver, DratProofHandler *drat_proof_handler, util_intops::StrongVector< LiteralIndex, LiteralIndex > *mapping, SolverLogger *logger)
bool LookForTrivialSatSolution(double deterministic_time_limit, Model *model, SolverLogger *logger)
Definition probing.cc:420
void ScanModelForDualBoundStrengthening(const PresolveContext &context, DualBoundStrengthening *dual_bound_strengthening)
Scan the model so that dual_bound_strengthening.Strenghten() works.
void ConstructOverlappingSets(absl::Span< IndexedInterval > intervals, CompactVectorVector< int > *result, absl::Span< const int > order)
bool AddLinearConstraintMultiple(int64_t factor, const ConstraintProto &to_add, ConstraintProto *to_modify)
Disjoint2dPackingResult DetectDisjointRegionIn2dPacking(absl::Span< const RectangleInRange > non_fixed_boxes, absl::Span< const Rectangle > fixed_boxes, int max_num_components)
constexpr int kAffineRelationConstraint
IntegerValue PositiveRemainder(IntegerValue dividend, IntegerValue positive_divisor)
int64_t SafeDoubleToInt64(double value)
Definition util.h:728
std::string FormatCounter(int64_t num)
Prints a positive number with separators for easier reading (ex: 1'348'065).
Definition util.cc:44
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
int ReindexArcs(IntContainer *tails, IntContainer *heads, absl::flat_hash_map< int, int > *mapping_output=nullptr)
Definition circuit.h:208
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset)
void FinalExpansionForLinearConstraint(PresolveContext *context)
constexpr int kObjectiveConstraint
We use some special constraint index in our variable <-> constraint graph.
bool DiophantineEquationOfSizeTwoHasSolutionInDomain(const Domain &x, int64_t a, const Domain &y, int64_t b, int64_t cte)
Definition util.cc:248
void DetectAndAddSymmetryToProto(const SatParameters &params, CpModelProto *proto, SolverLogger *logger)
Detects symmetries and fill the symmetry field.
int64_t AffineExpressionValueAt(const LinearExpressionProto &expr, int64_t value)
Evaluates an affine expression at the given value.
bool ReduceNumberofBoxesGreedy(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
bool ExpressionsContainsOnlyOneVar(const ExpressionList &exprs)
Returns true if there exactly one variable appearing in all the expressions.
int64_t ClosestMultiple(int64_t value, int64_t base)
Definition util.cc:317
void ScanModelForDominanceDetection(PresolveContext &context, VarDomination *var_domination)
bool LinearsDifferAtOneTerm(const LinearConstraintProto &lin1, const LinearConstraintProto &lin2)
bool ExpressionContainsSingleRef(const LinearExpressionProto &expr)
Returns true if a linear expression can be reduced to a single ref.
int64_t LinearExpressionGcd(const LinearExpressionProto &expr, int64_t gcd)
void ApplyToAllLiteralIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
InclusionDetector(const Storage &storage) -> InclusionDetector< Storage >
Deduction guide.
std::vector< std::pair< int, int > > FindPartialRectangleIntersections(absl::Span< const Rectangle > rectangles)
void DivideLinearExpression(int64_t divisor, LinearExpressionProto *expr)
bool LinearInequalityCanBeReducedWithClosestMultiple(int64_t base, absl::Span< const int64_t > coeffs, absl::Span< const int64_t > lbs, absl::Span< const int64_t > ubs, int64_t rhs, int64_t *new_rhs)
Definition util.cc:324
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
void PropagateAutomaton(const AutomatonConstraintProto &proto, const PresolveContext &context, std::vector< absl::flat_hash_set< int64_t > > *states, std::vector< absl::flat_hash_set< int64_t > > *labels)
Fills and propagates the set of reachable states/labels.
void GetOverlappingIntervalComponents(std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *components)
void CreateValidModelWithSingleConstraint(const ConstraintProto &ct, const PresolveContext *context, std::vector< int > *variable_mapping, CpModelProto *mini_model)
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
bool LinearExpressionProtosAreEqual(const LinearExpressionProto &a, const LinearExpressionProto &b, int64_t b_scaling)
Returns true iff a == b * b_scaling.
bool PresolveFixed2dRectangles(absl::Span< const RectangleInRange > non_fixed_boxes, std::vector< Rectangle > *fixed_boxes)
void ApplyToAllVariableIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
bool LoadModelForPresolve(const CpModelProto &model_proto, SatParameters params, PresolveContext *context, Model *local_model, absl::string_view name_for_logging)
void CanonicalizeTable(PresolveContext *context, ConstraintProto *ct)
In SWIG mode, we don't want anything besides these top-level includes.
bool AtMinOrMaxInt64(int64_t x)
Checks if x is equal to the min or the max value of an int64_t.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
std::string ProtobufShortDebugString(const P &message)
Definition proto_utils.h:41
int64_t CapProd(int64_t x, int64_t y)
absl::StatusOr< std::vector< int > > FastTopologicalSort(const AdjacencyLists &adj)
if(!yyg->yy_init)
Definition parser.yy.cc:965
void FindStronglyConnectedComponents(NodeIndex num_nodes, const Graph &graph, SccOutput *components)
Simple wrapper function for most usage.
Definition model.h:51
int64_t Max() const
Returns the max of the domain.
Definition model.cc:346
bool empty() const
Definition model.cc:335
int64_t Min() const
Returns the min of the domain.
Definition model.cc:340
bool Contains(int64_t value) const
Various inclusion tests on a domain.
Definition model.cc:363
CompactVectorVector< int, std::pair< int, int64_t > > * column
ColumnEqForDuplicateDetection(CompactVectorVector< int, std::pair< int, int64_t > > *_column)
ColumnHashForDuplicateDetection(CompactVectorVector< int, std::pair< int, int64_t > > *_column)
CompactVectorVector< int, std::pair< int, int64_t > > * column
#define SOLVER_LOG(logger,...)
Definition logging.h:109