Google OR-Tools v9.15
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/container/inlined_vector.h"
39#include "absl/flags/flag.h"
40#include "absl/hash/hash.h"
41#include "absl/log/check.h"
42#include "absl/log/log.h"
43#include "absl/log/vlog_is_on.h"
44#include "absl/numeric/int128.h"
45#include "absl/random/distributions.h"
46#include "absl/status/statusor.h"
47#include "absl/strings/str_cat.h"
48#include "absl/types/span.h"
49#include "google/protobuf/arena.h"
50#include "google/protobuf/repeated_field.h"
51#include "google/protobuf/repeated_ptr_field.h"
57#include "ortools/base/timer.h"
62#include "ortools/sat/circuit.h"
63#include "ortools/sat/clause.h"
74#include "ortools/sat/integer.h"
76#include "ortools/sat/model.h"
81#include "ortools/sat/probing.h"
88#include "ortools/sat/util.h"
92#include "ortools/util/bitset.h"
98
99namespace operations_research {
100namespace sat {
101
102namespace {
103
104LinearExpression2 GetLinearExpression2FromProto(int a, int64_t coeff_a, int b,
105 int64_t coeff_b) {
106 LinearExpression2 result;
107 DCHECK(RefIsPositive(a));
108 DCHECK(RefIsPositive(b));
109 result.vars[0] = IntegerVariable(2 * a);
110 result.vars[1] = IntegerVariable(2 * b);
111 result.coeffs[0] = IntegerValue(coeff_a);
112 result.coeffs[1] = IntegerValue(coeff_b);
113 return result;
114}
115
116// TODO(user): Just make sure this invariant is enforced in all our linear
117// constraint after copy, and simplify the code!
118bool LinearConstraintIsClean(const LinearConstraintProto& linear) {
119 const int num_vars = linear.vars().size();
120 for (int i = 0; i < num_vars; ++i) {
121 if (!RefIsPositive(linear.vars(i))) return false;
122 if (linear.coeffs(i) == 0) return false;
123 }
124 return true;
125}
126
127} // namespace
128
129bool CpModelPresolver::RemoveConstraint(ConstraintProto* ct) {
130 ct->Clear();
131 return true;
132}
133
134// Remove all empty constraints and duplicated intervals. Note that we need to
135// remap the interval references.
136//
137// Now that they have served their purpose, we also remove dummy constraints,
138// otherwise that causes issue because our model are invalid in tests.
140 interval_representative_.clear();
141 std::vector<int> interval_mapping(context_->working_model->constraints_size(),
142 -1);
143 int new_num_constraints = 0;
144 const int old_num_non_empty_constraints =
145 context_->working_model->constraints_size();
146 for (int c = 0; c < old_num_non_empty_constraints; ++c) {
147 const auto type = context_->working_model->constraints(c).constraint_case();
148 if (type == ConstraintProto::CONSTRAINT_NOT_SET) continue;
149 if (type == ConstraintProto::kDummyConstraint) continue;
150 context_->working_model->mutable_constraints(new_num_constraints)
151 ->Swap(context_->working_model->mutable_constraints(c));
152 if (type == ConstraintProto::kInterval) {
153 // Warning: interval_representative_ holds a pointer to the working model
154 // to compute hashes, so we need to be careful about not changing a
155 // constraint after its index is added to the map.
156 const auto [it, inserted] = interval_representative_.insert(
157 {new_num_constraints, new_num_constraints});
158 interval_mapping[c] = it->second;
159 if (it->second != new_num_constraints) {
160 context_->UpdateRuleStats(
161 "intervals: change duplicate index across constraints");
162 continue;
163 }
164 }
165 new_num_constraints++;
166 }
168 context_->working_model->mutable_constraints(), new_num_constraints);
169 for (ConstraintProto& ct_ref :
170 *context_->working_model->mutable_constraints()) {
172 [&interval_mapping](int* ref) {
173 *ref = interval_mapping[*ref];
174 CHECK_NE(-1, *ref);
175 },
176 &ct_ref);
177 }
178}
179
180bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto* ct) {
181 if (context_->ModelIsUnsat()) return false;
182 if (!HasEnforcementLiteral(*ct)) return false;
183
184 auto remove_if_not_interval = [this, ct]() {
186 return MarkOptionalIntervalAsFalse(ct);
187 } else {
188 return RemoveConstraint(ct);
189 }
190 };
191
192 int new_size = 0;
193 const int old_size = ct->enforcement_literal().size();
194 context_->tmp_literal_set.clear();
195 for (const int literal : ct->enforcement_literal()) {
196 if (context_->LiteralIsTrue(literal)) {
197 // We can remove a literal at true.
198 context_->UpdateRuleStats("enforcement: true literal");
199 continue;
200 }
201
202 if (context_->LiteralIsFalse(literal)) {
203 context_->UpdateRuleStats("enforcement: false literal");
204 return remove_if_not_interval();
205 }
206
207 if (context_->VariableIsUniqueAndRemovable(literal)) {
208 // We can simply set it to false and ignore the constraint in this case.
209 context_->UpdateRuleStats("enforcement: literal not used");
210 CHECK(context_->SetLiteralToFalse(literal));
211 return remove_if_not_interval();
212 }
213
214 // If the literal only appear in the objective, we might be able to fix it
215 // to false. TODO(user): generalize if the literal always appear with the
216 // same polarity.
217 if (context_->VariableWithCostIsUniqueAndRemovable(literal)) {
218 const int64_t obj_coeff =
219 context_->ObjectiveMap().at(PositiveRef(literal));
220 if (RefIsPositive(literal) == (obj_coeff > 0)) {
221 // It is just more advantageous to set it to false!
222 context_->UpdateRuleStats("enforcement: literal with unique direction");
223 CHECK(context_->SetLiteralToFalse(literal));
224 return remove_if_not_interval();
225 }
226 }
227
228 // Deals with duplicate literals.
229 //
230 // TODO(user): Ideally we could do that just once during the first copy,
231 // and later never create such constraint.
232 if (old_size > 1) {
233 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
234 if (!inserted) {
235 context_->UpdateRuleStats("enforcement: removed duplicate literal");
236 continue;
237 }
238 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
239 context_->UpdateRuleStats("enforcement: can never be true");
240 return remove_if_not_interval();
241 }
242 }
243
244 ct->set_enforcement_literal(new_size++, literal);
245 }
246 ct->mutable_enforcement_literal()->Truncate(new_size);
247 return new_size != old_size;
248}
249
250bool CpModelPresolver::PresolveBoolXor(ConstraintProto* ct) {
251 if (context_->ModelIsUnsat()) return false;
252
253 int new_size = 0;
254 bool changed = false;
255 int num_true_literals = 0;
256 int true_literal = std::numeric_limits<int32_t>::min();
257 for (const int literal : ct->bool_xor().literals()) {
258 // TODO(user): More generally, if a variable appear in only bool xor
259 // constraints, we can simply eliminate it using linear algebra on Z/2Z.
260 // This should solve in polynomial time the parity-learning*.fzn problems
261 // for instance. This seems low priority, but it is also easy to do. Even
262 // better would be to have a dedicated propagator with all bool_xor
263 // constraints that do the necessary linear algebra.
264 if (context_->VariableIsUniqueAndRemovable(literal)) {
265 context_->UpdateRuleStats("TODO bool_xor: remove constraint");
266 }
267
268 if (context_->LiteralIsFalse(literal)) {
269 context_->UpdateRuleStats("bool_xor: remove false literal");
270 changed = true;
271 continue;
272 } else if (context_->LiteralIsTrue(literal)) {
273 true_literal = literal; // Keep if we need to put one back.
274 num_true_literals++;
275 continue;
276 }
277
278 ct->mutable_bool_xor()->set_literals(new_size++, literal);
279 }
280
281 if (new_size == 0) {
282 if (num_true_literals % 2 == 0) {
283 return MarkConstraintAsFalse(ct, "bool_xor: always false");
284 } else {
285 context_->UpdateRuleStats("bool_xor: always true");
286 return RemoveConstraint(ct);
287 }
288 } else if (new_size == 1 && !HasEnforcementLiteral(*ct)) {
289 // We can fix the only active literal.
290 if (num_true_literals % 2 == 0) {
291 if (!context_->SetLiteralToTrue(ct->bool_xor().literals(0))) {
292 return context_->NotifyThatModelIsUnsat(
293 "bool_xor: cannot fix last literal");
294 }
295 } else {
296 if (!context_->SetLiteralToFalse(ct->bool_xor().literals(0))) {
297 return context_->NotifyThatModelIsUnsat(
298 "bool_xor: cannot fix last literal");
299 }
300 }
301 context_->UpdateRuleStats("bool_xor: one active literal");
302 return RemoveConstraint(ct);
303 } else if (new_size == 2) { // We can simplify the bool_xor.
304 const int a = ct->bool_xor().literals(0);
305 const int b = ct->bool_xor().literals(1);
306 if (a == b) {
307 if (num_true_literals % 2 == 0) {
308 return MarkConstraintAsFalse(ct, "bool_xor: always false");
309 } else {
310 context_->UpdateRuleStats("bool_xor: always true");
311 return RemoveConstraint(ct);
312 }
313 }
314 if (a == NegatedRef(b)) {
315 if (num_true_literals % 2 == 1) {
316 return MarkConstraintAsFalse(ct, "bool_xor: always false");
317 } else {
318 context_->UpdateRuleStats("bool_xor: always true");
319 return RemoveConstraint(ct);
320 }
321 }
322 if (!HasEnforcementLiteral(*ct)) {
323 if (num_true_literals % 2 == 0) { // a == not(b).
324 if (!context_->StoreBooleanEqualityRelation(a, NegatedRef(b))) {
325 return false;
326 }
327 } else { // a == b.
328 if (!context_->StoreBooleanEqualityRelation(a, b)) {
329 return false;
330 }
331 }
332 context_->UpdateNewConstraintsVariableUsage();
333 context_->UpdateRuleStats("bool_xor: two active literals");
334 return RemoveConstraint(ct);
335 } // TODO(user): maybe replace the enforced XOR by an enforced equality?
336 }
337
338 if (num_true_literals % 2 == 1) {
339 CHECK_NE(true_literal, std::numeric_limits<int32_t>::min());
340 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
341 }
342 if (num_true_literals > 1) {
343 context_->UpdateRuleStats("bool_xor: remove even number of true literals");
344 changed = true;
345 }
346 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
347 return changed;
348}
349
350bool CpModelPresolver::PresolveBoolOr(ConstraintProto* ct) {
351 if (context_->ModelIsUnsat()) return false;
352
353 // Move the enforcement literal inside the clause if any. Note that we do not
354 // mark this as a change since the literal in the constraint are the same.
355 if (HasEnforcementLiteral(*ct)) {
356 context_->UpdateRuleStats("bool_or: removed enforcement literal");
357 for (const int literal : ct->enforcement_literal()) {
358 ct->mutable_bool_or()->add_literals(NegatedRef(literal));
359 }
360 ct->clear_enforcement_literal();
361 }
362
363 // Inspects the literals and deal with fixed ones.
364 //
365 // TODO(user): Because we remove literal on the first copy, maybe we can get
366 // rid of the set here. However we still need to be careful when remapping
367 // literals to their representatives.
368 bool changed = false;
369 context_->tmp_literals.clear();
370 context_->tmp_literal_set.clear();
371 for (const int literal : ct->bool_or().literals()) {
372 if (context_->LiteralIsFalse(literal)) {
373 changed = true;
374 continue;
375 }
376 if (context_->LiteralIsTrue(literal)) {
377 context_->UpdateRuleStats("bool_or: always true");
378 return RemoveConstraint(ct);
379 }
380 // We can just set the variable to true in this case since it is not
381 // used in any other constraint (note that we artificially bump the
382 // objective var usage by 1).
383 if (context_->VariableIsUniqueAndRemovable(literal)) {
384 context_->UpdateRuleStats("bool_or: singleton");
385 if (!context_->SetLiteralToTrue(literal)) return true;
386 return RemoveConstraint(ct);
387 }
388 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
389 context_->UpdateRuleStats("bool_or: always true");
390 return RemoveConstraint(ct);
391 }
392
393 if (context_->tmp_literal_set.contains(literal)) {
394 changed = true;
395 } else {
396 context_->tmp_literal_set.insert(literal);
397 context_->tmp_literals.push_back(literal);
398 }
399 }
400 context_->tmp_literal_set.clear();
401
402 if (context_->tmp_literals.empty()) {
403 context_->UpdateRuleStats("bool_or: empty");
404 return context_->NotifyThatModelIsUnsat();
405 }
406 if (context_->tmp_literals.size() == 1) {
407 context_->UpdateRuleStats("bool_or: only one literal");
408 if (!context_->SetLiteralToTrue(context_->tmp_literals[0])) return true;
409 return RemoveConstraint(ct);
410 }
411 if (context_->tmp_literals.size() == 2) {
412 // For consistency, we move all "implication" into half-reified bool_and.
413 // TODO(user): merge by enforcement literal and detect implication cycles.
414 context_->UpdateRuleStats("bool_or: implications");
415 ct->add_enforcement_literal(NegatedRef(context_->tmp_literals[0]));
416 ct->mutable_bool_and()->add_literals(context_->tmp_literals[1]);
417 return changed;
418 }
419
420 if (changed) {
421 context_->UpdateRuleStats("bool_or: fixed literals");
422 ct->mutable_bool_or()->mutable_literals()->Clear();
423 for (const int lit : context_->tmp_literals) {
424 ct->mutable_bool_or()->add_literals(lit);
425 }
426 }
427 return changed;
428}
429
430// Note this function does not update the constraint graph. It assumes this is
431// done elsewhere.
432ABSL_MUST_USE_RESULT bool CpModelPresolver::MarkConstraintAsFalse(
433 ConstraintProto* ct, std::string_view reason) {
434 if (!context_->MarkConstraintAsFalse(ct, reason)) return false;
435 if (ct->constraint_case() == ConstraintProto::kBoolOr) PresolveBoolOr(ct);
436 return true;
437}
438
439ABSL_MUST_USE_RESULT bool CpModelPresolver::MarkOptionalIntervalAsFalse(
440 ConstraintProto* ct) {
441 DCHECK_EQ(ct->constraint_case(), ConstraintProto::kInterval);
442 CHECK_EQ(ct->enforcement_literal_size(), 1);
443 const int enforcement_literal = ct->enforcement_literal(0);
444 if (!context_->SetLiteralToFalse(enforcement_literal)) {
445 return false;
446 }
447 // Now that we forced the interval to be unperformed we know it will be
448 // ignored no matter what it contains as start/end/size, so we can make it
449 // trivial. But we cannot remove the interval constraint itself though,
450 // because it may be referenced in some no_overlap/no_overlap_2d constraints.
451 ct->mutable_interval()->clear_start();
452 ct->mutable_interval()->clear_end();
453 ct->mutable_interval()->clear_size();
454 return true;
455}
456
457bool CpModelPresolver::PresolveBoolAnd(ConstraintProto* ct) {
458 if (context_->ModelIsUnsat()) return false;
459
460 if (!HasEnforcementLiteral(*ct)) {
461 context_->UpdateRuleStats("bool_and: non-reified");
462 for (const int literal : ct->bool_and().literals()) {
463 if (!context_->SetLiteralToTrue(literal)) return true;
464 }
465 return RemoveConstraint(ct);
466 }
467
468 bool changed = false;
469 context_->tmp_literals.clear();
470 context_->tmp_literal_set.clear();
471 const absl::flat_hash_set<int> enforcement_literals_set(
472 ct->enforcement_literal().begin(), ct->enforcement_literal().end());
473 for (const int literal : ct->bool_and().literals()) {
474 if (context_->LiteralIsFalse(literal)) {
475 return MarkConstraintAsFalse(ct, "bool_and: always false");
476 }
477 if (context_->LiteralIsTrue(literal)) {
478 changed = true;
479 continue;
480 }
481 if (enforcement_literals_set.contains(literal)) {
482 context_->UpdateRuleStats("bool_and: x => x");
483 changed = true;
484 continue;
485 }
486 if (enforcement_literals_set.contains(NegatedRef(literal))) {
487 return MarkConstraintAsFalse(ct, "bool_and: x => not x");
488 }
489 if (context_->VariableIsUniqueAndRemovable(literal)) {
490 // This is a "dual" reduction.
491 changed = true;
492 context_->UpdateRuleStats(
493 "bool_and: setting unused literal in rhs to true");
494 if (!context_->SetLiteralToTrue(literal)) return true;
495 continue;
496 }
497
498 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
499 return MarkConstraintAsFalse(ct, "bool_and: cannot be enforced");
500 }
501
502 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
503 if (inserted) {
504 context_->tmp_literals.push_back(literal);
505 } else {
506 changed = true;
507 context_->UpdateRuleStats("bool_and: removed duplicate literal");
508 }
509 }
510
511 // Note that this is not the same behavior as a bool_or:
512 // - bool_or means "at least one", so it is false if empty.
513 // - bool_and means "all literals inside true", so it is true if empty.
514 if (context_->tmp_literals.empty()) return RemoveConstraint(ct);
515
516 if (changed) {
517 ct->mutable_bool_and()->mutable_literals()->Clear();
518 for (const int lit : context_->tmp_literals) {
519 ct->mutable_bool_and()->add_literals(lit);
520 }
521 context_->UpdateRuleStats("bool_and: fixed literals");
522 }
523
524 // If a variable can move freely in one direction except for this constraint,
525 // we can make it an equality.
526 //
527 // TODO(user): also consider literal on the other side of the =>.
528 if (ct->enforcement_literal().size() == 1 &&
529 ct->bool_and().literals().size() == 1) {
530 const int enforcement = ct->enforcement_literal(0);
531 if (context_->VariableWithCostIsUniqueAndRemovable(enforcement)) {
532 int var = PositiveRef(enforcement);
533 int64_t obj_coeff = context_->ObjectiveMap().at(var);
534 if (!RefIsPositive(enforcement)) obj_coeff = -obj_coeff;
535
536 // The other case where the constraint is redundant is treated elsewhere.
537 if (obj_coeff < 0) {
538 context_->UpdateRuleStats("bool_and: dual equality");
539 // Extending `ct` = "enforcement => implied_literal" to an equality can
540 // break the hint only if hint(implied_literal) = 1 and
541 // hint(enforcement) = 0. But in this case the `enforcement` hint can be
542 // increased to 1 to preserve the hint feasibility.
543 const int implied_literal = ct->bool_and().literals(0);
544 solution_crush_.SetLiteralToValueIf(enforcement, true, implied_literal);
545 if (!context_->StoreBooleanEqualityRelation(enforcement,
546 implied_literal)) {
547 return false;
548 }
549 }
550 }
551 }
552
553 return changed;
554}
555
556bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto* ct) {
557 bool is_at_most_one = ct->constraint_case() == ConstraintProto::kAtMostOne;
558 const std::string name = is_at_most_one ? "at_most_one: " : "exactly_one: ";
559 auto* literals = is_at_most_one
560 ? ct->mutable_at_most_one()->mutable_literals()
561 : ct->mutable_exactly_one()->mutable_literals();
562
563 // Having a canonical constraint is needed for duplicate detection.
564 // This also change how we regroup bool_and.
565 std::sort(literals->begin(), literals->end());
566
567 // Deal with duplicate variable reference.
568 context_->tmp_literal_set.clear();
569 for (const int literal : *literals) {
570 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
571 if (!inserted) {
572 if (!context_->SetLiteralToFalse(literal)) return false;
573 context_->UpdateRuleStats(absl::StrCat(name, "duplicate literals"));
574 }
575 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
576 int num_positive = 0;
577 int num_negative = 0;
578 for (const int other : *literals) {
579 if (PositiveRef(other) != PositiveRef(literal)) {
580 if (!context_->SetLiteralToFalse(other)) return false;
581 context_->UpdateRuleStats(absl::StrCat(name, "x and not(x)"));
582 } else {
583 if (other == literal) {
584 ++num_positive;
585 } else {
586 ++num_negative;
587 }
588 }
589 }
590
591 // This is tricky for the case where the at most one reduce to (lit,
592 // not(lit), not(lit)) for instance.
593 if (num_positive > 1 && !context_->SetLiteralToFalse(literal)) {
594 return false;
595 }
596 if (num_negative > 1 && !context_->SetLiteralToTrue(literal)) {
597 return false;
598 }
599 return RemoveConstraint(ct);
600 }
601 }
602
603 // We can always remove all singleton variables (with or without cost) in an
604 // at_most_one or exactly one. We collect them and deal with this at the end.
605 std::vector<std::pair<int, int64_t>> singleton_literal_with_cost;
606
607 // Remove fixed variables.
608 bool changed = false;
609 context_->tmp_literals.clear();
610 for (const int literal : *literals) {
611 if (context_->LiteralIsTrue(literal)) {
612 context_->UpdateRuleStats(absl::StrCat(name, "satisfied"));
613 for (const int other : *literals) {
614 if (other != literal) {
615 if (!context_->SetLiteralToFalse(other)) return false;
616 }
617 }
618 return RemoveConstraint(ct);
619 }
620
621 if (context_->LiteralIsFalse(literal)) {
622 changed = true;
623 continue;
624 }
625
626 // A singleton variable with or without cost can be removed. See below.
627 if (context_->VariableIsUniqueAndRemovable(literal)) {
628 // A variable that doesn't appear in the objective can be seen as
629 // appearing with a coefficient of zero.
630 singleton_literal_with_cost.push_back({literal, 0});
631 continue;
632 }
633 if (context_->VariableWithCostIsUniqueAndRemovable(literal)) {
634 const auto it = context_->ObjectiveMap().find(PositiveRef(literal));
635 DCHECK(it != context_->ObjectiveMap().end());
636 if (RefIsPositive(literal)) {
637 singleton_literal_with_cost.push_back({literal, it->second});
638 } else {
639 // Note that we actually just store the objective change if this literal
640 // is true compared to it being false.
641 singleton_literal_with_cost.push_back({literal, -it->second});
642 }
643 continue;
644 }
645
646 context_->tmp_literals.push_back(literal);
647 }
648
649 bool transform_to_at_most_one = false;
650 if (!singleton_literal_with_cost.empty()) {
651 changed = true;
652
653 // By domination argument, we can fix to false everything but the minimum.
654 if (singleton_literal_with_cost.size() > 1) {
655 absl::c_stable_sort(
656 singleton_literal_with_cost,
657 [](const std::pair<int, int64_t>& a,
658 const std::pair<int, int64_t>& b) { return a.second < b.second; });
659 for (int i = 1; i < singleton_literal_with_cost.size(); ++i) {
660 context_->UpdateRuleStats("at_most_one: dominated singleton");
661 if (!context_->SetLiteralToFalse(
662 singleton_literal_with_cost[i].first)) {
663 return false;
664 }
665 }
666 singleton_literal_with_cost.resize(1);
667 }
668
669 const int literal = singleton_literal_with_cost[0].first;
670 const int64_t literal_cost = singleton_literal_with_cost[0].second;
671 if (is_at_most_one && literal_cost >= 0) {
672 // We can just always set it to false in this case.
673 context_->UpdateRuleStats("at_most_one: singleton");
674 if (!context_->SetLiteralToFalse(literal)) return false;
675 } else if (context_->ShiftCostInExactlyOne(*literals, literal_cost)) {
676 // We can make the constraint an exactly one if needed since it is always
677 // beneficial to set this literal to true if everything else is zero. Now
678 // that we have an exactly one, we can transfer the cost to the other
679 // terms. The objective of literal should become zero, and we can then
680 // decide its value at postsolve and just have an at most one on the other
681 // literals.
682 DCHECK(!context_->ObjectiveMap().contains(PositiveRef(literal)));
683
684 if (!is_at_most_one) transform_to_at_most_one = true;
685 is_at_most_one = true;
686
687 context_->UpdateRuleStats("exactly_one: singleton");
688 context_->MarkVariableAsRemoved(PositiveRef(literal));
689
690 // Put a constraint in the mapping proto for postsolve.
691 auto* mapping_exo = context_->NewMappingConstraint(__FILE__, __LINE__)
692 ->mutable_exactly_one();
693 for (const int lit : context_->tmp_literals) {
694 mapping_exo->add_literals(lit);
695 }
696 mapping_exo->add_literals(literal);
697 } else {
698 // If ShiftCostInExactlyOne() failed, keep the literal in the amo.
699 context_->tmp_literals.push_back(literal);
700 }
701 }
702
703 if (!is_at_most_one && !transform_to_at_most_one &&
704 context_->ExploitExactlyOneInObjective(context_->tmp_literals)) {
705 context_->UpdateRuleStats("exactly_one: simplified objective");
706 }
707
708 if (transform_to_at_most_one) {
709 CHECK(changed);
710 ct->Clear();
711 literals = ct->mutable_at_most_one()->mutable_literals();
712 }
713 if (changed) {
714 literals->Clear();
715 for (const int lit : context_->tmp_literals) {
716 literals->Add(lit);
717 }
718 context_->UpdateRuleStats(absl::StrCat(name, "removed literals"));
719 }
720 return changed;
721}
722
723bool CpModelPresolver::PresolveAtMostOne(ConstraintProto* ct) {
724 if (context_->ModelIsUnsat()) return false;
725
726 CHECK(!HasEnforcementLiteral(*ct));
727 const bool changed = PresolveAtMostOrExactlyOne(ct);
728 if (ct->constraint_case() != ConstraintProto::kAtMostOne) return changed;
729
730 // Size zero: ok.
731 const auto& literals = ct->at_most_one().literals();
732 if (literals.empty()) {
733 context_->UpdateRuleStats("at_most_one: empty or all false");
734 return RemoveConstraint(ct);
735 }
736
737 // Size one: always satisfied.
738 if (literals.size() == 1) {
739 context_->UpdateRuleStats("at_most_one: size one");
740 return RemoveConstraint(ct);
741 }
742
743 return changed;
744}
745
746bool CpModelPresolver::PresolveExactlyOne(ConstraintProto* ct) {
747 if (context_->ModelIsUnsat()) return false;
748 CHECK(!HasEnforcementLiteral(*ct));
749 const bool changed = PresolveAtMostOrExactlyOne(ct);
750 if (ct->constraint_case() != ConstraintProto::kExactlyOne) return changed;
751
752 // Size zero: UNSAT.
753 const auto& literals = ct->exactly_one().literals();
754 if (literals.empty()) {
755 return context_->NotifyThatModelIsUnsat("exactly_one: empty or all false");
756 }
757
758 // Size one: fix variable.
759 if (literals.size() == 1) {
760 context_->UpdateRuleStats("exactly_one: size one");
761 if (!context_->SetLiteralToTrue(literals[0])) return false;
762 return RemoveConstraint(ct);
763 }
764
765 // Size two: Equivalence.
766 if (literals.size() == 2) {
767 context_->UpdateRuleStats("exactly_one: size two");
768 if (!context_->StoreBooleanEqualityRelation(literals[0],
769 NegatedRef(literals[1]))) {
770 return false;
771 }
772 return RemoveConstraint(ct);
773 }
774
775 return changed;
776}
777
778bool CpModelPresolver::CanonicalizeLinearArgument(const ConstraintProto& ct,
779 LinearArgumentProto* proto) {
780 if (context_->ModelIsUnsat()) return false;
781
782 // Canonicalize all involved expression.
783 bool changed = CanonicalizeLinearExpression(ct, proto->mutable_target());
784 for (LinearExpressionProto& exp : *(proto->mutable_exprs())) {
785 changed |= CanonicalizeLinearExpression(ct, &exp);
786 }
787 return changed;
788}
789
790// Deal with X = lin_max(exprs) where all exprs are divisible by gcd.
791// X must be divisible also, and we can divide everything.
792bool CpModelPresolver::DivideLinMaxByGcd(int c, ConstraintProto* ct) {
793 LinearArgumentProto* lin_max = ct->mutable_lin_max();
794
795 // Compute gcd of exprs first.
796 int64_t gcd = 0;
797 for (const LinearExpressionProto& expr : lin_max->exprs()) {
798 gcd = LinearExpressionGcd(expr, gcd);
799 if (gcd == 1) break;
800 }
801 if (gcd <= 1) return true;
802
803 // TODO(user): deal with all UNSAT case.
804 // Also if the target is affine, we can canonicalize it.
805 const LinearExpressionProto& target = lin_max->target();
806 const int64_t old_gcd = gcd;
807 gcd = LinearExpressionGcd(target, gcd);
808 if (gcd != old_gcd) {
809 if (target.vars().empty()) {
810 return context_->NotifyThatModelIsUnsat("infeasible lin_max");
811 }
812
813 // If the target is affine, we can solve the diophantine equation and
814 // express the target in term of a new variable.
815 if (target.vars().size() == 1) {
816 gcd = old_gcd;
817 context_->UpdateRuleStats("lin_max: canonicalize target using gcd");
818 if (!context_->CanonicalizeAffineVariable(
819 target.vars(0), target.coeffs(0), gcd, -target.offset())) {
820 return false;
821 }
822 CanonicalizeLinearExpression(*ct, lin_max->mutable_target());
823 context_->UpdateConstraintVariableUsage(c);
824 CHECK_EQ(LinearExpressionGcd(target, gcd), gcd);
825 } else {
826 context_->UpdateRuleStats(
827 "TODO lin_max: lhs not trivially divisible by rhs gcd");
828 }
829 }
830 if (gcd <= 1) return true;
831
832 context_->UpdateRuleStats("lin_max: divising by gcd");
833 DivideLinearExpression(gcd, lin_max->mutable_target());
834 for (LinearExpressionProto& expr : *lin_max->mutable_exprs()) {
835 DivideLinearExpression(gcd, &expr);
836 }
837 return true;
838}
839
840namespace {
841
842int64_t EvaluateSingleVariableExpression(const LinearExpressionProto& expr,
843 int var, int64_t value) {
844 int64_t result = expr.offset();
845 for (int i = 0; i < expr.vars().size(); ++i) {
846 CHECK_EQ(expr.vars(i), var);
847 result += expr.coeffs(i) * value;
848 }
849 return result;
850}
851
852template <class ExpressionList>
853int GetFirstVar(ExpressionList exprs) {
854 for (const LinearExpressionProto& expr : exprs) {
855 for (const int var : expr.vars()) {
856 DCHECK(RefIsPositive(var));
857 return var;
858 }
859 }
860 return -1;
861}
862
863} // namespace
864
865bool CpModelPresolver::PropagateAndReduceAffineMax(ConstraintProto* ct) {
866 // Get the unique variable appearing in the expressions.
867 const int unique_var = GetFirstVar(ct->lin_max().exprs());
868
869 const auto& lin_max = ct->lin_max();
870 const int num_exprs = lin_max.exprs_size();
871 const auto& target = lin_max.target();
872 std::vector<int> num_wins(num_exprs, 0);
873 std::vector<int64_t> reachable_target_values;
874 std::vector<int64_t> valid_variable_values;
875 std::vector<int64_t> tmp_values(num_exprs);
876
877 const bool target_has_same_unique_var =
878 target.vars_size() == 1 && target.vars(0) == unique_var;
879
880 CHECK_LE(context_->DomainOf(unique_var).Size(), 1000);
881
882 for (const int64_t value : context_->DomainOf(unique_var).Values()) {
883 int64_t current_max = std::numeric_limits<int64_t>::min();
884
885 // Fill tmp_values and compute current_max;
886 for (int i = 0; i < num_exprs; ++i) {
887 const int64_t v =
888 EvaluateSingleVariableExpression(lin_max.exprs(i), unique_var, value);
889 current_max = std::max(current_max, v);
890 tmp_values[i] = v;
891 }
892
893 // Check if any expr produced a value compatible with the target.
894 if (!context_->DomainContains(target, current_max)) continue;
895
896 // Special case: affine(x) == max(exprs(x)). We can check if the affine()
897 // and the max(exprs) are compatible.
898 if (target_has_same_unique_var &&
899 EvaluateSingleVariableExpression(target, unique_var, value) !=
900 current_max) {
901 continue;
902 }
903
904 valid_variable_values.push_back(value);
905 reachable_target_values.push_back(current_max);
906 for (int i = 0; i < num_exprs; ++i) {
907 DCHECK_LE(tmp_values[i], current_max);
908 if (tmp_values[i] == current_max) {
909 num_wins[i]++;
910 }
911 }
912 }
913
914 if (reachable_target_values.empty() || valid_variable_values.empty()) {
915 return MarkConstraintAsFalse(ct,
916 "lin_max: infeasible affine_max constraint");
917 }
918
919 {
920 bool reduced = false;
921 if (!context_->IntersectDomainWith(
922 target, Domain::FromValues(reachable_target_values), &reduced)) {
923 return true;
924 }
925 if (reduced) {
926 context_->UpdateRuleStats("lin_max: affine_max target domain reduced");
927 }
928 }
929
930 {
931 bool reduced = false;
932 if (!context_->IntersectDomainWith(
933 unique_var, Domain::FromValues(valid_variable_values), &reduced)) {
934 return true;
935 }
936 if (reduced) {
937 context_->UpdateRuleStats(
938 "lin_max: unique affine_max var domain reduced");
939 }
940 }
941
942 // If one expression always wins, even tied, we can eliminate all the others.
943 for (int i = 0; i < num_exprs; ++i) {
944 if (num_wins[i] == valid_variable_values.size()) {
945 const LinearExpressionProto winner_expr = lin_max.exprs(i);
946 ct->mutable_lin_max()->clear_exprs();
947 *ct->mutable_lin_max()->add_exprs() = winner_expr;
948 break;
949 }
950 }
951
952 bool changed = false;
953 if (ct->lin_max().exprs_size() > 1) {
954 int new_size = 0;
955 for (int i = 0; i < num_exprs; ++i) {
956 if (num_wins[i] == 0) continue;
957 *ct->mutable_lin_max()->mutable_exprs(new_size) = ct->lin_max().exprs(i);
958 new_size++;
959 }
960 if (new_size < ct->lin_max().exprs_size()) {
961 context_->UpdateRuleStats("lin_max: removed affine_max exprs");
962 google::protobuf::util::Truncate(ct->mutable_lin_max()->mutable_exprs(),
963 new_size);
964 changed = true;
965 }
966 }
967
968 if (context_->IsFixed(target)) {
969 context_->UpdateRuleStats("lin_max: fixed affine_max target");
970 return RemoveConstraint(ct);
971 }
972
973 if (target_has_same_unique_var) {
974 context_->UpdateRuleStats("lin_max: target_affine(x) = max(affine_i(x))");
975 return RemoveConstraint(ct);
976 }
977
978 // Remove the affine_max constraint if the target is removable and if domains
979 // have been propagated without loss. For now, we known that there is no loss
980 // if the target is a single ref. Since all the expression are affine, in this
981 // case we are fine.
982 if (ExpressionContainsSingleRef(target) &&
983 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
984 context_->MarkVariableAsRemoved(target.vars(0));
985 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
986 context_->UpdateRuleStats("lin_max: unused affine_max target");
987 return RemoveConstraint(ct);
988 }
989
990 return changed;
991}
992
993bool CpModelPresolver::PropagateAndReduceLinMax(ConstraintProto* ct) {
994 const LinearExpressionProto& target = ct->lin_max().target();
995
996 // Compute the infered min/max of the target.
997 // Update target domain (if it is not a complex expression).
998 {
999 int64_t infered_min = context_->MinOf(target);
1000 int64_t infered_max = std::numeric_limits<int64_t>::min();
1001 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1002 infered_min = std::max(infered_min, context_->MinOf(expr));
1003 infered_max = std::max(infered_max, context_->MaxOf(expr));
1004 }
1005
1006 if (target.vars().empty()) {
1007 if (!Domain(infered_min, infered_max).Contains(target.offset())) {
1008 return MarkConstraintAsFalse(ct, "lin_max: infeasible");
1009 }
1010 }
1011 if (target.vars().size() <= 1) { // Affine
1012 Domain rhs_domain;
1013 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1014 rhs_domain = rhs_domain.UnionWith(
1015 context_->DomainSuperSetOf(expr).IntersectionWith(
1016 {infered_min, infered_max}));
1017 }
1018 bool reduced = false;
1019 if (!context_->IntersectDomainWith(target, rhs_domain, &reduced)) {
1020 return true;
1021 }
1022 if (reduced) {
1023 context_->UpdateRuleStats("lin_max: target domain reduced");
1024 }
1025 }
1026 }
1027
1028 // Filter the expressions which are smaller than target_min.
1029 const int64_t target_min = context_->MinOf(target);
1030 bool changed = false;
1031 {
1032 // If one expression is >= target_min,
1033 // We can remove all the expression <= target min.
1034 //
1035 // Note that we must keep an expression >= target_min though, for corner
1036 // case like [2,3] = max([2], [0][3]);
1037 bool has_greater_or_equal_to_target_min = false;
1038 int64_t max_at_index_to_keep = std::numeric_limits<int64_t>::min();
1039 int index_to_keep = -1;
1040 for (int i = 0; i < ct->lin_max().exprs_size(); ++i) {
1041 const LinearExpressionProto& expr = ct->lin_max().exprs(i);
1042 if (context_->MinOf(expr) >= target_min) {
1043 const int64_t expr_max = context_->MaxOf(expr);
1044 if (expr_max > max_at_index_to_keep) {
1045 max_at_index_to_keep = expr_max;
1046 index_to_keep = i;
1047 }
1048 has_greater_or_equal_to_target_min = true;
1049 }
1050 }
1051
1052 int new_size = 0;
1053 for (int i = 0; i < ct->lin_max().exprs_size(); ++i) {
1054 const LinearExpressionProto& expr = ct->lin_max().exprs(i);
1055 const int64_t expr_max = context_->MaxOf(expr);
1056 // TODO(user): Also remove expression whose domain is incompatible with
1057 // the target even if the bounds are like [2] and [0][3]?
1058 if (expr_max < target_min) continue;
1059 if (expr_max == target_min && has_greater_or_equal_to_target_min &&
1060 i != index_to_keep) {
1061 continue;
1062 }
1063 *ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
1064 new_size++;
1065 }
1066 if (new_size < ct->lin_max().exprs_size()) {
1067 context_->UpdateRuleStats("lin_max: removed exprs");
1068 google::protobuf::util::Truncate(ct->mutable_lin_max()->mutable_exprs(),
1069 new_size);
1070 changed = true;
1071 }
1072 }
1073
1074 return changed;
1075}
1076
1077bool CpModelPresolver::PresolveLinMax(int c, ConstraintProto* ct) {
1078 if (context_->ModelIsUnsat()) return false;
1079 // TODO(user): add support for this case.
1080 if (HasEnforcementLiteral(*ct)) return false;
1081 const LinearExpressionProto& target = ct->lin_max().target();
1082
1083 // x = max(x, xi...) => forall i, x >= xi.
1084 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1085 if (LinearExpressionProtosAreEqual(expr, target)) {
1086 for (const LinearExpressionProto& e : ct->lin_max().exprs()) {
1087 if (LinearExpressionProtosAreEqual(e, target)) continue;
1088 LinearConstraintProto* prec =
1089 context_->working_model->add_constraints()->mutable_linear();
1090 prec->add_domain(0);
1091 prec->add_domain(std::numeric_limits<int64_t>::max());
1094 }
1095 context_->UpdateRuleStats("lin_max: x = max(x, ...)");
1096 return RemoveConstraint(ct);
1097 }
1098 }
1099
1100 const bool is_one_var_affine_max =
1101 ExpressionsContainsOnlyOneVar(ct->lin_max().exprs()) &&
1102 ct->lin_max().target().vars_size() <= 1;
1103 bool unique_var_is_small_enough = false;
1104 const bool is_int_abs = IsAffineIntAbs(*ct);
1105
1106 if (is_one_var_affine_max) {
1107 const int unique_var = GetFirstVar(ct->lin_max().exprs());
1108 unique_var_is_small_enough = context_->DomainOf(unique_var).Size() <= 1000;
1109 }
1110
1111 bool changed;
1112 if (is_one_var_affine_max && unique_var_is_small_enough) {
1113 changed = PropagateAndReduceAffineMax(ct);
1114 } else if (is_int_abs) {
1115 changed = PropagateAndReduceIntAbs(ct);
1116 } else {
1117 changed = PropagateAndReduceLinMax(ct);
1118 }
1119
1120 if (context_->ModelIsUnsat()) return false;
1121 if (ct->constraint_case() != ConstraintProto::kLinMax) {
1122 // The constraint was removed by the propagate helpers.
1123 return changed;
1124 }
1125
1126 if (ct->lin_max().exprs().empty()) {
1127 return MarkConstraintAsFalse(ct, "lin_max: no exprs");
1128 }
1129
1130 // Try to reduce lin_max using known relation.
1131 if (ct->lin_max().exprs().size() < 10) {
1132 const int num_exprs = ct->lin_max().exprs().size();
1133
1134 bool simplified = false;
1135 std::vector<bool> can_be_removed(num_exprs, false);
1136 for (int i = 0; i < num_exprs; ++i) {
1137 if (ct->lin_max().exprs(i).vars().size() != 1) continue;
1138 for (int j = 0; j < num_exprs; ++j) {
1139 if (i == j) continue;
1140 if (can_be_removed[j]) continue;
1141
1142 // Note that we skip constant expressions as this should already be
1143 // handled when we compute the domain of each expression and remove
1144 // the ones that are smaller than the target.
1145 if (ct->lin_max().exprs(j).vars().size() != 1) continue;
1146
1147 // Do we know if expr(i) <= expr(j) ?
1148 const LinearExpression2 expr2 = GetLinearExpression2FromProto(
1149 ct->lin_max().exprs(i).vars(0), ct->lin_max().exprs(i).coeffs(0),
1150 ct->lin_max().exprs(j).vars(0), -ct->lin_max().exprs(j).coeffs(0));
1151 const IntegerValue lb = kMinIntegerValue;
1152 const IntegerValue ub(ct->lin_max().exprs(j).offset() -
1153 ct->lin_max().exprs(i).offset());
1154 const RelationStatus status = known_linear2_.GetStatus(expr2, lb, ub);
1155 if (status == RelationStatus::IS_TRUE) {
1156 simplified = true;
1157 can_be_removed[i] = true;
1158 break;
1159 }
1160 }
1161 }
1162
1163 if (simplified) {
1164 context_->UpdateRuleStats(
1165 "lin_max: removed expression smaller than others");
1166 int new_size = 0;
1167 for (int i = 0; i < num_exprs; ++i) {
1168 if (can_be_removed[i]) continue;
1169 *ct->mutable_lin_max()->mutable_exprs(new_size++) =
1170 ct->lin_max().exprs(i);
1171 }
1172 google::protobuf::util::Truncate(ct->mutable_lin_max()->mutable_exprs(),
1173 new_size);
1174 context_->UpdateConstraintVariableUsage(c);
1175 }
1176 }
1177
1178 // If only one is left, we can convert to an equality. Note that we create a
1179 // new constraint otherwise it might not be processed again.
1180 if (ct->lin_max().exprs().size() == 1) {
1181 context_->UpdateRuleStats("lin_max: converted to equality");
1182 ConstraintProto* new_ct = context_->working_model->add_constraints();
1183 *new_ct = *ct; // copy name and potential reification.
1184 auto* arg = new_ct->mutable_linear();
1185 const LinearExpressionProto& a = ct->lin_max().target();
1186 const LinearExpressionProto& b = ct->lin_max().exprs(0);
1187 for (int i = 0; i < a.vars().size(); ++i) {
1188 arg->add_vars(a.vars(i));
1189 arg->add_coeffs(a.coeffs(i));
1190 }
1191 for (int i = 0; i < b.vars().size(); ++i) {
1192 arg->add_vars(b.vars(i));
1193 arg->add_coeffs(-b.coeffs(i));
1194 }
1195 arg->add_domain(b.offset() - a.offset());
1196 arg->add_domain(b.offset() - a.offset());
1197 context_->UpdateNewConstraintsVariableUsage();
1198 return RemoveConstraint(ct);
1199 }
1200
1201 if (!DivideLinMaxByGcd(c, ct)) return false;
1202
1203 // Cut everything above the max if possible.
1204 // If one of the linear expression has many term and is above the max, we
1205 // abort early since none of the other rule can be applied.
1206 const int64_t target_min = context_->MinOf(target);
1207 const int64_t target_max = context_->MaxOf(target);
1208 {
1209 bool abort = false;
1210 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1211 const int64_t value_min = context_->MinOf(expr);
1212 bool modified = false;
1213 if (!context_->IntersectDomainWith(expr, Domain(value_min, target_max),
1214 &modified)) {
1215 return true;
1216 }
1217 if (modified) {
1218 context_->UpdateRuleStats("lin_max: reduced expression domain");
1219 }
1220 const int64_t value_max = context_->MaxOf(expr);
1221 if (value_max > target_max) {
1222 context_->UpdateRuleStats("TODO lin_max: linear expression above max");
1223 abort = true;
1224 }
1225 }
1226 if (abort) return changed;
1227 }
1228
1229 // Checks if the affine target domain is constraining.
1230 bool linear_target_domain_contains_max_domain = false;
1231 if (ExpressionContainsSingleRef(target)) { // target = +/- var.
1232 int64_t infered_min = std::numeric_limits<int64_t>::min();
1233 int64_t infered_max = std::numeric_limits<int64_t>::min();
1234 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1235 infered_min = std::max(infered_min, context_->MinOf(expr));
1236 infered_max = std::max(infered_max, context_->MaxOf(expr));
1237 }
1238 Domain rhs_domain;
1239 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1240 rhs_domain = rhs_domain.UnionWith(
1241 context_->DomainSuperSetOf(expr).IntersectionWith(
1242 {infered_min, infered_max}));
1243 }
1244
1245 // Checks if all values from the max(exprs) belong in the domain of the
1246 // target.
1247 // Note that the target is +/-var.
1248 DCHECK_EQ(std::abs(target.coeffs(0)), 1);
1249 const Domain target_domain =
1250 target.coeffs(0) == 1 ? context_->DomainOf(target.vars(0))
1251 : context_->DomainOf(target.vars(0)).Negation();
1252 linear_target_domain_contains_max_domain =
1253 rhs_domain.IsIncludedIn(target_domain);
1254 }
1255
1256 // Avoid to remove the constraint for special cases:
1257 // affine(x) = max(expr(x, ...), ...);
1258 //
1259 // TODO(user): We could presolve this, but there are a few type of cases.
1260 // for example:
1261 // - x = max(x + 3, ...) : infeasible.
1262 // - x = max(x - 2, ...) : reduce arity: x = max(...)
1263 // - x = max(2x, ...) we have x <= 0
1264 // - etc...
1265 // Actually, I think for the expr=affine' case, it reduces to:
1266 // affine(x) >= affine'(x)
1267 // affine(x) = max(...);
1268 if (linear_target_domain_contains_max_domain) {
1269 const int target_var = target.vars(0);
1270 bool abort = false;
1271 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1272 for (const int var : expr.vars()) {
1273 if (var == target_var &&
1274 !LinearExpressionProtosAreEqual(expr, target)) {
1275 abort = true;
1276 break;
1277 }
1278 }
1279 if (abort) break;
1280 }
1281 if (abort) {
1282 // Actually the expression can be more than affine.
1283 // We only know that the target is affine here.
1284 context_->UpdateRuleStats(
1285 "TODO lin_max: affine(x) = max(affine'(x), ...) !!");
1286 linear_target_domain_contains_max_domain = false;
1287 }
1288 }
1289
1290 // If the target is not used, and safe, we can remove the constraint.
1291 if (linear_target_domain_contains_max_domain &&
1292 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
1293 context_->UpdateRuleStats("lin_max: unused affine target");
1294 context_->MarkVariableAsRemoved(target.vars(0));
1295 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1296 return RemoveConstraint(ct);
1297 }
1298
1299 // If the target is only used in the objective, and safe, we can simplify the
1300 // constraint.
1301 if (linear_target_domain_contains_max_domain &&
1302 context_->VariableWithCostIsUniqueAndRemovable(target.vars(0)) &&
1303 (target.coeffs(0) > 0) ==
1304 (context_->ObjectiveCoeff(target.vars(0)) > 0)) {
1305 context_->UpdateRuleStats("lin_max: rewrite with precedences");
1306 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1307 LinearConstraintProto* prec =
1308 context_->working_model->add_constraints()->mutable_linear();
1309 prec->add_domain(0);
1310 prec->add_domain(std::numeric_limits<int64_t>::max());
1313 }
1314 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1315 return RemoveConstraint(ct);
1316 }
1317
1318 // Deal with fixed target case.
1319 if (target_min == target_max) {
1320 bool all_booleans = true;
1321 std::vector<int> literals;
1322 const int64_t fixed_target = target_min;
1323 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1324 const int64_t value_min = context_->MinOf(expr);
1325 const int64_t value_max = context_->MaxOf(expr);
1326 CHECK_LE(value_max, fixed_target) << "Presolved above";
1327 if (value_max < fixed_target) continue;
1328
1329 if (value_min == value_max && value_max == fixed_target) {
1330 context_->UpdateRuleStats("lin_max: always satisfied");
1331 return RemoveConstraint(ct);
1332 }
1333 if (context_->ExpressionIsAffineBoolean(expr)) {
1334 CHECK_EQ(value_max, fixed_target);
1335 literals.push_back(context_->LiteralForExpressionMax(expr));
1336 } else {
1337 all_booleans = false;
1338 }
1339 }
1340 if (all_booleans) {
1341 if (literals.empty()) {
1342 return MarkConstraintAsFalse(ct, "lin_max: all boolean and no support");
1343 }
1344
1345 // At least one true;
1346 context_->UpdateRuleStats("lin_max: fixed target and all booleans");
1347 for (const int lit : literals) {
1348 ct->mutable_bool_or()->add_literals(lit);
1349 }
1350 return true;
1351 }
1352 return changed;
1353 }
1354
1355 changed |= PresolveLinMaxWhenAllBoolean(ct);
1356 return changed;
1357}
1358
1359// If everything is Boolean and affine, do not use a lin max!
1360bool CpModelPresolver::PresolveLinMaxWhenAllBoolean(ConstraintProto* ct) {
1361 if (context_->ModelIsUnsat()) return false;
1362 if (HasEnforcementLiteral(*ct)) return false;
1363
1364 const LinearExpressionProto& target = ct->lin_max().target();
1365 if (!context_->ExpressionIsAffineBoolean(target)) return false;
1366
1367 const int64_t target_min = context_->MinOf(target);
1368 const int64_t target_max = context_->MaxOf(target);
1369 const int target_ref = context_->LiteralForExpressionMax(target);
1370
1371 bool min_is_reachable = false;
1372 std::vector<int> min_literals;
1373 std::vector<int> literals_above_min;
1374 std::vector<int> max_literals;
1375
1376 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1377 if (!context_->ExpressionIsAffineBoolean(expr)) return false;
1378 const int64_t value_min = context_->MinOf(expr);
1379 const int64_t value_max = context_->MaxOf(expr);
1380 const int ref = context_->LiteralForExpressionMax(expr);
1381
1382 // Get corner case out of the way, and wait for the constraint to be
1383 // processed again in these case.
1384 if (value_min > target_min) {
1385 context_->UpdateRuleStats("lin_max: fix target");
1386 (void)context_->SetLiteralToTrue(target_ref);
1387 return false;
1388 }
1389 if (value_max > target_max) {
1390 context_->UpdateRuleStats("lin_max: fix bool expr");
1391 (void)context_->SetLiteralToFalse(ref);
1392 return false;
1393 }
1394
1395 // expr is fixed.
1396 if (value_min == value_max) {
1397 if (value_min == target_min) min_is_reachable = true;
1398 continue;
1399 }
1400
1401 CHECK_LE(value_min, target_min);
1402 if (value_min == target_min) {
1403 min_literals.push_back(NegatedRef(ref));
1404 }
1405
1406 CHECK_LE(value_max, target_max);
1407 if (value_max == target_max) {
1408 max_literals.push_back(ref);
1409 literals_above_min.push_back(ref);
1410 } else if (value_max > target_min) {
1411 literals_above_min.push_back(ref);
1412 } else if (value_max == target_min) {
1413 min_literals.push_back(ref);
1414 }
1415 }
1416
1417 context_->UpdateRuleStats("lin_max: all booleans");
1418
1419 // target_ref => at_least_one(max_literals);
1420 ConstraintProto* clause = context_->working_model->add_constraints();
1421 clause->add_enforcement_literal(target_ref);
1422 clause->mutable_bool_or();
1423 for (const int lit : max_literals) {
1424 clause->mutable_bool_or()->add_literals(lit);
1425 }
1426
1427 // not(target_ref) => not(lit) for lit in literals_above_min
1428 for (const int lit : literals_above_min) {
1429 context_->AddImplication(lit, target_ref);
1430 }
1431
1432 if (!min_is_reachable) {
1433 // not(target_ref) => at_least_one(min_literals).
1434 ConstraintProto* clause = context_->working_model->add_constraints();
1435 clause->add_enforcement_literal(NegatedRef(target_ref));
1436 clause->mutable_bool_or();
1437 for (const int lit : min_literals) {
1438 clause->mutable_bool_or()->add_literals(lit);
1439 }
1440 }
1441
1442 context_->UpdateNewConstraintsVariableUsage();
1443 return RemoveConstraint(ct);
1444}
1445
1446// This presolve expect that the constraint only contains 1-var affine
1447// expressions.
1448bool CpModelPresolver::PropagateAndReduceIntAbs(ConstraintProto* ct) {
1449 CHECK_EQ(ct->enforcement_literal_size(), 0);
1450 if (context_->ModelIsUnsat()) return false;
1451 const LinearExpressionProto& target_expr = ct->lin_max().target();
1452 const LinearExpressionProto& expr = ct->lin_max().exprs(0);
1453 DCHECK_EQ(expr.vars_size(), 1);
1454
1455 // Propagate domain from the expression to the target.
1456 {
1457 const Domain expr_domain = context_->DomainSuperSetOf(expr);
1458 const Domain new_target_domain =
1459 expr_domain.UnionWith(expr_domain.Negation())
1460 .IntersectionWith({0, std::numeric_limits<int64_t>::max()});
1461 bool target_domain_modified = false;
1462 if (!context_->IntersectDomainWith(target_expr, new_target_domain,
1463 &target_domain_modified)) {
1464 return false;
1465 }
1466 if (expr_domain.IsFixed()) {
1467 context_->UpdateRuleStats("lin_max: fixed expression in int_abs");
1468 return RemoveConstraint(ct);
1469 }
1470 if (target_domain_modified) {
1471 context_->UpdateRuleStats("lin_max: propagate domain from x to abs(x)");
1472 }
1473 }
1474
1475 // Propagate from target domain to variable.
1476 {
1477 const Domain target_domain =
1478 context_->DomainSuperSetOf(target_expr)
1479 .IntersectionWith(Domain(0, std::numeric_limits<int64_t>::max()));
1480 const Domain new_expr_domain =
1481 target_domain.UnionWith(target_domain.Negation());
1482 bool expr_domain_modified = false;
1483 if (!context_->IntersectDomainWith(expr, new_expr_domain,
1484 &expr_domain_modified)) {
1485 return true;
1486 }
1487 // This is the only reason why we don't support fully generic linear
1488 // expression.
1489 if (context_->IsFixed(target_expr)) {
1490 context_->UpdateRuleStats("lin_max: fixed abs target");
1491 return RemoveConstraint(ct);
1492 }
1493 if (expr_domain_modified) {
1494 context_->UpdateRuleStats("lin_max: propagate domain from abs(x) to x");
1495 }
1496 }
1497
1498 // Convert to equality if the sign of expr is fixed.
1499 if (context_->MinOf(expr) >= 0) {
1500 context_->UpdateRuleStats("lin_max: converted abs to equality");
1501 ConstraintProto* new_ct = context_->working_model->add_constraints();
1502 new_ct->set_name(ct->name());
1503 auto* arg = new_ct->mutable_linear();
1504 arg->add_domain(0);
1505 arg->add_domain(0);
1506 AddLinearExpressionToLinearConstraint(target_expr, 1, arg);
1508 bool changed = false;
1509 if (!CanonicalizeLinear(new_ct, &changed)) {
1510 return true;
1511 }
1512 context_->UpdateNewConstraintsVariableUsage();
1513 return RemoveConstraint(ct);
1514 }
1515
1516 if (context_->MaxOf(expr) <= 0) {
1517 context_->UpdateRuleStats("lin_max: converted abs to equality");
1518 ConstraintProto* new_ct = context_->working_model->add_constraints();
1519 new_ct->set_name(ct->name());
1520 auto* arg = new_ct->mutable_linear();
1521 arg->add_domain(0);
1522 arg->add_domain(0);
1523 AddLinearExpressionToLinearConstraint(target_expr, 1, arg);
1525 bool changed = false;
1526 if (!CanonicalizeLinear(new_ct, &changed)) {
1527 return true;
1528 }
1529 context_->UpdateNewConstraintsVariableUsage();
1530 return RemoveConstraint(ct);
1531 }
1532
1533 // Remove the abs constraint if the target is removable and if domains have
1534 // been propagated without loss.
1535 // For now, we known that there is no loss if the target is a single ref.
1536 // Since all the expression are affine, in this case we are fine.
1537 if (ExpressionContainsSingleRef(target_expr) &&
1538 context_->VariableIsUniqueAndRemovable(target_expr.vars(0))) {
1539 context_->MarkVariableAsRemoved(target_expr.vars(0));
1540 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1541 context_->UpdateRuleStats("lin_max: unused abs target");
1542 return RemoveConstraint(ct);
1543 }
1544
1545 return false;
1546}
1547
1549 const PresolveContext& context) {
1550 if (expr.exprs().size() == 2) {
1551 const LinearExpressionProto& expr0 = expr.exprs(0);
1552 const LinearExpressionProto& expr1 = expr.exprs(1);
1553 if (LinearExpressionProtosAreEqual(expr0, expr1)) {
1554 return context.DomainSuperSetOf(expr0).SquareSuperset();
1555 }
1556 if (expr0.vars().size() == 1 && expr1.vars().size() == 1 &&
1557 expr0.vars(0) == expr1.vars(0)) {
1558 return context.DomainOf(expr0.vars(0))
1559 .QuadraticSuperset(expr0.coeffs(0), expr0.offset(), expr1.coeffs(0),
1560 expr1.offset());
1561 }
1562 }
1563
1564 Domain implied(1);
1565 for (const LinearExpressionProto& expr : expr.exprs()) {
1566 implied =
1567 implied.ContinuousMultiplicationBy(context.DomainSuperSetOf(expr));
1568 }
1569 return implied;
1570}
1571
1572bool CpModelPresolver::PresolveIntProd(ConstraintProto* ct) {
1573 if (context_->ModelIsUnsat()) return false;
1574
1575 // Start by restricting the domain of target. We will be more precise later.
1576 bool domain_modified = false;
1577 Domain implied_domain =
1578 EvaluateImpliedIntProdDomain(ct->int_prod(), *context_);
1579 // TODO(user): if implied_domain and target domain are disjoint, mark the
1580 // constraint as false.
1581 if (!HasEnforcementLiteral(*ct) &&
1582 !context_->IntersectDomainWith(ct->int_prod().target(), implied_domain,
1583 &domain_modified)) {
1584 return false;
1585 }
1586
1587 // Remove a constraint if the target only appears in the constraint. For this
1588 // to be correct some conditions must be met:
1589 // - The target is an affine linear with coefficient -1 or 1.
1590 // - The target does not appear in the rhs (no x = (a*x + b) * ...).
1591 // - The target domain covers all the possible range of the rhs.
1592 // This can be done whether or not there are enforcement literals, even if
1593 // they are used in the target or the rhs.
1594 if (ExpressionContainsSingleRef(ct->int_prod().target()) &&
1595 context_->VariableIsUniqueAndRemovable(ct->int_prod().target().vars(0)) &&
1596 std::abs(ct->int_prod().target().coeffs(0)) == 1) {
1597 const LinearExpressionProto& target = ct->int_prod().target();
1598 if (!absl::c_any_of(ct->int_prod().exprs(),
1599 [&target](const LinearExpressionProto& expr) {
1600 return absl::c_linear_search(expr.vars(),
1601 target.vars(0));
1602 })) {
1603 const Domain target_domain =
1604 Domain(target.offset())
1605 .AdditionWith(context_->DomainOf(target.vars(0)));
1606 if (implied_domain.IsIncludedIn(target_domain)) {
1607 context_->MarkVariableAsRemoved(ct->int_prod().target().vars(0));
1608 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1609 context_->UpdateRuleStats("int_prod: unused affine target");
1610 return RemoveConstraint(ct);
1611 }
1612 }
1613 }
1614
1615 // Remove constant expressions and compute the product of the max positive
1616 // divisor of each term.
1617 int64_t constant_factor = 1;
1618 int new_size = 0;
1619 bool changed = false;
1620 LinearArgumentProto old_proto = ct->int_prod();
1621 LinearArgumentProto* proto = ct->mutable_int_prod();
1622 for (int i = 0; i < ct->int_prod().exprs().size(); ++i) {
1623 LinearExpressionProto expr = ct->int_prod().exprs(i);
1624 if (context_->IsFixed(expr)) {
1625 const int64_t expr_value = context_->FixedValue(expr);
1626 constant_factor = CapProd(constant_factor, expr_value);
1627 context_->UpdateRuleStats("int_prod: removed constant expressions");
1628 changed = true;
1629 } else {
1630 const int64_t expr_divisor = LinearExpressionGcd(expr);
1631 DivideLinearExpression(expr_divisor, &expr);
1632 constant_factor = CapProd(constant_factor, expr_divisor);
1633 *proto->mutable_exprs(new_size++) = expr;
1634 }
1635 }
1636 proto->mutable_exprs()->erase(proto->mutable_exprs()->begin() + new_size,
1637 proto->mutable_exprs()->end());
1638
1639 if (ct->int_prod().exprs().empty() || constant_factor == 0) {
1640 if (!context_->DomainContains(ct->int_prod().target(), constant_factor)) {
1641 return MarkConstraintAsFalse(ct, "int_prod: always false");
1642 }
1643 if (!HasEnforcementLiteral(*ct)) {
1644 if (!context_->IntersectDomainWith(ct->int_prod().target(),
1645 Domain(constant_factor))) {
1646 return false;
1647 }
1648 context_->UpdateRuleStats("int_prod: constant product");
1649 } else {
1650 // Replace ct with an enforced linear "target == constant_factor".
1651 ConstraintProto* new_ct = context_->working_model->add_constraints();
1652 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
1653 LinearConstraintProto* const lin = new_ct->mutable_linear();
1654 lin->add_domain(constant_factor);
1655 lin->add_domain(constant_factor);
1656 AddLinearExpressionToLinearConstraint(ct->int_prod().target(), 1, lin);
1657 context_->UpdateNewConstraintsVariableUsage();
1658 context_->UpdateRuleStats("enforced int_prod: constant product");
1659 }
1660 return RemoveConstraint(ct);
1661 }
1662
1663 // If target is fixed to zero, we can forget the constant factor.
1664 if (context_->IsFixed(ct->int_prod().target()) &&
1665 context_->FixedValue(ct->int_prod().target()) == 0 &&
1666 constant_factor != 1) {
1667 context_->UpdateRuleStats("int_prod: simplify by constant factor");
1668 constant_factor = 1;
1669 }
1670
1671 // In this case, the only possible value that fits in the domains is zero.
1672 // We will check for UNSAT if zero is not achievable by the rhs below.
1673 if (!HasEnforcementLiteral(*ct) && AtMinOrMaxInt64(constant_factor)) {
1674 context_->UpdateRuleStats("int_prod: overflow if non zero");
1675 if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(0))) {
1676 return false;
1677 }
1678 constant_factor = 1;
1679 }
1680
1681 // Replace with linear if it cannot overflow.
1682 if (ct->int_prod().exprs().size() == 1) {
1683 LinearExpressionProto* const target =
1684 ct->mutable_int_prod()->mutable_target();
1685 ConstraintProto* const new_ct = context_->working_model->add_constraints();
1686 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
1687 LinearConstraintProto* const lin = new_ct->mutable_linear();
1688
1689 if (context_->IsFixed(*target)) {
1690 int64_t target_value = context_->FixedValue(*target);
1691 if (target_value % constant_factor != 0) {
1692 context_->working_model->mutable_constraints()->RemoveLast();
1693 return MarkConstraintAsFalse(
1694 ct, "int_prod: product incompatible with fixed target");
1695 }
1696 // expression == target_value / constant_factor.
1697 lin->add_domain(target_value / constant_factor);
1698 lin->add_domain(target_value / constant_factor);
1699 AddLinearExpressionToLinearConstraint(ct->int_prod().exprs(0), 1, lin);
1700 context_->UpdateNewConstraintsVariableUsage();
1701 context_->UpdateRuleStats("int_prod: expression is constant");
1702 return RemoveConstraint(ct);
1703 }
1704
1705 const int64_t target_divisor = LinearExpressionGcd(*target);
1706
1707 // Reduce coefficients.
1708 const int64_t gcd =
1709 std::gcd(static_cast<uint64_t>(std::abs(constant_factor)),
1710 static_cast<uint64_t>(std::abs(target_divisor)));
1711 if (gcd != 1) {
1712 constant_factor /= gcd;
1713 DivideLinearExpression(gcd, target);
1714 }
1715
1716 // expression * constant_factor = target.
1717 lin->add_domain(0);
1718 lin->add_domain(0);
1719 const bool overflow = !SafeAddLinearExpressionToLinearConstraint(
1720 ct->int_prod().target(), 1, lin) ||
1722 ct->int_prod().exprs(0), -constant_factor, lin);
1723
1724 // Check for overflow.
1725 if (overflow ||
1726 PossibleIntegerOverflow(*context_->working_model, lin->vars(),
1727 lin->coeffs(), lin->domain(0))) {
1728 context_->working_model->mutable_constraints()->RemoveLast();
1729 // The constant factor will be handled by the creation of an affine
1730 // relation below.
1731 } else { // Replace with a linear equation.
1732 context_->UpdateNewConstraintsVariableUsage();
1733 context_->UpdateRuleStats("int_prod: linearize product by constant");
1734 return RemoveConstraint(ct);
1735 }
1736 }
1737
1738 if (constant_factor != 1) {
1739 // Lets canonicalize the target by introducing a new variable if necessary.
1740 //
1741 // coeff * X + offset must be a multiple of constant_factor, so
1742 // we can rewrite X so that this property is clear.
1743 //
1744 // Note(user): it is important for this to have a restricted target domain
1745 // so we can choose a better representative.
1746 const LinearExpressionProto old_target = ct->int_prod().target();
1747 if (!context_->IsFixed(old_target)) {
1748 // The call to CanonicalizeAffineVariable() creates an always enforced
1749 // affine relation or makes the model UNSAT. Both cases are invalid if
1750 // there are enforcement literals.
1751 if (HasEnforcementLiteral(*ct) ||
1752 CapProd(constant_factor, std::max(context_->MaxOf(old_target),
1753 -context_->MinOf(old_target))) >=
1754 std::numeric_limits<int64_t>::max() / 2) {
1755 // Restore the original constraint (we cannot add back a new term for
1756 // the constant factor: this may create a constraint with more than 2
1757 // terms).
1758 *ct->mutable_int_prod() = old_proto;
1759 context_->UpdateRuleStats(
1760 "int_prod: enforcement or overflow prevented creating a affine "
1761 "relation");
1762 return true;
1763 }
1764 const int ref = old_target.vars(0);
1765 const int64_t coeff = old_target.coeffs(0);
1766 const int64_t offset = old_target.offset();
1767 if (!context_->CanonicalizeAffineVariable(ref, coeff, constant_factor,
1768 -offset)) {
1769 return false;
1770 }
1771 if (context_->IsFixed(ref)) {
1772 changed = true;
1773 }
1774 }
1775
1776 // This can happen during CanonicalizeAffineVariable().
1777 if (context_->IsFixed(old_target)) {
1778 const int64_t target_value = context_->FixedValue(old_target);
1779 if (target_value % constant_factor != 0) {
1780 return MarkConstraintAsFalse(
1781 ct, "int_prod: constant factor does not divide constant target");
1782 }
1783 changed = true;
1784 proto->clear_target();
1785 proto->mutable_target()->set_offset(target_value / constant_factor);
1786 context_->UpdateRuleStats(
1787 "int_prod: divide product and fixed target by constant factor");
1788 } else {
1789 // We use absl::int128 to be resistant to overflow here.
1790 const AffineRelation::Relation r =
1791 context_->GetAffineRelation(old_target.vars(0));
1792 const absl::int128 temp_coeff =
1793 absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff);
1794 CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0);
1795 const absl::int128 temp_offset =
1796 absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) +
1797 absl::int128(old_target.offset());
1798 CHECK_EQ(temp_offset % absl::int128(constant_factor), 0);
1799 const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor);
1800 const absl::int128 new_offset =
1801 temp_offset / absl::int128(constant_factor);
1802
1803 // TODO(user): We try to keep coeff/offset small, if this happens, it
1804 // probably means there is no feasible solution involving int64_t and that
1805 // do not causes overflow while evaluating it, but it is hard to be
1806 // exactly sure we are correct here since it depends on the evaluation
1807 // order. Similarly, by introducing intermediate variable we might loose
1808 // solution if this intermediate variable value do not fit on an int64_t.
1809 if (new_coeff > absl::int128(std::numeric_limits<int64_t>::max()) ||
1810 new_coeff < absl::int128(std::numeric_limits<int64_t>::min()) ||
1811 new_offset > absl::int128(std::numeric_limits<int64_t>::max()) ||
1812 new_offset < absl::int128(std::numeric_limits<int64_t>::min())) {
1813 return MarkConstraintAsFalse(
1814 ct, "int_prod: overflow during simplification");
1815 }
1816
1817 // Rewrite the target.
1818 proto->mutable_target()->set_coeffs(0, static_cast<int64_t>(new_coeff));
1819 proto->mutable_target()->set_vars(0, r.representative);
1820 proto->mutable_target()->set_offset(static_cast<int64_t>(new_offset));
1821 context_->UpdateRuleStats("int_prod: divide product by constant factor");
1822 changed = true;
1823 }
1824 }
1825
1826 // Restrict the target domain if possible.
1827 implied_domain = EvaluateImpliedIntProdDomain(ct->int_prod(), *context_);
1828 const bool is_square = ct->int_prod().exprs_size() == 2 &&
1830 ct->int_prod().exprs(0), ct->int_prod().exprs(1));
1831 if (!HasEnforcementLiteral(*ct) &&
1832 !context_->IntersectDomainWith(ct->int_prod().target(), implied_domain,
1833 &domain_modified)) {
1834 return false;
1835 }
1836 if (domain_modified) {
1837 context_->UpdateRuleStats(absl::StrCat(
1838 is_square ? "int_square" : "int_prod", ": reduced target domain"));
1839 }
1840
1841 // y = x * x, we can reduce the domain of x from the domain of y.
1842 if (is_square) {
1843 const int64_t target_max = context_->MaxOf(ct->int_prod().target());
1844 DCHECK_GE(target_max, 0);
1845 const int64_t sqrt_max = FloorSquareRoot(target_max);
1846 bool expr_reduced = false;
1847 if (!HasEnforcementLiteral(*ct) &&
1848 !context_->IntersectDomainWith(ct->int_prod().exprs(0),
1849 {-sqrt_max, sqrt_max}, &expr_reduced)) {
1850 return false;
1851 }
1852 if (expr_reduced) {
1853 context_->UpdateRuleStats("int_square: reduced expr domain");
1854 }
1855 }
1856
1857 if (ct->int_prod().exprs_size() == 2) {
1858 LinearExpressionProto a = ct->int_prod().exprs(0);
1859 LinearExpressionProto b = ct->int_prod().exprs(1);
1860 const LinearExpressionProto product = ct->int_prod().target();
1863 a, product)) { // x = x * x, only true for {0, 1}.
1864 if (!HasEnforcementLiteral(*ct)) {
1865 if (!context_->IntersectDomainWith(product, Domain(0, 1))) {
1866 return false;
1867 }
1868 context_->UpdateRuleStats("int_square: fix variable to zero or one");
1869 return RemoveConstraint(ct);
1870 } else {
1871 context_->UpdateRuleStats(
1872 "TODO enforced int_square: fix variable to zero or one");
1873 // Replace ct with an enforced linear "product in [0, 1]".
1874 }
1875 }
1876 }
1877
1878 if (ct->int_prod().exprs().size() == 2) {
1879 const auto is_boolean_affine =
1880 [context = context_](const LinearExpressionProto& expr) {
1881 return expr.vars().size() == 1 && context->MinOf(expr.vars(0)) == 0 &&
1882 context->MaxOf(expr.vars(0)) == 1;
1883 };
1884 const LinearExpressionProto* boolean_linear = nullptr;
1885 const LinearExpressionProto* other_linear = nullptr;
1886 if (is_boolean_affine(ct->int_prod().exprs(0))) {
1887 boolean_linear = &ct->int_prod().exprs(0);
1888 other_linear = &ct->int_prod().exprs(1);
1889 } else if (is_boolean_affine(ct->int_prod().exprs(1))) {
1890 boolean_linear = &ct->int_prod().exprs(1);
1891 other_linear = &ct->int_prod().exprs(0);
1892 }
1893 if (boolean_linear) {
1894 // We have:
1895 // (u + b * v) * other_expr = B, where `b` is a boolean variable.
1896 //
1897 // We can rewrite this as:
1898 // u * other_expr = B, if b = false;
1899 // (u + v) * other_expr = B, if b = true
1900 ConstraintProto* constraint_for_false =
1901 context_->working_model->add_constraints();
1902 ConstraintProto* constraint_for_true =
1903 context_->working_model->add_constraints();
1904 *constraint_for_true->mutable_enforcement_literal() =
1905 ct->enforcement_literal();
1906 *constraint_for_false->mutable_enforcement_literal() =
1907 ct->enforcement_literal();
1908 constraint_for_true->add_enforcement_literal(boolean_linear->vars(0));
1909 constraint_for_false->add_enforcement_literal(
1910 NegatedRef(boolean_linear->vars(0)));
1911 LinearConstraintProto* linear_for_false =
1912 constraint_for_false->mutable_linear();
1913 LinearConstraintProto* linear_for_true =
1914 constraint_for_true->mutable_linear();
1915
1916 linear_for_false->add_domain(0);
1917 linear_for_false->add_domain(0);
1919 *other_linear, boolean_linear->offset(), linear_for_false);
1920 AddLinearExpressionToLinearConstraint(ct->int_prod().target(), -1,
1921 linear_for_false);
1922
1923 linear_for_true->add_domain(0);
1924 linear_for_true->add_domain(0);
1926 *other_linear, boolean_linear->offset() + boolean_linear->coeffs(0),
1927 linear_for_true);
1928 AddLinearExpressionToLinearConstraint(ct->int_prod().target(), -1,
1929 linear_for_true);
1930 context_->CanonicalizeLinearConstraint(constraint_for_false);
1931 context_->CanonicalizeLinearConstraint(constraint_for_true);
1932 if (PossibleIntegerOverflow(*context_->working_model,
1933 linear_for_false->vars(),
1934 linear_for_false->coeffs()) ||
1935 PossibleIntegerOverflow(*context_->working_model,
1936 linear_for_true->vars(),
1937 linear_for_true->coeffs())) {
1938 context_->working_model->mutable_constraints()->RemoveLast();
1939 context_->working_model->mutable_constraints()->RemoveLast();
1940 } else {
1941 context_->UpdateRuleStats("int_prod: boolean affine term");
1942 context_->UpdateNewConstraintsVariableUsage();
1943 return RemoveConstraint(ct);
1944 }
1945 }
1946 }
1947
1948 // For now, we only presolve the case where all variables are Booleans.
1949 const LinearExpressionProto target_expr = ct->int_prod().target();
1950 int target;
1951 if (!context_->ExpressionIsALiteral(target_expr, &target)) {
1952 return changed;
1953 }
1954 std::vector<int> literals;
1955 for (const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1956 int lit;
1957 if (!context_->ExpressionIsALiteral(expr, &lit)) {
1958 return changed;
1959 }
1960 literals.push_back(lit);
1961 }
1962
1963 // This is a Boolean constraint!
1964 context_->UpdateRuleStats("int_prod: all boolean");
1965 {
1966 ConstraintProto* new_ct = context_->working_model->add_constraints();
1967 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
1968 new_ct->add_enforcement_literal(target);
1969 auto* arg = new_ct->mutable_bool_and();
1970 for (const int lit : literals) {
1971 arg->add_literals(lit);
1972 }
1973 }
1974 {
1975 ConstraintProto* new_ct = context_->working_model->add_constraints();
1976 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
1977 auto* arg = new_ct->mutable_bool_or();
1978 arg->add_literals(target);
1979 for (const int lit : literals) {
1980 arg->add_literals(NegatedRef(lit));
1981 }
1982 }
1983 context_->UpdateNewConstraintsVariableUsage();
1984 return RemoveConstraint(ct);
1985}
1986
1987bool CpModelPresolver::PresolveIntDiv(int c, ConstraintProto* ct) {
1988 if (context_->ModelIsUnsat()) return false;
1989 // TODO(user): add support for this case.
1990 if (HasEnforcementLiteral(*ct)) return false;
1991
1992 const LinearExpressionProto target = ct->int_div().target();
1993 const LinearExpressionProto expr = ct->int_div().exprs(0);
1994 const LinearExpressionProto div = ct->int_div().exprs(1);
1995
1996 if (LinearExpressionProtosAreEqual(expr, div)) {
1997 if (!context_->IntersectDomainWith(target, Domain(1))) {
1998 return false;
1999 }
2000 context_->UpdateRuleStats("int_div: y = x / x");
2001 return RemoveConstraint(ct);
2002 } else if (LinearExpressionProtosAreEqual(expr, div, -1)) {
2003 if (!context_->IntersectDomainWith(target, Domain(-1))) {
2004 return false;
2005 }
2006 context_->UpdateRuleStats("int_div: y = - x / x");
2007 return RemoveConstraint(ct);
2008 }
2009
2010 // Sometimes we have only a single variable appearing in the whole constraint.
2011 // If the domain is small enough, we can just restrict the domain and remove
2012 // the constraint.
2013 if (ct->enforcement_literal().empty() &&
2014 context_->ConstraintToVars(c).size() == 1) {
2015 const int var = context_->ConstraintToVars(c)[0];
2016 if (context_->DomainOf(var).Size() >= 100) {
2017 context_->UpdateRuleStats(
2018 "TODO int_div: single variable with large domain");
2019 } else {
2020 std::vector<int64_t> possible_values;
2021 for (const int64_t v : context_->DomainOf(var).Values()) {
2022 const int64_t target_v =
2023 EvaluateSingleVariableExpression(target, var, v);
2024 const int64_t expr_v = EvaluateSingleVariableExpression(expr, var, v);
2025 const int64_t div_v = EvaluateSingleVariableExpression(div, var, v);
2026 if (div_v == 0) continue;
2027 if (target_v == expr_v / div_v) {
2028 possible_values.push_back(v);
2029 }
2030 }
2031 (void)context_->IntersectDomainWith(var,
2032 Domain::FromValues(possible_values));
2033 context_->UpdateRuleStats("int_div: single variable");
2034 return RemoveConstraint(ct);
2035 }
2036 }
2037
2038 // For now, we only presolve the case where the divisor is constant.
2039 if (!context_->IsFixed(div)) return false;
2040
2041 const int64_t divisor = context_->FixedValue(div);
2042
2043 // Trivial case one: target = expr / +/-1.
2044 if (divisor == 1 || divisor == -1) {
2045 LinearConstraintProto* const lin =
2046 context_->working_model->add_constraints()->mutable_linear();
2047 lin->add_domain(0);
2048 lin->add_domain(0);
2050 AddLinearExpressionToLinearConstraint(target, -divisor, lin);
2051 context_->UpdateNewConstraintsVariableUsage();
2052 context_->UpdateRuleStats("int_div: rewrite to equality");
2053 return RemoveConstraint(ct);
2054 }
2055
2056 // Reduce the domain of target.
2057 {
2058 bool domain_modified = false;
2059 const Domain target_implied_domain =
2060 context_->DomainSuperSetOf(expr).DivisionBy(divisor);
2061
2062 if (!context_->IntersectDomainWith(target, target_implied_domain,
2063 &domain_modified)) {
2064 return false;
2065 }
2066 if (domain_modified) {
2067 // Note: the case target is fixed has been processed before.
2068 if (target_implied_domain.IsFixed()) {
2069 context_->UpdateRuleStats(
2070 "int_div: target has been fixed by propagating X / cte");
2071 } else {
2072 context_->UpdateRuleStats(
2073 "int_div: updated domain of target in target = X / cte");
2074 }
2075 }
2076 }
2077
2078 // Trivial case three: fixed_target = expr / fixed_divisor.
2079 if (context_->IsFixed(target) &&
2080 CapAdd(1, CapProd(std::abs(divisor),
2081 1 + std::abs(context_->FixedValue(target)))) !=
2082 std::numeric_limits<int64_t>::max()) {
2083 int64_t t = context_->FixedValue(target);
2084 int64_t d = divisor;
2085 if (d < 0) {
2086 t = -t;
2087 d = -d;
2088 }
2089
2090 const Domain expr_implied_domain =
2091 t > 0
2092 ? Domain(t * d, (t + 1) * d - 1)
2093 : (t == 0 ? Domain(1 - d, d - 1) : Domain((t - 1) * d + 1, t * d));
2094 bool domain_modified = false;
2095 if (!context_->IntersectDomainWith(expr, expr_implied_domain,
2096 &domain_modified)) {
2097 return false;
2098 }
2099 if (domain_modified) {
2100 context_->UpdateRuleStats("int_div: target and divisor are fixed");
2101 } else {
2102 context_->UpdateRuleStats("int_div: always true");
2103 }
2104 return RemoveConstraint(ct);
2105 }
2106
2107 // Linearize if everything is positive, and we have no overflow.
2108 // TODO(user): Deal with other cases where there is no change of
2109 // sign. We can also deal with target = cte, div variable.
2110 if (context_->MinOf(target) >= 0 && context_->MinOf(expr) >= 0 &&
2111 divisor > 1 &&
2112 CapProd(divisor, context_->MaxOf(target)) !=
2113 std::numeric_limits<int64_t>::max()) {
2114 LinearConstraintProto* const lin =
2115 context_->working_model->add_constraints()->mutable_linear();
2116 lin->add_domain(0);
2117 lin->add_domain(divisor - 1);
2119 AddLinearExpressionToLinearConstraint(target, -divisor, lin);
2120 context_->UpdateNewConstraintsVariableUsage();
2121 context_->UpdateRuleStats(
2122 "int_div: linearize positive division with a constant divisor");
2123
2124 return RemoveConstraint(ct);
2125 }
2126
2127 // TODO(user): reduce the domain of X by introducing an
2128 // InverseDivisionOfSortedDisjointIntervals().
2129 return false;
2130}
2131
2132bool CpModelPresolver::PresolveIntMod(int c, ConstraintProto* ct) {
2133 if (context_->ModelIsUnsat()) return false;
2134 // TODO(user): add support for this case.
2135 if (HasEnforcementLiteral(*ct)) return false;
2136
2137 // TODO(user): Presolve f(X) = g(X) % fixed_mod.
2138 const LinearExpressionProto target = ct->int_mod().target();
2139 const LinearExpressionProto expr = ct->int_mod().exprs(0);
2140 const LinearExpressionProto mod = ct->int_mod().exprs(1);
2141
2142 if (context_->MinOf(target) > 0) {
2143 bool domain_changed = false;
2144 if (!context_->IntersectDomainWith(
2145 expr, Domain(0, std::numeric_limits<int64_t>::max()),
2146 &domain_changed)) {
2147 return false;
2148 }
2149 if (domain_changed) {
2150 context_->UpdateRuleStats(
2151 "int_mod: non negative target implies positive expression");
2152 }
2153 }
2154
2155 if (context_->MinOf(target) >= context_->MaxOf(mod) ||
2156 context_->MaxOf(target) <= -context_->MaxOf(mod)) {
2157 return context_->NotifyThatModelIsUnsat(
2158 "int_mod: incompatible target and mod");
2159 }
2160
2161 if (context_->MaxOf(target) < 0) {
2162 bool domain_changed = false;
2163 if (!context_->IntersectDomainWith(
2164 expr, Domain(std::numeric_limits<int64_t>::min(), 0),
2165 &domain_changed)) {
2166 return false;
2167 }
2168 if (domain_changed) {
2169 context_->UpdateRuleStats(
2170 "int_mod: non positive target implies negative expression");
2171 }
2172 }
2173
2174 if (context_->IsFixed(target) && context_->IsFixed(mod) &&
2175 context_->FixedValue(mod) > 1 && ct->enforcement_literal().empty() &&
2176 expr.vars().size() == 1) {
2177 // We can intersect the domain of expr with {k * mod + target}.
2178 const int64_t fixed_mod = context_->FixedValue(mod);
2179 const int64_t fixed_target = context_->FixedValue(target);
2180
2181 if (!context_->CanonicalizeAffineVariable(expr.vars(0), expr.coeffs(0),
2182 fixed_mod,
2183 fixed_target - expr.offset())) {
2184 return false;
2185 }
2186
2187 context_->UpdateRuleStats("int_mod: fixed mod and target");
2188 return RemoveConstraint(ct);
2189 }
2190
2191 bool domain_changed = false;
2192 if (!context_->IntersectDomainWith(
2193 target,
2194 context_->DomainSuperSetOf(expr).PositiveModuloBySuperset(
2195 context_->DomainSuperSetOf(mod)),
2196 &domain_changed)) {
2197 return false;
2198 }
2199
2200 if (domain_changed) {
2201 context_->UpdateRuleStats("int_mod: reduce target domain");
2202 }
2203
2204 // Remove the constraint if the target is removable.
2205 // This is triggered on the flatzinc rotating-workforce problems.
2206 //
2207 // TODO(user): We can deal with more cases, sometime even if the domain of
2208 // expr.vars(0) is large, the implied domain is not too complex.
2209 if (target.vars().size() == 1 && expr.vars().size() == 1 &&
2210 context_->DomainOf(expr.vars(0)).Size() < 100 && context_->IsFixed(mod) &&
2211 context_->VariableIsUniqueAndRemovable(target.vars(0)) &&
2212 target.vars(0) != expr.vars(0)) {
2213 const int64_t fixed_mod = context_->FixedValue(mod);
2214 std::vector<int64_t> values;
2215 const Domain dom = context_->DomainOf(target.vars(0));
2216 for (const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
2217 const int64_t rhs = (v * expr.coeffs(0) + expr.offset()) % fixed_mod;
2218 const int64_t target_term = rhs - target.offset();
2219 if (target_term % target.coeffs(0) != 0) continue;
2220 if (dom.Contains(target_term / target.coeffs(0))) {
2221 values.push_back(v);
2222 }
2223 }
2224
2225 context_->UpdateRuleStats("int_mod: remove singleton target");
2226 if (!context_->IntersectDomainWith(expr.vars(0),
2227 Domain::FromValues(values))) {
2228 return false;
2229 }
2230 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2231 ct->Clear();
2232 context_->UpdateConstraintVariableUsage(c);
2233 context_->MarkVariableAsRemoved(target.vars(0));
2234 return true;
2235 }
2236
2237 return false;
2238}
2239
2240// TODO(user): Now that everything has affine relations, we should maybe
2241// canonicalize all linear subexpression in a generic way.
2242bool CpModelPresolver::ExploitEquivalenceRelations(int c, ConstraintProto* ct) {
2243 bool changed = false;
2244
2245 // Optim: Special case for the linear constraint. We just remap the
2246 // enforcement literals, the normal variables will be replaced by their
2247 // representative in CanonicalizeLinear().
2248 if (ct->constraint_case() == ConstraintProto::kLinear) {
2249 for (int& ref : *ct->mutable_enforcement_literal()) {
2250 const int rep = this->context_->GetLiteralRepresentative(ref);
2251 if (rep != ref) {
2252 changed = true;
2253 ref = rep;
2254 }
2255 }
2256 return changed;
2257 }
2258
2259 // Optim: This extra loop is a lot faster than reparsing the variable from the
2260 // proto when there is nothing to do, which is quite often.
2261 bool work_to_do = false;
2262 for (const int var : context_->ConstraintToVars(c)) {
2263 const AffineRelation::Relation r = context_->GetAffineRelation(var);
2264 if (r.representative != var) {
2265 work_to_do = true;
2266 break;
2267 }
2268 }
2269 if (!work_to_do) return false;
2270
2271 // Remap literal and negated literal to their representative.
2273 [&changed, this](int* ref) {
2274 const int rep = this->context_->GetLiteralRepresentative(*ref);
2275 if (rep != *ref) {
2276 changed = true;
2277 *ref = rep;
2278 }
2279 },
2280 ct);
2281 return changed;
2282}
2283
2284bool CpModelPresolver::DivideLinearByGcd(ConstraintProto* ct) {
2285 if (context_->ModelIsUnsat()) return false;
2286
2287 // Compute the GCD of all coefficients.
2288 int64_t gcd = 0;
2289 const int num_vars = ct->linear().vars().size();
2290 for (int i = 0; i < num_vars; ++i) {
2291 const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2292 gcd = std::gcd(gcd, magnitude);
2293 if (gcd == 1) break;
2294 }
2295 if (gcd > 1) {
2296 context_->UpdateRuleStats("linear: divide by GCD");
2297 for (int i = 0; i < num_vars; ++i) {
2298 ct->mutable_linear()->set_coeffs(i, ct->linear().coeffs(i) / gcd);
2299 }
2300 const Domain rhs = ReadDomainFromProto(ct->linear());
2301 FillDomainInProto(rhs.InverseMultiplicationBy(gcd), ct->mutable_linear());
2302 if (ct->linear().domain_size() == 0) {
2303 return MarkConstraintAsFalse(ct, "linear: not satisfied after GCD");
2304 }
2305 }
2306 return false;
2307}
2308
2309bool CpModelPresolver::CanonicalizeLinearExpression(
2310 const ConstraintProto& ct, LinearExpressionProto* exp) {
2311 return context_->CanonicalizeLinearExpression(ct.enforcement_literal(), exp);
2312}
2313
2314bool CpModelPresolver::CanonicalizeLinear(ConstraintProto* ct, bool* changed) {
2315 if (ct->constraint_case() != ConstraintProto::kLinear) return true;
2316 if (context_->ModelIsUnsat()) return false;
2317
2318 if (ct->linear().domain().empty()) {
2319 *changed = true;
2320 return MarkConstraintAsFalse(ct, "linear: no domain");
2321 }
2322
2323 bool is_impossible = false;
2324 *changed = context_->CanonicalizeLinearConstraint(ct, &is_impossible);
2325 if (is_impossible) {
2326 *changed = true;
2327 return MarkConstraintAsFalse(ct, "linear: never in domain");
2328 }
2329 *changed |= DivideLinearByGcd(ct);
2330
2331 // For duplicate detection, we always make the first coeff positive.
2332 //
2333 // TODO(user): Move that to context_->CanonicalizeLinearConstraint(), and do
2334 // the same for LinearExpressionProto.
2335 if (!ct->linear().coeffs().empty() && ct->linear().coeffs(0) < 0) {
2336 for (int64_t& ref_coeff : *ct->mutable_linear()->mutable_coeffs()) {
2337 ref_coeff = -ref_coeff;
2338 }
2340 ct->mutable_linear());
2341 }
2342 if (ct->constraint_case() != ConstraintProto::kLinear) return true;
2343 if (ct->linear().vars().empty()) {
2344 *changed = true;
2345 return PresolveEmptyLinearConstraint(ct);
2346 }
2347
2348 return true;
2349}
2350
2351bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto* ct) {
2352 if (ct->constraint_case() != ConstraintProto::kLinear ||
2353 context_->ModelIsUnsat()) {
2354 return false;
2355 }
2356
2357 absl::btree_set<int> index_to_erase;
2358 const int num_vars = ct->linear().vars().size();
2359 Domain rhs = ReadDomainFromProto(ct->linear());
2360
2361 // First pass. Process singleton column that are not in the objective. Note
2362 // that for postsolve, it is important that we process them in the same order
2363 // in which they will be removed.
2364 for (int i = 0; i < num_vars; ++i) {
2365 const int var = ct->linear().vars(i);
2366 const int64_t coeff = ct->linear().coeffs(i);
2367 CHECK(RefIsPositive(var));
2368 if (context_->VariableIsUniqueAndRemovable(var)) {
2369 // This is not needed for the code below, but in practice, removing
2370 // singleton with a large coefficient create holes in the constraint rhs
2371 // and we will need to add more variable to deal with that.
2372 // This works way better on timtab1CUTS.pb.gz for instance.
2373 if (std::abs(coeff) != 1) continue;
2374
2375 bool exact;
2376 const auto term_domain =
2377 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2378 if (!exact) continue;
2379
2380 // We do not do that if the domain of rhs becomes too complex.
2381 const Domain new_rhs = rhs.AdditionWith(term_domain);
2382 if (new_rhs.NumIntervals() > 100) continue;
2383
2384 // Note that we can't do that if we loose information in the
2385 // multiplication above because the new domain might not be as strict
2386 // as the initial constraint otherwise. TODO(user): because of the
2387 // addition, it might be possible to cover more cases though.
2388 context_->UpdateRuleStats("linear: singleton column");
2389 index_to_erase.insert(i);
2390 rhs = new_rhs;
2391 continue;
2392 }
2393 }
2394
2395 // If the whole linear is independent from the rest of the problem, we
2396 // can solve it now. If it is enforced, then each variable will have two
2397 // values: Its minimum one and one minimizing the objective under the
2398 // constraint. The switch can be controlled by a single Boolean.
2399 //
2400 // TODO(user): Cover more case like dedicated algorithm to solve for a small
2401 // number of variable that are faster than the DP we use here.
2402 if (index_to_erase.empty()) {
2403 int num_singletons = 0;
2404 for (const int var : ct->linear().vars()) {
2405 if (!RefIsPositive(var)) break;
2406 if (!context_->VariableWithCostIsUniqueAndRemovable(var) &&
2407 !context_->VariableIsUniqueAndRemovable(var)) {
2408 break;
2409 }
2410 ++num_singletons;
2411 }
2412 if (num_singletons == num_vars) {
2413 // Try to solve the equation.
2414 std::vector<Domain> domains;
2415 std::vector<int64_t> coeffs;
2416 std::vector<int64_t> costs;
2417 for (int i = 0; i < num_vars; ++i) {
2418 const int var = ct->linear().vars(i);
2419 CHECK(RefIsPositive(var));
2420 domains.push_back(context_->DomainOf(var));
2421 coeffs.push_back(ct->linear().coeffs(i));
2422 costs.push_back(context_->ObjectiveCoeff(var));
2423 }
2424 BasicKnapsackSolver solver;
2425 const auto& result = solver.Solve(domains, coeffs, costs,
2426 ReadDomainFromProto(ct->linear()));
2427 if (!result.solved) {
2428 context_->UpdateRuleStats(
2429 "TODO independent linear: minimize single linear constraint");
2430 } else if (result.infeasible) {
2431 return MarkConstraintAsFalse(
2432 ct, "independent linear: no DP solution to simple constraint");
2433 } else {
2434 if (ct->enforcement_literal().empty()) {
2435 // Just fix everything.
2436 context_->UpdateRuleStats("independent linear: solved by DP");
2437 for (int i = 0; i < num_vars; ++i) {
2438 if (!context_->IntersectDomainWith(ct->linear().vars(i),
2439 Domain(result.solution[i]))) {
2440 return false;
2441 }
2442 }
2443 return RemoveConstraint(ct);
2444 }
2445
2446 // Each variable will take two values according to a single Boolean.
2447 int indicator;
2448 if (ct->enforcement_literal().size() == 1) {
2449 indicator = ct->enforcement_literal(0);
2450 } else {
2451 indicator =
2452 context_->NewBoolVarWithConjunction(ct->enforcement_literal());
2453 auto* new_ct = context_->working_model->add_constraints();
2454 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2455 new_ct->mutable_bool_or()->add_literals(indicator);
2456 context_->UpdateNewConstraintsVariableUsage();
2457 }
2458 for (int i = 0; i < num_vars; ++i) {
2459 const int64_t best_value =
2460 costs[i] > 0 ? domains[i].Min() : domains[i].Max();
2461 const int64_t other_value = result.solution[i];
2462 if (best_value == other_value) {
2463 if (!context_->IntersectDomainWith(ct->linear().vars(i),
2464 Domain(best_value))) {
2465 return false;
2466 }
2467 continue;
2468 }
2469 solution_crush_.SetVarToConditionalValue(
2470 ct->linear().vars(i), {indicator}, other_value, best_value);
2471 if (RefIsPositive(indicator)) {
2472 if (!context_->StoreAffineRelation(ct->linear().vars(i), indicator,
2473 other_value - best_value,
2474 best_value)) {
2475 return false;
2476 }
2477 } else {
2478 if (!context_->StoreAffineRelation(
2479 ct->linear().vars(i), PositiveRef(indicator),
2480 best_value - other_value, other_value)) {
2481 return false;
2482 }
2483 }
2484 }
2485 context_->UpdateRuleStats(
2486 "independent linear: with enforcement, but solved by DP");
2487 return RemoveConstraint(ct);
2488 }
2489 }
2490 }
2491
2492 // If we didn't find any, look for the one appearing in the objective.
2493 if (index_to_erase.empty()) {
2494 // Note that we only do that if we have a non-reified equality.
2495 if (context_->params().presolve_substitution_level() <= 0) return false;
2496 if (!ct->enforcement_literal().empty()) return false;
2497
2498 // If it is possible to do so, note that we can transform constraint into
2499 // equalities in PropagateDomainsInLinear().
2500 if (rhs.Min() != rhs.Max()) return false;
2501
2502 for (int i = 0; i < num_vars; ++i) {
2503 const int var = ct->linear().vars(i);
2504 const int64_t coeff = ct->linear().coeffs(i);
2505 CHECK(RefIsPositive(var));
2506
2507 // If the variable appear only in the objective and we have an equality,
2508 // we can transfer the cost to the rest of the linear expression, and
2509 // remove that variable. Note that this do not remove any feasible
2510 // solution and is not a "dual" reduction.
2511 //
2512 // Note that is similar to the substitution code in PresolveLinear() but
2513 // it doesn't require the variable to be implied free since we do not
2514 // remove the constraints afterwards, just the variable.
2515 if (!context_->VariableWithCostIsUnique(var)) continue;
2516 DCHECK(context_->ObjectiveMap().contains(var));
2517
2518 // We only support substitution that does not require to multiply the
2519 // objective by some factor.
2520 //
2521 // TODO(user): If the objective is a single variable, we can actually
2522 // "absorb" any factor into the objective scaling.
2523 const int64_t objective_coeff = context_->ObjectiveMap().at(var);
2524 CHECK_NE(coeff, 0);
2525 if (objective_coeff % coeff != 0) continue;
2526
2527 // TODO(user): We have an issue if objective coeff is not one, because
2528 // the RecomputeSingletonObjectiveDomain() do not properly put holes
2529 // in the objective domain, which might cause an issue. Note that this
2530 // presolve rule is actually almost never applied on the miplib.
2531 if (std::abs(objective_coeff) != 1) continue;
2532
2533 // We do not do that if the domain of rhs becomes too complex.
2534 bool exact;
2535 const auto term_domain =
2536 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2537 if (!exact) continue;
2538 const Domain new_rhs = rhs.AdditionWith(term_domain);
2539 if (new_rhs.NumIntervals() > 100) continue;
2540
2541 // Special case: If the objective was a single variable, we can transfer
2542 // the domain of var to the objective, and just completely remove this
2543 // equality constraint.
2544 //
2545 // TODO(user): Maybe if var has a complex domain, we might not want to
2546 // substitute it?
2547 if (context_->ObjectiveMap().size() == 1) {
2548 // This make sure the domain of var is restricted and the objective
2549 // domain updated.
2550 if (!context_->RecomputeSingletonObjectiveDomain()) {
2551 return true;
2552 }
2553
2554 // The function above might fix var, in which case, we just abort.
2555 if (context_->IsFixed(var)) continue;
2556
2557 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2558 if (context_->ModelIsUnsat()) return true;
2559 continue;
2560 }
2561
2562 context_->UpdateRuleStats("linear: singleton column define objective");
2563 context_->MarkVariableAsRemoved(var);
2564 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2565 return RemoveConstraint(ct);
2566 }
2567
2568 // On supportcase20, this transformation make the LP relaxation way worse.
2569 // TODO(user): understand why.
2570 if (true) continue;
2571
2572 // Update the objective and remove the variable from its equality
2573 // constraint by expanding its rhs. This might fail if the new linear
2574 // objective expression can lead to overflow.
2575 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2576 if (context_->ModelIsUnsat()) return true;
2577 continue;
2578 }
2579
2580 context_->UpdateRuleStats(
2581 "linear: singleton column in equality and in objective");
2582 rhs = new_rhs;
2583 index_to_erase.insert(i);
2584 break;
2585 }
2586 }
2587 if (index_to_erase.empty()) return false;
2588
2589 // Tricky: If we have a singleton variable in an enforced constraint, and at
2590 // postsolve the enforcement is false, we might just ignore the constraint.
2591 // This is fine, but we still need to assign any removed variable to a
2592 // feasible value, otherwise later postsolve rules might not work correctly.
2593 // Adding these linear1 achieve that.
2594 //
2595 // TODO(user): Alternatively, we could copy the constraint without the
2596 // enforcement to the mapping model, since singleton variable are supposed
2597 // to always have a feasible value anyway.
2598 if (!ct->enforcement_literal().empty()) {
2599 for (const int i : index_to_erase) {
2600 const int var = ct->linear().vars(i);
2601 auto* new_lin =
2602 context_->NewMappingConstraint(__FILE__, __LINE__)->mutable_linear();
2603 new_lin->add_vars(var);
2604 new_lin->add_coeffs(1);
2605 FillDomainInProto(context_->DomainOf(var), new_lin);
2606 }
2607 }
2608
2609 // TODO(user): we could add the constraint to mapping_model only once
2610 // instead of adding a reduced version of it each time a new singleton
2611 // variable appear in the same constraint later. That would work but would
2612 // also force the postsolve to take search decisions...
2613 if (absl::GetFlag(FLAGS_cp_model_debug_postsolve)) {
2614 auto* new_ct = context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2615 const std::string name(new_ct->name());
2616 *new_ct = *ct;
2617 new_ct->set_name(absl::StrCat(ct->name(), " copy ", name));
2618 } else {
2619 *context_->NewMappingConstraint(*ct, __FILE__, __LINE__) = *ct;
2620 }
2621
2622 int new_size = 0;
2623 for (int i = 0; i < num_vars; ++i) {
2624 if (index_to_erase.count(i)) {
2625 context_->MarkVariableAsRemoved(ct->linear().vars(i));
2626 continue;
2627 }
2628 ct->mutable_linear()->set_coeffs(new_size, ct->linear().coeffs(i));
2629 ct->mutable_linear()->set_vars(new_size, ct->linear().vars(i));
2630 ++new_size;
2631 }
2632 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
2633 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
2634 FillDomainInProto(rhs, ct->mutable_linear());
2635 DivideLinearByGcd(ct);
2636 return true;
2637}
2638
2639// If the gcd of all but one term (with index target_index) is not one, we can
2640// rewrite the last term using an affine representative.
2641bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality(
2642 int target_index, ConstraintProto* ct) {
2643 int64_t gcd = 0;
2644 const int num_variables = ct->linear().vars().size();
2645 for (int i = 0; i < num_variables; ++i) {
2646 if (i == target_index) continue;
2647 const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2648 gcd = std::gcd(gcd, magnitude);
2649 if (gcd == 1) return false;
2650 }
2651
2652 // If we take the constraint % gcd, we have
2653 // ref * coeff % gcd = rhs % gcd
2654 CHECK_GT(gcd, 1);
2655 const int ref = ct->linear().vars(target_index);
2656 const int64_t coeff = ct->linear().coeffs(target_index);
2657 const int64_t rhs = ct->linear().domain(0);
2658
2659 // This should have been processed before by just dividing the whole
2660 // constraint by the gcd.
2661 if (coeff % gcd == 0) return false;
2662
2663 if (!context_->CanonicalizeAffineVariable(ref, coeff, gcd, rhs)) {
2664 return false;
2665 }
2666
2667 // We use the new variable in the constraint.
2668 // Note that we will divide everything by the gcd too.
2669 bool changed = false;
2670 (void)CanonicalizeLinear(ct, &changed);
2671 return changed;
2672}
2673
2674namespace {
2675
2676bool IsLinearEqualityConstraint(const ConstraintProto& ct) {
2677 return ct.constraint_case() == ConstraintProto::kLinear &&
2678 ct.linear().domain().size() == 2 &&
2679 ct.linear().domain(0) == ct.linear().domain(1) &&
2680 ct.enforcement_literal().empty();
2681}
2682
2683} // namespace
2684
2685// Any equality must be true modulo n.
2686//
2687// If the gcd of all but one term is not one, we can rewrite the last term using
2688// an affine representative by considering the equality modulo that gcd.
2689// As an heuristic, we only test the smallest term or small primes 2, 3, and 5.
2690//
2691// We also handle the special case of having two non-zero literals modulo 2.
2692//
2693// TODO(user): Use more complex algo to detect all the cases? By splitting the
2694// constraint in two, and computing the gcd of each halves, we can reduce the
2695// problem to two problem of half size. So at least we can do it in O(n log n).
2696bool CpModelPresolver::PresolveLinearEqualityWithModulo(ConstraintProto* ct) {
2697 if (context_->ModelIsUnsat()) return false;
2698 if (!IsLinearEqualityConstraint(*ct)) return false;
2699
2700 const int num_variables = ct->linear().vars().size();
2701 if (num_variables < 2) return false;
2702
2703 std::vector<int> mod2_indices;
2704 std::vector<int> mod3_indices;
2705 std::vector<int> mod5_indices;
2706
2707 int64_t min_magnitude;
2708 int num_smallest = 0;
2709 int smallest_index;
2710 for (int i = 0; i < num_variables; ++i) {
2711 const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2712 if (num_smallest == 0 || magnitude < min_magnitude) {
2713 min_magnitude = magnitude;
2714 num_smallest = 1;
2715 smallest_index = i;
2716 } else if (magnitude == min_magnitude) {
2717 ++num_smallest;
2718 }
2719
2720 if (magnitude % 2 != 0) mod2_indices.push_back(i);
2721 if (magnitude % 3 != 0) mod3_indices.push_back(i);
2722 if (magnitude % 5 != 0) mod5_indices.push_back(i);
2723 }
2724
2725 if (mod2_indices.size() == 2) {
2726 bool ok = true;
2727 std::vector<int> literals;
2728 for (const int i : mod2_indices) {
2729 const int ref = ct->linear().vars(i);
2730 if (!context_->CanBeUsedAsLiteral(ref)) {
2731 ok = false;
2732 break;
2733 }
2734 literals.push_back(ref);
2735 }
2736 if (ok) {
2737 const int64_t rhs = std::abs(ct->linear().domain(0));
2738 context_->UpdateRuleStats("linear: only two odd Booleans in equality");
2739 if (rhs % 2) {
2740 if (!context_->StoreBooleanEqualityRelation(literals[0],
2741 NegatedRef(literals[1]))) {
2742 return false;
2743 }
2744 } else {
2745 if (!context_->StoreBooleanEqualityRelation(literals[0], literals[1])) {
2746 return false;
2747 }
2748 }
2749 }
2750 }
2751
2752 // TODO(user): More than one reduction might be possible, so we will need
2753 // to call this again if we apply any of these reduction.
2754 if (mod2_indices.size() == 1) {
2755 return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0], ct);
2756 }
2757 if (mod3_indices.size() == 1) {
2758 return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0], ct);
2759 }
2760 if (mod5_indices.size() == 1) {
2761 return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0], ct);
2762 }
2763 if (num_smallest == 1) {
2764 return AddVarAffineRepresentativeFromLinearEquality(smallest_index, ct);
2765 }
2766
2767 return false;
2768}
2769
2770bool CpModelPresolver::PresolveLinearOfSizeOne(ConstraintProto* ct) {
2771 CHECK_EQ(ct->linear().vars().size(), 1);
2772 CHECK(RefIsPositive(ct->linear().vars(0)));
2773 DCHECK(context_->VariableIsAffineRepresentative(ct->linear().vars(0)));
2774
2775 const int var = ct->linear().vars(0);
2776 const Domain var_domain = context_->DomainOf(var);
2777 const Domain rhs = ReadDomainFromProto(ct->linear())
2778 .InverseMultiplicationBy(ct->linear().coeffs(0))
2779 .IntersectionWith(var_domain);
2780 if (rhs.IsEmpty()) {
2781 return MarkConstraintAsFalse(ct, "linear1: infeasible");
2782 }
2783 if (rhs == var_domain) {
2784 context_->UpdateRuleStats("linear1: always true");
2785 return RemoveConstraint(ct);
2786 }
2787
2788 // We can always canonicalize the constraint to a coefficient of 1.
2789 // Note that this should never trigger as we usually divide by gcd already.
2790 if (ct->linear().coeffs(0) != 1) {
2791 context_->UpdateRuleStats("linear1: canonicalized");
2792 ct->mutable_linear()->set_coeffs(0, 1);
2793 FillDomainInProto(rhs, ct->mutable_linear());
2794 }
2795
2796 // Size one constraint with no enforcement?
2797 if (!HasEnforcementLiteral(*ct)) {
2798 context_->UpdateRuleStats("linear1: without enforcement");
2799 if (!context_->IntersectDomainWith(var, rhs)) return false;
2800 return RemoveConstraint(ct);
2801 }
2802
2803 // This is just an implication, lets convert it right away.
2804 if (context_->CanBeUsedAsLiteral(var)) {
2805 DCHECK(rhs.IsFixed());
2806 if (rhs.FixedValue() == 1) {
2807 ct->mutable_bool_and()->add_literals(var);
2808 } else {
2809 CHECK_EQ(rhs.FixedValue(), 0);
2810 ct->mutable_bool_and()->add_literals(NegatedRef(var));
2811 }
2812
2813 // No var <-> constraint graph changes.
2814 // But this is no longer a linear1.
2815 return true;
2816 }
2817
2818 // Detect encoding.
2819 bool changed = false;
2820 if (ct->enforcement_literal().size() == 1) {
2821 // If we already have an encoding literal, this constraint is really
2822 // an implication.
2823 int lit = ct->enforcement_literal(0);
2824
2825 // For correctness below, it is important lit is the canonical literal,
2826 // otherwise we might remove the constraint even though it is the one
2827 // defining an encoding literal.
2828 const int representative = context_->GetLiteralRepresentative(lit);
2829 if (lit != representative) {
2830 lit = representative;
2831 ct->set_enforcement_literal(0, lit);
2832 context_->UpdateRuleStats("linear1: remapped enforcement literal");
2833 changed = true;
2834 }
2835
2836 if (rhs.IsFixed()) {
2837 const int64_t value = rhs.FixedValue();
2838 int encoding_lit;
2839 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2840 if (lit == encoding_lit) return changed;
2841 context_->AddImplication(lit, encoding_lit);
2842 context_->UpdateNewConstraintsVariableUsage();
2843 ct->Clear();
2844 context_->UpdateRuleStats("linear1: transformed to implication");
2845 return true;
2846 } else {
2847 if (context_->StoreLiteralImpliesVarEqValue(lit, var, value)) {
2848 // The domain is not actually modified, but we want to rescan the
2849 // constraints linked to this variable.
2850 context_->modified_domains.Set(var);
2851 }
2852 context_->UpdateNewConstraintsVariableUsage();
2853 }
2854 return changed;
2855 }
2856
2857 const Domain complement = rhs.Complement().IntersectionWith(var_domain);
2858 if (complement.IsFixed()) {
2859 const int64_t value = complement.FixedValue();
2860 int encoding_lit;
2861 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2862 if (NegatedRef(lit) == encoding_lit) return changed;
2863 context_->AddImplication(lit, NegatedRef(encoding_lit));
2864 context_->UpdateNewConstraintsVariableUsage();
2865 ct->Clear();
2866 context_->UpdateRuleStats("linear1: transformed to implication");
2867 return true;
2868 } else {
2869 if (context_->StoreLiteralImpliesVarNeValue(lit, var, value)) {
2870 // The domain is not actually modified, but we want to rescan the
2871 // constraints linked to this variable.
2872 context_->modified_domains.Set(var);
2873 }
2874 context_->UpdateNewConstraintsVariableUsage();
2875 }
2876 return changed;
2877 }
2878 }
2879
2880 return changed;
2881}
2882
2883bool CpModelPresolver::PresolveLinearOfSizeTwo(ConstraintProto* ct) {
2884 DCHECK_EQ(ct->linear().vars().size(), 2);
2885
2886 const LinearConstraintProto& arg = ct->linear();
2887 const int var1 = arg.vars(0);
2888 const int var2 = arg.vars(1);
2889 const int64_t coeff1 = arg.coeffs(0);
2890 const int64_t coeff2 = arg.coeffs(1);
2891
2892 // Starts by updating our hash map of known relation.
2893 {
2894 const LinearExpression2 expr2 =
2895 GetLinearExpression2FromProto(var1, coeff1, var2, coeff2);
2896 const IntegerValue lb(arg.domain(0));
2897 const IntegerValue ub(arg.domain(arg.domain().size() - 1));
2898
2899 const RelationStatus status = known_linear2_.GetStatus(expr2, lb, ub);
2900 if (status == RelationStatus::IS_TRUE) {
2901 // Note that we don't track what constraint implied the relation, so we
2902 // cannot remove this constraint even if the relation is already known.
2903 //
2904 // However since we only add it if the relation is not
2905 // enforced, this should be correct.
2906 //
2907 // Tricky: If the constraint domain is not simple, we cannot really deduce
2908 // anything.
2909 if (!ct->enforcement_literal().empty() &&
2910 ct->linear().domain().size() == 2) {
2911 context_->UpdateRuleStats("linear2: already known enforced relation");
2912 return RemoveConstraint(ct);
2913 }
2914 } else if (status == RelationStatus::IS_FALSE) {
2915 return MarkConstraintAsFalse(ct, "linear2: infeasible relation");
2916 } else if (ct->enforcement_literal().empty()) {
2917 known_linear2_.Add(expr2, lb, ub);
2918 if (context_->ModelIsUnsat()) return false;
2919 }
2920 }
2921
2922 const Domain rhs = ReadDomainFromProto(arg);
2923 bool mult1_is_exact = true;
2924 bool mult2_is_exact = true;
2925 const Domain scaled_domain1 =
2926 context_->DomainOf(var1).MultiplicationBy(coeff1, &mult1_is_exact);
2927 const Domain scaled_domain2 =
2928 context_->DomainOf(var2).MultiplicationBy(coeff2, &mult2_is_exact);
2929 if (mult1_is_exact && mult2_is_exact) {
2930 const Domain infeasible_reachable_values =
2931 scaled_domain1.AdditionWith(scaled_domain2)
2932 .IntersectionWith(rhs.Complement());
2933
2934 if (!infeasible_reachable_values.IsEmpty() &&
2935 infeasible_reachable_values.IsFixed()) {
2936 return PresolveLinear2NeCst(ct, infeasible_reachable_values.FixedValue());
2937 }
2938 }
2939
2940 if (rhs.IsFixed()) {
2941 if (ct->enforcement_literal().empty()) {
2942 return PresolveUnenforcedLinear2EqCst(ct, rhs.FixedValue());
2943 } else {
2944 return PresolveEnforcedLinear2EqCst(ct, rhs.FixedValue());
2945 }
2946 }
2947
2948 return PresolveLinear2WithBooleans(ct);
2949}
2950
2951// If it is not an equality, we only presolve the constraint if one of
2952// the variable is Boolean. Note that if both are Boolean, then a similar
2953// reduction is done by PresolveLinearOnBooleans(). If we have an equality,
2954// then the code below will do something stronger than this.
2955//
2956// TODO(user): We should probably instead generalize the code of
2957// ExtractEnforcementLiteralFromLinearConstraint(), or just temporary
2958// propagate domain of enforced linear constraints, to detect Boolean that
2959// must be true or false. This way we can do the same for longer constraints.
2960bool CpModelPresolver::PresolveLinear2WithBooleans(ConstraintProto* ct) {
2961 DCHECK_EQ(ct->linear().vars().size(), 2);
2962
2963 const LinearConstraintProto& arg = ct->linear();
2964 const int var1 = arg.vars(0);
2965 const int var2 = arg.vars(1);
2966 const int64_t coeff1 = arg.coeffs(0);
2967 const int64_t coeff2 = arg.coeffs(1);
2968
2969 int lit, var;
2970 int64_t value_on_true, coeff;
2971 if (context_->CanBeUsedAsLiteral(var1)) {
2972 lit = var1;
2973 value_on_true = coeff1;
2974 var = var2;
2975 coeff = coeff2;
2976 } else if (context_->CanBeUsedAsLiteral(var2)) {
2977 lit = var2;
2978 value_on_true = coeff2;
2979 var = var1;
2980 coeff = coeff1;
2981 } else {
2982 return false;
2983 }
2984 if (!RefIsPositive(lit)) return false;
2985
2986 const Domain rhs = ReadDomainFromProto(ct->linear());
2987 const Domain rhs_if_true =
2988 rhs.AdditionWith(Domain(-value_on_true)).InverseMultiplicationBy(coeff);
2989 const Domain rhs_if_false = rhs.InverseMultiplicationBy(coeff);
2990 const bool implied_false =
2991 context_->DomainOf(var).IntersectionWith(rhs_if_true).IsEmpty();
2992 const bool implied_true =
2993 context_->DomainOf(var).IntersectionWith(rhs_if_false).IsEmpty();
2994 if (implied_true && implied_false) {
2995 return MarkConstraintAsFalse(ct, "linear2: infeasible");
2996 } else if (implied_true) {
2997 context_->UpdateRuleStats("linear2: boolean with one feasible value");
2998
2999 // => true.
3000 ConstraintProto* new_ct = context_->working_model->add_constraints();
3001 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
3002 new_ct->mutable_bool_and()->add_literals(lit);
3003 context_->UpdateNewConstraintsVariableUsage();
3004
3005 // Rewrite to => var in rhs_if_true.
3006 ct->mutable_linear()->Clear();
3007 ct->mutable_linear()->add_vars(var);
3008 ct->mutable_linear()->add_coeffs(1);
3009 FillDomainInProto(rhs_if_true, ct->mutable_linear());
3010 return PresolveSmallLinear(ct) || true;
3011 } else if (implied_false) {
3012 context_->UpdateRuleStats("linear2: boolean with one feasible value");
3013
3014 // => false.
3015 ConstraintProto* new_ct = context_->working_model->add_constraints();
3016 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
3017 new_ct->mutable_bool_and()->add_literals(NegatedRef(lit));
3018 context_->UpdateNewConstraintsVariableUsage();
3019
3020 // Rewrite to => var in rhs_if_false.
3021 ct->mutable_linear()->Clear();
3022 ct->mutable_linear()->add_vars(var);
3023 ct->mutable_linear()->add_coeffs(1);
3024 FillDomainInProto(rhs_if_false, ct->mutable_linear());
3025 return PresolveSmallLinear(ct) || true;
3026 } else if (ct->enforcement_literal().empty() &&
3027 !context_->CanBeUsedAsLiteral(var)) {
3028 // We currently only do that if there are no enforcement and we don't have
3029 // two Booleans as this can be presolved differently. We expand it into
3030 // two linear1 constraint that have a chance to be merged with other
3031 // "encoding" constraints.
3032 context_->UpdateRuleStats("linear2: contains a boolean");
3033
3034 // lit => var \in rhs_if_true
3035 const Domain var_domain = context_->DomainOf(var);
3036 if (!var_domain.IsIncludedIn(rhs_if_true)) {
3037 ConstraintProto* new_ct = context_->working_model->add_constraints();
3038 new_ct->add_enforcement_literal(lit);
3039 new_ct->mutable_linear()->add_vars(var);
3040 new_ct->mutable_linear()->add_coeffs(1);
3041 FillDomainInProto(rhs_if_true.IntersectionWith(var_domain),
3042 new_ct->mutable_linear());
3043 }
3044
3045 // NegatedRef(lit) => var \in rhs_if_false
3046 if (!var_domain.IsIncludedIn(rhs_if_false)) {
3047 ConstraintProto* new_ct = context_->working_model->add_constraints();
3048 new_ct->add_enforcement_literal(NegatedRef(lit));
3049 new_ct->mutable_linear()->add_vars(var);
3050 new_ct->mutable_linear()->add_coeffs(1);
3051 FillDomainInProto(rhs_if_false.IntersectionWith(var_domain),
3052 new_ct->mutable_linear());
3053 }
3054
3055 context_->UpdateNewConstraintsVariableUsage();
3056 return RemoveConstraint(ct);
3057 }
3058
3059 // Code below require equality.
3060 context_->UpdateRuleStats("TODO linear2: contains a boolean");
3061 return false;
3062}
3063
3064bool CpModelPresolver::PresolveLinear2NeCst(ConstraintProto* ct, int64_t rhs) {
3065 const LinearConstraintProto& arg = ct->linear();
3066 const int var1 = arg.vars(0);
3067 const int var2 = arg.vars(1);
3068
3069 const int64_t coeff1 = arg.coeffs(0);
3070 const int64_t coeff2 = arg.coeffs(1);
3071
3072 // coeff1 * v1 + coeff2 * v2 != cte.
3073 int64_t a = coeff1;
3074 int64_t b = coeff2;
3075 int64_t cte = rhs;
3076 int64_t x0 = 0;
3077 int64_t y0 = 0;
3078 if (!SolveDiophantineEquationOfSizeTwo(a, b, cte, x0, y0)) {
3079 // no solution.
3080 context_->UpdateRuleStats("linear2: remove always feasible ax + by != cte");
3081 return RemoveConstraint(ct);
3082 }
3083
3084 const Domain domain_of_z =
3085 context_->DomainOf(var1)
3086 .AdditionWith(Domain(-x0))
3087 .InverseMultiplicationBy(b)
3088 .IntersectionWith(context_->DomainOf(var2)
3089 .AdditionWith(Domain(-y0))
3090 .InverseMultiplicationBy(-a));
3091 const int64_t max_domain_size =
3092 context_->params().max_domain_size_for_linear2_expansion();
3093 const int64_t small_domain_size = max_domain_size / 2;
3094 if (domain_of_z.Size() <= max_domain_size &&
3095 (context_->IsMostlyFullyEncoded(var1) ||
3096 context_->DomainSize(var1) <= small_domain_size) &&
3097 (context_->IsMostlyFullyEncoded(var2) ||
3098 context_->DomainSize(var2) <= small_domain_size)) {
3099 // The number of clauses to create is small enough. We can encode the
3100 // constraint using just clauses.
3101 int num_clauses = 0;
3102 for (const int64_t z : domain_of_z.Values()) {
3103 const int64_t value1 = x0 + b * z;
3104 const int64_t value2 = y0 - a * z;
3105 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
3106 if (!context_->VarCanTakeValue(var1, value1) ||
3107 !context_->VarCanTakeValue(var2, value2)) {
3108 continue;
3109 }
3110
3111 // We cannot have both lit1 and lit2 true.
3112 const int lit1 = context_->GetOrCreateVarValueEncoding(var1, value1);
3113 const int lit2 = context_->GetOrCreateVarValueEncoding(var2, value2);
3114 auto* bool_or = context_->AddEnforcedConstraint(ct)->mutable_bool_or();
3115 bool_or->add_literals(NegatedRef(lit1));
3116 bool_or->add_literals(NegatedRef(lit2));
3117 ++num_clauses;
3118 }
3119
3120 VLOG(3) << "ConvertLinear2NeCst: |enforcements| = "
3121 << ct->enforcement_literal_size()
3122 << ", domain1 = " << context_->DomainOf(var1)
3123 << ", domain2 = " << context_->DomainOf(var2)
3124 << ", coeff1 = " << coeff1 << ", coeff2 = " << coeff2
3125 << ", domain_of_z = " << domain_of_z
3126 << ", num_clauses = " << num_clauses;
3127
3128 context_->UpdateNewConstraintsVariableUsage();
3129 context_->UpdateRuleStats("linear2: convert ax + by != cte to clauses");
3130 return RemoveConstraint(ct);
3131 } else {
3132 VLOG(3) << "TODO ConvertLinear2NeCst: |enforcements| = "
3133 << ct->enforcement_literal_size()
3134 << ", domain1 = " << context_->DomainOf(var1)
3135 << ", domain2 = " << context_->DomainOf(var2)
3136 << ", coeff1 = " << coeff1 << ", coeff2 = " << coeff2
3137 << ", rhs = " << rhs << ", domain_of_z = " << domain_of_z
3138 << ", |encoding1| = " << context_->GetValueEncodingSize(var1)
3139 << ", |encoding2| = " << context_->GetValueEncodingSize(var2);
3140 context_->UpdateRuleStats(
3141 "TODO linear2: convert ax + by != cte to clauses for large domains");
3142 return false;
3143 }
3144}
3145
3146bool CpModelPresolver::PresolveUnenforcedLinear2EqCst(ConstraintProto* ct,
3147 int64_t rhs) {
3148 DCHECK_EQ(ct->linear().vars().size(), 2);
3149
3150 const LinearConstraintProto& arg = ct->linear();
3151 const int var1 = arg.vars(0);
3152 const int var2 = arg.vars(1);
3153 const int64_t coeff1 = arg.coeffs(0);
3154 const int64_t coeff2 = arg.coeffs(1);
3155
3156 // We have: enforcement => (coeff1 * v1 + coeff2 * v2 == rhs).
3157 CHECK(ct->enforcement_literal().empty());
3158 // Detect affine relation.
3159 //
3160 // TODO(user): it might be better to first add only the affine relation with
3161 // a coefficient of magnitude 1, and later the one with larger coeffs.
3162 bool added = false;
3163 if (coeff1 == 1) {
3164 added = context_->StoreAffineRelation(var1, var2, -coeff2, rhs);
3165 } else if (coeff2 == 1) {
3166 added = context_->StoreAffineRelation(var2, var1, -coeff1, rhs);
3167 } else if (coeff1 == -1) {
3168 added = context_->StoreAffineRelation(var1, var2, coeff2, -rhs);
3169 } else if (coeff2 == -1) {
3170 added = context_->StoreAffineRelation(var2, var1, coeff1, -rhs);
3171 } else {
3172 // In this case, we can solve the diophantine equation, and write
3173 // both x and y in term of a new affine representative z.
3174 //
3175 // Note that PresolveLinearEqualityWithModulo() will have the same effect.
3176 //
3177 // We can also decide to fully expand the equality if the variables
3178 // are fully encoded.
3179 context_->UpdateRuleStats("TODO linear2: ax + by = cte");
3180 }
3181 if (added) return RemoveConstraint(ct);
3182 return false;
3183}
3184
3185bool CpModelPresolver::PresolveEnforcedLinear2EqCst(ConstraintProto* ct,
3186 int64_t rhs) {
3187 CHECK(!ct->enforcement_literal().empty());
3188 DCHECK(context_->VariableIsAffineRepresentative(ct->linear().vars(0)));
3189 DCHECK(context_->VariableIsAffineRepresentative(ct->linear().vars(1)));
3190 const LinearConstraintProto& arg = ct->linear();
3191
3192 const int var1 = arg.vars(0);
3193 const int64_t coeff1 = arg.coeffs(0);
3194 const Domain d1 = context_->DomainOf(var1);
3195
3196 const int var2 = arg.vars(1);
3197 const int64_t coeff2 = arg.coeffs(1);
3198 const Domain d2 = context_->DomainOf(var2);
3199
3200 // We look ahead to detect solutions to ax + by == cte.
3201 int64_t a = coeff1;
3202 int64_t b = coeff2;
3203 int64_t cte = rhs;
3204 int64_t x0 = 0;
3205 int64_t y0 = 0;
3206 if (!SolveDiophantineEquationOfSizeTwo(a, b, cte, x0, y0)) {
3207 return MarkConstraintAsFalse(
3208 ct, "linear2: implied ax + by = cte has no solutions");
3209 }
3210 const Domain reduced_domain =
3211 context_->DomainOf(var1)
3212 .AdditionWith(Domain(-x0))
3213 .InverseMultiplicationBy(b)
3214 .IntersectionWith(context_->DomainOf(var2)
3215 .AdditionWith(Domain(-y0))
3216 .InverseMultiplicationBy(-a));
3217
3218 if (reduced_domain.IsEmpty()) { // no solution
3219 return MarkConstraintAsFalse(
3220 ct, "linear2: implied ax + by = cte has no solutions");
3221 }
3222
3223 if (reduced_domain.Size() == 1) {
3224 const int64_t z = reduced_domain.FixedValue();
3225 const int64_t value1 = x0 + b * z;
3226 const int64_t value2 = y0 - a * z;
3227
3228 DCHECK(context_->DomainOf(var1).Contains(value1));
3229 DCHECK(context_->DomainOf(var2).Contains(value2));
3230 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
3231
3232 LinearConstraintProto* linear1 =
3233 context_->AddEnforcedConstraint(ct)->mutable_linear();
3234 linear1->add_vars(var1);
3235 linear1->add_coeffs(1);
3236 linear1->add_domain(value1);
3237 linear1->add_domain(value1);
3238
3239 LinearConstraintProto* linear2 =
3240 context_->AddEnforcedConstraint(ct)->mutable_linear();
3241 linear2->add_vars(var2);
3242 linear2->add_coeffs(1);
3243 linear2->add_domain(value2);
3244 linear2->add_domain(value2);
3245
3246 context_->UpdateRuleStats(
3247 "linear2: implied ax + by = cte has only one solution");
3248 context_->UpdateNewConstraintsVariableUsage();
3249 return RemoveConstraint(ct);
3250 }
3251
3252 const int64_t max_domain_size =
3253 context_->params().max_domain_size_for_linear2_expansion();
3254 const int64_t small_domain_size = max_domain_size / 2;
3255
3256 if (reduced_domain.Size() <= max_domain_size &&
3257 (context_->IsMostlyFullyEncoded(var1) ||
3258 context_->DomainSize(var1) <= small_domain_size) &&
3259 (context_->IsMostlyFullyEncoded(var2) ||
3260 context_->DomainSize(var2) <= small_domain_size)) {
3261 int num_imply1 = 0;
3262 int num_imply2 = 0;
3263 const auto imply_one_direction = [ct, this, &num_imply1, &num_imply2](
3264 const Domain& domain1,
3265 const Domain& domain2, int var1,
3266 int var2, int64_t coeff1,
3267 int64_t coeff2, int64_t cte) {
3268 if (context_->ModelIsUnsat()) return false;
3269
3270 for (const int64_t value : domain1.Values()) {
3271 const int64_t residual = cte - coeff1 * value;
3272 const int64_t implied_value = residual / coeff2;
3273 if (residual % coeff2 != 0 || !domain2.Contains(implied_value)) {
3274 const int lit1 = context_->GetOrCreateVarValueEncoding(var1, value);
3275 context_->AddEnforcedConstraint(ct)->mutable_bool_and()->add_literals(
3276 NegatedRef(lit1));
3277 ++num_imply1;
3278 } else {
3279 const int lit1 = context_->GetOrCreateVarValueEncoding(var1, value);
3280 const int lit2 =
3281 context_->GetOrCreateVarValueEncoding(var2, implied_value);
3282 ConstraintProto* imply_value = context_->AddEnforcedConstraint(ct);
3283 imply_value->mutable_bool_or()->add_literals(NegatedRef(lit1));
3284 imply_value->mutable_bool_or()->add_literals(lit2);
3285 ++num_imply2;
3286 }
3287 }
3288
3289 return true;
3290 };
3291
3292 if (!imply_one_direction(d1, d2, var1, var2, coeff1, coeff2, rhs)) {
3293 return false;
3294 }
3295 if (d1.Size() > 2 || d2.Size() > 2 || num_imply1 > 0) {
3296 if (!imply_one_direction(d2, d1, var2, var1, coeff2, coeff1, rhs)) {
3297 return false;
3298 }
3299 }
3300
3301 VLOG(3) << "ConvertLinear2EqCst: |enforcements| = "
3302 << ct->enforcement_literal_size() << ", domain1 = " << d1
3303 << ", domain2 = " << d2 << ", coeff1 = " << coeff1
3304 << ", coeff2 = " << coeff2 << ", num_imply1 = " << num_imply1
3305 << ", num_imply2 = " << num_imply2;
3306
3307 context_->UpdateNewConstraintsVariableUsage();
3308 context_->UpdateRuleStats(
3309 "linear2: convert implied ax + by == cte to clauses");
3310 return RemoveConstraint(ct);
3311 } else if (std::abs(coeff1) != 1 || std::abs(coeff2) != 1 ||
3312 coeff1 + coeff2 != 0) {
3313 VLOG(3) << "TODO ConvertLinear2EqCst: |enforcements| = "
3314 << ct->enforcement_literal_size()
3315 << ", domain1 = " << context_->DomainOf(var1)
3316 << ", domain2 = " << context_->DomainOf(var2)
3317 << ", coeff1 = " << coeff1 << ", coeff2 = " << coeff2
3318 << ", rhs = " << rhs
3319 << ", |encoding1| = " << context_->GetValueEncodingSize(var1)
3320 << ", |encoding2| = " << context_->GetValueEncodingSize(var2);
3321 context_->UpdateRuleStats(
3322 "TODO linear2: convert implied ax + by == cte to clauses for large "
3323 "domains");
3324 }
3325 return false;
3326}
3327
3328bool CpModelPresolver::PresolveEmptyLinearConstraint(ConstraintProto* ct) {
3329 const Domain rhs = ReadDomainFromProto(ct->linear());
3330 if (rhs.Contains(0)) {
3331 context_->UpdateRuleStats("linear: empty");
3332 return RemoveConstraint(ct);
3333 } else {
3334 return MarkConstraintAsFalse(ct, "linear: empty");
3335 }
3336}
3337
3338bool CpModelPresolver::PresolveSmallLinear(ConstraintProto* ct) {
3339 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
3340 if (context_->ModelIsUnsat()) return false;
3341
3342 bool changed = false;
3343 if (ct->linear().vars().size() <= 2) {
3344 if (!CanonicalizeLinear(ct, &changed)) return true;
3345 if (ct->constraint_case() != ConstraintProto::kLinear) return true;
3346 }
3347
3348 if (ct->linear().vars().empty()) {
3349 return PresolveEmptyLinearConstraint(ct);
3350 } else if (ct->linear().vars().size() == 1) {
3351 return PresolveLinearOfSizeOne(ct) || changed;
3352 } else if (ct->linear().vars().size() == 2) {
3353 return PresolveLinearOfSizeTwo(ct) || changed;
3354 }
3355
3356 return changed;
3357}
3358
3359bool CpModelPresolver::PresolveDiophantine(ConstraintProto* ct) {
3360 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
3361 if (ct->linear().vars().size() <= 1) return false;
3362 if (context_->ModelIsUnsat()) return false;
3363 // The transformation can add extra variables, and creates duplicate solutions
3364 // when enumerate_all_solutions is true.
3365 if (context_->params().enumerate_all_solutions()) return false;
3366
3367 const LinearConstraintProto& linear_constraint = ct->linear();
3368 if (linear_constraint.domain_size() != 2) return false;
3369 if (linear_constraint.domain(0) != linear_constraint.domain(1)) return false;
3370
3371 std::vector<int64_t> lbs(linear_constraint.vars_size());
3372 std::vector<int64_t> ubs(linear_constraint.vars_size());
3373 for (int i = 0; i < linear_constraint.vars_size(); ++i) {
3374 lbs[i] = context_->MinOf(linear_constraint.vars(i));
3375 ubs[i] = context_->MaxOf(linear_constraint.vars(i));
3376 }
3377 DiophantineSolution diophantine_solution = SolveDiophantine(
3378 linear_constraint.coeffs(), linear_constraint.domain(0), lbs, ubs);
3379
3380 if (!diophantine_solution.has_solutions) {
3381 return MarkConstraintAsFalse(ct, "diophantine: equality has no solutions");
3382 }
3383 if (diophantine_solution.no_reformulation_needed) return false;
3384 // Only first coefficients of kernel_basis elements and special_solution could
3385 // overflow int64_t due to the reduction applied in SolveDiophantineEquation,
3386 for (const std::vector<absl::int128>& b : diophantine_solution.kernel_basis) {
3387 if (!IsNegatableInt64(b[0])) {
3388 context_->UpdateRuleStats(
3389 "diophantine: couldn't apply due to int64_t overflow");
3390 return false;
3391 }
3392 }
3393 if (!IsNegatableInt64(diophantine_solution.special_solution[0])) {
3394 context_->UpdateRuleStats(
3395 "diophantine: couldn't apply due to int64_t overflow");
3396 return false;
3397 }
3398
3399 const int num_replaced_variables =
3400 static_cast<int>(diophantine_solution.special_solution.size());
3401 const int num_new_variables =
3402 static_cast<int>(diophantine_solution.kernel_vars_lbs.size());
3403 DCHECK_EQ(num_new_variables + 1, num_replaced_variables);
3404 for (int i = 0; i < num_new_variables; ++i) {
3405 if (!IsNegatableInt64(diophantine_solution.kernel_vars_lbs[i]) ||
3406 !IsNegatableInt64(diophantine_solution.kernel_vars_ubs[i])) {
3407 context_->UpdateRuleStats(
3408 "diophantine: couldn't apply due to int64_t overflow");
3409 return false;
3410 }
3411 }
3412 // TODO(user): Make sure the newly generated linear constraint
3413 // satisfy our no-overflow precondition on the min/max activity.
3414 // We should check that the model still satisfy conditions in
3415 // `PossibleIntegerOverflow` (sat/cp_model_checker.cc)
3416
3417 // Create new variables.
3418 std::vector<int> new_variables(num_new_variables);
3419 for (int i = 0; i < num_new_variables; ++i) {
3420 new_variables[i] = context_->working_model->variables_size();
3421 IntegerVariableProto* var = context_->working_model->add_variables();
3422 var->add_domain(
3423 static_cast<int64_t>(diophantine_solution.kernel_vars_lbs[i]));
3424 var->add_domain(
3425 static_cast<int64_t>(diophantine_solution.kernel_vars_ubs[i]));
3426 if (!ct->name().empty()) {
3427 var->set_name(absl::StrCat("u_diophantine_", ct->name(), "_", i));
3428 }
3429 }
3430
3431 // For i = 0, ..., num_replaced_variables - 1, creates
3432 // x[i] = special_solution[i]
3433 // + sum(kernel_basis[k][i]*y[k], max(1, i) <= k < vars.size - 1)
3434 // where:
3435 // y[k] is the newly created variable if 0 <= k < num_new_variables
3436 // y[k] = x[index_permutation[k + 1]] otherwise.
3437 std::vector<std::vector<int64_t>> lin_vars_lbs(num_replaced_variables);
3438 for (int i = 0; i < num_replaced_variables; ++i) {
3439 ConstraintProto* identity = context_->working_model->add_constraints();
3440 LinearConstraintProto* lin = identity->mutable_linear();
3441 if (!ct->name().empty()) {
3442 identity->set_name(absl::StrCat("c_diophantine_", ct->name(), "_", i));
3443 }
3444 *identity->mutable_enforcement_literal() = ct->enforcement_literal();
3445 const int var =
3446 linear_constraint.vars(diophantine_solution.index_permutation[i]);
3447 lin->add_vars(var);
3448 lin_vars_lbs[i].push_back(context_->MinOf(var));
3449 lin->add_coeffs(1);
3450 lin->add_domain(
3451 static_cast<int64_t>(diophantine_solution.special_solution[i]));
3452 lin->add_domain(
3453 static_cast<int64_t>(diophantine_solution.special_solution[i]));
3454 for (int j = std::max(1, i); j < num_replaced_variables; ++j) {
3455 lin->add_vars(new_variables[j - 1]);
3456 lin_vars_lbs[i].push_back(
3457 static_cast<int64_t>(diophantine_solution.kernel_vars_lbs[j - 1]));
3458 lin->add_coeffs(
3459 -static_cast<int64_t>(diophantine_solution.kernel_basis[j - 1][i]));
3460 }
3461 for (int j = num_replaced_variables; j < linear_constraint.vars_size();
3462 ++j) {
3463 const int var =
3464 linear_constraint.vars(diophantine_solution.index_permutation[j]);
3465 lin->add_vars(var);
3466 lin_vars_lbs[i].push_back(context_->MinOf(var));
3467 lin->add_coeffs(
3468 -static_cast<int64_t>(diophantine_solution.kernel_basis[j - 1][i]));
3469 }
3470
3471 // TODO(user): The domain in the proto are not necessarily up to date so
3472 // this might be stricter than necessary. Fix? It shouldn't matter too much
3473 // though.
3474 if (PossibleIntegerOverflow(*(context_->working_model), lin->vars(),
3475 lin->coeffs())) {
3476 context_->UpdateRuleStats(
3477 "diophantine: couldn't apply due to overflowing activity of new "
3478 "constraints");
3479 // Cancel working_model changes.
3480 context_->working_model->mutable_constraints()->DeleteSubrange(
3481 context_->working_model->constraints_size() - i - 1, i + 1);
3482 context_->working_model->mutable_variables()->DeleteSubrange(
3483 context_->working_model->variables_size() - num_new_variables,
3484 num_new_variables);
3485 return false;
3486 }
3487 }
3488 context_->InitializeNewDomains();
3489 // Scan the new constraints added above in reverse order so that the hint of
3490 // `new_variables[k]` can be computed from the hint of the existing variables
3491 // and from the hints of `new_variables[k']`, with k' > k.
3492 const int num_constraints = context_->working_model->constraints_size();
3493 for (int i = 0; i < num_replaced_variables; ++i) {
3494 const LinearConstraintProto& linear =
3495 context_->working_model->constraints(num_constraints - 1 - i).linear();
3496 DCHECK(linear.domain_size() == 2 && linear.domain(0) == linear.domain(1));
3497 solution_crush_.SetVarToLinearConstraintSolution(
3498 ct->enforcement_literal(), std::nullopt, linear.vars(), lin_vars_lbs[i],
3499 linear.coeffs(), linear.domain(0));
3500 }
3501
3502 if (VLOG_IS_ON(2)) {
3503 std::string log_eq = absl::StrCat(linear_constraint.domain(0), " = ");
3504 const int terms_to_show = std::min<int>(15, linear_constraint.vars_size());
3505 for (int i = 0; i < terms_to_show; ++i) {
3506 if (i > 0) absl::StrAppend(&log_eq, " + ");
3507 absl::StrAppend(
3508 &log_eq,
3509 linear_constraint.coeffs(diophantine_solution.index_permutation[i]),
3510 " x",
3511 linear_constraint.vars(diophantine_solution.index_permutation[i]));
3512 }
3513 if (terms_to_show < linear_constraint.vars_size()) {
3514 absl::StrAppend(&log_eq, "+ ... (", linear_constraint.vars_size(),
3515 " terms)");
3516 }
3517 VLOG(2) << "[Diophantine] " << log_eq;
3518 }
3519
3520 context_->UpdateRuleStats("diophantine: reformulated equality");
3521 context_->UpdateNewConstraintsVariableUsage();
3522 return RemoveConstraint(ct);
3523}
3524
3525// This tries to decompose the constraint into coeff * part1 + part2 and show
3526// that the value that part2 take is not important, thus the constraint can
3527// only be transformed on a constraint on the first part.
3528//
3529// TODO(user): Improve !! we miss simple case like x + 47 y + 50 z >= 50
3530// for positive variables. We should remove x, and ideally we should rewrite
3531// this as y + 2z >= 2 if we can show that its relaxation is just better?
3532// We should at least see that it is the same as 47y + 50 z >= 48.
3533//
3534// TODO(user): One easy algo is to first remove all enforcement term (even
3535// non-Boolean one) before applying the algo here and then re-linearize the
3536// non-Boolean terms.
3537void CpModelPresolver::TryToReduceCoefficientsOfLinearConstraint(
3538 int c, ConstraintProto* ct) {
3539 if (ct->constraint_case() != ConstraintProto::kLinear) return;
3540 if (context_->ModelIsUnsat()) return;
3541
3542 // Only consider "simple" constraints.
3543 const LinearConstraintProto& lin = ct->linear();
3544 if (lin.domain().size() != 2) return;
3545 const Domain rhs = ReadDomainFromProto(lin);
3546
3547 // Precompute a bunch of quantities and "canonicalize" the constraint.
3548 int64_t lb_sum = 0;
3549 int64_t ub_sum = 0;
3550 int64_t max_variation = 0;
3551
3552 rd_entries_.clear();
3553 rd_magnitudes_.clear();
3554 rd_lbs_.clear();
3555 rd_ubs_.clear();
3556
3557 int64_t max_magnitude = 0;
3558 const int num_terms = lin.vars().size();
3559 for (int i = 0; i < num_terms; ++i) {
3560 const int64_t coeff = lin.coeffs(i);
3561 const int64_t magnitude = std::abs(lin.coeffs(i));
3562 if (magnitude == 0) continue;
3563 max_magnitude = std::max(max_magnitude, magnitude);
3564
3565 int64_t lb;
3566 int64_t ub;
3567 if (coeff > 0) {
3568 lb = context_->MinOf(lin.vars(i));
3569 ub = context_->MaxOf(lin.vars(i));
3570 } else {
3571 lb = -context_->MaxOf(lin.vars(i));
3572 ub = -context_->MinOf(lin.vars(i));
3573 }
3574 lb_sum += lb * magnitude;
3575 ub_sum += ub * magnitude;
3576
3577 // Abort if fixed term, that might mess up code below.
3578 if (lb == ub) return;
3579
3580 rd_lbs_.push_back(lb);
3581 rd_ubs_.push_back(ub);
3582 rd_magnitudes_.push_back(magnitude);
3583 rd_entries_.push_back({magnitude, magnitude * (ub - lb), i});
3584 max_variation += rd_entries_.back().max_variation;
3585 }
3586
3587 // Mark trivially false constraint as such. This should have been already
3588 // done, but we require non-negative quantity below.
3589 if (lb_sum > rhs.Max() || rhs.Min() > ub_sum) {
3590 (void)MarkConstraintAsFalse(ct, "linear: trivially false");
3591 context_->UpdateConstraintVariableUsage(c);
3592 return;
3593 }
3594 const IntegerValue rhs_ub(CapSub(rhs.Max(), lb_sum));
3595 const IntegerValue rhs_lb(CapSub(ub_sum, rhs.Min()));
3596 const bool use_ub = max_variation > rhs_ub;
3597 const bool use_lb = max_variation > rhs_lb;
3598 if (!use_ub && !use_lb) {
3599 context_->UpdateRuleStats("linear: trivially true");
3600 (void)RemoveConstraint(ct);
3601 context_->UpdateConstraintVariableUsage(c);
3602 return;
3603 }
3604
3605 // No point doing more work for constraint with all coeff at +/-1.
3606 if (max_magnitude <= 1) return;
3607
3608 // TODO(user): All the lb/ub_feasible/infeasible class are updated in
3609 // exactly the same way. Find a more efficient algo?
3610 if (use_lb) {
3611 lb_feasible_.Reset(rhs_lb.value());
3612 lb_infeasible_.Reset(rhs.Min() - lb_sum - 1);
3613 }
3614 if (use_ub) {
3615 ub_feasible_.Reset(rhs_ub.value());
3616 ub_infeasible_.Reset(ub_sum - rhs.Max() - 1);
3617 }
3618
3619 // Process entries by decreasing magnitude. Update max_error to correspond
3620 // only to the sum of the not yet processed terms.
3621 uint64_t gcd = 0;
3622 int64_t max_error = max_variation;
3623 std::stable_sort(rd_entries_.begin(), rd_entries_.end(),
3624 [](const RdEntry& a, const RdEntry& b) {
3625 return a.magnitude > b.magnitude;
3626 });
3627 int64_t range = 0;
3628 rd_divisors_.clear();
3629 for (int i = 0; i < rd_entries_.size(); ++i) {
3630 const RdEntry& e = rd_entries_[i];
3631 gcd = std::gcd(gcd, e.magnitude);
3632 max_error -= e.max_variation;
3633
3634 // We regroup all term with the same coefficient into one.
3635 //
3636 // TODO(user): I am not sure there is no possible simplification across two
3637 // term with the same coeff, but it should be rare if it ever happens.
3638 range += e.max_variation / e.magnitude;
3639 if (i + 1 < rd_entries_.size() &&
3640 e.magnitude == rd_entries_[i + 1].magnitude) {
3641 continue;
3642 }
3643 const int64_t saved_range = range;
3644 range = 0;
3645
3646 if (e.magnitude > 1) {
3647 if ((!use_ub ||
3648 max_error <= PositiveRemainder(rhs_ub, IntegerValue(e.magnitude))) &&
3649 (!use_lb ||
3650 max_error <= PositiveRemainder(rhs_lb, IntegerValue(e.magnitude)))) {
3651 rd_divisors_.push_back(e.magnitude);
3652 }
3653 }
3654
3655 bool simplify_lb = false;
3656 if (use_lb) {
3657 lb_feasible_.AddMultiples(e.magnitude, saved_range);
3658 lb_infeasible_.AddMultiples(e.magnitude, saved_range);
3659
3660 // For a <= constraint, the max_feasible + error is still feasible.
3661 if (CapAdd(lb_feasible_.CurrentMax(), max_error) <=
3662 lb_feasible_.Bound()) {
3663 simplify_lb = true;
3664 }
3665 // For a <= constraint describing the infeasible set, the max_infeasible +
3666 // error is still infeasible.
3667 if (CapAdd(lb_infeasible_.CurrentMax(), max_error) <=
3668 lb_infeasible_.Bound()) {
3669 simplify_lb = true;
3670 }
3671 } else {
3672 simplify_lb = true;
3673 }
3674 bool simplify_ub = false;
3675 if (use_ub) {
3676 ub_feasible_.AddMultiples(e.magnitude, saved_range);
3677 ub_infeasible_.AddMultiples(e.magnitude, saved_range);
3678 if (CapAdd(ub_feasible_.CurrentMax(), max_error) <=
3679 ub_feasible_.Bound()) {
3680 simplify_ub = true;
3681 }
3682 if (CapAdd(ub_infeasible_.CurrentMax(), max_error) <=
3683 ub_infeasible_.Bound()) {
3684 simplify_ub = true;
3685 }
3686 } else {
3687 simplify_ub = true;
3688 }
3689
3690 if (max_error == 0) break; // Last term.
3691 if (simplify_lb && simplify_ub) {
3692 // We have a simplification since the second part can be ignored.
3693 context_->UpdateRuleStats("linear: remove irrelevant part");
3694 int64_t shift_lb = 0;
3695 int64_t shift_ub = 0;
3696 rd_vars_.clear();
3697 rd_coeffs_.clear();
3698 for (int j = 0; j <= i; ++j) {
3699 const int index = rd_entries_[j].index;
3700 const int64_t m = rd_magnitudes_[index];
3701 shift_lb += rd_lbs_[index] * m;
3702 shift_ub += rd_ubs_[index] * m;
3703 rd_vars_.push_back(lin.vars(index));
3704 rd_coeffs_.push_back(lin.coeffs(index));
3705 }
3706 LinearConstraintProto* mut_lin = ct->mutable_linear();
3707 mut_lin->mutable_vars()->Assign(rd_vars_.begin(), rd_vars_.end());
3708 mut_lin->mutable_coeffs()->Assign(rd_coeffs_.begin(), rd_coeffs_.end());
3709
3710 // The constraint become:
3711 // sum ci (X - lb) <= rhs_ub
3712 // sum ci (ub - X) <= rhs_lb
3713 // sum ci ub - rhs_lb <= sum ci X <= rhs_ub + sum ci lb.
3714 const int64_t new_rhs_lb =
3715 use_lb ? shift_ub - lb_feasible_.CurrentMax() : shift_lb;
3716 const int64_t new_rhs_ub =
3717 use_ub ? shift_lb + ub_feasible_.CurrentMax() : shift_ub;
3718 if (new_rhs_lb > new_rhs_ub) {
3719 (void)MarkConstraintAsFalse(ct, "linear: false after simplification");
3720 context_->UpdateConstraintVariableUsage(c);
3721 return;
3722 }
3723 FillDomainInProto(Domain(new_rhs_lb, new_rhs_ub), mut_lin);
3724 DivideLinearByGcd(ct);
3725 context_->UpdateConstraintVariableUsage(c);
3726 return;
3727 }
3728 }
3729
3730 if (gcd > 1) {
3731 // This might happen as a result of extra reduction after we already tried
3732 // this reduction.
3733 if (DivideLinearByGcd(ct)) {
3734 context_->UpdateConstraintVariableUsage(c);
3735 }
3736 return;
3737 }
3738
3739 // We didn't remove any irrelevant part, but we might be able to tighten
3740 // the constraint bound.
3741 if ((use_lb && lb_feasible_.CurrentMax() < lb_feasible_.Bound()) ||
3742 (use_ub && ub_feasible_.CurrentMax() < ub_feasible_.Bound())) {
3743 context_->UpdateRuleStats("linear: reduce rhs with DP");
3744 const int64_t new_rhs_lb =
3745 use_lb ? ub_sum - lb_feasible_.CurrentMax() : lb_sum;
3746 const int64_t new_rhs_ub =
3747 use_ub ? lb_sum + ub_feasible_.CurrentMax() : ub_sum;
3748 if (new_rhs_lb > new_rhs_ub) {
3749 (void)MarkConstraintAsFalse(ct, "linear: reduce rhs with DP");
3750 context_->UpdateConstraintVariableUsage(c);
3751 return;
3752 }
3753 FillDomainInProto(Domain(new_rhs_lb, new_rhs_ub), ct->mutable_linear());
3754 }
3755
3756 // Limit the number of "divisor" we try for approximate gcd.
3757 if (rd_divisors_.size() > 3) rd_divisors_.resize(3);
3758 for (const int64_t divisor : rd_divisors_) {
3759 // Try the <= side first.
3760 int64_t new_ub;
3762 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, rhs.Max(), &new_ub)) {
3763 continue;
3764 }
3765
3766 // The other side.
3767 int64_t minus_new_lb;
3768 for (int i = 0; i < rd_lbs_.size(); ++i) {
3769 std::swap(rd_lbs_[i], rd_ubs_[i]);
3770 rd_lbs_[i] = -rd_lbs_[i];
3771 rd_ubs_[i] = -rd_ubs_[i];
3772 }
3774 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, -rhs.Min(),
3775 &minus_new_lb)) {
3776 for (int i = 0; i < rd_lbs_.size(); ++i) {
3777 std::swap(rd_lbs_[i], rd_ubs_[i]);
3778 rd_lbs_[i] = -rd_lbs_[i];
3779 rd_ubs_[i] = -rd_ubs_[i];
3780 }
3781 continue;
3782 }
3783
3784 // Rewrite the constraint !
3785 context_->UpdateRuleStats("linear: simplify using approximate gcd");
3786 int new_size = 0;
3787 LinearConstraintProto* mutable_linear = ct->mutable_linear();
3788 for (int i = 0; i < lin.coeffs().size(); ++i) {
3789 const int64_t new_coeff =
3790 ClosestMultiple(lin.coeffs(i), divisor) / divisor;
3791 if (new_coeff == 0) continue;
3792 mutable_linear->set_vars(new_size, lin.vars(i));
3793 mutable_linear->set_coeffs(new_size, new_coeff);
3794 ++new_size;
3795 }
3796 mutable_linear->mutable_vars()->Truncate(new_size);
3797 mutable_linear->mutable_coeffs()->Truncate(new_size);
3798 const Domain new_rhs = Domain(-minus_new_lb, new_ub);
3799 if (new_rhs.IsEmpty()) {
3800 (void)MarkConstraintAsFalse(ct, "linear: false after approximate gcd");
3801 } else {
3802 FillDomainInProto(new_rhs, mutable_linear);
3803 }
3804 context_->UpdateConstraintVariableUsage(c);
3805 return;
3806 }
3807}
3808
3809namespace {
3810
3811// In the equation terms + coeff * var_domain \included rhs, returns true if can
3812// we always fix rhs to its min value for any value in terms. It is okay to
3813// not be as generic as possible here.
3814bool RhsCanBeFixedToMin(int64_t coeff, const Domain& var_domain,
3815 const Domain& terms, const Domain& rhs) {
3816 if (var_domain.NumIntervals() != 1) return false;
3817 if (std::abs(coeff) != 1) return false;
3818
3819 // If for all values in terms, there is one value below rhs.Min(), then
3820 // because we add only one integer interval, if there is a feasible value, it
3821 // can be at rhs.Min().
3822 //
3823 // TODO(user): generalize to larger coeff magnitude if rhs is also a multiple
3824 // or if terms is a multiple.
3825 if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
3826 return true;
3827 }
3828 if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
3829 return true;
3830 }
3831 return false;
3832}
3833
3834bool RhsCanBeFixedToMax(int64_t coeff, const Domain& var_domain,
3835 const Domain& terms, const Domain& rhs) {
3836 if (var_domain.NumIntervals() != 1) return false;
3837 if (std::abs(coeff) != 1) return false;
3838
3839 if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
3840 return true;
3841 }
3842 if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
3843 return true;
3844 }
3845 return false;
3846}
3847
3848int FixLiteralFromSet(const absl::flat_hash_set<int>& literals_at_true,
3849 LinearConstraintProto* linear) {
3850 int new_size = 0;
3851 int num_fixed = 0;
3852 const int num_terms = linear->vars().size();
3853 int64_t shift = 0;
3854 for (int i = 0; i < num_terms; ++i) {
3855 const int var = linear->vars(i);
3856 const int64_t coeff = linear->coeffs(i);
3857 if (literals_at_true.contains(var)) {
3858 // Var is at one.
3859 shift += coeff;
3860 ++num_fixed;
3861 } else if (!literals_at_true.contains(NegatedRef(var))) {
3862 linear->set_vars(new_size, var);
3863 linear->set_coeffs(new_size, coeff);
3864 ++new_size;
3865 } else {
3866 ++num_fixed;
3867 // Else the variable is at zero.
3868 }
3869 }
3870 linear->mutable_vars()->Truncate(new_size);
3871 linear->mutable_coeffs()->Truncate(new_size);
3872 if (shift != 0) {
3873 FillDomainInProto(ReadDomainFromProto(*linear).AdditionWith(Domain(-shift)),
3874 linear);
3875 }
3876 return num_fixed;
3877}
3878
3879} // namespace
3880
3881void CpModelPresolver::ProcessAtMostOneAndLinear() {
3882 if (time_limit_->LimitReached()) return;
3883 if (context_->ModelIsUnsat()) return;
3884 if (context_->params().presolve_inclusion_work_limit() == 0) return;
3885 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
3886
3887 ActivityBoundHelper amo_in_linear;
3888 amo_in_linear.AddAllAtMostOnes(*context_->working_model);
3889
3890 int num_changes = 0;
3891 const int num_constraints = context_->working_model->constraints_size();
3892 for (int c = 0; c < num_constraints; ++c) {
3893 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
3894 if (ct->constraint_case() != ConstraintProto::kLinear) continue;
3895
3896 // We loop if the constraint changed.
3897 for (int i = 0; i < 5; ++i) {
3898 const int old_size = ct->linear().vars().size();
3899 const int old_enf_size = ct->enforcement_literal().size();
3900 ProcessOneLinearWithAmo(c, ct, &amo_in_linear);
3901 if (context_->ModelIsUnsat()) return;
3902 if (ct->constraint_case() != ConstraintProto::kLinear) break;
3903 if (ct->linear().vars().size() == old_size &&
3904 ct->enforcement_literal().size() == old_enf_size) {
3905 break;
3906 }
3907 ++num_changes;
3908 }
3909 }
3910
3911 timer.AddCounter("num_changes", num_changes);
3912}
3913
3914// TODO(user): Similarly amo and bool_or intersection or amo and enforcement
3915// literals list can be presolved.
3916//
3917// TODO(user): This is stronger than the fully included case. Avoid having
3918// the second code?
3919void CpModelPresolver::ProcessOneLinearWithAmo(int ct_index,
3920 ConstraintProto* ct,
3921 ActivityBoundHelper* helper) {
3922 if (ct->constraint_case() != ConstraintProto::kLinear) return;
3923 if (ct->linear().vars().size() <= 1) return;
3924
3925 // TODO(user): It is possible in some corner-case that the linear constraint
3926 // is NOT canonicalized. This is because we might detect equivalence here and
3927 // we will continue with ProcessOneLinearWithAmo() in the parent loop.
3928 tmp_terms_.clear();
3929 temp_ct_.Clear();
3930 Domain non_boolean_domain(0);
3931 const int initial_size = ct->linear().vars().size();
3932 int64_t min_magnitude = std::numeric_limits<int64_t>::max();
3933 int64_t max_magnitude = 0;
3934 for (int i = 0; i < initial_size; ++i) {
3935 // TODO(user): Just do not use negative reference in linear!
3936 int ref = ct->linear().vars(i);
3937 int64_t coeff = ct->linear().coeffs(i);
3938 if (!RefIsPositive(ref)) {
3939 ref = NegatedRef(ref);
3940 coeff = -coeff;
3941 }
3942 if (context_->CanBeUsedAsLiteral(ref)) {
3943 tmp_terms_.push_back({ref, coeff});
3944 min_magnitude = std::min(min_magnitude, std::abs(coeff));
3945 max_magnitude = std::max(max_magnitude, std::abs(coeff));
3946 } else {
3947 non_boolean_domain =
3948 non_boolean_domain
3949 .AdditionWith(
3950 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
3951 .RelaxIfTooComplex();
3952 temp_ct_.mutable_linear()->add_vars(ref);
3953 temp_ct_.mutable_linear()->add_coeffs(coeff);
3954 }
3955 }
3956
3957 // Skip if there are no Booleans.
3958 if (tmp_terms_.empty()) return;
3959
3960 // Detect encoded AMO.
3961 //
3962 // TODO(user): Support more coefficient strengthening cases.
3963 // For instance on neos-954925.pb.gz we have stuff like:
3964 // 20 * (AMO1 + AMO2) - [coeff in 48 to 53] >= -15
3965 // this is really AMO1 + AMO2 - 2 * AMO3 >= 0.
3966 // Maybe if we reify the AMO to exactly one, this is visible since large
3967 // AMO can be rewriten with single variable (1 - extra var in exactly one).
3968 const Domain rhs = ReadDomainFromProto(ct->linear());
3969 if (non_boolean_domain == Domain(0) && rhs.NumIntervals() == 1 &&
3970 min_magnitude < max_magnitude) {
3971 int64_t min_activity = 0;
3972 for (const auto [ref, coeff] : tmp_terms_) {
3973 if (coeff <= 0) {
3974 min_activity += coeff;
3975 }
3976 }
3977 const int64_t transformed_rhs = rhs.Max() - min_activity;
3978 if (min_activity >= rhs.Min() && max_magnitude <= transformed_rhs) {
3979 std::vector<int> literals;
3980 for (const auto [ref, coeff] : tmp_terms_) {
3981 if (coeff + min_magnitude > transformed_rhs) continue;
3982 literals.push_back(coeff > 0 ? ref : NegatedRef(ref));
3983 }
3984 if (helper->IsAmo(literals)) {
3985 // We actually have an at-most-one in disguise.
3986 context_->UpdateRuleStats("linear + amo: detect hidden AMO");
3987 int64_t shift = 0;
3988 for (int i = 0; i < initial_size; ++i) {
3989 CHECK(RefIsPositive(ct->linear().vars(i)));
3990 if (ct->linear().coeffs(i) > 0) {
3991 ct->mutable_linear()->set_coeffs(i, 1);
3992 } else {
3993 ct->mutable_linear()->set_coeffs(i, -1);
3994 shift -= 1;
3995 }
3996 }
3997 FillDomainInProto(Domain(shift, shift + 1), ct->mutable_linear());
3998 return;
3999 }
4000 }
4001 }
4002
4003 // Get more precise activity estimate based on at most one and heuristics.
4004 const int64_t min_bool_activity =
4005 helper->ComputeMinActivity(tmp_terms_, &conditional_mins_);
4006 const int64_t max_bool_activity =
4007 helper->ComputeMaxActivity(tmp_terms_, &conditional_maxs_);
4008
4009 // Detect trivially true/false constraint under these new bounds.
4010 // TODO(user): relax rhs if only one side is trivial.
4011 const Domain activity = non_boolean_domain.AdditionWith(
4012 Domain(min_bool_activity, max_bool_activity));
4013 if (activity.IntersectionWith(rhs).IsEmpty()) {
4014 // Note that this covers min_bool_activity > max_bool_activity.
4015 (void)MarkConstraintAsFalse(ct,
4016 "linear + amo: infeasible linear constraint");
4017 context_->UpdateConstraintVariableUsage(ct_index);
4018 return;
4019 } else if (activity.IsIncludedIn(rhs)) {
4020 context_->UpdateRuleStats("linear + amo: trivial linear constraint");
4021 ct->Clear();
4022 context_->UpdateConstraintVariableUsage(ct_index);
4023 return;
4024 }
4025
4026 // We can use the new bound to propagate other terms.
4027 if (ct->enforcement_literal().empty() && !temp_ct_.linear().vars().empty()) {
4029 rhs.AdditionWith(
4030 Domain(min_bool_activity, max_bool_activity).Negation()),
4031 temp_ct_.mutable_linear());
4032 if (!PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_)) {
4033 return;
4034 }
4035 if (context_->ModelIsUnsat()) return;
4036 }
4037
4038 // Extract enforcement or fix literal.
4039 //
4040 // TODO(user): Do not use domain fonction, can be slow.
4041 //
4042 // TODO(user): Actually we might make the linear relaxation worse by
4043 // extracting some of these enforcement, as those can be "lifted" booleans. We
4044 // currently deal with that in RemoveEnforcementThatMakesConstraintTrivial(),
4045 // but that might not be the most efficient.
4046 //
4047 // TODO(user): Another reason for making the LP worse is that if we replace
4048 // part of the constraint via FindBig*LinearOverlap() then our activity bounds
4049 // might not be as precise when we will linearize this constraint again.
4050 std::vector<int> new_enforcement;
4051 std::vector<int> must_be_true;
4052 for (int i = 0; i < tmp_terms_.size(); ++i) {
4053 const int ref = tmp_terms_[i].first;
4054
4055 const Domain bool0(conditional_mins_[i][0], conditional_maxs_[i][0]);
4056 const Domain activity0 = bool0.AdditionWith(non_boolean_domain);
4057 if (activity0.IntersectionWith(rhs).IsEmpty()) {
4058 // Must be 1.
4059 must_be_true.push_back(ref);
4060 } else if (activity0.IsIncludedIn(rhs)) {
4061 // Trivial constraint on 0.
4062 new_enforcement.push_back(ref);
4063 }
4064
4065 const Domain bool1(conditional_mins_[i][1], conditional_maxs_[i][1]);
4066 const Domain activity1 = bool1.AdditionWith(non_boolean_domain);
4067 if (activity1.IntersectionWith(rhs).IsEmpty()) {
4068 // Must be 0.
4069 must_be_true.push_back(NegatedRef(ref));
4070 } else if (activity1.IsIncludedIn(rhs)) {
4071 // Trivial constraint on 1.
4072 new_enforcement.push_back(NegatedRef(ref));
4073 }
4074 }
4075
4076 // Note that both list can be non empty, if for instance we have small * X +
4077 // big * Y + ... <= rhs and amo(X, Y). We could see that Y can never be true
4078 // and if X is true, then the constraint could be trivial.
4079 //
4080 // So we fix things first if we can.
4081 if (ct->enforcement_literal().empty() && !must_be_true.empty()) {
4082 // Note that our logic to do more presolve iteration depends on the
4083 // number of rule applied, so it is important to count this correctly.
4084 context_->UpdateRuleStats("linear + amo: fixed literal",
4085 must_be_true.size());
4086 for (const int lit : must_be_true) {
4087 if (!context_->SetLiteralToTrue(lit)) return;
4088 }
4089 bool changed = false;
4090 if (!CanonicalizeLinear(ct, &changed)) return;
4091 context_->UpdateConstraintVariableUsage(ct_index);
4092 return;
4093 }
4094
4095 if (!new_enforcement.empty()) {
4096 context_->UpdateRuleStats("linear + amo: extracted enforcement literal",
4097 new_enforcement.size());
4098 for (const int ref : new_enforcement) {
4099 ct->add_enforcement_literal(ref);
4100 }
4101 }
4102
4103 if (!ct->enforcement_literal().empty()) {
4104 const int old_enf_size = ct->enforcement_literal().size();
4105 if (!helper->PresolveEnforcement(ct->linear().vars(), ct, &temp_set_)) {
4106 context_->UpdateRuleStats("linear + amo: infeasible enforcement");
4107 ct->Clear();
4108 context_->UpdateConstraintVariableUsage(ct_index);
4109 return;
4110 }
4111 if (ct->enforcement_literal().size() < old_enf_size) {
4112 context_->UpdateRuleStats("linear + amo: simplified enforcement list");
4113 context_->UpdateConstraintVariableUsage(ct_index);
4114 }
4115
4116 for (const int lit : must_be_true) {
4117 if (temp_set_.contains(NegatedRef(lit))) {
4118 // A literal must be true but is incompatible with what the enforcement
4119 // implies. The constraint must be false!
4120 (void)MarkConstraintAsFalse(
4121 ct, "linear + amo: advanced infeasible linear constraint");
4122 context_->UpdateConstraintVariableUsage(ct_index);
4123 return;
4124 }
4125 }
4126
4127 // TODO(user): do that in more cases?
4128 if (ct->enforcement_literal().size() == 1 && !must_be_true.empty()) {
4129 // Add implication, and remove literal from the constraint in this case.
4130 // To remove them, we just add them to temp_set_ and FixLiteralFromSet()
4131 // will take care of it.
4132 context_->UpdateRuleStats("linear + amo: added implications");
4133 ConstraintProto* new_ct = context_->working_model->add_constraints();
4134 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
4135 for (const int lit : must_be_true) {
4136 new_ct->mutable_bool_and()->add_literals(lit);
4137 temp_set_.insert(lit);
4138 }
4139 context_->UpdateNewConstraintsVariableUsage();
4140 }
4141
4142 const int num_fixed = FixLiteralFromSet(temp_set_, ct->mutable_linear());
4143 if (num_fixed > new_enforcement.size()) {
4144 context_->UpdateRuleStats(
4145 "linear + amo: fixed literal implied by enforcement");
4146 }
4147 if (num_fixed > 0) {
4148 context_->UpdateConstraintVariableUsage(ct_index);
4149 }
4150 }
4151
4152 if (ct->linear().vars().empty()) {
4153 context_->UpdateRuleStats("linear + amo: empty after processing");
4154 PresolveSmallLinear(ct);
4155 context_->UpdateConstraintVariableUsage(ct_index);
4156 return;
4157 }
4158
4159 // If the constraint is of size 1 or 2, we re-presolve it right away.
4160 if (initial_size != ct->linear().vars().size() && PresolveSmallLinear(ct)) {
4161 context_->UpdateConstraintVariableUsage(ct_index);
4162 if (ct->constraint_case() != ConstraintProto::kLinear) return;
4163 }
4164
4165 // Detect enforcement literal that could actually be lifted, and as such can
4166 // just be removed from the enforcement list. Ideally, during relaxation we
4167 // would lift such Boolean again.
4168 //
4169 // Note that this code is independent from anything above.
4170 if (!ct->enforcement_literal().empty()) {
4171 // TODO(user): remove duplication with code above?
4172 tmp_terms_.clear();
4173 Domain non_boolean_domain(0);
4174 const int num_ct_terms = ct->linear().vars().size();
4175 for (int i = 0; i < num_ct_terms; ++i) {
4176 const int ref = ct->linear().vars(i);
4177 const int64_t coeff = ct->linear().coeffs(i);
4178 CHECK(RefIsPositive(ref));
4179 if (context_->CanBeUsedAsLiteral(ref)) {
4180 tmp_terms_.push_back({ref, coeff});
4181 } else {
4182 non_boolean_domain =
4183 non_boolean_domain
4184 .AdditionWith(
4185 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
4186 .RelaxIfTooComplex();
4187 }
4188 }
4189 const int num_removed = helper->RemoveEnforcementThatMakesConstraintTrivial(
4190 tmp_terms_, non_boolean_domain, ReadDomainFromProto(ct->linear()), ct);
4191 if (num_removed > 0) {
4192 context_->UpdateRuleStats("linear + amo: removed enforcement literal",
4193 num_removed);
4194 context_->UpdateConstraintVariableUsage(ct_index);
4195 }
4196 }
4197}
4198
4199bool CpModelPresolver::PropagateDomainsInLinear(int ct_index,
4200 ConstraintProto* ct) {
4201 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
4202 if (context_->ModelIsUnsat()) return false;
4203
4204 // For fast mode.
4205 int64_t min_activity;
4206 int64_t max_activity;
4207
4208 // For slow mode.
4209 const int num_vars = ct->linear().vars_size();
4210 auto& term_domains = context_->tmp_term_domains;
4211 auto& left_domains = context_->tmp_left_domains;
4212 const bool slow_mode = num_vars < 10;
4213
4214 // Compute the implied rhs bounds from the variable ones.
4215 if (slow_mode) {
4216 term_domains.resize(num_vars + 1);
4217 left_domains.resize(num_vars + 1);
4218 left_domains[0] = Domain(0);
4219 term_domains[num_vars] = Domain(0);
4220 for (int i = 0; i < num_vars; ++i) {
4221 const int var = ct->linear().vars(i);
4222 const int64_t coeff = ct->linear().coeffs(i);
4223 DCHECK(RefIsPositive(var));
4224 term_domains[i] = context_->DomainOf(var).MultiplicationBy(coeff);
4225 left_domains[i + 1] =
4226 left_domains[i].AdditionWith(term_domains[i]).RelaxIfTooComplex();
4227 }
4228 } else {
4229 std::tie(min_activity, max_activity) =
4230 context_->ComputeMinMaxActivity(ct->linear());
4231 }
4232 const Domain& implied_rhs =
4233 slow_mode ? left_domains[num_vars] : Domain(min_activity, max_activity);
4234
4235 // Abort if trivial.
4236 const Domain old_rhs = ReadDomainFromProto(ct->linear());
4237 if (implied_rhs.IsIncludedIn(old_rhs)) {
4238 if (ct_index != -1) context_->UpdateRuleStats("linear: always true");
4239 return RemoveConstraint(ct);
4240 }
4241
4242 // Incorporate the implied rhs information.
4243 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
4244 if (rhs.IsEmpty()) {
4245 return MarkConstraintAsFalse(ct, "linear: infeasible");
4246 }
4247 if (rhs != old_rhs) {
4248 if (ct_index != -1) context_->UpdateRuleStats("linear: simplified rhs");
4249 }
4250 FillDomainInProto(rhs, ct->mutable_linear());
4251
4252 // Propagate the variable bounds.
4253 if (ct->enforcement_literal().size() > 1) return false;
4254
4255 bool new_bounds = false;
4256 bool recanonicalize = false;
4257 Domain negated_rhs = rhs.Negation();
4258 Domain right_domain(0);
4259 Domain new_domain;
4260 Domain activity_minus_term;
4261 for (int i = num_vars - 1; i >= 0; --i) {
4262 const int var = ct->linear().vars(i);
4263 const int64_t var_coeff = ct->linear().coeffs(i);
4264
4265 if (slow_mode) {
4266 right_domain =
4267 right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
4268 activity_minus_term = left_domains[i].AdditionWith(right_domain);
4269 } else {
4270 int64_t min_term = var_coeff * context_->MinOf(var);
4271 int64_t max_term = var_coeff * context_->MaxOf(var);
4272 if (var_coeff < 0) std::swap(min_term, max_term);
4273 activity_minus_term =
4274 Domain(min_activity - min_term, max_activity - max_term);
4275 }
4276 new_domain = activity_minus_term.AdditionWith(negated_rhs)
4277 .InverseMultiplicationBy(-var_coeff);
4278
4279 if (ct->enforcement_literal().empty()) {
4280 // Push the new domain.
4281 if (!context_->IntersectDomainWith(var, new_domain, &new_bounds)) {
4282 return true;
4283 }
4284 } else if (ct->enforcement_literal().size() == 1) {
4285 // We cannot push the new domain, but we can add some deduction.
4286 CHECK(RefIsPositive(var));
4287 if (!context_->DomainOfVarIsIncludedIn(var, new_domain)) {
4288 context_->deductions.AddDeduction(ct->enforcement_literal(0), var,
4289 new_domain);
4290 }
4291 }
4292
4293 if (context_->IsFixed(var)) {
4294 // This will make sure we remove that fixed variable from the constraint.
4295 recanonicalize = true;
4296 continue;
4297 }
4298
4299 // The other transformations below require a non-reified constraint.
4300 if (ct_index == -1) continue;
4301 if (!ct->enforcement_literal().empty()) continue;
4302
4303 // Given a variable that only appear in one constraint and in the
4304 // objective, for any feasible solution, it will be always better to move
4305 // this singleton variable as much as possible towards its good objective
4306 // direction. Sometime, we can detect that we will always be able to
4307 // do this until the only constraint of this singleton variable is tight.
4308 //
4309 // When this happens, we can make the constraint an equality. Note that it
4310 // might not always be good to restrict constraint like this, but in this
4311 // case, the RemoveSingletonInLinear() code should be able to remove this
4312 // variable altogether.
4313 if (rhs.Min() != rhs.Max() &&
4314 context_->VariableWithCostIsUniqueAndRemovable(var)) {
4315 const int64_t obj_coeff = context_->ObjectiveMap().at(var);
4316 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
4317 bool fixed = false;
4318 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->DomainOf(var),
4319 activity_minus_term, rhs)) {
4320 rhs = Domain(rhs.Min());
4321 fixed = true;
4322 }
4323 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->DomainOf(var),
4324 activity_minus_term, rhs)) {
4325 rhs = Domain(rhs.Max());
4326 fixed = true;
4327 }
4328 if (fixed) {
4329 context_->UpdateRuleStats("linear: tightened into equality");
4330 // Compute a new `var` hint so that the lhs of `ct` is equal to `rhs`.
4331 solution_crush_.SetVarToLinearConstraintSolution(
4332 /*enforcement_lits=*/{}, i, ct->linear().vars(),
4333 /*default_values=*/{}, ct->linear().coeffs(), rhs.FixedValue());
4334 FillDomainInProto(rhs, ct->mutable_linear());
4335 negated_rhs = rhs.Negation();
4336
4337 // Restart the loop.
4338 i = num_vars;
4339 right_domain = Domain(0);
4340 continue;
4341 }
4342 }
4343
4344 // Can we perform some substitution?
4345 //
4346 // TODO(user): there is no guarantee we will not miss some since we might
4347 // not reprocess a constraint once other have been deleted.
4348
4349 // Skip affine constraint. It is more efficient to substitute them lazily
4350 // when we process other constraints. Note that if we relax the fact that
4351 // we substitute only equalities, we can deal with inequality of size 2
4352 // here.
4353 if (ct->linear().vars().size() <= 2) continue;
4354
4355 // TODO(user): We actually do not need a strict equality when
4356 // keep_all_feasible_solutions is false, but that simplifies things as the
4357 // SubstituteVariable() function cannot fail this way.
4358 if (rhs.Min() != rhs.Max()) continue;
4359
4360 // NOTE: The mapping doesn't allow us to remove a variable if
4361 // keep_all_feasible_solutions is true.
4362 //
4363 // TODO(user): This shouldn't be necessary, but caused some failure on
4364 // IntModExpandTest.FzTest. Fix.
4365 if (context_->params().keep_all_feasible_solutions_in_presolve()) continue;
4366
4367 // Only consider "implied free" variables. Note that the coefficient of
4368 // magnitude 1 is important otherwise we can't easily remove the
4369 // constraint since the fact that the sum of the other terms must be a
4370 // multiple of coeff will not be enforced anymore.
4371 if (std::abs(var_coeff) != 1) continue;
4372 if (context_->params().presolve_substitution_level() <= 0) continue;
4373
4374 // Only consider substitution that reduce the number of entries.
4375 const bool is_in_objective = context_->VarToConstraints(var).contains(-1);
4376 {
4377 int col_size = context_->VarToConstraints(var).size();
4378 if (is_in_objective) col_size--;
4379 const int row_size = ct->linear().vars_size();
4380
4381 // This is actually an upper bound on the number of entries added since
4382 // some of them might already be present.
4383 const int num_entries_added = (row_size - 1) * (col_size - 1);
4384 const int num_entries_removed = col_size + row_size - 1;
4385 if (num_entries_added > num_entries_removed) continue;
4386 }
4387
4388 // Check pre-conditions on all the constraints in which this variable
4389 // appear. Basically they must all be linear.
4390 std::vector<int> others;
4391 bool abort = false;
4392 for (const int c : context_->VarToConstraints(var)) {
4393 if (c == kObjectiveConstraint) continue;
4394 if (c == kAffineRelationConstraint) {
4395 abort = true;
4396 break;
4397 }
4398 if (c == ct_index) continue;
4399 if (context_->working_model->constraints(c).constraint_case() !=
4401 abort = true;
4402 break;
4403 }
4404 for (const int ref :
4405 context_->working_model->constraints(c).enforcement_literal()) {
4406 if (PositiveRef(ref) == var) {
4407 abort = true;
4408 break;
4409 }
4410 }
4411 if (abort) break;
4412 others.push_back(c);
4413 }
4414 if (abort) continue;
4415
4416 // If the domain implied by this constraint is the same as the current
4417 // domain of the variable, this variable is implied free. Otherwise, we
4418 // check if the intersection with the domain implied by another constraint
4419 // make it implied free.
4420 if (context_->DomainOf(var) != new_domain) {
4421 // We only do that for doubleton because we don't want the propagation to
4422 // be less strong. If we were to replace this variable in other constraint
4423 // the implied bound from the linear expression might not be as good.
4424 //
4425 // TODO(user): We still substitute even if this happens in the objective
4426 // though. Is that good?
4427 if (others.size() != 1) continue;
4428 const ConstraintProto& other_ct =
4429 context_->working_model->constraints(others.front());
4430 if (!other_ct.enforcement_literal().empty()) continue;
4431
4432 // Compute the implied domain using the other constraint.
4433 // We only do that if it is not too long to avoid quadratic worst case.
4434 const LinearConstraintProto& other_lin = other_ct.linear();
4435 if (other_lin.vars().size() > 100) continue;
4436 Domain implied = ReadDomainFromProto(other_lin);
4437 int64_t other_coeff = 0;
4438 for (int i = 0; i < other_lin.vars().size(); ++i) {
4439 const int v = other_lin.vars(i);
4440 const int64_t coeff = other_lin.coeffs(i);
4441 if (v == var) {
4442 // It is possible the constraint is not canonical if it wasn't
4443 // processed yet !
4444 other_coeff += coeff;
4445 } else {
4446 implied =
4447 implied
4448 .AdditionWith(context_->DomainOf(v).MultiplicationBy(-coeff))
4449 .RelaxIfTooComplex();
4450 }
4451 }
4452 if (other_coeff == 0) continue;
4453 implied = implied.InverseMultiplicationBy(other_coeff);
4454
4455 // Since we compute it, we can as well update the domain right now.
4456 // This is also needed for postsolve to have a tight domain.
4457 if (!context_->IntersectDomainWith(var, implied)) return false;
4458 if (context_->IsFixed(var)) continue;
4459 if (new_domain.IntersectionWith(implied) != context_->DomainOf(var)) {
4460 continue;
4461 }
4462
4463 context_->UpdateRuleStats("linear: doubleton free");
4464 }
4465
4466 // Substitute in objective.
4467 // This can fail in overflow corner cases, so we abort before doing any
4468 // actual changes.
4469 if (is_in_objective &&
4470 !context_->SubstituteVariableInObjective(var, var_coeff, *ct)) {
4471 if (context_->ModelIsUnsat()) return false;
4472 continue;
4473 }
4474
4475 // Do the actual substitution.
4476 ConstraintProto copy_if_we_abort;
4477 absl::c_sort(others);
4478 for (const int c : others) {
4479 // TODO(user): The copy is needed to have a simpler overflow-checking
4480 // code were we check once the substitution is done. If needed we could
4481 // optimize that, but with more code.
4482 copy_if_we_abort = context_->working_model->constraints(c);
4483
4484 // In some corner cases, this might violate our overflow precondition or
4485 // even create an overflow. The danger is limited since the range of the
4486 // linear expression used in the definition do not exceed the domain of
4487 // the variable we substitute. But this is not the case for the doubleton
4488 // case above.
4489 if (!SubstituteVariable(
4490 var, var_coeff, *ct,
4491 context_->working_model->mutable_constraints(c))) {
4492 // The function above can fail because of overflow, but also if the
4493 // constraint was not canonicalized yet and the variable is actually not
4494 // there (we have var - var for instance).
4495 //
4496 // TODO(user): we canonicalize it right away, but I am not sure it is
4497 // really needed.
4498 bool changed = false;
4499 if (!CanonicalizeLinear(context_->working_model->mutable_constraints(c),
4500 &changed)) {
4501 return true;
4502 }
4503 if (changed) {
4504 context_->UpdateConstraintVariableUsage(c);
4505 }
4506 abort = true;
4507 break;
4508 }
4509
4511 *context_->working_model,
4512 context_->working_model->constraints(c).linear().vars(),
4513 context_->working_model->constraints(c).linear().coeffs())) {
4514 // Revert the change in this case.
4515 *context_->working_model->mutable_constraints(c) = copy_if_we_abort;
4516 abort = true;
4517 break;
4518 }
4519
4520 // TODO(user): We should re-enqueue these constraints for presolve.
4521 context_->UpdateConstraintVariableUsage(c);
4522 }
4523 if (abort) continue;
4524
4525 context_->UpdateRuleStats(
4526 absl::StrCat("linear: variable substitution ", others.size()));
4527
4528 // The variable now only appear in its definition and we can remove it
4529 // because it was implied free.
4530 //
4531 // Tricky: If the linear constraint contains other variables that are only
4532 // used here, then the postsolve needs more info. We do need to indicate
4533 // that whatever the value of those other variables, we will have a way to
4534 // assign var. We do that by putting it fist.
4535 CHECK_EQ(context_->VarToConstraints(var).size(), 1);
4536 context_->MarkVariableAsRemoved(var);
4537 ConstraintProto* mapping_ct =
4538 context_->NewMappingConstraint(__FILE__, __LINE__);
4539 *mapping_ct = *ct;
4540 LinearConstraintProto* mapping_linear_ct = mapping_ct->mutable_linear();
4541 std::swap(mapping_linear_ct->mutable_vars()->at(0),
4542 mapping_linear_ct->mutable_vars()->at(i));
4543 std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
4544 mapping_linear_ct->mutable_coeffs()->at(i));
4545 return RemoveConstraint(ct);
4546 }
4547
4548 // special case.
4549 if (ct_index == -1) {
4550 if (new_bounds) {
4551 context_->UpdateRuleStats(
4552 "linear: reduced variable domains in derived constraint");
4553 }
4554 return false;
4555 }
4556
4557 if (new_bounds) {
4558 context_->UpdateRuleStats("linear: reduced variable domains");
4559 }
4560 if (recanonicalize) {
4561 bool changed = false;
4562 (void)CanonicalizeLinear(ct, &changed);
4563 return changed;
4564 }
4565 return false;
4566}
4567
4568// The constraint from its lower value is sum positive_coeff * X <= rhs.
4569// If from_lower_bound is false, then it is the constraint from its upper value.
4570void CpModelPresolver::LowerThanCoeffStrengthening(bool from_lower_bound,
4571 int64_t min_magnitude,
4572 int64_t rhs,
4573 ConstraintProto* ct) {
4574 const LinearConstraintProto& arg = ct->linear();
4575 const int64_t second_threshold = rhs - min_magnitude;
4576 const int num_vars = arg.vars_size();
4577
4578 // Special case:
4579 // - The terms above rhs must be fixed to zero.
4580 // - The terms in (second_threshold, rhs] can be fixed to rhs as
4581 // they will force all other terms to zero if not at zero themselves.
4582 // - If what is left can be simplified to a single coefficient, we can
4583 // put the constraint into a special form.
4584 //
4585 // TODO(user): More generally, if we ignore term that set everything else to
4586 // zero, we can preprocess the constraint left and then add them back. So we
4587 // can do all our other reduction like normal GCD or more advanced ones like
4588 // DP based or approximate GCD.
4589 if (min_magnitude <= second_threshold) {
4590 // Compute max_magnitude for the term <= second_threshold.
4591 int64_t max_magnitude_left = 0;
4592 int64_t max_activity_left = 0;
4593 int64_t activity_when_coeff_are_one = 0;
4594 int64_t gcd = 0;
4595 for (int i = 0; i < num_vars; ++i) {
4596 const int64_t magnitude = std::abs(arg.coeffs(i));
4597 if (magnitude <= second_threshold) {
4598 gcd = std::gcd(gcd, magnitude);
4599 max_magnitude_left = std::max(max_magnitude_left, magnitude);
4600 const int64_t bound_diff =
4601 context_->MaxOf(arg.vars(i)) - context_->MinOf(arg.vars(i));
4602 activity_when_coeff_are_one += bound_diff;
4603 max_activity_left += magnitude * bound_diff;
4604 }
4605 }
4606 CHECK_GT(min_magnitude, 0);
4607 CHECK_LE(min_magnitude, max_magnitude_left);
4608
4609 // Not considering the variable that set everyone at zero when true:
4610 int64_t new_rhs = 0;
4611 bool set_all_to_one = false;
4612 if (max_activity_left <= rhs) {
4613 // We are left with a trivial constraint.
4614 context_->UpdateRuleStats("linear with partial amo: trivial");
4615 new_rhs = activity_when_coeff_are_one;
4616 set_all_to_one = true;
4617 } else if (rhs / min_magnitude == rhs / max_magnitude_left) {
4618 // We are left with a sum <= new_rhs constraint.
4619 context_->UpdateRuleStats("linear with partial amo: constant coeff");
4620 new_rhs = rhs / min_magnitude;
4621 set_all_to_one = true;
4622 } else if (gcd > 1) {
4623 // We are left with a constraint that can be simplified by gcd.
4624 context_->UpdateRuleStats("linear with partial amo: gcd");
4625 new_rhs = rhs / gcd;
4626 }
4627
4628 if (new_rhs > 0) {
4629 int64_t rhs_offset = 0;
4630 for (int i = 0; i < num_vars; ++i) {
4631 const int ref = arg.vars(i);
4632 const int64_t coeff = from_lower_bound ? arg.coeffs(i) : -arg.coeffs(i);
4633
4634 int64_t new_coeff;
4635 const int64_t magnitude = std::abs(coeff);
4636 if (magnitude > rhs) {
4637 new_coeff = new_rhs + 1;
4638 } else if (magnitude > second_threshold) {
4639 new_coeff = new_rhs;
4640 } else {
4641 new_coeff = set_all_to_one ? 1 : magnitude / gcd;
4642 }
4643
4644 // In the transformed domain we will always have
4645 // magnitude * (var - lb) or magnitude * (ub - var)
4646 if (coeff > 0) {
4647 ct->mutable_linear()->set_coeffs(i, new_coeff);
4648 rhs_offset += new_coeff * context_->MinOf(ref);
4649 } else {
4650 ct->mutable_linear()->set_coeffs(i, -new_coeff);
4651 rhs_offset -= new_coeff * context_->MaxOf(ref);
4652 }
4653 }
4654 FillDomainInProto(Domain(rhs_offset, new_rhs + rhs_offset),
4655 ct->mutable_linear());
4656 return;
4657 }
4658 }
4659
4660 int64_t rhs_offset = 0;
4661 for (int i = 0; i < num_vars; ++i) {
4662 int ref = arg.vars(i);
4663 int64_t coeff = arg.coeffs(i);
4664 if (coeff < 0) {
4665 ref = NegatedRef(ref);
4666 coeff = -coeff;
4667 }
4668
4669 if (coeff > rhs) {
4670 if (ct->enforcement_literal().empty()) {
4671 // Shifted variable must be zero.
4672 //
4673 // TODO(user): Note that here IntersectDomainWith() can only return
4674 // false if for some reason this variable has an affine representative
4675 // for which this fail. Ideally we should always replace/merge
4676 // representative right away, but this is a bit difficult to enforce
4677 // currently.
4678 context_->UpdateRuleStats("linear: fix variable to its bound");
4679 if (!context_->IntersectDomainWith(
4680 ref, Domain(from_lower_bound ? context_->MinOf(ref)
4681 : context_->MaxOf(ref)))) {
4682 return;
4683 }
4684 }
4685
4686 // TODO(user): What to do with the coeff if there is enforcement?
4687 continue;
4688 }
4689 if (coeff > second_threshold && coeff < rhs) {
4690 context_->UpdateRuleStats(
4691 "linear: coefficient strengthening by increasing it");
4692 if (from_lower_bound) {
4693 // coeff * (X - LB + LB) -> rhs * (X - LB) + coeff * LB
4694 rhs_offset -= (coeff - rhs) * context_->MinOf(ref);
4695 } else {
4696 // coeff * (X - UB + UB) -> rhs * (X - UB) + coeff * UB
4697 rhs_offset -= (coeff - rhs) * context_->MaxOf(ref);
4698 }
4699 ct->mutable_linear()->set_coeffs(i, arg.coeffs(i) > 0 ? rhs : -rhs);
4700 }
4701 }
4702 if (rhs_offset != 0) {
4703 FillDomainInProto(ReadDomainFromProto(arg).AdditionWith(Domain(rhs_offset)),
4704 ct->mutable_linear());
4705 }
4706}
4707
4708// Identify Boolean variable that makes the constraint always true when set to
4709// true or false. Moves such literal to the constraint enforcement literals
4710// list.
4711//
4712// We also generalize this to integer variable at one of their bound.
4713//
4714// This operation is similar to coefficient strengthening in the MIP world.
4715void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
4716 int ct_index, ConstraintProto* ct) {
4717 if (ct->constraint_case() != ConstraintProto::kLinear) return;
4718 if (context_->ModelIsUnsat()) return;
4719
4720 const LinearConstraintProto& arg = ct->linear();
4721 const int num_vars = arg.vars_size();
4722
4723 // No need to process size one constraints, they will be presolved separately.
4724 // We also do not want to split them in two.
4725 if (num_vars <= 1) return;
4726
4727 int64_t min_sum = 0;
4728 int64_t max_sum = 0;
4729 int64_t max_coeff_magnitude = 0;
4730 int64_t min_coeff_magnitude = std::numeric_limits<int64_t>::max();
4731 for (int i = 0; i < num_vars; ++i) {
4732 const int ref = arg.vars(i);
4733 const int64_t coeff = arg.coeffs(i);
4734 if (coeff > 0) {
4735 max_coeff_magnitude = std::max(max_coeff_magnitude, coeff);
4736 min_coeff_magnitude = std::min(min_coeff_magnitude, coeff);
4737 min_sum += coeff * context_->MinOf(ref);
4738 max_sum += coeff * context_->MaxOf(ref);
4739 } else {
4740 max_coeff_magnitude = std::max(max_coeff_magnitude, -coeff);
4741 min_coeff_magnitude = std::min(min_coeff_magnitude, -coeff);
4742 min_sum += coeff * context_->MaxOf(ref);
4743 max_sum += coeff * context_->MinOf(ref);
4744 }
4745 }
4746 if (max_coeff_magnitude == 1) return;
4747
4748 // We can only extract enforcement literals if the maximum coefficient
4749 // magnitude is large enough. Note that we handle complex domain.
4750 //
4751 // TODO(user): Depending on how we split below, the threshold are not the
4752 // same. This is maybe not too important, we just don't split as often as we
4753 // could, but it is still unclear if splitting is good.
4754 const auto& domain = ct->linear().domain();
4755 const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
4756 const int64_t lb_threshold = max_sum - domain[1];
4757 if (max_coeff_magnitude + min_coeff_magnitude <
4758 std::max(ub_threshold, lb_threshold)) {
4759 // We also have other kind of coefficient strengthening.
4760 // In something like 3x + 5y <= 6, the coefficient 5 can be changed to 6.
4761 // And in 5x + 12y <= 12, the coeff 5 can be changed to 6 (not sure how to
4762 // generalize this one).
4763 if (domain.size() == 2 && min_coeff_magnitude > 1 &&
4764 min_coeff_magnitude < max_coeff_magnitude) {
4765 const int64_t rhs_min = domain[0];
4766 const int64_t rhs_max = domain[1];
4767 if (min_sum >= rhs_min &&
4768 max_coeff_magnitude + min_coeff_magnitude > rhs_max - min_sum) {
4769 LowerThanCoeffStrengthening(/*from_lower_bound=*/true,
4770 min_coeff_magnitude, rhs_max - min_sum, ct);
4771 return;
4772 }
4773 if (max_sum <= rhs_max &&
4774 max_coeff_magnitude + min_coeff_magnitude > max_sum - rhs_min) {
4775 LowerThanCoeffStrengthening(/*from_lower_bound=*/false,
4776 min_coeff_magnitude, max_sum - rhs_min, ct);
4777 return;
4778 }
4779 }
4780 }
4781
4782 // We need the constraint to be only bounded on one side in order to extract
4783 // enforcement literal.
4784 //
4785 // If it is boxed and we know that some coefficient are big enough (see test
4786 // above), then we split the constraint in two. That might not seems always
4787 // good, but for the CP propagation engine, we don't loose anything by doing
4788 // so, and for the LP we will regroup the constraints if they still have the
4789 // exact same coeff after the presolve.
4790 //
4791 // TODO(user): Creating two new constraints and removing the current one might
4792 // not be the most efficient, but it simplify the presolve code by not having
4793 // to do anything special to trigger a new presolving of these constraints.
4794 // Try to improve if this becomes a problem.
4795 const Domain rhs_domain = ReadDomainFromProto(ct->linear());
4796 const bool lower_bounded = min_sum < rhs_domain.Min();
4797 const bool upper_bounded = max_sum > rhs_domain.Max();
4798 if (!lower_bounded && !upper_bounded) return;
4799 if (lower_bounded && upper_bounded) {
4800 // We disable this for now.
4801 if (true) return;
4802
4803 // Lets not split except if we extract enforcement.
4804 if (max_coeff_magnitude < std::max(ub_threshold, lb_threshold)) return;
4805
4806 context_->UpdateRuleStats("linear: split boxed constraint");
4807 ConstraintProto* new_ct1 = context_->working_model->add_constraints();
4808 *new_ct1 = *ct;
4809 if (!ct->name().empty()) {
4810 new_ct1->set_name(absl::StrCat(ct->name(), " (part 1)"));
4811 }
4812 FillDomainInProto(Domain(min_sum, rhs_domain.Max()),
4813 new_ct1->mutable_linear());
4814
4815 ConstraintProto* new_ct2 = context_->working_model->add_constraints();
4816 *new_ct2 = *ct;
4817 if (!ct->name().empty()) {
4818 new_ct2->set_name(absl::StrCat(ct->name(), " (part 2)"));
4819 }
4820 FillDomainInProto(rhs_domain.UnionWith(Domain(rhs_domain.Max(), max_sum)),
4821 new_ct2->mutable_linear());
4822
4823 context_->UpdateNewConstraintsVariableUsage();
4824 ct->Clear();
4825 context_->UpdateConstraintVariableUsage(ct_index);
4826 return;
4827 }
4828
4829 // Any coefficient greater than this will cause the constraint to be trivially
4830 // satisfied when the variable move away from its bound. Note that as we
4831 // remove coefficient, the threshold do not change!
4832 const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
4833
4834 // All coeffs in [second_threshold, threshold) can be reduced to
4835 // second_threshold.
4836 //
4837 // TODO(user): If 2 * min_coeff_magnitude >= bound, then the constraint can
4838 // be completely rewriten to 2 * (enforcement_part) + sum var >= 2 which is
4839 // what happen eventually when bound is even, but not if it is odd currently.
4840 int64_t second_threshold =
4841 std::max(MathUtil::CeilOfRatio(threshold, int64_t{2}),
4842 threshold - min_coeff_magnitude);
4843
4844 // Tricky: The second threshold only work if the domain is simple. If the
4845 // domain has holes, changing the coefficient might change whether the
4846 // variable can be at one or not by herself.
4847 //
4848 // TODO(user): We could still reduce it to the smaller value with same
4849 // feasibility.
4850 if (rhs_domain.NumIntervals() > 1) {
4851 second_threshold = threshold; // Disable.
4852 }
4853
4854 // Do we only extract Booleans?
4855 //
4856 // Note that for now the default is false, and also there are problem calling
4857 // GetOrCreateVarValueEncoding() after expansion because we might have removed
4858 // the variable used in the encoding.
4859 const bool only_extract_booleans =
4860 !context_->params().presolve_extract_integer_enforcement() ||
4861 context_->ModelIsExpanded();
4862
4863 // To avoid a quadratic loop, we will rewrite the linear expression at the
4864 // same time as we extract enforcement literals.
4865 int new_size = 0;
4866 int64_t rhs_offset = 0;
4867 bool some_integer_encoding_were_extracted = false;
4868 LinearConstraintProto* mutable_arg = ct->mutable_linear();
4869 for (int i = 0; i < arg.vars_size(); ++i) {
4870 int ref = arg.vars(i);
4871 int64_t coeff = arg.coeffs(i);
4872 if (coeff < 0) {
4873 ref = NegatedRef(ref);
4874 coeff = -coeff;
4875 }
4876
4877 // TODO(user): If the encoding Boolean already exist, we could extract
4878 // the non-Boolean enforcement term.
4879 const bool is_boolean = context_->CanBeUsedAsLiteral(ref);
4880 if (context_->IsFixed(ref) || coeff < threshold ||
4881 (only_extract_booleans && !is_boolean)) {
4882 mutable_arg->set_vars(new_size, mutable_arg->vars(i));
4883
4884 int64_t new_magnitude = std::abs(arg.coeffs(i));
4885 if (coeff > threshold) {
4886 // We keep this term but reduces its coeff.
4887 // This is only for the case where only_extract_booleans == true.
4888 new_magnitude = threshold;
4889 context_->UpdateRuleStats("linear: coefficient strenghtening");
4890 } else if (coeff > second_threshold && coeff < threshold) {
4891 // This cover the special case where one big + on small is enough
4892 // to satisfy the constraint, we can reduce the big.
4893 new_magnitude = second_threshold;
4894 context_->UpdateRuleStats("linear: advanced coefficient strenghtening");
4895 }
4896 if (coeff != new_magnitude) {
4897 if (lower_bounded) {
4898 // coeff * (X - LB + LB) -> new_magnitude * (X - LB) + coeff * LB
4899 rhs_offset -= (coeff - new_magnitude) * context_->MinOf(ref);
4900 } else {
4901 // coeff * (X - UB + UB) -> new_magnitude * (X - UB) + coeff * UB
4902 rhs_offset -= (coeff - new_magnitude) * context_->MaxOf(ref);
4903 }
4904 }
4905
4906 mutable_arg->set_coeffs(
4907 new_size, arg.coeffs(i) > 0 ? new_magnitude : -new_magnitude);
4908 ++new_size;
4909 continue;
4910 }
4911
4912 if (is_boolean) {
4913 context_->UpdateRuleStats("linear: extracted enforcement literal");
4914 } else {
4915 some_integer_encoding_were_extracted = true;
4916 context_->UpdateRuleStats(
4917 "linear: extracted integer enforcement literal");
4918 }
4919 if (lower_bounded) {
4920 ct->add_enforcement_literal(is_boolean
4921 ? NegatedRef(ref)
4922 : context_->GetOrCreateVarValueEncoding(
4923 ref, context_->MinOf(ref)));
4924 rhs_offset -= coeff * context_->MinOf(ref);
4925 } else {
4926 ct->add_enforcement_literal(is_boolean
4927 ? ref
4928 : context_->GetOrCreateVarValueEncoding(
4929 ref, context_->MaxOf(ref)));
4930 rhs_offset -= coeff * context_->MaxOf(ref);
4931 }
4932 }
4933 mutable_arg->mutable_vars()->Truncate(new_size);
4934 mutable_arg->mutable_coeffs()->Truncate(new_size);
4935 FillDomainInProto(rhs_domain.AdditionWith(Domain(rhs_offset)), mutable_arg);
4936 if (some_integer_encoding_were_extracted || new_size == 1) {
4937 context_->UpdateConstraintVariableUsage(ct_index);
4938 context_->UpdateNewConstraintsVariableUsage();
4939 }
4940}
4941
4942void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto* ct) {
4943 if (context_->ModelIsUnsat()) return;
4944 if (HasEnforcementLiteral(*ct)) return;
4945 const Domain rhs = ReadDomainFromProto(ct->linear());
4946
4947 const LinearConstraintProto& arg = ct->linear();
4948 const int num_vars = arg.vars_size();
4949 int64_t min_sum = 0;
4950 int64_t max_sum = 0;
4951 for (int i = 0; i < num_vars; ++i) {
4952 const int ref = arg.vars(i);
4953 const int64_t coeff = arg.coeffs(i);
4954 const int64_t term_a = coeff * context_->MinOf(ref);
4955 const int64_t term_b = coeff * context_->MaxOf(ref);
4956 min_sum += std::min(term_a, term_b);
4957 max_sum += std::max(term_a, term_b);
4958 }
4959 for (const int type : {0, 1}) {
4960 std::vector<int> at_most_one;
4961 for (int i = 0; i < num_vars; ++i) {
4962 const int ref = arg.vars(i);
4963 const int64_t coeff = arg.coeffs(i);
4964 if (context_->MinOf(ref) != 0) continue;
4965 if (context_->MaxOf(ref) != 1) continue;
4966
4967 if (type == 0) {
4968 // TODO(user): we could add one more Boolean with a lower coeff as long
4969 // as we have lower_coeff + min_of_other_coeff > rhs.Max().
4970 if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
4971 at_most_one.push_back(coeff > 0 ? ref : NegatedRef(ref));
4972 }
4973 } else {
4974 if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
4975 at_most_one.push_back(coeff > 0 ? NegatedRef(ref) : ref);
4976 }
4977 }
4978 }
4979 if (at_most_one.size() > 1) {
4980 if (type == 0) {
4981 context_->UpdateRuleStats("linear: extracted at most one (max)");
4982 } else {
4983 context_->UpdateRuleStats("linear: extracted at most one (min)");
4984 }
4985 ConstraintProto* new_ct = context_->working_model->add_constraints();
4986 new_ct->set_name(ct->name());
4987 for (const int ref : at_most_one) {
4988 new_ct->mutable_at_most_one()->add_literals(ref);
4989 }
4990 context_->UpdateNewConstraintsVariableUsage();
4991 }
4992 }
4993}
4994
4995// Convert some linear constraint involving only Booleans to their Boolean
4996// form.
4997bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto* ct) {
4998 if (ct->linear().vars().empty()) return false;
4999 if (context_->ModelIsUnsat()) return false;
5000
5001 // For special kind of constraint detection.
5002 int64_t sum_of_coeffs = 0;
5003 int num_positive = 0;
5004 int num_negative = 0;
5005
5006 const LinearConstraintProto& arg = ct->linear();
5007 const int num_vars = arg.vars_size();
5008 int64_t min_coeff = std::numeric_limits<int64_t>::max();
5009 int64_t max_coeff = 0;
5010 int64_t min_sum = 0;
5011 int64_t max_sum = 0;
5012 for (int i = 0; i < num_vars; ++i) {
5013 // We assume we already ran PresolveLinear().
5014 const int var = arg.vars(i);
5015 const int64_t coeff = arg.coeffs(i);
5016 CHECK(RefIsPositive(var));
5017 CHECK_NE(coeff, 0);
5018 if (context_->MinOf(var) != 0) return false;
5019 if (context_->MaxOf(var) != 1) return false;
5020
5021 sum_of_coeffs += coeff;
5022 if (coeff > 0) {
5023 ++num_positive;
5024 max_sum += coeff;
5025 min_coeff = std::min(min_coeff, coeff);
5026 max_coeff = std::max(max_coeff, coeff);
5027 } else {
5028 // We replace the Boolean ref, by a ref to its negation (1 - x).
5029 ++num_negative;
5030 min_sum += coeff;
5031 min_coeff = std::min(min_coeff, -coeff);
5032 max_coeff = std::max(max_coeff, -coeff);
5033 }
5034 }
5035 CHECK_LE(min_coeff, max_coeff);
5036
5037 // Detect trivially true/false constraints. Note that this is not necessarily
5038 // detected by PresolveLinear(). We do that here because we assume below
5039 // that this cannot happen.
5040 //
5041 // TODO(user): this could be generalized to constraint not containing only
5042 // Booleans.
5043 const Domain rhs_domain = ReadDomainFromProto(arg);
5044 if ((!rhs_domain.Contains(min_sum) &&
5045 min_sum + min_coeff > rhs_domain.Max()) ||
5046 (!rhs_domain.Contains(max_sum) &&
5047 max_sum - min_coeff < rhs_domain.Min())) {
5048 return MarkConstraintAsFalse(ct,
5049 "linear: all booleans and trivially false");
5050 }
5051 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
5052 context_->UpdateRuleStats("linear: all booleans and trivially true");
5053 return RemoveConstraint(ct);
5054 }
5055
5056 // This discover cases like "A + B + C - 3*D = 0"
5057 // where all Booleans must be equivalent!
5058 // This happens a lot on woodlands09.mps for instance.
5059 //
5060 // TODO(user): generalize if enforced?
5061 // TODO(user): generalize to other variant! Use DP to identify constraint with
5062 // just one or two solutions? or a few solution with same variable values?
5063 if (ct->enforcement_literal().empty() && sum_of_coeffs == 0 &&
5064 (num_negative == 1 || num_positive == 1) && rhs_domain.IsFixed() &&
5065 rhs_domain.FixedValue() == 0) {
5066 // This forces either all variable at 1 or all at zero.
5067 context_->UpdateRuleStats("linear: all equivalent!");
5068 for (int i = 1; i < num_vars; ++i) {
5069 if (!context_->StoreBooleanEqualityRelation(ct->linear().vars(0),
5070 ct->linear().vars(i))) {
5071 return false;
5072 }
5073 }
5074 return RemoveConstraint(ct);
5075 }
5076
5077 // Detect clauses, reified ands, at_most_one.
5078 //
5079 // TODO(user): split a == 1 constraint or similar into a clause and an at
5080 // most one constraint?
5081 DCHECK(!rhs_domain.IsEmpty());
5082 if (min_sum + min_coeff > rhs_domain.Max()) {
5083 // All Boolean are false if the reified literal is true.
5084 context_->UpdateRuleStats("linear: negative reified and");
5085 const auto copy = arg;
5086 ct->mutable_bool_and()->clear_literals();
5087 for (int i = 0; i < num_vars; ++i) {
5088 ct->mutable_bool_and()->add_literals(
5089 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
5090 }
5091 PresolveBoolAnd(ct);
5092 return true;
5093 } else if (max_sum - min_coeff < rhs_domain.Min()) {
5094 // All Boolean are true if the reified literal is true.
5095 context_->UpdateRuleStats("linear: positive reified and");
5096 const auto copy = arg;
5097 ct->mutable_bool_and()->clear_literals();
5098 for (int i = 0; i < num_vars; ++i) {
5099 ct->mutable_bool_and()->add_literals(
5100 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
5101 }
5102 PresolveBoolAnd(ct);
5103 return true;
5104 } else if (min_sum + min_coeff >= rhs_domain.Min() &&
5105 rhs_domain.front().end >= max_sum) {
5106 // At least one Boolean is true.
5107 context_->UpdateRuleStats("linear: positive clause");
5108 const auto copy = arg;
5109 ct->mutable_bool_or()->clear_literals();
5110 for (int i = 0; i < num_vars; ++i) {
5111 ct->mutable_bool_or()->add_literals(
5112 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
5113 }
5114 PresolveBoolOr(ct);
5115 return true;
5116 } else if (max_sum - min_coeff <= rhs_domain.Max() &&
5117 rhs_domain.back().start <= min_sum) {
5118 // At least one Boolean is false.
5119 context_->UpdateRuleStats("linear: negative clause");
5120 const auto copy = arg;
5121 ct->mutable_bool_or()->clear_literals();
5122 for (int i = 0; i < num_vars; ++i) {
5123 ct->mutable_bool_or()->add_literals(
5124 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
5125 }
5126 PresolveBoolOr(ct);
5127 return true;
5128 } else if (!HasEnforcementLiteral(*ct) &&
5129 min_sum + max_coeff <= rhs_domain.Max() &&
5130 min_sum + 2 * min_coeff > rhs_domain.Max() &&
5131 rhs_domain.back().start <= min_sum) {
5132 // At most one Boolean is true.
5133 // TODO(user): Support enforced at most one.
5134 context_->UpdateRuleStats("linear: positive at most one");
5135 const auto copy = arg;
5136 ct->mutable_at_most_one()->clear_literals();
5137 for (int i = 0; i < num_vars; ++i) {
5138 ct->mutable_at_most_one()->add_literals(
5139 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
5140 }
5141 return true;
5142 } else if (!HasEnforcementLiteral(*ct) &&
5143 max_sum - max_coeff >= rhs_domain.Min() &&
5144 max_sum - 2 * min_coeff < rhs_domain.Min() &&
5145 rhs_domain.front().end >= max_sum) {
5146 // At most one Boolean is false.
5147 // TODO(user): Support enforced at most one.
5148 context_->UpdateRuleStats("linear: negative at most one");
5149 const auto copy = arg;
5150 ct->mutable_at_most_one()->clear_literals();
5151 for (int i = 0; i < num_vars; ++i) {
5152 ct->mutable_at_most_one()->add_literals(
5153 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
5154 }
5155 return true;
5156 } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
5157 min_sum < rhs_domain.Min() &&
5158 min_sum + min_coeff >= rhs_domain.Min() &&
5159 min_sum + 2 * min_coeff > rhs_domain.Max() &&
5160 min_sum + max_coeff <= rhs_domain.Max()) {
5161 // TODO(user): Support enforced exactly one.
5162 context_->UpdateRuleStats("linear: positive equal one");
5163 ConstraintProto* exactly_one = context_->working_model->add_constraints();
5164 exactly_one->set_name(ct->name());
5165 for (int i = 0; i < num_vars; ++i) {
5166 exactly_one->mutable_exactly_one()->add_literals(
5167 arg.coeffs(i) > 0 ? arg.vars(i) : NegatedRef(arg.vars(i)));
5168 }
5169 context_->UpdateNewConstraintsVariableUsage();
5170 return RemoveConstraint(ct);
5171 } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
5172 max_sum > rhs_domain.Max() &&
5173 max_sum - min_coeff <= rhs_domain.Max() &&
5174 max_sum - 2 * min_coeff < rhs_domain.Min() &&
5175 max_sum - max_coeff >= rhs_domain.Min()) {
5176 // TODO(user): Support enforced exactly one.
5177 context_->UpdateRuleStats("linear: negative equal one");
5178 ConstraintProto* exactly_one = context_->working_model->add_constraints();
5179 exactly_one->set_name(ct->name());
5180 for (int i = 0; i < num_vars; ++i) {
5181 exactly_one->mutable_exactly_one()->add_literals(
5182 arg.coeffs(i) > 0 ? NegatedRef(arg.vars(i)) : arg.vars(i));
5183 }
5184 context_->UpdateNewConstraintsVariableUsage();
5185 return RemoveConstraint(ct);
5186 }
5187
5188 // Expand small expression into clause.
5189 //
5190 // TODO(user): This is bad from a LP relaxation perspective. Do not do that
5191 // now? On another hand it is good for the SAT presolving.
5192 if (num_vars > 3) return false;
5193 context_->UpdateRuleStats("linear: small Boolean expression");
5194
5195 // Enumerate all possible value of the Booleans and add a clause if constraint
5196 // is false. TODO(user): the encoding could be made better in some cases.
5197 const int max_mask = (1 << arg.vars_size());
5198 for (int mask = 0; mask < max_mask; ++mask) {
5199 int64_t value = 0;
5200 for (int i = 0; i < num_vars; ++i) {
5201 if ((mask >> i) & 1) value += arg.coeffs(i);
5202 }
5203 if (rhs_domain.Contains(value)) continue;
5204
5205 // Add a new clause to exclude this bad assignment.
5206 ConstraintProto* new_ct = context_->working_model->add_constraints();
5207 auto* new_arg = new_ct->mutable_bool_or();
5208 if (HasEnforcementLiteral(*ct)) {
5209 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
5210 }
5211 for (int i = 0; i < num_vars; ++i) {
5212 new_arg->add_literals(((mask >> i) & 1) ? NegatedRef(arg.vars(i))
5213 : arg.vars(i));
5214 }
5215 }
5216
5217 context_->UpdateNewConstraintsVariableUsage();
5218 return RemoveConstraint(ct);
5219}
5220
5221bool CpModelPresolver::PresolveInterval(int c, ConstraintProto* ct) {
5222 if (context_->ModelIsUnsat()) return false;
5223 IntervalConstraintProto* interval = ct->mutable_interval();
5224
5225 // If the size is < 0, then the interval cannot be performed.
5226 if (!ct->enforcement_literal().empty() && context_->SizeMax(c) < 0) {
5227 context_->UpdateRuleStats("interval: negative size implies unperformed");
5228 return MarkOptionalIntervalAsFalse(ct);
5229 }
5230
5231 if (ct->enforcement_literal().empty()) {
5232 bool domain_changed = false;
5233 // Size can't be negative.
5234 if (!context_->IntersectDomainWith(
5235 interval->size(), Domain(0, std::numeric_limits<int64_t>::max()),
5236 &domain_changed)) {
5237 return false;
5238 }
5239 if (domain_changed) {
5240 context_->UpdateRuleStats(
5241 "interval: performed intervals must have a positive size");
5242 }
5243 }
5244
5245 // Note that the linear relation is stored elsewhere, so it is safe to just
5246 // remove such special interval constraint.
5247 if (context_->ConstraintVariableGraphIsUpToDate() &&
5248 context_->IntervalUsage(c) == 0) {
5249 context_->UpdateRuleStats("intervals: removed unused interval");
5250 return RemoveConstraint(ct);
5251 }
5252
5253 bool changed = false;
5254 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_start());
5255 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_size());
5256 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_end());
5257 return changed;
5258}
5259
5260// TODO(user): avoid code duplication between expand and presolve.
5261bool CpModelPresolver::PresolveInverse(ConstraintProto* ct) {
5262 // TODO(user): add support for this case.
5263 if (HasEnforcementLiteral(*ct)) return false;
5264 const int size = ct->inverse().f_direct().size();
5265 bool changed = false;
5266
5267 // Make sure the domains are included in [0, size - 1).
5268 for (const int ref : ct->inverse().f_direct()) {
5269 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
5270 VLOG(1) << "Empty domain for a variable in PresolveInverse()";
5271 return false;
5272 }
5273 }
5274 for (const int ref : ct->inverse().f_inverse()) {
5275 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
5276 VLOG(1) << "Empty domain for a variable in PresolveInverse()";
5277 return false;
5278 }
5279 }
5280
5281 // Detect duplicated variable.
5282 // Even with negated variables, the reduced domain in [0..size - 1]
5283 // implies that the constraint is infeasible if ref and its negation
5284 // appear together.
5285 {
5286 absl::flat_hash_set<int> direct_vars;
5287 for (const int ref : ct->inverse().f_direct()) {
5288 const auto [it, inserted] = direct_vars.insert(PositiveRef(ref));
5289 if (!inserted) {
5290 return context_->NotifyThatModelIsUnsat("inverse: duplicated variable");
5291 }
5292 }
5293
5294 absl::flat_hash_set<int> inverse_vars;
5295 for (const int ref : ct->inverse().f_inverse()) {
5296 const auto [it, inserted] = inverse_vars.insert(PositiveRef(ref));
5297 if (!inserted) {
5298 return context_->NotifyThatModelIsUnsat("inverse: duplicated variable");
5299 }
5300 }
5301 }
5302
5303 // Propagate from one vector to its counterpart.
5304 // Note this reaches the fixpoint as there is a one to one mapping between
5305 // (variable-value) pairs in each vector.
5306 const auto filter_inverse_domain =
5307 [this, size, &changed](const auto& direct, const auto& inverse) {
5308 // Build the set of values in the inverse vector.
5309 std::vector<absl::flat_hash_set<int64_t>> inverse_values(size);
5310 for (int i = 0; i < size; ++i) {
5311 const Domain domain = context_->DomainOf(inverse[i]);
5312 for (const int64_t j : domain.Values()) {
5313 inverse_values[i].insert(j);
5314 }
5315 }
5316
5317 // Propagate from the inverse vector to the direct vector. Reduce the
5318 // domains of each variable in the direct vector by checking that the
5319 // inverse value exists.
5320 std::vector<int64_t> possible_values;
5321 for (int i = 0; i < size; ++i) {
5322 possible_values.clear();
5323 const Domain domain = context_->DomainOf(direct[i]);
5324 bool removed_value = false;
5325 for (const int64_t j : domain.Values()) {
5326 if (inverse_values[j].contains(i)) {
5327 possible_values.push_back(j);
5328 } else {
5329 removed_value = true;
5330 }
5331 }
5332 if (removed_value) {
5333 changed = true;
5334 if (!context_->IntersectDomainWith(
5335 direct[i], Domain::FromValues(possible_values))) {
5336 VLOG(1) << "Empty domain for a variable in PresolveInverse()";
5337 return false;
5338 }
5339 }
5340 }
5341 return true;
5342 };
5343
5344 if (!filter_inverse_domain(ct->inverse().f_direct(),
5345 ct->inverse().f_inverse())) {
5346 return false;
5347 }
5348
5349 if (!filter_inverse_domain(ct->inverse().f_inverse(),
5350 ct->inverse().f_direct())) {
5351 return false;
5352 }
5353
5354 if (changed) {
5355 context_->UpdateRuleStats("inverse: reduce domains");
5356 }
5357
5358 return false;
5359}
5360
5361bool CpModelPresolver::PresolveElement(int c, ConstraintProto* ct) {
5362 if (context_->ModelIsUnsat()) return false;
5363
5364 if (ct->element().exprs().empty()) {
5365 return MarkConstraintAsFalse(ct, "element: empty array");
5366 }
5367
5368 bool changed = false;
5369 changed |= CanonicalizeLinearExpression(
5370 *ct, ct->mutable_element()->mutable_linear_index());
5371 changed |= CanonicalizeLinearExpression(
5372 *ct, ct->mutable_element()->mutable_linear_target());
5373 for (int i = 0; i < ct->element().exprs_size(); ++i) {
5374 changed |= CanonicalizeLinearExpression(
5375 *ct, ct->mutable_element()->mutable_exprs(i));
5376 }
5377
5378 const LinearExpressionProto& index = ct->element().linear_index();
5379 const LinearExpressionProto& target = ct->element().linear_target();
5380
5381 // TODO(user): add support for this case.
5382 if (HasEnforcementLiteral(*ct)) return changed;
5383
5384 // Reduce index domain from the array size.
5385 {
5386 bool index_modified = false;
5387 if (!context_->IntersectDomainWith(
5388 index, Domain(0, ct->element().exprs_size() - 1),
5389 &index_modified)) {
5390 return false;
5391 }
5392 if (index_modified) {
5393 context_->UpdateRuleStats(
5394 "element: reduced index domain from array size");
5395 }
5396 }
5397
5398 // Special case if the index is fixed.
5399 if (context_->IsFixed(index)) {
5400 const int64_t index_value = context_->FixedValue(index);
5401 ConstraintProto* new_ct = context_->working_model->add_constraints();
5402 new_ct->mutable_linear()->add_domain(0);
5403 new_ct->mutable_linear()->add_domain(0);
5404 AddLinearExpressionToLinearConstraint(target, 1, new_ct->mutable_linear());
5405 AddLinearExpressionToLinearConstraint(ct->element().exprs(index_value), -1,
5406 new_ct->mutable_linear());
5407 bool is_impossible = false;
5408 context_->CanonicalizeLinearConstraint(new_ct, &is_impossible);
5409 if (is_impossible) {
5410 return context_->NotifyThatModelIsUnsat(
5411 "element: impossible fixed index");
5412 }
5413 context_->UpdateNewConstraintsVariableUsage();
5414 context_->UpdateRuleStats("element: fixed index");
5415 return RemoveConstraint(ct);
5416 }
5417
5418 // We know index is not fixed.
5419 const int index_var = index.vars(0);
5420
5421 {
5422 // Cleanup the array: if exprs[i] contains index_var, fix its value.
5423 const Domain& index_var_domain = context_->DomainOf(index_var);
5424 std::vector<int64_t> reached_indices(ct->element().exprs_size(), false);
5425 for (const int64_t index_var_value : index_var_domain.Values()) {
5426 const int64_t index_value =
5427 AffineExpressionValueAt(index, index_var_value);
5428 reached_indices[index_value] = true;
5429 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5430 if (expr.vars_size() == 1 && expr.vars(0) == index_var) {
5431 const int64_t expr_value =
5432 AffineExpressionValueAt(expr, index_var_value);
5433 ct->mutable_element()->mutable_exprs(index_value)->clear_vars();
5434 ct->mutable_element()->mutable_exprs(index_value)->clear_coeffs();
5435 ct->mutable_element()
5436 ->mutable_exprs(index_value)
5437 ->set_offset(expr_value);
5438 changed = true;
5439 context_->UpdateRuleStats(
5440 "element: fix expression depending on the index");
5441 }
5442 }
5443
5444 // Cleanup the array: clear unreached expressions.
5445 for (int i = 0; i < ct->element().exprs_size(); ++i) {
5446 if (!reached_indices[i]) {
5447 ct->mutable_element()->mutable_exprs(i)->Clear();
5448 changed = true;
5449 }
5450 }
5451 }
5452
5453 // Canonicalization and cleanups of the expressions could have messed up the
5454 // var-constraint graph.
5455 if (changed) context_->UpdateConstraintVariableUsage(c);
5456
5457 // Reduces the domain of the index.
5458 {
5459 const Domain& index_var_domain = context_->DomainOf(index_var);
5460 const Domain& target_domain = context_->DomainSuperSetOf(target);
5461 std::vector<int64_t> possible_index_var_values;
5462 for (const int64_t index_var_value : index_var_domain.Values()) {
5463 const int64_t index_value =
5464 AffineExpressionValueAt(index, index_var_value);
5465 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5466
5467 bool is_possible_index;
5468 if (target.vars_size() == 1 && target.vars(0) == index_var) {
5469 // The target domain can be reduced if it shares its variable with the
5470 // index.
5471 is_possible_index = context_->DomainContains(
5472 expr, AffineExpressionValueAt(target, index_var_value));
5473 } else {
5474 is_possible_index =
5475 context_->IntersectionOfAffineExprsIsNotEmpty(target, expr);
5476 }
5477
5478 if (is_possible_index) {
5479 possible_index_var_values.push_back(index_var_value);
5480 } else {
5481 ct->mutable_element()->mutable_exprs(index_value)->Clear();
5482 changed = true;
5483 }
5484 }
5485 if (possible_index_var_values.size() < index_var_domain.Size()) {
5486 if (!context_->IntersectDomainWith(
5487 index_var, Domain::FromValues(possible_index_var_values))) {
5488 return true;
5489 }
5490 context_->UpdateRuleStats("element: reduced index domain ");
5491 // If the index is fixed, this is a equality constraint.
5492 if (context_->IsFixed(index)) {
5493 ConstraintProto* const eq = context_->working_model->add_constraints();
5494 eq->mutable_linear()->add_domain(0);
5495 eq->mutable_linear()->add_domain(0);
5496 AddLinearExpressionToLinearConstraint(target, 1, eq->mutable_linear());
5498 ct->element().exprs(context_->FixedValue(index)), -1,
5499 eq->mutable_linear());
5500 context_->CanonicalizeLinearConstraint(eq);
5501 context_->UpdateNewConstraintsVariableUsage();
5502 context_->UpdateRuleStats("element: fixed index");
5503 return RemoveConstraint(ct);
5504 }
5505 }
5506 }
5507
5508 bool all_included_in_target_domain = true;
5509 {
5510 // Accumulate expressions domains to build a superset of the target domain.
5511 Domain infered_domain;
5512 const Domain& index_var_domain = context_->DomainOf(index_var);
5513 const Domain& target_domain = context_->DomainSuperSetOf(target);
5514 for (const int64_t index_var_value : index_var_domain.Values()) {
5515 const int64_t index_value =
5516 AffineExpressionValueAt(index, index_var_value);
5517 CHECK_GE(index_value, 0);
5518 CHECK_LT(index_value, ct->element().exprs_size());
5519 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5520 const Domain expr_domain = context_->DomainSuperSetOf(expr);
5521 if (!expr_domain.IsIncludedIn(target_domain)) {
5522 all_included_in_target_domain = false;
5523 }
5524 infered_domain = infered_domain.UnionWith(expr_domain);
5525 }
5526
5527 bool domain_modified = false;
5528 if (!context_->IntersectDomainWith(target, infered_domain,
5529 &domain_modified)) {
5530 return true;
5531 }
5532 if (domain_modified) {
5533 context_->UpdateRuleStats("element: reduce target domain");
5534 }
5535 }
5536
5537 bool all_constants = true;
5538 {
5539 const Domain& index_var_domain = context_->DomainOf(index_var);
5540 std::vector<int64_t> expr_constants;
5541
5542 for (const int64_t index_var_value : index_var_domain.Values()) {
5543 const int64_t index_value =
5544 AffineExpressionValueAt(index, index_var_value);
5545 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5546 if (context_->IsFixed(expr)) {
5547 expr_constants.push_back(context_->FixedValue(expr));
5548 } else {
5549 all_constants = false;
5550 break;
5551 }
5552 }
5553 }
5554
5555 // Detect is the element can be rewritten as a * target + b * index == c.
5556 if (all_constants) {
5557 if (context_->IsFixed(target)) {
5558 // If the accessible part of the array is made of a single constant
5559 // value, then we do not care about the index. And, because of the
5560 // previous target domain reduction, the target is also fixed.
5561 context_->UpdateRuleStats("element: one value array");
5562 return RemoveConstraint(ct);
5563 }
5564 int64_t first_index_var_value;
5565 int64_t first_target_var_value;
5566 int64_t d_index = 0;
5567 int64_t d_target = 0;
5568 int num_terms = 0;
5569 bool is_affine = true;
5570 const Domain& index_var_domain = context_->DomainOf(index_var);
5571 for (const int64_t index_var_value : index_var_domain.Values()) {
5572 ++num_terms;
5573 const int64_t index_value =
5574 AffineExpressionValueAt(index, index_var_value);
5575 const int64_t expr_value =
5576 context_->FixedValue(ct->element().exprs(index_value));
5577 const int64_t target_var_value = GetInnerVarValue(target, expr_value);
5578 if (num_terms == 1) {
5579 first_index_var_value = index_var_value;
5580 first_target_var_value = target_var_value;
5581 } else if (num_terms == 2) {
5582 d_index = index_var_value - first_index_var_value;
5583 d_target = target_var_value - first_target_var_value;
5584 const int64_t gcd = std::gcd(d_index, d_target);
5585 d_index /= gcd;
5586 d_target /= gcd;
5587 } else {
5588 const int64_t offset = CapSub(
5589 CapProd(d_index, CapSub(target_var_value, first_target_var_value)),
5590 CapProd(d_target, CapSub(index_var_value, first_index_var_value)));
5591 if (offset != 0) {
5592 is_affine = false;
5593 break;
5594 }
5595 }
5596 }
5597 if (is_affine) {
5598 const int64_t offset = CapSub(CapProd(first_target_var_value, d_index),
5599 CapProd(first_index_var_value, d_target));
5600 if (!AtMinOrMaxInt64(offset)) {
5601 ConstraintProto* const lin = context_->working_model->add_constraints();
5602 lin->mutable_linear()->add_vars(target.vars(0));
5603 lin->mutable_linear()->add_coeffs(d_index);
5604 lin->mutable_linear()->add_vars(index_var);
5605 lin->mutable_linear()->add_coeffs(-d_target);
5606 lin->mutable_linear()->add_domain(offset);
5607 lin->mutable_linear()->add_domain(offset);
5608 context_->CanonicalizeLinearConstraint(lin);
5609 context_->UpdateNewConstraintsVariableUsage();
5610 context_->UpdateRuleStats("element: rewrite as affine constraint");
5611 return RemoveConstraint(ct);
5612 }
5613 }
5614 }
5615
5616 // If a variable (target or index) appears only in this constraint, it does
5617 // not necessarily mean that we can remove the constraint, as the variable
5618 // can be used multiple times in the element. So let's count the local
5619 // uses of each variable.
5620 //
5621 // TODO(user): now that we used fixed values for these case, this is no longer
5622 // needed I think.
5623 absl::flat_hash_map<int, int> local_var_occurrence_counter;
5624 {
5625 auto count = [&local_var_occurrence_counter](
5626 const LinearExpressionProto& expr) mutable {
5627 for (const int var : expr.vars()) {
5628 local_var_occurrence_counter[var]++;
5629 }
5630 };
5631 count(index);
5632 count(target);
5633 for (const int64_t index_var_value :
5634 context_->DomainOf(index_var).Values()) {
5635 count(
5636 ct->element().exprs(AffineExpressionValueAt(index, index_var_value)));
5637 }
5638 }
5639
5640 if (context_->VariableIsUniqueAndRemovable(index_var) &&
5641 local_var_occurrence_counter.at(index_var) == 1 && all_constants) {
5642 // This constraint is just here to reduce the domain of the target! We can
5643 // add it to the mapping_model to reconstruct the index value during
5644 // postsolve and get rid of it now.
5645 //
5646 // The non constant case is handled during expansion.
5647 context_->UpdateRuleStats(
5648 "element: removed as the index is not used elsewhere");
5649 context_->MarkVariableAsRemoved(index_var);
5650 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
5651 return RemoveConstraint(ct);
5652 }
5653
5654 // The case where all domains are not included in the target domain is handled
5655 // during expansion.
5656 if (!context_->IsFixed(target) &&
5657 context_->VariableIsUniqueAndRemovable(target.vars(0)) &&
5658 local_var_occurrence_counter.at(target.vars(0)) == 1 &&
5659 all_included_in_target_domain && std::abs(target.coeffs(0)) == 1) {
5660 context_->UpdateRuleStats(
5661 "element: removed as the target is not used elsewhere");
5662 context_->MarkVariableAsRemoved(target.vars(0));
5663 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
5664 return RemoveConstraint(ct);
5665 }
5666
5667 return changed;
5668}
5669
5670bool CpModelPresolver::PresolveTable(ConstraintProto* ct) {
5671 if (context_->ModelIsUnsat()) return false;
5672
5673 bool changed = false;
5674 for (int i = 0; i < ct->table().exprs_size(); ++i) {
5675 changed |= CanonicalizeLinearExpression(
5676 *ct, ct->mutable_table()->mutable_exprs(i));
5677 }
5678
5679 const int initial_num_exprs = ct->table().exprs_size();
5680 if (initial_num_exprs > 0) CanonicalizeTable(context_, ct);
5681 changed |= (ct->table().exprs_size() != initial_num_exprs);
5682
5683 if (ct->table().exprs().empty()) {
5684 context_->UpdateRuleStats("table: no expressions");
5685 return RemoveConstraint(ct);
5686 }
5687
5688 if (ct->table().values().empty()) {
5689 if (ct->table().negated()) {
5690 context_->UpdateRuleStats("table: negative table without tuples");
5691 return RemoveConstraint(ct);
5692 } else {
5693 return MarkConstraintAsFalse(ct, "table: positive table without tuples");
5694 }
5695 }
5696
5697 int num_fixed_exprs = 0;
5698 for (const LinearExpressionProto& expr : ct->table().exprs()) {
5699 if (context_->IsFixed(expr)) ++num_fixed_exprs;
5700 }
5701 if (num_fixed_exprs == ct->table().exprs_size()) {
5702 context_->UpdateRuleStats("table: all expressions are fixed");
5703 DCHECK_LE(ct->table().values_size(), num_fixed_exprs);
5704 if (ct->table().negated() == ct->table().values().empty()) {
5705 context_->UpdateRuleStats("table: always true");
5706 return RemoveConstraint(ct);
5707 } else {
5708 return MarkConstraintAsFalse(ct, "table: always false");
5709 }
5710 return RemoveConstraint(ct);
5711 }
5712
5713 if (num_fixed_exprs > 0) {
5714 CanonicalizeTable(context_, ct);
5715 }
5716
5717 // Nothing more to do for negated tables.
5718 if (ct->table().negated()) return changed;
5719
5720 // And for constraints with enforcement literals.
5721 if (HasEnforcementLiteral(*ct)) return changed;
5722
5723 // Filter the variables domains.
5724 const int num_exprs = ct->table().exprs_size();
5725 const int num_tuples = ct->table().values_size() / num_exprs;
5726 std::vector<std::vector<int64_t>> new_domains(num_exprs);
5727 for (int e = 0; e < num_exprs; ++e) {
5728 const LinearExpressionProto& expr = ct->table().exprs(e);
5729 if (context_->IsFixed(expr)) {
5730 new_domains[e].push_back(context_->FixedValue(expr));
5731 continue;
5732 }
5733
5734 for (int t = 0; t < num_tuples; ++t) {
5735 new_domains[e].push_back(ct->table().values(t * num_exprs + e));
5736 }
5737 gtl::STLSortAndRemoveDuplicates(&new_domains[e]);
5738 DCHECK_EQ(1, expr.vars_size());
5739 DCHECK_EQ(1, expr.coeffs(0));
5740 DCHECK_EQ(0, expr.offset());
5741 const int var = expr.vars(0);
5742 bool domain_modified = false;
5743 if (!context_->IntersectDomainWith(var, Domain::FromValues(new_domains[e]),
5744 &domain_modified)) {
5745 return true;
5746 }
5747 if (domain_modified) {
5748 context_->UpdateRuleStats("table: reduce variable domain");
5749 }
5750 }
5751
5752 if (num_exprs == 1) {
5753 // Now that we have properly updated the domain, we can remove the
5754 // constraint.
5755 context_->UpdateRuleStats("table: only one column!");
5756 return RemoveConstraint(ct);
5757 }
5758
5759 // Check that the table is not complete or just here to exclude a few tuples.
5760 double prod = 1.0;
5761 for (int e = 0; e < num_exprs; ++e) prod *= new_domains[e].size();
5762 if (prod == static_cast<double>(num_tuples)) {
5763 context_->UpdateRuleStats("table: all tuples!");
5764 return RemoveConstraint(ct);
5765 }
5766
5767 // Convert to the negated table if we gain a lot of entries by doing so.
5768 // Note however that currently the negated table do not propagate as much as
5769 // it could.
5770 if (static_cast<double>(num_tuples) > 0.7 * prod) {
5771 std::vector<std::vector<int64_t>> current_tuples(num_tuples);
5772 for (int t = 0; t < num_tuples; ++t) {
5773 current_tuples[t].resize(num_exprs);
5774 for (int e = 0; e < num_exprs; ++e) {
5775 current_tuples[t][e] = ct->table().values(t * num_exprs + e);
5776 }
5777 }
5778
5779 // Enumerate all possible tuples.
5780 std::vector<std::vector<int64_t>> var_to_values(num_exprs);
5781 for (int e = 0; e < num_exprs; ++e) {
5782 var_to_values[e].assign(new_domains[e].begin(), new_domains[e].end());
5783 }
5784 std::vector<std::vector<int64_t>> all_tuples(prod);
5785 for (int i = 0; i < prod; ++i) {
5786 all_tuples[i].resize(num_exprs);
5787 int index = i;
5788 for (int j = 0; j < num_exprs; ++j) {
5789 all_tuples[i][j] = var_to_values[j][index % var_to_values[j].size()];
5790 index /= var_to_values[j].size();
5791 }
5792 }
5794
5795 // Compute the complement of new_tuples.
5796 std::vector<std::vector<int64_t>> diff(prod - num_tuples);
5797 std::set_difference(all_tuples.begin(), all_tuples.end(),
5798 current_tuples.begin(), current_tuples.end(),
5799 diff.begin());
5800
5801 // Negate the constraint.
5802 ct->mutable_table()->set_negated(!ct->table().negated());
5803 ct->mutable_table()->clear_values();
5804 for (const std::vector<int64_t>& t : diff) {
5805 for (const int64_t v : t) ct->mutable_table()->add_values(v);
5806 }
5807 context_->UpdateRuleStats("table: negated");
5808 }
5809
5810 return changed;
5811}
5812
5813namespace {
5814
5815// A container that is valid if only one value was added.
5816class UniqueNonNegativeValue {
5817 public:
5818 void Add(int value) {
5819 DCHECK_GE(value, 0);
5820 if (value_ == -1) {
5821 value_ = value;
5822 } else {
5823 value_ = -2;
5824 }
5825 }
5826
5827 bool HasUniqueValue() const { return value_ >= 0; }
5828
5829 int64_t value() const {
5830 DCHECK(HasUniqueValue());
5831 return value_;
5832 }
5833
5834 private:
5835 int value_ = -1;
5836};
5837
5838std::string Plural(int n, std::string_view s) {
5839 return n <= 1 ? absl::StrCat(n, " ", s)
5840 : absl::StrCat(FormatCounter(n), " ", s, "s");
5841};
5842
5843} // namespace
5844
5845bool CpModelPresolver::PresolveAllDiff(ConstraintProto* ct) {
5846 if (context_->ModelIsUnsat()) return false;
5847 // TODO(user): add support for this case.
5848 if (HasEnforcementLiteral(*ct)) return false;
5849
5850 AllDifferentConstraintProto& all_diff = *ct->mutable_all_diff();
5851
5852 bool variables_have_changed = false;
5853 for (LinearExpressionProto& exp :
5854 *(ct->mutable_all_diff()->mutable_exprs())) {
5855 variables_have_changed |= CanonicalizeLinearExpression(*ct, &exp);
5856 }
5857
5858 const int size = all_diff.exprs_size();
5859 if (size == 0) {
5860 context_->UpdateRuleStats("all_diff: empty constraint");
5861 return RemoveConstraint(ct);
5862 }
5863 if (size == 1) {
5864 context_->UpdateRuleStats("all_diff: one expression");
5865 return RemoveConstraint(ct);
5866 }
5867
5868 absl::flat_hash_set<int64_t> fixed_values;
5869 int new_size = 0;
5870 for (int i = 0; i < size; ++i) {
5871 if (!context_->IsFixed(all_diff.exprs(i))) {
5872 if (i != new_size) {
5873 *all_diff.mutable_exprs(new_size) = all_diff.exprs(i);
5874 }
5875 ++new_size;
5876 } else {
5877 const int64_t value = context_->FixedValue(all_diff.exprs(i));
5878 if (!fixed_values.insert(value).second) {
5879 return context_->NotifyThatModelIsUnsat(
5880 "all_diff: duplicate fixed values");
5881 }
5882 }
5883 }
5884
5885 if (new_size < size) {
5886 all_diff.mutable_exprs()->DeleteSubrange(new_size, size - new_size);
5887 context_->UpdateRuleStats("all_diff: remove fixed expressions");
5888 }
5889
5890 if (!fixed_values.empty()) {
5891 const Domain to_keep =
5892 Domain::FromValues({fixed_values.begin(), fixed_values.end()})
5893 .Complement();
5894 bool propagated = false;
5895 for (int i = 0; i < all_diff.exprs_size(); ++i) {
5896 if (!context_->IntersectDomainWith(all_diff.exprs(i), to_keep,
5897 &propagated)) {
5898 return true;
5899 }
5900 }
5901 if (propagated) {
5902 context_->UpdateRuleStats("all_diff: propagate fixed expressions");
5903 }
5904 }
5905
5906 // Detect duplicate expressions, and remove impossible values from expressions
5907 // with the same variable.
5908 // We use btree_map to have a deterministic order.
5909 absl::btree_map<int, std::vector<std::pair<int64_t, int64_t>>> terms;
5910 std::vector<int64_t> forbidden_values;
5911 for (const LinearExpressionProto& expr : all_diff.exprs()) {
5912 if (expr.vars_size() != 1) continue;
5913 terms[expr.vars(0)].push_back(
5914 std::make_pair(expr.coeffs(0), expr.offset()));
5915 }
5916 for (auto& [var, terms] : terms) {
5917 if (terms.size() == 1) continue;
5918 std::sort(terms.begin(), terms.end());
5919
5920 // Check for duplicate expressions.
5921 for (int i = 1; i < terms.size(); ++i) {
5922 if (terms[i] == terms[i - 1]) {
5923 return context_->NotifyThatModelIsUnsat(
5924 "all_diff: duplicate expressions");
5925 }
5926 }
5927
5928 // Remove impossible values from expressions with the same variable.
5929 // a * var + b == c * var + d
5930 // -> (a - c) * var = d - b
5931 // Therefore var cannot take the value (d - b) / (a - c) if integral.
5932 forbidden_values.clear();
5933 for (int i = 0; i + 1 < terms.size(); ++i) {
5934 for (int j = i + 1; j < terms.size(); ++j) {
5935 const int64_t coeff = terms[i].first - terms[j].first;
5936 if (coeff == 0) continue;
5937 const int64_t offset = terms[j].second - terms[i].second;
5938 const int64_t value = offset / coeff;
5939 if (value * coeff == offset) {
5940 forbidden_values.push_back(value);
5941 }
5942 }
5943 }
5944 if (!forbidden_values.empty()) {
5945 const Domain to_keep = Domain::FromValues(forbidden_values).Complement();
5946 bool propagated = false;
5947 if (!context_->IntersectDomainWith(var, to_keep, &propagated)) {
5948 return true;
5949 }
5950 if (propagated) {
5951 context_->UpdateRuleStats(
5952 "all_diff: propagate expressions with the same variable");
5953 }
5954 }
5955 }
5956
5957 // Propagate mandatory values if the all diff is actually a permutation.
5958 if (all_diff.exprs_size() >= 2 && all_diff.exprs_size() <= 512) {
5959 Domain union_of_domains = context_->DomainSuperSetOf(all_diff.exprs(0));
5960 for (int i = 1; i < all_diff.exprs_size(); ++i) {
5961 union_of_domains = union_of_domains.UnionWith(
5962 context_->DomainSuperSetOf(all_diff.exprs(i)));
5963 }
5964
5965 if (union_of_domains.Size() < all_diff.exprs_size()) {
5966 return context_->NotifyThatModelIsUnsat(
5967 "all_diff: more expressions than values");
5968 }
5969
5970 if (all_diff.exprs_size() == union_of_domains.Size()) {
5971 absl::btree_map<int64_t, UniqueNonNegativeValue> value_to_index;
5972 for (int i = 0; i < all_diff.exprs_size(); ++i) {
5973 const LinearExpressionProto& expr = all_diff.exprs(i);
5974 DCHECK_EQ(expr.vars_size(), 1);
5975 for (const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
5976 value_to_index[AffineExpressionValueAt(expr, v)].Add(i);
5977 }
5978 }
5979
5980 bool propagated = false;
5981 for (const auto& [value, unique_index] : value_to_index) {
5982 if (!unique_index.HasUniqueValue()) continue;
5983
5984 const LinearExpressionProto& expr =
5985 all_diff.exprs(unique_index.value());
5986 if (!context_->IntersectDomainWith(expr, Domain(value), &propagated)) {
5987 return true;
5988 }
5989 }
5990
5991 if (propagated) {
5992 context_->UpdateRuleStats(
5993 "all_diff: propagated mandatory values in permutation");
5994 }
5995 }
5996 }
5997
5998 return variables_have_changed;
5999}
6000
6001namespace {
6002
6003// Add the constraint (lhs => rhs) to the given proto. The hash map lhs ->
6004// bool_and constraint index is used to merge implications with the same lhs.
6005void AddImplication(int lhs, int rhs, CpModelProto* proto,
6006 absl::flat_hash_map<int, int>* ref_to_bool_and) {
6007 if (ref_to_bool_and->contains(lhs)) {
6008 const int ct_index = (*ref_to_bool_and)[lhs];
6009 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
6010 } else if (ref_to_bool_and->contains(NegatedRef(rhs))) {
6011 const int ct_index = (*ref_to_bool_and)[NegatedRef(rhs)];
6012 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
6013 NegatedRef(lhs));
6014 } else {
6015 (*ref_to_bool_and)[lhs] = proto->constraints_size();
6016 ConstraintProto* ct = proto->add_constraints();
6017 ct->add_enforcement_literal(lhs);
6018 ct->mutable_bool_and()->add_literals(rhs);
6019 }
6020}
6021
6022template <typename ClauseContainer>
6023void ExtractClauses(bool merge_into_bool_and,
6024 absl::Span<const int> index_mapping,
6025 const ClauseContainer& container, CpModelProto* proto,
6026 std::string_view debug_name = "") {
6027 // We regroup the "implication" into bool_and to have a more concise proto and
6028 // also for nicer information about the number of binary clauses.
6029 //
6030 // Important: however, we do not do that for the model used during postsolving
6031 // since the order of the constraints might be important there depending on
6032 // how we perform the postsolve.
6033 absl::flat_hash_map<int, int> ref_to_bool_and;
6034 for (int i = 0; i < container.NumClauses(); ++i) {
6035 const std::vector<Literal>& clause = container.Clause(i);
6036 if (clause.empty()) continue;
6037
6038 // bool_and.
6039 //
6040 // TODO(user): Be smarter in how we regroup clause of size 2?
6041 if (merge_into_bool_and && clause.size() == 2) {
6042 const int var_a = index_mapping[clause[0].Variable().value()];
6043 const int var_b = index_mapping[clause[1].Variable().value()];
6044 const int ref_a = clause[0].IsPositive() ? var_a : NegatedRef(var_a);
6045 const int ref_b = clause[1].IsPositive() ? var_b : NegatedRef(var_b);
6046 AddImplication(NegatedRef(ref_a), ref_b, proto, &ref_to_bool_and);
6047 continue;
6048 }
6049
6050 // bool_or.
6051 ConstraintProto* ct = proto->add_constraints();
6052 if (!debug_name.empty()) {
6053 ct->set_name(debug_name);
6054 }
6055 ct->mutable_bool_or()->mutable_literals()->Reserve(clause.size());
6056 for (const Literal l : clause) {
6057 const int var = index_mapping[l.Variable().value()];
6058 if (l.IsPositive()) {
6059 ct->mutable_bool_or()->add_literals(var);
6060 } else {
6061 ct->mutable_bool_or()->add_literals(NegatedRef(var));
6062 }
6063 }
6064 }
6065}
6066
6067} // namespace
6068
6069bool CpModelPresolver::PresolveNoOverlap(ConstraintProto* ct) {
6070 if (context_->ModelIsUnsat()) return false;
6071 NoOverlapConstraintProto* proto = ct->mutable_no_overlap();
6072 bool changed = false;
6073
6074 // Filter out absent intervals.
6075 {
6076 int new_size = 0;
6077 const int initial_num_intervals = proto->intervals_size();
6078 for (int i = 0; i < initial_num_intervals; ++i) {
6079 const int interval_index = proto->intervals(i);
6080 if (context_->ConstraintIsInactive(interval_index)) continue;
6081 proto->set_intervals(new_size++, interval_index);
6082 }
6083
6084 if (new_size < initial_num_intervals) {
6085 proto->mutable_intervals()->Truncate(new_size);
6086 context_->UpdateRuleStats("no_overlap: removed absent intervals");
6087 changed = true;
6088 }
6089
6090 if (proto->intervals_size() == 1) {
6091 context_->UpdateRuleStats("no_overlap: only one interval");
6092 return RemoveConstraint(ct);
6093 }
6094 if (proto->intervals().empty()) {
6095 context_->UpdateRuleStats("no_overlap: no intervals");
6096 return RemoveConstraint(ct);
6097 }
6098 }
6099
6100 // TODO(user): add support for enforcement literals.
6101 if (HasEnforcementLiteral(*ct)) return changed;
6102
6103 // Process duplicate intervals.
6104 {
6105 // Collect duplicate intervals.
6106 absl::flat_hash_set<int> visited_intervals;
6107 absl::flat_hash_set<int> duplicate_intervals;
6108 for (const int interval_index : proto->intervals()) {
6109 if (!visited_intervals.insert(interval_index).second) {
6110 duplicate_intervals.insert(interval_index);
6111 }
6112 }
6113
6114 const int initial_num_intervals = proto->intervals_size();
6115 int new_size = 0;
6116 visited_intervals.clear();
6117
6118 for (int i = 0; i < initial_num_intervals; ++i) {
6119 const int interval_index = proto->intervals(i);
6120
6121 if (duplicate_intervals.contains(interval_index)) {
6122 // Once processed, we can always remove further duplicates.
6123 if (!visited_intervals.insert(interval_index).second) continue;
6124
6125 ConstraintProto* interval_ct =
6126 context_->working_model->mutable_constraints(interval_index);
6127
6128 // Case 1: size > 0. Interval must be unperformed.
6129 if (context_->SizeMin(interval_index) > 0) {
6130 if (HasEnforcementLiteral(*interval_ct)) {
6131 context_->UpdateRuleStats(
6132 "no_overlap: unperform duplicate non zero-sized intervals");
6133 if (!MarkOptionalIntervalAsFalse(interval_ct)) {
6134 return false;
6135 }
6136 // We can remove the interval from the no_overlap.
6137 continue;
6138 } else {
6139 return context_->NotifyThatModelIsUnsat(
6140 "no_overlap: duplicate interval with positive size");
6141 }
6142 }
6143
6144 // No need to do anything if the size is 0.
6145 if (context_->SizeMax(interval_index) > 0) {
6146 // Case 2: interval is performed. Size must be set to 0.
6147 if (!context_->ConstraintIsOptional(interval_index)) {
6148 if (!context_->IntersectDomainWith(interval_ct->interval().size(),
6149 Domain(0))) {
6150 return false;
6151 }
6152 context_->UpdateRuleStats(
6153 "no_overlap: zero the size of performed duplicate intervals");
6154 // We still need to add the interval to the no_overlap as zero sized
6155 // intervals still cannot overlap with other intervals.
6156 } else { // Case 3: interval is optional and size can be > 0.
6157 const int performed_literal = interval_ct->enforcement_literal(0);
6158 ConstraintProto* size_eq_zero =
6159 context_->working_model->add_constraints();
6160 size_eq_zero->add_enforcement_literal(performed_literal);
6161 size_eq_zero->mutable_linear()->add_domain(0);
6162 size_eq_zero->mutable_linear()->add_domain(0);
6164 interval_ct->interval().size(), 1,
6165 size_eq_zero->mutable_linear());
6166 context_->UpdateRuleStats(
6167 "no_overlap: make duplicate intervals as unperformed or zero "
6168 "sized");
6169 context_->UpdateNewConstraintsVariableUsage();
6170 }
6171 }
6172 }
6173
6174 proto->set_intervals(new_size++, interval_index);
6175 }
6176
6177 if (new_size < initial_num_intervals) {
6178 proto->mutable_intervals()->Truncate(new_size);
6179 changed = true;
6180 }
6181 }
6182
6183 // Split constraints in disjoint sets.
6184 if (proto->intervals_size() > 1) {
6185 std::vector<IndexedInterval> indexed_intervals;
6186 for (int i = 0; i < proto->intervals().size(); ++i) {
6187 const int index = proto->intervals(i);
6188 indexed_intervals.push_back({index,
6189 IntegerValue(context_->StartMin(index)),
6190 IntegerValue(context_->EndMax(index))});
6191 }
6192 std::vector<std::vector<int>> components;
6193 GetOverlappingIntervalComponents(&indexed_intervals, &components);
6194
6195 if (components.size() > 1) {
6196 for (const std::vector<int>& intervals : components) {
6197 if (intervals.size() <= 1) continue;
6198
6199 NoOverlapConstraintProto* new_no_overlap =
6200 context_->working_model->add_constraints()->mutable_no_overlap();
6201 // Fill in the intervals. Unfortunately, the Assign() method does not
6202 // compile in or-tools.
6203 for (const int i : intervals) {
6204 new_no_overlap->add_intervals(i);
6205 }
6206 }
6207 context_->UpdateNewConstraintsVariableUsage();
6208 context_->UpdateRuleStats("no_overlap: split into disjoint components");
6209 return RemoveConstraint(ct);
6210 }
6211 }
6212
6213 std::vector<int> constant_intervals;
6214 int64_t size_min_of_non_constant_intervals =
6215 std::numeric_limits<int64_t>::max();
6216 for (int i = 0; i < proto->intervals_size(); ++i) {
6217 const int interval_index = proto->intervals(i);
6218 if (context_->IntervalIsConstant(interval_index)) {
6219 constant_intervals.push_back(interval_index);
6220 } else {
6221 size_min_of_non_constant_intervals =
6222 std::min(size_min_of_non_constant_intervals,
6223 context_->SizeMin(interval_index));
6224 }
6225 }
6226
6227 bool move_constraint_last = false;
6228 if (!constant_intervals.empty()) {
6229 // Sort constant_intervals by start min.
6230 std::sort(constant_intervals.begin(), constant_intervals.end(),
6231 [this](int i1, int i2) {
6232 const int64_t s1 = context_->StartMin(i1);
6233 const int64_t e1 = context_->EndMax(i1);
6234 const int64_t s2 = context_->StartMin(i2);
6235 const int64_t e2 = context_->EndMax(i2);
6236 return std::tie(s1, e1) < std::tie(s2, e2);
6237 });
6238
6239 // Check for overlapping constant intervals. We need to check feasibility
6240 // before we simplify the constraint, as we might remove conflicting
6241 // overlapping constant intervals.
6242 for (int i = 0; i + 1 < constant_intervals.size(); ++i) {
6243 if (context_->EndMax(constant_intervals[i]) >
6244 context_->StartMin(constant_intervals[i + 1])) {
6245 context_->UpdateRuleStats("no_overlap: constant intervals overlap");
6246 return context_->NotifyThatModelIsUnsat();
6247 }
6248 }
6249
6250 if (constant_intervals.size() == proto->intervals_size()) {
6251 context_->UpdateRuleStats("no_overlap: no variable intervals");
6252 return RemoveConstraint(ct);
6253 }
6254
6255 absl::flat_hash_set<int> intervals_to_remove;
6256
6257 // If two constant intervals are separated by a gap smaller that the min
6258 // size of all non-constant intervals, then we can merge them.
6259 for (int i = 0; i + 1 < constant_intervals.size(); ++i) {
6260 const int start = i;
6261 while (i + 1 < constant_intervals.size() &&
6262 context_->StartMin(constant_intervals[i + 1]) -
6263 context_->EndMax(constant_intervals[i]) <
6264 size_min_of_non_constant_intervals) {
6265 i++;
6266 }
6267 if (i == start) continue;
6268 for (int j = start; j <= i; ++j) {
6269 intervals_to_remove.insert(constant_intervals[j]);
6270 }
6271 const int64_t new_start = context_->StartMin(constant_intervals[start]);
6272 const int64_t new_end = context_->EndMax(constant_intervals[i]);
6273 proto->add_intervals(context_->working_model->constraints_size());
6274 IntervalConstraintProto* new_interval =
6275 context_->working_model->add_constraints()->mutable_interval();
6276 new_interval->mutable_start()->set_offset(new_start);
6277 new_interval->mutable_size()->set_offset(new_end - new_start);
6278 new_interval->mutable_end()->set_offset(new_end);
6279 move_constraint_last = true;
6280 }
6281
6282 // Cleanup the original proto.
6283 if (!intervals_to_remove.empty()) {
6284 int new_size = 0;
6285 const int old_size = proto->intervals_size();
6286 for (int i = 0; i < old_size; ++i) {
6287 const int interval_index = proto->intervals(i);
6288 if (intervals_to_remove.contains(interval_index)) {
6289 continue;
6290 }
6291 proto->set_intervals(new_size++, interval_index);
6292 }
6293 CHECK_LT(new_size, old_size);
6294 proto->mutable_intervals()->Truncate(new_size);
6295 context_->UpdateRuleStats(
6296 "no_overlap: merge constant contiguous intervals");
6297 intervals_to_remove.clear();
6298 constant_intervals.clear();
6299 changed = true;
6300 context_->UpdateNewConstraintsVariableUsage();
6301 }
6302 }
6303
6304 {
6305 // Special case for "all-diff" encoded as no-overlap.
6306 int num_size_zero_or_one = 0;
6307 bool has_optional_size_one = false;
6308 for (const int index : proto->intervals()) {
6309 const ConstraintProto& interval_ct =
6310 context_->working_model->constraints(index);
6311 const LinearExpressionProto& size = interval_ct.interval().size();
6312 if (size.vars().empty() && size.offset() >= 0 && size.offset() <= 1) {
6313 ++num_size_zero_or_one;
6314 }
6315 if (size.vars().empty() && size.offset() == 1 &&
6316 !interval_ct.enforcement_literal().empty()) {
6317 has_optional_size_one = true;
6318 }
6319 }
6320 const int initial_num_intervals = proto->intervals().size();
6321 if (num_size_zero_or_one == initial_num_intervals) {
6322 if (has_optional_size_one) {
6323 // If there is only size zero or one, we can remove the size zero
6324 // intervals as there is no constraint on them.
6325 int new_size = 0;
6326 for (const int index : proto->intervals()) {
6327 const IntervalConstraintProto& interval =
6328 context_->working_model->constraints(index).interval();
6329 if (interval.size().offset() == 0) continue;
6330 proto->set_intervals(new_size++, index);
6331 }
6332 if (new_size < initial_num_intervals) {
6333 proto->mutable_intervals()->Truncate(new_size);
6334 changed = true;
6335 context_->UpdateRuleStats("no_overlap: removed size 0 from all diff");
6336 }
6337 } else {
6338 // All size one intervals are present, we can convert to an
6339 // all_different constraint, and remove size 0 intervals.
6340 AllDifferentConstraintProto* all_diff =
6341 context_->AddEnforcedConstraint(ct)->mutable_all_diff();
6342 for (const int index : proto->intervals()) {
6343 const IntervalConstraintProto& interval =
6344 context_->working_model->constraints(index).interval();
6345 if (interval.size().offset() == 0) continue;
6346 *all_diff->add_exprs() = interval.start();
6347 }
6348 if (all_diff->exprs_size() < initial_num_intervals) {
6349 context_->UpdateRuleStats("no_overlap: removed size 0 from all diff");
6350 }
6351 context_->UpdateNewConstraintsVariableUsage();
6352 context_->UpdateRuleStats("no_overlap: converted to all diff");
6353 return RemoveConstraint(ct);
6354 }
6355 }
6356 }
6357
6358 if (proto->intervals_size() == 1) {
6359 context_->UpdateRuleStats("no_overlap: only one interval");
6360 return RemoveConstraint(ct);
6361 }
6362 if (proto->intervals().empty()) {
6363 context_->UpdateRuleStats("no_overlap: no intervals");
6364 return RemoveConstraint(ct);
6365 }
6366
6367 // Unfortunately, because we want all intervals to appear before a constraint
6368 // that uses them, we need to move the constraint last when we merged constant
6369 // intervals.
6370 if (move_constraint_last) {
6371 changed = true;
6372 *context_->working_model->add_constraints() = *ct;
6373 context_->UpdateNewConstraintsVariableUsage();
6374 return RemoveConstraint(ct);
6375 }
6376
6377 return changed;
6378}
6379
6380bool CpModelPresolver::PresolveNoOverlap2DFramed(
6381 absl::Span<const Rectangle> fixed_boxes,
6382 absl::Span<const RectangleInRange> non_fixed_boxes, ConstraintProto* ct) {
6383 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6384
6385 DCHECK(!non_fixed_boxes.empty());
6386 Rectangle bounding_box = non_fixed_boxes[0].bounding_area;
6387 for (const RectangleInRange& box : non_fixed_boxes) {
6388 bounding_box.GrowToInclude(box.bounding_area);
6389 }
6390 std::vector<Rectangle> espace_for_single_box =
6391 FindEmptySpaces(bounding_box, {fixed_boxes.begin(), fixed_boxes.end()});
6392 // TODO(user): Find a faster way to see if fixed boxes are delimiting a
6393 // rectangle.
6394 std::vector<Rectangle> empty;
6395 ReduceNumberofBoxesGreedy(&espace_for_single_box, &empty);
6396 ReduceNumberOfBoxesExactMandatory(&espace_for_single_box, &empty);
6397 if (espace_for_single_box.size() != 1) {
6398 // Not a rectangular frame, since the inside is not a rectangle.
6399 return false;
6400 }
6401 Rectangle fixed_boxes_bb = fixed_boxes.front();
6402 for (const Rectangle& box : fixed_boxes) {
6403 fixed_boxes_bb.GrowToInclude(box);
6404 }
6405 const Rectangle framed_region = espace_for_single_box.front();
6406 for (const RectangleInRange& box : non_fixed_boxes) {
6407 if (!box.bounding_area.IsInsideOf(fixed_boxes_bb)) {
6408 // Something can be outside of the frame.
6409 return false;
6410 }
6411 if (non_fixed_boxes.size() > 1 &&
6412 (2 * box.x_size <= framed_region.SizeX() ||
6413 2 * box.y_size <= framed_region.SizeY())) {
6414 // We can fit two boxes in the delimited space between the fixed boxes, so
6415 // we cannot replace it by an at-most-one.
6416 return false;
6417 }
6418 const int x_interval_index = proto.x_intervals(box.box_index);
6419 const int y_interval_index = proto.y_intervals(box.box_index);
6420 if (!context_->working_model->constraints(x_interval_index)
6421 .enforcement_literal()
6422 .empty() &&
6423 !context_->working_model->constraints(y_interval_index)
6424 .enforcement_literal()
6425 .empty()) {
6426 if (context_->working_model->constraints(x_interval_index)
6427 .enforcement_literal(0) !=
6428 context_->working_model->constraints(y_interval_index)
6429 .enforcement_literal(0)) {
6430 // Two different enforcement literals.
6431 return false;
6432 }
6433 }
6434 }
6435 // All this no_overlap_2d constraint is doing is forcing at most one of
6436 // the non-fixed boxes to be in the `framed_region` rectangle. A
6437 // better representation of this is to simply enforce that the items fit
6438 // that rectangle with linear constraints and add a at-most-one constraint.
6439 std::vector<int> enforcement_literals_for_amo;
6440 bool has_mandatory = false;
6441 for (const RectangleInRange& box : non_fixed_boxes) {
6442 const int box_index = box.box_index;
6443 const int x_interval_index = proto.x_intervals(box_index);
6444 const int y_interval_index = proto.y_intervals(box_index);
6445 const ConstraintProto& x_interval_ct =
6446 context_->working_model->constraints(x_interval_index);
6447 const ConstraintProto& y_interval_ct =
6448 context_->working_model->constraints(y_interval_index);
6449 if (x_interval_ct.enforcement_literal().empty() &&
6450 y_interval_ct.enforcement_literal().empty()) {
6451 // Mandatory box, update the domains.
6452 if (has_mandatory) {
6453 return context_->NotifyThatModelIsUnsat(
6454 "Two mandatory boxes in the same space");
6455 }
6456 has_mandatory = true;
6457 if (!context_->IntersectDomainWith(x_interval_ct.interval().start(),
6458 Domain(framed_region.x_min.value(),
6459 framed_region.x_max.value()))) {
6460 return true;
6461 }
6462 if (!context_->IntersectDomainWith(x_interval_ct.interval().end(),
6463 Domain(framed_region.x_min.value(),
6464 framed_region.x_max.value()))) {
6465 return true;
6466 }
6467 if (!context_->IntersectDomainWith(y_interval_ct.interval().start(),
6468 Domain(framed_region.y_min.value(),
6469 framed_region.y_max.value()))) {
6470 return true;
6471 }
6472 if (!context_->IntersectDomainWith(y_interval_ct.interval().end(),
6473 Domain(framed_region.y_min.value(),
6474 framed_region.y_max.value()))) {
6475 return true;
6476 }
6477 } else {
6478 auto add_linear_constraint = [&](const ConstraintProto& interval_ct,
6479 int enforcement_literal,
6480 IntegerValue min, IntegerValue max) {
6481 // TODO(user): If size is constant add only one linear constraint
6482 // instead of two.
6483 context_->AddImplyInDomain(enforcement_literal,
6484 interval_ct.interval().start(),
6485 Domain(min.value(), max.value()));
6486 context_->AddImplyInDomain(enforcement_literal,
6487 interval_ct.interval().end(),
6488 Domain(min.value(), max.value()));
6489 };
6490 const int enforcement_literal =
6491 x_interval_ct.enforcement_literal().empty()
6492 ? y_interval_ct.enforcement_literal(0)
6493 : x_interval_ct.enforcement_literal(0);
6494 enforcement_literals_for_amo.push_back(enforcement_literal);
6495 add_linear_constraint(x_interval_ct, enforcement_literal,
6496 framed_region.x_min, framed_region.x_max);
6497 add_linear_constraint(y_interval_ct, enforcement_literal,
6498 framed_region.y_min, framed_region.y_max);
6499 }
6500 }
6501 if (has_mandatory) {
6502 for (const int lit : enforcement_literals_for_amo) {
6503 if (!context_->SetLiteralToFalse(lit)) {
6504 return true;
6505 }
6506 }
6507 } else if (enforcement_literals_for_amo.size() > 1) {
6508 context_->working_model->add_constraints()
6509 ->mutable_at_most_one()
6510 ->mutable_literals()
6511 ->Add(enforcement_literals_for_amo.begin(),
6512 enforcement_literals_for_amo.end());
6513 }
6514 context_->UpdateRuleStats("no_overlap_2d: at most one rectangle in region");
6515 context_->UpdateNewConstraintsVariableUsage();
6516 return RemoveConstraint(ct);
6517}
6518
6519bool CpModelPresolver::ExpandEncoded2DBinPacking(
6520 absl::Span<const Rectangle> fixed_boxes,
6521 absl::Span<const RectangleInRange> non_fixed_boxes, ConstraintProto* ct) {
6522 const Disjoint2dPackingResult disjoint_packing_presolve_result =
6524 non_fixed_boxes, fixed_boxes,
6525 context_->params()
6526 .maximum_regions_to_split_in_disconnected_no_overlap_2d());
6527 if (disjoint_packing_presolve_result.bins.empty()) return false;
6528
6529 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6530 std::vector<SolutionCrush::BoxInAreaLiteral> box_in_area_lits;
6531 absl::flat_hash_map<int, std::vector<int>> box_to_presence_literal;
6532 // For the boxes that are optional, add a presence literal for each box in a
6533 // fake "absent" bin.
6534 for (int idx = 0; idx < non_fixed_boxes.size(); ++idx) {
6535 const int b = non_fixed_boxes[idx].box_index;
6536 const ConstraintProto& x_interval_ct =
6537 context_->working_model->constraints(proto.x_intervals(b));
6538 const ConstraintProto& y_interval_ct =
6539 context_->working_model->constraints(proto.y_intervals(b));
6540 if (x_interval_ct.enforcement_literal().empty() &&
6541 y_interval_ct.enforcement_literal().empty()) {
6542 // Mandatory box, cannot be in the "absent" bin -1.
6543 continue;
6544 }
6545 int enforcement_literal = x_interval_ct.enforcement_literal().empty()
6546 ? y_interval_ct.enforcement_literal(0)
6547 : x_interval_ct.enforcement_literal(0);
6548 int potentially_other_enforcement_literal =
6549 y_interval_ct.enforcement_literal().empty()
6550 ? x_interval_ct.enforcement_literal(0)
6551 : y_interval_ct.enforcement_literal(0);
6552
6553 if (enforcement_literal == potentially_other_enforcement_literal) {
6554 // The box is in the "absent" bin -1.
6555 box_to_presence_literal[idx].push_back(NegatedRef(enforcement_literal));
6556 } else {
6557 const int interval_is_absent_literal =
6558 context_->NewBoolVarWithConjunction(
6559 {enforcement_literal, potentially_other_enforcement_literal});
6560
6561 BoolArgumentProto* bool_or =
6562 context_->working_model->add_constraints()->mutable_bool_or();
6563 bool_or->add_literals(NegatedRef(interval_is_absent_literal));
6564 for (const int lit :
6565 {enforcement_literal, potentially_other_enforcement_literal}) {
6566 context_->AddImplication(NegatedRef(interval_is_absent_literal), lit);
6567 bool_or->add_literals(NegatedRef(lit));
6568 }
6569 box_to_presence_literal[idx].push_back(interval_is_absent_literal);
6570 }
6571 }
6572 // Now create the literals "item i in bin j".
6573 for (int bin_index = 0;
6574 bin_index < disjoint_packing_presolve_result.bins.size(); ++bin_index) {
6575 const Disjoint2dPackingResult::Bin& bin =
6576 disjoint_packing_presolve_result.bins[bin_index];
6577 NoOverlap2DConstraintProto new_no_overlap_2d;
6578 for (const Rectangle& ret : bin.fixed_boxes) {
6579 new_no_overlap_2d.add_x_intervals(
6580 context_->working_model->constraints_size());
6581 new_no_overlap_2d.add_y_intervals(
6582 context_->working_model->constraints_size() + 1);
6583 IntervalConstraintProto* new_interval =
6584 context_->working_model->add_constraints()->mutable_interval();
6585 new_interval->mutable_start()->set_offset(ret.x_min.value());
6586 new_interval->mutable_size()->set_offset(ret.SizeX().value());
6587 new_interval->mutable_end()->set_offset(ret.x_max.value());
6588
6589 new_interval =
6590 context_->working_model->add_constraints()->mutable_interval();
6591 new_interval->mutable_start()->set_offset(ret.y_min.value());
6592 new_interval->mutable_size()->set_offset(ret.SizeY().value());
6593 new_interval->mutable_end()->set_offset(ret.y_max.value());
6594 }
6595 for (const int idx : bin.non_fixed_box_indexes) {
6596 int presence_in_box_lit = context_->NewBoolVar("binpacking");
6597 box_to_presence_literal[idx].push_back(presence_in_box_lit);
6598 const int b = non_fixed_boxes[idx].box_index;
6599 box_in_area_lits.push_back({.box_index = b,
6600 .area_index = bin_index,
6601 .literal = presence_in_box_lit});
6602 const ConstraintProto& x_interval_ct =
6603 context_->working_model->constraints(proto.x_intervals(b));
6604 const ConstraintProto& y_interval_ct =
6605 context_->working_model->constraints(proto.y_intervals(b));
6606 ConstraintProto* new_interval_x =
6607 context_->working_model->add_constraints();
6608 *new_interval_x = x_interval_ct;
6609 new_interval_x->clear_enforcement_literal();
6610 new_interval_x->add_enforcement_literal(presence_in_box_lit);
6611 ConstraintProto* new_interval_y =
6612 context_->working_model->add_constraints();
6613 *new_interval_y = y_interval_ct;
6614 new_interval_y->clear_enforcement_literal();
6615 new_interval_y->add_enforcement_literal(presence_in_box_lit);
6616 new_no_overlap_2d.add_x_intervals(
6617 context_->working_model->constraints_size() - 2);
6618 new_no_overlap_2d.add_y_intervals(
6619 context_->working_model->constraints_size() - 1);
6620 }
6621 context_->working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
6622 &new_no_overlap_2d);
6623 }
6624
6625 // Each box is in exactly one bin (including the fake "absent" bin).
6626 for (int box_index = 0; box_index < non_fixed_boxes.size(); ++box_index) {
6627 const std::vector<int>& presence_literals =
6628 box_to_presence_literal[box_index];
6629 if (presence_literals.empty()) {
6630 return context_->NotifyThatModelIsUnsat(
6631 "A mandatory box cannot be placed in any position");
6632 }
6633 auto* exactly_one =
6634 context_->working_model->add_constraints()->mutable_exactly_one();
6635 for (const int presence_literal : presence_literals) {
6636 exactly_one->add_literals(presence_literal);
6637 }
6638 }
6639 CompactVectorVector<int, Rectangle> areas;
6640 for (int bin_index = 0;
6641 bin_index < disjoint_packing_presolve_result.bins.size(); ++bin_index) {
6642 areas.Add(disjoint_packing_presolve_result.bins[bin_index].bin_area);
6643 }
6644 solution_crush_.AssignVariableToPackingArea(
6645 areas, *context_->working_model, proto.x_intervals(), proto.y_intervals(),
6646 box_in_area_lits);
6647 context_->UpdateNewConstraintsVariableUsage();
6648 context_->UpdateRuleStats(
6649 "no_overlap_2d: fixed boxes partition available space, converted "
6650 "to optional regions");
6651 return RemoveConstraint(ct);
6652}
6653
6654bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
6655 if (context_->ModelIsUnsat()) {
6656 return false;
6657 }
6658 // TODO(user): add support for enforcement literals.
6659 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6660 bool truncated = false;
6661
6662 // Filter absent boxes.
6663 {
6664 const int initial_num_boxes = proto.x_intervals_size();
6665 int new_size = 0;
6666 for (int i = 0; i < proto.x_intervals_size(); ++i) {
6667 const int x_interval_index = proto.x_intervals(i);
6668 const int y_interval_index = proto.y_intervals(i);
6669
6670 ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index);
6671 ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index);
6672
6673 // We don't want to fully presolve the intervals (intervals have their own
6674 // presolve), but we don't want to bother with negative sizes downstream
6675 // in this function, so we want to remove them ASAP.
6676 for (const int interval_index : {x_interval_index, y_interval_index}) {
6677 if (context_->StartMin(interval_index) >
6678 context_->EndMax(interval_index)) {
6679 const ConstraintProto& interval_ct =
6680 context_->working_model->constraints(interval_index);
6681 if (interval_ct.enforcement_literal_size() == 1) {
6682 const int literal = interval_ct.enforcement_literal(0);
6683 if (!context_->SetLiteralToFalse(literal)) {
6684 return true;
6685 }
6686 } else {
6687 return context_->NotifyThatModelIsUnsat(
6688 "no_overlap_2d: impossible interval");
6689 }
6690 }
6691
6692 if (context_->SizeMin(interval_index) < 0) {
6693 const ConstraintProto& interval_ct =
6694 context_->working_model->constraints(interval_index);
6695 if (interval_ct.enforcement_literal().empty()) {
6696 bool domain_changed = false;
6697 // Size can't be negative.
6698 if (!context_->IntersectDomainWith(
6699 interval_ct.interval().size(),
6700 Domain(0, std::numeric_limits<int64_t>::max()),
6701 &domain_changed)) {
6702 return false;
6703 }
6704 }
6705 }
6706 }
6707
6708 if (context_->ConstraintIsInactive(x_interval_index) ||
6709 context_->ConstraintIsInactive(y_interval_index)) {
6710 continue;
6711 }
6712
6713 new_size++;
6714 }
6715
6716 if (new_size < initial_num_boxes) {
6717 truncated = true;
6718 context_->UpdateRuleStats("no_overlap_2d: removed inactive boxes");
6719 ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
6720 ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
6721 }
6722
6723 if (new_size == 0) {
6724 context_->UpdateRuleStats("no_overlap_2d: no boxes");
6725 return RemoveConstraint(ct);
6726 }
6727
6728 if (new_size == 1) {
6729 context_->UpdateRuleStats("no_overlap_2d: only one box");
6730 return RemoveConstraint(ct);
6731 }
6732 }
6733
6734 if (HasEnforcementLiteral(*ct)) return false;
6735
6736 bool x_constant = true;
6737 bool y_constant = true;
6738 bool has_zero_sized_interval = false;
6739 bool has_potential_zero_sized_interval = false;
6740
6741 std::vector<Rectangle> bounding_boxes, fixed_boxes, non_fixed_bounding_boxes;
6742 std::vector<RectangleInRange> non_fixed_boxes;
6743 absl::flat_hash_set<int> fixed_item_indexes;
6744 for (int i = 0; i < proto.x_intervals_size(); ++i) {
6745 const int x_interval_index = proto.x_intervals(i);
6746 const int y_interval_index = proto.y_intervals(i);
6747
6748 bounding_boxes.push_back(
6749 {IntegerValue(context_->StartMin(x_interval_index)),
6750 IntegerValue(context_->EndMax(x_interval_index)),
6751 IntegerValue(context_->StartMin(y_interval_index)),
6752 IntegerValue(context_->EndMax(y_interval_index))});
6753 if (context_->IntervalIsConstant(x_interval_index) &&
6754 context_->IntervalIsConstant(y_interval_index) &&
6755 context_->SizeMax(x_interval_index) > 0 &&
6756 context_->SizeMax(y_interval_index) > 0) {
6757 fixed_boxes.push_back(bounding_boxes.back());
6758 fixed_item_indexes.insert(i);
6759 } else {
6760 non_fixed_bounding_boxes.push_back(bounding_boxes.back());
6761 non_fixed_boxes.push_back(
6762 {.box_index = i,
6763 .bounding_area = bounding_boxes.back(),
6764 .x_size = std::max(int64_t{0}, context_->SizeMin(x_interval_index)),
6765 .y_size =
6766 std::max(int64_t{0}, context_->SizeMin(y_interval_index))});
6767 }
6768
6769 if (x_constant && !context_->IntervalIsConstant(x_interval_index)) {
6770 x_constant = false;
6771 }
6772 if (y_constant && !context_->IntervalIsConstant(y_interval_index)) {
6773 y_constant = false;
6774 }
6775 if (context_->SizeMax(x_interval_index) <= 0 ||
6776 context_->SizeMax(y_interval_index) <= 0) {
6777 has_zero_sized_interval = true;
6778 }
6779 if (context_->SizeMin(x_interval_index) <= 0 ||
6780 context_->SizeMin(y_interval_index) <= 0) {
6781 has_potential_zero_sized_interval = true;
6782 }
6783 }
6784
6785 const CompactVectorVector<int> components =
6786 GetOverlappingRectangleComponents(bounding_boxes);
6787 if (components.size() > 1) {
6788 for (int i = 0; i < components.size(); ++i) {
6789 absl::Span<const int> boxes = components[i];
6790 if (boxes.size() <= 1) continue;
6791
6792 NoOverlap2DConstraintProto* new_no_overlap_2d =
6793 context_->working_model->add_constraints()->mutable_no_overlap_2d();
6794 for (const int b : boxes) {
6795 new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
6796 new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
6797 }
6798 }
6799 context_->UpdateNewConstraintsVariableUsage();
6800 context_->UpdateRuleStats("no_overlap_2d: split into disjoint components");
6801 return RemoveConstraint(ct);
6802 }
6803
6804 // TODO(user): handle this case. See issue #4068.
6805 if (!has_zero_sized_interval && (x_constant || y_constant)) {
6806 context_->UpdateRuleStats(
6807 "no_overlap_2d: a dimension is constant, splitting into many "
6808 "no_overlaps");
6809 std::vector<IndexedInterval> indexed_intervals;
6810 for (int i = 0; i < proto.x_intervals_size(); ++i) {
6811 int x = proto.x_intervals(i);
6812 int y = proto.y_intervals(i);
6813 if (x_constant) std::swap(x, y);
6814 indexed_intervals.push_back({x, IntegerValue(context_->StartMin(y)),
6815 IntegerValue(context_->EndMax(y))});
6816 }
6817 CompactVectorVector<int> no_overlaps;
6818 absl::c_stable_sort(indexed_intervals,
6819 IndexedInterval::ComparatorByStart());
6820 ConstructOverlappingSets(absl::MakeSpan(indexed_intervals), &no_overlaps);
6821 for (int i = 0; i < no_overlaps.size(); ++i) {
6822 ConstraintProto* new_ct = context_->working_model->add_constraints();
6823 // Unfortunately, the Assign() method does not work in or-tools as the
6824 // protobuf int32_t type is not the int type.
6825 for (const int i : no_overlaps[i]) {
6826 new_ct->mutable_no_overlap()->add_intervals(i);
6827 }
6828 }
6829 context_->UpdateNewConstraintsVariableUsage();
6830 return RemoveConstraint(ct);
6831 }
6832
6833 // We check if the fixed boxes are not overlapping so downstream code can
6834 // assume it to be true.
6835 if (!FindPartialRectangleIntersections(fixed_boxes).empty()) {
6836 return context_->NotifyThatModelIsUnsat(
6837 "Two fixed boxes in no_overlap_2d overlap");
6838 }
6839
6840 if (non_fixed_bounding_boxes.empty()) {
6841 context_->UpdateRuleStats("no_overlap_2d: all boxes are fixed");
6842 return RemoveConstraint(ct);
6843 }
6844
6845 // TODO(user): presolve the zero-size fixed items so they are disjoint from
6846 // the other fixed items. Then the following presolve is still valid. On the
6847 // other hand, we cannot do much with non-fixed zero-size items.
6848 if (!has_potential_zero_sized_interval && !fixed_boxes.empty()) {
6849 const bool presolved =
6850 PresolveFixed2dRectangles(non_fixed_boxes, &fixed_boxes);
6851 if (presolved) {
6852 NoOverlap2DConstraintProto new_no_overlap_2d;
6853
6854 // Replace the old fixed intervals by the new ones.
6855 const int old_size = proto.x_intervals_size();
6856 for (int i = 0; i < old_size; ++i) {
6857 if (fixed_item_indexes.contains(i)) {
6858 continue;
6859 }
6860 new_no_overlap_2d.add_x_intervals(proto.x_intervals(i));
6861 new_no_overlap_2d.add_y_intervals(proto.y_intervals(i));
6862 }
6863 for (const Rectangle& fixed_box : fixed_boxes) {
6864 const int item_x_interval =
6865 context_->working_model->constraints().size();
6866 IntervalConstraintProto* new_interval =
6867 context_->working_model->add_constraints()->mutable_interval();
6868 new_interval->mutable_start()->set_offset(fixed_box.x_min.value());
6869 new_interval->mutable_size()->set_offset(fixed_box.SizeX().value());
6870 new_interval->mutable_end()->set_offset(fixed_box.x_max.value());
6871
6872 const int item_y_interval =
6873 context_->working_model->constraints().size();
6874 new_interval =
6875 context_->working_model->add_constraints()->mutable_interval();
6876 new_interval->mutable_start()->set_offset(fixed_box.y_min.value());
6877 new_interval->mutable_size()->set_offset(fixed_box.SizeY().value());
6878 new_interval->mutable_end()->set_offset(fixed_box.y_max.value());
6879
6880 new_no_overlap_2d.add_x_intervals(item_x_interval);
6881 new_no_overlap_2d.add_y_intervals(item_y_interval);
6882 }
6883 context_->working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
6884 &new_no_overlap_2d);
6885 context_->UpdateNewConstraintsVariableUsage();
6886 context_->UpdateRuleStats("no_overlap_2d: presolved fixed rectangles");
6887 return RemoveConstraint(ct);
6888 }
6889 }
6890
6891 if (!fixed_boxes.empty() && fixed_boxes.size() <= 4 &&
6892 !non_fixed_boxes.empty() && !has_potential_zero_sized_interval) {
6893 if (PresolveNoOverlap2DFramed(fixed_boxes, non_fixed_boxes, ct)) {
6894 return true;
6895 }
6896 }
6897
6898 // If the non-fixed boxes are disjoint but connected by fixed boxes, we can
6899 // split the constraint and duplicate the fixed boxes. To avoid duplicating
6900 // too many fixed boxes, we do this after we we applied the presolve reducing
6901 // their number to as few as possible.
6902 const CompactVectorVector<int> non_fixed_components =
6903 GetOverlappingRectangleComponents(non_fixed_bounding_boxes);
6904 if (non_fixed_components.size() > 1) {
6905 for (int i = 0; i < non_fixed_components.size(); ++i) {
6906 // Note: we care about components of size 1 because they might be
6907 // overlapping with the fixed boxes.
6908 absl::Span<const int> indexes = non_fixed_components[i];
6909
6910 NoOverlap2DConstraintProto* new_no_overlap_2d =
6911 context_->working_model->add_constraints()->mutable_no_overlap_2d();
6912 for (const int idx : indexes) {
6913 const int b = non_fixed_boxes[idx].box_index;
6914 new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
6915 new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
6916 }
6917 for (const int b : fixed_item_indexes) {
6918 new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
6919 new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
6920 }
6921 }
6922 context_->UpdateNewConstraintsVariableUsage();
6923 context_->UpdateRuleStats(
6924 "no_overlap_2d: split into disjoint components duplicating fixed "
6925 "boxes");
6926 return RemoveConstraint(ct);
6927 }
6928
6929 if (!has_potential_zero_sized_interval) {
6930 if (ExpandEncoded2DBinPacking(fixed_boxes, non_fixed_boxes, ct)) {
6931 return true;
6932 }
6933 }
6934 RunPropagatorsForConstraint(*ct);
6935 return truncated;
6936}
6937
6938namespace {
6939LinearExpressionProto ConstantExpressionProto(int64_t value) {
6941 expr.set_offset(value);
6942 return expr;
6943}
6944} // namespace
6945
6946void CpModelPresolver::DetectDuplicateIntervals(
6947 int c, google::protobuf::RepeatedField<int32_t>* intervals) {
6948 interval_representative_.clear();
6949 bool changed = false;
6950 const int size = intervals->size();
6951 for (int i = 0; i < size; ++i) {
6952 const int index = (*intervals)[i];
6953 const auto [it, inserted] = interval_representative_.insert({index, index});
6954 if (it->second != index) {
6955 changed = true;
6956 intervals->Set(i, it->second);
6957 context_->UpdateRuleStats(
6958 "intervals: change duplicate index inside constraint");
6959 }
6960 }
6961 if (changed) context_->UpdateConstraintVariableUsage(c);
6962}
6963
6964bool CpModelPresolver::PresolveCumulative(ConstraintProto* ct) {
6965 if (context_->ModelIsUnsat()) return false;
6966 // TODO(user): add support for enforcement literals.
6967 if (HasEnforcementLiteral(*ct)) return false;
6968
6969 CumulativeConstraintProto* proto = ct->mutable_cumulative();
6970
6971 bool changed = CanonicalizeLinearExpression(*ct, proto->mutable_capacity());
6972 for (LinearExpressionProto& exp :
6973 *(ct->mutable_cumulative()->mutable_demands())) {
6974 changed |= CanonicalizeLinearExpression(*ct, &exp);
6975 }
6976
6977 const int64_t capacity_max = context_->MaxOf(proto->capacity());
6978
6979 // Checks the capacity of the constraint.
6980 {
6981 bool domain_changed = false;
6982 if (!context_->IntersectDomainWith(
6983 proto->capacity(), Domain(0, capacity_max), &domain_changed)) {
6984 return true;
6985 }
6986 if (domain_changed) {
6987 context_->UpdateRuleStats("cumulative: trimmed negative capacity");
6988 }
6989 }
6990
6991 // Merge identical intervals.
6992 {
6993 std::vector<int> intervals = {proto->intervals().begin(),
6994 proto->intervals().end()};
6996 if (intervals.size() < proto->intervals_size()) {
6997 absl::btree_map<int, std::vector<LinearExpressionProto>>
6998 interval_to_sizes;
6999 for (int i = 0; i < proto->intervals_size(); ++i) {
7000 interval_to_sizes[proto->intervals(i)].push_back(proto->demands(i));
7001 }
7002
7003 absl::btree_map<int, int64_t> terms;
7004 proto->clear_intervals();
7005 proto->clear_demands();
7006 for (const auto& [interval, demands] : interval_to_sizes) {
7007 terms.clear();
7008 int64_t offset = 0;
7009 for (const LinearExpressionProto& demand : demands) {
7010 for (int i = 0; i < demand.vars_size(); ++i) {
7011 terms[demand.vars(i)] += demand.coeffs(i);
7012 }
7013 offset += demand.offset();
7014 }
7015 if (terms.size() <= 1) {
7016 proto->add_intervals(interval);
7017 LinearExpressionProto* demand = proto->add_demands();
7018 for (const auto& [var, coeff] : terms) {
7019 demand->add_vars(var);
7020 demand->add_coeffs(coeff);
7021 }
7022 demand->set_offset(offset);
7023 context_->UpdateRuleStats(
7024 "cumulative: merged demands of identical interval");
7025 } else {
7026 LinearConstraintProto* sum_of_terms =
7027 context_->working_model->add_constraints()->mutable_linear();
7028 std::vector<int> vars;
7029 vars.reserve(terms.size());
7030 std::vector<int64_t> coeffs;
7031 coeffs.reserve(terms.size());
7032 Domain new_domain(0);
7033 for (const auto& [var, coeff] : terms) {
7034 vars.push_back(var);
7035 coeffs.push_back(coeff);
7036 new_domain = new_domain.AdditionWith(
7037 context_->DomainOf(var).ContinuousMultiplicationBy(coeff));
7038 sum_of_terms->add_vars(var);
7039 sum_of_terms->add_coeffs(coeff);
7040 }
7041 const int variable_demand = context_->NewIntVar(new_domain);
7042 context_->solution_crush().SetVarToLinearExpression(variable_demand,
7043 vars, coeffs);
7044 sum_of_terms->add_vars(variable_demand);
7045 sum_of_terms->add_coeffs(-1);
7046 FillDomainInProto(0, sum_of_terms);
7047 context_->UpdateNewConstraintsVariableUsage();
7048
7049 proto->add_intervals(interval);
7050 LinearExpressionProto* demand = proto->add_demands();
7051 demand->add_vars(variable_demand);
7052 demand->add_coeffs(1);
7053 demand->set_offset(offset);
7054 changed = true;
7055
7056 context_->UpdateRuleStats(
7057 "cumulative: merged variable demands of identical interval");
7058 }
7059 }
7060 }
7061 }
7062
7063 // Filter absent intervals, or zero demands, or demand incompatible with the
7064 // capacity.
7065 {
7066 int new_size = 0;
7067 int num_zero_demand_removed = 0;
7068 int num_zero_size_removed = 0;
7069 int num_incompatible_intervals = 0;
7070 for (int i = 0; i < proto->intervals_size(); ++i) {
7071 if (context_->ConstraintIsInactive(proto->intervals(i))) continue;
7072
7073 const LinearExpressionProto& demand_expr = proto->demands(i);
7074 const int64_t demand_max = context_->MaxOf(demand_expr);
7075 if (demand_max == 0) {
7076 num_zero_demand_removed++;
7077 continue;
7078 }
7079
7080 const int interval_index = proto->intervals(i);
7081 if (context_->SizeMax(interval_index) <= 0) {
7082 // Size 0 intervals cannot contribute to a cumulative.
7083 num_zero_size_removed++;
7084 continue;
7085 }
7086
7087 // Inconsistent intervals cannot be performed.
7088 const int64_t start_min = context_->StartMin(interval_index);
7089 const int64_t end_max = context_->EndMax(interval_index);
7090 if (start_min > end_max) {
7091 if (context_->ConstraintIsOptional(interval_index)) {
7092 ConstraintProto* interval_ct =
7093 context_->working_model->mutable_constraints(interval_index);
7094 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
7095 const int literal = interval_ct->enforcement_literal(0);
7096 if (!context_->SetLiteralToFalse(literal)) {
7097 return true;
7098 }
7099 num_incompatible_intervals++;
7100 continue;
7101 } else {
7102 return context_->NotifyThatModelIsUnsat(
7103 "cumulative: inconsistent intervals cannot be performed");
7104 }
7105 }
7106
7107 if (context_->MinOf(demand_expr) > capacity_max) {
7108 if (context_->ConstraintIsOptional(interval_index)) {
7109 if (context_->SizeMin(interval_index) > 0) {
7110 ConstraintProto* interval_ct =
7111 context_->working_model->mutable_constraints(interval_index);
7112 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
7113 const int literal = interval_ct->enforcement_literal(0);
7114 if (!context_->SetLiteralToFalse(literal)) {
7115 return true;
7116 }
7117 num_incompatible_intervals++;
7118 continue;
7119 }
7120 } else { // Interval performed.
7121 // Try to set the size to 0.
7122 const ConstraintProto& interval_ct =
7123 context_->working_model->constraints(interval_index);
7124 if (!context_->IntersectDomainWith(interval_ct.interval().size(),
7125 {0, 0})) {
7126 return true;
7127 }
7128 context_->UpdateRuleStats(
7129 "cumulative: zero size of performed demand that exceeds "
7130 "capacity");
7131 ++num_zero_demand_removed;
7132 continue;
7133 }
7134 }
7135
7136 proto->set_intervals(new_size, interval_index);
7137 *proto->mutable_demands(new_size) = proto->demands(i);
7138 new_size++;
7139 }
7140
7141 if (new_size < proto->intervals_size()) {
7142 changed = true;
7143 proto->mutable_intervals()->Truncate(new_size);
7144 proto->mutable_demands()->erase(
7145 proto->mutable_demands()->begin() + new_size,
7146 proto->mutable_demands()->end());
7147 }
7148
7149 if (num_zero_demand_removed > 0) {
7150 context_->UpdateRuleStats(
7151 "cumulative: removed intervals with no demands");
7152 }
7153 if (num_zero_size_removed > 0) {
7154 context_->UpdateRuleStats(
7155 "cumulative: removed intervals with a size of zero");
7156 }
7157 if (num_incompatible_intervals > 0) {
7158 context_->UpdateRuleStats(
7159 "cumulative: removed intervals that can't be performed");
7160 }
7161 }
7162
7163 // Checks the compatibility of demands w.r.t. the capacity.
7164 {
7165 for (int i = 0; i < proto->demands_size(); ++i) {
7166 const int interval = proto->intervals(i);
7167 const LinearExpressionProto& demand_expr = proto->demands(i);
7168 if (context_->ConstraintIsOptional(interval)) continue;
7169 if (context_->SizeMin(interval) <= 0) continue;
7170 bool domain_changed = false;
7171 if (!context_->IntersectDomainWith(demand_expr, {0, capacity_max},
7172 &domain_changed)) {
7173 return true;
7174 }
7175 if (domain_changed) {
7176 context_->UpdateRuleStats(
7177 "cumulative: fit demand in [0..capacity_max]");
7178 }
7179 }
7180 }
7181
7182 // Split constraints in disjoint sets.
7183 //
7184 // TODO(user): This can be improved:
7185 // If we detect bridge nodes in the graph of overlapping components, we
7186 // can split the graph around the bridge and add the bridge node to both
7187 // side. Note that if it we take into account precedences between intervals,
7188 // we can detect more bridges.
7189 if (proto->intervals_size() > 1) {
7190 std::vector<IndexedInterval> indexed_intervals;
7191 for (int i = 0; i < proto->intervals().size(); ++i) {
7192 const int index = proto->intervals(i);
7193 indexed_intervals.push_back({i, IntegerValue(context_->StartMin(index)),
7194 IntegerValue(context_->EndMax(index))});
7195 }
7196 std::vector<std::vector<int>> components;
7197 GetOverlappingIntervalComponents(&indexed_intervals, &components);
7198
7199 if (components.size() > 1) {
7200 for (const std::vector<int>& component : components) {
7201 CumulativeConstraintProto* new_cumulative =
7202 context_->working_model->add_constraints()->mutable_cumulative();
7203 for (const int i : component) {
7204 new_cumulative->add_intervals(proto->intervals(i));
7205 *new_cumulative->add_demands() = proto->demands(i);
7206 }
7207 *new_cumulative->mutable_capacity() = proto->capacity();
7208 }
7209 context_->UpdateNewConstraintsVariableUsage();
7210 context_->UpdateRuleStats("cumulative: split into disjoint components");
7211 return RemoveConstraint(ct);
7212 }
7213 }
7214
7215 // TODO(user): move the algorithmic part of what we do below in a
7216 // separate function to unit test it more properly.
7217 {
7218 // Build max load profiles.
7219 absl::btree_map<int64_t, int64_t> time_to_demand_deltas;
7220 const int64_t capacity_min = context_->MinOf(proto->capacity());
7221 for (int i = 0; i < proto->intervals_size(); ++i) {
7222 const int interval_index = proto->intervals(i);
7223 const int64_t demand_max = context_->MaxOf(proto->demands(i));
7224 time_to_demand_deltas[context_->StartMin(interval_index)] += demand_max;
7225 time_to_demand_deltas[context_->EndMax(interval_index)] -= demand_max;
7226 }
7227
7228 // We construct the profile which correspond to a set of [time, next_time)
7229 // to max_profile height. And for each time in our discrete set of
7230 // time_exprs (all the start_min and end_max) we count for how often the
7231 // height was above the capacity before this time.
7232 //
7233 // This rely on the iteration in sorted order.
7234 int num_possible_overloads = 0;
7235 int64_t current_load = 0;
7236 absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before;
7237 for (const auto& it : time_to_demand_deltas) {
7238 num_possible_overloads_before[it.first] = num_possible_overloads;
7239 current_load += it.second;
7240 if (current_load > capacity_min) {
7241 ++num_possible_overloads;
7242 }
7243 }
7244 CHECK_EQ(current_load, 0);
7245
7246 // No possible overload with the min capacity.
7247 if (num_possible_overloads == 0) {
7248 context_->UpdateRuleStats(
7249 "cumulative: max profile is always under the min capacity");
7250 return RemoveConstraint(ct);
7251 }
7252
7253 // An interval that does not intersect with the potential_overload_domains
7254 // cannot contribute to a conflict. We can safely remove them.
7255 //
7256 // This is an extension of the presolve rule from
7257 // "Presolving techniques and linear relaxations for cumulative
7258 // scheduling" PhD dissertation by Stefan Heinz, ZIB.
7259 int new_size = 0;
7260 for (int i = 0; i < proto->intervals_size(); ++i) {
7261 const int index = proto->intervals(i);
7262 const int64_t start_min = context_->StartMin(index);
7263 const int64_t end_max = context_->EndMax(index);
7264
7265 // In the cumulative, if start_min == end_max, the interval is of size
7266 // zero and we can just ignore it. If the model is unsat or the interval
7267 // must be absent (start_min > end_max), this should be dealt with at
7268 // the interval constraint level and we can just remove it from here.
7269 //
7270 // Note that currently, the interpretation for interval of length zero
7271 // is different for the no-overlap constraint.
7272 if (start_min >= end_max) continue;
7273
7274 // Note that by construction, both point are in the map. The formula
7275 // counts exactly for how many time_exprs in [start_min, end_max), we have
7276 // a point in our discrete set of time that exceeded the capacity. Because
7277 // we included all the relevant points, this works.
7278 const int num_diff = num_possible_overloads_before.at(end_max) -
7279 num_possible_overloads_before.at(start_min);
7280 if (num_diff == 0) continue;
7281
7282 proto->set_intervals(new_size, proto->intervals(i));
7283 *proto->mutable_demands(new_size) = proto->demands(i);
7284 new_size++;
7285 }
7286
7287 if (new_size < proto->intervals_size()) {
7288 changed = true;
7289 proto->mutable_intervals()->Truncate(new_size);
7290 proto->mutable_demands()->erase(
7291 proto->mutable_demands()->begin() + new_size,
7292 proto->mutable_demands()->end());
7293 context_->UpdateRuleStats(
7294 "cumulative: remove never conflicting intervals");
7295 }
7296 }
7297
7298 if (proto->intervals().empty()) {
7299 context_->UpdateRuleStats("cumulative: no intervals");
7300 return RemoveConstraint(ct);
7301 }
7302
7303 {
7304 int64_t max_of_performed_demand_mins = 0;
7305 int64_t sum_of_max_demands = 0;
7306 for (int i = 0; i < proto->intervals_size(); ++i) {
7307 const int interval_index = proto->intervals(i);
7308 const ConstraintProto& interval_ct =
7309 context_->working_model->constraints(interval_index);
7310
7311 const LinearExpressionProto& demand_expr = proto->demands(i);
7312 sum_of_max_demands += context_->MaxOf(demand_expr);
7313
7314 if (interval_ct.enforcement_literal().empty() &&
7315 context_->SizeMin(interval_index) > 0) {
7316 max_of_performed_demand_mins = std::max(max_of_performed_demand_mins,
7317 context_->MinOf(demand_expr));
7318 }
7319 }
7320
7321 const LinearExpressionProto& capacity_expr = proto->capacity();
7322 if (max_of_performed_demand_mins > context_->MinOf(capacity_expr)) {
7323 context_->UpdateRuleStats("cumulative: propagate min capacity");
7324 if (!context_->IntersectDomainWith(
7325 capacity_expr, Domain(max_of_performed_demand_mins,
7326 std::numeric_limits<int64_t>::max()))) {
7327 return true;
7328 }
7329 }
7330
7331 if (max_of_performed_demand_mins > context_->MaxOf(capacity_expr)) {
7332 context_->UpdateRuleStats("cumulative: cannot fit performed demands");
7333 return context_->NotifyThatModelIsUnsat();
7334 }
7335
7336 if (sum_of_max_demands <= context_->MinOf(capacity_expr)) {
7337 context_->UpdateRuleStats("cumulative: capacity exceeds sum of demands");
7338 return RemoveConstraint(ct);
7339 }
7340 }
7341
7342 if (context_->IsFixed(proto->capacity())) {
7343 int64_t gcd = 0;
7344 for (int i = 0; i < ct->cumulative().demands_size(); ++i) {
7345 const LinearExpressionProto& demand_expr = ct->cumulative().demands(i);
7346 if (!context_->IsFixed(demand_expr)) {
7347 // Abort if the demand is not fixed.
7348 gcd = 1;
7349 break;
7350 }
7351 gcd = std::gcd(gcd, context_->MinOf(demand_expr));
7352 if (gcd == 1) break;
7353 }
7354 if (gcd > 1) {
7355 changed = true;
7356 for (int i = 0; i < ct->cumulative().demands_size(); ++i) {
7357 const int64_t demand = context_->MinOf(ct->cumulative().demands(i));
7358 *proto->mutable_demands(i) = ConstantExpressionProto(demand / gcd);
7359 }
7360
7361 const int64_t old_capacity = context_->MinOf(proto->capacity());
7362 *proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd);
7363 context_->UpdateRuleStats(
7364 "cumulative: divide demands and capacity by gcd");
7365 }
7366 }
7367
7368 const int num_intervals = proto->intervals_size();
7369 const LinearExpressionProto& capacity_expr = proto->capacity();
7370
7371 std::vector<LinearExpressionProto> start_exprs(num_intervals);
7372
7373 int num_duration_one = 0;
7374 int num_greater_half_capacity = 0;
7375
7376 bool has_optional_interval = false;
7377 for (int i = 0; i < num_intervals; ++i) {
7378 const int index = proto->intervals(i);
7379 // TODO(user): adapt in the presence of optional intervals.
7380 if (context_->ConstraintIsOptional(index)) has_optional_interval = true;
7381 const ConstraintProto& ct =
7382 context_->working_model->constraints(proto->intervals(i));
7383 const IntervalConstraintProto& interval = ct.interval();
7384 start_exprs[i] = interval.start();
7385
7386 const LinearExpressionProto& demand_expr = proto->demands(i);
7387 if (context_->SizeMin(index) == 1 && context_->SizeMax(index) == 1) {
7388 num_duration_one++;
7389 }
7390 if (context_->SizeMin(index) <= 0) {
7391 // The behavior for zero-duration interval is currently not the same in
7392 // the no-overlap and the cumulative constraint.
7393 return changed;
7394 }
7395
7396 const int64_t demand_min = context_->MinOf(demand_expr);
7397 const int64_t demand_max = context_->MaxOf(demand_expr);
7398 if (demand_min > capacity_max / 2) {
7399 num_greater_half_capacity++;
7400 }
7401 if (demand_min > capacity_max) {
7402 context_->UpdateRuleStats("cumulative: demand_min exceeds capacity max");
7403 if (!context_->ConstraintIsOptional(index)) {
7404 return context_->NotifyThatModelIsUnsat();
7405 } else {
7406 CHECK_EQ(ct.enforcement_literal().size(), 1);
7407 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
7408 return true;
7409 }
7410 }
7411 return changed;
7412 } else if (demand_max > capacity_max) {
7413 if (ct.enforcement_literal().empty()) {
7414 context_->UpdateRuleStats(
7415 "cumulative: demand_max exceeds capacity max");
7416 if (!context_->IntersectDomainWith(
7417 demand_expr,
7418 Domain(std::numeric_limits<int64_t>::min(), capacity_max))) {
7419 return true;
7420 }
7421 } else {
7422 // TODO(user): we abort because we cannot convert this to a no_overlap
7423 // for instance.
7424 context_->UpdateRuleStats(
7425 "cumulative: demand_max of optional interval exceeds capacity");
7426 return changed;
7427 }
7428 }
7429 }
7430 if (num_greater_half_capacity == num_intervals) {
7431 if (num_duration_one == num_intervals && !has_optional_interval) {
7432 context_->UpdateRuleStats("cumulative: convert to all_different");
7433 ConstraintProto* new_ct = context_->working_model->add_constraints();
7434 auto* arg = new_ct->mutable_all_diff();
7435 for (const LinearExpressionProto& expr : start_exprs) {
7436 *arg->add_exprs() = expr;
7437 }
7438 if (!context_->IsFixed(capacity_expr)) {
7439 const int64_t capacity_min = context_->MinOf(capacity_expr);
7440 for (const LinearExpressionProto& expr : proto->demands()) {
7441 if (capacity_min >= context_->MaxOf(expr)) continue;
7442 LinearConstraintProto* fit =
7443 context_->working_model->add_constraints()->mutable_linear();
7444 fit->add_domain(0);
7445 fit->add_domain(std::numeric_limits<int64_t>::max());
7446 AddLinearExpressionToLinearConstraint(capacity_expr, 1, fit);
7448 }
7449 }
7450 context_->UpdateNewConstraintsVariableUsage();
7451 return RemoveConstraint(ct);
7452 } else {
7453 context_->UpdateRuleStats("cumulative: convert to no_overlap");
7454 // Before we remove the cumulative, add constraints to enforce that the
7455 // capacity is greater than the demand of any performed intervals.
7456 for (int i = 0; i < proto->demands_size(); ++i) {
7457 const LinearExpressionProto& demand_expr = proto->demands(i);
7458 const int64_t demand_max = context_->MaxOf(demand_expr);
7459 if (demand_max > context_->MinOf(capacity_expr)) {
7460 ConstraintProto* capacity_gt =
7461 context_->working_model->add_constraints();
7462 *capacity_gt->mutable_enforcement_literal() =
7463 context_->working_model->constraints(proto->intervals(i))
7464 .enforcement_literal();
7465 capacity_gt->mutable_linear()->add_domain(0);
7466 capacity_gt->mutable_linear()->add_domain(
7467 std::numeric_limits<int64_t>::max());
7469 capacity_gt->mutable_linear());
7471 capacity_gt->mutable_linear());
7472 }
7473 }
7474
7475 ConstraintProto* new_ct = context_->working_model->add_constraints();
7476 auto* arg = new_ct->mutable_no_overlap();
7477 for (const int interval : proto->intervals()) {
7478 arg->add_intervals(interval);
7479 }
7480 context_->UpdateNewConstraintsVariableUsage();
7481 return RemoveConstraint(ct);
7482 }
7483 }
7484
7485 RunPropagatorsForConstraint(*ct);
7486 return changed;
7487}
7488
7489bool CpModelPresolver::PresolveRoutes(ConstraintProto* ct) {
7490 if (context_->ModelIsUnsat()) return false;
7491 // TODO(user): add support for this case.
7492 if (HasEnforcementLiteral(*ct)) return false;
7493 RoutesConstraintProto& proto = *ct->mutable_routes();
7494
7495 const int old_size = proto.literals_size();
7496 int new_size = 0;
7497 std::vector<bool> has_incoming_or_outgoing_arcs;
7498 const int num_arcs = proto.literals_size();
7499 for (int i = 0; i < num_arcs; ++i) {
7500 const int ref = proto.literals(i);
7501 const int tail = proto.tails(i);
7502 const int head = proto.heads(i);
7503
7504 if (tail >= has_incoming_or_outgoing_arcs.size()) {
7505 has_incoming_or_outgoing_arcs.resize(tail + 1, false);
7506 }
7507 if (head >= has_incoming_or_outgoing_arcs.size()) {
7508 has_incoming_or_outgoing_arcs.resize(head + 1, false);
7509 }
7510
7511 if (context_->LiteralIsFalse(ref)) {
7512 context_->UpdateRuleStats("routes: removed false arcs");
7513 continue;
7514 }
7515 proto.set_literals(new_size, ref);
7516 proto.set_tails(new_size, tail);
7517 proto.set_heads(new_size, head);
7518 ++new_size;
7519 has_incoming_or_outgoing_arcs[tail] = true;
7520 has_incoming_or_outgoing_arcs[head] = true;
7521 }
7522
7523 if (old_size > 0 && new_size == 0) {
7524 // A routes constraint cannot have a self loop on 0. Therefore, if there
7525 // were arcs, it means it contains non zero nodes. Without arc, the
7526 // constraint is unfeasible.
7527 return context_->NotifyThatModelIsUnsat(
7528 "routes: graph with nodes and no arcs");
7529 }
7530
7531 // if a node misses an incomping or outgoing arc, the model is trivially
7532 // infeasible.
7533 for (int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) {
7534 if (!has_incoming_or_outgoing_arcs[n]) {
7535 return context_->NotifyThatModelIsUnsat(absl::StrCat(
7536 "routes: node ", n, " misses incoming or outgoing arcs"));
7537 }
7538 }
7539
7540 if (new_size < num_arcs) {
7541 proto.mutable_literals()->Truncate(new_size);
7542 proto.mutable_tails()->Truncate(new_size);
7543 proto.mutable_heads()->Truncate(new_size);
7544 return true;
7545 }
7546
7547 RunPropagatorsForConstraint(*ct);
7548 return false;
7549}
7550
7551bool CpModelPresolver::PresolveCircuit(ConstraintProto* ct) {
7552 if (context_->ModelIsUnsat()) return false;
7553 // TODO(user): add support for this case.
7554 if (HasEnforcementLiteral(*ct)) return false;
7555 CircuitConstraintProto& proto = *ct->mutable_circuit();
7556
7557 // The indexing might not be dense, so fix that first.
7558 ReindexArcs(ct->mutable_circuit()->mutable_tails(),
7559 ct->mutable_circuit()->mutable_heads());
7560
7561 // Convert the flat structure to a graph, note that we includes all the arcs
7562 // here (even if they are at false).
7563 std::vector<std::vector<int>> incoming_arcs;
7564 std::vector<std::vector<int>> outgoing_arcs;
7565 int num_nodes = 0;
7566 const int num_arcs = proto.literals_size();
7567 for (int i = 0; i < num_arcs; ++i) {
7568 const int ref = proto.literals(i);
7569 const int tail = proto.tails(i);
7570 const int head = proto.heads(i);
7571 num_nodes = std::max(num_nodes, std::max(tail, head) + 1);
7572 if (std::max(tail, head) >= incoming_arcs.size()) {
7573 incoming_arcs.resize(std::max(tail, head) + 1);
7574 outgoing_arcs.resize(std::max(tail, head) + 1);
7575 }
7576 incoming_arcs[head].push_back(ref);
7577 outgoing_arcs[tail].push_back(ref);
7578 }
7579
7580 // All the node must have some incoming and outgoing arcs.
7581 for (int i = 0; i < num_nodes; ++i) {
7582 if (incoming_arcs[i].empty() || outgoing_arcs[i].empty()) {
7583 return MarkConstraintAsFalse(ct, "circuit: node with no arcs");
7584 }
7585 }
7586
7587 // Note that it is important to reach the fixed point here:
7588 // One arc at true, then all other arc at false. This is because we rely
7589 // on this in case the circuit is fully specified below.
7590 //
7591 // TODO(user): Use a better complexity if needed.
7592 bool loop_again = true;
7593 int num_fixed_at_true = 0;
7594 while (loop_again) {
7595 loop_again = false;
7596 for (const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
7597 for (const std::vector<int>& refs : *node_to_refs) {
7598 if (refs.size() == 1) {
7599 if (!context_->LiteralIsTrue(refs.front())) {
7600 ++num_fixed_at_true;
7601 if (!context_->SetLiteralToTrue(refs.front())) return true;
7602 }
7603 continue;
7604 }
7605
7606 // At most one true, so if there is one, mark all the other to false.
7607 int num_true = 0;
7608 int true_ref;
7609 for (const int ref : refs) {
7610 if (context_->LiteralIsTrue(ref)) {
7611 ++num_true;
7612 true_ref = ref;
7613 break;
7614 }
7615 }
7616 if (num_true > 1) {
7617 return context_->NotifyThatModelIsUnsat();
7618 }
7619 if (num_true == 1) {
7620 for (const int ref : refs) {
7621 if (ref != true_ref) {
7622 if (!context_->IsFixed(ref)) {
7623 context_->UpdateRuleStats("circuit: set literal to false");
7624 loop_again = true;
7625 }
7626 if (!context_->SetLiteralToFalse(ref)) return true;
7627 }
7628 }
7629 }
7630 }
7631 }
7632 }
7633 if (num_fixed_at_true > 0) {
7634 context_->UpdateRuleStats("circuit: fixed singleton arcs");
7635 }
7636
7637 // Remove false arcs.
7638 int new_size = 0;
7639 int num_true = 0;
7640 int circuit_start = -1;
7641 std::vector<int> next(num_nodes, -1);
7642 std::vector<int> new_in_degree(num_nodes, 0);
7643 std::vector<int> new_out_degree(num_nodes, 0);
7644 for (int i = 0; i < num_arcs; ++i) {
7645 const int ref = proto.literals(i);
7646 if (context_->LiteralIsFalse(ref)) continue;
7647 if (context_->LiteralIsTrue(ref)) {
7648 if (next[proto.tails(i)] != -1) {
7649 return context_->NotifyThatModelIsUnsat();
7650 }
7651 next[proto.tails(i)] = proto.heads(i);
7652 if (proto.tails(i) != proto.heads(i)) {
7653 circuit_start = proto.tails(i);
7654 }
7655 ++num_true;
7656 }
7657 ++new_out_degree[proto.tails(i)];
7658 ++new_in_degree[proto.heads(i)];
7659 proto.set_tails(new_size, proto.tails(i));
7660 proto.set_heads(new_size, proto.heads(i));
7661 proto.set_literals(new_size, ref);
7662 ++new_size;
7663 }
7664
7665 // Detect infeasibility due to a node having no more incoming or outgoing arc.
7666 // This is a bit tricky because for now the meaning of the constraint says
7667 // that all nodes that appear in at least one of the arcs must be in the
7668 // circuit or have a self-arc. So if any such node ends up with an incoming or
7669 // outgoing degree of zero once we remove false arcs then the constraint is
7670 // infeasible!
7671 for (int i = 0; i < num_nodes; ++i) {
7672 if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
7673 return context_->NotifyThatModelIsUnsat();
7674 }
7675 }
7676
7677 // Test if a subcircuit is already present.
7678 if (circuit_start != -1) {
7679 std::vector<bool> visited(num_nodes, false);
7680 int current = circuit_start;
7681 while (current != -1 && !visited[current]) {
7682 visited[current] = true;
7683 current = next[current];
7684 }
7685 if (current == circuit_start) {
7686 // We have a sub-circuit! mark all other arc false except self-loop not in
7687 // circuit.
7688 std::vector<bool> has_self_arc(num_nodes, false);
7689 for (int i = 0; i < num_arcs; ++i) {
7690 if (visited[proto.tails(i)]) continue;
7691 if (proto.tails(i) == proto.heads(i)) {
7692 has_self_arc[proto.tails(i)] = true;
7693 if (!context_->SetLiteralToTrue(proto.literals(i))) return true;
7694 } else {
7695 if (!context_->SetLiteralToFalse(proto.literals(i))) return true;
7696 }
7697 }
7698 for (int n = 0; n < num_nodes; ++n) {
7699 if (!visited[n] && !has_self_arc[n]) {
7700 // We have a subircuit, but it doesn't cover all the mandatory nodes.
7701 return MarkConstraintAsFalse(
7702 ct, "circuit: non-covering fixed subcircuit");
7703 }
7704 }
7705 context_->UpdateRuleStats("circuit: fully specified");
7706 return RemoveConstraint(ct);
7707 }
7708 } else {
7709 // All self loop?
7710 if (num_true == new_size) {
7711 context_->UpdateRuleStats("circuit: empty circuit");
7712 return RemoveConstraint(ct);
7713 }
7714 }
7715
7716 // Look for in/out-degree of two, this will imply that one of the indicator
7717 // Boolean is equal to the negation of the other.
7718 for (int i = 0; i < num_nodes; ++i) {
7719 for (const std::vector<int>* arc_literals :
7720 {&incoming_arcs[i], &outgoing_arcs[i]}) {
7721 std::vector<int> literals;
7722 for (const int ref : *arc_literals) {
7723 if (context_->LiteralIsFalse(ref)) continue;
7724 if (context_->LiteralIsTrue(ref)) {
7725 literals.clear();
7726 break;
7727 }
7728 literals.push_back(ref);
7729 }
7730 if (literals.size() == 2 && literals[0] != NegatedRef(literals[1])) {
7731 context_->UpdateRuleStats("circuit: degree 2");
7732 if (!context_->StoreBooleanEqualityRelation(literals[0],
7733 NegatedRef(literals[1]))) {
7734 return true;
7735 }
7736 }
7737 }
7738 }
7739
7740 // Truncate the circuit and return.
7741 if (new_size < num_arcs) {
7742 proto.mutable_tails()->Truncate(new_size);
7743 proto.mutable_heads()->Truncate(new_size);
7744 proto.mutable_literals()->Truncate(new_size);
7745 context_->UpdateRuleStats("circuit: removed false arcs");
7746 return true;
7747 }
7748 RunPropagatorsForConstraint(*ct);
7749 return false;
7750}
7751
7752bool CpModelPresolver::PresolveAutomaton(ConstraintProto* ct) {
7753 if (context_->ModelIsUnsat()) return false;
7754 // TODO(user): add support for this case.
7755 if (HasEnforcementLiteral(*ct)) return false;
7756
7757 AutomatonConstraintProto* proto = ct->mutable_automaton();
7758 if (proto->exprs_size() == 0 || proto->transition_label_size() == 0) {
7759 return false;
7760 }
7761
7762 bool changed = false;
7763 for (int i = 0; i < proto->exprs_size(); ++i) {
7764 changed |= CanonicalizeLinearExpression(*ct, proto->mutable_exprs(i));
7765 }
7766
7767 std::vector<absl::flat_hash_set<int64_t>> reachable_states;
7768 std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
7769 PropagateAutomaton(*proto, *context_, &reachable_states, &reachable_labels);
7770
7771 // Filter domains and compute the union of all relevant labels.
7772 for (int time = 0; time < reachable_labels.size(); ++time) {
7773 const LinearExpressionProto& expr = proto->exprs(time);
7774 if (context_->IsFixed(expr)) {
7775 if (!reachable_labels[time].contains(context_->FixedValue(expr))) {
7776 return MarkConstraintAsFalse(ct, "automaton: unsat");
7777 }
7778 } else {
7779 std::vector<int64_t> unscaled_reachable_labels;
7780 for (const int64_t label : reachable_labels[time]) {
7781 unscaled_reachable_labels.push_back(GetInnerVarValue(expr, label));
7782 }
7783 bool removed_values = false;
7784 if (!context_->IntersectDomainWith(
7785 expr.vars(0), Domain::FromValues(unscaled_reachable_labels),
7786 &removed_values)) {
7787 return true;
7788 }
7789 if (removed_values) {
7790 context_->UpdateRuleStats("automaton: reduce variable domain");
7791 }
7792 }
7793 }
7794
7795 return changed;
7796}
7797
7798bool CpModelPresolver::PresolveReservoir(ConstraintProto* ct) {
7799 if (context_->ModelIsUnsat()) return false;
7800 // TODO(user): add support for this case.
7801 if (HasEnforcementLiteral(*ct)) return false;
7802
7803 ReservoirConstraintProto& proto = *ct->mutable_reservoir();
7804 bool changed = false;
7805 for (LinearExpressionProto& exp : *(proto.mutable_time_exprs())) {
7806 changed |= CanonicalizeLinearExpression(*ct, &exp);
7807 }
7808 for (LinearExpressionProto& exp : *(proto.mutable_level_changes())) {
7809 changed |= CanonicalizeLinearExpression(*ct, &exp);
7810 }
7811
7812 if (proto.active_literals().empty()) {
7813 const int true_literal = context_->GetTrueLiteral();
7814 for (int i = 0; i < proto.time_exprs_size(); ++i) {
7815 proto.add_active_literals(true_literal);
7816 }
7817 changed = true;
7818 }
7819
7820 const auto& demand_is_null = [&](int i) {
7821 return (context_->IsFixed(proto.level_changes(i)) &&
7822 context_->FixedValue(proto.level_changes(i)) == 0) ||
7823 context_->LiteralIsFalse(proto.active_literals(i));
7824 };
7825
7826 // Remove zero level_changes, and inactive events.
7827 int num_zeros = 0;
7828 for (int i = 0; i < proto.level_changes_size(); ++i) {
7829 if (demand_is_null(i)) num_zeros++;
7830 }
7831
7832 if (num_zeros > 0) { // Remove null events
7833 changed = true;
7834 int new_size = 0;
7835 for (int i = 0; i < proto.level_changes_size(); ++i) {
7836 if (demand_is_null(i)) continue;
7837 *proto.mutable_level_changes(new_size) = proto.level_changes(i);
7838 *proto.mutable_time_exprs(new_size) = proto.time_exprs(i);
7839 proto.set_active_literals(new_size, proto.active_literals(i));
7840 new_size++;
7841 }
7842
7843 proto.mutable_level_changes()->erase(
7844 proto.mutable_level_changes()->begin() + new_size,
7845 proto.mutable_level_changes()->end());
7846 proto.mutable_time_exprs()->erase(
7847 proto.mutable_time_exprs()->begin() + new_size,
7848 proto.mutable_time_exprs()->end());
7849 proto.mutable_active_literals()->Truncate(new_size);
7850
7851 context_->UpdateRuleStats(
7852 "reservoir: remove zero level_changes or inactive events");
7853 }
7854
7855 // The rest of the presolve only applies if all demands are fixed.
7856 for (const LinearExpressionProto& level_change : proto.level_changes()) {
7857 if (!context_->IsFixed(level_change)) return changed;
7858 }
7859
7860 const int num_events = proto.level_changes_size();
7861 int64_t gcd = proto.level_changes().empty()
7862 ? 0
7863 : std::abs(context_->FixedValue(proto.level_changes(0)));
7864 int num_positives = 0;
7865 int num_negatives = 0;
7866 int64_t max_sum_of_positive_level_changes = 0;
7867 int64_t min_sum_of_negative_level_changes = 0;
7868 for (int i = 0; i < num_events; ++i) {
7869 const int64_t demand = context_->FixedValue(proto.level_changes(i));
7870 gcd = std::gcd(gcd, std::abs(demand));
7871 if (demand > 0) {
7872 num_positives++;
7873 max_sum_of_positive_level_changes += demand;
7874 } else {
7875 DCHECK_LT(demand, 0);
7876 num_negatives++;
7877 min_sum_of_negative_level_changes += demand;
7878 }
7879 }
7880
7881 if (min_sum_of_negative_level_changes >= proto.min_level() &&
7882 max_sum_of_positive_level_changes <= proto.max_level()) {
7883 context_->UpdateRuleStats("reservoir: always feasible");
7884 return RemoveConstraint(ct);
7885 }
7886
7887 if (min_sum_of_negative_level_changes > proto.max_level() ||
7888 max_sum_of_positive_level_changes < proto.min_level()) {
7889 context_->UpdateRuleStats("reservoir: trivially infeasible");
7890 return context_->NotifyThatModelIsUnsat();
7891 }
7892
7893 if (min_sum_of_negative_level_changes > proto.min_level()) {
7894 proto.set_min_level(min_sum_of_negative_level_changes);
7895 context_->UpdateRuleStats(
7896 "reservoir: increase min_level to reachable value");
7897 }
7898
7899 if (max_sum_of_positive_level_changes < proto.max_level()) {
7900 proto.set_max_level(max_sum_of_positive_level_changes);
7901 context_->UpdateRuleStats("reservoir: reduce max_level to reachable value");
7902 }
7903
7904 if (proto.min_level() <= 0 && proto.max_level() >= 0 &&
7905 (num_positives == 0 || num_negatives == 0)) {
7906 // If all level_changes have the same sign, and if the initial state is
7907 // always feasible, we do not care about the order, just the sum.
7908 auto* const sum_ct = context_->working_model->add_constraints();
7909 auto* const sum = sum_ct->mutable_linear();
7910 int64_t fixed_contrib = 0;
7911 for (int i = 0; i < proto.level_changes_size(); ++i) {
7912 const int64_t demand = context_->FixedValue(proto.level_changes(i));
7913 DCHECK_NE(demand, 0);
7914
7915 const int active = proto.active_literals(i);
7916 if (RefIsPositive(active)) {
7917 sum->add_vars(active);
7918 sum->add_coeffs(demand);
7919 } else {
7920 sum->add_vars(PositiveRef(active));
7921 sum->add_coeffs(-demand);
7922 fixed_contrib += demand;
7923 }
7924 }
7925 sum->add_domain(proto.min_level() - fixed_contrib);
7926 sum->add_domain(proto.max_level() - fixed_contrib);
7927 context_->UpdateRuleStats("reservoir: converted to linear");
7928 bool changed = false;
7929 if (!CanonicalizeLinear(sum_ct, &changed)) {
7930 return true;
7931 }
7932 return RemoveConstraint(ct);
7933 }
7934
7935 if (gcd > 1) {
7936 for (int i = 0; i < proto.level_changes_size(); ++i) {
7937 proto.mutable_level_changes(i)->set_offset(
7938 context_->FixedValue(proto.level_changes(i)) / gcd);
7939 proto.mutable_level_changes(i)->clear_vars();
7940 proto.mutable_level_changes(i)->clear_coeffs();
7941 }
7942
7943 // Adjust min and max levels.
7944 // max level is always rounded down.
7945 // min level is always rounded up.
7946 const Domain reduced_domain = Domain({proto.min_level(), proto.max_level()})
7947 .InverseMultiplicationBy(gcd);
7948 proto.set_min_level(reduced_domain.Min());
7949 proto.set_max_level(reduced_domain.Max());
7950 context_->UpdateRuleStats(
7951 "reservoir: simplify level_changes and levels by gcd");
7952 }
7953
7954 if (num_positives == 1 && num_negatives > 0) {
7955 context_->UpdateRuleStats(
7956 "TODO reservoir: one producer, multiple consumers");
7957 }
7958
7959 absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set;
7960 for (int i = 0; i < proto.level_changes_size(); ++i) {
7961 const LinearExpressionProto& time = proto.time_exprs(i);
7962 const int var = context_->IsFixed(time) ? std::numeric_limits<int>::min()
7963 : time.vars(0);
7964 const int64_t coeff = context_->IsFixed(time) ? 0 : time.coeffs(0);
7965 const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple(
7966 var, coeff,
7967 context_->IsFixed(time) ? context_->FixedValue(time) : time.offset(),
7968 proto.active_literals(i));
7969 if (time_active_set.contains(key)) {
7970 context_->UpdateRuleStats("TODO reservoir: merge synchronized events");
7971 break;
7972 } else {
7973 time_active_set.insert(key);
7974 }
7975 }
7976
7977 RunPropagatorsForConstraint(*ct);
7978 return changed;
7979}
7980
7981// TODO(user): It is probably more efficient to keep all the bool_and in a
7982// global place during all the presolve, and just output them at the end
7983// rather than modifying more than once the proto.
7984void CpModelPresolver::ConvertToBoolAnd() {
7985 absl::flat_hash_map<int, int> ref_to_bool_and;
7986 const int num_constraints = context_->working_model->constraints_size();
7987 std::vector<int> to_remove;
7988 for (int c = 0; c < num_constraints; ++c) {
7989 const ConstraintProto& ct = context_->working_model->constraints(c);
7990 if (HasEnforcementLiteral(ct)) continue;
7991
7992 if (ct.constraint_case() == ConstraintProto::kBoolOr &&
7993 ct.bool_or().literals().size() == 2) {
7994 AddImplication(NegatedRef(ct.bool_or().literals(0)),
7995 ct.bool_or().literals(1), context_->working_model,
7996 &ref_to_bool_and);
7997 to_remove.push_back(c);
7998 continue;
7999 }
8000
8001 if (ct.constraint_case() == ConstraintProto::kAtMostOne &&
8002 ct.at_most_one().literals().size() == 2) {
8003 AddImplication(ct.at_most_one().literals(0),
8004 NegatedRef(ct.at_most_one().literals(1)),
8005 context_->working_model, &ref_to_bool_and);
8006 to_remove.push_back(c);
8007 continue;
8008 }
8009 }
8010
8011 context_->UpdateNewConstraintsVariableUsage();
8012 for (const int c : to_remove) {
8013 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8014 CHECK(RemoveConstraint(ct));
8015 context_->UpdateConstraintVariableUsage(c);
8016 }
8017}
8018
8019void CpModelPresolver::RunPropagatorsForConstraint(const ConstraintProto& ct) {
8020 if (context_->ModelIsUnsat()) return;
8021
8022 Model model;
8023
8024 // Enable as many propagators as possible. We do not care if some propagator
8025 // is a bit slow or if the explanation is too big: anything that improves our
8026 // bounds is an improvement.
8027 SatParameters local_params;
8028 local_params.set_use_try_edge_reasoning_in_no_overlap_2d(true);
8029 local_params.set_exploit_all_precedences(true);
8030 local_params.set_use_hard_precedences_in_cumulative(true);
8031 local_params.set_max_num_intervals_for_timetable_edge_finding(1000);
8032 local_params.set_use_overload_checker_in_cumulative(true);
8033 local_params.set_use_strong_propagation_in_disjunctive(true);
8034 local_params.set_use_timetable_edge_finding_in_cumulative(true);
8035 local_params.set_max_pairs_pairwise_reasoning_in_no_overlap_2d(50000);
8036 local_params.set_use_timetabling_in_no_overlap_2d(true);
8037 local_params.set_use_energetic_reasoning_in_no_overlap_2d(true);
8038 local_params.set_use_area_energetic_reasoning_in_no_overlap_2d(true);
8039 local_params.set_use_conservative_scale_overload_checker(true);
8040 local_params.set_use_dual_scheduling_heuristics(true);
8041
8042 model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
8043 std::vector<int> variable_mapping;
8044 CreateValidModelWithSingleConstraint(ct, context_, &variable_mapping,
8045 &tmp_model_);
8046 DCHECK_EQ(ValidateCpModel(tmp_model_, false), "");
8047 if (!LoadModelForPresolve(tmp_model_, std::move(local_params), context_,
8048 &model, "single constraint")) {
8049 return;
8050 }
8051
8052 time_limit_->AdvanceDeterministicTime(
8053 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
8054 auto* mapping = model.GetOrCreate<CpModelMapping>();
8055 auto* integer_trail = model.GetOrCreate<IntegerTrail>();
8056 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
8057 auto* trail = model.GetOrCreate<Trail>();
8058
8059 int num_changed_bounds = 0;
8060 int num_fixed_bools = 0;
8061 for (int var = 0; var < variable_mapping.size(); ++var) {
8062 const int proto_var = variable_mapping[var];
8063 if (mapping->IsBoolean(var)) {
8064 const Literal l = mapping->Literal(var);
8065 if (trail->Assignment().LiteralIsFalse(l)) {
8066 if (!context_->SetLiteralToFalse(proto_var)) return;
8067 ++num_fixed_bools;
8068 continue;
8069 } else if (trail->Assignment().LiteralIsTrue(l)) {
8070 if (!context_->SetLiteralToTrue(proto_var)) return;
8071 ++num_fixed_bools;
8072 continue;
8073 }
8074 // Add Boolean equivalence relations.
8075 const Literal r = implication_graph->RepresentativeOf(l);
8076 if (r != l) {
8077 const int r_var =
8078 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
8079 if (r_var < 0) continue;
8080 if (!context_->StoreBooleanEqualityRelation(
8081 proto_var, r.IsPositive() ? r_var : NegatedRef(r_var))) {
8082 return;
8083 }
8084 }
8085 } else {
8086 // Restrict variable domain.
8087 bool changed = false;
8088 if (!context_->IntersectDomainWith(
8089 proto_var,
8090 integer_trail->InitialVariableDomain(mapping->Integer(var)),
8091 &changed)) {
8092 return;
8093 }
8094 if (changed) ++num_changed_bounds;
8095 }
8096 }
8097 if (num_changed_bounds > 0) {
8098 context_->UpdateRuleStats("propagators: changed bounds",
8099 num_changed_bounds);
8100 }
8101 if (num_fixed_bools > 0) {
8102 context_->UpdateRuleStats("propagators: fixed booleans", num_fixed_bools);
8103 }
8104}
8105
8106// TODO(user): It might make sense to run this in parallel. The same apply for
8107// other expansive and self-contains steps like symmetry detection, etc...
8108void CpModelPresolver::Probe() {
8109 auto probing_timer =
8110 std::make_unique<PresolveTimer>(__FUNCTION__, logger_, time_limit_);
8111
8112 Model model;
8113 if (!LoadModelForProbing(context_, &model)) return;
8114
8115 // Probe.
8116 //
8117 // TODO(user): Compute the transitive reduction instead of just the
8118 // equivalences, and use the newly learned binary clauses?
8119 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
8120 auto* sat_solver = model.GetOrCreate<SatSolver>();
8121 auto* mapping = model.GetOrCreate<CpModelMapping>();
8122 auto* prober = model.GetOrCreate<Prober>();
8123
8124 // Try to detect trivial clauses thanks to implications.
8125 // This can be slow, so we bound the amount of work done.
8126 //
8127 // Idea: If we have l1, l2 in a bool_or and not(l1) => l2, the constraint is
8128 // always true.
8129 //
8130 // Correctness: Note that we always replace a clause with another one that
8131 // subsumes it. So we are correct even if new clauses are learned and used
8132 // for propagation along the way.
8133 //
8134 // TODO(user): Improve the algo?
8135 const auto& assignment = sat_solver->Assignment();
8136 prober->SetPropagationCallback([&](Literal decision) {
8137 if (probing_timer->WorkLimitIsReached()) return;
8138 const int decision_var =
8139 mapping->GetProtoVariableFromBooleanVariable(decision.Variable());
8140 if (decision_var < 0) return;
8141 probing_timer->TrackSimpleLoop(
8142 context_->VarToConstraints(decision_var).size());
8143 std::vector<int> to_update;
8144 for (const int c : context_->VarToConstraints(decision_var)) {
8145 if (c < 0) continue;
8146 const ConstraintProto& ct = context_->working_model->constraints(c);
8147 if (ct.enforcement_literal().size() > 2) {
8148 // Any l for which decision => l can be removed.
8149 //
8150 // If decision => not(l), constraint can never be satisfied. However
8151 // because we don't know if this constraint was part of the
8152 // propagation we replace it by an implication.
8153 //
8154 // TODO(user): remove duplication with code below.
8155 // TODO(user): If decision appear positively, we could potentially
8156 // remove a bunch of terms (all the ones involving variables implied
8157 // by the decision) from the inner constraint, especially in the
8158 // linear case.
8159 int decision_ref;
8160 int false_ref;
8161 bool decision_is_positive = false;
8162 bool has_false_literal = false;
8163 bool simplification_possible = false;
8164 probing_timer->TrackSimpleLoop(ct.enforcement_literal().size());
8165 for (const int ref : ct.enforcement_literal()) {
8166 const Literal lit = mapping->Literal(ref);
8167 if (PositiveRef(ref) == decision_var) {
8168 decision_ref = ref;
8169 decision_is_positive = assignment.LiteralIsTrue(lit);
8170 if (!decision_is_positive) break;
8171 continue;
8172 }
8173 if (assignment.LiteralIsFalse(lit)) {
8174 false_ref = ref;
8175 has_false_literal = true;
8176 } else if (assignment.LiteralIsTrue(lit)) {
8177 // If decision => l, we can remove l from the list.
8178 simplification_possible = true;
8179 }
8180 }
8181 if (!decision_is_positive) continue;
8182
8183 if (has_false_literal) {
8184 // Reduce to implication.
8185 auto* mutable_ct = context_->working_model->mutable_constraints(c);
8186 mutable_ct->Clear();
8187 mutable_ct->add_enforcement_literal(decision_ref);
8188 mutable_ct->mutable_bool_and()->add_literals(NegatedRef(false_ref));
8189 context_->UpdateRuleStats(
8190 "probing: reduced enforced constraint to implication");
8191 to_update.push_back(c);
8192 continue;
8193 }
8194
8195 if (simplification_possible) {
8196 int new_size = 0;
8197 auto* mutable_enforcements =
8198 context_->working_model->mutable_constraints(c)
8199 ->mutable_enforcement_literal();
8200 for (const int ref : ct.enforcement_literal()) {
8201 if (PositiveRef(ref) != decision_var &&
8202 assignment.LiteralIsTrue(mapping->Literal(ref))) {
8203 continue;
8204 }
8205 mutable_enforcements->Set(new_size++, ref);
8206 }
8207 mutable_enforcements->Truncate(new_size);
8208 context_->UpdateRuleStats("probing: simplified enforcement list");
8209 to_update.push_back(c);
8210 }
8211 continue;
8212 }
8213
8214 if (ct.constraint_case() != ConstraintProto::kBoolOr) continue;
8215 if (ct.bool_or().literals().size() <= 2) continue;
8216
8217 int decision_ref;
8218 int true_ref;
8219 bool decision_is_negative = false;
8220 bool has_true_literal = false;
8221 bool simplification_possible = false;
8222 probing_timer->TrackSimpleLoop(ct.bool_or().literals().size());
8223 for (const int ref : ct.bool_or().literals()) {
8224 const Literal lit = mapping->Literal(ref);
8225 if (PositiveRef(ref) == decision_var) {
8226 decision_ref = ref;
8227 decision_is_negative = assignment.LiteralIsFalse(lit);
8228 if (!decision_is_negative) break;
8229 continue;
8230 }
8231 if (assignment.LiteralIsTrue(lit)) {
8232 true_ref = ref;
8233 has_true_literal = true;
8234 } else if (assignment.LiteralIsFalse(lit)) {
8235 // If not(l1) => not(l2), we can remove l2 from the clause.
8236 simplification_possible = true;
8237 }
8238 }
8239 if (!decision_is_negative) continue;
8240
8241 if (has_true_literal) {
8242 // This will later be merged with the current implications and removed
8243 // if it is a duplicate.
8244 auto* mutable_bool_or =
8245 context_->working_model->mutable_constraints(c)->mutable_bool_or();
8246 mutable_bool_or->mutable_literals()->Clear();
8247 mutable_bool_or->add_literals(decision_ref);
8248 mutable_bool_or->add_literals(true_ref);
8249 context_->UpdateRuleStats("probing: bool_or reduced to implication");
8250 to_update.push_back(c);
8251 continue;
8252 }
8253
8254 if (simplification_possible) {
8255 int new_size = 0;
8256 auto* mutable_bool_or =
8257 context_->working_model->mutable_constraints(c)->mutable_bool_or();
8258 for (const int ref : ct.bool_or().literals()) {
8259 if (PositiveRef(ref) != decision_var &&
8260 assignment.LiteralIsFalse(mapping->Literal(ref))) {
8261 continue;
8262 }
8263 mutable_bool_or->set_literals(new_size++, ref);
8264 }
8265 mutable_bool_or->mutable_literals()->Truncate(new_size);
8266 context_->UpdateRuleStats("probing: simplified clauses");
8267 to_update.push_back(c);
8268 }
8269 }
8270
8271 absl::c_sort(to_update);
8272 for (const int c : to_update) {
8273 context_->UpdateConstraintVariableUsage(c);
8274 }
8275 });
8276
8277 prober->ProbeBooleanVariables(
8278 context_->params().probing_deterministic_time_limit());
8279
8280 for (const auto& [expr, ub] : model.GetOrCreate<RootLevelLinear2Bounds>()
8281 ->GetSortedNonTrivialUpperBounds()) {
8282 if (expr.vars[0] == kNoIntegerVariable ||
8283 expr.vars[1] == kNoIntegerVariable) {
8284 continue;
8285 }
8286 const IntegerVariable var0 = PositiveVariable(expr.vars[0]);
8287 const IntegerVariable var1 = PositiveVariable(expr.vars[1]);
8288 const int proto_var0 = mapping->GetProtoVariableFromIntegerVariable(var0);
8289 const int proto_var1 = mapping->GetProtoVariableFromIntegerVariable(var1);
8290 if (proto_var0 < 0 || proto_var1 < 0) continue;
8291 const int64_t coeff0 = VariableIsPositive(expr.vars[0])
8292 ? expr.coeffs[0].value()
8293 : -expr.coeffs[0].value();
8294 const int64_t coeff1 = VariableIsPositive(expr.vars[1])
8295 ? expr.coeffs[1].value()
8296 : -expr.coeffs[1].value();
8297 known_linear2_.Add(
8298 GetLinearExpression2FromProto(proto_var0, coeff0, proto_var1, coeff1),
8299 kMinIntegerValue, ub);
8300 }
8301
8302 probing_timer->AddCounter("probed", prober->num_decisions());
8303 probing_timer->AddToWork(
8304 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
8305 if (sat_solver->ModelIsUnsat() || !implication_graph->DetectEquivalences()) {
8306 return (void)context_->NotifyThatModelIsUnsat("during probing");
8307 }
8308
8309 time_limit_->ResetHistory();
8310
8311 // Update the presolve context with fixed Boolean variables.
8312 int num_fixed = 0;
8313 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
8314 for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
8315 const Literal l = sat_solver->LiteralTrail()[i];
8316 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
8317 if (var >= 0) {
8318 const int ref = l.IsPositive() ? var : NegatedRef(var);
8319 if (context_->IsFixed(ref)) continue;
8320 ++num_fixed;
8321 if (!context_->SetLiteralToTrue(ref)) return;
8322 }
8323 }
8324 probing_timer->AddCounter("fixed_bools", num_fixed);
8325
8326 int num_equiv = 0;
8327 int num_changed_bounds = 0;
8328 const int num_variables = context_->working_model->variables().size();
8329 auto* integer_trail = model.GetOrCreate<IntegerTrail>();
8330 for (int var = 0; var < num_variables; ++var) {
8331 // Restrict IntegerVariable domain.
8332 // Note that Boolean are already dealt with above.
8333 if (!mapping->IsBoolean(var)) {
8334 bool changed = false;
8335 if (!context_->IntersectDomainWith(
8336 var, integer_trail->InitialVariableDomain(mapping->Integer(var)),
8337 &changed)) {
8338 return;
8339 }
8340 if (changed) ++num_changed_bounds;
8341 continue;
8342 }
8343
8344 // Add Boolean equivalence relations.
8345 const Literal l = mapping->Literal(var);
8346 const Literal r = implication_graph->RepresentativeOf(l);
8347 if (r != l) {
8348 ++num_equiv;
8349 const int r_var =
8350 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
8351 CHECK_GE(r_var, 0);
8352 if (!context_->StoreBooleanEqualityRelation(
8353 var, r.IsPositive() ? r_var : NegatedRef(r_var))) {
8354 return;
8355 }
8356 }
8357 }
8358 probing_timer->AddCounter("new_bounds", num_changed_bounds);
8359 probing_timer->AddCounter("equiv", num_equiv);
8360 probing_timer->AddCounter("new_binary_clauses",
8361 prober->num_new_binary_clauses());
8362
8363 // Note that we prefer to run this after we exported all equivalence to the
8364 // context, so that our enforcement list can be presolved to the best of our
8365 // knowledge.
8366 DetectDuplicateConstraintsWithDifferentEnforcements(
8367 mapping, implication_graph, model.GetOrCreate<Trail>());
8368
8369 // Stop probing timer now and display info.
8370 probing_timer.reset();
8371
8372 // Run clique merging using detected implications from probing.
8373 if (context_->params().merge_at_most_one_work_limit() > 0.0) {
8374 PresolveTimer timer("MaxClique", logger_, time_limit_);
8375 std::vector<std::vector<Literal>> cliques;
8376 std::vector<int> clique_ct_index;
8377
8378 // TODO(user): On large model, most of the time is spend in this copy,
8379 // clearing and updating the constraint variable graph...
8380 int64_t num_literals_before = 0;
8381 const int num_constraints = context_->working_model->constraints_size();
8382 for (int c = 0; c < num_constraints; ++c) {
8383 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8384 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
8385 std::vector<Literal> clique;
8386 for (const int ref : ct->at_most_one().literals()) {
8387 clique.push_back(mapping->Literal(ref));
8388 }
8389 num_literals_before += clique.size();
8390 cliques.push_back(clique);
8391 ct->Clear();
8392 context_->UpdateConstraintVariableUsage(c);
8393 } else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
8394 if (ct->enforcement_literal().size() != 1) continue;
8395 const Literal enforcement =
8396 mapping->Literal(ct->enforcement_literal(0));
8397 for (const int ref : ct->bool_and().literals()) {
8398 if (ref == ct->enforcement_literal(0)) continue;
8399 num_literals_before += 2;
8400 cliques.push_back({enforcement, mapping->Literal(ref).Negated()});
8401 }
8402 ct->Clear();
8403 context_->UpdateConstraintVariableUsage(c);
8404 }
8405 }
8406 const int64_t num_old_cliques = cliques.size();
8407
8408 // We adapt the limit if there is a lot of literals in amo/implications.
8409 // Usually we can have big reduction on large problem so it seems
8410 // worthwhile.
8411 double limit = context_->params().merge_at_most_one_work_limit();
8412 if (num_literals_before > 1e6) {
8413 limit *= num_literals_before / 1e6;
8414 }
8415
8416 double dtime = 0.0;
8417 implication_graph->MergeAtMostOnes(absl::MakeSpan(cliques),
8418 SafeDoubleToInt64(limit), &dtime);
8419 timer.AddToWork(dtime);
8420
8421 // Note that because TransformIntoMaxCliques() extend cliques, we are ok
8422 // to ignore any unmapped literal. In case of equivalent literal, we always
8423 // use the smaller indices as a representative, so we should be good.
8424 int num_new_cliques = 0;
8425 int64_t num_literals_after = 0;
8426 for (const std::vector<Literal>& clique : cliques) {
8427 if (clique.empty()) continue;
8428 num_new_cliques++;
8429 num_literals_after += clique.size();
8430 ConstraintProto* ct = context_->working_model->add_constraints();
8431 for (const Literal literal : clique) {
8432 const int var =
8433 mapping->GetProtoVariableFromBooleanVariable(literal.Variable());
8434 if (var < 0) continue;
8435 if (literal.IsPositive()) {
8436 ct->mutable_at_most_one()->add_literals(var);
8437 } else {
8438 ct->mutable_at_most_one()->add_literals(NegatedRef(var));
8439 }
8440 }
8441
8442 // Make sure we do not have duplicate variable reference.
8443 PresolveAtMostOne(ct);
8444 }
8445 context_->UpdateNewConstraintsVariableUsage();
8446 if (num_new_cliques != num_old_cliques) {
8447 context_->UpdateRuleStats("at_most_one: transformed into max clique");
8448 }
8449
8450 if (num_old_cliques != num_new_cliques ||
8451 num_literals_before != num_literals_after) {
8452 timer.AddMessage(
8453 absl::StrCat("Merged ", Plural(num_old_cliques, "constraint"),
8454 " with ", Plural(num_literals_before, "literal"),
8455 " into ", Plural(num_new_cliques, "constraint"),
8456 " with ", Plural(num_literals_after, "literal")));
8457 }
8458 }
8459}
8460
8461namespace {
8462
8463bool FixFromAssignment(const VariablesAssignment& assignment,
8464 absl::Span<const int> var_mapping,
8465 PresolveContext* context) {
8466 const int num_vars = assignment.NumberOfVariables();
8467 for (int i = 0; i < num_vars; ++i) {
8468 const Literal lit(BooleanVariable(i), true);
8469 const int ref = var_mapping[i];
8470 if (assignment.LiteralIsTrue(lit)) {
8471 if (!context->SetLiteralToTrue(ref)) return false;
8472 } else if (assignment.LiteralIsFalse(lit)) {
8473 if (!context->SetLiteralToFalse(ref)) return false;
8474 }
8475 }
8476 return true;
8477}
8478
8479} // namespace
8480
8481// TODO(user): What to do with the at_most_one/exactly_one constraints?
8482// currently we do not take them into account here.
8483bool CpModelPresolver::PresolvePureSatPart() {
8484 // TODO(user): Reenable some SAT presolve with
8485 // keep_all_feasible_solutions set to true.
8486 if (context_->ModelIsUnsat()) return true;
8487 if (context_->params().keep_all_feasible_solutions_in_presolve()) return true;
8488
8489 // Compute a dense re-indexing for the Booleans of the problem.
8490 int num_variables = 0;
8491 int num_ignored_variables = 0;
8492 const int total_num_vars = context_->working_model->variables().size();
8493 std::vector<int> new_index(total_num_vars, -1);
8494 std::vector<int> new_to_old_index;
8495 for (int i = 0; i < total_num_vars; ++i) {
8496 if (!context_->CanBeUsedAsLiteral(i)) {
8497 ++num_ignored_variables;
8498 continue;
8499 }
8500
8501 // This is important to not assign variable in equivalence to random values.
8502 if (context_->VarToConstraints(i).empty()) continue;
8503
8504 new_to_old_index.push_back(i);
8505 new_index[i] = num_variables++;
8506 DCHECK_EQ(num_variables, new_to_old_index.size());
8507 }
8508
8509 // The conversion from proto index to remapped Literal.
8510 auto convert = [&new_index](int ref) {
8511 const int index = new_index[PositiveRef(ref)];
8512 DCHECK_NE(index, -1);
8513 return Literal(BooleanVariable(index), RefIsPositive(ref));
8514 };
8515
8516 // Load the pure-SAT part in a fresh Model.
8517 //
8518 // TODO(user): The removing and adding back of the same clause when nothing
8519 // happens in the presolve "seems" bad. That said, complexity wise, it is
8520 // a lot faster that what happens in the presolve though.
8521 //
8522 // TODO(user): Add the "small" at most one constraints to the SAT presolver by
8523 // expanding them to implications? that could remove a lot of clauses. Do that
8524 // when we are sure we don't load duplicates at_most_one/implications in the
8525 // solver. Ideally, the pure sat presolve could be improved to handle at most
8526 // one, and we could merge this with what the ProcessSetPPC() is doing.
8527 Model local_model;
8528 local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
8529 auto* sat_solver = local_model.GetOrCreate<SatSolver>();
8530 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
8531 sat_solver->SetNumVariables(num_variables);
8532
8533 // Fix variables if any. Because we might not have reached the presove "fixed
8534 // point" above, some variable in the added clauses might be fixed. We need to
8535 // indicate this to the SAT presolver.
8536 for (const int var : new_to_old_index) {
8537 if (context_->IsFixed(var)) {
8538 if (context_->LiteralIsTrue(var)) {
8539 if (!sat_solver->AddUnitClause({convert(var)})) return false;
8540 } else {
8541 if (!sat_solver->AddUnitClause({convert(NegatedRef(var))})) {
8542 return false;
8543 }
8544 }
8545 }
8546 }
8547
8548 std::vector<Literal> clause;
8549 int num_removed_constraints = 0;
8550 int num_ignored_constraints = 0;
8551 const bool load_amo = context_->params().load_at_most_ones_in_sat_presolve();
8552 for (int i = 0; i < context_->working_model->constraints_size(); ++i) {
8553 const ConstraintProto& ct = context_->working_model->constraints(i);
8554
8555 if (ct.constraint_case() == ConstraintProto::kBoolOr) {
8556 ++num_removed_constraints;
8557 clause.clear();
8558 for (const int ref : ct.bool_or().literals()) {
8559 clause.push_back(convert(ref));
8560 }
8561 for (const int ref : ct.enforcement_literal()) {
8562 clause.push_back(convert(ref).Negated());
8563 }
8564 sat_solver->AddProblemClause(clause);
8565
8566 context_->working_model->mutable_constraints(i)->Clear();
8567 context_->UpdateConstraintVariableUsage(i);
8568 continue;
8569 }
8570
8571 // TODO(user): we should probably make sure we don't have empty amo.
8572 if (load_amo && ct.constraint_case() == ConstraintProto::kAtMostOne &&
8573 ct.enforcement_literal().empty() &&
8574 !ct.at_most_one().literals().empty()) {
8575 clause.clear();
8576 for (const int ref : ct.at_most_one().literals()) {
8577 clause.push_back(convert(ref));
8578 }
8579 if (!graph->AddAtMostOne(clause)) return false;
8580
8581 ++num_removed_constraints;
8582 context_->working_model->mutable_constraints(i)->Clear();
8583 context_->UpdateConstraintVariableUsage(i);
8584 continue;
8585 }
8586
8587 if (load_amo && ct.constraint_case() == ConstraintProto::kExactlyOne &&
8588 ct.enforcement_literal().empty()) {
8589 clause.clear();
8590 for (const int ref : ct.exactly_one().literals()) {
8591 clause.push_back(convert(ref));
8592 }
8593
8594 // We load it as two constraints.
8595 if (!graph->AddAtMostOne(clause)) return false;
8596 sat_solver->AddProblemClause(clause);
8597
8598 ++num_removed_constraints;
8599 context_->working_model->mutable_constraints(i)->Clear();
8600 context_->UpdateConstraintVariableUsage(i);
8601 continue;
8602 }
8603
8604 if (ct.constraint_case() == ConstraintProto::kBoolAnd) {
8605 // We currently do not expand "complex" bool_and that would result
8606 // in too many literals.
8607 const int left_size = ct.enforcement_literal().size();
8608 const int right_size = ct.bool_and().literals().size();
8609 if (left_size > 1 && right_size > 1 &&
8610 (left_size + 1) * right_size > 10'000) {
8611 ++num_ignored_constraints;
8612 continue;
8613 }
8614
8615 ++num_removed_constraints;
8616 std::vector<Literal> clause;
8617 for (const int ref : ct.enforcement_literal()) {
8618 clause.push_back(convert(ref).Negated());
8619 }
8620 clause.push_back(Literal(kNoLiteralIndex)); // will be replaced below.
8621 for (const int ref : ct.bool_and().literals()) {
8622 clause.back() = convert(ref);
8623 sat_solver->AddProblemClause(clause);
8624 }
8625
8626 context_->working_model->mutable_constraints(i)->Clear();
8627 context_->UpdateConstraintVariableUsage(i);
8628 continue;
8629 }
8630
8631 if (ct.constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
8632 continue;
8633 }
8634
8635 ++num_ignored_constraints;
8636 }
8637 if (sat_solver->ModelIsUnsat()) return false;
8638
8639 // Abort early if there was no Boolean constraints.
8640 if (num_removed_constraints == 0) return true;
8641
8642 // Mark the variables appearing elsewhere or in the objective as non-removable
8643 // by the sat presolver.
8644 //
8645 // TODO(user): do not remove variable that appear in the decision heuristic?
8646 // TODO(user): We could go further for variable with only one polarity by
8647 // removing variable from the objective if they can be set to their "low"
8648 // objective value, and also removing enforcement literal that can be set to
8649 // false and don't appear elsewhere.
8650 int num_in_extra_constraints = 0;
8651 std::vector<bool> can_be_removed(num_variables, false);
8652 for (int i = 0; i < num_variables; ++i) {
8653 const int var = new_to_old_index[i];
8654 if (context_->VarToConstraints(var).empty()) {
8655 can_be_removed[i] = true;
8656 } else {
8657 // That might correspond to the objective or a variable with an affine
8658 // relation that is still in the model.
8659 ++num_in_extra_constraints;
8660 }
8661 }
8662
8663 // The "full solver" postsolve does not support changing the value of a
8664 // variable from the solution of the presolved problem, and we do need this
8665 // for blocked clause. It should be possible to allow for this by adding extra
8666 // variable to the mapping model at presolve and some linking constraints, but
8667 // this is messy.
8668 //
8669 // We also disable this if the user asked for tightened domain as this might
8670 // fix variable to a potentially infeasible value, and just correct them later
8671 // during postsolve of a particular solution.
8672 SatParameters sat_params = context_->params();
8673 if (sat_params.debug_postsolve_with_full_solver() ||
8674 sat_params.fill_tightened_domains_in_response()) {
8675 sat_params.set_presolve_blocked_clause(false);
8676 }
8677
8678 // This option is only supported by the custom postsolve code.
8679 if (!sat_params.debug_postsolve_with_full_solver()) {
8680 sat_params.set_filter_sat_postsolve_clauses(true);
8681 }
8682
8683 SatPostsolver sat_postsolver(num_variables);
8684
8685 // If the problem is a pure-SAT problem, we run the new SAT presolver.
8686 // This takes more time but it is usually worthwile
8687 //
8688 // Note that the probing that it does is faster than the
8689 // ProbeAndFindEquivalentLiteral() call below, but does not do equivalence
8690 // detection as completely, so we still apply the other "probing" code
8691 // afterwards even if it will not fix more literals, but it will do one pass
8692 // of proper equivalence detection.
8693 util_intops::StrongVector<LiteralIndex, LiteralIndex> equiv_map;
8694 if (!context_->params().debug_postsolve_with_full_solver() &&
8695 num_ignored_variables == 0 && num_ignored_constraints == 0 &&
8696 num_in_extra_constraints == 0) {
8697 // Some problems are formulated in such a way that our SAT heuristics
8698 // simply works without conflict. Get them out of the way first because it
8699 // is possible that the presolve lose this "lucky" ordering. This is in
8700 // particular the case on the SAT14.crafted.complete-xxx-... problems.
8701 if (!LookForTrivialSatSolution(/*deterministic_time_limit=*/1.0,
8702 &local_model, logger_)) {
8703 return false;
8704 }
8705 if (sat_solver->LiteralTrail().Index() == num_variables) {
8706 // Problem solved! We should be able to assign the solution.
8707 CHECK(FixFromAssignment(sat_solver->Assignment(), new_to_old_index,
8708 context_));
8709 return true;
8710 }
8711
8712 SatPresolveOptions options;
8713 options.log_info = true; // log_info;
8714 options.extract_binary_clauses_in_probing = false;
8715 options.use_transitive_reduction = false;
8716 options.deterministic_time_limit =
8717 context_->params().presolve_probing_deterministic_time_limit();
8718 options.use_equivalence_sat_sweeping =
8719 context_->params().inprocessing_use_sat_sweeping();
8720
8721 auto* inprocessing = local_model.GetOrCreate<Inprocessing>();
8722 inprocessing->ProvideLogger(logger_);
8723 if (!inprocessing->PresolveLoop(options)) return false;
8724 for (const auto& c : local_model.GetOrCreate<PostsolveClauses>()->clauses) {
8725 sat_postsolver.Add(c[0], c);
8726 }
8727
8728 // Probe + find equivalent literals.
8729 // TODO(user): Use a derived time limit in the probing phase.
8730 ProbeAndFindEquivalentLiteral(sat_solver, &sat_postsolver, &equiv_map,
8731 logger_);
8732
8733 if (sat_solver->ModelIsUnsat()) return false;
8734 } else {
8735 // TODO(user): BVA takes time and does not seems to help on the minizinc
8736 // benchmarks. So we currently disable it, except if we are on a pure-SAT
8737 // problem, where we follow the default (true) or the user specified value.
8738 sat_params.set_presolve_use_bva(false);
8739 }
8740
8741 // Disable BVA if we want to keep the symmetries.
8742 //
8743 // TODO(user): We could still do it, we just need to do in a symmetric way
8744 // and also update the generators to take into account the new variables. This
8745 // do not seems that easy.
8746 if (context_->params().keep_symmetry_in_presolve()) {
8747 sat_params.set_presolve_use_bva(false);
8748 }
8749
8750 // Update the time limit of the initial propagation.
8751 if (!sat_solver->ResetToLevelZero()) return false;
8752 time_limit_->AdvanceDeterministicTime(
8753 local_model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
8754
8755 // The "old" SAT presolve do not read at_most_ones.
8756 // So extract them back from the sat_solver, and only continue with submodel.
8757 graph->CleanupAllRemovedAndFixedVariables();
8758 graph->ResetAtMostOneIterator();
8759 while (true) {
8760 absl::Span<const Literal> amo = graph->NextAtMostOne();
8761 if (amo.empty()) break;
8762
8763 // Re-add the amo to the proto.
8764 ConstraintProto* ct = context_->working_model->add_constraints();
8765 ct->mutable_at_most_one()->mutable_literals()->Reserve(amo.size());
8766 for (Literal l : amo) {
8767 // TODO(user): ProbeAndFindEquivalentLiteral() do not register newly
8768 // found equivalence to the BinaryImplicationGraph, It should so that
8769 // we already get cleaned AMO here.
8770 if (l.Index() < equiv_map.size()) {
8771 l = Literal(equiv_map[l]);
8772 }
8773
8774 const int var = new_to_old_index[l.Variable().value()];
8775 ct->mutable_at_most_one()->add_literals(l.IsPositive() ? var
8776 : NegatedRef(var));
8777
8778 // These cannot be removed anymore by the old SAT presolver.
8779 can_be_removed[l.Variable().value()] = false;
8780 }
8781 }
8782 context_->UpdateNewConstraintsVariableUsage();
8783
8784 // Apply the "old" SAT presolve.
8785 SatPresolver sat_presolver(&sat_postsolver, logger_);
8786 sat_presolver.SetNumVariables(num_variables);
8787 if (!equiv_map.empty()) {
8788 sat_presolver.SetEquivalentLiteralMapping(equiv_map);
8789 }
8790 sat_presolver.SetTimeLimit(time_limit_);
8791 sat_presolver.SetParameters(sat_params);
8792
8793 // Load in the presolver.
8794 // Register the fixed variables with the postsolver.
8795 for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
8796 sat_postsolver.FixVariable(sat_solver->LiteralTrail()[i]);
8797 }
8798 if (!sat_solver->ExtractClauses(&sat_presolver)) return false;
8799
8800 // Run the presolve for a small number of passes.
8801 // TODO(user): Add a local time limit? this can be slow on big SAT problem.
8802 for (int i = 0; i < 1; ++i) {
8803 const int old_num_clause = sat_postsolver.NumClauses();
8804 if (!sat_presolver.Presolve(can_be_removed)) return false;
8805 if (old_num_clause == sat_postsolver.NumClauses()) break;
8806 }
8807
8808 // Add any new variables to our internal structure.
8809 const int new_num_variables = sat_presolver.NumVariables();
8810 if (new_num_variables > num_variables) {
8811 VLOG(1) << "New variables added by the SAT presolver.";
8812 for (int i = num_variables; i < new_num_variables; ++i) {
8813 new_to_old_index.push_back(context_->working_model->variables().size());
8814 IntegerVariableProto* var_proto =
8815 context_->working_model->add_variables();
8816 var_proto->add_domain(0);
8817 var_proto->add_domain(1);
8818 }
8819 context_->InitializeNewDomains();
8820 }
8821
8822 // Fix variables if any.
8823 if (!FixFromAssignment(sat_postsolver.assignment(), new_to_old_index,
8824 context_)) {
8825 return false;
8826 }
8827
8828 // Add the presolver clauses back into the model.
8829 ExtractClauses(/*merge_into_bool_and=*/true, new_to_old_index, sat_presolver,
8830 context_->working_model);
8831
8832 // Update the constraints <-> variables graph.
8833 context_->UpdateNewConstraintsVariableUsage();
8834
8835 // We mark as removed any variables removed by the pure SAT presolve.
8836 // This is mainly to discover or avoid bug as we might have stale entries
8837 // in our encoding hash-map for instance.
8838 for (int i = 0; i < num_variables; ++i) {
8839 const int var = new_to_old_index[i];
8840 if (context_->VarToConstraints(var).empty()) {
8841 context_->MarkVariableAsRemoved(var);
8842 }
8843 }
8844
8845 // Add the sat_postsolver clauses to mapping_model.
8846 const std::string name =
8847 absl::GetFlag(FLAGS_cp_model_debug_postsolve) ? "sat_postsolver" : "";
8848 ExtractClauses(/*merge_into_bool_and=*/false, new_to_old_index,
8849 sat_postsolver, context_->mapping_model, name);
8850 return true;
8851}
8852
8853void CpModelPresolver::ShiftObjectiveWithExactlyOnes() {
8854 if (context_->ModelIsUnsat()) return;
8855
8856 // The objective is already loaded in the context, but we re-canonicalize
8857 // it with the latest information.
8858 if (!context_->CanonicalizeObjective()) {
8859 return;
8860 }
8861
8862 std::vector<int> exos;
8863 const int num_constraints = context_->working_model->constraints_size();
8864 for (int c = 0; c < num_constraints; ++c) {
8865 const ConstraintProto& ct = context_->working_model->constraints(c);
8866 if (!ct.enforcement_literal().empty()) continue;
8867 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8868 exos.push_back(c);
8869 }
8870 }
8871
8872 // This is not the same from what we do in ExpandObjective() because we do not
8873 // make the minimum cost zero but the second minimum. Note that when we do
8874 // that, we still do not degrade the trivial objective bound as we would if we
8875 // went any further.
8876 //
8877 // One reason why this might be beneficial is that it lower the maximum cost
8878 // magnitude, making more Booleans with the same cost and thus simplifying
8879 // the core optimizer job. I am not 100% sure.
8880 //
8881 // TODO(user): We need to loop a few time to reach a fixed point. Understand
8882 // exactly if there is a fixed-point and how to reach it in a nicer way.
8883 int num_shifts = 0;
8884 for (int i = 0; i < 3; ++i) {
8885 for (const int c : exos) {
8886 const ConstraintProto& ct = context_->working_model->constraints(c);
8887 const int num_terms = ct.exactly_one().literals().size();
8888 if (num_terms <= 1) continue;
8889 int64_t min_obj = std::numeric_limits<int64_t>::max();
8890 int64_t second_min = std::numeric_limits<int64_t>::max();
8891 for (int i = 0; i < num_terms; ++i) {
8892 const int literal = ct.exactly_one().literals(i);
8893 const int64_t var_obj = context_->ObjectiveCoeff(PositiveRef(literal));
8894 const int64_t obj = RefIsPositive(literal) ? var_obj : -var_obj;
8895 if (obj < min_obj) {
8896 second_min = min_obj;
8897 min_obj = obj;
8898 } else if (obj < second_min) {
8899 second_min = obj;
8900 }
8901 }
8902 if (second_min == 0) continue;
8903 ++num_shifts;
8904 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
8905 second_min)) {
8906 if (context_->ModelIsUnsat()) return;
8907 continue;
8908 }
8909 }
8910 }
8911 if (num_shifts > 0) {
8912 context_->UpdateRuleStats("objective: shifted cost with exactly ones",
8913 num_shifts);
8914 }
8915}
8916
8917bool CpModelPresolver::PropagateObjective() {
8918 if (!context_->working_model->has_objective()) return true;
8919 if (context_->ModelIsUnsat()) return false;
8920 context_->WriteObjectiveToProto();
8921
8922 int64_t min_activity = 0;
8923 int64_t max_variation = 0;
8924 const CpObjectiveProto& objective = context_->working_model->objective();
8925 const int num_terms = objective.vars().size();
8926 for (int i = 0; i < num_terms; ++i) {
8927 const int var = objective.vars(i);
8928 const int64_t coeff = objective.coeffs(i);
8929 CHECK(RefIsPositive(var));
8930 CHECK_NE(coeff, 0);
8931
8932 const int64_t domain_min = context_->MinOf(var);
8933 const int64_t domain_max = context_->MaxOf(var);
8934 if (coeff > 0) {
8935 min_activity += coeff * domain_min;
8936 } else {
8937 min_activity += coeff * domain_max;
8938 }
8939 const int64_t variation = std::abs(coeff) * (domain_max - domain_min);
8940 max_variation = std::max(max_variation, variation);
8941 }
8942
8943 // Infeasible ?
8944 const int64_t slack =
8945 CapSub(ReadDomainFromProto(objective).Max(), min_activity);
8946 if (slack < 0) {
8947 return context_->NotifyThatModelIsUnsat(
8948 "infeasible while propagating objective");
8949 }
8950
8951 // No propagation ?
8952 if (max_variation <= slack) return true;
8953
8954 int num_propagations = 0;
8955 for (int i = 0; i < num_terms; ++i) {
8956 const int var = objective.vars(i);
8957 const int64_t coeff = objective.coeffs(i);
8958 const int64_t domain_min = context_->MinOf(var);
8959 const int64_t domain_max = context_->MaxOf(var);
8960
8961 const int64_t new_diff = slack / std::abs(coeff);
8962 if (new_diff >= domain_max - domain_min) continue;
8963
8964 ++num_propagations;
8965 if (coeff > 0) {
8966 if (!context_->IntersectDomainWith(
8967 var, Domain(domain_min, domain_min + new_diff))) {
8968 return false;
8969 }
8970 } else {
8971 if (!context_->IntersectDomainWith(
8972 var, Domain(domain_max - new_diff, domain_max))) {
8973 return false;
8974 }
8975 }
8976 }
8977 CHECK_GT(num_propagations, 0);
8978
8979 context_->UpdateRuleStats("objective: restricted var domains by propagation",
8980 num_propagations);
8981 return true;
8982}
8983
8984// Expand the objective expression in some easy cases.
8985//
8986// The ideas is to look at all the "tight" equality constraints. These should
8987// give a topological order on the variable in which we can perform
8988// substitution.
8989//
8990// Basically, we will only use constraints of the form X' = sum ci * Xi' with ci
8991// > 0 and the variable X' being shifted version >= 0. Note that if there is a
8992// cycle with these constraints, all variables involved must be equal to each
8993// other and likely zero. Otherwise, we can express everything in terms of the
8994// leaves.
8995//
8996// This assumes we are more or less at the propagation fix point, even if we
8997// try to address cases where we are not.
8998void CpModelPresolver::ExpandObjective() {
8999 if (time_limit_->LimitReached()) return;
9000 if (context_->ModelIsUnsat()) return;
9001 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9002
9003 // The objective is already loaded in the context, but we re-canonicalize
9004 // it with the latest information.
9005 if (!context_->CanonicalizeObjective()) {
9006 return;
9007 }
9008
9009 const int num_variables = context_->working_model->variables_size();
9010 const int num_constraints = context_->working_model->constraints_size();
9011
9012 // We consider two types of shifted variables (X - LB(X)) and (UB(X) - X).
9013 const auto get_index = [](int var, bool to_lb) {
9014 return 2 * var + (to_lb ? 0 : 1);
9015 };
9016 const auto get_lit_index = [](int lit) {
9017 return RefIsPositive(lit) ? 2 * lit : 2 * PositiveRef(lit) + 1;
9018 };
9019 const int num_nodes = 2 * num_variables;
9020 std::vector<std::vector<int>> index_graph(num_nodes);
9021
9022 // TODO(user): instead compute how much each constraint can be further
9023 // expanded?
9024 std::vector<int> index_to_best_c(num_nodes, -1);
9025 std::vector<int> index_to_best_size(num_nodes, 0);
9026
9027 // Lets see first if there are "tight" constraint and for which variables.
9028 // We stop processing constraint if we have too many entries.
9029 int num_entries = 0;
9030 int num_propagations = 0;
9031 int num_tight_variables = 0;
9032 int num_tight_constraints = 0;
9033 const int kNumEntriesThreshold = 1e8;
9034 for (int c = 0; c < num_constraints; ++c) {
9035 if (num_entries > kNumEntriesThreshold) break;
9036
9037 const ConstraintProto& ct = context_->working_model->constraints(c);
9038 if (!ct.enforcement_literal().empty()) continue;
9039
9040 // Deal with exactly one.
9041 // An exactly one is always tight on the upper bound of one term.
9042 //
9043 // Note(user): This code assume there is no fixed variable in the exactly
9044 // one. We thus make sure the constraint is re-presolved if for some reason
9045 // we didn't reach the fixed point before calling this code.
9046 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
9047 if (PresolveExactlyOne(context_->working_model->mutable_constraints(c))) {
9048 context_->UpdateConstraintVariableUsage(c);
9049 }
9050 }
9051 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
9052 const int num_terms = ct.exactly_one().literals().size();
9053 ++num_tight_constraints;
9054 num_tight_variables += num_terms;
9055 for (int i = 0; i < num_terms; ++i) {
9056 if (num_entries > kNumEntriesThreshold) break;
9057 const int neg_index = get_lit_index(ct.exactly_one().literals(i)) ^ 1;
9058
9059 const int old_c = index_to_best_c[neg_index];
9060 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
9061 index_to_best_c[neg_index] = c;
9062 index_to_best_size[neg_index] = num_terms;
9063 }
9064
9065 for (int j = 0; j < num_terms; ++j) {
9066 if (j == i) continue;
9067 const int other_index = get_lit_index(ct.exactly_one().literals(j));
9068 ++num_entries;
9069 index_graph[neg_index].push_back(other_index);
9070 }
9071 }
9072 continue;
9073 }
9074
9075 // Skip everything that is not a linear equality constraint.
9076 if (!IsLinearEqualityConstraint(ct)) continue;
9077
9078 // Let see for which variable is it "tight". We need a coeff of 1, and that
9079 // the implied bounds match exactly.
9080 const auto [min_activity, max_activity] =
9081 context_->ComputeMinMaxActivity(ct.linear());
9082
9083 bool is_tight = false;
9084 const int64_t rhs = ct.linear().domain(0);
9085 const int num_terms = ct.linear().vars_size();
9086 for (int i = 0; i < num_terms; ++i) {
9087 const int var = ct.linear().vars(i);
9088 const int64_t coeff = ct.linear().coeffs(i);
9089 if (std::abs(coeff) != 1) continue;
9090 if (num_entries > kNumEntriesThreshold) break;
9091
9092 const int index = get_index(var, coeff > 0);
9093
9094 const int64_t var_range = context_->MaxOf(var) - context_->MinOf(var);
9095 const int64_t implied_shifted_ub = rhs - min_activity;
9096 if (implied_shifted_ub <= var_range) {
9097 if (implied_shifted_ub < var_range) ++num_propagations;
9098 is_tight = true;
9099 ++num_tight_variables;
9100
9101 const int neg_index = index ^ 1;
9102 const int old_c = index_to_best_c[neg_index];
9103 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
9104 index_to_best_c[neg_index] = c;
9105 index_to_best_size[neg_index] = num_terms;
9106 }
9107
9108 for (int j = 0; j < num_terms; ++j) {
9109 if (j == i) continue;
9110 const int other_index =
9111 get_index(ct.linear().vars(j), ct.linear().coeffs(j) > 0);
9112 ++num_entries;
9113 index_graph[neg_index].push_back(other_index);
9114 }
9115 }
9116 const int64_t implied_shifted_lb = max_activity - rhs;
9117 if (implied_shifted_lb <= var_range) {
9118 if (implied_shifted_lb < var_range) ++num_propagations;
9119 is_tight = true;
9120 ++num_tight_variables;
9121
9122 const int old_c = index_to_best_c[index];
9123 if (old_c == -1 || num_terms > index_to_best_size[index]) {
9124 index_to_best_c[index] = c;
9125 index_to_best_size[index] = num_terms;
9126 }
9127
9128 for (int j = 0; j < num_terms; ++j) {
9129 if (j == i) continue;
9130 const int other_index =
9131 get_index(ct.linear().vars(j), ct.linear().coeffs(j) < 0);
9132 ++num_entries;
9133 index_graph[index].push_back(other_index);
9134 }
9135 }
9136 }
9137 if (is_tight) ++num_tight_constraints;
9138 }
9139
9140 // Note(user): We assume the fixed point was already reached by the linear
9141 // presolve, so we don't add extra code here for that. But we still abort if
9142 // some are left to cover corner cases were linear a still not propagated.
9143 if (num_propagations > 0) {
9144 context_->UpdateRuleStats("TODO objective: propagation possible!");
9145 return;
9146 }
9147
9148 // In most cases, we should have no cycle and thus a topo order.
9149 //
9150 // In case there is a cycle, then all member of a strongly connected component
9151 // must be equivalent, this is because from X to Y, if we follow the chain we
9152 // will have X = non_negative_sum + Y and Y = non_negative_sum + X.
9153 //
9154 // Moreover, many shifted variables will need to be zero once we start to have
9155 // equivalence.
9156 //
9157 // TODO(user): Make the fixing to zero? or at least when this happen redo
9158 // a presolve pass?
9159 //
9160 // TODO(user): Densify index to only look at variable that can be substituted
9161 // further.
9162 const auto topo_order = util::graph::FastTopologicalSort(index_graph);
9163 if (!topo_order.ok()) {
9164 // Tricky: We need to cache all domains to derive the proper relations.
9165 // This is because StoreAffineRelation() might propagate them.
9166 std::vector<int64_t> var_min(num_variables);
9167 std::vector<int64_t> var_max(num_variables);
9168 for (int var = 0; var < num_variables; ++var) {
9169 var_min[var] = context_->MinOf(var);
9170 var_max[var] = context_->MaxOf(var);
9171 }
9172
9173 std::vector<std::vector<int>> components;
9174 FindStronglyConnectedComponents(static_cast<int>(index_graph.size()),
9175 index_graph, &components);
9176 for (const std::vector<int>& compo : components) {
9177 if (compo.size() == 1) continue;
9178
9179 const int rep_var = compo[0] / 2;
9180 const bool rep_to_lp = (compo[0] % 2) == 0;
9181 for (int i = 1; i < compo.size(); ++i) {
9182 const int var = compo[i] / 2;
9183 const bool to_lb = (compo[i] % 2) == 0;
9184
9185 // (rep - rep_lb) | (rep_ub - rep) == (var - var_lb) | (var_ub - var)
9186 // +/- rep = +/- var + offset.
9187 const int64_t rep_coeff = rep_to_lp ? 1 : -1;
9188 const int64_t var_coeff = to_lb ? 1 : -1;
9189 const int64_t offset =
9190 (to_lb ? -var_min[var] : var_max[var]) -
9191 (rep_to_lp ? -var_min[rep_var] : var_max[rep_var]);
9192 if (!context_->StoreAffineRelation(rep_var, var, rep_coeff * var_coeff,
9193 rep_coeff * offset)) {
9194 return;
9195 }
9196 }
9197 context_->UpdateRuleStats("objective: detected equivalence",
9198 compo.size() - 1);
9199 }
9200 return;
9201 }
9202
9203 // If the removed variable is now unique, we could remove it if it is implied
9204 // free. But this should already be done by RemoveSingletonInLinear(), so we
9205 // don't redo it here.
9206 int num_expands = 0;
9207 int num_issues = 0;
9208 for (const int index : *topo_order) {
9209 if (index_graph[index].empty()) continue;
9210
9211 const int var = index / 2;
9212 const int64_t obj_coeff = context_->ObjectiveCoeff(var);
9213 if (obj_coeff == 0) continue;
9214
9215 const bool to_lb = (index % 2) == 0;
9216 if (obj_coeff > 0 == to_lb) {
9217 const ConstraintProto& ct =
9218 context_->working_model->constraints(index_to_best_c[index]);
9219 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
9220 int64_t shift = 0;
9221 for (const int lit : ct.exactly_one().literals()) {
9222 if (PositiveRef(lit) == var) {
9223 shift = RefIsPositive(lit) ? obj_coeff : -obj_coeff;
9224 break;
9225 }
9226 }
9227 if (shift == 0) {
9228 ++num_issues;
9229 continue;
9230 }
9231 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
9232 shift)) {
9233 if (context_->ModelIsUnsat()) return;
9234 ++num_issues;
9235 continue;
9236 }
9237 CHECK_EQ(context_->ObjectiveCoeff(var), 0);
9238 ++num_expands;
9239 continue;
9240 }
9241
9242 int64_t objective_coeff_in_expanded_constraint = 0;
9243 const int num_terms = ct.linear().vars().size();
9244 for (int i = 0; i < num_terms; ++i) {
9245 if (ct.linear().vars(i) == var) {
9246 objective_coeff_in_expanded_constraint = ct.linear().coeffs(i);
9247 break;
9248 }
9249 }
9250 if (objective_coeff_in_expanded_constraint == 0) {
9251 ++num_issues;
9252 continue;
9253 }
9254
9255 if (!context_->SubstituteVariableInObjective(
9256 var, objective_coeff_in_expanded_constraint, ct)) {
9257 if (context_->ModelIsUnsat()) return;
9258 ++num_issues;
9259 continue;
9260 }
9261
9262 ++num_expands;
9263 }
9264 }
9265
9266 if (num_expands > 0) {
9267 context_->UpdateRuleStats("objective: expanded via tight equality",
9268 num_expands);
9269 }
9270
9271 timer.AddCounter("propagations", num_propagations);
9272 timer.AddCounter("entries", num_entries);
9273 timer.AddCounter("tight_variables", num_tight_variables);
9274 timer.AddCounter("tight_constraints", num_tight_constraints);
9275 timer.AddCounter("expands", num_expands);
9276 timer.AddCounter("issues", num_issues);
9277}
9278
9279bool CpModelPresolver::MergeCliqueConstraintsHelper(
9280 std::vector<std::vector<Literal>>& cliques, std::string_view entry_name,
9281 PresolveTimer& timer) {
9282 if (cliques.empty()) return false; // Nothing has changed.
9283 const int num_constraints = context_->working_model->constraints_size();
9284 int old_num_clique_constraints = cliques.size();
9285 int old_num_entries = 0;
9286 for (const std::vector<Literal>& clique : cliques) {
9287 old_num_entries += clique.size();
9288 }
9289
9290 // We reuse the max-clique code from sat.
9291 Model local_model;
9292 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
9293 local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
9294 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
9295 graph->Resize(num_constraints);
9296 for (const std::vector<Literal>& clique : cliques) {
9297 // All variables at false is always a valid solution of the local model,
9298 // so this should never return UNSAT.
9299 CHECK(graph->AddAtMostOne(clique));
9300 }
9301 CHECK(graph->DetectEquivalences());
9302 graph->TransformIntoMaxCliques(
9303 &cliques,
9304 SafeDoubleToInt64(context_->params().merge_no_overlap_work_limit()));
9305
9306 time_limit_->ResetHistory();
9307
9308 // Update the number of constraints and entries after the max-clique.
9309 int new_num_clique_constraints = 0;
9310 int new_num_entries = 0;
9311 for (const std::vector<Literal>& clique : cliques) {
9312 if (clique.empty()) continue;
9313 new_num_clique_constraints++;
9314 new_num_entries += clique.size();
9315 }
9316
9317 if (old_num_clique_constraints != new_num_clique_constraints ||
9318 old_num_entries != new_num_entries) {
9319 timer.AddMessage(absl::StrCat(
9320 "Merged ", Plural(old_num_clique_constraints, "constraint"), " with ",
9321 Plural(old_num_entries, entry_name), " into ",
9322 Plural(new_num_clique_constraints, "constraint"), " with ",
9323 Plural(new_num_entries, entry_name)));
9324 return true;
9325 }
9326
9327 return false; // Nothing has changed.
9328}
9329
9330bool CpModelPresolver::MergeNoOverlapConstraints() {
9331 PresolveTimer timer("MergeNoOverlap", logger_, time_limit_);
9332 if (context_->ModelIsUnsat()) return false;
9333 if (time_limit_->LimitReached()) return true;
9334
9335 const int num_constraints = context_->working_model->constraints_size();
9336 // Extract the no-overlap constraints with no enforcement literals.
9337 // TODO(user): generalize this to merge constraints with the same
9338 // enforcement literals?
9339 std::vector<int> disjunctive_index;
9340 std::vector<std::vector<Literal>> cliques;
9341 for (int c = 0; c < num_constraints; ++c) {
9342 const ConstraintProto& ct = context_->working_model->constraints(c);
9343 if (ct.constraint_case() != ConstraintProto::kNoOverlap) continue;
9344 if (HasEnforcementLiteral(ct)) continue;
9345 std::vector<Literal> clique;
9346 for (const int i : ct.no_overlap().intervals()) {
9347 clique.push_back(Literal(BooleanVariable(i), true));
9348 }
9349 cliques.push_back(clique);
9350 disjunctive_index.push_back(c);
9351 }
9352
9353 if (!MergeCliqueConstraintsHelper(cliques, "interval", timer)) {
9354 return true; // Nothing to do, and model is SAT.
9355 }
9356
9357 // Remove previous no_overlap constraints and add the new recomputed ones.
9358 for (int i = 0; i < cliques.size(); ++i) {
9359 const int ct_index = disjunctive_index[i];
9360 if (RemoveConstraint(
9361 context_->working_model->mutable_constraints(ct_index))) {
9362 context_->UpdateConstraintVariableUsage(ct_index);
9363 }
9364 }
9365 for (int i = 0; i < cliques.size(); ++i) {
9366 if (cliques[i].empty()) continue;
9367 ConstraintProto* ct = context_->working_model->add_constraints();
9368 for (const Literal l : cliques[i]) {
9369 CHECK(l.IsPositive());
9370 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
9371 }
9372 }
9373 context_->UpdateRuleStats("no_overlap: merged constraints");
9374 context_->UpdateNewConstraintsVariableUsage();
9375 return true;
9376}
9377
9378bool CpModelPresolver::MergeNoOverlap2DConstraints() {
9379 PresolveTimer timer("MergeNoOverlap2D", logger_, time_limit_);
9380 if (context_->ModelIsUnsat()) return false;
9381 if (time_limit_->LimitReached()) return true;
9382
9383 const int num_constraints = context_->working_model->constraints_size();
9384 // Extract the no-overlap constraints with no enforcement literals.
9385 // TODO(user): generalize this to merge constraints with the same
9386 // enforcement literals?
9387 std::vector<int> no_overlap2d_index;
9388 std::vector<std::vector<Literal>> cliques;
9389 absl::flat_hash_map<std::pair<int, int>, int> rectangle_to_index;
9390 std::vector<std::pair<int, int>> index_to_rectangle;
9391 for (int c = 0; c < num_constraints; ++c) {
9392 const ConstraintProto& ct = context_->working_model->constraints(c);
9393 if (ct.constraint_case() != ConstraintProto::kNoOverlap2D) continue;
9394 if (HasEnforcementLiteral(ct)) continue;
9395 std::vector<Literal> clique;
9396 for (int i = 0; i < ct.no_overlap_2d().x_intervals_size(); ++i) {
9397 const std::pair<int, int> rect = {ct.no_overlap_2d().x_intervals(i),
9398 ct.no_overlap_2d().y_intervals(i)};
9399 const auto [it, inserted] =
9400 rectangle_to_index.insert({rect, rectangle_to_index.size()});
9401 if (inserted) index_to_rectangle.push_back(rect);
9402 clique.push_back(Literal(BooleanVariable(it->second), true));
9403 }
9404 cliques.push_back(clique);
9405 no_overlap2d_index.push_back(c);
9406 }
9407
9408 if (!MergeCliqueConstraintsHelper(cliques, "rectangle", timer)) {
9409 return true; // Nothing to do, and model is SAT.
9410 }
9411
9412 // Remove previous no_overlap constraints and add the new recomputed ones.
9413 for (int i = 0; i < cliques.size(); ++i) {
9414 const int ct_index = no_overlap2d_index[i];
9415 if (RemoveConstraint(
9416 context_->working_model->mutable_constraints(ct_index))) {
9417 context_->UpdateConstraintVariableUsage(ct_index);
9418 }
9419 }
9420 for (int i = 0; i < cliques.size(); ++i) {
9421 if (cliques[i].empty()) continue;
9422 ConstraintProto* ct = context_->working_model->add_constraints();
9423 for (const Literal l : cliques[i]) {
9424 CHECK(l.IsPositive());
9425 const std::pair<int, int> rect = index_to_rectangle[l.Variable().value()];
9426 ct->mutable_no_overlap_2d()->add_x_intervals(rect.first);
9427 ct->mutable_no_overlap_2d()->add_y_intervals(rect.second);
9428 }
9429 }
9430 context_->UpdateRuleStats("no_overlap_2d: merged constraints");
9431 context_->UpdateNewConstraintsVariableUsage();
9432 return true;
9433}
9434
9435void CpModelPresolver::DetectEncodedComplexDomains(PresolveContext* context) {
9436 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9437 if (context->ModelIsUnsat()) return;
9438 if (time_limit_->LimitReached()) return;
9439
9440 std::vector<VariableEncodingLocalModel> local_models =
9442 for (VariableEncodingLocalModel& local_model : local_models) {
9443 if (time_limit_->LimitReached()) return;
9444 if (!DetectAllEncodedComplexDomain(context, local_model)) {
9445 return;
9446 }
9447 }
9448}
9449
9450// TODO(user): Should we take into account the exactly_one constraints? note
9451// that such constraint cannot be extended. If if a literal implies two literals
9452// at one inside an exactly one constraint then it must be false. Similarly if
9453// it implies all literals at zero inside the exactly one.
9454void CpModelPresolver::TransformIntoMaxCliques() {
9455 if (context_->ModelIsUnsat()) return;
9456 if (context_->params().merge_at_most_one_work_limit() <= 0.0) return;
9457
9458 auto convert = [](int ref) {
9459 if (RefIsPositive(ref)) return Literal(BooleanVariable(ref), true);
9460 return Literal(BooleanVariable(NegatedRef(ref)), false);
9461 };
9462 const int num_constraints = context_->working_model->constraints_size();
9463
9464 // Extract the bool_and and at_most_one constraints.
9465 // TODO(user): use probing info?
9466 std::vector<std::vector<Literal>> cliques;
9467
9468 for (int c = 0; c < num_constraints; ++c) {
9469 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9470 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
9471 std::vector<Literal> clique;
9472 for (const int ref : ct->at_most_one().literals()) {
9473 clique.push_back(convert(ref));
9474 }
9475 cliques.push_back(clique);
9476 if (RemoveConstraint(ct)) {
9477 context_->UpdateConstraintVariableUsage(c);
9478 }
9479 } else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
9480 if (ct->enforcement_literal().size() != 1) continue;
9481 const Literal enforcement = convert(ct->enforcement_literal(0));
9482 for (const int ref : ct->bool_and().literals()) {
9483 if (ref == ct->enforcement_literal(0)) continue;
9484 cliques.push_back({enforcement, convert(ref).Negated()});
9485 }
9486 if (RemoveConstraint(ct)) {
9487 context_->UpdateConstraintVariableUsage(c);
9488 }
9489 }
9490 }
9491
9492 int64_t num_literals_before = 0;
9493 const int num_old_cliques = cliques.size();
9494
9495 // We reuse the max-clique code from sat.
9496 Model local_model;
9497 const int num_variables = context_->working_model->variables().size();
9498 local_model.GetOrCreate<Trail>()->Resize(num_variables);
9499 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
9500 graph->Resize(num_variables);
9501 for (const std::vector<Literal>& clique : cliques) {
9502 num_literals_before += clique.size();
9503 if (!graph->AddAtMostOne(clique)) {
9504 return (void)context_->NotifyThatModelIsUnsat();
9505 }
9506 }
9507 if (!graph->DetectEquivalences()) {
9508 return (void)context_->NotifyThatModelIsUnsat();
9509 }
9510 graph->MergeAtMostOnes(
9511 absl::MakeSpan(cliques),
9512 SafeDoubleToInt64(context_->params().merge_at_most_one_work_limit()));
9513
9514 // Add the Boolean variable equivalence detected by DetectEquivalences().
9515 // Those are needed because TransformIntoMaxCliques() will replace all
9516 // variable by its representative.
9517 for (int var = 0; var < num_variables; ++var) {
9518 const Literal l = Literal(BooleanVariable(var), true);
9519 if (graph->RepresentativeOf(l) != l) {
9520 const Literal r = graph->RepresentativeOf(l);
9521 if (!context_->StoreBooleanEqualityRelation(
9522 var, r.IsPositive() ? r.Variable().value()
9523 : NegatedRef(r.Variable().value()))) {
9524 return;
9525 }
9526 }
9527 }
9528
9529 int num_new_cliques = 0;
9530 int64_t num_literals_after = 0;
9531 for (const std::vector<Literal>& clique : cliques) {
9532 if (clique.empty()) continue;
9533 num_new_cliques++;
9534 num_literals_after += clique.size();
9535 ConstraintProto* ct = context_->working_model->add_constraints();
9536 for (const Literal literal : clique) {
9537 if (literal.IsPositive()) {
9538 ct->mutable_at_most_one()->add_literals(literal.Variable().value());
9539 } else {
9540 ct->mutable_at_most_one()->add_literals(
9541 NegatedRef(literal.Variable().value()));
9542 }
9543 }
9544
9545 // Make sure we do not have duplicate variable reference.
9546 PresolveAtMostOne(ct);
9547 }
9548 context_->UpdateNewConstraintsVariableUsage();
9549 if (num_new_cliques != num_old_cliques) {
9550 context_->UpdateRuleStats("at_most_one: transformed into max clique");
9551 }
9552
9553 if (num_old_cliques != num_new_cliques ||
9554 num_literals_before != num_literals_after) {
9555 SOLVER_LOG(logger_, "[MaxClique] Merged ", num_old_cliques, " with ",
9556 num_literals_before, " literals) into ", num_new_cliques, "(",
9557 num_literals_after, " literals) at_most_ones.");
9558 }
9559}
9560
9561void CpModelPresolver::TransformClausesToExactlyOne() {
9562 if (context_->ModelIsUnsat()) return;
9563 if (!context_->params().find_clauses_that_are_exactly_one()) return;
9564 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9565
9566 auto convert = [](int ref) {
9567 if (RefIsPositive(ref)) return Literal(BooleanVariable(ref), true);
9568 return Literal(BooleanVariable(NegatedRef(ref)), false);
9569 };
9570 const int num_constraints = context_->working_model->constraints_size();
9571
9572 // We reuse the BinaryImplicationGraph code to "propagate" 2-SAT.
9573 Model local_model;
9574 const int num_variables = context_->working_model->variables().size();
9575 local_model.GetOrCreate<Trail>()->Resize(num_variables);
9576 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
9577 graph->Resize(num_variables);
9578
9579 // Extract the bool_and and at_most_one constraints.
9580 // TODO(user): use probing info?
9581 int num_amos = 0;
9582 std::vector<Literal> tmp_clique;
9583 std::vector<int> clause_indices;
9584 std::vector<std::vector<Literal>> clauses;
9585 for (int c = 0; c < num_constraints; ++c) {
9586 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9587 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
9588 tmp_clique.clear();
9589 for (const int ref : ct->at_most_one().literals()) {
9590 tmp_clique.push_back(convert(ref));
9591 }
9592 ++num_amos;
9593 if (!graph->AddAtMostOne(tmp_clique)) {
9594 return (void)context_->NotifyThatModelIsUnsat();
9595 }
9596 } else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
9597 if (ct->enforcement_literal().size() != 1) continue;
9598 const Literal enforcement = convert(ct->enforcement_literal(0));
9599 for (const int ref : ct->bool_and().literals()) {
9600 if (ref == ct->enforcement_literal(0)) continue;
9601 ++num_amos;
9602 if (!graph->AddAtMostOne({enforcement, convert(ref).Negated()})) {
9603 return (void)context_->NotifyThatModelIsUnsat();
9604 }
9605 }
9606 } else if (ct->constraint_case() == ConstraintProto::kBoolOr) {
9607 if (!ct->enforcement_literal().empty()) continue;
9608 clause_indices.push_back(c);
9609 std::vector<Literal> clause;
9610 clause.reserve(ct->bool_or().literals().size());
9611 for (const int ref : ct->bool_or().literals()) {
9612 clause.push_back(convert(ref));
9613 }
9614 clauses.push_back(std::move(clause));
9615 }
9616 }
9617
9618 if (!graph->DetectEquivalences()) {
9619 return (void)context_->NotifyThatModelIsUnsat();
9620 }
9621
9622 // Add the Boolean variable equivalence detected by DetectEquivalences().
9623 // Those are needed because TransformIntoMaxCliques() will replace all
9624 // variable by its representative.
9625 for (int var = 0; var < num_variables; ++var) {
9626 const Literal l = Literal(BooleanVariable(var), true);
9627 if (graph->RepresentativeOf(l) != l) {
9628 const Literal r = graph->RepresentativeOf(l);
9629 if (!context_->StoreBooleanEqualityRelation(
9630 var, r.IsPositive() ? r.Variable().value()
9631 : NegatedRef(r.Variable().value()))) {
9632 return;
9633 }
9634 }
9635 }
9636
9637 auto signature = [](absl::Span<const Literal> literals) {
9638 uint64_t result = 0;
9639 for (const Literal l : literals) {
9640 result |= (l.Index().value()) & 63;
9641 }
9642 return result;
9643 };
9644 auto implied_signature = [](absl::Span<const Literal> literals) {
9645 uint64_t result = literals[0].Index().value() & 63;
9646 for (const Literal l : literals.subspan(1)) {
9647 result |= (l.NegatedIndex().value()) & 63;
9648 }
9649 return result;
9650 };
9651
9652 // Probe variables (using only amo graph) and filter clauses.
9653 //
9654 // TODO(user): be faster. with one "probing" we can look at all the clauses
9655 // containing that literal and filter them.
9656 int num_transformed = 0;
9657 int num_checked = 0;
9658 util_intops::StrongVector<LiteralIndex, int> count(2 * num_variables, 0);
9659 util_intops::StrongVector<LiteralIndex, int> signatures(2 * num_variables, 0);
9660 for (int i = 0; i < clauses.size(); ++i) {
9661 ++num_checked;
9662 bool is_exo = true;
9663 const int clause_size = clauses[i].size();
9664
9665 // First heuristic scan.
9666 timer.TrackSimpleLoop(clause_size);
9667 const uint64_t clause_signature = signature(clauses[i]);
9668 for (const Literal l : clauses[i]) {
9669 if (count[l] == 0) continue;
9670 if (count[l] < clause_size || (clause_signature & ~signatures[l])) {
9671 is_exo = false;
9672 break;
9673 }
9674 }
9675 if (!is_exo) continue;
9676
9677 timer.TrackSimpleLoop(clause_size);
9678 for (const Literal l : clauses[i]) {
9679 graph->ResetWorkDone();
9680 absl::Span<const Literal> implied = graph->GetAllImpliedLiterals(l);
9681 CHECK_GT(implied.size(), 0); // Always contain l.
9682 count[l] = implied.size();
9683 signatures[l] = implied_signature(implied);
9684 timer.AddToWork(graph->WorkDone() * 1e-9);
9685 if (implied.size() < clause_size || (clause_signature & ~signatures[l])) {
9686 is_exo = false;
9687 break;
9688 }
9689 timer.TrackSimpleLoop(clause_size);
9690 for (const Literal o : clauses[i]) {
9691 if (o == l) continue;
9692 if (!graph->LiteralIsImplied(o.Negated())) {
9693 is_exo = false;
9694 break;
9695 }
9696 }
9697 if (!is_exo) break;
9698 }
9699 if (is_exo) {
9700 ++num_transformed;
9701 context_->UpdateRuleStats("clauses: transformed into exactly one");
9702 google::protobuf::RepeatedField<int32_t> tmp =
9703 context_->working_model->constraints(clause_indices[i])
9704 .bool_or()
9705 .literals();
9706 *(context_->working_model->mutable_constraints(clause_indices[i])
9707 ->mutable_exactly_one()
9708 ->mutable_literals()) = tmp;
9709 }
9710 if (timer.WorkLimitIsReached()) break;
9711 }
9712
9713 timer.AddCounter("num_amos", num_amos);
9714 timer.AddCounter("num_clauses", clauses.size());
9715 timer.AddCounter("num_transformed", num_transformed);
9716 timer.AddCounter("num_checked", num_checked);
9717}
9718
9720 if (context_->ModelIsUnsat()) return false;
9721 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9722
9723 // Generic presolve to exploit variable/literal equivalence.
9724 if (ExploitEquivalenceRelations(c, ct)) {
9725 context_->UpdateConstraintVariableUsage(c);
9726 }
9727
9728 // Generic presolve for reified constraint.
9729 if (PresolveEnforcementLiteral(ct)) {
9730 context_->UpdateConstraintVariableUsage(c);
9731 }
9732
9733 // Call the presolve function for this constraint if any.
9734 switch (ct->constraint_case()) {
9736 return PresolveBoolOr(ct);
9738 return PresolveBoolAnd(ct);
9740 return PresolveAtMostOne(ct);
9742 return PresolveExactlyOne(ct);
9744 return PresolveBoolXor(ct);
9746 if (CanonicalizeLinearArgument(*ct, ct->mutable_lin_max())) {
9747 context_->UpdateConstraintVariableUsage(c);
9748 }
9749 return PresolveLinMax(c, ct);
9751 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_prod())) {
9752 context_->UpdateConstraintVariableUsage(c);
9753 }
9754 return PresolveIntProd(ct);
9756 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_div())) {
9757 context_->UpdateConstraintVariableUsage(c);
9758 }
9759 return PresolveIntDiv(c, ct);
9761 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_mod())) {
9762 context_->UpdateConstraintVariableUsage(c);
9763 }
9764 return PresolveIntMod(c, ct);
9766 bool changed = false;
9767 if (!CanonicalizeLinear(ct, &changed)) {
9768 return true;
9769 }
9770 if (changed) {
9771 context_->UpdateConstraintVariableUsage(c);
9772 }
9773 if (PropagateDomainsInLinear(c, ct)) {
9774 context_->UpdateConstraintVariableUsage(c);
9775 }
9776 if (PresolveSmallLinear(ct)) {
9777 context_->UpdateConstraintVariableUsage(c);
9778 }
9779 if (PresolveLinearEqualityWithModulo(ct)) {
9780 context_->UpdateConstraintVariableUsage(c);
9781 }
9782 // We first propagate the domains before calling this presolve rule.
9783 if (RemoveSingletonInLinear(ct)) {
9784 context_->UpdateConstraintVariableUsage(c);
9785
9786 // There is no need to re-do a propagation here, but the constraint
9787 // size might have been reduced.
9788 if (PresolveSmallLinear(ct)) {
9789 context_->UpdateConstraintVariableUsage(c);
9790 }
9791 }
9792 if (PresolveSmallLinear(ct)) {
9793 context_->UpdateConstraintVariableUsage(c);
9794 }
9795 if (PresolveLinearOnBooleans(ct)) {
9796 context_->UpdateConstraintVariableUsage(c);
9797 }
9798
9799 // If we extracted some enforcement, we redo some presolve.
9800 const int old_num_enforcement_literals = ct->enforcement_literal_size();
9801 ExtractEnforcementLiteralFromLinearConstraint(c, ct);
9802 if (context_->ModelIsUnsat()) return false;
9803 if (ct->enforcement_literal_size() > old_num_enforcement_literals) {
9804 if (DivideLinearByGcd(ct)) {
9805 context_->UpdateConstraintVariableUsage(c);
9806 }
9807 if (PresolveSmallLinear(ct)) {
9808 context_->UpdateConstraintVariableUsage(c);
9809 }
9810 }
9811
9812 if (PresolveDiophantine(ct)) {
9813 context_->UpdateConstraintVariableUsage(c);
9814 }
9815
9816 TryToReduceCoefficientsOfLinearConstraint(c, ct);
9817 return false;
9818 }
9820 return PresolveInterval(c, ct);
9822 return PresolveInverse(ct);
9824 return PresolveElement(c, ct);
9826 return PresolveTable(ct);
9828 return PresolveAllDiff(ct);
9830 DetectDuplicateIntervals(c,
9832 return PresolveNoOverlap(ct);
9834 const bool changed = PresolveNoOverlap2D(c, ct);
9836 // For 2D, we don't exploit index duplication between x/y so it is not
9837 // important to do it beforehand. Moreover in some situation
9838 // PresolveNoOverlap2D() remove a lot of interval, so better to do it
9839 // afterwards.
9840 DetectDuplicateIntervals(
9842 DetectDuplicateIntervals(
9844 }
9845 return changed;
9846 }
9848 DetectDuplicateIntervals(c,
9850 return PresolveCumulative(ct);
9852 return PresolveCircuit(ct);
9854 return PresolveRoutes(ct);
9856 return PresolveAutomaton(ct);
9858 return PresolveReservoir(ct);
9859 default:
9860 return false;
9861 }
9862}
9863
9864// Returns false iff the model is UNSAT.
9865bool CpModelPresolver::ProcessSetPPCSubset(int subset_c, int superset_c,
9866 absl::flat_hash_set<int>* tmp_set,
9867 bool* remove_subset,
9868 bool* remove_superset,
9869 bool* stop_processing_superset) {
9870 ConstraintProto* subset_ct =
9871 context_->working_model->mutable_constraints(subset_c);
9872 ConstraintProto* superset_ct =
9873 context_->working_model->mutable_constraints(superset_c);
9874
9875 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
9877 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
9878 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
9879 context_->UpdateRuleStats("setppc: bool_or in at_most_one");
9880
9881 tmp_set->clear();
9882 if (subset_ct->constraint_case() == ConstraintProto::kBoolOr) {
9883 tmp_set->insert(subset_ct->bool_or().literals().begin(),
9884 subset_ct->bool_or().literals().end());
9885 } else {
9886 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
9887 subset_ct->exactly_one().literals().end());
9888 }
9889
9890 // Fix extras in superset_c to 0, note that these will be removed from the
9891 // constraint later.
9892 for (const int literal :
9894 ? superset_ct->at_most_one().literals()
9895 : superset_ct->exactly_one().literals()) {
9896 if (tmp_set->contains(literal)) continue;
9897 if (!context_->SetLiteralToFalse(literal)) return false;
9898 context_->UpdateRuleStats("setppc: fixed variables");
9899 }
9900
9901 // Change superset_c to exactly_one if not already.
9902 if (superset_ct->constraint_case() != ConstraintProto::kExactlyOne) {
9903 ConstraintProto copy = *superset_ct;
9904 (*superset_ct->mutable_exactly_one()->mutable_literals()) =
9905 copy.at_most_one().literals();
9906 }
9907
9908 *remove_subset = true;
9909 return true;
9910 }
9911
9912 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
9914 superset_ct->constraint_case() == ConstraintProto::kBoolOr) {
9915 context_->UpdateRuleStats("setppc: removed dominated constraints");
9916 *remove_superset = true;
9917 return true;
9918 }
9919
9920 if (subset_ct->constraint_case() == ConstraintProto::kAtMostOne &&
9921 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
9922 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
9923 context_->UpdateRuleStats("setppc: removed dominated constraints");
9924 *remove_subset = true;
9925 return true;
9926 }
9927
9928 // Note(user): Only the exactly one should really be needed, the intersection
9929 // is taken care of by ProcessAtMostOneAndLinear() in a better way.
9930 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne &&
9931 superset_ct->constraint_case() == ConstraintProto::kLinear) {
9932 tmp_set->clear();
9933 int64_t min_sum = std::numeric_limits<int64_t>::max();
9934 int64_t max_sum = std::numeric_limits<int64_t>::min();
9935 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
9936 subset_ct->exactly_one().literals().end());
9937
9938 // Compute the min/max on the subset of the sum that correspond the exo.
9939 int num_matches = 0;
9940 temp_ct_.Clear();
9941 Domain reachable(0);
9942 std::vector<std::pair<int64_t, int>> coeff_counts;
9943 for (int i = 0; i < superset_ct->linear().vars().size(); ++i) {
9944 const int var = superset_ct->linear().vars(i);
9945 const int64_t coeff = superset_ct->linear().coeffs(i);
9946 if (tmp_set->contains(var)) {
9947 ++num_matches;
9948 min_sum = std::min(min_sum, coeff);
9949 max_sum = std::max(max_sum, coeff);
9950 coeff_counts.push_back({superset_ct->linear().coeffs(i), 1});
9951 } else {
9952 reachable =
9953 reachable
9954 .AdditionWith(
9955 context_->DomainOf(var).ContinuousMultiplicationBy(coeff))
9956 .RelaxIfTooComplex();
9957 temp_ct_.mutable_linear()->add_vars(var);
9958 temp_ct_.mutable_linear()->add_coeffs(coeff);
9959 }
9960 }
9961
9962 // If a linear constraint contains more than one at_most_one or exactly_one,
9963 // after processing one, we might no longer have an inclusion.
9964 //
9965 // TODO(user): If we have multiple disjoint inclusion, we can propagate
9966 // more. For instance on neos-1593097.mps we basically have a
9967 // weighted_sum_over_at_most_one1 >= weighted_sum_over_at_most_one2.
9968 if (num_matches != tmp_set->size()) return true;
9969 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne) {
9970 context_->UpdateRuleStats("setppc: exactly_one included in linear");
9971 } else {
9972 context_->UpdateRuleStats("setppc: at_most_one included in linear");
9973 }
9974
9975 reachable = reachable.AdditionWith(Domain(min_sum, max_sum));
9976 const Domain superset_rhs = ReadDomainFromProto(superset_ct->linear());
9977 if (reachable.IsIncludedIn(superset_rhs)) {
9978 // The constraint is trivial !
9979 context_->UpdateRuleStats("setppc: removed trivial linear constraint");
9980 *remove_superset = true;
9981 return true;
9982 }
9983 if (reachable.IntersectionWith(superset_rhs).IsEmpty()) {
9984 // TODO(user): constraint might become bool_or.
9985 *stop_processing_superset = true;
9986 return MarkConstraintAsFalse(
9987 superset_ct, "setppc: removed infeasible linear constraint");
9988 }
9989
9990 // We reuse the normal linear constraint code to propagate domains of
9991 // the other variable using the inclusion information.
9992 if (superset_ct->enforcement_literal().empty()) {
9993 CHECK_GT(num_matches, 0);
9995 .AdditionWith(Domain(-max_sum, -min_sum)),
9996 temp_ct_.mutable_linear());
9997 PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_);
9998 }
9999
10000 // If we have an exactly one in a linear, we can shift the coefficients of
10001 // all these variables by any constant value. We select a value that reduces
10002 // the number of terms the most.
10003 std::sort(coeff_counts.begin(), coeff_counts.end());
10004 int new_size = 0;
10005 for (int i = 0; i < coeff_counts.size(); ++i) {
10006 if (new_size > 0 &&
10007 coeff_counts[i].first == coeff_counts[new_size - 1].first) {
10008 coeff_counts[new_size - 1].second++;
10009 continue;
10010 }
10011 coeff_counts[new_size++] = coeff_counts[i];
10012 }
10013 coeff_counts.resize(new_size);
10014 int64_t best = 0;
10015 int64_t best_count = 0;
10016 for (const auto [coeff, count] : coeff_counts) {
10017 if (count > best_count) {
10018 best = coeff;
10019 best_count = count;
10020 }
10021 }
10022 if (best != 0) {
10023 LinearConstraintProto new_ct = superset_ct->linear();
10024 int new_size = 0;
10025 for (int i = 0; i < new_ct.vars().size(); ++i) {
10026 const int var = new_ct.vars(i);
10027 int64_t coeff = new_ct.coeffs(i);
10028 if (tmp_set->contains(var)) {
10029 if (coeff == best) continue; // delete term.
10030 coeff -= best;
10031 }
10032 new_ct.set_vars(new_size, var);
10033 new_ct.set_coeffs(new_size, coeff);
10034 ++new_size;
10035 }
10036
10037 new_ct.mutable_vars()->Truncate(new_size);
10038 new_ct.mutable_coeffs()->Truncate(new_size);
10039 FillDomainInProto(ReadDomainFromProto(new_ct).AdditionWith(Domain(-best)),
10040 &new_ct);
10041 if (!PossibleIntegerOverflow(*context_->working_model, new_ct.vars(),
10042 new_ct.coeffs())) {
10043 *superset_ct->mutable_linear() = std::move(new_ct);
10044 context_->UpdateConstraintVariableUsage(superset_c);
10045 context_->UpdateRuleStats("setppc: reduced linear coefficients");
10046 }
10047 }
10048
10049 return true;
10050 }
10051
10052 // We can't deduce anything in the last remaining cases, like an at most one
10053 // in an at least one.
10054 return true;
10055}
10056
10057// TODO(user): TransformIntoMaxCliques() convert the bool_and to
10058// at_most_one, but maybe also duplicating them into bool_or would allow this
10059// function to do more presolving.
10060//
10061// TODO(user): If an exactly_one of size n and a clause/amo share n - 1 terms,
10062// then we can simplify the clause by using the last term of the exactly_one
10063// inside it instead.
10064void CpModelPresolver::ProcessSetPPC() {
10065 if (time_limit_->LimitReached()) return;
10066 if (context_->ModelIsUnsat()) return;
10067 if (context_->params().presolve_inclusion_work_limit() == 0) return;
10068 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10069
10070 // TODO(user): compute on the fly instead of temporary storing variables?
10071 CompactVectorVector<int> storage;
10072 InclusionDetector detector(storage, time_limit_);
10073 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
10074
10075 // We use an encoding of literal that allows to index arrays.
10076 std::vector<int> temp_literals;
10077 const int num_constraints = context_->working_model->constraints_size();
10078 std::vector<int> relevant_constraints;
10079 for (int c = 0; c < num_constraints; ++c) {
10080 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
10081 const auto type = ct->constraint_case();
10082 if (type == ConstraintProto::kBoolOr ||
10085 // Because TransformIntoMaxCliques() can detect literal equivalence
10086 // relation, we make sure the constraints are presolved before being
10087 // inspected.
10088 if (PresolveOneConstraint(c)) {
10089 context_->UpdateConstraintVariableUsage(c);
10090 }
10091 if (context_->ModelIsUnsat()) return;
10092
10093 temp_literals.clear();
10094 for (const int ref :
10095 type == ConstraintProto::kAtMostOne ? ct->at_most_one().literals()
10096 : type == ConstraintProto::kBoolOr ? ct->bool_or().literals()
10097 : ct->exactly_one().literals()) {
10098 temp_literals.push_back(
10099 Literal(BooleanVariable(PositiveRef(ref)), RefIsPositive(ref))
10100 .Index()
10101 .value());
10102 }
10103 relevant_constraints.push_back(c);
10104 detector.AddPotentialSet(storage.Add(temp_literals));
10105 } else if (type == ConstraintProto::kLinear) {
10106 // We also want to test inclusion with the pseudo-Boolean part of
10107 // linear constraints of size at least 3. Exactly one of size two are
10108 // equivalent literals, and we already deal with this case.
10109 //
10110 // TODO(user): This is not ideal as we currently only process exactly one
10111 // included into linear, and we add overhead by detecting all the other
10112 // cases that we ignore later. That said, we could just propagate a bit
10113 // more the domain if we know at_least_one or at_most_one between literals
10114 // in a linear constraint.
10115 const int size = ct->linear().vars().size();
10116 if (size <= 2) continue;
10117
10118 // TODO(user): We only deal with positive var here. Ideally we should
10119 // match the VARIABLES of the at_most_one/exactly_one with the VARIABLES
10120 // of the linear, and complement all variable to have a literal inclusion.
10121 temp_literals.clear();
10122 for (int i = 0; i < size; ++i) {
10123 const int var = ct->linear().vars(i);
10124 if (!context_->CanBeUsedAsLiteral(var)) continue;
10125 if (!RefIsPositive(var)) continue;
10126 temp_literals.push_back(
10127 Literal(BooleanVariable(var), true).Index().value());
10128 }
10129 if (temp_literals.size() > 2) {
10130 // Note that we only care about the linear being the superset.
10131 relevant_constraints.push_back(c);
10132 detector.AddPotentialSuperset(storage.Add(temp_literals));
10133 }
10134 }
10135 }
10136
10137 absl::flat_hash_set<int> tmp_set;
10138 int64_t num_inclusions = 0;
10139 detector.DetectInclusions([&](int subset, int superset) {
10140 ++num_inclusions;
10141 bool remove_subset = false;
10142 bool remove_superset = false;
10143 bool stop_processing_superset = false;
10144 const int subset_c = relevant_constraints[subset];
10145 const int superset_c = relevant_constraints[superset];
10146 detector.IncreaseWorkDone(storage[subset].size());
10147 detector.IncreaseWorkDone(storage[superset].size());
10148 if (!ProcessSetPPCSubset(subset_c, superset_c, &tmp_set, &remove_subset,
10149 &remove_superset, &stop_processing_superset)) {
10150 detector.Stop();
10151 return;
10152 }
10153 if (remove_subset) {
10154 context_->working_model->mutable_constraints(subset_c)->Clear();
10155 context_->UpdateConstraintVariableUsage(subset_c);
10156 detector.StopProcessingCurrentSubset();
10157 }
10158 if (remove_superset) {
10159 context_->working_model->mutable_constraints(superset_c)->Clear();
10160 context_->UpdateConstraintVariableUsage(superset_c);
10161 detector.StopProcessingCurrentSuperset();
10162 }
10163 if (stop_processing_superset) {
10164 context_->UpdateConstraintVariableUsage(superset_c);
10165 detector.StopProcessingCurrentSuperset();
10166 }
10167 });
10168
10169 timer.AddToWork(detector.work_done() * 1e-9);
10170 timer.AddCounter("relevant_constraints", relevant_constraints.size());
10171 timer.AddCounter("num_inclusions", num_inclusions);
10172}
10173
10174void CpModelPresolver::DetectIncludedEnforcement() {
10175 if (time_limit_->LimitReached()) return;
10176 if (context_->ModelIsUnsat()) return;
10177 if (context_->params().presolve_inclusion_work_limit() == 0) return;
10178 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10179
10180 // TODO(user): compute on the fly instead of temporary storing variables?
10181 std::vector<int> relevant_constraints;
10182 CompactVectorVector<int> storage;
10183 InclusionDetector detector(storage, time_limit_);
10184 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
10185
10186 std::vector<int> temp_literals;
10187 const int num_constraints = context_->working_model->constraints_size();
10188 for (int c = 0; c < num_constraints; ++c) {
10189 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
10190 if (ct->enforcement_literal().size() <= 1) continue;
10191
10192 // Make sure there is no x => x.
10193 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
10194 if (PresolveOneConstraint(c)) {
10195 context_->UpdateConstraintVariableUsage(c);
10196 }
10197 if (context_->ModelIsUnsat()) return;
10198 }
10199
10200 // We use an encoding of literal that allows to index arrays.
10201 temp_literals.clear();
10202 for (const int ref : ct->enforcement_literal()) {
10203 temp_literals.push_back(
10204 Literal(BooleanVariable(PositiveRef(ref)), RefIsPositive(ref))
10205 .Index()
10206 .value());
10207 }
10208 relevant_constraints.push_back(c);
10209
10210 // We only deal with bool_and included in other. Not the other way around,
10211 // Altough linear enforcement included in bool_and does happen.
10212 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
10213 detector.AddPotentialSet(storage.Add(temp_literals));
10214 } else {
10215 detector.AddPotentialSuperset(storage.Add(temp_literals));
10216 }
10217 }
10218
10219 int64_t num_inclusions = 0;
10220 detector.DetectInclusions([&](int subset, int superset) {
10221 ++num_inclusions;
10222 const int subset_c = relevant_constraints[subset];
10223 const int superset_c = relevant_constraints[superset];
10224 ConstraintProto* subset_ct =
10225 context_->working_model->mutable_constraints(subset_c);
10226 ConstraintProto* superset_ct =
10227 context_->working_model->mutable_constraints(superset_c);
10228 if (subset_ct->constraint_case() != ConstraintProto::kBoolAnd) return;
10229
10230 context_->tmp_literal_set.clear();
10231 for (const int ref : subset_ct->bool_and().literals()) {
10232 context_->tmp_literal_set.insert(ref);
10233 }
10234
10235 // Filter superset enforcement.
10236 {
10237 int new_size = 0;
10238 for (const int ref : superset_ct->enforcement_literal()) {
10239 if (context_->tmp_literal_set.contains(ref)) {
10240 context_->UpdateRuleStats("bool_and: filtered enforcement");
10241 } else if (context_->tmp_literal_set.contains(NegatedRef(ref))) {
10242 context_->UpdateRuleStats("bool_and: never enforced");
10243 superset_ct->Clear();
10244 context_->UpdateConstraintVariableUsage(superset_c);
10245 detector.StopProcessingCurrentSuperset();
10246 return;
10247 } else {
10248 superset_ct->set_enforcement_literal(new_size++, ref);
10249 }
10250 }
10251 if (new_size < superset_ct->bool_and().literals().size()) {
10252 context_->UpdateConstraintVariableUsage(superset_c);
10253 superset_ct->mutable_enforcement_literal()->Truncate(new_size);
10254 }
10255 }
10256
10257 if (superset_ct->constraint_case() == ConstraintProto::kBoolAnd) {
10258 int new_size = 0;
10259 for (const int ref : superset_ct->bool_and().literals()) {
10260 if (context_->tmp_literal_set.contains(ref)) {
10261 context_->UpdateRuleStats("bool_and: filtered literal");
10262 } else if (context_->tmp_literal_set.contains(NegatedRef(ref))) {
10263 if (!MarkConstraintAsFalse(superset_ct, "bool_and: must be false"))
10264 return;
10265 context_->UpdateConstraintVariableUsage(superset_c);
10266 detector.StopProcessingCurrentSuperset();
10267 return;
10268 } else {
10269 superset_ct->mutable_bool_and()->set_literals(new_size++, ref);
10270 }
10271 }
10272 if (new_size < superset_ct->bool_and().literals().size()) {
10273 context_->UpdateConstraintVariableUsage(superset_c);
10274 superset_ct->mutable_bool_and()->mutable_literals()->Truncate(new_size);
10275 }
10276 }
10277
10278 if (superset_ct->constraint_case() == ConstraintProto::kLinear) {
10279 context_->UpdateRuleStats("TODO bool_and enforcement in linear enf");
10280 }
10281 });
10282
10283 timer.AddToWork(1e-9 * static_cast<double>(detector.work_done()));
10284 timer.AddCounter("relevant_constraints", relevant_constraints.size());
10285 timer.AddCounter("num_inclusions", num_inclusions);
10286}
10287
10288// Note that because we remove the linear constraint, this will not be called
10289// often, so it is okay to use "heavy" data structure here.
10290//
10291// TODO(user): in the at most one case, consider always creating an associated
10292// literal (l <=> var == rhs), and add the exactly_one = at_most_one U not(l)?
10293// This constraint is implicit from what we create, however internally we will
10294// not recover it easily, so we might not add the linear relaxation
10295// corresponding to the constraint we just removed.
10296bool CpModelPresolver::ProcessEncodingFromLinear(
10297 const int linear_encoding_ct_index,
10298 const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms,
10299 int64_t* num_multiple_terms) {
10300 // Preprocess exactly or at most one.
10301 bool in_exactly_one = false;
10302 absl::flat_hash_map<int, int> var_to_ref;
10303 if (at_most_or_exactly_one.constraint_case() == ConstraintProto::kAtMostOne) {
10304 for (const int ref : at_most_or_exactly_one.at_most_one().literals()) {
10305 CHECK(!var_to_ref.contains(PositiveRef(ref)));
10306 var_to_ref[PositiveRef(ref)] = ref;
10307 }
10308 } else {
10309 CHECK_EQ(at_most_or_exactly_one.constraint_case(),
10311 in_exactly_one = true;
10312 for (const int ref : at_most_or_exactly_one.exactly_one().literals()) {
10313 CHECK(!var_to_ref.contains(PositiveRef(ref)));
10314 var_to_ref[PositiveRef(ref)] = ref;
10315 }
10316 }
10317
10318 // Preprocess the linear constraints.
10319 const ConstraintProto& linear_encoding =
10320 context_->working_model->constraints(linear_encoding_ct_index);
10321 int64_t rhs = linear_encoding.linear().domain(0);
10322 int target_ref = std::numeric_limits<int>::min();
10323 std::vector<std::pair<int, int64_t>> ref_to_coeffs;
10324 const int num_terms = linear_encoding.linear().vars().size();
10325 for (int i = 0; i < num_terms; ++i) {
10326 const int ref = linear_encoding.linear().vars(i);
10327 const int64_t coeff = linear_encoding.linear().coeffs(i);
10328 const auto it = var_to_ref.find(PositiveRef(ref));
10329
10330 if (it == var_to_ref.end()) {
10331 CHECK_EQ(target_ref, std::numeric_limits<int>::min()) << "Uniqueness";
10332 CHECK_EQ(std::abs(coeff), 1);
10333 target_ref = coeff == 1 ? ref : NegatedRef(ref);
10334 continue;
10335 }
10336
10337 // We transform the constraint so that the Boolean reference match exactly
10338 // what is in the at most one.
10339 if (it->second == ref) {
10340 // The term in the constraint is the same as in the at_most_one.
10341 ref_to_coeffs.push_back({ref, coeff});
10342 } else {
10343 // We replace "coeff * ref" by "coeff - coeff * (1 - ref)"
10344 rhs -= coeff;
10345 ref_to_coeffs.push_back({NegatedRef(ref), -coeff});
10346 }
10347 }
10348 if (target_ref == std::numeric_limits<int>::min() ||
10349 context_->CanBeUsedAsLiteral(target_ref)) {
10350 // We didn't find the unique integer variable. This might have happenned
10351 // because by processing other encoding we might end up with a fully boolean
10352 // constraint. Just abort, it will be presolved later.
10353 context_->UpdateRuleStats("encoding: candidate linear is all boolean now");
10354 return true;
10355 }
10356
10357 // Extract the encoding.
10358 std::vector<int64_t> all_values;
10359 absl::btree_map<int64_t, std::vector<int>> value_to_refs;
10360 for (const auto& [ref, coeff] : ref_to_coeffs) {
10361 const int64_t value = rhs - coeff;
10362 all_values.push_back(value);
10363 value_to_refs[value].push_back(ref);
10364 var_to_ref.erase(PositiveRef(ref));
10365 }
10366 // The one not used "encodes" the rhs value.
10367 for (const auto& [var, ref] : var_to_ref) {
10368 all_values.push_back(rhs);
10369 value_to_refs[rhs].push_back(ref);
10370 }
10371 if (!in_exactly_one) {
10372 // To cover the corner case when the inclusion is an equality. For an at
10373 // most one, the rhs should be always reachable when all Boolean are false.
10374 all_values.push_back(rhs);
10375 }
10376
10377 // Make sure the target domain is up to date.
10378 const Domain new_domain = Domain::FromValues(all_values);
10379 bool domain_reduced = false;
10380 if (!context_->IntersectDomainWith(target_ref, new_domain, &domain_reduced)) {
10381 return false;
10382 }
10383 if (domain_reduced) {
10384 context_->UpdateRuleStats("encoding: reduced target domain");
10385 }
10386
10387 if (context_->CanBeUsedAsLiteral(target_ref)) {
10388 // If target is now a literal, lets not process it here.
10389 context_->UpdateRuleStats("encoding: candidate linear is all boolean now");
10390 return true;
10391 }
10392
10393 // Encode the encoding.
10394 absl::flat_hash_set<int64_t> value_set;
10395 const Domain target_domain =
10396 RefIsPositive(target_ref)
10397 ? context_->DomainOf(target_ref)
10398 : context_->DomainOf(NegatedRef(target_ref)).Negation();
10399 for (const int64_t v : target_domain.Values()) {
10400 value_set.insert(v);
10401 }
10402 for (auto& [value, literals] : value_to_refs) {
10403 // For determinism.
10404 absl::c_sort(literals);
10405
10406 // If the value is not in the domain, just set all literal to false.
10407 if (!value_set.contains(value)) {
10408 for (const int lit : literals) {
10409 if (!context_->SetLiteralToFalse(lit)) return false;
10410 }
10411 continue;
10412 }
10413
10414 if (literals.size() == 1 && (in_exactly_one || value != rhs)) {
10415 // Optimization if there is just one literal for this value.
10416 // Note that for the "at most one" case, we can't do that for the rhs.
10417 ++*num_unique_terms;
10418 if (!context_->InsertVarValueEncoding(literals[0], target_ref, value)) {
10419 return false;
10420 }
10421 } else {
10422 ++*num_multiple_terms;
10423 const int associated_lit =
10424 context_->GetOrCreateVarValueEncoding(target_ref, value);
10425 for (const int lit : literals) {
10426 context_->AddImplication(lit, associated_lit);
10427 }
10428
10429 // All false means associated_lit is false too.
10430 // But not for the rhs case if we are not in exactly one.
10431 if (in_exactly_one || value != rhs) {
10432 // TODO(user): Instead of bool_or + implications, we could add an
10433 // exactly one! Experiment with this. In particular it might capture
10434 // more structure for later heuristic to add the exactly one instead.
10435 // This also applies to automata/table/element expansion.
10436 auto* bool_or =
10437 context_->working_model->add_constraints()->mutable_bool_or();
10438 for (const int lit : literals) bool_or->add_literals(lit);
10439 bool_or->add_literals(NegatedRef(associated_lit));
10440 }
10441 }
10442 }
10443
10444 // Remove linear constraint now that it is fully encoded.
10445 context_->working_model->mutable_constraints(linear_encoding_ct_index)
10446 ->Clear();
10447 context_->UpdateNewConstraintsVariableUsage();
10448 context_->UpdateConstraintVariableUsage(linear_encoding_ct_index);
10449 return true;
10450}
10451
10454 CompactVectorVector<int, std::pair<int, int64_t>>* _column)
10455 : column(_column) {}
10456 std::size_t operator()(int c) const { return absl::HashOf((*column)[c]); }
10457
10459};
10460
10463 CompactVectorVector<int, std::pair<int, int64_t>>* _column)
10464 : column(_column) {}
10465 bool operator()(int a, int b) const {
10466 if (a == b) return true;
10467
10468 // We use absl::span<> comparison.
10469 return (*column)[a] == (*column)[b];
10470 }
10471
10473};
10474
10475// Note that our symmetry-detector will also identify full permutation group
10476// for these columns, but it is better to handle that even before. We can
10477// also detect variable with different domains but with indentical columns.
10479 if (time_limit_->LimitReached()) return;
10480 if (context_->ModelIsUnsat()) return;
10481 if (context_->params().keep_all_feasible_solutions_in_presolve()) return;
10482 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10483
10484 const int num_vars = context_->working_model->variables().size();
10485 const int num_constraints = context_->working_model->constraints().size();
10486
10487 // Our current implementation require almost a full copy.
10488 // First construct a transpose var to columns (constraint_index, coeff).
10489 std::vector<int> flat_vars;
10490 std::vector<std::pair<int, int64_t>> flat_terms;
10492
10493 // We will only support columns that include:
10494 // - objective
10495 // - linear (non-enforced part)
10496 // - at_most_one/exactly_one/clauses (but with positive variable only).
10497 //
10498 // TODO(user): deal with enforcement_literal, especially bool_and. It is a bit
10499 // annoying to have to deal with all kind of constraints. Maybe convert
10500 // bool_and to at_most_one first? We already do that in other places. Note
10501 // however that an at most one of size 2 means at most 2 columns can be
10502 // identical. If we have a bool and with many term on the left, all column
10503 // could be indentical, but we have to linearize the constraint first.
10504 std::vector<bool> appear_in_amo(num_vars, false);
10505 std::vector<bool> appear_in_bool_constraint(num_vars, false);
10506 for (int c = 0; c < num_constraints; ++c) {
10507 const ConstraintProto& ct = context_->working_model->constraints(c);
10508 absl::Span<const int> literals;
10509
10510 bool is_amo = false;
10512 is_amo = true;
10513 literals = ct.at_most_one().literals();
10514 } else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
10515 is_amo = true; // That works here.
10516 literals = ct.exactly_one().literals();
10517 } else if (ct.constraint_case() == ConstraintProto::kBoolOr) {
10518 literals = ct.bool_or().literals();
10519 }
10520
10521 if (!literals.empty()) {
10522 for (const int lit : literals) {
10523 // It is okay to ignore terms (the columns will not be full).
10524 if (!RefIsPositive(lit)) continue;
10525 if (is_amo) appear_in_amo[lit] = true;
10526 appear_in_bool_constraint[lit] = true;
10527 flat_vars.push_back(lit);
10528 flat_terms.push_back({c, 1});
10529 }
10530 continue;
10531 }
10532
10534 const int num_terms = ct.linear().vars().size();
10535 for (int i = 0; i < num_terms; ++i) {
10536 const int var = ct.linear().vars(i);
10537 const int64_t coeff = ct.linear().coeffs(i);
10538 flat_vars.push_back(var);
10539 flat_terms.push_back({c, coeff});
10540 }
10541 continue;
10542 }
10543 }
10544
10545 // Use kObjectiveConstraint (-1) for the objective.
10546 //
10547 // TODO(user): deal with equivalent column with different objective value.
10548 // It might not be easy to presolve, but we can at least have a single
10549 // variable = sum of var appearing only in objective. And we can transfer the
10550 // min cost.
10551 if (context_->working_model->has_objective()) {
10552 context_->WriteObjectiveToProto();
10553 const int num_terms = context_->working_model->objective().vars().size();
10554 for (int i = 0; i < num_terms; ++i) {
10555 const int var = context_->working_model->objective().vars(i);
10556 const int64_t coeff = context_->working_model->objective().coeffs(i);
10557 flat_vars.push_back(var);
10558 flat_terms.push_back({kObjectiveConstraint, coeff});
10559 }
10560 }
10561
10562 // Now construct the graph.
10563 var_to_columns.ResetFromFlatMapping(flat_vars, flat_terms);
10564
10565 // Find duplicate columns using an hash map.
10566 // We only consider "full" columns.
10567 // var -> var_representative using columns hash/comparison.
10568 absl::flat_hash_map<int, int, ColumnHashForDuplicateDetection,
10570 duplicates(
10571 /*capacity=*/num_vars,
10572 ColumnHashForDuplicateDetection(&var_to_columns),
10573 ColumnEqForDuplicateDetection(&var_to_columns));
10574 std::vector<int> flat_duplicates;
10575 std::vector<int> flat_representatives;
10576 for (int var = 0; var < var_to_columns.size(); ++var) {
10577 const int size_seen = var_to_columns[var].size();
10578 if (size_seen == 0) continue;
10579 if (size_seen != context_->VarToConstraints(var).size()) continue;
10580
10581 // TODO(user): If we have duplicate columns appearing in Boolean constraint
10582 // we can only easily substitute if the sum of columns is a Boolean (i.e. if
10583 // it appear in an at most one or exactly one). Otherwise we will need to
10584 // transform such constraint to linear, do that?
10585 if (appear_in_bool_constraint[var] && !appear_in_amo[var]) {
10586 context_->UpdateRuleStats(
10587 "TODO duplicate: duplicate columns in Boolean constraints");
10588 continue;
10589 }
10590
10591 const auto [it, inserted] = duplicates.insert({var, var});
10592 if (!inserted) {
10593 flat_duplicates.push_back(var);
10594 flat_representatives.push_back(it->second);
10595 }
10596 }
10597
10598 // Process duplicates.
10599 int num_equivalent_classes = 0;
10601 rep_to_dups.ResetFromFlatMapping(flat_representatives, flat_duplicates);
10602 std::vector<std::pair<int, int64_t>> definition;
10603 std::vector<int> var_to_remove;
10604 std::vector<int> var_to_rep(num_vars, -1);
10605 for (int var = 0; var < rep_to_dups.size(); ++var) {
10606 if (rep_to_dups[var].empty()) continue;
10607
10608 // Since columns are the same, we can introduce a new variable = sum all
10609 // columns. Note that the linear expression will not overflow, but the
10610 // overflow check also requires that max_sum < int_max/2, which might
10611 // happen.
10612 //
10613 // In the corner case where there is a lot of holes in the domain, and the
10614 // sum domain is too complex, we skip. Hopefully this should be rare.
10615 definition.clear();
10616 definition.push_back({var, 1});
10617 Domain domain = context_->DomainOf(var);
10618 for (const int other_var : rep_to_dups[var]) {
10619 definition.push_back({other_var, 1});
10620 domain = domain.AdditionWith(context_->DomainOf(other_var));
10621 if (domain.NumIntervals() > 100) break;
10622 }
10623 if (domain.NumIntervals() > 100) {
10624 context_->UpdateRuleStats(
10625 "TODO duplicate: domain of the sum is too complex");
10626 continue;
10627 }
10628 if (appear_in_amo[var]) {
10629 domain = domain.IntersectionWith(Domain(0, 1));
10630 }
10631 const int new_var = context_->NewIntVarWithDefinition(
10632 domain, definition, /*append_constraint_to_mapping_model=*/true);
10633 if (new_var == -1) {
10634 context_->UpdateRuleStats("TODO duplicate: possible overflow");
10635 continue;
10636 }
10637
10638 var_to_remove.push_back(var);
10639 CHECK_EQ(var_to_rep[var], -1);
10640 var_to_rep[var] = new_var;
10641 for (const int other_var : rep_to_dups[var]) {
10642 var_to_remove.push_back(other_var);
10643 CHECK_EQ(var_to_rep[other_var], -1);
10644 var_to_rep[other_var] = new_var;
10645 }
10646
10647 // Deal with objective right away.
10648 const int64_t obj_coeff = context_->ObjectiveCoeff(var);
10649 if (obj_coeff != 0) {
10650 context_->RemoveVariableFromObjective(var);
10651 for (const int other_var : rep_to_dups[var]) {
10652 CHECK_EQ(context_->ObjectiveCoeff(other_var), obj_coeff);
10653 context_->RemoveVariableFromObjective(other_var);
10654 }
10655 context_->AddToObjective(new_var, obj_coeff);
10656 }
10657
10658 num_equivalent_classes++;
10659 }
10660
10661 // Lets rescan the model, and remove all variables, replacing them by
10662 // the sum. We do that in one O(model size) pass.
10663 if (!var_to_remove.empty()) {
10664 absl::flat_hash_set<int> seen;
10665 std::vector<std::pair<int, int64_t>> new_terms;
10666 for (int c = 0; c < num_constraints; ++c) {
10667 ConstraintProto* mutable_ct =
10668 context_->working_model->mutable_constraints(c);
10669
10670 seen.clear();
10671 new_terms.clear();
10672
10673 // Deal with bool case.
10674 // TODO(user): maybe converting to linear + single code is better?
10675 BoolArgumentProto* mutable_arg = nullptr;
10676 if (mutable_ct->constraint_case() == ConstraintProto::kAtMostOne) {
10677 mutable_arg = mutable_ct->mutable_at_most_one();
10678 } else if (mutable_ct->constraint_case() ==
10680 mutable_arg = mutable_ct->mutable_exactly_one();
10681 } else if (mutable_ct->constraint_case() == ConstraintProto::kBoolOr) {
10682 mutable_arg = mutable_ct->mutable_bool_or();
10683 }
10684 if (mutable_arg != nullptr) {
10685 int new_size = 0;
10686 const int num_terms = mutable_arg->literals().size();
10687 for (int i = 0; i < num_terms; ++i) {
10688 const int lit = mutable_arg->literals(i);
10689 const int rep = var_to_rep[PositiveRef(lit)];
10690 if (rep != -1) {
10691 CHECK(RefIsPositive(lit));
10692 const auto [_, inserted] = seen.insert(rep);
10693 if (inserted) new_terms.push_back({rep, 1});
10694 continue;
10695 }
10696 mutable_arg->set_literals(new_size, lit);
10697 ++new_size;
10698 }
10699 if (new_size == num_terms) continue; // skip.
10700
10701 // TODO(user): clear amo/exo of size 1.
10702 mutable_arg->mutable_literals()->Truncate(new_size);
10703 for (const auto [var, coeff] : new_terms) {
10704 mutable_arg->add_literals(var);
10705 }
10706 context_->UpdateConstraintVariableUsage(c);
10707 continue;
10708 }
10709
10710 // Deal with linear case.
10711 if (mutable_ct->constraint_case() == ConstraintProto::kLinear) {
10712 int new_size = 0;
10713 LinearConstraintProto* mutable_linear = mutable_ct->mutable_linear();
10714 const int num_terms = mutable_linear->vars().size();
10715 for (int i = 0; i < num_terms; ++i) {
10716 const int var = mutable_linear->vars(i);
10717 const int64_t coeff = mutable_linear->coeffs(i);
10718 const int rep = var_to_rep[var];
10719 if (rep != -1) {
10720 const auto [_, inserted] = seen.insert(rep);
10721 if (inserted) new_terms.push_back({rep, coeff});
10722 continue;
10723 }
10724 mutable_linear->set_vars(new_size, var);
10725 mutable_linear->set_coeffs(new_size, coeff);
10726 ++new_size;
10727 }
10728 if (new_size == num_terms) continue; // skip.
10729
10730 mutable_linear->mutable_vars()->Truncate(new_size);
10731 mutable_linear->mutable_coeffs()->Truncate(new_size);
10732 for (const auto [var, coeff] : new_terms) {
10733 mutable_linear->add_vars(var);
10734 mutable_linear->add_coeffs(coeff);
10735 }
10736 context_->UpdateConstraintVariableUsage(c);
10737 continue;
10738 }
10739 }
10740 }
10741
10742 // We removed all occurrence of "var_to_remove" so we can remove them now.
10743 // Note that since we introduce a new variable per equivalence class, we
10744 // remove one less for each equivalent class.
10745 const int num_var_reduction = var_to_remove.size() - num_equivalent_classes;
10746 for (const int var : var_to_remove) {
10747 CHECK(context_->VarToConstraints(var).empty());
10748 context_->MarkVariableAsRemoved(var);
10749 }
10750 if (num_var_reduction > 0) {
10751 context_->UpdateRuleStats("duplicate: removed duplicated column",
10752 num_var_reduction);
10753 }
10754
10755 timer.AddCounter("num_equiv_classes", num_equivalent_classes);
10756 timer.AddCounter("num_removed_vars", num_var_reduction);
10757}
10758
10759void CpModelPresolver::DetectDuplicateConstraints() {
10760 if (time_limit_->LimitReached()) return;
10761 if (context_->ModelIsUnsat()) return;
10762 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10763
10764 // We need the objective written for this.
10765 if (context_->working_model->has_objective()) {
10766 if (!context_->CanonicalizeObjective()) return;
10767 context_->WriteObjectiveToProto();
10768 }
10769
10770 // If we detect duplicate intervals, we will remap constraints using them.
10771 std::vector<int> interval_mapping;
10772
10773 // Remove duplicate constraints.
10774 // Note that at this point the objective in the proto should be up to date.
10775 //
10776 // TODO(user): We might want to do that earlier so that our count of variable
10777 // usage is not biased by duplicate constraints.
10778 const std::vector<std::pair<int, int>> duplicates =
10780 timer.AddCounter("duplicates", duplicates.size());
10781 for (const auto& [dup, rep] : duplicates) {
10782 // Note that it is important to look at the type of the representative in
10783 // case the constraint became empty.
10784 DCHECK_LT(kObjectiveConstraint, 0);
10785 const int type =
10788 : context_->working_model->constraints(rep).constraint_case();
10789
10790 if (type == ConstraintProto::kInterval) {
10791 interval_mapping.resize(context_->working_model->constraints().size(),
10792 -1);
10793 CHECK_EQ(interval_mapping[rep], -1);
10794 interval_mapping[dup] = rep;
10795 }
10796
10797 // For linear constraint, we merge their rhs since it was ignored in the
10798 // FindDuplicateConstraints() call.
10799 if (type == ConstraintProto::kLinear) {
10800 const Domain rep_domain = ReadDomainFromProto(
10801 context_->working_model->constraints(rep).linear());
10802 const Domain d = ReadDomainFromProto(
10803 context_->working_model->constraints(dup).linear());
10804 if (rep_domain != d) {
10805 context_->UpdateRuleStats("duplicate: merged rhs of linear constraint");
10806 const Domain rhs = rep_domain.IntersectionWith(d);
10807 if (rhs.IsEmpty()) {
10808 if (!MarkConstraintAsFalse(
10809 context_->working_model->mutable_constraints(rep),
10810 "duplicate: false after merging")) {
10811 return;
10812 }
10813
10814 // The representative constraint is no longer a linear constraint,
10815 // so we will not enter this type case again and will just remove
10816 // all subsequent duplicate linear constraints.
10817 context_->UpdateConstraintVariableUsage(rep);
10818 continue;
10819 }
10820 FillDomainInProto(rhs, context_->working_model->mutable_constraints(rep)
10821 ->mutable_linear());
10822 }
10823 }
10824
10825 if (type == kObjectiveConstraint) {
10826 context_->UpdateRuleStats(
10827 "duplicate: linear constraint parallel to objective");
10828 const Domain objective_domain =
10829 ReadDomainFromProto(context_->working_model->objective());
10830 const Domain d = ReadDomainFromProto(
10831 context_->working_model->constraints(dup).linear());
10832 if (objective_domain != d) {
10833 context_->UpdateRuleStats("duplicate: updated objective domain");
10834 const Domain new_domain = objective_domain.IntersectionWith(d);
10835 if (new_domain.IsEmpty()) {
10836 return (void)context_->NotifyThatModelIsUnsat(
10837 "Constraint parallel to the objective makes the objective domain "
10838 "empty");
10839 }
10840 FillDomainInProto(new_domain,
10841 context_->working_model->mutable_objective());
10842
10843 // TODO(user): this write/read is a bit unclean, but needed.
10844 context_->ReadObjectiveFromProto();
10845 }
10846 }
10847
10848 // Remove the duplicate constraint.
10849 context_->working_model->mutable_constraints(dup)->Clear();
10850 context_->UpdateConstraintVariableUsage(dup);
10851 context_->UpdateRuleStats("duplicate: removed constraint");
10852 }
10853
10854 if (!interval_mapping.empty()) {
10855 context_->UpdateRuleStats("duplicate: remapped duplicate intervals");
10856 const int num_constraints = context_->working_model->constraints().size();
10857 for (int c = 0; c < num_constraints; ++c) {
10858 bool changed = false;
10860 [&interval_mapping, &changed](int* ref) {
10861 const int new_ref = interval_mapping[*ref];
10862 if (new_ref != -1) {
10863 changed = true;
10864 *ref = new_ref;
10865 }
10866 },
10867 context_->working_model->mutable_constraints(c));
10868 if (changed) context_->UpdateConstraintVariableUsage(c);
10869 }
10870 }
10871}
10872
10873void CpModelPresolver::DetectDuplicateConstraintsWithDifferentEnforcements(
10874 const CpModelMapping* mapping, BinaryImplicationGraph* implication_graph,
10875 Trail* trail) {
10876 if (time_limit_->LimitReached()) return;
10877 if (context_->ModelIsUnsat()) return;
10878 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10879
10880 // We need the objective written for this.
10881 if (context_->working_model->has_objective()) {
10882 if (!context_->CanonicalizeObjective()) return;
10883 context_->WriteObjectiveToProto();
10884 }
10885
10886 absl::flat_hash_set<Literal> enforcement_vars;
10887 std::vector<std::pair<Literal, Literal>> implications_used;
10888 // TODO(user): We can also do similar stuff to linear constraint that just
10889 // differ at a singleton variable. Or that are equalities. Like if expr + X =
10890 // cte and expr + Y = other_cte, we can see that X is in affine relation with
10891 // Y.
10892 const std::vector<std::pair<int, int>> duplicates_without_enforcement =
10893 FindDuplicateConstraints(*context_->working_model, true);
10894 timer.AddCounter("without_enforcements",
10895 duplicates_without_enforcement.size());
10896 for (const auto& [dup, rep] : duplicates_without_enforcement) {
10897 auto* dup_ct = context_->working_model->mutable_constraints(dup);
10898 auto* rep_ct = context_->working_model->mutable_constraints(rep);
10899
10900 if (dup_ct->constraint_case() == ConstraintProto::kInterval) {
10901 context_->UpdateRuleStats(
10902 "TODO interval: same interval with different enforcement?");
10903 continue;
10904 }
10905
10906 // Make sure our enforcement list are up to date: nothing fixed and that
10907 // its uses the literal representatives.
10908 if (PresolveEnforcementLiteral(dup_ct)) {
10909 context_->UpdateConstraintVariableUsage(dup);
10910 }
10911 if (PresolveEnforcementLiteral(rep_ct)) {
10912 context_->UpdateConstraintVariableUsage(rep);
10913 }
10914
10915 // Skip this pair if one of the constraint was simplified
10916 if (rep_ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET ||
10917 dup_ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
10918 continue;
10919 }
10920
10921 // If one of them has no enforcement, then the other can be ignored.
10922 // We always keep rep, but clear its enforcement if any.
10923 if (dup_ct->enforcement_literal().empty() ||
10924 rep_ct->enforcement_literal().empty()) {
10925 context_->UpdateRuleStats("duplicate: removed enforced constraint");
10926 rep_ct->mutable_enforcement_literal()->Clear();
10927 context_->UpdateConstraintVariableUsage(rep);
10928 dup_ct->Clear();
10929 context_->UpdateConstraintVariableUsage(dup);
10930 continue;
10931 }
10932
10933 const int a = rep_ct->enforcement_literal(0);
10934 const int b = dup_ct->enforcement_literal(0);
10935
10936 if (a == NegatedRef(b) && rep_ct->enforcement_literal().size() == 1 &&
10937 dup_ct->enforcement_literal().size() == 1) {
10938 context_->UpdateRuleStats(
10939 "duplicate: both with enforcement and its negation");
10940 rep_ct->mutable_enforcement_literal()->Clear();
10941 context_->UpdateConstraintVariableUsage(rep);
10942 dup_ct->Clear();
10943 context_->UpdateConstraintVariableUsage(dup);
10944 continue;
10945 }
10946
10947 // Special case. This looks specific but users might reify with a cost
10948 // a duplicate constraint. In this case, no need to have two variables,
10949 // we can make them equal by duality argument.
10950 //
10951 // TODO(user): Deal with more general situation? Note that we already
10952 // do something similar in dual_bound_strengthening.Strengthen() were we
10953 // are more general as we just require an unique blocking constraint rather
10954 // than a singleton variable.
10955 //
10956 // But we could detect that "a <=> constraint" and "b <=> constraint", then
10957 // we can also add the equality. Alternatively, we can just introduce a new
10958 // variable and merge all duplicate constraint into 1 + bunch of boolean
10959 // constraints liking enforcements.
10960 if (context_->VariableWithCostIsUniqueAndRemovable(a) &&
10961 context_->VariableWithCostIsUniqueAndRemovable(b)) {
10962 // Both these case should be presolved before, but it is easy to deal with
10963 // if we encounter them here in some corner cases. And the code after
10964 // 'continue' uses this, in particular to update the hint.
10965 bool skip = false;
10966 if (RefIsPositive(a) == context_->ObjectiveCoeff(PositiveRef(a)) > 0) {
10967 context_->UpdateRuleStats("duplicate: dual fixing enforcement");
10968 if (!context_->SetLiteralToFalse(a)) return;
10969 skip = true;
10970 }
10971 if (RefIsPositive(b) == context_->ObjectiveCoeff(PositiveRef(b)) > 0) {
10972 context_->UpdateRuleStats("duplicate: dual fixing enforcement");
10973 if (!context_->SetLiteralToFalse(b)) return;
10974 skip = true;
10975 }
10976 if (skip) continue;
10977
10978 // If there are more than one enforcement literal, then the Booleans
10979 // are not necessarily equivalent: if a constraint is disabled by other
10980 // literal, we don't want to put a or b at 1 and pay an extra cost.
10981 //
10982 // TODO(user): If a is alone, then b==1 can implies a == 1.
10983 // We can also replace [(b, others) => constraint] with (b, others) <=> a.
10984 //
10985 // TODO(user): If the other enforcements are the same, we can also add
10986 // the equivalence and remove the duplicate constraint.
10987 if (rep_ct->enforcement_literal().size() > 1 ||
10988 dup_ct->enforcement_literal().size() > 1) {
10989 context_->UpdateRuleStats(
10990 "TODO duplicate: identical constraint with unique enforcement "
10991 "cost");
10992 continue;
10993 }
10994
10995 // Sign is correct, i.e. ignoring the constraint is expensive.
10996 // The two enforcement can be made equivalent.
10997 context_->UpdateRuleStats("duplicate: dual equivalence of enforcement");
10998 // If `a` and `b` hints are different then the whole hint satisfies
10999 // the enforced constraint. We can thus change them to true (this cannot
11000 // increase the objective value thanks to the `skip` test above -- the
11001 // objective domain is non-constraining, but this only guarantees that
11002 // singleton variables can freely *decrease* the objective).
11003 solution_crush_.UpdateLiteralsToFalseIfDifferent(NegatedRef(a),
11004 NegatedRef(b));
11005 if (!context_->StoreBooleanEqualityRelation(a, b)) return;
11006
11007 // We can also remove duplicate constraint now. It will be done later but
11008 // it seems more efficient to just do it now.
11009 if (dup_ct->enforcement_literal().size() == 1 &&
11010 rep_ct->enforcement_literal().size() == 1) {
11011 dup_ct->Clear();
11012 context_->UpdateConstraintVariableUsage(dup);
11013 continue;
11014 }
11015 }
11016
11017 // Check if the enforcement of one constraint implies the ones of the other.
11018 if (implication_graph != nullptr && mapping != nullptr &&
11019 trail != nullptr) {
11020 for (int i = 0; i < 2; i++) {
11021 // When A and B only differ on their enforcement literals and the
11022 // enforcements of constraint A implies the enforcements of constraint
11023 // B, then constraint A is redundant and we can remove it.
11024 const int c_a = i == 0 ? dup : rep;
11025 const int c_b = i == 0 ? rep : dup;
11026 const auto& ct_a = context_->working_model->constraints(c_a);
11027 const auto& ct_b = context_->working_model->constraints(c_b);
11028
11029 enforcement_vars.clear();
11030 implications_used.clear();
11031 for (const int proto_lit : ct_b.enforcement_literal()) {
11032 const Literal lit = mapping->Literal(proto_lit);
11033 DCHECK(!trail->Assignment().LiteralIsAssigned(lit));
11034 enforcement_vars.insert(lit);
11035 }
11036 for (const int proto_lit : ct_a.enforcement_literal()) {
11037 const Literal lit = mapping->Literal(proto_lit);
11038 DCHECK(!trail->Assignment().LiteralIsAssigned(lit));
11039 for (const Literal implication_lit :
11040 implication_graph->DirectImplications(lit)) {
11041 auto extracted = enforcement_vars.extract(implication_lit);
11042 if (!extracted.empty() && lit != implication_lit) {
11043 implications_used.push_back({lit, implication_lit});
11044 }
11045 }
11046 }
11047 if (enforcement_vars.empty()) {
11048 // Tricky: Because we keep track of literal <=> var == value, we
11049 // cannot easily simplify linear1 here. This is because a scenario
11050 // like this can happen:
11051 //
11052 // We have registered the fact that a <=> X=1 because we saw two
11053 // constraints a => X=1 and not(a) => X!= 1
11054 //
11055 // Now, we are here and we have:
11056 // a => X=1, b => X=1, a => b
11057 // So we rewrite this as
11058 // a => b, b => X=1
11059 //
11060 // But later, the PresolveLinearOfSizeOne() see
11061 // b => X=1 and just rewrite this as b => a since (a <=> X=1).
11062 // This is wrong because the constraint "b => X=1" is needed for the
11063 // equivalence (a <=> X=1), but we lost that fact.
11064 //
11065 // Note(user): In the scenario above we can see that a <=> b, and if
11066 // we know that fact, then the transformation is correctly handled.
11067 // The bug was triggered when the Probing finished early due to time
11068 // limit and we never detected that equivalence.
11069 //
11070 // TODO(user): Try to find a cleaner way to handle this. We could
11071 // query our HasVarValueEncoding() directly here and directly detect a
11072 // <=> b. However we also need to figure the case of
11073 // half-implications.
11074 {
11075 if (ct_a.constraint_case() == ConstraintProto::kLinear &&
11076 ct_a.linear().vars().size() == 1 &&
11077 ct_a.enforcement_literal().size() == 1) {
11078 const int var = ct_a.linear().vars(0);
11079 const Domain var_domain = context_->DomainOf(var);
11080 const Domain rhs =
11081 ReadDomainFromProto(ct_a.linear())
11082 .InverseMultiplicationBy(ct_a.linear().coeffs(0))
11083 .IntersectionWith(var_domain);
11084
11085 // IsFixed() do not work on empty domain.
11086 if (rhs.IsEmpty()) {
11087 if (!MarkConstraintAsFalse(rep_ct,
11088 "duplicate: linear1 infeasible"))
11089 return;
11090 if (!MarkConstraintAsFalse(dup_ct,
11091 "duplicate: linear1 infeasible"))
11092 return;
11093 context_->UpdateConstraintVariableUsage(rep);
11094 context_->UpdateConstraintVariableUsage(dup);
11095 continue;
11096 }
11097 if (rhs == var_domain) {
11098 context_->UpdateRuleStats("duplicate: linear1 always true");
11099 rep_ct->Clear();
11100 dup_ct->Clear();
11101 context_->UpdateConstraintVariableUsage(rep);
11102 context_->UpdateConstraintVariableUsage(dup);
11103 continue;
11104 }
11105
11106 // We skip if it is a var == value or var != value constraint.
11107 if (rhs.IsFixed() ||
11108 rhs.Complement().IntersectionWith(var_domain).IsFixed()) {
11109 context_->UpdateRuleStats(
11110 "TODO duplicate: skipped identical encoding constraints");
11111 continue;
11112 }
11113 }
11114 }
11115
11116 context_->UpdateRuleStats(
11117 "duplicate: identical constraint with implied enforcements");
11118 if (c_a == rep) {
11119 // We don't want to remove the representative element of the
11120 // duplicates detection, so swap the constraints.
11121 rep_ct->Swap(dup_ct);
11122 context_->UpdateConstraintVariableUsage(rep);
11123 }
11124 dup_ct->Clear();
11125 context_->UpdateConstraintVariableUsage(dup);
11126 // Subtle point: we need to add the implications we used back to the
11127 // graph. This is because in some case the implications are only true
11128 // in the presence of the "duplicated" constraints.
11129 for (const auto& [a, b] : implications_used) {
11130 const int proto_lit_a = mapping->GetProtoLiteralFromLiteral(a);
11131 const int proto_lit_b = mapping->GetProtoLiteralFromLiteral(b);
11132 context_->AddImplication(proto_lit_a, proto_lit_b);
11133 }
11134 context_->UpdateNewConstraintsVariableUsage();
11135 break;
11136 }
11137 }
11138 }
11139 }
11140}
11141
11143 if (time_limit_->LimitReached()) return;
11144 if (context_->ModelIsUnsat()) return;
11145 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11146
11147 // List the variable that are pairwise different, also store in offset[x, y]
11148 // the offsets such that x >= y + offset.second OR y >= x + offset.first.
11149 std::vector<std::pair<int, int>> different_vars;
11150 absl::flat_hash_map<std::pair<int, int>, std::pair<int64_t, int64_t>> offsets;
11151
11152 // Process the fact "v1 - v2 \in Domain".
11153 const auto process_difference = [&different_vars, &offsets](int v1, int v2,
11154 Domain d) {
11155 Domain exclusion = d.Complement().PartAroundZero();
11156 if (exclusion.IsEmpty()) return;
11157 if (v1 == v2) return;
11158 std::pair<int, int> key = {v1, v2};
11159 if (v1 > v2) {
11160 std::swap(key.first, key.second);
11161 exclusion = exclusion.Negation();
11162 }
11163
11164 // We have x - y not in exclusion,
11165 // so x - y > exclusion.Max() --> x > y + exclusion.Max();
11166 // OR x - y < exclusion.Min() --> y > x - exclusion.Min();
11167 different_vars.push_back(key);
11168 offsets[key] = {exclusion.Min() == std::numeric_limits<int64_t>::min()
11169 ? std::numeric_limits<int64_t>::max()
11170 : CapAdd(-exclusion.Min(), 1),
11171 CapAdd(exclusion.Max(), 1)};
11172 };
11173
11174 // Try to find identical linear constraint with incompatible domains.
11175 // This works really well on neos16.mps.gz where we have
11176 // a <=> x <= y
11177 // b <=> x >= y
11178 // and a => not(b),
11179 // Because of this presolve, we detect that not(a) => b and thus that a and
11180 // not(b) are equivalent. We can thus simplify the problem to just
11181 // a => x < y
11182 // not(a) => x > y
11183 //
11184 // TODO(user): On that same problem, we could actually just have x != y and
11185 // remove the enforcement literal that is just used for that. But then we
11186 // will just re-create it, since we don't have a native way to handle x != y.
11187 //
11188 // TODO(user): Again on neos16.mps, we actually have cliques of x != y so we
11189 // end up with a bunch of groups of 7 variables in [0, 6] that are all
11190 // different. If we can detect that, then we close the problem quickly instead
11191 // of not closing it.
11192 bool has_all_diff = false;
11193 bool has_no_overlap = false;
11194 std::vector<std::pair<uint64_t, int>> hashes;
11195 const int num_constraints = context_->working_model->constraints_size();
11196 for (int c = 0; c < num_constraints; ++c) {
11197 const ConstraintProto& ct = context_->working_model->constraints(c);
11199 has_all_diff = true;
11200 continue;
11201 }
11203 has_no_overlap = true;
11204 continue;
11205 }
11206 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
11207 if (ct.linear().vars().size() == 1) continue;
11208
11209 // Detect direct encoding of x != y. Note that we also see that from x > y
11210 // and related.
11211 if (ct.linear().vars().size() == 2 && ct.enforcement_literal().empty() &&
11212 ct.linear().coeffs(0) == -ct.linear().coeffs(1)) {
11213 // We assume the constraint was already divided by its gcd.
11214 if (ct.linear().coeffs(0) == 1) {
11215 process_difference(ct.linear().vars(0), ct.linear().vars(1),
11217 } else if (ct.linear().coeffs(0) == -1) {
11218 process_difference(ct.linear().vars(0), ct.linear().vars(1),
11219 ReadDomainFromProto(ct.linear()).Negation());
11220 }
11221 }
11222
11223 // TODO(user): Handle this case?
11224 if (ct.enforcement_literal().size() > 1) continue;
11225
11226 uint64_t hash = kDefaultFingerprintSeed;
11227 hash = FingerprintRepeatedField(ct.linear().vars(), hash);
11228 hash = FingerprintRepeatedField(ct.linear().coeffs(), hash);
11229 hashes.push_back({hash, c});
11230 }
11231 std::sort(hashes.begin(), hashes.end());
11232 for (int next, start = 0; start < hashes.size(); start = next) {
11233 next = start + 1;
11234 while (next < hashes.size() && hashes[next].first == hashes[start].first) {
11235 ++next;
11236 }
11237 absl::Span<const std::pair<uint64_t, int>> range(&hashes[start],
11238 next - start);
11239 if (range.size() <= 1) continue;
11240 if (range.size() > 10) continue;
11241
11242 for (int i = 0; i < range.size(); ++i) {
11243 const ConstraintProto& ct1 =
11244 context_->working_model->constraints(range[i].second);
11245 const int num_terms = ct1.linear().vars().size();
11246 for (int j = i + 1; j < range.size(); ++j) {
11247 const ConstraintProto& ct2 =
11248 context_->working_model->constraints(range[j].second);
11249 if (ct2.linear().vars().size() != num_terms) continue;
11250 if (!ReadDomainFromProto(ct1.linear())
11252 .IsEmpty()) {
11253 continue;
11254 }
11255 if (absl::MakeSpan(ct1.linear().vars().data(), num_terms) !=
11256 absl::MakeSpan(ct2.linear().vars().data(), num_terms)) {
11257 continue;
11258 }
11259 if (absl::MakeSpan(ct1.linear().coeffs().data(), num_terms) !=
11260 absl::MakeSpan(ct2.linear().coeffs().data(), num_terms)) {
11261 continue;
11262 }
11263
11264 if (ct1.enforcement_literal().empty() &&
11265 ct2.enforcement_literal().empty()) {
11266 (void)context_->NotifyThatModelIsUnsat(
11267 "two incompatible linear constraint");
11268 return;
11269 }
11270 if (ct1.enforcement_literal().empty()) {
11271 context_->UpdateRuleStats(
11272 "incompatible linear: set enforcement to false");
11273 if (!context_->SetLiteralToFalse(ct2.enforcement_literal(0))) {
11274 return;
11275 }
11276 continue;
11277 }
11278 if (ct2.enforcement_literal().empty()) {
11279 context_->UpdateRuleStats(
11280 "incompatible linear: set enforcement to false");
11281 if (!context_->SetLiteralToFalse(ct1.enforcement_literal(0))) {
11282 return;
11283 }
11284 continue;
11285 }
11286
11287 const int lit1 = ct1.enforcement_literal(0);
11288 const int lit2 = ct2.enforcement_literal(0);
11289
11290 // Detect x != y via lit => x > y && not(lit) => x < y.
11291 if (ct1.linear().vars().size() == 2 &&
11292 ct1.linear().coeffs(0) == -ct1.linear().coeffs(1) &&
11293 lit1 == NegatedRef(lit2)) {
11294 // We have x - y in domain1 or in domain2, so it must be in the union.
11295 Domain union_of_domain =
11298
11299 // We assume the constraint was already divided by its gcd.
11300 if (ct1.linear().coeffs(0) == 1) {
11301 process_difference(ct1.linear().vars(0), ct1.linear().vars(1),
11302 std::move(union_of_domain));
11303 } else if (ct1.linear().coeffs(0) == -1) {
11304 process_difference(ct1.linear().vars(0), ct1.linear().vars(1),
11305 union_of_domain.Negation());
11306 }
11307 }
11308
11309 if (lit1 != NegatedRef(lit2)) {
11310 context_->UpdateRuleStats("incompatible linear: add implication");
11311 context_->AddImplication(lit1, NegatedRef(lit2));
11312 }
11313 }
11314 }
11315 }
11316
11317 // Detect all_different cliques.
11318 // We reuse the max-clique code from sat.
11319 //
11320 // TODO(user): To avoid doing that more than once, we only run it if there
11321 // is no all-diff in the model already. This is not perfect.
11322 //
11323 // Note(user): The all diff added here will not be expanded since we run this
11324 // after expansion. This is fragile though. Not even sure this is what we
11325 // want.
11326 //
11327 // TODO(user): Start with the existing all diff and expand them rather than
11328 // not running this if there are all_diff present.
11329 //
11330 // TODO(user): Only add them at the end of the presolve! it hurt our presolve
11331 // (like probing is slower) and only serve for linear relaxation.
11332 if (context_->params().infer_all_diffs() && !has_all_diff &&
11333 !has_no_overlap && different_vars.size() > 2) {
11334 WallTimer local_time;
11335 local_time.Start();
11336
11337 std::vector<std::vector<Literal>> cliques;
11338 absl::flat_hash_set<int> used_var;
11339
11340 Model local_model;
11341 const int num_variables = context_->working_model->variables().size();
11342 local_model.GetOrCreate<Trail>()->Resize(num_variables);
11343 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
11344 graph->Resize(num_variables);
11345 for (const auto [var1, var2] : different_vars) {
11346 if (!RefIsPositive(var1)) continue;
11347 if (!RefIsPositive(var2)) continue;
11348 if (var1 == var2) {
11349 (void)context_->NotifyThatModelIsUnsat("x != y with x == y");
11350 return;
11351 }
11352 // All variables at false is always a valid solution of the local model,
11353 // so this should never return UNSAT.
11354 CHECK(graph->AddAtMostOne({Literal(BooleanVariable(var1), true),
11355 Literal(BooleanVariable(var2), true)}));
11356 if (!used_var.contains(var1)) {
11357 used_var.insert(var1);
11358 cliques.push_back({Literal(BooleanVariable(var1), true),
11359 Literal(BooleanVariable(var2), true)});
11360 }
11361 if (!used_var.contains(var2)) {
11362 used_var.insert(var2);
11363 cliques.push_back({Literal(BooleanVariable(var1), true),
11364 Literal(BooleanVariable(var2), true)});
11365 }
11366 }
11367 CHECK(graph->DetectEquivalences());
11368 graph->TransformIntoMaxCliques(&cliques, 1e8);
11369
11370 int num_cliques = 0;
11371 int64_t cumulative_size = 0;
11372 for (std::vector<Literal>& clique : cliques) {
11373 if (clique.size() <= 2) continue;
11374
11375 ++num_cliques;
11376 cumulative_size += clique.size();
11377 std::sort(clique.begin(), clique.end());
11378
11379 // We have an all-diff, but inspect the offsets to see if we have a
11380 // disjunctive ! Note that this is quadratic, but no more complex than the
11381 // scan of the model we just did above, since we had one linear constraint
11382 // per entry.
11383 const int num_terms = clique.size();
11384 std::vector<int64_t> sizes(num_terms,
11385 std::numeric_limits<int64_t>::max());
11386 for (int i = 0; i < num_terms; ++i) {
11387 const int v1 = clique[i].Variable().value();
11388 for (int j = i + 1; j < num_terms; ++j) {
11389 const int v2 = clique[j].Variable().value();
11390 const auto [o1, o2] = offsets.at({v1, v2});
11391 sizes[i] = std::min(sizes[i], o1);
11392 sizes[j] = std::min(sizes[j], o2);
11393 }
11394 }
11395
11396 int num_greater_than_one = 0;
11397 int64_t issue = 0;
11398 for (int i = 0; i < num_terms; ++i) {
11399 CHECK_GE(sizes[i], 1);
11400 if (sizes[i] > 1) ++num_greater_than_one;
11401
11402 // When this happens, it means this interval can never be before
11403 // any other. We should probably handle this case better, but for now we
11404 // abort.
11405 issue = CapAdd(issue, sizes[i]);
11406 if (issue == std::numeric_limits<int64_t>::max()) {
11407 context_->UpdateRuleStats("TODO no_overlap: with task always last");
11408 num_greater_than_one = 0;
11409 break;
11410 }
11411 }
11412
11413 if (num_greater_than_one > 0) {
11414 // We have one size greater than 1, lets add a no_overlap!
11415 //
11416 // TODO(user): try to remove all the quadratic boolean and their
11417 // corresponding linear2 ? Any Boolean not used elsewhere could be
11418 // removed.
11419 context_->UpdateRuleStats(
11420 "no_overlap: inferred from x != y constraints");
11421
11422 std::vector<int> intervals;
11423 for (int i = 0; i < num_terms; ++i) {
11424 intervals.push_back(context_->working_model->constraints().size());
11425 auto* new_interval =
11426 context_->working_model->add_constraints()->mutable_interval();
11427 new_interval->mutable_start()->set_offset(0);
11428 new_interval->mutable_start()->add_coeffs(1);
11429 new_interval->mutable_start()->add_vars(clique[i].Variable().value());
11430
11431 new_interval->mutable_size()->set_offset(sizes[i]);
11432
11433 new_interval->mutable_end()->set_offset(sizes[i]);
11434 new_interval->mutable_end()->add_coeffs(1);
11435 new_interval->mutable_end()->add_vars(clique[i].Variable().value());
11436 }
11437 auto* new_ct =
11438 context_->working_model->add_constraints()->mutable_no_overlap();
11439 for (const int interval : intervals) {
11440 new_ct->add_intervals(interval);
11441 }
11442 } else {
11443 context_->UpdateRuleStats("all_diff: inferred from x != y constraints");
11444 auto* new_ct =
11445 context_->working_model->add_constraints()->mutable_all_diff();
11446 for (const Literal l : clique) {
11447 auto* expr = new_ct->add_exprs();
11448 expr->add_vars(l.Variable().value());
11449 expr->add_coeffs(1);
11450 }
11451 }
11452 }
11453
11454 timer.AddCounter("different", different_vars.size());
11455 timer.AddCounter("cliques", num_cliques);
11456 timer.AddCounter("size", cumulative_size);
11457 }
11458
11459 context_->UpdateNewConstraintsVariableUsage();
11460}
11461
11462namespace {
11463
11464// Add factor * subset_ct to the given superset_ct.
11465void Substitute(int64_t factor,
11466 const absl::flat_hash_map<int, int64_t>& subset_coeff_map,
11467 const Domain& subset_rhs, const Domain& superset_rhs,
11468 LinearConstraintProto* mutable_linear) {
11469 int new_size = 0;
11470 const int old_size = mutable_linear->vars().size();
11471 for (int i = 0; i < old_size; ++i) {
11472 const int var = mutable_linear->vars(i);
11473 int64_t coeff = mutable_linear->coeffs(i);
11474 const auto it = subset_coeff_map.find(var);
11475 if (it != subset_coeff_map.end()) {
11476 coeff += factor * it->second;
11477 if (coeff == 0) continue;
11478 }
11479
11480 mutable_linear->set_vars(new_size, var);
11481 mutable_linear->set_coeffs(new_size, coeff);
11482 ++new_size;
11483 }
11484 mutable_linear->mutable_vars()->Truncate(new_size);
11485 mutable_linear->mutable_coeffs()->Truncate(new_size);
11487 superset_rhs.AdditionWith(subset_rhs.MultiplicationBy(factor)),
11488 mutable_linear);
11489}
11490
11491} // namespace
11492
11493void CpModelPresolver::DetectDominatedLinearConstraints() {
11494 if (time_limit_->LimitReached()) return;
11495 if (context_->ModelIsUnsat()) return;
11496 if (context_->params().presolve_inclusion_work_limit() == 0) return;
11497 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11498
11499 // Because we only deal with linear constraint and we want to ignore the
11500 // enforcement part, we reuse the variable list in the inclusion detector.
11501 // Note that we ignore "unclean" constraint, so we only have positive
11502 // reference there.
11503 class Storage {
11504 public:
11505 explicit Storage(CpModelProto* proto) : proto_(*proto) {}
11506 int size() const { return static_cast<int>(proto_.constraints().size()); }
11507 absl::Span<const int> operator[](int c) const {
11508 return absl::MakeSpan(proto_.constraints(c).linear().vars());
11509 }
11510
11511 private:
11512 const CpModelProto& proto_;
11513 };
11514 Storage storage(context_->working_model);
11515 InclusionDetector detector(storage, time_limit_);
11516 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
11517
11518 // Because we use the constraint <-> variable graph, we cannot modify it
11519 // during DetectInclusions(). So we delay the update of the graph.
11520 std::vector<int> constraint_indices_to_clean;
11521
11522 // Cache the linear expression domain.
11523 // TODO(user): maybe we should store this instead of recomputing it.
11524 absl::flat_hash_map<int, Domain> cached_expr_domain;
11525
11526 const int num_constraints = context_->working_model->constraints().size();
11527 for (int c = 0; c < num_constraints; ++c) {
11528 const ConstraintProto& ct = context_->working_model->constraints(c);
11529 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
11530
11531 // We only look at long enforced constraint to avoid all the linear of size
11532 // one or two which can be numerous.
11533 if (!ct.enforcement_literal().empty()) {
11534 if (ct.linear().vars().size() < 3) continue;
11535 }
11536
11537 if (!LinearConstraintIsClean(ct.linear())) {
11538 // This shouldn't happen except in potential corner cases were the
11539 // constraints were not canonicalized before this point. We just skip
11540 // such constraint.
11541 continue;
11542 }
11543
11544 detector.AddPotentialSet(c);
11545
11546 const auto [min_activity, max_activity] =
11547 context_->ComputeMinMaxActivity(ct.linear());
11548 cached_expr_domain[c] = Domain(min_activity, max_activity);
11549 }
11550
11551 int64_t num_inclusions = 0;
11552 absl::flat_hash_map<int, int64_t> coeff_map;
11553 detector.DetectInclusions([&](int subset_c, int superset_c) {
11554 ++num_inclusions;
11555
11556 // Store the coeff of the subset linear constraint in a map.
11557 const ConstraintProto& subset_ct =
11558 context_->working_model->constraints(subset_c);
11559 const LinearConstraintProto& subset_lin = subset_ct.linear();
11560 coeff_map.clear();
11561 detector.IncreaseWorkDone(subset_lin.vars().size());
11562 for (int i = 0; i < subset_lin.vars().size(); ++i) {
11563 coeff_map[subset_lin.vars(i)] = subset_lin.coeffs(i);
11564 }
11565
11566 // We have a perfect match if 'factor_a * subset == factor_b * superset' on
11567 // the common positions. Note that assuming subset has been gcd reduced,
11568 // there is not point considering factor_b != 1.
11569 bool perfect_match = true;
11570
11571 // Find interesting factor of the subset that cancels terms of the superset.
11572 int64_t factor = 0;
11573 int64_t min_pos_factor = std::numeric_limits<int64_t>::max();
11574 int64_t max_neg_factor = std::numeric_limits<int64_t>::min();
11575
11576 // Lets compute the implied domain of the linear expression
11577 // "superset - subset". Note that we actually do not need exact inclusion
11578 // for this algorithm to work, but it is an heuristic to not try it with
11579 // all pair of constraints.
11580 const ConstraintProto& superset_ct =
11581 context_->working_model->constraints(superset_c);
11582 const LinearConstraintProto& superset_lin = superset_ct.linear();
11583 int64_t diff_min_activity = 0;
11584 int64_t diff_max_activity = 0;
11585 detector.IncreaseWorkDone(superset_lin.vars().size());
11586 for (int i = 0; i < superset_lin.vars().size(); ++i) {
11587 const int var = superset_lin.vars(i);
11588 int64_t coeff = superset_lin.coeffs(i);
11589 const auto it = coeff_map.find(var);
11590
11591 if (it != coeff_map.end()) {
11592 const int64_t subset_coeff = it->second;
11593
11594 const int64_t div = coeff / subset_coeff;
11595 if (div > 0) {
11596 min_pos_factor = std::min(div, min_pos_factor);
11597 } else {
11598 max_neg_factor = std::max(div, max_neg_factor);
11599 }
11600
11601 if (perfect_match) {
11602 if (coeff % subset_coeff == 0) {
11603 if (factor == 0) {
11604 // Note that factor can be negative.
11605 factor = div;
11606 } else if (factor != div) {
11607 perfect_match = false;
11608 }
11609 } else {
11610 perfect_match = false;
11611 }
11612 }
11613
11614 // TODO(user): compute the factor first in case it is != 1 ?
11615 coeff -= subset_coeff;
11616 }
11617 if (coeff == 0) continue;
11618 context_->CappedUpdateMinMaxActivity(var, coeff, &diff_min_activity,
11619 &diff_max_activity);
11620 }
11621
11622 const Domain diff_domain(diff_min_activity, diff_max_activity);
11623 const Domain subset_rhs = ReadDomainFromProto(subset_lin);
11624 const Domain superset_rhs = ReadDomainFromProto(superset_lin);
11625
11626 // Case 1: superset is redundant.
11627 // We process this one first as it let us remove the longest constraint.
11628 //
11629 // Important: because of how we computed the inclusion, the diff_domain is
11630 // only valid if none of the enforcement appear in the subset.
11631 //
11632 // TODO(user): Compute the correct infered domain in this case.
11633 if (subset_ct.enforcement_literal().empty()) {
11634 const Domain implied_superset_domain =
11635 subset_rhs.AdditionWith(diff_domain)
11636 .IntersectionWith(cached_expr_domain[superset_c]);
11637 if (implied_superset_domain.IsIncludedIn(superset_rhs)) {
11638 context_->UpdateRuleStats(
11639 "linear inclusion: redundant containing constraint");
11640 context_->working_model->mutable_constraints(superset_c)->Clear();
11641 constraint_indices_to_clean.push_back(superset_c);
11642 detector.StopProcessingCurrentSuperset();
11643 return;
11644 }
11645 }
11646
11647 // Case 2: subset is redundant.
11648 if (superset_ct.enforcement_literal().empty()) {
11649 const Domain implied_subset_domain =
11650 superset_rhs.AdditionWith(diff_domain.Negation())
11651 .IntersectionWith(cached_expr_domain[subset_c]);
11652 if (implied_subset_domain.IsIncludedIn(subset_rhs)) {
11653 context_->UpdateRuleStats(
11654 "linear inclusion: redundant included constraint");
11655 context_->working_model->mutable_constraints(subset_c)->Clear();
11656 constraint_indices_to_clean.push_back(subset_c);
11657 detector.StopProcessingCurrentSubset();
11658 return;
11659 }
11660 }
11661
11662 // If the subset is an equality, and we can add a factor of it to the
11663 // superset so that the activity range is guaranteed to be tighter, we
11664 // always do it. This should both sparsify the problem but also lead to
11665 // tighter propagation.
11666 if (subset_rhs.IsFixed() && subset_ct.enforcement_literal().empty()) {
11667 const int64_t best_factor =
11668 max_neg_factor > -min_pos_factor ? max_neg_factor : min_pos_factor;
11669
11670 // Compute the activity range before and after. Because our pos/neg factor
11671 // are the smallest possible, if one is undefined then we are guaranteed
11672 // to be tighter, and do not need to compute this.
11673 //
11674 // TODO(user): can we compute the best factor that make this as tight as
11675 // possible instead? that looks doable.
11676 bool is_tigher = true;
11677 if (min_pos_factor != std::numeric_limits<int64_t>::max() &&
11678 max_neg_factor != std::numeric_limits<int64_t>::min()) {
11679 int64_t min_before = 0;
11680 int64_t max_before = 0;
11681 int64_t min_after = CapProd(best_factor, subset_rhs.FixedValue());
11682 int64_t max_after = min_after;
11683 for (int i = 0; i < superset_lin.vars().size(); ++i) {
11684 const int var = superset_lin.vars(i);
11685 const auto it = coeff_map.find(var);
11686 if (it == coeff_map.end()) continue;
11687
11688 const int64_t coeff_before = superset_lin.coeffs(i);
11689 const int64_t coeff_after = coeff_before - best_factor * it->second;
11690 context_->CappedUpdateMinMaxActivity(var, coeff_before, &min_before,
11691 &max_before);
11692 context_->CappedUpdateMinMaxActivity(var, coeff_after, &min_after,
11693 &max_after);
11694 }
11695 is_tigher = min_after >= min_before && max_after <= max_before;
11696 }
11697 if (is_tigher) {
11698 context_->UpdateRuleStats("linear inclusion: sparsify superset");
11699 Substitute(-best_factor, coeff_map, subset_rhs, superset_rhs,
11700 context_->working_model->mutable_constraints(superset_c)
11701 ->mutable_linear());
11702 constraint_indices_to_clean.push_back(superset_c);
11703 detector.StopProcessingCurrentSuperset();
11704 return;
11705 }
11706 }
11707
11708 // We do a bit more if we have an exact match and factor * subset is exactly
11709 // a subpart of the superset constraint.
11710 if (perfect_match && subset_ct.enforcement_literal().empty() &&
11711 superset_ct.enforcement_literal().empty()) {
11712 CHECK_NE(factor, 0);
11713
11714 // Propagate domain on the superset - subset variables.
11715 // TODO(user): We can probably still do that if the inclusion is not
11716 // perfect.
11717 temp_ct_.Clear();
11718 auto* mutable_linear = temp_ct_.mutable_linear();
11719 for (int i = 0; i < superset_lin.vars().size(); ++i) {
11720 const int var = superset_lin.vars(i);
11721 const int64_t coeff = superset_lin.coeffs(i);
11722 const auto it = coeff_map.find(var);
11723 if (it != coeff_map.end()) continue;
11724 mutable_linear->add_vars(var);
11725 mutable_linear->add_coeffs(coeff);
11726 }
11728 superset_rhs.AdditionWith(subset_rhs.MultiplicationBy(-factor)),
11729 mutable_linear);
11730 PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_);
11731 if (context_->ModelIsUnsat()) detector.Stop();
11732
11733 if (superset_rhs.IsFixed()) {
11734 if (subset_lin.vars().size() + 1 == superset_lin.vars().size()) {
11735 // Because we propagated the equation on the singleton variable above,
11736 // and we have an equality, the subset is redundant!
11737 context_->UpdateRuleStats(
11738 "linear inclusion: subset + singleton is equality");
11739 context_->working_model->mutable_constraints(subset_c)->Clear();
11740 constraint_indices_to_clean.push_back(subset_c);
11741 detector.StopProcessingCurrentSubset();
11742 return;
11743 }
11744
11745 // This one could make sense if subset is large vs superset.
11746 context_->UpdateRuleStats(
11747 "TODO linear inclusion: superset is equality");
11748 }
11749 }
11750 });
11751
11752 for (const int c : constraint_indices_to_clean) {
11753 context_->UpdateConstraintVariableUsage(c);
11754 }
11755
11756 timer.AddToWork(1e-9 * static_cast<double>(detector.work_done()));
11757 timer.AddCounter("relevant_constraints", detector.num_potential_supersets());
11758 timer.AddCounter("num_inclusions", num_inclusions);
11759 timer.AddCounter("num_redundant", constraint_indices_to_clean.size());
11760}
11761
11762// TODO(user): Also substitute if this appear in the objective?
11763// TODO(user): In some case we only need common_part <= new_var.
11764bool CpModelPresolver::RemoveCommonPart(
11765 const absl::flat_hash_map<int, int64_t>& common_var_coeff_map,
11766 absl::Span<const std::pair<int, int64_t>> block,
11767 ActivityBoundHelper* helper) {
11768 int new_var;
11769 int64_t gcd = 0;
11770 int64_t offset = 0;
11771
11772 // If the common part is expressable via one of the constraint in the block as
11773 // == gcd * X + offset, we can just use this variable instead of creating a
11774 // new variable.
11775 int definiting_equation = -1;
11776 for (const auto [c, multiple] : block) {
11777 const ConstraintProto& ct = context_->working_model->constraints(c);
11778 if (std::abs(multiple) != 1) continue;
11779 if (!IsLinearEqualityConstraint(ct)) continue;
11780 if (ct.linear().vars().size() != common_var_coeff_map.size() + 1) continue;
11781
11782 context_->UpdateRuleStats(
11783 "linear matrix: defining equation for common rectangle");
11784 definiting_equation = c;
11785
11786 // Find the missing term and its coefficient.
11787 int64_t coeff = 0;
11788 const int num_terms = ct.linear().vars().size();
11789 for (int k = 0; k < num_terms; ++k) {
11790 if (common_var_coeff_map.contains(ct.linear().vars(k))) continue;
11791 new_var = ct.linear().vars(k);
11792 coeff = ct.linear().coeffs(k);
11793 break;
11794 }
11795 CHECK_NE(coeff, 0);
11796
11797 // We have multiple * common + coeff * X = constant.
11798 // So common = multiple^-1 * constant - multiple^-1 * coeff * X;
11799 gcd = -multiple * coeff;
11800 offset = multiple * ct.linear().domain(0);
11801 break;
11802 }
11803
11804 // We need a new variable and defining equation.
11805 if (definiting_equation == -1) {
11806 offset = 0;
11807 int64_t min_activity = 0;
11808 int64_t max_activity = 0;
11809 tmp_terms_.clear();
11810 std::vector<std::pair<int, int64_t>> common_part;
11811 for (const auto [var, coeff] : common_var_coeff_map) {
11812 common_part.push_back({var, coeff});
11813 gcd = std::gcd(gcd, std::abs(coeff));
11814 if (context_->CanBeUsedAsLiteral(var) && !context_->IsFixed(var)) {
11815 tmp_terms_.push_back({var, coeff});
11816 continue;
11817 }
11818 if (coeff > 0) {
11819 min_activity += coeff * context_->MinOf(var);
11820 max_activity += coeff * context_->MaxOf(var);
11821 } else {
11822 min_activity += coeff * context_->MaxOf(var);
11823 max_activity += coeff * context_->MinOf(var);
11824 }
11825 }
11826
11827 // We isolated the Boolean in tmp_terms_, use the helper to get
11828 // more precise activity bounds. Note that while tmp_terms_ was built from
11829 // a hash map and is in an unspecified order, the Compute*Activity() helpers
11830 // will still return a deterministic result.
11831 if (!tmp_terms_.empty()) {
11832 min_activity += helper->ComputeMinActivity(tmp_terms_);
11833 max_activity += helper->ComputeMaxActivity(tmp_terms_);
11834 }
11835
11836 if (gcd > 1) {
11837 min_activity /= gcd;
11838 max_activity /= gcd;
11839 for (int i = 0; i < common_part.size(); ++i) {
11840 common_part[i].second /= gcd;
11841 }
11842 }
11843
11844 // Create new variable.
11845 std::sort(common_part.begin(), common_part.end());
11846 new_var = context_->NewIntVarWithDefinition(
11847 Domain(min_activity, max_activity), common_part);
11848 if (new_var == -1) return false;
11849 }
11850
11851 // Replace in each constraint the common part by gcd * multiple * new_var !
11852 for (const auto [c, multiple] : block) {
11853 if (c == definiting_equation) continue;
11854
11855 auto* mutable_linear =
11856 context_->working_model->mutable_constraints(c)->mutable_linear();
11857 const int num_terms = mutable_linear->vars().size();
11858 int new_size = 0;
11859 bool new_var_already_seen = false;
11860 for (int k = 0; k < num_terms; ++k) {
11861 if (common_var_coeff_map.contains(mutable_linear->vars(k))) {
11862 CHECK_EQ(common_var_coeff_map.at(mutable_linear->vars(k)) * multiple,
11863 mutable_linear->coeffs(k));
11864 continue;
11865 }
11866
11867 // Tricky: the new variable can already be present in this expression!
11868 int64_t new_coeff = mutable_linear->coeffs(k);
11869 if (mutable_linear->vars(k) == new_var) {
11870 new_var_already_seen = true;
11871 new_coeff += gcd * multiple;
11872 if (new_coeff == 0) continue;
11873 }
11874
11875 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
11876 mutable_linear->set_coeffs(new_size, new_coeff);
11877 ++new_size;
11878 }
11879 mutable_linear->mutable_vars()->Truncate(new_size);
11880 mutable_linear->mutable_coeffs()->Truncate(new_size);
11881 if (!new_var_already_seen) {
11882 mutable_linear->add_vars(new_var);
11883 mutable_linear->add_coeffs(gcd * multiple);
11884 }
11885 if (offset != 0) {
11886 FillDomainInProto(ReadDomainFromProto(*mutable_linear)
11887 .AdditionWith(Domain(-offset * multiple)),
11888 mutable_linear);
11889 }
11890 context_->UpdateConstraintVariableUsage(c);
11891 }
11892 return true;
11893}
11894
11895namespace {
11896
11897int64_t FindVarCoeff(int var, const ConstraintProto& ct) {
11898 const int num_terms = ct.linear().vars().size();
11899 for (int k = 0; k < num_terms; ++k) {
11900 if (ct.linear().vars(k) == var) return ct.linear().coeffs(k);
11901 }
11902 return 0;
11903}
11904
11905int64_t ComputeNonZeroReduction(size_t block_size, size_t common_part_size) {
11906 // We replace the block by a column of new variable.
11907 // But we also need to define this new variable.
11908 return static_cast<int64_t>(block_size * (common_part_size - 1) -
11909 common_part_size - 1);
11910}
11911
11912} // namespace
11913
11914// The idea is to find a set of literal in AMO relationship that appear in
11915// many linear constraints. If this is the case, we can create a new variable to
11916// make an exactly one constraint, and replace it in the linear.
11917void CpModelPresolver::FindBigAtMostOneAndLinearOverlap(
11918 ActivityBoundHelper* helper) {
11919 if (time_limit_->LimitReached()) return;
11920 if (context_->ModelIsUnsat()) return;
11921 if (context_->params().presolve_inclusion_work_limit() == 0) return;
11922 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11923
11924 int64_t num_blocks = 0;
11925 int64_t nz_reduction = 0;
11926 std::vector<int> amo_cts;
11927 std::vector<int> amo_literals;
11928
11929 std::vector<int> common_part;
11930 std::vector<int> best_common_part;
11931
11932 std::vector<bool> common_part_sign;
11933 std::vector<bool> best_common_part_sign;
11934
11935 // We store for each var if the literal was positive or not.
11936 absl::flat_hash_map<int, bool> var_in_amo;
11937
11938 for (int x = 0; x < context_->working_model->variables().size(); ++x) {
11939 // We pick a variable x that appear in some AMO.
11940 if (helper->NumAmoForVariable(x) == 0) continue;
11941 if (time_limit_->LimitReached()) break;
11942 if (timer.WorkLimitIsReached()) break;
11943
11944 amo_cts.clear();
11945 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11946 for (const int c : context_->VarToConstraints(x)) {
11947 if (c < 0) continue;
11948 const ConstraintProto& ct = context_->working_model->constraints(c);
11949 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
11950 amo_cts.push_back(c);
11951 } else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
11952 amo_cts.push_back(c);
11953 }
11954 }
11955 if (amo_cts.empty()) continue;
11956
11957 // Pick a random AMO containing x.
11958 //
11959 // TODO(user): better algo!
11960 //
11961 // Note that we don't care about the polarity, for each linear constraint,
11962 // if the coeff magnitude are the same, we will just have two values
11963 // controlled by whether the AMO (or EXO subset) is at one or zero.
11964 var_in_amo.clear();
11965 amo_literals.clear();
11966 common_part.clear();
11967 common_part_sign.clear();
11968 int base_ct_index;
11969 {
11970 // For determinism.
11971 std::sort(amo_cts.begin(), amo_cts.end());
11972 const int random_c =
11973 absl::Uniform<int>(*context_->random(), 0, amo_cts.size());
11974 base_ct_index = amo_cts[random_c];
11975 const ConstraintProto& ct =
11976 context_->working_model->constraints(base_ct_index);
11977 const auto& literals = ct.constraint_case() == ConstraintProto::kAtMostOne
11978 ? ct.at_most_one().literals()
11979 : ct.exactly_one().literals();
11980 timer.TrackSimpleLoop(5 * literals.size()); // hash insert are slow.
11981 for (const int literal : literals) {
11982 amo_literals.push_back(literal);
11983 common_part.push_back(PositiveRef(literal));
11984 common_part_sign.push_back(RefIsPositive(literal));
11985 const auto [_, inserted] =
11986 var_in_amo.insert({PositiveRef(literal), RefIsPositive(literal)});
11987 CHECK(inserted);
11988 }
11989 }
11990
11991 const int64_t x_multiplier = var_in_amo.at(x) ? 1 : -1;
11992
11993 // Collect linear constraints with at least two Boolean terms in var_in_amo
11994 // with the same coefficient than x.
11995 std::vector<int> block_cts;
11996 std::vector<int> linear_cts;
11997 int max_common_part = 0;
11998 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11999 for (const int c : context_->VarToConstraints(x)) {
12000 if (c < 0) continue;
12001 const ConstraintProto& ct = context_->working_model->constraints(c);
12002 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
12003 const int num_terms = ct.linear().vars().size();
12004 if (num_terms < 2) continue;
12005
12006 timer.TrackSimpleLoop(2 * num_terms);
12007 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
12008 if (x_coeff == 0) continue; // could be in enforcement.
12009
12010 int num_in_amo = 0;
12011 for (int k = 0; k < num_terms; ++k) {
12012 const int var = ct.linear().vars(k);
12013 if (!RefIsPositive(var)) {
12014 num_in_amo = 0; // Abort.
12015 break;
12016 }
12017 const auto it = var_in_amo.find(var);
12018 if (it == var_in_amo.end()) continue;
12019 int64_t coeff = ct.linear().coeffs(k);
12020 if (!it->second) coeff = -coeff;
12021 if (coeff != x_coeff) continue;
12022 ++num_in_amo;
12023 }
12024 if (num_in_amo < 2) continue;
12025
12026 max_common_part += num_in_amo;
12027 if (num_in_amo == common_part.size()) {
12028 // This is a perfect match!
12029 block_cts.push_back(c);
12030 } else {
12031 linear_cts.push_back(c);
12032 }
12033 }
12034 if (linear_cts.empty() && block_cts.empty()) continue;
12035 if (max_common_part < 100) continue;
12036
12037 // Remember the best block encountered in the greedy algo below.
12038 // Note that we always start with the current perfect match.
12039 best_common_part = common_part;
12040 best_common_part_sign = common_part_sign;
12041 int best_block_size = block_cts.size();
12042 int best_saved_nz =
12043 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
12044
12045 // For determinism.
12046 std::sort(block_cts.begin(), block_cts.end());
12047 std::sort(linear_cts.begin(), linear_cts.end());
12048
12049 // We will just greedily compute a big block with a random order.
12050 // TODO(user): We could sort by match with the full constraint instead.
12051 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
12052 for (const int c : linear_cts) {
12053 const ConstraintProto& ct = context_->working_model->constraints(c);
12054 const int num_terms = ct.linear().vars().size();
12055 timer.TrackSimpleLoop(2 * num_terms);
12056 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
12057 CHECK_NE(x_coeff, 0);
12058
12059 common_part.clear();
12060 common_part_sign.clear();
12061 for (int k = 0; k < num_terms; ++k) {
12062 const int var = ct.linear().vars(k);
12063 const auto it = var_in_amo.find(var);
12064 if (it == var_in_amo.end()) continue;
12065 int64_t coeff = ct.linear().coeffs(k);
12066 if (!it->second) coeff = -coeff;
12067 if (coeff != x_coeff) continue;
12068 common_part.push_back(var);
12069 common_part_sign.push_back(it->second);
12070 }
12071 if (common_part.size() < 2) continue;
12072
12073 // Change var_in_amo;
12074 block_cts.push_back(c);
12075 if (common_part.size() < var_in_amo.size()) {
12076 var_in_amo.clear();
12077 for (int i = 0; i < common_part.size(); ++i) {
12078 var_in_amo[common_part[i]] = common_part_sign[i];
12079 }
12080 }
12081
12082 // We have a block that can be replaced with a single new boolean +
12083 // defining exo constraint. Note that we can also replace in the base
12084 // constraint, hence the +1 to the block size.
12085 const int64_t saved_nz =
12086 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
12087 if (saved_nz > best_saved_nz) {
12088 best_block_size = block_cts.size();
12089 best_saved_nz = saved_nz;
12090 best_common_part = common_part;
12091 best_common_part_sign = common_part_sign;
12092 }
12093 }
12094 if (best_saved_nz < 100) continue;
12095
12096 // Use the best rectangle.
12097 // We start with the full match.
12098 // TODO(user): maybe we should always just use this if it is large enough?
12099 block_cts.resize(best_block_size);
12100 var_in_amo.clear();
12101 for (int i = 0; i < best_common_part.size(); ++i) {
12102 var_in_amo[best_common_part[i]] = best_common_part_sign[i];
12103 }
12104
12105 ++num_blocks;
12106 nz_reduction += best_saved_nz;
12107 context_->UpdateRuleStats("linear matrix: common amo rectangle");
12108
12109 // First filter the amo.
12110 int new_size = 0;
12111 for (const int lit : amo_literals) {
12112 if (!var_in_amo.contains(PositiveRef(lit))) continue;
12113 amo_literals[new_size++] = lit;
12114 }
12115 if (new_size == amo_literals.size()) {
12116 const ConstraintProto& ct =
12117 context_->working_model->constraints(base_ct_index);
12118 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
12119 context_->UpdateRuleStats("TODO linear matrix: constant rectangle!");
12120 } else {
12121 context_->UpdateRuleStats(
12122 "TODO linear matrix: reuse defining constraint");
12123 }
12124 } else if (new_size + 1 == amo_literals.size()) {
12125 const ConstraintProto& ct =
12126 context_->working_model->constraints(base_ct_index);
12127 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
12128 context_->UpdateRuleStats("TODO linear matrix: reuse exo constraint");
12129 }
12130 }
12131 amo_literals.resize(new_size);
12132
12133 // Create a new literal that is one iff one of the literal in AMO is one.
12134 const int new_var = context_->NewBoolVarWithClause(amo_literals);
12135 {
12136 auto* new_exo =
12137 context_->working_model->add_constraints()->mutable_exactly_one();
12138 new_exo->mutable_literals()->Reserve(amo_literals.size() + 1);
12139 for (const int lit : amo_literals) {
12140 new_exo->add_literals(lit);
12141 }
12142 new_exo->add_literals(NegatedRef(new_var));
12143 context_->UpdateNewConstraintsVariableUsage();
12144 }
12145
12146 // Filter the base amo/exo.
12147 {
12148 ConstraintProto* ct =
12149 context_->working_model->mutable_constraints(base_ct_index);
12150 auto* mutable_literals =
12151 ct->constraint_case() == ConstraintProto::kAtMostOne
12152 ? ct->mutable_at_most_one()->mutable_literals()
12153 : ct->mutable_exactly_one()->mutable_literals();
12154 int new_size = 0;
12155 for (const int lit : *mutable_literals) {
12156 if (var_in_amo.contains(PositiveRef(lit))) continue;
12157 (*mutable_literals)[new_size++] = lit;
12158 }
12159 (*mutable_literals)[new_size++] = new_var;
12160 mutable_literals->Truncate(new_size);
12161 context_->UpdateConstraintVariableUsage(base_ct_index);
12162 }
12163
12164 // Use this Boolean in all the linear constraints.
12165 for (const int c : block_cts) {
12166 auto* mutable_linear =
12167 context_->working_model->mutable_constraints(c)->mutable_linear();
12168
12169 // The removed expression will be (offset + coeff_x * new_bool).
12170 int64_t offset = 0;
12171 int64_t coeff_x = 0;
12172
12173 int new_size = 0;
12174 const int num_terms = mutable_linear->vars().size();
12175 for (int k = 0; k < num_terms; ++k) {
12176 const int var = mutable_linear->vars(k);
12177 CHECK(RefIsPositive(var));
12178 int64_t coeff = mutable_linear->coeffs(k);
12179 const auto it = var_in_amo.find(var);
12180 if (it != var_in_amo.end()) {
12181 if (it->second) {
12182 // default is zero, amo at one means we add coeff.
12183 } else {
12184 // term is -coeff * (1 - var) + coeff.
12185 // default is coeff, amo at 1 means we remove coeff.
12186 offset += coeff;
12187 coeff = -coeff;
12188 }
12189 if (coeff_x == 0) coeff_x = coeff;
12190 CHECK_EQ(coeff, coeff_x);
12191 continue;
12192 }
12193 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
12194 mutable_linear->set_coeffs(new_size, coeff);
12195 ++new_size;
12196 }
12197
12198 // Add the new term.
12199 mutable_linear->set_vars(new_size, new_var);
12200 mutable_linear->set_coeffs(new_size, coeff_x);
12201 ++new_size;
12202
12203 mutable_linear->mutable_vars()->Truncate(new_size);
12204 mutable_linear->mutable_coeffs()->Truncate(new_size);
12205 if (offset != 0) {
12207 ReadDomainFromProto(*mutable_linear).AdditionWith(Domain(-offset)),
12208 mutable_linear);
12209 }
12210 context_->UpdateConstraintVariableUsage(c);
12211 }
12212 }
12213
12214 timer.AddCounter("blocks", num_blocks);
12215 timer.AddCounter("saved_nz", nz_reduction);
12216 DCHECK(context_->ConstraintVariableUsageIsConsistent());
12217}
12218
12219// This helps on neos-5045105-creuse.pb.gz for instance.
12220void CpModelPresolver::FindBigVerticalLinearOverlap(
12221 ActivityBoundHelper* helper) {
12222 if (time_limit_->LimitReached()) return;
12223 if (context_->ModelIsUnsat()) return;
12224 if (context_->params().presolve_inclusion_work_limit() == 0) return;
12225 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12226
12227 int64_t num_blocks = 0;
12228 int64_t nz_reduction = 0;
12229 absl::flat_hash_map<int, int64_t> coeff_map;
12230 for (int x = 0; x < context_->working_model->variables().size(); ++x) {
12231 if (timer.WorkLimitIsReached()) break;
12232
12233 bool in_enforcement = false;
12234 std::vector<int> linear_cts;
12235 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
12236 for (const int c : context_->VarToConstraints(x)) {
12237 if (c < 0) continue;
12238 const ConstraintProto& ct = context_->working_model->constraints(c);
12239 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
12240
12241 const int num_terms = ct.linear().vars().size();
12242 if (num_terms < 2) continue;
12243 bool is_canonical = true;
12244 timer.TrackSimpleLoop(num_terms);
12245 for (int k = 0; k < num_terms; ++k) {
12246 if (!RefIsPositive(ct.linear().vars(k))) {
12247 is_canonical = false;
12248 break;
12249 }
12250 }
12251 if (!is_canonical) continue;
12252
12253 // We don't care about enforcement literal, but we don't want x inside.
12254 timer.TrackSimpleLoop(ct.enforcement_literal().size());
12255 for (const int lit : ct.enforcement_literal()) {
12256 if (PositiveRef(lit) == x) {
12257 in_enforcement = true;
12258 break;
12259 }
12260 }
12261
12262 // Note(user): We will actually abort right away in this case, but we
12263 // want work_done to be deterministic! so we do the work anyway.
12264 if (in_enforcement) continue;
12265 linear_cts.push_back(c);
12266 }
12267
12268 // If a Boolean is used in enforcement, we prefer not to combine it with
12269 // others. TODO(user): more generally ignore Boolean or only replace if
12270 // there is a big non-zero improvement.
12271 if (in_enforcement) continue;
12272 if (linear_cts.size() < 10) continue;
12273
12274 // For determinism.
12275 std::sort(linear_cts.begin(), linear_cts.end());
12276 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
12277
12278 // Now it is almost the same algo as for FindBigHorizontalLinearOverlap().
12279 // We greedely compute a "common" rectangle using the first constraint
12280 // as a "base" one. Note that if a aX + bY appear in the majority of
12281 // constraint, we have a good chance to find this block since we start by
12282 // a random constraint.
12283 coeff_map.clear();
12284
12285 std::vector<std::pair<int, int64_t>> block;
12286 std::vector<std::pair<int, int64_t>> common_part;
12287 for (const int c : linear_cts) {
12288 const ConstraintProto& ct = context_->working_model->constraints(c);
12289 const int num_terms = ct.linear().vars().size();
12290 timer.TrackSimpleLoop(num_terms);
12291
12292 // Compute the coeff of x.
12293 const int64_t x_coeff = FindVarCoeff(x, ct);
12294 if (x_coeff == 0) continue;
12295
12296 if (block.empty()) {
12297 // This is our base constraint.
12298 coeff_map.clear();
12299 for (int k = 0; k < num_terms; ++k) {
12300 coeff_map[ct.linear().vars(k)] = ct.linear().coeffs(k);
12301 }
12302 if (coeff_map.size() < 2) continue;
12303 block.push_back({c, x_coeff});
12304 continue;
12305 }
12306
12307 // We are looking for a common divisor of coeff_map and this constraint.
12308 const int64_t gcd =
12309 std::gcd(std::abs(coeff_map.at(x)), std::abs(x_coeff));
12310 const int64_t multiple_base = coeff_map.at(x) / gcd;
12311 const int64_t multiple_ct = x_coeff / gcd;
12312 common_part.clear();
12313 for (int k = 0; k < num_terms; ++k) {
12314 const int64_t coeff = ct.linear().coeffs(k);
12315 if (coeff % multiple_ct != 0) continue;
12316
12317 const auto it = coeff_map.find(ct.linear().vars(k));
12318 if (it == coeff_map.end()) continue;
12319 if (it->second % multiple_base != 0) continue;
12320 if (it->second / multiple_base != coeff / multiple_ct) continue;
12321
12322 common_part.push_back({ct.linear().vars(k), coeff / multiple_ct});
12323 }
12324
12325 // Skip bad constraint.
12326 if (common_part.size() < 2) continue;
12327
12328 // Update coeff_map.
12329 block.push_back({c, x_coeff});
12330 coeff_map.clear();
12331 for (const auto [var, coeff] : common_part) {
12332 coeff_map[var] = coeff;
12333 }
12334 }
12335
12336 // We have a candidate.
12337 const int64_t saved_nz =
12338 ComputeNonZeroReduction(block.size(), coeff_map.size());
12339 if (saved_nz < 30) continue;
12340
12341 // Fix multiples, currently this contain the coeff of x for each constraint.
12342 const int64_t base_x = coeff_map.at(x);
12343 for (auto& [c, multipier] : block) {
12344 CHECK_EQ(multipier % base_x, 0);
12345 multipier /= base_x;
12346 }
12347
12348 // Introduce new_var = coeff_map and perform the substitution.
12349 if (!RemoveCommonPart(coeff_map, block, helper)) continue;
12350 ++num_blocks;
12351 nz_reduction += saved_nz;
12352 context_->UpdateRuleStats("linear matrix: common vertical rectangle");
12353 }
12354
12355 timer.AddCounter("blocks", num_blocks);
12356 timer.AddCounter("saved_nz", nz_reduction);
12357 DCHECK(context_->ConstraintVariableUsageIsConsistent());
12358}
12359
12360// Note that internally, we already split long linear into smaller chunk, so
12361// it should be beneficial to identify common part between many linear
12362// constraint.
12363//
12364// Note(user): This was made to work on var-smallemery-m6j6.pb.gz, but applies
12365// to quite a few miplib problem. Try to improve the heuristics and algorithm to
12366// be faster and detect larger block.
12367void CpModelPresolver::FindBigHorizontalLinearOverlap(
12368 ActivityBoundHelper* helper) {
12369 if (time_limit_->LimitReached()) return;
12370 if (context_->ModelIsUnsat()) return;
12371 if (context_->params().presolve_inclusion_work_limit() == 0) return;
12372 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12373
12374 const int num_constraints = context_->working_model->constraints_size();
12375 std::vector<std::pair<int, int>> to_sort;
12376 for (int c = 0; c < num_constraints; ++c) {
12377 const ConstraintProto& ct = context_->working_model->constraints(c);
12378 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
12379 const int size = ct.linear().vars().size();
12380 if (size < 5) continue;
12381 to_sort.push_back({-size, c});
12382 }
12383 std::sort(to_sort.begin(), to_sort.end());
12384
12385 std::vector<int> sorted_linear;
12386 for (int i = 0; i < to_sort.size(); ++i) {
12387 sorted_linear.push_back(to_sort[i].second);
12388 }
12389
12390 // On large problem, using and hash_map can be slow, so we use the vector
12391 // version and for now fill the map only when doing the change.
12392 std::vector<int> var_to_coeff_non_zeros;
12393 std::vector<int64_t> var_to_coeff(context_->working_model->variables_size(),
12394 0);
12395
12396 int64_t num_blocks = 0;
12397 int64_t nz_reduction = 0;
12398 for (int i = 0; i < sorted_linear.size(); ++i) {
12399 const int c = sorted_linear[i];
12400 if (c < 0) continue;
12401 if (timer.WorkLimitIsReached()) break;
12402
12403 for (const int var : var_to_coeff_non_zeros) {
12404 var_to_coeff[var] = 0;
12405 }
12406 var_to_coeff_non_zeros.clear();
12407 {
12408 const ConstraintProto& ct = context_->working_model->constraints(c);
12409 const int num_terms = ct.linear().vars().size();
12410 timer.TrackSimpleLoop(num_terms);
12411 for (int k = 0; k < num_terms; ++k) {
12412 const int var = ct.linear().vars(k);
12413 var_to_coeff[var] = ct.linear().coeffs(k);
12414 var_to_coeff_non_zeros.push_back(var);
12415 }
12416 }
12417
12418 // Look for an initial overlap big enough.
12419 //
12420 // Note that because we construct it incrementally, we need the first two
12421 // constraint to have an overlap of at least half this.
12422 int saved_nz = 100;
12423 std::vector<int> used_sorted_linear = {i};
12424 std::vector<std::pair<int, int64_t>> block = {{c, 1}};
12425 std::vector<std::pair<int, int64_t>> common_part;
12426 std::vector<std::pair<int, int>> old_matches;
12427
12428 for (int j = 0; j < sorted_linear.size(); ++j) {
12429 if (i == j) continue;
12430 const int other_c = sorted_linear[j];
12431 if (other_c < 0) continue;
12432 const ConstraintProto& ct = context_->working_model->constraints(other_c);
12433
12434 // No need to continue if linear is not large enough.
12435 const int num_terms = ct.linear().vars().size();
12436 const int best_saved_nz =
12437 ComputeNonZeroReduction(block.size() + 1, num_terms);
12438 if (best_saved_nz <= saved_nz) break;
12439
12440 // This is the hot loop here.
12441 timer.TrackSimpleLoop(num_terms);
12442 common_part.clear();
12443 for (int k = 0; k < num_terms; ++k) {
12444 const int var = ct.linear().vars(k);
12445 if (var_to_coeff[var] == ct.linear().coeffs(k)) {
12446 common_part.push_back({var, ct.linear().coeffs(k)});
12447 }
12448 }
12449
12450 // We replace (new_block_size) * (common_size) by
12451 // 1/ and equation of size common_size + 1
12452 // 2/ new_block_size variable
12453 // So new_block_size * common_size - common_size - 1 - new_block_size
12454 // which is (new_block_size - 1) * (common_size - 1) - 2;
12455 const int64_t new_saved_nz =
12456 ComputeNonZeroReduction(block.size() + 1, common_part.size());
12457 if (new_saved_nz > saved_nz) {
12458 saved_nz = new_saved_nz;
12459 used_sorted_linear.push_back(j);
12460 block.push_back({other_c, 1});
12461
12462 // Rebuild the map.
12463 // TODO(user): We could only clear the non-common part.
12464 for (const int var : var_to_coeff_non_zeros) {
12465 var_to_coeff[var] = 0;
12466 }
12467 var_to_coeff_non_zeros.clear();
12468 for (const auto [var, coeff] : common_part) {
12469 var_to_coeff[var] = coeff;
12470 var_to_coeff_non_zeros.push_back(var);
12471 }
12472 } else {
12473 if (common_part.size() > 1) {
12474 old_matches.push_back({j, common_part.size()});
12475 }
12476 }
12477 }
12478
12479 // Introduce a new variable = common_part.
12480 // Use it in all linear constraint.
12481 if (block.size() > 1) {
12482 // Try to extend with exact matches that were skipped.
12483 const int match_size = var_to_coeff_non_zeros.size();
12484 for (const auto [index, old_match_size] : old_matches) {
12485 if (old_match_size < match_size) continue;
12486
12487 int new_match_size = 0;
12488 const int other_c = sorted_linear[index];
12489 const ConstraintProto& ct =
12490 context_->working_model->constraints(other_c);
12491 const int num_terms = ct.linear().vars().size();
12492 for (int k = 0; k < num_terms; ++k) {
12493 if (var_to_coeff[ct.linear().vars(k)] == ct.linear().coeffs(k)) {
12494 ++new_match_size;
12495 }
12496 }
12497 if (new_match_size == match_size) {
12498 context_->UpdateRuleStats(
12499 "linear matrix: common horizontal rectangle extension");
12500 used_sorted_linear.push_back(index);
12501 block.push_back({other_c, 1});
12502 }
12503 }
12504
12505 // TODO(user): avoid creating the map? this is not visible in profile
12506 // though since we only do it when a reduction is performed.
12507 absl::flat_hash_map<int, int64_t> coeff_map;
12508 for (const int var : var_to_coeff_non_zeros) {
12509 coeff_map[var] = var_to_coeff[var];
12510 }
12511 if (!RemoveCommonPart(coeff_map, block, helper)) continue;
12512
12513 ++num_blocks;
12514 nz_reduction += ComputeNonZeroReduction(block.size(), coeff_map.size());
12515 context_->UpdateRuleStats("linear matrix: common horizontal rectangle");
12516 for (const int i : used_sorted_linear) sorted_linear[i] = -1;
12517 }
12518 }
12519
12520 timer.AddCounter("blocks", num_blocks);
12521 timer.AddCounter("saved_nz", nz_reduction);
12522 timer.AddCounter("linears", sorted_linear.size());
12523 DCHECK(context_->ConstraintVariableUsageIsConsistent());
12524}
12525
12526// Find two linear constraints of the form:
12527// - term1 + identical_terms = rhs1
12528// - term2 + identical_terms = rhs2
12529// This allows to infer an affine relation, and remove one constraint and one
12530// variable.
12531void CpModelPresolver::FindAlmostIdenticalLinearConstraints() {
12532 if (time_limit_->LimitReached()) return;
12533 if (context_->ModelIsUnsat()) return;
12534
12535 // Work tracking is required, since in the worst case (n identical
12536 // constraints), we are in O(n^3). In practice we are way faster though. And
12537 // identical constraints should have already be removed when we call this.
12538 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12539
12540 // Only keep non-enforced linear equality of size > 2. Sort by size.
12541 std::vector<std::pair<int, int>> to_sort;
12542 const int num_constraints = context_->working_model->constraints_size();
12543 for (int c = 0; c < num_constraints; ++c) {
12544 const ConstraintProto& ct = context_->working_model->constraints(c);
12545 if (!IsLinearEqualityConstraint(ct)) continue;
12546 if (ct.linear().vars().size() <= 2) continue;
12547
12548 // Our canonicalization should sort constraints, we skip non-canonical ones.
12549 if (!std::is_sorted(ct.linear().vars().begin(), ct.linear().vars().end())) {
12550 continue;
12551 }
12552
12553 to_sort.push_back({ct.linear().vars().size(), c});
12554 }
12555 std::sort(to_sort.begin(), to_sort.end());
12556
12557 // One watcher data structure.
12558 // This is similar to what is used by the inclusion detector.
12559 std::vector<int> var_to_clear;
12560 std::vector<std::vector<std::pair<int, int64_t>>> var_to_ct_coeffs_;
12561 const int num_variables = context_->working_model->variables_size();
12562 var_to_ct_coeffs_.resize(num_variables);
12563
12564 int end;
12565 int num_tested_pairs = 0;
12566 int num_affine_relations = 0;
12567 for (int start = 0; start < to_sort.size(); start = end) {
12568 // Split by identical size.
12569 end = start + 1;
12570 const int length = to_sort[start].first;
12571 for (; end < to_sort.size(); ++end) {
12572 if (to_sort[end].first != length) break;
12573 }
12574 const int span_size = end - start;
12575 if (span_size == 1) continue;
12576
12577 // Watch one term of each constraint randomly.
12578 for (const int var : var_to_clear) var_to_ct_coeffs_[var].clear();
12579 var_to_clear.clear();
12580 for (int i = start; i < end; ++i) {
12581 const int c = to_sort[i].second;
12582 const LinearConstraintProto& lin =
12583 context_->working_model->constraints(c).linear();
12584 const int index =
12585 absl::Uniform<int>(*context_->random(), 0, lin.vars().size());
12586 const int var = lin.vars(index);
12587 if (var_to_ct_coeffs_[var].empty()) var_to_clear.push_back(var);
12588 var_to_ct_coeffs_[var].push_back({c, lin.coeffs(index)});
12589 }
12590
12591 // For each constraint, try other constraints that have at least one term in
12592 // common with the same coeff. Note that for two constraint of size 3, we
12593 // will miss a working pair only if we both watch the variable that is
12594 // different. So only with a probability (1/3)^2. Since we call this more
12595 // than once per presolve, we should be mostly good. For larger constraint,
12596 // we shouldn't miss much.
12597 for (int i1 = start; i1 < end; ++i1) {
12598 if (timer.WorkLimitIsReached()) break;
12599 const int c1 = to_sort[i1].second;
12600 const LinearConstraintProto& lin1 =
12601 context_->working_model->constraints(c1).linear();
12602 bool skip = false;
12603 for (int i = 0; !skip && i < lin1.vars().size(); ++i) {
12604 for (const auto [c2, coeff2] : var_to_ct_coeffs_[lin1.vars(i)]) {
12605 if (c2 == c1) continue;
12606
12607 // TODO(user): we could easily deal with * -1 or other multiples.
12608 if (coeff2 != lin1.coeffs(i)) continue;
12609 if (timer.WorkLimitIsReached()) break;
12610
12611 // Skip if we processed this earlier and deleted it.
12612 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
12613 if (ct2.constraint_case() != ConstraintProto::kLinear) continue;
12614 const LinearConstraintProto& lin2 =
12615 context_->working_model->constraints(c2).linear();
12616 if (lin2.vars().size() != length) continue;
12617
12618 // TODO(user): In practice LinearsDifferAtOneTerm() will abort
12619 // early if the constraints differ early, so we are even faster than
12620 // this.
12621 timer.TrackSimpleLoop(length);
12622
12623 ++num_tested_pairs;
12624 if (LinearsDifferAtOneTerm(lin1, lin2)) {
12625 // The two equalities only differ at one term !
12626 // do c1 -= c2 and presolve c1 right away.
12627 // We should detect new affine relation and remove it.
12628 auto* to_modify = context_->working_model->mutable_constraints(c1);
12630 -1, context_->working_model->constraints(c2), to_modify)) {
12631 continue;
12632 }
12633
12634 // Affine will be of size 2, but we might also have the same
12635 // variable with different coeff in both constraint, in which case
12636 // the linear will be of size 1.
12637 DCHECK_LE(to_modify->linear().vars().size(), 2);
12638
12639 ++num_affine_relations;
12640 context_->UpdateRuleStats(
12641 "linear: advanced affine relation from 2 constraints");
12642
12643 // We should stop processing c1 since it should be empty afterward.
12644 DivideLinearByGcd(to_modify);
12645 PresolveSmallLinear(to_modify);
12646 context_->UpdateConstraintVariableUsage(c1);
12647 skip = true;
12648 break;
12649 }
12650 }
12651 }
12652 }
12653 }
12654
12655 timer.AddCounter("num_tested_pairs", num_tested_pairs);
12656 timer.AddCounter("found", num_affine_relations);
12657 DCHECK(context_->ConstraintVariableUsageIsConsistent());
12658}
12659
12660void CpModelPresolver::ExtractEncodingFromLinear() {
12661 if (time_limit_->LimitReached()) return;
12662 if (context_->ModelIsUnsat()) return;
12663 if (context_->params().presolve_inclusion_work_limit() == 0) return;
12664 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12665
12666 // TODO(user): compute on the fly instead of temporary storing variables?
12667 std::vector<int> relevant_constraints;
12668 CompactVectorVector<int> storage;
12669 InclusionDetector detector(storage, time_limit_);
12670 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
12671
12672 // Loop over the constraints and fill the structures above.
12673 //
12674 // TODO(user): Ideally we want to process exactly_one first in case a
12675 // linear constraint is both included in an at_most_one and an exactly_one.
12676 std::vector<int> vars;
12677 const int num_constraints = context_->working_model->constraints().size();
12678 for (int c = 0; c < num_constraints; ++c) {
12679 const ConstraintProto& ct = context_->working_model->constraints(c);
12680 switch (ct.constraint_case()) {
12682 vars.clear();
12683 for (const int ref : ct.at_most_one().literals()) {
12684 vars.push_back(PositiveRef(ref));
12685 }
12686 relevant_constraints.push_back(c);
12687 detector.AddPotentialSuperset(storage.Add(vars));
12688 break;
12689 }
12691 vars.clear();
12692 for (const int ref : ct.exactly_one().literals()) {
12693 vars.push_back(PositiveRef(ref));
12694 }
12695 relevant_constraints.push_back(c);
12696 detector.AddPotentialSuperset(storage.Add(vars));
12697 break;
12698 }
12700 // We only consider equality with no enforcement.
12701 if (!IsLinearEqualityConstraint(ct)) continue;
12702
12703 // We also want a single non-Boolean.
12704 // Note that this assume the constraint is canonicalized.
12705 bool is_candidate = true;
12706 int num_integers = 0;
12707 vars.clear();
12708 const int num_terms = ct.linear().vars().size();
12709 for (int i = 0; i < num_terms; ++i) {
12710 const int ref = ct.linear().vars(i);
12711 if (context_->CanBeUsedAsLiteral(ref)) {
12712 vars.push_back(PositiveRef(ref));
12713 } else {
12714 ++num_integers;
12715 if (std::abs(ct.linear().coeffs(i)) != 1) {
12716 is_candidate = false;
12717 break;
12718 }
12719 if (num_integers == 2) {
12720 is_candidate = false;
12721 break;
12722 }
12723 }
12724 }
12725
12726 // We ignore cases with just one Boolean as this should be already dealt
12727 // with elsewhere.
12728 if (is_candidate && num_integers == 1 && vars.size() > 1) {
12729 relevant_constraints.push_back(c);
12730 detector.AddPotentialSubset(storage.Add(vars));
12731 }
12732 break;
12733 }
12734 default:
12735 break;
12736 }
12737 }
12738
12739 // Stats.
12740 int64_t num_exactly_one_encodings = 0;
12741 int64_t num_at_most_one_encodings = 0;
12742 int64_t num_literals = 0;
12743 int64_t num_unique_terms = 0;
12744 int64_t num_multiple_terms = 0;
12745
12746 detector.DetectInclusions([&](int subset, int superset) {
12747 const int subset_c = relevant_constraints[subset];
12748 const int superset_c = relevant_constraints[superset];
12749 const ConstraintProto& superset_ct =
12750 context_->working_model->constraints(superset_c);
12751 if (superset_ct.constraint_case() == ConstraintProto::kAtMostOne) {
12752 ++num_at_most_one_encodings;
12753 } else {
12754 ++num_exactly_one_encodings;
12755 }
12756 num_literals += storage[subset].size();
12757 context_->UpdateRuleStats("encoding: extracted from linear");
12758
12759 if (!ProcessEncodingFromLinear(subset_c, superset_ct, &num_unique_terms,
12760 &num_multiple_terms)) {
12761 detector.Stop(); // UNSAT.
12762 }
12763
12764 detector.StopProcessingCurrentSubset();
12765 });
12766
12767 timer.AddCounter("potential_supersets", detector.num_potential_supersets());
12768 timer.AddCounter("potential_subsets", detector.num_potential_subsets());
12769 timer.AddCounter("amo_encodings", num_at_most_one_encodings);
12770 timer.AddCounter("exo_encodings", num_exactly_one_encodings);
12771 timer.AddCounter("unique_terms", num_unique_terms);
12772 timer.AddCounter("multiple_terms", num_multiple_terms);
12773 timer.AddCounter("literals", num_literals);
12774}
12775
12776// Special case: if a literal l appear in exactly two constraints:
12777// - l => var in domain1
12778// - not(l) => var in domain2
12779// then we know that domain(var) is included in domain1 U domain2,
12780// and that the literal l can be removed (and determined at postsolve).
12781//
12782// TODO(user): This could be generalized further to linear of size > 1 if for
12783// example the terms are the same.
12784//
12785// We wait for the model expansion to take place in order to avoid removing
12786// encoding that will later be re-created during expansion.
12787void CpModelPresolver::LookAtVariableWithDegreeTwo(int var) {
12788 CHECK(RefIsPositive(var));
12789 CHECK(context_->ConstraintVariableGraphIsUpToDate());
12790 if (context_->ModelIsUnsat()) return;
12791 if (context_->params().keep_all_feasible_solutions_in_presolve()) return;
12792 if (context_->IsFixed(var)) return;
12793 if (!context_->ModelIsExpanded()) return;
12794 if (!context_->CanBeUsedAsLiteral(var)) return;
12795
12796 // TODO(user): If var is in objective, we might be able to tighten domains.
12797 // ex: enf => x \in [0, 1]
12798 // not(enf) => x \in [1, 2]
12799 // The x can be removed from one place. Maybe just do <=> not in [0,1] with
12800 // dual code?
12801 if (context_->VarToConstraints(var).size() != 2) return;
12802
12803 bool abort = false;
12804 int ct_var = -1;
12805 Domain union_of_domain;
12806 int num_positive = 0;
12807 std::vector<int> constraint_indices_to_remove;
12808 for (const int c : context_->VarToConstraints(var)) {
12809 if (c < 0) {
12810 abort = true;
12811 break;
12812 }
12813 constraint_indices_to_remove.push_back(c);
12814 const ConstraintProto& ct = context_->working_model->constraints(c);
12815 if (ct.enforcement_literal().size() != 1 ||
12816 PositiveRef(ct.enforcement_literal(0)) != var ||
12817 ct.constraint_case() != ConstraintProto::kLinear ||
12818 ct.linear().vars().size() != 1) {
12819 abort = true;
12820 break;
12821 }
12822 if (ct.enforcement_literal(0) == var) ++num_positive;
12823 if (ct_var != -1 && PositiveRef(ct.linear().vars(0)) != ct_var) {
12824 abort = true;
12825 break;
12826 }
12827 ct_var = PositiveRef(ct.linear().vars(0));
12828 union_of_domain = union_of_domain.UnionWith(
12829 ReadDomainFromProto(ct.linear())
12830 .InverseMultiplicationBy(RefIsPositive(ct.linear().vars(0))
12831 ? ct.linear().coeffs(0)
12832 : -ct.linear().coeffs(0)));
12833 }
12834 if (abort) return;
12835 if (num_positive != 1) return;
12836 if (!context_->IntersectDomainWith(ct_var, union_of_domain)) return;
12837
12838 context_->UpdateRuleStats("variables: removable enforcement literal");
12839 absl::c_sort(constraint_indices_to_remove); // For determinism
12840
12841 // Note(user): Only one constraint should be enough given how the postsolve
12842 // work. However that will not work for the case where we postsolve by solving
12843 // the mapping model (debug_postsolve_with_full_solver:true).
12844 for (const int c : constraint_indices_to_remove) {
12845 context_->NewMappingConstraint(context_->working_model->constraints(c),
12846 __FILE__, __LINE__);
12847 context_->working_model->mutable_constraints(c)->Clear();
12848 context_->UpdateConstraintVariableUsage(c);
12849 }
12850 context_->MarkVariableAsRemoved(var);
12851}
12852
12853namespace {
12854
12855absl::Span<const int> AtMostOneOrExactlyOneLiterals(const ConstraintProto& ct) {
12856 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
12857 return {ct.at_most_one().literals()};
12858 } else {
12859 return {ct.exactly_one().literals()};
12860 }
12861}
12862
12863} // namespace
12864
12865void CpModelPresolver::ProcessVariableInTwoAtMostOrExactlyOne(int var) {
12866 DCHECK(RefIsPositive(var));
12867 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
12868 if (context_->ModelIsUnsat()) return;
12869 if (context_->IsFixed(var)) return;
12870 if (context_->VariableWasRemoved(var)) return;
12871 if (!context_->ModelIsExpanded()) return;
12872 if (!context_->CanBeUsedAsLiteral(var)) return;
12873
12874 int64_t cost = 0;
12875 if (context_->VarToConstraints(var).contains(kObjectiveConstraint)) {
12876 if (context_->VarToConstraints(var).size() != 3) return;
12877 cost = context_->ObjectiveMap().at(var);
12878 } else {
12879 if (context_->VarToConstraints(var).size() != 2) return;
12880 }
12881
12882 // We have a variable with a cost (or without) that appear in two constraints.
12883 // We want two at_most_one or exactly_one.
12884 // TODO(user): Also deal with bool_and.
12885 int c1 = -1;
12886 int c2 = -1;
12887 for (const int c : context_->VarToConstraints(var)) {
12888 if (c < 0) continue;
12889 const ConstraintProto& ct = context_->working_model->constraints(c);
12890 if (ct.constraint_case() != ConstraintProto::kAtMostOne &&
12891 ct.constraint_case() != ConstraintProto::kExactlyOne) {
12892 return;
12893 }
12894 if (c1 == -1) {
12895 c1 = c;
12896 } else {
12897 c2 = c;
12898 }
12899 }
12900
12901 // This can happen for variable in a kAffineRelationConstraint.
12902 if (c1 == -1 || c2 == -1) return;
12903
12904 // Tricky: We iterate on a map above, so the order is non-deterministic, we
12905 // do not want that, so we re-order the constraints.
12906 if (c1 > c2) std::swap(c1, c2);
12907
12908 // We can always sum the two constraints.
12909 // If var appear in one and not(var) in the other, the two term cancel out to
12910 // one, so we still have an <= 1 (or eventually a ==1 (see below).
12911 //
12912 // Note that if the constraint are of size one, they can just be preprocessed
12913 // individually and just be removed. So we abort here as the code below
12914 // is incorrect if new_ct is an empty constraint.
12915 context_->tmp_literals.clear();
12916 int c1_ref = std::numeric_limits<int>::min();
12917 const ConstraintProto& ct1 = context_->working_model->constraints(c1);
12918 if (AtMostOneOrExactlyOneLiterals(ct1).size() <= 1) return;
12919 for (const int lit : AtMostOneOrExactlyOneLiterals(ct1)) {
12920 if (PositiveRef(lit) == var) {
12921 c1_ref = lit;
12922 } else {
12923 context_->tmp_literals.push_back(lit);
12924 }
12925 }
12926 int c2_ref = std::numeric_limits<int>::min();
12927 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
12928 if (AtMostOneOrExactlyOneLiterals(ct2).size() <= 1) return;
12929 for (const int lit : AtMostOneOrExactlyOneLiterals(ct2)) {
12930 if (PositiveRef(lit) == var) {
12931 c2_ref = lit;
12932 } else {
12933 context_->tmp_literals.push_back(lit);
12934 }
12935 }
12936 DCHECK_NE(c1_ref, std::numeric_limits<int>::min());
12937 DCHECK_NE(c2_ref, std::numeric_limits<int>::min());
12938 if (c1_ref != NegatedRef(c2_ref)) return;
12939
12940 // If the cost is non-zero, we can use an exactly one to make it zero.
12941 // Use that exactly one in the postsolve to recover the value of var.
12942 int64_t cost_shift = 0;
12943 absl::Span<const int> literals;
12944 if (ct1.constraint_case() == ConstraintProto::kExactlyOne) {
12945 cost_shift = RefIsPositive(c1_ref) ? cost : -cost;
12946 literals = ct1.exactly_one().literals();
12947 } else if (ct2.constraint_case() == ConstraintProto::kExactlyOne) {
12948 cost_shift = RefIsPositive(c2_ref) ? cost : -cost;
12949 literals = ct2.exactly_one().literals();
12950 } else {
12951 // Dual argument. The one with a negative cost can be transformed to
12952 // an exactly one.
12953 // Tricky: if there is a cost, we don't want the objective to be
12954 // constraining to be able to do that.
12955 if (context_->params().keep_all_feasible_solutions_in_presolve()) return;
12956 if (context_->params().keep_symmetry_in_presolve()) return;
12957 if (cost != 0 && context_->ObjectiveDomainIsConstraining()) return;
12958
12959 if (RefIsPositive(c1_ref) == (cost < 0)) {
12960 cost_shift = RefIsPositive(c1_ref) ? cost : -cost;
12961 literals = ct1.at_most_one().literals();
12962 } else {
12963 cost_shift = RefIsPositive(c2_ref) ? cost : -cost;
12964 literals = ct2.at_most_one().literals();
12965 }
12966 }
12967
12968 if (!context_->ShiftCostInExactlyOne(literals, cost_shift)) return;
12969 DCHECK(!context_->ObjectiveMap().contains(var));
12970 context_->NewMappingConstraint(__FILE__, __LINE__)
12971 ->mutable_exactly_one()
12972 ->mutable_literals()
12973 ->Assign(literals.begin(), literals.end());
12974
12975 // We can now replace the two constraint by a single one, and delete var!
12976 const int new_ct_index = context_->working_model->constraints().size();
12977 ConstraintProto* new_ct = context_->working_model->add_constraints();
12978 if (ct1.constraint_case() == ConstraintProto::kExactlyOne &&
12979 ct2.constraint_case() == ConstraintProto::kExactlyOne) {
12980 for (const int lit : context_->tmp_literals) {
12981 new_ct->mutable_exactly_one()->add_literals(lit);
12982 }
12983 } else {
12984 // At most one here is enough: if all zero, we can satisfy one of the
12985 // two exactly one at postsolve.
12986 for (const int lit : context_->tmp_literals) {
12987 new_ct->mutable_at_most_one()->add_literals(lit);
12988 }
12989 }
12990
12991 context_->UpdateNewConstraintsVariableUsage();
12992 context_->working_model->mutable_constraints(c1)->Clear();
12993 context_->UpdateConstraintVariableUsage(c1);
12994 context_->working_model->mutable_constraints(c2)->Clear();
12995 context_->UpdateConstraintVariableUsage(c2);
12996
12997 context_->UpdateRuleStats(
12998 "at_most_one: resolved two constraints with opposite literal");
12999 context_->MarkVariableAsRemoved(var);
13000
13001 // TODO(user): If the merged list contains duplicates or literal that are
13002 // negation of other, we need to deal with that right away. For some reason
13003 // something is not robust to that it seems. Investigate & fix!
13004 DCHECK_NE(new_ct->constraint_case(), ConstraintProto::CONSTRAINT_NOT_SET);
13005 if (PresolveAtMostOrExactlyOne(new_ct)) {
13006 context_->UpdateConstraintVariableUsage(new_ct_index);
13007 }
13008}
13009
13010// TODO(user): We can still remove the variable even if we want to keep
13011// all feasible solutions for the cases when we have a full encoding.
13012// Similarly this shouldn't break symmetry, but we do need to do it for all
13013// symmetric variable at once.
13014//
13015// TODO(user): In fixed search, we disable this rule because we don't update
13016// the search strategy, but for some strategy we could.
13017//
13018// TODO(user): The hint might get lost if the encoding was created during
13019// the presolve.
13020void CpModelPresolver::ProcessVariableOnlyUsedInEncoding(int var) {
13021 if (context_->ModelIsUnsat()) return;
13022 if (context_->params().keep_all_feasible_solutions_in_presolve()) return;
13023 if (context_->params().keep_symmetry_in_presolve()) return;
13024 if (context_->IsFixed(var)) return;
13025 if (context_->VariableWasRemoved(var)) return;
13026 if (context_->CanBeUsedAsLiteral(var)) return;
13027 if (context_->params().search_branching() == SatParameters::FIXED_SEARCH) {
13028 return;
13029 }
13030
13031 const bool is_only_used_in_linear1 =
13032 context_->VariableIsOnlyUsedInLinear1AndOneExtraConstraint(var);
13033 const bool is_only_used_in_encoding =
13034 context_->VariableIsOnlyUsedInEncodingAndMaybeInObjective(var);
13035 if (!is_only_used_in_encoding && is_only_used_in_linear1) {
13036 VariableEncodingLocalModel local_model;
13037 local_model.var = var;
13038 local_model.single_constraint_using_the_var_outside_the_local_model = -1;
13039 local_model.var_in_more_than_one_constraint_outside_the_local_model = false;
13040 for (const int c : context_->VarToConstraints(var)) {
13041 if (c >= 0) {
13042 const ConstraintProto& ct = context_->working_model->constraints(c);
13043 if (ct.constraint_case() == ConstraintProto::kLinear &&
13044 ct.linear().vars().size() == 1 && ct.linear().vars(0) == var) {
13045 local_model.linear1_constraints.push_back(c);
13046 continue;
13047 }
13048 }
13049 if (c == kObjectiveConstraint) {
13050 local_model.variable_coeff_in_objective =
13051 context_->ObjectiveMap().at(var);
13052 } else if (
13053 local_model.single_constraint_using_the_var_outside_the_local_model ==
13054 -1 &&
13055 c >= 0) {
13056 // First "other" constraint.
13057 local_model.single_constraint_using_the_var_outside_the_local_model = c;
13058 } else {
13059 // We have a second "other" constraint.
13060 local_model.single_constraint_using_the_var_outside_the_local_model =
13061 -1;
13062 local_model.var_in_more_than_one_constraint_outside_the_local_model =
13063 true;
13064 }
13065 }
13066
13067 MaybeTransferLinear1ToAnotherVariable(local_model, context_);
13068 return;
13069 }
13070 if (!is_only_used_in_encoding) return;
13071
13072 // Presolve newly created constraints.
13073 const int old_size = context_->working_model->constraints_size();
13074 TryToReplaceVariableByItsEncoding(var, context_, solution_crush_);
13075 for (int c = old_size; c < context_->working_model->constraints_size(); ++c) {
13076 if (PresolveOneConstraint(c)) {
13077 context_->UpdateConstraintVariableUsage(c);
13078 }
13079 }
13080}
13081
13082void CpModelPresolver::TryToSimplifyDomain(int var) {
13083 DCHECK(RefIsPositive(var));
13084 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
13085 if (context_->ModelIsUnsat()) return;
13086 if (context_->IsFixed(var)) return;
13087 if (context_->VariableWasRemoved(var)) return;
13088 if (context_->VariableIsNotUsedAnymore(var)) return;
13089
13090 const AffineRelation::Relation r = context_->GetAffineRelation(var);
13091 if (r.representative != var) return;
13092
13093 // Only process discrete domain.
13094 const Domain& domain = context_->DomainOf(var);
13095
13096 // Special case for non-Boolean domain of size 2.
13097 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
13098 context_->CanonicalizeDomainOfSizeTwo(var);
13099 return;
13100 }
13101
13102 if (domain.NumIntervals() != domain.Size()) return;
13103
13104 const int64_t var_min = domain.Min();
13105 int64_t gcd = domain[1].start - var_min;
13106 for (int index = 2; index < domain.NumIntervals(); ++index) {
13107 const ClosedInterval& i = domain[index];
13108 DCHECK_EQ(i.start, i.end);
13109 const int64_t shifted_value = i.start - var_min;
13110 DCHECK_GT(shifted_value, 0);
13111
13112 gcd = std::gcd(gcd, shifted_value);
13113 if (gcd == 1) break;
13114 }
13115 if (gcd == 1) return;
13116
13117 // This does all the work since var * 1 % gcd = var_min % gcd.
13118 context_->CanonicalizeAffineVariable(var, 1, gcd, var_min);
13119}
13120
13121// Adds all affine relations to our model for the variables that are still used.
13122void CpModelPresolver::EncodeAllAffineRelations() {
13123 int64_t num_added = 0;
13124 for (int var = 0; var < context_->working_model->variables_size(); ++var) {
13125 if (context_->IsFixed(var)) continue;
13126
13127 const AffineRelation::Relation r = context_->GetAffineRelation(var);
13128 if (r.representative == var) continue;
13129
13130 // TODO(user): It seems some affine relation are still removable at this
13131 // stage even though they should be removed inside PresolveToFixPoint().
13132 // Investigate. For now, we just remove such relations.
13133 if (context_->VariableIsNotUsedAnymore(var)) continue;
13134 if (!PresolveAffineRelationIfAny(var)) break;
13135 if (context_->VariableIsNotUsedAnymore(var)) continue;
13136 if (context_->IsFixed(var)) continue;
13137
13138 ++num_added;
13139 ConstraintProto* ct = context_->working_model->add_constraints();
13140 auto* arg = ct->mutable_linear();
13141 arg->add_vars(var);
13142 arg->add_coeffs(1);
13143 arg->add_vars(r.representative);
13144 arg->add_coeffs(-r.coeff);
13145 arg->add_domain(r.offset);
13146 arg->add_domain(r.offset);
13147 context_->UpdateNewConstraintsVariableUsage();
13148 }
13149
13150 // Now that we encoded all remaining affine relation with constraints, we
13151 // remove the special marker to have a proper constraint variable graph.
13152 context_->RemoveAllVariablesFromAffineRelationConstraint();
13153
13154 if (num_added > 0) {
13155 SOLVER_LOG(logger_, num_added, " affine relations still in the model.");
13156 }
13157}
13158
13159// Presolve a variable in relation with its representative.
13160bool CpModelPresolver::PresolveAffineRelationIfAny(int var) {
13161 const AffineRelation::Relation r = context_->GetAffineRelation(var);
13162 if (r.representative == var) return true;
13163
13164 // Propagate domains.
13165 if (!context_->PropagateAffineRelation(var)) return false;
13166
13167 // Once an affine relation is detected, the variables should be added to
13168 // the kAffineRelationConstraint. The only way to be unmarked is if the
13169 // variable do not appear in any other constraint and is not a representative,
13170 // in which case it should never be added back.
13171 if (context_->IsFixed(var)) return true;
13172 DCHECK(context_->VarToConstraints(var).contains(kAffineRelationConstraint));
13173 DCHECK(!context_->VariableIsNotUsedAnymore(r.representative));
13174
13175 // If var is no longer used, remove. Note that we can always do that since we
13176 // propagated the domain above and so we can find a feasible value for a for
13177 // any value of the representative.
13178 context_->RemoveNonRepresentativeAffineVariableIfUnused(var);
13179 return true;
13180}
13181
13182// Re-add to the queue the constraints that touch a variable that changed.
13183bool CpModelPresolver::ProcessChangedVariables(std::vector<bool>* in_queue,
13184 std::deque<int>* queue) {
13185 // TODO(user): Avoid reprocessing the constraints that changed the domain?
13186 if (context_->ModelIsUnsat()) return false;
13187 if (time_limit_->LimitReached()) return false;
13188 in_queue->resize(context_->working_model->constraints_size(), false);
13189 const auto& vector_that_can_grow_during_iter =
13190 context_->modified_domains.PositionsSetAtLeastOnce();
13191 for (int i = 0; i < vector_that_can_grow_during_iter.size(); ++i) {
13192 const int v = vector_that_can_grow_during_iter[i];
13193 context_->modified_domains.Clear(v);
13194 if (context_->VariableIsNotUsedAnymore(v)) continue;
13195 if (context_->ModelIsUnsat()) return false;
13196 if (!PresolveAffineRelationIfAny(v)) return false;
13197 if (context_->VariableIsNotUsedAnymore(v)) continue;
13198
13199 TryToSimplifyDomain(v);
13200
13201 // TODO(user): Integrate these with TryToSimplifyDomain().
13202 if (context_->ModelIsUnsat()) return false;
13203 context_->UpdateNewConstraintsVariableUsage();
13204
13205 if (!context_->CanonicalizeOneObjectiveVariable(v)) return false;
13206
13207 in_queue->resize(context_->working_model->constraints_size(), false);
13208 const int size_before = queue->size();
13209 for (const int c : context_->VarToConstraints(v)) {
13210 if (c >= 0 && !(*in_queue)[c]) {
13211 (*in_queue)[c] = true;
13212 queue->push_back(c);
13213 }
13214 }
13215
13216 // Make sure the order is deterministic! because var_to_constraints[]
13217 // order changes from one run to the next.
13218 std::sort(queue->begin() + size_before, queue->end());
13219 }
13220 context_->modified_domains.ResetAllToFalse();
13221 return !queue->empty();
13222}
13223
13224void CpModelPresolver::PresolveToFixPoint() {
13225 if (time_limit_->LimitReached()) return;
13226 if (context_->ModelIsUnsat()) return;
13227 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
13228
13229 // We do at most 2 tests per PresolveToFixPoint() call since this can be slow.
13230 int num_dominance_tests = 0;
13231 int num_dual_strengthening = 0;
13232
13233 // Limit on number of operations.
13234 const int64_t max_num_operations =
13235 context_->params().debug_max_num_presolve_operations() > 0
13236 ? context_->params().debug_max_num_presolve_operations()
13237 : std::numeric_limits<int64_t>::max();
13238
13239 // This is used for constraint having unique variables in them (i.e. not
13240 // appearing anywhere else) to not call the presolve more than once for this
13241 // reason.
13242 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
13243
13244 // The queue of "active" constraints, initialized to the non-empty ones.
13245 std::vector<bool> in_queue(context_->working_model->constraints_size(),
13246 false);
13247 std::deque<int> queue;
13248 for (int c = 0; c < in_queue.size(); ++c) {
13249 if (context_->working_model->constraints(c).constraint_case() !=
13251 in_queue[c] = true;
13252 queue.push_back(c);
13253 }
13254 }
13255
13256 // When thinking about how the presolve works, it seems like a good idea to
13257 // process the "simple" constraints first in order to be more efficient.
13258 // In September 2019, experiment on the flatzinc problems shows no changes in
13259 // the results. We should actually count the number of rules triggered.
13260 if (context_->params().permute_presolve_constraint_order()) {
13261 std::shuffle(queue.begin(), queue.end(), *context_->random());
13262 } else {
13263 std::sort(queue.begin(), queue.end(), [this](int a, int b) {
13264 const int score_a = context_->ConstraintToVars(a).size();
13265 const int score_b = context_->ConstraintToVars(b).size();
13266 return score_a < score_b || (score_a == score_b && a < b);
13267 });
13268 }
13269
13270 // We put a hard limit on the number of loop to prevent some corner case with
13271 // propagation loops. Note that the limit is quite high so it shouldn't really
13272 // be reached in most situation.
13273 int num_loops = 0;
13274 constexpr int kMaxNumLoops = 1000;
13275 for (; num_loops < kMaxNumLoops && !queue.empty(); ++num_loops) {
13276 if (time_limit_->LimitReached()) break;
13277 if (context_->ModelIsUnsat()) break;
13278 if (context_->num_presolve_operations > max_num_operations) break;
13279
13280 // Empty the queue of single constraint presolve.
13281 while (!queue.empty() && !context_->ModelIsUnsat()) {
13282 if (time_limit_->LimitReached()) break;
13283 if (context_->num_presolve_operations > max_num_operations) break;
13284 const int c = queue.front();
13285 in_queue[c] = false;
13286 queue.pop_front();
13287
13288 const int old_num_constraint =
13289 context_->working_model->constraints_size();
13290 const bool changed = PresolveOneConstraint(c);
13291 if (context_->ModelIsUnsat()) {
13292 SOLVER_LOG(
13293 logger_, "Unsat after presolving constraint #", c,
13294 " (warning, dump might be inconsistent): ",
13295 ProtobufShortDebugString(context_->working_model->constraints(c)));
13296 }
13297
13298 // Add to the queue any newly created constraints.
13299 const int new_num_constraints =
13300 context_->working_model->constraints_size();
13301 if (new_num_constraints > old_num_constraint) {
13302 context_->UpdateNewConstraintsVariableUsage();
13303 in_queue.resize(new_num_constraints, true);
13304 for (int c = old_num_constraint; c < new_num_constraints; ++c) {
13305 queue.push_back(c);
13306 }
13307 }
13308
13309 // TODO(user): Is seems safer to remove the changed Boolean and maybe
13310 // just compare the number of applied "rules" before/after.
13311 if (changed) {
13312 context_->UpdateConstraintVariableUsage(c);
13313 }
13314 }
13315
13316 if (context_->ModelIsUnsat()) return;
13317
13318 in_queue.resize(context_->working_model->constraints_size(), false);
13319 const auto& vector_that_can_grow_during_iter =
13320 context_->var_with_reduced_small_degree.PositionsSetAtLeastOnce();
13321 for (int i = 0; i < vector_that_can_grow_during_iter.size(); ++i) {
13322 const int v = vector_that_can_grow_during_iter[i];
13323 if (context_->VariableIsNotUsedAnymore(v)) continue;
13324
13325 // Remove the variable from the set to allow it to be pushed again.
13326 // This is necessary since a few affine logic needs to add the same
13327 // variable back to a second pass of processing.
13328 context_->var_with_reduced_small_degree.Clear(v);
13329
13330 // Make sure all affine relations are propagated.
13331 // This also remove the relation if the degree is now one.
13332 if (context_->ModelIsUnsat()) return;
13333 if (!PresolveAffineRelationIfAny(v)) return;
13334
13335 const int degree = context_->VarToConstraints(v).size();
13336 if (degree == 0) continue;
13337 if (degree == 2) LookAtVariableWithDegreeTwo(v);
13338 if (degree == 2 || degree == 3) {
13339 // Tricky: this function can add new constraint.
13340 ProcessVariableInTwoAtMostOrExactlyOne(v);
13341 in_queue.resize(context_->working_model->constraints_size(), false);
13342 continue;
13343 }
13344
13345 // Re-add to the queue constraints that have unique variables. Note that
13346 // to not enter an infinite loop, we call each (var, constraint) pair at
13347 // most once.
13348 if (degree != 1) continue;
13349 const int c = *context_->VarToConstraints(v).begin();
13350 if (c < 0) continue;
13351
13352 // Note that to avoid bad complexity in problem like a TSP with just one
13353 // big constraint. we mark all the singleton variables of a constraint
13354 // even if this constraint is already in the queue.
13355 if (var_constraint_pair_already_called.contains(
13356 std::pair<int, int>(v, c))) {
13357 continue;
13358 }
13359 var_constraint_pair_already_called.insert({v, c});
13360
13361 if (!in_queue[c]) {
13362 in_queue[c] = true;
13363 queue.push_back(c);
13364 }
13365 }
13366 context_->var_with_reduced_small_degree.ResetAllToFalse();
13367
13368 if (ProcessChangedVariables(&in_queue, &queue)) continue;
13369
13370 DCHECK(!context_->HasUnusedAffineVariable());
13371
13372 // Deal with integer variable only appearing in an encoding.
13373 if (!context_->CanonicalizeObjective()) return;
13374 for (int v = 0; v < context_->working_model->variables().size(); ++v) {
13375 ProcessVariableOnlyUsedInEncoding(v);
13376 }
13377 if (ProcessChangedVariables(&in_queue, &queue)) continue;
13378
13379 // Perform dual reasoning.
13380 //
13381 // TODO(user): We can support assumptions but we need to not cut them out
13382 // of the feasible region.
13383 if (context_->params().keep_all_feasible_solutions_in_presolve()) break;
13384 if (!context_->working_model->assumptions().empty()) break;
13385
13386 // Starts by the "faster" algo that exploit variables that can move freely
13387 // in one direction. Or variables that are just blocked by one constraint in
13388 // one direction.
13389 for (int i = 0; i < 10; ++i) {
13390 if (context_->ModelIsUnsat()) return;
13391 ++num_dual_strengthening;
13392 DualBoundStrengthening dual_bound_strengthening;
13393 ScanModelForDualBoundStrengthening(*context_, &dual_bound_strengthening);
13394
13395 // TODO(user): Make sure that if we fix one variable, we fix its full
13396 // symmetric orbit. There should be no reason that we don't do that
13397 // though.
13398 if (!dual_bound_strengthening.Strengthen(context_)) return;
13399 if (ProcessChangedVariables(&in_queue, &queue)) break;
13400
13401 // It is possible we deleted some constraint, but the queue is empty.
13402 // In this case we redo a pass of dual bound strenghtening as we might
13403 // perform more reduction.
13404 //
13405 // TODO(user): maybe we could reach fix point directly?
13406 if (dual_bound_strengthening.NumDeletedConstraints() == 0) break;
13407 }
13408 if (!queue.empty()) continue;
13409
13410 // Dominance reasoning will likely break symmetry.
13411 // TODO(user): We can apply the one that do not break any though, or the
13412 // operations that are safe.
13413 if (context_->params().keep_symmetry_in_presolve()) break;
13414
13415 // Detect & exploit dominance between variables.
13416 // TODO(user): This can be slow, remove from fix-pint loop?
13417 if (num_dominance_tests++ < 2) {
13418 if (context_->ModelIsUnsat()) return;
13419 PresolveTimer timer("DetectDominanceRelations", logger_, time_limit_);
13420 VarDomination var_dom;
13421 ScanModelForDominanceDetection(*context_, &var_dom);
13422 if (!ExploitDominanceRelations(var_dom, context_)) return;
13423 if (ProcessChangedVariables(&in_queue, &queue)) continue;
13424 }
13425 }
13426
13427 if (context_->ModelIsUnsat()) return;
13428
13429 // Second "pass" for transformation better done after all of the above and
13430 // that do not need a fix-point loop.
13431 //
13432 // TODO(user): Also add deductions achieved during probing!
13433 //
13434 // TODO(user): ideally we should "wake-up" any constraint that contains an
13435 // absent interval in the main propagation loop above. But we currently don't
13436 // maintain such list.
13437 if (!time_limit_->LimitReached()) {
13438 const int num_constraints = context_->working_model->constraints_size();
13439 TimeLimitCheckEveryNCalls bool_or_check_time_limit(100, time_limit_);
13440 for (int c = 0; c < num_constraints; ++c) {
13441 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13442 // We don't want to check the time limit at each "small" constraint as
13443 // there could be many.
13444 bool check_time_limit = false;
13445
13446 switch (ct->constraint_case()) {
13448 // Filter out absent intervals.
13449 if (PresolveNoOverlap(ct)) {
13450 context_->UpdateConstraintVariableUsage(c);
13451 }
13452 check_time_limit = true;
13453 break;
13455 // Filter out absent intervals.
13456 if (PresolveNoOverlap2D(c, ct)) {
13457 context_->UpdateConstraintVariableUsage(c);
13458 }
13459 check_time_limit = true;
13460 break;
13462 // Filter out absent intervals.
13463 if (PresolveCumulative(ct)) {
13464 context_->UpdateConstraintVariableUsage(c);
13465 }
13466 check_time_limit = true;
13467 break;
13469 // Try to infer domain reductions from clauses and the saved "implies
13470 // in domain" relations.
13471 for (const auto& pair :
13472 context_->deductions.ProcessClause(ct->bool_or().literals())) {
13473 bool modified = false;
13474 if (!context_->IntersectDomainWith(pair.first, pair.second,
13475 &modified)) {
13476 return;
13477 }
13478 if (modified) {
13479 context_->UpdateRuleStats("deductions: reduced variable domain");
13480 }
13481 }
13482 if (bool_or_check_time_limit.LimitReached()) check_time_limit = true;
13483 break;
13484 }
13485 default:
13486 break;
13487 }
13488 if (check_time_limit && time_limit_->LimitReached()) break;
13489 }
13490 }
13491
13492 timer.AddCounter("num_loops", num_loops);
13493 timer.AddCounter("num_dual_strengthening", num_dual_strengthening);
13494 context_->deductions.MarkProcessingAsDoneForNow();
13495}
13496
13497// TODO(user): Use better heuristic?
13498//
13499// TODO(user): This is similar to what Bounded variable addition (BVA) does.
13500// By adding a new variable, enforcement => literals becomes
13501// enforcement => x => literals, and we have one clause + #literals implication
13502// instead of #literals clauses. What BVA does in addition is to use the same
13503// x for other enforcement list if the rhs literals are shared.
13504void CpModelPresolver::MergeClauses() {
13505 if (context_->ModelIsUnsat()) return;
13506 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
13507
13508 // Constraint index that changed.
13509 std::vector<int> to_clean;
13510
13511 // Keep a map from negation of enforcement_literal => bool_and ct index.
13512 absl::flat_hash_map<uint64_t, int> bool_and_map;
13513
13514 // First loop over the constraint:
13515 // - Register already existing bool_and.
13516 // - score at_most_ones literals.
13517 // - Record bool_or.
13518 const int num_variables = context_->working_model->variables_size();
13519 std::vector<int> bool_or_indices;
13520 std::vector<int64_t> literal_score(2 * num_variables, 0);
13521 const auto get_index = [](int ref) {
13522 return 2 * PositiveRef(ref) + (RefIsPositive(ref) ? 0 : 1);
13523 };
13524
13525 int64_t num_collisions = 0;
13526 int64_t num_merges = 0;
13527 int64_t num_saved_literals = 0;
13528 ClauseWithOneMissingHasher hasher(*context_->random());
13529 const int num_constraints = context_->working_model->constraints_size();
13530 for (int c = 0; c < num_constraints; ++c) {
13531 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13532 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
13533 if (ct->enforcement_literal().size() > 1) {
13534 // We need to sort the negated literals.
13535 std::sort(ct->mutable_enforcement_literal()->begin(),
13536 ct->mutable_enforcement_literal()->end(),
13537 std::greater<int>());
13538 const auto [it, inserted] = bool_and_map.insert(
13539 {hasher.HashOfNegatedLiterals(ct->enforcement_literal()), c});
13540 if (inserted) {
13541 to_clean.push_back(c);
13542 } else {
13543 // See if this is a true duplicate. If yes, merge rhs.
13544 ConstraintProto* other_ct =
13545 context_->working_model->mutable_constraints(it->second);
13546 const absl::Span<const int> s1(ct->enforcement_literal());
13547 const absl::Span<const int> s2(other_ct->enforcement_literal());
13548 if (s1 == s2) {
13549 context_->UpdateRuleStats(
13550 "bool_and: merged constraints with same enforcement");
13551 other_ct->mutable_bool_and()->mutable_literals()->Add(
13552 ct->bool_and().literals().begin(),
13553 ct->bool_and().literals().end());
13554 ct->Clear();
13555 context_->UpdateConstraintVariableUsage(c);
13556 }
13557 }
13558 }
13559 continue;
13560 }
13561 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
13562 const int size = ct->at_most_one().literals().size();
13563 for (const int ref : ct->at_most_one().literals()) {
13564 literal_score[get_index(ref)] += size;
13565 }
13566 continue;
13567 }
13568 if (ct->constraint_case() == ConstraintProto::kExactlyOne) {
13569 const int size = ct->exactly_one().literals().size();
13570 for (const int ref : ct->exactly_one().literals()) {
13571 literal_score[get_index(ref)] += size;
13572 }
13573 continue;
13574 }
13575
13576 if (ct->constraint_case() != ConstraintProto::kBoolOr) continue;
13577
13578 // Both of these test shouldn't happen, but we have them to be safe.
13579 if (!ct->enforcement_literal().empty()) continue;
13580 if (ct->bool_or().literals().size() <= 2) continue;
13581
13582 std::sort(ct->mutable_bool_or()->mutable_literals()->begin(),
13583 ct->mutable_bool_or()->mutable_literals()->end());
13584 hasher.RegisterClause(c, ct->bool_or().literals());
13585 bool_or_indices.push_back(c);
13586 }
13587
13588 for (const int c : bool_or_indices) {
13589 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13590
13591 bool merged = false;
13592 timer.TrackSimpleLoop(ct->bool_or().literals().size());
13593 if (timer.WorkLimitIsReached()) break;
13594 for (const int ref : ct->bool_or().literals()) {
13595 const uint64_t hash = hasher.HashWithout(c, ref);
13596 const auto it = bool_and_map.find(hash);
13597 if (it != bool_and_map.end()) {
13598 ++num_collisions;
13599 const int base_c = it->second;
13600 auto* and_ct = context_->working_model->mutable_constraints(base_c);
13602 ct->bool_or().literals(), and_ct->enforcement_literal(), ref)) {
13603 ++num_merges;
13604 num_saved_literals += ct->bool_or().literals().size() - 1;
13605 merged = true;
13606 and_ct->mutable_bool_and()->add_literals(ref);
13607 ct->Clear();
13608 context_->UpdateConstraintVariableUsage(c);
13609 break;
13610 }
13611 }
13612 }
13613
13614 if (!merged) {
13615 // heuristic: take first literal whose negation has highest score.
13616 int best_ref = ct->bool_or().literals(0);
13617 int64_t best_score = literal_score[get_index(NegatedRef(best_ref))];
13618 for (const int ref : ct->bool_or().literals()) {
13619 const int64_t score = literal_score[get_index(NegatedRef(ref))];
13620 if (score > best_score) {
13621 best_ref = ref;
13622 best_score = score;
13623 }
13624 }
13625
13626 const uint64_t hash = hasher.HashWithout(c, best_ref);
13627 const auto [_, inserted] = bool_and_map.insert({hash, c});
13628 if (inserted) {
13629 to_clean.push_back(c);
13630 context_->tmp_literals.clear();
13631 for (const int lit : ct->bool_or().literals()) {
13632 if (lit == best_ref) continue;
13633 context_->tmp_literals.push_back(NegatedRef(lit));
13634 }
13635 ct->Clear();
13636 ct->mutable_enforcement_literal()->Assign(
13637 context_->tmp_literals.begin(), context_->tmp_literals.end());
13638 ct->mutable_bool_and()->add_literals(best_ref);
13639 }
13640 }
13641 }
13642
13643 // Retransform to bool_or bool_and with a single rhs.
13644 for (const int c : to_clean) {
13645 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13646 if (ct->bool_and().literals().size() > 1) {
13647 context_->UpdateConstraintVariableUsage(c);
13648 continue;
13649 }
13650
13651 // We have a single bool_and, lets transform it back to single bool_or.
13652 context_->tmp_literals.clear();
13653 context_->tmp_literals.push_back(ct->bool_and().literals(0));
13654 for (const int ref : ct->enforcement_literal()) {
13655 context_->tmp_literals.push_back(NegatedRef(ref));
13656 }
13657 ct->Clear();
13658 ct->mutable_bool_or()->mutable_literals()->Assign(
13659 context_->tmp_literals.begin(), context_->tmp_literals.end());
13660 }
13661
13662 timer.AddCounter("num_collisions", num_collisions);
13663 timer.AddCounter("num_merges", num_merges);
13664 timer.AddCounter("num_saved_literals", num_saved_literals);
13665}
13666
13667// =============================================================================
13668// Public API.
13669// =============================================================================
13670
13672 std::vector<int>* postsolve_mapping) {
13673 CpModelPresolver presolver(context, postsolve_mapping);
13674 return presolver.Presolve();
13675}
13676
13677CpModelPresolver::CpModelPresolver(PresolveContext* context,
13678 std::vector<int>* postsolve_mapping)
13679 : postsolve_mapping_(postsolve_mapping),
13680 context_(context),
13681 solution_crush_(context->solution_crush()),
13682 logger_(context->logger()),
13683 time_limit_(context->time_limit()),
13684 interval_representative_(context->working_model->constraints_size(),
13685 IntervalConstraintHash{context->working_model},
13686 IntervalConstraintEq{context->working_model}) {}
13687
13688CpSolverStatus CpModelPresolver::InfeasibleStatus() {
13689 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13691}
13692
13693// At the end of presolve, the mapping model is initialized to contains all
13694// the variable from the original model + the one created during presolve
13695// expand. It also contains the tightened domains.
13696namespace {
13697void InitializeMappingModelVariables(absl::Span<const Domain> domains,
13698 std::vector<int>* fixed_postsolve_mapping,
13699 CpModelProto* mapping_proto) {
13700 // Extend the fixed mapping to take into account all newly created variable
13701 // since the time it was constructed.
13702 int old_num_variables = mapping_proto->variables().size();
13703 while (fixed_postsolve_mapping->size() < domains.size()) {
13704 mapping_proto->add_variables();
13705 fixed_postsolve_mapping->push_back(old_num_variables++);
13706 DCHECK_EQ(old_num_variables, mapping_proto->variables().size());
13707 }
13708
13709 // Overwrite the domains.
13710 //
13711 // Note that if the fixed_postsolve_mapping was not null, the mapping model
13712 // should contains the original variable domains at the time the fixed mapping
13713 // was computed.
13714 for (int i = 0; i < domains.size(); ++i) {
13715 FillDomainInProto(domains[i], mapping_proto->mutable_variables(
13716 (*fixed_postsolve_mapping)[i]));
13717 }
13718
13719 // Remap the mapping proto.
13720 // We only deal with constraint here, do not touch the rest.
13721 //
13722 // TODO(user): Maybe we should have a real "postsolve" proto so we can
13723 // interleave postsolve "constraint" and remapping phase. This would allow to
13724 // do that in the middle of the presolve. But maybe this is not as impactful.
13725 auto mapping_function = [fixed_postsolve_mapping](int* ref) {
13726 const int image = (*fixed_postsolve_mapping)[PositiveRef(*ref)];
13727 CHECK_GE(image, 0);
13728 *ref = RefIsPositive(*ref) ? image : NegatedRef(image);
13729 };
13730 for (ConstraintProto& ct_ref : *mapping_proto->mutable_constraints()) {
13731 ApplyToAllVariableIndices(mapping_function, &ct_ref);
13732 ApplyToAllLiteralIndices(mapping_function, &ct_ref);
13733 }
13734}
13735} // namespace
13736
13737void CpModelPresolver::ExpandCpModelAndCanonicalizeConstraints() {
13738 const int num_constraints_before_expansion =
13739 context_->working_model->constraints_size();
13740 ExpandCpModel(context_);
13741 if (context_->ModelIsUnsat()) return;
13742
13743 // TODO(user): Make sure we can't have duplicate in these constraint.
13744 // These are due to ExpandCpModel() were we create such constraint with
13745 // duplicate. The problem is that some code assumes these are presolved
13746 // before being called.
13747 const int num_constraints = context_->working_model->constraints().size();
13748 for (int c = num_constraints_before_expansion; c < num_constraints; ++c) {
13749 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13750 const auto type = ct->constraint_case();
13751 if (type == ConstraintProto::kAtMostOne ||
13753 if (PresolveOneConstraint(c)) {
13754 context_->UpdateConstraintVariableUsage(c);
13755 }
13756 if (context_->ModelIsUnsat()) return;
13757 } else if (type == ConstraintProto::kLinear) {
13758 bool changed = false;
13759 if (!CanonicalizeLinear(ct, &changed)) {
13760 return;
13761 }
13762 if (changed) {
13763 context_->UpdateConstraintVariableUsage(c);
13764 }
13765 }
13766 }
13767}
13768
13769namespace {
13770
13771// Updates the solution hint in the proto with the crushed solution values.
13772void UpdateHintInProto(PresolveContext* context) {
13773 if (context->ModelIsUnsat()) return;
13774
13775 SolutionCrush& crush = context->solution_crush();
13776 if (!crush.SolutionIsLoaded()) return;
13777 const int num_vars = context->working_model->variables().size();
13778 for (int i = 0; i < num_vars; ++i) {
13779 // If the initial hint is incomplete or infeasible, the crushed hint might
13780 // contain values outside of their respective domains (see SolutionCrush).
13781 crush.SetOrUpdateVarToDomain(i, context->DomainOf(i));
13782 }
13783 // If the time limit is reached, the presolved model might still contain
13784 // non-representative "affine" variables.
13785 for (int i = 0; i < num_vars; ++i) {
13786 const auto relation = context->GetAffineRelation(i);
13787 if (relation.representative != i) {
13788 crush.SetVarToLinearExpression(
13789 i, {{relation.representative, relation.coeff}}, relation.offset);
13790 }
13791 }
13792 crush.StoreSolutionAsHint(*context->working_model);
13793}
13794
13795// Canonicalizes the routes constraints node expressions. In particular,
13796// replaces the variables in these expressions with their representative.
13797void CanonicalizeRoutesConstraintNodeExpressions(PresolveContext* context) {
13798 CpModelProto& proto = *context->working_model;
13799 for (ConstraintProto& ct_ref : *proto.mutable_constraints()) {
13800 if (ct_ref.constraint_case() != ConstraintProto::kRoutes) continue;
13802 *ct_ref.mutable_routes()->mutable_dimensions()) {
13803 for (LinearExpressionProto& expr : *node_exprs.mutable_exprs()) {
13804 context->CanonicalizeLinearExpression({}, &expr);
13805 }
13806 }
13807 }
13808}
13809
13810} // namespace
13811
13812// The presolve works as follow:
13813//
13814// First stage:
13815// We will process all active constraints until a fix point is reached. During
13816// this stage:
13817// - Variable will never be deleted, but their domain will be reduced.
13818// - Constraint will never be deleted (they will be marked as empty if needed).
13819// - New variables and new constraints can be added after the existing ones.
13820// - Constraints are added only when needed to the mapping_problem if they are
13821// needed during the postsolve.
13822//
13823// Second stage:
13824// - All the variables domain will be copied to the mapping_model.
13825// - Everything will be remapped so that only the variables appearing in some
13826// constraints will be kept and their index will be in [0, num_new_variables).
13828 context_->InitializeNewDomains();
13830 // If the objective is a floating point one, we scale it.
13831 //
13832 // TODO(user): We should probably try to delay this even more. For that we
13833 // just need to isolate more the "dual" reduction that usually need to look at
13834 // the objective.
13835 if (context_->working_model->has_floating_point_objective()) {
13836 context_->WriteVariableDomainsToProto();
13837 if (!ScaleFloatingPointObjective(context_->params(), logger_,
13838 context_->working_model)) {
13839 SOLVER_LOG(logger_,
13840 "The floating point objective cannot be scaled with enough "
13841 "precision");
13843 }
13844
13845 // At this point, we didn't create any new variables, so the integer
13846 // objective is in term of the orinal problem variables. We save it so that
13847 // we can expose to the user what exact objective we are actually
13848 // optimizing.
13849 *context_->mapping_model->mutable_objective() =
13850 context_->working_model->objective();
13851 }
13852
13853 // If there is a large proprotion of fixed variables, lets remap the model
13854 // before we start the actual presolve. This is useful for LNS in particular.
13855 //
13856 // fixed_postsolve_mapping[i] will contains the original index of the variable
13857 // that will be at position i after MaybeRemoveFixedVariables(). If the
13858 // mapping is left empty, it will be set to the identity mapping later by
13859 // InitializeMappingModelVariables().
13860 std::vector<int> fixed_postsolve_mapping;
13861 if (!MaybeRemoveFixedVariables(&fixed_postsolve_mapping)) {
13862 return InfeasibleStatus();
13863 }
13864
13865 // Initialize the initial context.working_model domains.
13866 // Initialize the objective and the constraint <-> variable graph.
13867 //
13868 // Note that we did some basic presolving during the first copy of the model.
13869 // This is important has initializing the constraint <-> variable graph can
13870 // be costly, so better to remove trivially feasible constraint for instance.
13871 context_->InitializeNewDomains();
13872 context_->LoadSolutionHint();
13873 context_->ReadObjectiveFromProto();
13874 if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
13875
13878 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13879
13880 // If presolve is false, just run expansion.
13881 if (!context_->params().cp_model_presolve()) {
13882 for (ConstraintProto& ct :
13883 *context_->working_model->mutable_constraints()) {
13884 if (ct.constraint_case() == ConstraintProto::kLinear) {
13885 context_->CanonicalizeLinearConstraint(&ct);
13886 }
13887 }
13888
13889 if (!solution_crush_.SolutionIsLoaded()) {
13890 context_->LoadSolutionHint();
13891 }
13892 ExpandCpModelAndCanonicalizeConstraints();
13893 UpdateHintInProto(context_);
13894 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13895
13896 // We still write back the canonical objective has we don't deal well
13897 // with uninitialized domain or duplicate variables.
13898 if (context_->working_model->has_objective()) {
13899 context_->WriteObjectiveToProto();
13900 }
13901
13902 // We need to append all the variable equivalence that are still used!
13903 EncodeAllAffineRelations();
13904
13905 // Make sure we also have an initialized mapping model as we use this for
13906 // filling the tightened variables. Even without presolve, we do some
13907 // trivial presolving during the initial copy of the model, and expansion
13908 // might do more.
13909 context_->WriteVariableDomainsToProto();
13910 InitializeMappingModelVariables(context_->AllDomains(),
13911 &fixed_postsolve_mapping,
13912 context_->mapping_model);
13913
13914 // We don't want to run postsolve when the presolve is disabled, but the
13915 // expansion might have added some constraints to the mapping model. To
13916 // restore correctness, we merge them with the working model.
13917 if (!context_->mapping_model->constraints().empty()) {
13918 context_->UpdateRuleStats(
13919 "TODO: mapping model not empty with presolve disabled");
13920 context_->working_model->mutable_constraints()->MergeFrom(
13921 context_->mapping_model->constraints());
13922 context_->mapping_model->clear_constraints();
13923 }
13924
13925 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13927 }
13928
13929 // Presolve all variable domain once. The PresolveToFixPoint() function will
13930 // only reprocess domain that changed.
13931 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13932 for (int var = 0; var < context_->working_model->variables().size(); ++var) {
13933 if (context_->VariableIsNotUsedAnymore(var)) continue;
13934 if (!PresolveAffineRelationIfAny(var)) return InfeasibleStatus();
13935
13936 // Try to canonicalize the domain, note that we should have detected all
13937 // affine relations before, so we don't recreate "canononical" variables
13938 // if they already exist in the model.
13939 TryToSimplifyDomain(var);
13940 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13941 context_->UpdateNewConstraintsVariableUsage();
13942 }
13943 if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
13944
13945 // Main propagation loop.
13946 for (int iter = 0; iter < context_->params().max_presolve_iterations();
13947 ++iter) {
13948 if (time_limit_->LimitReached()) break;
13949 context_->UpdateRuleStats("presolve: iteration");
13950 const int64_t old_num_presolve_op = context_->num_presolve_operations;
13951
13952 // Propagate the objective.
13953 if (!PropagateObjective()) return InfeasibleStatus();
13954
13955 // TODO(user): The presolve transformations we do after this is called might
13956 // result in even more presolve if we were to call this again! improve the
13957 // code. See for instance plusexample_6_sat.fzn were represolving the
13958 // presolved problem reduces it even more.
13959 PresolveToFixPoint();
13960
13961 // Call expansion.
13962 if (!context_->ModelIsExpanded()) {
13963 ExtractEncodingFromLinear();
13964 ExpandCpModelAndCanonicalizeConstraints();
13965 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13966
13967 // We need to re-evaluate the degree because some presolve rule only
13968 // run after expansion.
13969 const int num_vars = context_->working_model->variables().size();
13970 for (int var = 0; var < num_vars; ++var) {
13971 if (context_->VarToConstraints(var).size() <= 3) {
13972 context_->var_with_reduced_small_degree.Set(var);
13973 }
13974 }
13975 }
13976 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13977
13978 // We run the symmetry before more complex presolve rules as many of them
13979 // are heuristic based and might break the symmetry present in the original
13980 // problems. This happens for example on the flatzinc wordpress problem.
13981 //
13982 // TODO(user): Decide where is the best place for this.
13983 //
13984 // TODO(user): try not to break symmetry in our clique extension or other
13985 // more advanced presolve rule? Ideally we could even exploit them. But in
13986 // this case, it is still good to compute them early.
13987 if (context_->params().symmetry_level() > 0 && !context_->ModelIsUnsat() &&
13988 !time_limit_->LimitReached()) {
13989 // Both kind of duplications might introduce a lot of symmetries and we
13990 // want to do that before we even compute them.
13992 DetectDuplicateConstraints();
13993 if (context_->params().keep_symmetry_in_presolve()) {
13994 // If the presolve always keep symmetry, we compute it once and for all.
13995 if (!context_->working_model->has_symmetry()) {
13996 DetectAndAddSymmetryToProto(context_->params(),
13997 context_->working_model, logger_,
13998 context_->time_limit());
13999 }
14000
14001 // We distinguish an empty symmetry message meaning that symmetry were
14002 // computed and there is none, and the absence of symmetry message
14003 // meaning we don't know.
14004 //
14005 // TODO(user): Maybe this is a bit brittle. Also move this logic to
14006 // DetectAndAddSymmetryToProto() ?
14007 if (!context_->working_model->has_symmetry()) {
14008 context_->working_model->mutable_symmetry()->Clear();
14009 }
14010 } else if (!context_->params()
14011 .keep_all_feasible_solutions_in_presolve()) {
14013 }
14014 }
14015
14016 // Runs SAT specific presolve on the pure-SAT part of the problem.
14017 // Note that because this can only remove/fix variable not used in the other
14018 // part of the problem, there is no need to redo more presolve afterwards.
14019 if (context_->params().cp_model_use_sat_presolve()) {
14020 if (!time_limit_->LimitReached()) {
14021 if (!PresolvePureSatPart()) {
14022 (void)context_->NotifyThatModelIsUnsat(
14023 "Proven Infeasible during SAT presolve");
14024 return InfeasibleStatus();
14025 }
14026 }
14027 }
14028
14029 // Extract redundant at most one constraint from the linear ones.
14030 //
14031 // TODO(user): more generally if we do some probing, the same relation will
14032 // be detected (and more). Also add an option to turn this off?
14033 //
14034 // TODO(user): instead of extracting at most one, extract pairwise conflicts
14035 // and add them to bool_and clauses? this is some sort of small scale
14036 // probing, but good for sat presolve and clique later?
14037 if (!context_->ModelIsUnsat() && iter == 0) {
14038 const int old_size = context_->working_model->constraints_size();
14039 for (int c = 0; c < old_size; ++c) {
14040 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
14041 if (ct->constraint_case() != ConstraintProto::kLinear) continue;
14042 ExtractAtMostOneFromLinear(ct);
14043 }
14044 context_->UpdateNewConstraintsVariableUsage();
14045 }
14046
14047 if (context_->params().cp_model_probing_level() > 0) {
14048 if (!time_limit_->LimitReached()) {
14049 Probe();
14050 PresolveToFixPoint();
14051 }
14052 } else {
14053 TransformIntoMaxCliques();
14054 }
14055
14056 // Deal with pair of constraints.
14057 //
14058 // TODO(user): revisit when different transformation appear.
14059 // TODO(user): merge these code instead of doing many passes?
14060 ProcessAtMostOneAndLinear();
14061 DetectDuplicateConstraints();
14062 DetectDuplicateConstraintsWithDifferentEnforcements();
14063 DetectDominatedLinearConstraints();
14065 ProcessSetPPC();
14066 TransformClausesToExactlyOne();
14067
14068 if (!time_limit_->LimitReached()) {
14069 DetectEncodedComplexDomains(context_);
14070 }
14071
14072 // These operations might break symmetry. Or at the very least, the newly
14073 // created variable must be incorporated in the generators.
14074 if (context_->params().find_big_linear_overlap() &&
14075 !context_->params().keep_symmetry_in_presolve()) {
14076 FindAlmostIdenticalLinearConstraints();
14077
14078 ActivityBoundHelper activity_amo_helper;
14079 activity_amo_helper.AddAllAtMostOnes(*context_->working_model);
14080 FindBigAtMostOneAndLinearOverlap(&activity_amo_helper);
14081
14082 // Heuristic: vertical introduce smaller defining constraint and appear in
14083 // many constraints, so might be more constrained. We might also still
14084 // make horizontal rectangle with the variable introduced.
14085 FindBigVerticalLinearOverlap(&activity_amo_helper);
14086 FindBigHorizontalLinearOverlap(&activity_amo_helper);
14087 }
14088 if (context_->ModelIsUnsat()) return InfeasibleStatus();
14089
14090 // We do that after the duplicate, SAT and SetPPC constraints.
14091 if (!time_limit_->LimitReached()) {
14092 // Merge clauses that differ in just one literal.
14093 // Heuristic use at_most_one to try to tighten the initial LP Relaxation.
14094 MergeClauses();
14095 if (/*DISABLES CODE*/ (false)) DetectIncludedEnforcement();
14096 }
14097
14098 // The TransformIntoMaxCliques() call above transform all bool and into
14099 // at most one of size 2. This does the reverse and merge them.
14100 ConvertToBoolAnd();
14101
14102 // Call the main presolve to remove the fixed variables and do more
14103 // deductions.
14104 PresolveToFixPoint();
14105
14106 // Exit the loop if no operations were performed.
14107 //
14108 // TODO(user): try to be smarter and avoid looping again if little changed.
14109 const int64_t num_ops =
14110 context_->num_presolve_operations - old_num_presolve_op;
14111 if (num_ops == 0) break;
14112 }
14113 if (context_->ModelIsUnsat()) return InfeasibleStatus();
14114
14115 if (!MergeNoOverlapConstraints()) return InfeasibleStatus();
14116 if (!MergeNoOverlap2DConstraints()) return InfeasibleStatus();
14117
14118 // Tries to spread the objective amongst many variables.
14119 // We re-do a canonicalization with the final linear expression.
14120 if (context_->working_model->has_objective()) {
14121 if (!context_->params().keep_symmetry_in_presolve()) {
14122 ExpandObjective();
14123 if (!context_->modified_domains.PositionsSetAtLeastOnce().empty()) {
14124 // If we have fixed variables or created new affine relations, there
14125 // might be more things to presolve.
14126 PresolveToFixPoint();
14127 }
14128 if (context_->ModelIsUnsat()) return InfeasibleStatus();
14129 ShiftObjectiveWithExactlyOnes();
14130 if (context_->ModelIsUnsat()) return InfeasibleStatus();
14131 }
14132 }
14133
14134 // Now that everything that could possibly be fixed was fixed, make sure we
14135 // don't leave any linear constraint with fixed variables.
14136 for (int c = 0; c < context_->working_model->constraints_size(); ++c) {
14137 ConstraintProto& ct = *context_->working_model->mutable_constraints(c);
14138 bool need_canonicalize = false;
14139 if (ct.constraint_case() == ConstraintProto::kLinear) {
14140 for (const int v : ct.linear().vars()) {
14141 if (context_->IsFixed(v)) {
14142 need_canonicalize = true;
14143 break;
14144 }
14145 }
14146 }
14147 if (need_canonicalize) {
14148 bool changed = false;
14149 if (!CanonicalizeLinear(&ct, &changed)) {
14150 return InfeasibleStatus();
14151 }
14152 if (changed) {
14153 context_->UpdateConstraintVariableUsage(c);
14154 }
14155 }
14156 }
14157
14158 // Take care of linear constraint with a complex rhs.
14160
14161 // Adds all needed affine relation to context_->working_model.
14162 EncodeAllAffineRelations();
14163 if (context_->ModelIsUnsat()) return InfeasibleStatus();
14164
14165 // If we have symmetry information, lets filter it.
14166 if (context_->working_model->has_symmetry()) {
14168 context_->working_model->mutable_symmetry(), context_)) {
14169 return InfeasibleStatus();
14170 }
14171 }
14172
14173 // The strategy variable indices will be remapped in ApplyVariableMapping()
14174 // but first we use the representative of the affine relations for the
14175 // variables that are not present anymore.
14176 //
14177 // Note that we properly take into account the sign of the coefficient which
14178 // will result in the same domain reduction strategy. Moreover, if the
14179 // variable order is not CHOOSE_FIRST, then we also encode the associated
14180 // affine transformation in order to preserve the order.
14181 absl::flat_hash_set<int> used_variables;
14182 for (DecisionStrategyProto& strategy :
14183 *context_->working_model->mutable_search_strategy()) {
14184 CHECK(strategy.variables().empty());
14185 if (strategy.exprs().empty()) continue;
14186
14187 // Canonicalize each expression to use affine representative.
14188 ConstraintProto empy_enforcement;
14189 for (LinearExpressionProto& expr : *strategy.mutable_exprs()) {
14190 CanonicalizeLinearExpression(empy_enforcement, &expr);
14191 }
14192
14193 // Remove fixed expression and affine corresponding to same variables.
14194 int new_size = 0;
14195 for (const LinearExpressionProto& expr : strategy.exprs()) {
14196 if (context_->IsFixed(expr)) continue;
14197
14198 const auto [_, inserted] = used_variables.insert(expr.vars(0));
14199 if (!inserted) continue;
14200
14201 *strategy.mutable_exprs(new_size++) = expr;
14202 }
14203 google::protobuf::util::Truncate(strategy.mutable_exprs(), new_size);
14204 }
14205
14206 // Sync the domains and initialize the mapping model variables.
14207 context_->WriteVariableDomainsToProto();
14208
14209 // Some vars may have been fixed by the affine relations. This may can impact
14210 // the objective. Let's re-do the canonicalization.
14211 if (context_->working_model->has_objective()) {
14212 // We re-do a canonicalization with the final linear expression.
14213 if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
14214 context_->WriteObjectiveToProto();
14215 }
14216
14217 // Starts the postsolve mapping model.
14218 InitializeMappingModelVariables(context_->AllDomains(),
14219 &fixed_postsolve_mapping,
14220 context_->mapping_model);
14221
14222 // Remove all the unused variables from the presolved model.
14223 postsolve_mapping_->clear();
14224 std::vector<int> mapping(context_->working_model->variables_size(), -1);
14225 absl::flat_hash_map<int64_t, int> constant_to_index;
14226 int num_unused_variables = 0;
14227 for (int i = 0; i < context_->working_model->variables_size(); ++i) {
14228 if (mapping[i] != -1) continue; // Already mapped.
14229
14230 if (context_->VariableWasRemoved(i)) {
14231 // Heuristic: If a variable is removed and has a representative that is
14232 // not, we "move" the representative to the spot of that variable in the
14233 // original order. This is to preserve any info encoded in the variable
14234 // order by the modeler.
14235 const int r = PositiveRef(context_->GetAffineRelation(i).representative);
14236 if (mapping[r] == -1 && !context_->VariableIsNotUsedAnymore(r)) {
14237 mapping[r] = postsolve_mapping_->size();
14238 postsolve_mapping_->push_back(fixed_postsolve_mapping[r]);
14239 }
14240 continue;
14241 }
14242
14243 // Deal with unused variables.
14244 //
14245 // If the variable is not fixed, we have multiple feasible solution for
14246 // this variable, so we can't remove it if we want all of them.
14247 if (context_->VariableIsNotUsedAnymore(i) &&
14248 (!context_->params().keep_all_feasible_solutions_in_presolve() ||
14249 context_->IsFixed(i))) {
14250 // Tricky. Variables that were not removed by a presolve rule should be
14251 // fixed first during postsolve, so that more complex postsolve rules
14252 // can use their values. One way to do that is to fix them here.
14253 //
14254 // We prefer to fix them to zero if possible.
14255 ++num_unused_variables;
14256 FillDomainInProto(Domain(context_->DomainOf(i).SmallestValue()),
14257 context_->mapping_model->mutable_variables(
14258 fixed_postsolve_mapping[i]));
14259 continue;
14260 }
14261
14262 // Merge identical constant. Note that the only place were constant are
14263 // still left are in the circuit and route constraint for fixed arcs.
14264 if (context_->IsFixed(i)) {
14265 auto [it, inserted] = constant_to_index.insert(
14266 {context_->FixedValue(i), postsolve_mapping_->size()});
14267 if (!inserted) {
14268 mapping[i] = it->second;
14269 continue;
14270 }
14271 }
14272
14273 mapping[i] = postsolve_mapping_->size();
14274 postsolve_mapping_->push_back(fixed_postsolve_mapping[i]);
14275 }
14276 context_->UpdateRuleStats(absl::StrCat("presolve: ", num_unused_variables,
14277 " unused variables removed."));
14278
14279 if (context_->params().permute_variable_randomly()) {
14280 // The mapping might merge variable, so we have to be careful here.
14281 const int n = postsolve_mapping_->size();
14282 std::vector<int> perm(n);
14283 std::iota(perm.begin(), perm.end(), 0);
14284 std::shuffle(perm.begin(), perm.end(), *context_->random());
14285 for (int i = 0; i < context_->working_model->variables_size(); ++i) {
14286 if (mapping[i] != -1) mapping[i] = perm[mapping[i]];
14287 }
14288 std::vector<int> new_postsolve_mapping(n);
14289 for (int i = 0; i < n; ++i) {
14290 new_postsolve_mapping[perm[i]] = (*postsolve_mapping_)[i];
14291 }
14292 *postsolve_mapping_ = std::move(new_postsolve_mapping);
14293 }
14294
14295 DCHECK(context_->ConstraintVariableUsageIsConsistent());
14296 CanonicalizeRoutesConstraintNodeExpressions(context_);
14297 UpdateHintInProto(context_);
14298 const int old_size = postsolve_mapping_->size();
14299 ApplyVariableMapping(absl::MakeSpan(mapping), context_->working_model,
14300 postsolve_mapping_);
14301 CHECK_EQ(old_size, postsolve_mapping_->size());
14302
14303 // Compact all non-empty constraint at the beginning.
14305
14306 // Hack to display the number of deductions stored.
14307 if (context_->deductions.NumDeductions() > 0) {
14308 context_->UpdateRuleStats(absl::StrCat(
14309 "deductions: ", context_->deductions.NumDeductions(), " stored"));
14310 }
14311
14312 // Stats and checks.
14313 if (logger_->LoggingIsEnabled()) context_->LogInfo();
14314
14315 // This is not supposed to happen, and is more indicative of an error than an
14316 // INVALID model. But for our no-overflow preconditions, we might run into bad
14317 // situation that causes the final model to be invalid.
14318 {
14319 const std::string error =
14320 ValidateCpModel(*context_->working_model, /*after_presolve=*/true);
14321 if (!error.empty()) {
14322 SOLVER_LOG(logger_, "Error while validating postsolved model: ", error);
14324 }
14325 }
14326 {
14327 const std::string error = ValidateCpModel(*context_->mapping_model);
14328 if (!error.empty()) {
14329 SOLVER_LOG(logger_,
14330 "Error while validating mapping_model model: ", error);
14332 }
14333 }
14334
14336}
14337
14338void ApplyVariableMapping(absl::Span<int> mapping, CpModelProto* cp_model,
14339 std::vector<int>* reverse_mapping) {
14340 // Remap all the variable/literal references in the constraints and the
14341 // enforcement literals in the variables.
14342 const auto mapping_function = [&mapping, &reverse_mapping](int* ref) {
14343 const int var = PositiveRef(*ref);
14344 int image = mapping[var];
14345 if (image < 0) {
14346 // We extend the mapping if this variable is still used.
14347 image = mapping[var] = reverse_mapping->size();
14348 reverse_mapping->push_back(var);
14349 }
14350 *ref = RefIsPositive(*ref) ? image : NegatedRef(image);
14351 };
14352 for (ConstraintProto& ct_ref : *cp_model->mutable_constraints()) {
14353 ApplyToAllVariableIndices(mapping_function, &ct_ref);
14354 ApplyToAllLiteralIndices(mapping_function, &ct_ref);
14355 if (ct_ref.constraint_case() == ConstraintProto::kRoutes) {
14357 *ct_ref.mutable_routes()->mutable_dimensions()) {
14358 for (LinearExpressionProto& expr : *node_exprs.mutable_exprs()) {
14359 if (expr.vars().empty()) continue;
14360 CHECK_EQ(expr.vars().size(), 1);
14361 CHECK(RefIsPositive(expr.vars(0)));
14362 const int var = expr.vars(0);
14363 const auto& definition = cp_model->variables(var);
14364 const int64_t min = definition.domain(0);
14365 const int64_t max = definition.domain(definition.domain().size() - 1);
14366 if (min == max) {
14367 expr.set_offset(expr.offset() + min * expr.coeffs(0));
14368 expr.clear_vars();
14369 expr.clear_coeffs();
14370 continue;
14371 }
14372 const int image = mapping[var];
14373 if (image < 0) {
14374 // TODO(user): is this correct? may this lead to incorrect cuts
14375 // in routing_cuts.cc in some cases?
14376 expr.clear_vars();
14377 expr.clear_coeffs();
14378 continue;
14379 }
14380 expr.set_vars(0, image);
14381 }
14382 }
14383 }
14384 }
14385
14386 // Remap the objective variables.
14387 if (cp_model->has_objective()) {
14388 for (int& mutable_ref : *cp_model->mutable_objective()->mutable_vars()) {
14389 mapping_function(&mutable_ref);
14390 }
14391 }
14392
14393 // Remap the assumptions.
14394 for (int& mutable_ref : *cp_model->mutable_assumptions()) {
14395 mapping_function(&mutable_ref);
14396 }
14397
14398 // Remap the symmetries. Note that we should have properly dealt with fixed
14399 // orbit and such in FilterOrbitOnUnusedOrFixedVariables().
14400 if (cp_model->has_symmetry()) {
14401 for (SparsePermutationProto& generator :
14402 *cp_model->mutable_symmetry()->mutable_permutations()) {
14403 for (int& var : *generator.mutable_support()) {
14404 mapping_function(&var);
14405 }
14406 }
14407
14408 // We clear the orbitope info (we don't really use it after presolve).
14409 cp_model->mutable_symmetry()->clear_orbitopes();
14410 }
14411
14412 // Note: For the rest of the mapping, if mapping[i] is -1, we can just ignore
14413 // the variable instead of trying to map it.
14414
14415 // Remap the search decision heuristic.
14416 // Note that we delete any heuristic related to a removed variable.
14417 for (DecisionStrategyProto& strategy : *cp_model->mutable_search_strategy()) {
14418 int new_size = 0;
14419 for (LinearExpressionProto expr : strategy.exprs()) {
14420 DCHECK_EQ(expr.vars().size(), 1);
14421 const int image = mapping[expr.vars(0)];
14422 if (image >= 0) {
14423 expr.set_vars(0, image);
14424 *strategy.mutable_exprs(new_size++) = expr;
14425 }
14426 }
14427 google::protobuf::util::Truncate(strategy.mutable_exprs(), new_size);
14428 }
14429
14430 // Remove strategy with empty affine expression.
14431 {
14432 int new_size = 0;
14433 for (const DecisionStrategyProto& strategy : cp_model->search_strategy()) {
14434 if (strategy.exprs().empty()) continue;
14435 *cp_model->mutable_search_strategy(new_size++) = strategy;
14436 }
14437 google::protobuf::util::Truncate(cp_model->mutable_search_strategy(),
14438 new_size);
14439 }
14440
14441 // Remap the solution hint.
14442 if (cp_model->has_solution_hint()) {
14443 auto* mutable_hint = cp_model->mutable_solution_hint();
14444
14445 // Note that after remapping, we may have duplicate variables. For instance,
14446 // identical constant variables are mapped to a single one. So we make sure
14447 // we don't output duplicates here and just keep the first occurrence.
14448 absl::flat_hash_set<int> hinted_images;
14449
14450 int new_size = 0;
14451 const int old_size = mutable_hint->vars().size();
14452 for (int i = 0; i < old_size; ++i) {
14453 const int hinted_var = mutable_hint->vars(i);
14454 const int64_t hinted_value = mutable_hint->values(i);
14455 const int image = mapping[hinted_var];
14456 if (image >= 0) {
14457 if (!hinted_images.insert(image).second) continue;
14458 mutable_hint->set_vars(new_size, image);
14459 mutable_hint->set_values(new_size, hinted_value);
14460 ++new_size;
14461 }
14462 }
14463 mutable_hint->mutable_vars()->Truncate(new_size);
14464 mutable_hint->mutable_values()->Truncate(new_size);
14465 }
14466
14467 // Move the variable definitions.
14468 google::protobuf::RepeatedPtrField<IntegerVariableProto>
14469 new_variables_storage;
14470 google::protobuf::RepeatedPtrField<IntegerVariableProto>* new_variables;
14471 if (cp_model->GetArena() == nullptr) {
14472 new_variables = &new_variables_storage;
14473 } else {
14474 new_variables = google::protobuf::Arena::Create<
14475 google::protobuf::RepeatedPtrField<IntegerVariableProto>>(
14476 cp_model->GetArena());
14477 }
14478 for (int i = 0; i < mapping.size(); ++i) {
14479 const int image = mapping[i];
14480 if (image < 0) continue;
14481 while (image >= new_variables->size()) {
14482 new_variables->Add();
14483 }
14484 (*new_variables)[image].Swap(cp_model->mutable_variables(i));
14485 }
14486 cp_model->mutable_variables()->Swap(new_variables);
14487
14488 // Check that all variables have a non-empty domain.
14489 for (const IntegerVariableProto& v : cp_model->variables()) {
14490 CHECK_GT(v.domain_size(), 0);
14491 }
14492}
14493
14494bool CpModelPresolver::MaybeRemoveFixedVariables(
14495 std::vector<int>* postsolve_mapping) {
14496 postsolve_mapping->clear();
14497 if (!context_->params().remove_fixed_variables_early()) return true;
14498 if (!context_->params().cp_model_presolve()) return true;
14499
14500 // This is supposed to be already called, but it is a no-opt if this was the
14501 // case, and it comment nicely that we do require domains to be up to date
14502 // in the context.
14503 context_->InitializeNewDomains();
14504 if (context_->ModelIsUnsat()) return false;
14505
14506 // Initialize the mapping to remove all fixed variables.
14507 const int num_vars = context_->working_model->variables().size();
14508 std::vector<int> mapping(num_vars, -1);
14509 for (int i = 0; i < num_vars; ++i) {
14510 if (context_->IsFixed(i)) continue;
14511 mapping[i] = postsolve_mapping->size();
14512 postsolve_mapping->push_back(i);
14513 }
14514
14515 // Lets only do this if the proportion of fixed variables is large enough.
14516 const int num_fixed = num_vars - postsolve_mapping->size();
14517 if (num_fixed < 1000 || num_fixed * 2 <= num_vars) {
14518 postsolve_mapping->clear();
14519 return true;
14520 }
14521
14522 // TODO(user): Right now the copy does not remove fixed variables from the
14523 // objective, but ReadObjectiveFromProto() does it. Maybe we should just not
14524 // copy them in the first place.
14525 if (context_->working_model->has_objective()) {
14526 context_->ReadObjectiveFromProto();
14527 if (!context_->CanonicalizeObjective()) return false;
14528 if (!PropagateObjective()) return false;
14529 if (context_->ModelIsUnsat()) return false;
14530 context_->WriteObjectiveToProto();
14531 }
14532
14533 // Copy the current domains into the mapping model.
14534 // Note that we are not sure the domain where properly written.
14535 context_->WriteVariableDomainsToProto();
14536 *context_->mapping_model->mutable_variables() =
14537 context_->working_model->variables();
14538
14539 SOLVER_LOG(logger_, "Large number of fixed variables ",
14540 FormatCounter(num_fixed), " / ", FormatCounter(num_vars),
14541 ", doing a first remapping phase to go down to ",
14542 FormatCounter(postsolve_mapping->size()), " variables.");
14543
14544 // Perform the actual mapping.
14545 // Note that this might re-add fixed variable that are still used.
14546 const int old_size = postsolve_mapping->size();
14547 ApplyVariableMapping(absl::MakeSpan(mapping), context_->working_model,
14548 postsolve_mapping);
14549 if (postsolve_mapping->size() > old_size) {
14550 const int new_extra = postsolve_mapping->size() - old_size;
14551 SOLVER_LOG(logger_, "TODO: ", new_extra,
14552 " fixed variables still required in the model!");
14553 }
14554
14555 // Reset some part of the context, the caller re-reads the new domains.
14556 context_->ResetAfterCopy();
14557 return true;
14558}
14559
14560namespace {
14561
14562// We ignore all the fields but the linear expression.
14563ConstraintProto CopyObjectiveForDuplicateDetection(
14564 const CpObjectiveProto& objective) {
14565 ConstraintProto copy;
14566 *copy.mutable_linear()->mutable_vars() = objective.vars();
14567 *copy.mutable_linear()->mutable_coeffs() = objective.coeffs();
14568 return copy;
14569}
14570
14571struct ConstraintHashForDuplicateDetection {
14572 const CpModelProto* working_model;
14573 bool ignore_enforcement;
14574 ConstraintProto objective_constraint;
14575
14576 ConstraintHashForDuplicateDetection(const CpModelProto* working_model,
14577 bool ignore_enforcement)
14578 : working_model(working_model),
14579 ignore_enforcement(ignore_enforcement),
14580 objective_constraint(
14581 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
14582
14583 // We hash our mostly frequently used constraint directly without extra memory
14584 // allocation. We revert to a generic code using proto serialization for the
14585 // others.
14586 std::size_t operator()(int ct_idx) const {
14587 const ConstraintProto& ct = ct_idx == kObjectiveConstraint
14588 ? objective_constraint
14589 : working_model->constraints(ct_idx);
14590 const std::pair<ConstraintProto::ConstraintCase, absl::Span<const int>>
14591 type_and_enforcement = {ct.constraint_case(),
14592 ignore_enforcement
14593 ? absl::Span<const int>()
14594 : absl::MakeSpan(ct.enforcement_literal())};
14595 switch (ct.constraint_case()) {
14596 case ConstraintProto::kLinear:
14597 if (ignore_enforcement) {
14598 return absl::HashOf(type_and_enforcement,
14599 absl::MakeSpan(ct.linear().vars()),
14600 absl::MakeSpan(ct.linear().coeffs()),
14601 absl::MakeSpan(ct.linear().domain()));
14602 } else {
14603 // We ignore domain for linear constraint, because if the rest of the
14604 // constraint is the same we can just intersect them.
14605 return absl::HashOf(type_and_enforcement,
14606 absl::MakeSpan(ct.linear().vars()),
14607 absl::MakeSpan(ct.linear().coeffs()));
14608 }
14610 return absl::HashOf(type_and_enforcement,
14611 absl::MakeSpan(ct.bool_and().literals()));
14613 return absl::HashOf(type_and_enforcement,
14614 absl::MakeSpan(ct.bool_or().literals()));
14616 return absl::HashOf(type_and_enforcement,
14617 absl::MakeSpan(ct.at_most_one().literals()));
14619 return absl::HashOf(type_and_enforcement,
14620 absl::MakeSpan(ct.exactly_one().literals()));
14621 default:
14622 ConstraintProto copy = ct;
14623 copy.clear_name();
14624 if (ignore_enforcement) {
14625 copy.mutable_enforcement_literal()->Clear();
14626 }
14627 return absl::HashOf(copy.SerializeAsString());
14628 }
14629 }
14630};
14631
14632struct ConstraintEqForDuplicateDetection {
14633 const CpModelProto* working_model;
14634 bool ignore_enforcement;
14635 ConstraintProto objective_constraint;
14636
14637 ConstraintEqForDuplicateDetection(const CpModelProto* working_model,
14638 bool ignore_enforcement)
14639 : working_model(working_model),
14640 ignore_enforcement(ignore_enforcement),
14641 objective_constraint(
14642 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
14643
14644 bool operator()(int a, int b) const {
14645 if (a == b) {
14646 return true;
14647 }
14648 const ConstraintProto& ct_a = a == kObjectiveConstraint
14649 ? objective_constraint
14650 : working_model->constraints(a);
14651 const ConstraintProto& ct_b = b == kObjectiveConstraint
14652 ? objective_constraint
14653 : working_model->constraints(b);
14654
14655 if (ct_a.constraint_case() != ct_b.constraint_case()) return false;
14656 if (!ignore_enforcement) {
14657 if (absl::MakeSpan(ct_a.enforcement_literal()) !=
14658 absl::MakeSpan(ct_b.enforcement_literal())) {
14659 return false;
14660 }
14661 }
14662 switch (ct_a.constraint_case()) {
14664 // As above, we ignore domain for linear constraint, because if the rest
14665 // of the constraint is the same we can just intersect them.
14666 if (ignore_enforcement && absl::MakeSpan(ct_a.linear().domain()) !=
14667 absl::MakeSpan(ct_b.linear().domain())) {
14668 return false;
14669 }
14670 return absl::MakeSpan(ct_a.linear().vars()) ==
14671 absl::MakeSpan(ct_b.linear().vars()) &&
14672 absl::MakeSpan(ct_a.linear().coeffs()) ==
14673 absl::MakeSpan(ct_b.linear().coeffs());
14675 return absl::MakeSpan(ct_a.bool_and().literals()) ==
14676 absl::MakeSpan(ct_b.bool_and().literals());
14678 return absl::MakeSpan(ct_a.bool_or().literals()) ==
14679 absl::MakeSpan(ct_b.bool_or().literals());
14681 return absl::MakeSpan(ct_a.at_most_one().literals()) ==
14682 absl::MakeSpan(ct_b.at_most_one().literals());
14684 return absl::MakeSpan(ct_a.exactly_one().literals()) ==
14685 absl::MakeSpan(ct_b.exactly_one().literals());
14686 default:
14687 // Slow (hopefully comparably rare) path.
14688 ConstraintProto copy_a = ct_a;
14689 ConstraintProto copy_b = ct_b;
14690 copy_a.clear_name();
14691 copy_b.clear_name();
14692 if (ignore_enforcement) {
14693 copy_a.mutable_enforcement_literal()->Clear();
14694 copy_b.mutable_enforcement_literal()->Clear();
14695 }
14696 return copy_a.SerializeAsString() == copy_b.SerializeAsString();
14697 }
14698 }
14699};
14700
14701} // namespace
14702
14703std::vector<std::pair<int, int>> FindDuplicateConstraints(
14704 const CpModelProto& model_proto, bool ignore_enforcement) {
14705 std::vector<std::pair<int, int>> result;
14706
14707 // We use a map hash that uses the underlying constraint to compute the hash
14708 // and the equality for the indices.
14709 absl::flat_hash_map<int, int, ConstraintHashForDuplicateDetection,
14710 ConstraintEqForDuplicateDetection>
14711 equiv_constraints(
14712 model_proto.constraints_size(),
14713 ConstraintHashForDuplicateDetection{&model_proto, ignore_enforcement},
14714 ConstraintEqForDuplicateDetection{&model_proto, ignore_enforcement});
14715
14716 // Create a special representative for the linear objective.
14717 if (model_proto.has_objective() && !ignore_enforcement) {
14718 equiv_constraints[kObjectiveConstraint] = kObjectiveConstraint;
14719 }
14720
14721 const int num_constraints = model_proto.constraints().size();
14722 for (int c = 0; c < num_constraints; ++c) {
14723 const auto type = model_proto.constraints(c).constraint_case();
14724 if (type == ConstraintProto::CONSTRAINT_NOT_SET) continue;
14725
14726 // Nothing we will presolve in this case.
14727 if (ignore_enforcement && type == ConstraintProto::kBoolAnd) continue;
14728
14729 const auto [it, inserted] = equiv_constraints.insert({c, c});
14730 if (it->second != c) {
14731 // Already present!
14732 result.push_back({c, it->second});
14733 }
14734 }
14735
14736 return result;
14737}
14738
14739namespace {
14740bool SimpleLinearExprEq(const LinearExpressionProto& a,
14741 const LinearExpressionProto& b) {
14742 return absl::MakeSpan(a.vars()) == absl::MakeSpan(b.vars()) &&
14743 absl::MakeSpan(a.coeffs()) == absl::MakeSpan(b.coeffs()) &&
14744 a.offset() == b.offset();
14745}
14746
14747std::size_t LinearExpressionHash(const LinearExpressionProto& expr) {
14748 return absl::HashOf(absl::MakeSpan(expr.vars()),
14749 absl::MakeSpan(expr.coeffs()), expr.offset());
14750}
14751
14752} // namespace
14753
14754bool CpModelPresolver::IntervalConstraintEq::operator()(int a, int b) const {
14755 const ConstraintProto& ct_a = working_model->constraints(a);
14756 const ConstraintProto& ct_b = working_model->constraints(b);
14757 return absl::MakeSpan(ct_a.enforcement_literal()) ==
14758 absl::MakeSpan(ct_b.enforcement_literal()) &&
14759 SimpleLinearExprEq(ct_a.interval().start(), ct_b.interval().start()) &&
14760 SimpleLinearExprEq(ct_a.interval().size(), ct_b.interval().size()) &&
14761 SimpleLinearExprEq(ct_a.interval().end(), ct_b.interval().end());
14762}
14763
14764std::size_t CpModelPresolver::IntervalConstraintHash::operator()(
14765 int ct_idx) const {
14766 const ConstraintProto& ct = working_model->constraints(ct_idx);
14767 return absl::HashOf(absl::MakeSpan(ct.enforcement_literal()),
14768 LinearExpressionHash(ct.interval().start()),
14769 LinearExpressionHash(ct.interval().size()),
14770 LinearExpressionHash(ct.interval().end()));
14771}
14772
14773} // namespace sat
14774} // namespace operations_research
const std::vector< Variable * > & variables() const
Definition model.h:376
void Start()
Definition timer.h:30
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:39
void AddCounter(std::string name, int64_t count)
Definition logging.h:147
void set_literals(int index, ::int32_t value)
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_literals()
void ResetFromFlatMapping(Keys keys, Values values, int minimum_num_nodes=0)
Definition util.h:1046
::operations_research::sat::CumulativeConstraintProto *PROTOBUF_NONNULL mutable_cumulative()
::int32_t enforcement_literal(int index) const
::operations_research::sat::NoOverlapConstraintProto *PROTOBUF_NONNULL mutable_no_overlap()
void set_enforcement_literal(int index, ::int32_t value)
const ::operations_research::sat::BoolArgumentProto & exactly_one() const
::operations_research::sat::NoOverlap2DConstraintProto *PROTOBUF_NONNULL mutable_no_overlap_2d()
const ::operations_research::sat::LinearConstraintProto & linear() const
const ::operations_research::sat::BoolArgumentProto & at_most_one() const
::operations_research::sat::LinearArgumentProto *PROTOBUF_NONNULL mutable_int_div()
::operations_research::sat::LinearArgumentProto *PROTOBUF_NONNULL mutable_int_prod()
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_exactly_one()
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_bool_or()
::operations_research::sat::LinearArgumentProto *PROTOBUF_NONNULL mutable_lin_max()
const ::operations_research::sat::BoolArgumentProto & bool_or() const
::operations_research::sat::LinearConstraintProto *PROTOBUF_NONNULL mutable_linear()
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_enforcement_literal()
::operations_research::sat::LinearArgumentProto *PROTOBUF_NONNULL mutable_int_mod()
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_at_most_one()
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
::operations_research::sat::ConstraintProto *PROTOBUF_NONNULL mutable_constraints(int index)
const ::operations_research::sat::ConstraintProto & constraints(int index) const
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_intervals()
const ::operations_research::sat::LinearExpressionProto & exprs(int index) const
void set_vars(int index, ::int32_t value)
::google::protobuf::RepeatedField<::int64_t > *PROTOBUF_NONNULL mutable_coeffs()
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_vars()
void set_coeffs(int index, ::int64_t value)
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_y_intervals()
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_x_intervals()
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_intervals()
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)
bool CanonicalizeLinearConstraint(ConstraintProto *ct, bool *is_impossible=nullptr)
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
ConstraintProto * NewMappingConstraint(absl::string_view file, int line)
void UpdateRuleStats(std::string_view name, int num_times=1)
RoutesConstraintProto_NodeExpressions NodeExpressions
static constexpr SearchBranching FIXED_SEARCH
void StoreSolutionAsHint(CpModelProto &model) const
void Truncate(RepeatedPtrField< T > *array, int new_size)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:55
bool ReduceNumberOfBoxesExactMandatory(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
constexpr uint64_t kDefaultFingerprintSeed
uint64_t FingerprintRepeatedField(const google::protobuf::RepeatedField< T > &sequence, uint64_t seed)
void ApplyToAllVariableIndices(absl::FunctionRef< void(int *)> f, ConstraintProto *ct)
bool LinearExpressionProtosAreEqual(const LinearExpressionProto &a, const LinearExpressionProto &b, int64_t b_scaling)
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:193
std::vector< VariableEncodingLocalModel > CreateVariableEncodingLocalModels(PresolveContext *context)
CompactVectorVector< int > GetOverlappingRectangleComponents(absl::Span< const Rectangle > rectangles)
Domain EvaluateImpliedIntProdDomain(const LinearArgumentProto &expr, const PresolveContext &context)
int64_t FloorSquareRoot(int64_t a)
Definition util.cc:289
void ApplyToAllLiteralIndices(absl::FunctionRef< void(int *)> f, ConstraintProto *ct)
bool HasEnforcementLiteral(const ConstraintProto &ct)
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)
bool SafeAddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
bool IsNegatableInt64(absl::int128 x)
Definition util.h:929
void ProbeAndFindEquivalentLiteral(SatSolver *solver, SatPostsolver *postsolver, util_intops::StrongVector< LiteralIndex, LiteralIndex > *mapping, SolverLogger *logger)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
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)
const IntegerVariable kNoIntegerVariable(-1)
bool ScaleFloatingPointObjective(const SatParameters &params, SolverLogger *logger, CpModelProto *proto)
void DetectAndAddSymmetryToProto(const SatParameters &params, CpModelProto *proto, SolverLogger *logger, TimeLimit *time_limit)
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset, std::pair< int64_t, int64_t > *implied_domain)
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
bool FilterOrbitOnUnusedOrFixedVariables(SymmetryProto *symmetry, PresolveContext *context)
bool LookForTrivialSatSolution(double deterministic_time_limit, Model *model, SolverLogger *logger)
Definition probing.cc:1084
void ScanModelForDualBoundStrengthening(const PresolveContext &context, DualBoundStrengthening *dual_bound_strengthening)
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
IntegerVariable PositiveVariable(IntegerVariable i)
IntegerValue PositiveRemainder(IntegerValue dividend, IntegerValue positive_divisor)
void ApplyVariableMapping(absl::Span< int > mapping, CpModelProto *cp_model, std::vector< int > *reverse_mapping)
int64_t SafeDoubleToInt64(double value)
Definition util.h:917
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
void TryToReplaceVariableByItsEncoding(int var, PresolveContext *context, SolutionCrush &solution_crush)
int ReindexArcs(IntContainer *tails, IntContainer *heads, absl::flat_hash_map< int, int > *mapping_output=nullptr)
Definition circuit.h:220
bool MaybeTransferLinear1ToAnotherVariable(VariableEncodingLocalModel &local_model, PresolveContext *context)
void FinalExpansionForLinearConstraint(PresolveContext *context)
constexpr int kObjectiveConstraint
int64_t AffineExpressionValueAt(const LinearExpressionProto &expr, int64_t value)
bool ReduceNumberofBoxesGreedy(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
bool ExpressionsContainsOnlyOneVar(const ExpressionList &exprs)
int64_t ClosestMultiple(int64_t value, int64_t base)
Definition util.cc:306
void ScanModelForDominanceDetection(PresolveContext &context, VarDomination *var_domination)
bool LinearsDifferAtOneTerm(const LinearConstraintProto &lin1, const LinearConstraintProto &lin2)
bool ExpressionContainsSingleRef(const LinearExpressionProto &expr)
int64_t LinearExpressionGcd(const LinearExpressionProto &expr, int64_t gcd)
InclusionDetector(const Storage &storage) -> InclusionDetector< Storage >
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:313
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)
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)
bool IsAffineIntAbs(const ConstraintProto &ct)
void ApplyToAllIntervalIndices(absl::FunctionRef< void(int *)> f, ConstraintProto *ct)
bool VariableIsPositive(IntegerVariable i)
bool PresolveFixed2dRectangles(absl::Span< const RectangleInRange > non_fixed_boxes, std::vector< Rectangle > *fixed_boxes)
bool LoadModelForPresolve(const CpModelProto &model_proto, SatParameters params, PresolveContext *context, Model *local_model, absl::string_view name_for_logging)
bool DetectAllEncodedComplexDomain(PresolveContext *context, VariableEncodingLocalModel &local_model)
void CanonicalizeTable(PresolveContext *context, ConstraintProto *ct)
OR-Tools root namespace.
bool AtMinOrMaxInt64(int64_t x)
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
ClosedInterval::Iterator end(ClosedInterval interval)
std::string ProtobufShortDebugString(const P &message)
Definition proto_utils.h:46
std::string FormatCounter(int64_t num)
Definition logging.cc:30
int64_t CapProd(int64_t x, int64_t y)
ClosedInterval::Iterator begin(ClosedInterval interval)
absl::StatusOr< std::vector< typename util::GraphTraits< AdjacencyLists >::NodeIndex > > FastTopologicalSort(const AdjacencyLists &adj)
if(!yyg->yy_init)
Definition parser.yy.cc:966
void FindStronglyConnectedComponents(NodeIndex num_nodes, const Graph &graph, SccOutput *components)
Definition model.h:50
int64_t Max() const
Definition model.cc:346
bool empty() const
Definition model.cc:335
int64_t Min() const
Definition model.cc:340
bool Contains(int64_t value) const
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:114