Google OR-Tools v9.11
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-2024 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 <string>
26#include <tuple>
27#include <utility>
28#include <vector>
29
30#include "absl/algorithm/container.h"
31#include "absl/base/attributes.h"
32#include "absl/container/btree_map.h"
33#include "absl/container/btree_set.h"
34#include "absl/container/flat_hash_map.h"
35#include "absl/container/flat_hash_set.h"
36#include "absl/hash/hash.h"
37#include "absl/log/check.h"
38#include "absl/meta/type_traits.h"
39#include "absl/numeric/int128.h"
40#include "absl/random/distributions.h"
41#include "absl/status/statusor.h"
42#include "absl/strings/str_cat.h"
43#include "absl/types/span.h"
44#include "google/protobuf/repeated_field.h"
45#include "google/protobuf/text_format.h"
51#include "ortools/base/timer.h"
56#include "ortools/sat/circuit.h"
57#include "ortools/sat/clause.h"
58#include "ortools/sat/cp_model.pb.h"
67#include "ortools/sat/integer.h"
68#include "ortools/sat/model.h"
71#include "ortools/sat/probing.h"
74#include "ortools/sat/sat_parameters.pb.h"
77#include "ortools/sat/util.h"
80#include "ortools/util/bitset.h"
86
87namespace operations_research {
88namespace sat {
89
90namespace {
91
92// TODO(user): Just make sure this invariant is enforced in all our linear
93// constraint after copy, and simplify the code!
94bool LinearConstraintIsClean(const LinearConstraintProto& linear) {
95 const int num_vars = linear.vars().size();
96 for (int i = 0; i < num_vars; ++i) {
97 if (!RefIsPositive(linear.vars(i))) return false;
98 if (linear.coeffs(i) == 0) return false;
99 }
100 return true;
101}
102
103} // namespace
104
105bool CpModelPresolver::RemoveConstraint(ConstraintProto* ct) {
106 ct->Clear();
107 return true;
108}
109
110// Remove all empty constraints and duplicated intervals. Note that we need to
111// remap the interval references.
112//
113// Now that they have served their purpose, we also remove dummy constraints,
114// otherwise that causes issue because our model are invalid in tests.
116 interval_representative_.clear();
117 std::vector<int> interval_mapping(context_->working_model->constraints_size(),
118 -1);
119 int new_num_constraints = 0;
120 const int old_num_non_empty_constraints =
121 context_->working_model->constraints_size();
122 for (int c = 0; c < old_num_non_empty_constraints; ++c) {
123 const auto type = context_->working_model->constraints(c).constraint_case();
124 if (type == ConstraintProto::CONSTRAINT_NOT_SET) continue;
125 if (type == ConstraintProto::kDummyConstraint) continue;
126 context_->working_model->mutable_constraints(new_num_constraints)
127 ->Swap(context_->working_model->mutable_constraints(c));
128 if (type == ConstraintProto::kInterval) {
129 // Warning: interval_representative_ holds a pointer to the working model
130 // to compute hashes, so we need to be careful about not changing a
131 // constraint after its index is added to the map.
132 const auto [it, inserted] = interval_representative_.insert(
133 {new_num_constraints, new_num_constraints});
134 interval_mapping[c] = it->second;
135 if (it->second != new_num_constraints) {
136 context_->UpdateRuleStats(
137 "intervals: change duplicate index across constraints");
138 continue;
139 }
140 }
141 new_num_constraints++;
142 }
144 context_->working_model->mutable_constraints(), new_num_constraints);
145 for (ConstraintProto& ct_ref :
146 *context_->working_model->mutable_constraints()) {
148 [&interval_mapping](int* ref) {
149 *ref = interval_mapping[*ref];
150 CHECK_NE(-1, *ref);
151 },
152 &ct_ref);
153 }
154}
155
156bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto* ct) {
157 if (context_->ModelIsUnsat()) return false;
158 if (!HasEnforcementLiteral(*ct)) return false;
159
160 int new_size = 0;
161 const int old_size = ct->enforcement_literal().size();
162 context_->tmp_literal_set.clear();
163 for (const int literal : ct->enforcement_literal()) {
164 if (context_->LiteralIsTrue(literal)) {
165 // We can remove a literal at true.
166 context_->UpdateRuleStats("enforcement: true literal");
167 continue;
168 }
169
170 if (context_->LiteralIsFalse(literal)) {
171 context_->UpdateRuleStats("enforcement: false literal");
172 return RemoveConstraint(ct);
173 }
174
175 if (context_->VariableIsUniqueAndRemovable(literal)) {
176 // We can simply set it to false and ignore the constraint in this case.
177 context_->UpdateRuleStats("enforcement: literal not used");
178 CHECK(context_->SetLiteralToFalse(literal));
179 return RemoveConstraint(ct);
180 }
181
182 // If the literal only appear in the objective, we might be able to fix it
183 // to false. TODO(user): generalize if the literal always appear with the
184 // same polarity.
186 const int64_t obj_coeff =
187 context_->ObjectiveMap().at(PositiveRef(literal));
188 if (RefIsPositive(literal) == (obj_coeff > 0)) {
189 // It is just more advantageous to set it to false!
190 context_->UpdateRuleStats("enforcement: literal with unique direction");
191 CHECK(context_->SetLiteralToFalse(literal));
192 return RemoveConstraint(ct);
193 }
194 }
195
196 // Deals with duplicate literals.
197 //
198 // TODO(user): Ideally we could do that just once during the first copy,
199 // and later never create such constraint.
200 if (old_size > 1) {
201 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
202 if (!inserted) {
203 context_->UpdateRuleStats("enforcement: removed duplicate literal");
204 continue;
205 }
206 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
207 context_->UpdateRuleStats("enforcement: can never be true");
208 return RemoveConstraint(ct);
209 }
210 }
211
212 ct->set_enforcement_literal(new_size++, literal);
213 }
214 ct->mutable_enforcement_literal()->Truncate(new_size);
215 return new_size != old_size;
216}
217
218bool CpModelPresolver::PresolveBoolXor(ConstraintProto* ct) {
219 if (context_->ModelIsUnsat()) return false;
220 if (HasEnforcementLiteral(*ct)) return false;
221
222 int new_size = 0;
223 bool changed = false;
224 int num_true_literals = 0;
225 int true_literal = std::numeric_limits<int32_t>::min();
226 for (const int literal : ct->bool_xor().literals()) {
227 // TODO(user): More generally, if a variable appear in only bool xor
228 // constraints, we can simply eliminate it using linear algebra on Z/2Z.
229 // This should solve in polynomial time the parity-learning*.fzn problems
230 // for instance. This seems low priority, but it is also easy to do. Even
231 // better would be to have a dedicated propagator with all bool_xor
232 // constraints that do the necessary linear algebra.
233 if (context_->VariableIsUniqueAndRemovable(literal)) {
234 context_->UpdateRuleStats("TODO bool_xor: remove constraint");
235 }
236
237 if (context_->LiteralIsFalse(literal)) {
238 context_->UpdateRuleStats("bool_xor: remove false literal");
239 changed = true;
240 continue;
241 } else if (context_->LiteralIsTrue(literal)) {
242 true_literal = literal; // Keep if we need to put one back.
243 num_true_literals++;
244 continue;
245 }
246
247 ct->mutable_bool_xor()->set_literals(new_size++, literal);
248 }
249
250 if (new_size == 0) {
251 if (num_true_literals % 2 == 0) {
252 return context_->NotifyThatModelIsUnsat("bool_xor: always false");
253 } else {
254 context_->UpdateRuleStats("bool_xor: always true");
255 return RemoveConstraint(ct);
256 }
257 } else if (new_size == 1) { // We can fix the only active literal.
258 if (num_true_literals % 2 == 0) {
259 if (!context_->SetLiteralToTrue(ct->bool_xor().literals(0))) {
260 return context_->NotifyThatModelIsUnsat(
261 "bool_xor: cannot fix last literal");
262 }
263 } else {
264 if (!context_->SetLiteralToFalse(ct->bool_xor().literals(0))) {
265 return context_->NotifyThatModelIsUnsat(
266 "bool_xor: cannot fix last literal");
267 }
268 }
269 context_->UpdateRuleStats("bool_xor: one active literal");
270 return RemoveConstraint(ct);
271 } else if (new_size == 2) { // We can simplify the bool_xor.
272 const int a = ct->bool_xor().literals(0);
273 const int b = ct->bool_xor().literals(1);
274 if (a == b) {
275 if (num_true_literals % 2 == 0) {
276 return context_->NotifyThatModelIsUnsat("bool_xor: always false");
277 } else {
278 context_->UpdateRuleStats("bool_xor: always true");
279 return RemoveConstraint(ct);
280 }
281 }
282 if (a == NegatedRef(b)) {
283 if (num_true_literals % 2 == 1) {
284 return context_->NotifyThatModelIsUnsat("bool_xor: always false");
285 } else {
286 context_->UpdateRuleStats("bool_xor: always true");
287 return RemoveConstraint(ct);
288 }
289 }
290 if (num_true_literals % 2 == 0) { // a == not(b).
292 } else { // a == b.
293 context_->StoreBooleanEqualityRelation(a, b);
294 }
296 context_->UpdateRuleStats("bool_xor: two active literals");
297 return RemoveConstraint(ct);
298 }
299
300 if (num_true_literals % 2 == 1) {
301 CHECK_NE(true_literal, std::numeric_limits<int32_t>::min());
302 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
303 }
304 if (num_true_literals > 1) {
305 context_->UpdateRuleStats("bool_xor: remove even number of true literals");
306 changed = true;
307 }
308 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
309 return changed;
310}
311
312bool CpModelPresolver::PresolveBoolOr(ConstraintProto* ct) {
313 if (context_->ModelIsUnsat()) return false;
314
315 // Move the enforcement literal inside the clause if any. Note that we do not
316 // mark this as a change since the literal in the constraint are the same.
318 context_->UpdateRuleStats("bool_or: removed enforcement literal");
319 for (const int literal : ct->enforcement_literal()) {
320 ct->mutable_bool_or()->add_literals(NegatedRef(literal));
321 }
322 ct->clear_enforcement_literal();
323 }
324
325 // Inspects the literals and deal with fixed ones.
326 //
327 // TODO(user): Because we remove literal on the first copy, maybe we can get
328 // rid of the set here. However we still need to be careful when remapping
329 // literals to their representatives.
330 bool changed = false;
331 context_->tmp_literals.clear();
332 context_->tmp_literal_set.clear();
333 for (const int literal : ct->bool_or().literals()) {
334 if (context_->LiteralIsFalse(literal)) {
335 changed = true;
336 continue;
337 }
338 if (context_->LiteralIsTrue(literal)) {
339 context_->UpdateRuleStats("bool_or: always true");
340 return RemoveConstraint(ct);
341 }
342 // We can just set the variable to true in this case since it is not
343 // used in any other constraint (note that we artificially bump the
344 // objective var usage by 1).
345 if (context_->VariableIsUniqueAndRemovable(literal)) {
346 context_->UpdateRuleStats("bool_or: singleton");
347 if (!context_->SetLiteralToTrue(literal)) return true;
348 return RemoveConstraint(ct);
349 }
350 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
351 context_->UpdateRuleStats("bool_or: always true");
352 return RemoveConstraint(ct);
353 }
354
355 if (context_->tmp_literal_set.contains(literal)) {
356 changed = true;
357 } else {
358 context_->tmp_literal_set.insert(literal);
359 context_->tmp_literals.push_back(literal);
360 }
361 }
362 context_->tmp_literal_set.clear();
363
364 if (context_->tmp_literals.empty()) {
365 context_->UpdateRuleStats("bool_or: empty");
366 return context_->NotifyThatModelIsUnsat();
367 }
368 if (context_->tmp_literals.size() == 1) {
369 context_->UpdateRuleStats("bool_or: only one literal");
370 if (!context_->SetLiteralToTrue(context_->tmp_literals[0])) return true;
371 return RemoveConstraint(ct);
372 }
373 if (context_->tmp_literals.size() == 2) {
374 // For consistency, we move all "implication" into half-reified bool_and.
375 // TODO(user): merge by enforcement literal and detect implication cycles.
376 context_->UpdateRuleStats("bool_or: implications");
377 ct->add_enforcement_literal(NegatedRef(context_->tmp_literals[0]));
378 ct->mutable_bool_and()->add_literals(context_->tmp_literals[1]);
379 return changed;
380 }
381
382 if (changed) {
383 context_->UpdateRuleStats("bool_or: fixed literals");
384 ct->mutable_bool_or()->mutable_literals()->Clear();
385 for (const int lit : context_->tmp_literals) {
386 ct->mutable_bool_or()->add_literals(lit);
387 }
388 }
389 return changed;
390}
391
392// Note this function does not update the constraint graph. It assumes this is
393// done elsewhere.
394ABSL_MUST_USE_RESULT bool CpModelPresolver::MarkConstraintAsFalse(
395 ConstraintProto* ct) {
397 // Change the constraint to a bool_or.
398 ct->mutable_bool_or()->clear_literals();
399 for (const int lit : ct->enforcement_literal()) {
400 ct->mutable_bool_or()->add_literals(NegatedRef(lit));
401 }
402 ct->clear_enforcement_literal();
403 PresolveBoolOr(ct);
404 return true;
405 } else {
406 return context_->NotifyThatModelIsUnsat();
407 }
408}
409
410bool CpModelPresolver::PresolveBoolAnd(ConstraintProto* ct) {
411 if (context_->ModelIsUnsat()) return false;
412
413 if (!HasEnforcementLiteral(*ct)) {
414 context_->UpdateRuleStats("bool_and: non-reified.");
415 for (const int literal : ct->bool_and().literals()) {
416 if (!context_->SetLiteralToTrue(literal)) return true;
417 }
418 return RemoveConstraint(ct);
419 }
420
421 bool changed = false;
422 context_->tmp_literals.clear();
423 context_->tmp_literal_set.clear();
424 const absl::flat_hash_set<int> enforcement_literals_set(
425 ct->enforcement_literal().begin(), ct->enforcement_literal().end());
426 for (const int literal : ct->bool_and().literals()) {
427 if (context_->LiteralIsFalse(literal)) {
428 context_->UpdateRuleStats("bool_and: always false");
429 return MarkConstraintAsFalse(ct);
430 }
431 if (context_->LiteralIsTrue(literal)) {
432 changed = true;
433 continue;
434 }
435 if (enforcement_literals_set.contains(literal)) {
436 context_->UpdateRuleStats("bool_and: x => x");
437 changed = true;
438 continue;
439 }
440 if (enforcement_literals_set.contains(NegatedRef(literal))) {
441 context_->UpdateRuleStats("bool_and: x => not x");
442 return MarkConstraintAsFalse(ct);
443 }
444 if (context_->VariableIsUniqueAndRemovable(literal)) {
445 changed = true;
446 if (!context_->SetLiteralToTrue(literal)) return true;
447 continue;
448 }
449
450 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
451 context_->UpdateRuleStats("bool_and: cannot be enforced");
452 return MarkConstraintAsFalse(ct);
453 }
454
455 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
456 if (inserted) {
457 context_->tmp_literals.push_back(literal);
458 } else {
459 changed = true;
460 context_->UpdateRuleStats("bool_and: removed duplicate literal");
461 }
462 }
463
464 // Note that this is not the same behavior as a bool_or:
465 // - bool_or means "at least one", so it is false if empty.
466 // - bool_and means "all literals inside true", so it is true if empty.
467 if (context_->tmp_literals.empty()) return RemoveConstraint(ct);
468
469 if (changed) {
470 ct->mutable_bool_and()->mutable_literals()->Clear();
471 for (const int lit : context_->tmp_literals) {
472 ct->mutable_bool_and()->add_literals(lit);
473 }
474 context_->UpdateRuleStats("bool_and: fixed literals");
475 }
476
477 // If a variable can move freely in one direction except for this constraint,
478 // we can make it an equality.
479 //
480 // TODO(user): also consider literal on the other side of the =>.
481 if (ct->enforcement_literal().size() == 1 &&
482 ct->bool_and().literals().size() == 1) {
483 const int enforcement = ct->enforcement_literal(0);
484 if (context_->VariableWithCostIsUniqueAndRemovable(enforcement)) {
485 int var = PositiveRef(enforcement);
486 int64_t obj_coeff = context_->ObjectiveMap().at(var);
487 if (!RefIsPositive(enforcement)) obj_coeff = -obj_coeff;
488
489 // The other case where the constraint is redundant is treated elsewhere.
490 if (obj_coeff < 0) {
491 context_->UpdateRuleStats("bool_and: dual equality.");
492 context_->StoreBooleanEqualityRelation(enforcement,
493 ct->bool_and().literals(0));
494 }
495 }
496 }
497
498 return changed;
499}
500
501bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto* ct) {
502 bool is_at_most_one = ct->constraint_case() == ConstraintProto::kAtMostOne;
503 const std::string name = is_at_most_one ? "at_most_one: " : "exactly_one: ";
504 auto* literals = is_at_most_one
505 ? ct->mutable_at_most_one()->mutable_literals()
506 : ct->mutable_exactly_one()->mutable_literals();
507
508 // Having a canonical constraint is needed for duplicate detection.
509 // This also change how we regroup bool_and.
510 std::sort(literals->begin(), literals->end());
511
512 // Deal with duplicate variable reference.
513 context_->tmp_literal_set.clear();
514 for (const int literal : *literals) {
515 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
516 if (!inserted) {
517 if (!context_->SetLiteralToFalse(literal)) return false;
518 context_->UpdateRuleStats(absl::StrCat(name, "duplicate literals"));
519 }
520 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
521 int num_positive = 0;
522 int num_negative = 0;
523 for (const int other : *literals) {
524 if (PositiveRef(other) != PositiveRef(literal)) {
525 if (!context_->SetLiteralToFalse(other)) return false;
526 context_->UpdateRuleStats(absl::StrCat(name, "x and not(x)"));
527 } else {
528 if (other == literal) {
529 ++num_positive;
530 } else {
531 ++num_negative;
532 }
533 }
534 }
535
536 // This is tricky for the case where the at most one reduce to (lit,
537 // not(lit), not(lit)) for instance.
538 if (num_positive > 1 && !context_->SetLiteralToFalse(literal)) {
539 return false;
540 }
541 if (num_negative > 1 && !context_->SetLiteralToTrue(literal)) {
542 return false;
543 }
544 return RemoveConstraint(ct);
545 }
546 }
547
548 // We can always remove all singleton variables (with or without cost) in an
549 // at_most_one or exactly one. We collect them and deal with this at the end.
550 std::vector<std::pair<int, int64_t>> singleton_literal_with_cost;
551
552 // Remove fixed variables.
553 bool changed = false;
554 context_->tmp_literals.clear();
555 for (const int literal : *literals) {
556 if (context_->LiteralIsTrue(literal)) {
557 context_->UpdateRuleStats(absl::StrCat(name, "satisfied"));
558 for (const int other : *literals) {
559 if (other != literal) {
560 if (!context_->SetLiteralToFalse(other)) return false;
561 }
562 }
563 return RemoveConstraint(ct);
564 }
565
566 if (context_->LiteralIsFalse(literal)) {
567 changed = true;
568 continue;
569 }
570
571 // A singleton variable with or without cost can be removed. See below.
572 if (context_->VariableIsUniqueAndRemovable(literal)) {
573 singleton_literal_with_cost.push_back({literal, 0});
574 continue;
575 }
577 const auto it = context_->ObjectiveMap().find(PositiveRef(literal));
578 DCHECK(it != context_->ObjectiveMap().end());
579 if (RefIsPositive(literal)) {
580 singleton_literal_with_cost.push_back({literal, it->second});
581 } else {
582 // Note that we actually just store the objective change if this literal
583 // is true compared to it being false.
584 singleton_literal_with_cost.push_back({literal, -it->second});
585 }
586 continue;
587 }
588
589 context_->tmp_literals.push_back(literal);
590 }
591
592 bool transform_to_at_most_one = false;
593 if (!singleton_literal_with_cost.empty()) {
594 changed = true;
595
596 // By domination argument, we can fix to false everything but the minimum.
597 if (singleton_literal_with_cost.size() > 1) {
598 std::sort(
599 singleton_literal_with_cost.begin(),
600 singleton_literal_with_cost.end(),
601 [](const std::pair<int, int64_t>& a,
602 const std::pair<int, int64_t>& b) { return a.second < b.second; });
603 for (int i = 1; i < singleton_literal_with_cost.size(); ++i) {
604 context_->UpdateRuleStats("at_most_one: dominated singleton");
605 if (!context_->SetLiteralToFalse(
606 singleton_literal_with_cost[i].first)) {
607 return false;
608 }
609 }
610 singleton_literal_with_cost.resize(1);
611 }
612
613 const int literal = singleton_literal_with_cost[0].first;
614 const int64_t literal_cost = singleton_literal_with_cost[0].second;
615 if (is_at_most_one && literal_cost >= 0) {
616 // We can just always set it to false in this case.
617 context_->UpdateRuleStats("at_most_one: singleton");
618 if (!context_->SetLiteralToFalse(literal)) return false;
619 } else if (context_->ShiftCostInExactlyOne(*literals, literal_cost)) {
620 // We can make the constraint an exactly one if needed since it is always
621 // beneficial to set this literal to true if everything else is zero. Now
622 // that we have an exactly one, we can transfer the cost to the other
623 // terms. The objective of literal should become zero, and we can then
624 // decide its value at postsolve and just have an at most one on the other
625 // literals.
626 DCHECK(!context_->ObjectiveMap().contains(PositiveRef(literal)));
627
628 if (!is_at_most_one) transform_to_at_most_one = true;
629 is_at_most_one = true;
630
631 context_->UpdateRuleStats("exactly_one: singleton");
633
634 // Put a constraint in the mapping proto for postsolve.
635 auto* mapping_exo = context_->NewMappingConstraint(__FILE__, __LINE__)
636 ->mutable_exactly_one();
637 for (const int lit : context_->tmp_literals) {
638 mapping_exo->add_literals(lit);
639 }
640 mapping_exo->add_literals(literal);
641 }
642 }
643
644 if (!is_at_most_one && !transform_to_at_most_one &&
645 context_->ExploitExactlyOneInObjective(context_->tmp_literals)) {
646 context_->UpdateRuleStats("exactly_one: simplified objective");
647 }
648
649 if (transform_to_at_most_one) {
650 CHECK(changed);
651 ct->Clear();
652 literals = ct->mutable_at_most_one()->mutable_literals();
653 }
654 if (changed) {
655 literals->Clear();
656 for (const int lit : context_->tmp_literals) {
657 literals->Add(lit);
658 }
659 context_->UpdateRuleStats(absl::StrCat(name, "removed literals"));
660 }
661 return changed;
662}
663
664bool CpModelPresolver::PresolveAtMostOne(ConstraintProto* ct) {
665 if (context_->ModelIsUnsat()) return false;
666
667 CHECK(!HasEnforcementLiteral(*ct));
668 const bool changed = PresolveAtMostOrExactlyOne(ct);
669 if (ct->constraint_case() != ConstraintProto::kAtMostOne) return changed;
670
671 // Size zero: ok.
672 const auto& literals = ct->at_most_one().literals();
673 if (literals.empty()) {
674 context_->UpdateRuleStats("at_most_one: empty or all false");
675 return RemoveConstraint(ct);
676 }
677
678 // Size one: always satisfied.
679 if (literals.size() == 1) {
680 context_->UpdateRuleStats("at_most_one: size one");
681 return RemoveConstraint(ct);
682 }
683
684 return changed;
685}
686
687bool CpModelPresolver::PresolveExactlyOne(ConstraintProto* ct) {
688 if (context_->ModelIsUnsat()) return false;
689 CHECK(!HasEnforcementLiteral(*ct));
690 const bool changed = PresolveAtMostOrExactlyOne(ct);
691 if (ct->constraint_case() != ConstraintProto::kExactlyOne) return changed;
692
693 // Size zero: UNSAT.
694 const auto& literals = ct->exactly_one().literals();
695 if (literals.empty()) {
696 return context_->NotifyThatModelIsUnsat("exactly_one: empty or all false");
697 }
698
699 // Size one: fix variable.
700 if (literals.size() == 1) {
701 context_->UpdateRuleStats("exactly_one: size one");
702 if (!context_->SetLiteralToTrue(literals[0])) return false;
703 return RemoveConstraint(ct);
704 }
705
706 // Size two: Equivalence.
707 if (literals.size() == 2) {
708 context_->UpdateRuleStats("exactly_one: size two");
709 context_->StoreBooleanEqualityRelation(literals[0],
710 NegatedRef(literals[1]));
711 return RemoveConstraint(ct);
712 }
713
714 return changed;
715}
716
717bool CpModelPresolver::CanonicalizeLinearArgument(const ConstraintProto& ct,
718 LinearArgumentProto* proto) {
719 if (context_->ModelIsUnsat()) return false;
720
721 // Canonicalize all involved expression.
722 bool changed = CanonicalizeLinearExpression(ct, proto->mutable_target());
723 for (LinearExpressionProto& exp : *(proto->mutable_exprs())) {
724 changed |= CanonicalizeLinearExpression(ct, &exp);
725 }
726 return changed;
727}
728
729// Deal with X = lin_max(exprs) where all exprs are divisible by gcd.
730// X must be divisible also, and we can divide everything.
731bool CpModelPresolver::DivideLinMaxByGcd(int c, ConstraintProto* ct) {
732 LinearArgumentProto* lin_max = ct->mutable_lin_max();
733
734 // Compute gcd of exprs first.
735 int64_t gcd = 0;
736 for (const LinearExpressionProto& expr : lin_max->exprs()) {
737 gcd = LinearExpressionGcd(expr, gcd);
738 if (gcd == 1) break;
739 }
740 if (gcd <= 1) return true;
741
742 // TODO(user): deal with all UNSAT case.
743 // Also if the target is affine, we can canonicalize it.
744 const LinearExpressionProto& target = lin_max->target();
745 const int64_t old_gcd = gcd;
746 gcd = LinearExpressionGcd(target, gcd);
747 if (gcd != old_gcd) {
748 if (target.vars().empty()) {
749 return context_->NotifyThatModelIsUnsat("infeasible lin_max");
750 }
751
752 // If the target is affine, we can solve the diophantine equation and
753 // express the target in term of a new variable.
754 if (target.vars().size() == 1) {
755 gcd = old_gcd;
756 context_->UpdateRuleStats("lin_max: canonicalize target using gcd");
757 if (!context_->CanonicalizeAffineVariable(
758 target.vars(0), target.coeffs(0), gcd, -target.offset())) {
759 return false;
760 }
761 CanonicalizeLinearExpression(*ct, lin_max->mutable_target());
763 CHECK_EQ(LinearExpressionGcd(target, gcd), gcd);
764 } else {
765 context_->UpdateRuleStats(
766 "TODO lin_max: lhs not trivially divisible by rhs gcd");
767 }
768 }
769 if (gcd <= 1) return true;
770
771 context_->UpdateRuleStats("lin_max: divising by gcd");
772 DivideLinearExpression(gcd, lin_max->mutable_target());
773 for (LinearExpressionProto& expr : *lin_max->mutable_exprs()) {
774 DivideLinearExpression(gcd, &expr);
775 }
776 return true;
777}
778
779namespace {
780
781int64_t EvaluateSingleVariableExpression(const LinearExpressionProto& expr,
782 int var, int64_t value) {
783 int64_t result = expr.offset();
784 for (int i = 0; i < expr.vars().size(); ++i) {
785 CHECK_EQ(expr.vars(i), var);
786 result += expr.coeffs(i) * value;
787 }
788 return result;
789}
790
791template <class ExpressionList>
792int GetFirstVar(ExpressionList exprs) {
793 for (const LinearExpressionProto& expr : exprs) {
794 for (const int var : expr.vars()) {
795 DCHECK(RefIsPositive(var));
796 return var;
797 }
798 }
799 return -1;
800}
801
802bool IsAffineIntAbs(const ConstraintProto& ct) {
803 if (ct.constraint_case() != ConstraintProto::kLinMax ||
804 ct.lin_max().exprs_size() != 2 || ct.lin_max().target().vars_size() > 1 ||
805 ct.lin_max().exprs(0).vars_size() != 1 ||
806 ct.lin_max().exprs(1).vars_size() != 1) {
807 return false;
808 }
809
810 const LinearArgumentProto& lin_max = ct.lin_max();
811 if (lin_max.exprs(0).offset() != -lin_max.exprs(1).offset()) return false;
812 if (PositiveRef(lin_max.exprs(0).vars(0)) !=
813 PositiveRef(lin_max.exprs(1).vars(0))) {
814 return false;
815 }
816
817 const int64_t left_coeff = RefIsPositive(lin_max.exprs(0).vars(0))
818 ? lin_max.exprs(0).coeffs(0)
819 : -lin_max.exprs(0).coeffs(0);
820 const int64_t right_coeff = RefIsPositive(lin_max.exprs(1).vars(0))
821 ? lin_max.exprs(1).coeffs(0)
822 : -lin_max.exprs(1).coeffs(0);
823 return left_coeff == -right_coeff;
824}
825
826} // namespace
827
828bool CpModelPresolver::PropagateAndReduceAffineMax(ConstraintProto* ct) {
829 // Get the unique variable appearing in the expressions.
830 const int unique_var = GetFirstVar(ct->lin_max().exprs());
831
832 const auto& lin_max = ct->lin_max();
833 const int num_exprs = lin_max.exprs_size();
834 const auto& target = lin_max.target();
835 std::vector<int> num_wins(num_exprs, 0);
836 std::vector<int64_t> reachable_target_values;
837 std::vector<int64_t> valid_variable_values;
838 std::vector<int64_t> tmp_values(num_exprs);
839
840 const bool target_has_same_unique_var =
841 target.vars_size() == 1 && target.vars(0) == unique_var;
842
843 CHECK_LE(context_->DomainOf(unique_var).Size(), 1000);
844
845 for (const int64_t value : context_->DomainOf(unique_var).Values()) {
846 int64_t current_max = std::numeric_limits<int64_t>::min();
847
848 // Fill tmp_values and compute current_max;
849 for (int i = 0; i < num_exprs; ++i) {
850 const int64_t v =
851 EvaluateSingleVariableExpression(lin_max.exprs(i), unique_var, value);
852 current_max = std::max(current_max, v);
853 tmp_values[i] = v;
854 }
855
856 // Check if any expr produced a value compatible with the target.
857 if (!context_->DomainContains(target, current_max)) continue;
858
859 // Special case: affine(x) == max(exprs(x)). We can check if the affine()
860 // and the max(exprs) are compatible.
861 if (target_has_same_unique_var &&
862 EvaluateSingleVariableExpression(target, unique_var, value) !=
863 current_max) {
864 continue;
865 }
866
867 valid_variable_values.push_back(value);
868 reachable_target_values.push_back(current_max);
869 for (int i = 0; i < num_exprs; ++i) {
870 DCHECK_LE(tmp_values[i], current_max);
871 if (tmp_values[i] == current_max) {
872 num_wins[i]++;
873 }
874 }
875 }
876
877 if (reachable_target_values.empty() || valid_variable_values.empty()) {
878 context_->UpdateRuleStats("lin_max: infeasible affine_max constraint");
879 return MarkConstraintAsFalse(ct);
880 }
881
882 {
883 bool reduced = false;
884 if (!context_->IntersectDomainWith(
885 target, Domain::FromValues(reachable_target_values), &reduced)) {
886 return true;
887 }
888 if (reduced) {
889 context_->UpdateRuleStats("lin_max: affine_max target domain reduced");
890 }
891 }
892
893 {
894 bool reduced = false;
895 if (!context_->IntersectDomainWith(
896 unique_var, Domain::FromValues(valid_variable_values), &reduced)) {
897 return true;
898 }
899 if (reduced) {
900 context_->UpdateRuleStats(
901 "lin_max: unique affine_max var domain reduced");
902 }
903 }
904
905 // If one expression always wins, even tied, we can eliminate all the others.
906 for (int i = 0; i < num_exprs; ++i) {
907 if (num_wins[i] == valid_variable_values.size()) {
908 const LinearExpressionProto winner_expr = lin_max.exprs(i);
909 ct->mutable_lin_max()->clear_exprs();
910 *ct->mutable_lin_max()->add_exprs() = winner_expr;
911 break;
912 }
913 }
914
915 bool changed = false;
916 if (ct->lin_max().exprs_size() > 1) {
917 int new_size = 0;
918 for (int i = 0; i < num_exprs; ++i) {
919 if (num_wins[i] == 0) continue;
920 *ct->mutable_lin_max()->mutable_exprs(new_size) = ct->lin_max().exprs(i);
921 new_size++;
922 }
923 if (new_size < ct->lin_max().exprs_size()) {
924 context_->UpdateRuleStats("lin_max: removed affine_max exprs");
925 google::protobuf::util::Truncate(ct->mutable_lin_max()->mutable_exprs(),
926 new_size);
927 changed = true;
928 }
929 }
930
931 if (context_->IsFixed(target)) {
932 context_->UpdateRuleStats("lin_max: fixed affine_max target");
933 return RemoveConstraint(ct);
934 }
935
936 if (target_has_same_unique_var) {
937 context_->UpdateRuleStats("lin_max: target_affine(x) = max(affine_i(x))");
938 return RemoveConstraint(ct);
939 }
940
941 // Remove the affine_max constraint if the target is removable and if domains
942 // have been propagated without loss. For now, we known that there is no loss
943 // if the target is a single ref. Since all the expression are affine, in this
944 // case we are fine.
945 if (ExpressionContainsSingleRef(target) &&
946 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
947 context_->MarkVariableAsRemoved(target.vars(0));
948 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
949 context_->UpdateRuleStats("lin_max: unused affine_max target");
950 return RemoveConstraint(ct);
951 }
952
953 return changed;
954}
955
956bool CpModelPresolver::PropagateAndReduceLinMax(ConstraintProto* ct) {
957 const LinearExpressionProto& target = ct->lin_max().target();
958
959 // Compute the infered min/max of the target.
960 // Update target domain (if it is not a complex expression).
961 {
962 int64_t infered_min = context_->MinOf(target);
963 int64_t infered_max = std::numeric_limits<int64_t>::min();
964 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
965 infered_min = std::max(infered_min, context_->MinOf(expr));
966 infered_max = std::max(infered_max, context_->MaxOf(expr));
967 }
968
969 if (target.vars().empty()) {
970 if (!Domain(infered_min, infered_max).Contains(target.offset())) {
971 context_->UpdateRuleStats("lin_max: infeasible");
972 return MarkConstraintAsFalse(ct);
973 }
974 }
975 if (target.vars().size() <= 1) { // Affine
976 Domain rhs_domain;
977 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
978 rhs_domain = rhs_domain.UnionWith(
979 context_->DomainSuperSetOf(expr).IntersectionWith(
980 {infered_min, infered_max}));
981 }
982 bool reduced = false;
983 if (!context_->IntersectDomainWith(target, rhs_domain, &reduced)) {
984 return true;
985 }
986 if (reduced) {
987 context_->UpdateRuleStats("lin_max: target domain reduced");
988 }
989 }
990 }
991
992 // Filter the expressions which are smaller than target_min.
993 const int64_t target_min = context_->MinOf(target);
994 bool changed = false;
995 {
996 // If one expression is >= target_min,
997 // We can remove all the expression <= target min.
998 //
999 // Note that we must keep an expression >= target_min though, for corner
1000 // case like [2,3] = max([2], [0][3]);
1001 bool has_greater_or_equal_to_target_min = false;
1002 int64_t max_at_index_to_keep = std::numeric_limits<int64_t>::min();
1003 int index_to_keep = -1;
1004 for (int i = 0; i < ct->lin_max().exprs_size(); ++i) {
1005 const LinearExpressionProto& expr = ct->lin_max().exprs(i);
1006 if (context_->MinOf(expr) >= target_min) {
1007 const int64_t expr_max = context_->MaxOf(expr);
1008 if (expr_max > max_at_index_to_keep) {
1009 max_at_index_to_keep = expr_max;
1010 index_to_keep = i;
1011 }
1012 has_greater_or_equal_to_target_min = true;
1013 }
1014 }
1015
1016 int new_size = 0;
1017 for (int i = 0; i < ct->lin_max().exprs_size(); ++i) {
1018 const LinearExpressionProto& expr = ct->lin_max().exprs(i);
1019 const int64_t expr_max = context_->MaxOf(expr);
1020 // TODO(user): Also remove expression whose domain is incompatible with
1021 // the target even if the bounds are like [2] and [0][3]?
1022 if (expr_max < target_min) continue;
1023 if (expr_max == target_min && has_greater_or_equal_to_target_min &&
1024 i != index_to_keep) {
1025 continue;
1026 }
1027 *ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
1028 new_size++;
1029 }
1030 if (new_size < ct->lin_max().exprs_size()) {
1031 context_->UpdateRuleStats("lin_max: removed exprs");
1032 google::protobuf::util::Truncate(ct->mutable_lin_max()->mutable_exprs(),
1033 new_size);
1034 changed = true;
1035 }
1036 }
1037
1038 return changed;
1039}
1040
1041bool CpModelPresolver::PresolveLinMax(ConstraintProto* ct) {
1042 if (context_->ModelIsUnsat()) return false;
1043 if (HasEnforcementLiteral(*ct)) return false;
1044 const LinearExpressionProto& target = ct->lin_max().target();
1045
1046 // x = max(x, xi...) => forall i, x >= xi.
1047 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1048 if (LinearExpressionProtosAreEqual(expr, target)) {
1049 for (const LinearExpressionProto& e : ct->lin_max().exprs()) {
1050 if (LinearExpressionProtosAreEqual(e, target)) continue;
1051 LinearConstraintProto* prec =
1052 context_->working_model->add_constraints()->mutable_linear();
1053 prec->add_domain(0);
1054 prec->add_domain(std::numeric_limits<int64_t>::max());
1057 }
1058 context_->UpdateRuleStats("lin_max: x = max(x, ...)");
1059 return RemoveConstraint(ct);
1060 }
1061 }
1062
1063 const bool is_one_var_affine_max =
1064 ExpressionsContainsOnlyOneVar(ct->lin_max().exprs()) &&
1065 ct->lin_max().target().vars_size() <= 1;
1066 bool unique_var_is_small_enough = false;
1067 const bool is_int_abs = IsAffineIntAbs(*ct);
1068
1069 if (is_one_var_affine_max) {
1070 const int unique_var = GetFirstVar(ct->lin_max().exprs());
1071 unique_var_is_small_enough = context_->DomainOf(unique_var).Size() <= 1000;
1072 }
1073
1074 // This is a test.12y
1075
1076 bool changed;
1077 if (is_one_var_affine_max && unique_var_is_small_enough) {
1078 changed = PropagateAndReduceAffineMax(ct);
1079 } else if (is_int_abs) {
1080 changed = PropagateAndReduceIntAbs(ct);
1081 } else {
1082 changed = PropagateAndReduceLinMax(ct);
1083 }
1084
1085 if (context_->ModelIsUnsat()) return false;
1086 if (ct->constraint_case() != ConstraintProto::kLinMax) {
1087 // The constraint was removed by the propagate helpers.
1088 return changed;
1089 }
1090
1091 if (ct->lin_max().exprs().empty()) {
1092 context_->UpdateRuleStats("lin_max: no exprs");
1093 return MarkConstraintAsFalse(ct);
1094 }
1095
1096 // If only one is left, we can convert to an equality. Note that we create a
1097 // new constraint otherwise it might not be processed again.
1098 if (ct->lin_max().exprs().size() == 1) {
1099 context_->UpdateRuleStats("lin_max: converted to equality");
1100 ConstraintProto* new_ct = context_->working_model->add_constraints();
1101 *new_ct = *ct; // copy name and potential reification.
1102 auto* arg = new_ct->mutable_linear();
1103 const LinearExpressionProto& a = ct->lin_max().target();
1104 const LinearExpressionProto& b = ct->lin_max().exprs(0);
1105 for (int i = 0; i < a.vars().size(); ++i) {
1106 arg->add_vars(a.vars(i));
1107 arg->add_coeffs(a.coeffs(i));
1108 }
1109 for (int i = 0; i < b.vars().size(); ++i) {
1110 arg->add_vars(b.vars(i));
1111 arg->add_coeffs(-b.coeffs(i));
1112 }
1113 arg->add_domain(b.offset() - a.offset());
1114 arg->add_domain(b.offset() - a.offset());
1116 return RemoveConstraint(ct);
1117 }
1118
1119 // Cut everything above the max if possible.
1120 // If one of the linear expression has many term and is above the max, we
1121 // abort early since none of the other rule can be applied.
1122 const int64_t target_min = context_->MinOf(target);
1123 const int64_t target_max = context_->MaxOf(target);
1124 {
1125 bool abort = false;
1126 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1127 const int64_t value_min = context_->MinOf(expr);
1128 bool modified = false;
1129 if (!context_->IntersectDomainWith(expr, Domain(value_min, target_max),
1130 &modified)) {
1131 return true;
1132 }
1133 if (modified) {
1134 context_->UpdateRuleStats("lin_max: reduced expression domain.");
1135 }
1136 const int64_t value_max = context_->MaxOf(expr);
1137 if (value_max > target_max) {
1138 context_->UpdateRuleStats("TODO lin_max: linear expression above max.");
1139 abort = true;
1140 }
1141 }
1142 if (abort) return changed;
1143 }
1144
1145 // Checks if the affine target domain is constraining.
1146 bool affine_target_domain_contains_max_domain = false;
1147 if (ExpressionContainsSingleRef(target)) { // target = +/- var.
1148 int64_t infered_min = std::numeric_limits<int64_t>::min();
1149 int64_t infered_max = std::numeric_limits<int64_t>::min();
1150 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1151 infered_min = std::max(infered_min, context_->MinOf(expr));
1152 infered_max = std::max(infered_max, context_->MaxOf(expr));
1153 }
1154 Domain rhs_domain;
1155 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1156 rhs_domain = rhs_domain.UnionWith(
1157 context_->DomainSuperSetOf(expr).IntersectionWith(
1158 {infered_min, infered_max}));
1159 }
1160
1161 // Checks if all values from the max(exprs) belong in the domain of the
1162 // target.
1163 // Note that the target is +/-var.
1164 DCHECK_EQ(std::abs(target.coeffs(0)), 1);
1165 const Domain target_domain =
1166 target.coeffs(0) == 1 ? context_->DomainOf(target.vars(0))
1167 : context_->DomainOf(target.vars(0)).Negation();
1168 affine_target_domain_contains_max_domain =
1169 rhs_domain.IsIncludedIn(target_domain);
1170 }
1171
1172 // Avoid to remove the constraint for special cases:
1173 // affine(x) = max(expr(x, ...), ...);
1174 //
1175 // TODO(user): We could presolve this, but there are a few type of cases.
1176 // for example:
1177 // - x = max(x + 3, ...) : infeasible.
1178 // - x = max(x - 2, ...) : reduce arity: x = max(...)
1179 // - x = max(2x, ...) we have x <= 0
1180 // - etc...
1181 // Actually, I think for the expr=affine' case, it reduces to:
1182 // affine(x) >= affine'(x)
1183 // affine(x) = max(...);
1184 if (affine_target_domain_contains_max_domain) {
1185 const int target_var = target.vars(0);
1186 bool abort = false;
1187 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1188 for (const int var : expr.vars()) {
1189 if (var == target_var &&
1190 !LinearExpressionProtosAreEqual(expr, target)) {
1191 abort = true;
1192 break;
1193 }
1194 }
1195 if (abort) break;
1196 }
1197 if (abort) {
1198 // Actually the expression can be more than affine.
1199 // We only know that the target is affine here.
1200 context_->UpdateRuleStats(
1201 "TODO lin_max: affine(x) = max(affine'(x), ...) !!");
1202 affine_target_domain_contains_max_domain = false;
1203 }
1204 }
1205
1206 // If the target is not used, and safe, we can remove the constraint.
1207 if (affine_target_domain_contains_max_domain &&
1208 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
1209 context_->UpdateRuleStats("lin_max: unused affine target");
1210 context_->MarkVariableAsRemoved(target.vars(0));
1211 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1212 return RemoveConstraint(ct);
1213 }
1214
1215 // If the target is only used in the objective, and safe, we can simplify the
1216 // constraint.
1217 if (affine_target_domain_contains_max_domain &&
1218 context_->VariableWithCostIsUniqueAndRemovable(target.vars(0)) &&
1219 (target.coeffs(0) > 0) ==
1220 (context_->ObjectiveCoeff(target.vars(0)) > 0)) {
1221 context_->UpdateRuleStats("lin_max: rewrite with precedences");
1222 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1223 LinearConstraintProto* prec =
1224 context_->working_model->add_constraints()->mutable_linear();
1225 prec->add_domain(0);
1226 prec->add_domain(std::numeric_limits<int64_t>::max());
1229 }
1230 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1231 return RemoveConstraint(ct);
1232 }
1233
1234 // Deal with fixed target case.
1235 if (target_min == target_max) {
1236 bool all_booleans = true;
1237 std::vector<int> literals;
1238 const int64_t fixed_target = target_min;
1239 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1240 const int64_t value_min = context_->MinOf(expr);
1241 const int64_t value_max = context_->MaxOf(expr);
1242 CHECK_LE(value_max, fixed_target) << "Presolved above";
1243 if (value_max < fixed_target) continue;
1244
1245 if (value_min == value_max && value_max == fixed_target) {
1246 context_->UpdateRuleStats("lin_max: always satisfied");
1247 return RemoveConstraint(ct);
1248 }
1249 if (context_->ExpressionIsAffineBoolean(expr)) {
1250 CHECK_EQ(value_max, fixed_target);
1251 literals.push_back(context_->LiteralForExpressionMax(expr));
1252 } else {
1253 all_booleans = false;
1254 }
1255 }
1256 if (all_booleans) {
1257 if (literals.empty()) {
1258 return MarkConstraintAsFalse(ct);
1259 }
1260
1261 // At least one true;
1262 context_->UpdateRuleStats("lin_max: fixed target and all booleans");
1263 for (const int lit : literals) {
1264 ct->mutable_bool_or()->add_literals(lit);
1265 }
1266 return true;
1267 }
1268 return changed;
1269 }
1270
1271 changed |= PresolveLinMaxWhenAllBoolean(ct);
1272 return changed;
1273}
1274
1275// If everything is Boolean and affine, do not use a lin max!
1276bool CpModelPresolver::PresolveLinMaxWhenAllBoolean(ConstraintProto* ct) {
1277 if (context_->ModelIsUnsat()) return false;
1278 if (HasEnforcementLiteral(*ct)) return false;
1279
1280 const LinearExpressionProto& target = ct->lin_max().target();
1281 if (!context_->ExpressionIsAffineBoolean(target)) return false;
1282
1283 const int64_t target_min = context_->MinOf(target);
1284 const int64_t target_max = context_->MaxOf(target);
1285 const int target_ref = context_->LiteralForExpressionMax(target);
1286
1287 bool min_is_reachable = false;
1288 std::vector<int> min_literals;
1289 std::vector<int> literals_above_min;
1290 std::vector<int> max_literals;
1291
1292 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1293 if (!context_->ExpressionIsAffineBoolean(expr)) return false;
1294 const int64_t value_min = context_->MinOf(expr);
1295 const int64_t value_max = context_->MaxOf(expr);
1296 const int ref = context_->LiteralForExpressionMax(expr);
1297
1298 // Get corner case out of the way, and wait for the constraint to be
1299 // processed again in these case.
1300 if (value_min > target_min) {
1301 context_->UpdateRuleStats("lin_max: fix target");
1302 (void)context_->SetLiteralToTrue(target_ref);
1303 return false;
1304 }
1305 if (value_max > target_max) {
1306 context_->UpdateRuleStats("lin_max: fix bool expr");
1307 (void)context_->SetLiteralToFalse(ref);
1308 return false;
1309 }
1310
1311 // expr is fixed.
1312 if (value_min == value_max) {
1313 if (value_min == target_min) min_is_reachable = true;
1314 continue;
1315 }
1316
1317 CHECK_LE(value_min, target_min);
1318 if (value_min == target_min) {
1319 min_literals.push_back(NegatedRef(ref));
1320 }
1321
1322 CHECK_LE(value_max, target_max);
1323 if (value_max == target_max) {
1324 max_literals.push_back(ref);
1325 literals_above_min.push_back(ref);
1326 } else if (value_max > target_min) {
1327 literals_above_min.push_back(ref);
1328 } else if (value_max == target_min) {
1329 min_literals.push_back(ref);
1330 }
1331 }
1332
1333 context_->UpdateRuleStats("lin_max: all Booleans.");
1334
1335 // target_ref => at_least_one(max_literals);
1336 ConstraintProto* clause = context_->working_model->add_constraints();
1337 clause->add_enforcement_literal(target_ref);
1338 clause->mutable_bool_or();
1339 for (const int lit : max_literals) {
1340 clause->mutable_bool_or()->add_literals(lit);
1341 }
1342
1343 // not(target_ref) => not(lit) for lit in literals_above_min
1344 for (const int lit : literals_above_min) {
1345 context_->AddImplication(lit, target_ref);
1346 }
1347
1348 if (!min_is_reachable) {
1349 // not(target_ref) => at_least_one(min_literals).
1350 ConstraintProto* clause = context_->working_model->add_constraints();
1351 clause->add_enforcement_literal(NegatedRef(target_ref));
1352 clause->mutable_bool_or();
1353 for (const int lit : min_literals) {
1354 clause->mutable_bool_or()->add_literals(lit);
1355 }
1356 }
1357
1359 return RemoveConstraint(ct);
1360}
1361
1362// This presolve expect that the constraint only contains 1-var affine
1363// expressions.
1364bool CpModelPresolver::PropagateAndReduceIntAbs(ConstraintProto* ct) {
1365 CHECK_EQ(ct->enforcement_literal_size(), 0);
1366 if (context_->ModelIsUnsat()) return false;
1367 const LinearExpressionProto& target_expr = ct->lin_max().target();
1368 const LinearExpressionProto& expr = ct->lin_max().exprs(0);
1369 DCHECK_EQ(expr.vars_size(), 1);
1370
1371 // Propagate domain from the expression to the target.
1372 {
1373 const Domain expr_domain = context_->DomainSuperSetOf(expr);
1374 const Domain new_target_domain =
1375 expr_domain.UnionWith(expr_domain.Negation())
1376 .IntersectionWith({0, std::numeric_limits<int64_t>::max()});
1377 bool target_domain_modified = false;
1378 if (!context_->IntersectDomainWith(target_expr, new_target_domain,
1379 &target_domain_modified)) {
1380 return false;
1381 }
1382 if (expr_domain.IsFixed()) {
1383 context_->UpdateRuleStats("lin_max: fixed expression in int_abs");
1384 return RemoveConstraint(ct);
1385 }
1386 if (target_domain_modified) {
1387 context_->UpdateRuleStats("lin_max: propagate domain from x to abs(x)");
1388 }
1389 }
1390
1391 // Propagate from target domain to variable.
1392 {
1393 const Domain target_domain =
1394 context_->DomainSuperSetOf(target_expr)
1395 .IntersectionWith(Domain(0, std::numeric_limits<int64_t>::max()));
1396 const Domain new_expr_domain =
1397 target_domain.UnionWith(target_domain.Negation());
1398 bool expr_domain_modified = false;
1399 if (!context_->IntersectDomainWith(expr, new_expr_domain,
1400 &expr_domain_modified)) {
1401 return true;
1402 }
1403 // This is the only reason why we don't support fully generic linear
1404 // expression.
1405 if (context_->IsFixed(target_expr)) {
1406 context_->UpdateRuleStats("lin_max: fixed abs target");
1407 return RemoveConstraint(ct);
1408 }
1409 if (expr_domain_modified) {
1410 context_->UpdateRuleStats("lin_max: propagate domain from abs(x) to x");
1411 }
1412 }
1413
1414 // Convert to equality if the sign of expr is fixed.
1415 if (context_->MinOf(expr) >= 0) {
1416 context_->UpdateRuleStats("lin_max: converted abs to equality");
1417 ConstraintProto* new_ct = context_->working_model->add_constraints();
1418 new_ct->set_name(ct->name());
1419 auto* arg = new_ct->mutable_linear();
1420 arg->add_domain(0);
1421 arg->add_domain(0);
1422 AddLinearExpressionToLinearConstraint(target_expr, 1, arg);
1424 CanonicalizeLinear(new_ct);
1426 return RemoveConstraint(ct);
1427 }
1428
1429 if (context_->MaxOf(expr) <= 0) {
1430 context_->UpdateRuleStats("lin_max: converted abs to equality");
1431 ConstraintProto* new_ct = context_->working_model->add_constraints();
1432 new_ct->set_name(ct->name());
1433 auto* arg = new_ct->mutable_linear();
1434 arg->add_domain(0);
1435 arg->add_domain(0);
1436 AddLinearExpressionToLinearConstraint(target_expr, 1, arg);
1438 CanonicalizeLinear(new_ct);
1440 return RemoveConstraint(ct);
1441 }
1442
1443 // Remove the abs constraint if the target is removable and if domains have
1444 // been propagated without loss.
1445 // For now, we known that there is no loss if the target is a single ref.
1446 // Since all the expression are affine, in this case we are fine.
1447 if (ExpressionContainsSingleRef(target_expr) &&
1448 context_->VariableIsUniqueAndRemovable(target_expr.vars(0))) {
1449 context_->MarkVariableAsRemoved(target_expr.vars(0));
1450 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1451 context_->UpdateRuleStats("lin_max: unused abs target");
1452 return RemoveConstraint(ct);
1453 }
1454
1455 return false;
1456}
1457
1458bool CpModelPresolver::PresolveIntProd(ConstraintProto* ct) {
1459 if (context_->ModelIsUnsat()) return false;
1460 if (HasEnforcementLiteral(*ct)) return false;
1461
1462 // Start by restricting the domain of target. We will be more precise later.
1463 bool domain_modified = false;
1464 {
1465 Domain implied(1);
1466 for (const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1467 implied =
1468 implied.ContinuousMultiplicationBy(context_->DomainSuperSetOf(expr));
1469 }
1470 if (!context_->IntersectDomainWith(ct->int_prod().target(), implied,
1471 &domain_modified)) {
1472 return false;
1473 }
1474 }
1475
1476 // Remove constant expressions and compute the product of the max positive
1477 // divisor of each term.
1478 int64_t constant_factor = 1;
1479 int new_size = 0;
1480 bool changed = false;
1481 LinearArgumentProto* proto = ct->mutable_int_prod();
1482 for (int i = 0; i < ct->int_prod().exprs().size(); ++i) {
1483 LinearExpressionProto expr = ct->int_prod().exprs(i);
1484 if (context_->IsFixed(expr)) {
1485 const int64_t expr_value = context_->FixedValue(expr);
1486 constant_factor = CapProd(constant_factor, expr_value);
1487 context_->UpdateRuleStats("int_prod: removed constant expressions.");
1488 changed = true;
1489 } else {
1490 const int64_t expr_divisor = LinearExpressionGcd(expr);
1491 DivideLinearExpression(expr_divisor, &expr);
1492 constant_factor = CapProd(constant_factor, expr_divisor);
1493 *proto->mutable_exprs(new_size++) = expr;
1494 }
1495 }
1496 proto->mutable_exprs()->erase(proto->mutable_exprs()->begin() + new_size,
1497 proto->mutable_exprs()->end());
1498
1499 if (ct->int_prod().exprs().empty() || constant_factor == 0) {
1500 if (!context_->IntersectDomainWith(ct->int_prod().target(),
1501 Domain(constant_factor))) {
1502 return false;
1503 }
1504 context_->UpdateRuleStats("int_prod: constant product");
1505 return RemoveConstraint(ct);
1506 }
1507
1508 // If target is fixed to zero, we can forget the constant factor.
1509 if (context_->IsFixed(ct->int_prod().target()) &&
1510 context_->FixedValue(ct->int_prod().target()) == 0 &&
1511 constant_factor != 1) {
1512 context_->UpdateRuleStats("int_prod: simplify by constant factor");
1513 constant_factor = 1;
1514 }
1515
1516 // In this case, the only possible value that fit in the domains is zero.
1517 // We will check for UNSAT if zero is not achievable by the rhs below.
1518 if (AtMinOrMaxInt64(constant_factor)) {
1519 context_->UpdateRuleStats("int_prod: overflow if non zero");
1520 if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(0))) {
1521 return false;
1522 }
1523 constant_factor = 1;
1524 }
1525
1526 // Replace by linear if it cannot overflow.
1527 if (ct->int_prod().exprs().size() == 1) {
1528 LinearExpressionProto* const target =
1529 ct->mutable_int_prod()->mutable_target();
1530 LinearConstraintProto* const lin =
1531 context_->working_model->add_constraints()->mutable_linear();
1532
1533 if (context_->IsFixed(*target)) {
1534 int64_t target_value = context_->FixedValue(*target);
1535 if (target_value % constant_factor != 0) {
1536 return context_->NotifyThatModelIsUnsat(
1537 "int_prod: product incompatible with fixed target");
1538 }
1539 // expression == target_value / constant_factor.
1540 lin->add_domain(target_value / constant_factor);
1541 lin->add_domain(target_value / constant_factor);
1542 AddLinearExpressionToLinearConstraint(ct->int_prod().exprs(0), 1, lin);
1544 context_->UpdateRuleStats("int_prod: expression is constant.");
1545 return RemoveConstraint(ct);
1546 }
1547
1548 const int64_t target_divisor = LinearExpressionGcd(*target);
1549
1550 // Reduce coefficients.
1551 const int64_t gcd =
1552 MathUtil::GCD64(static_cast<uint64_t>(std::abs(constant_factor)),
1553 static_cast<uint64_t>(std::abs(target_divisor)));
1554 if (gcd != 1) {
1555 constant_factor /= gcd;
1556 DivideLinearExpression(gcd, target);
1557 }
1558
1559 // expression * constant_factor = target.
1560 lin->add_domain(0);
1561 lin->add_domain(0);
1562 const bool overflow = !SafeAddLinearExpressionToLinearConstraint(
1563 ct->int_prod().target(), 1, lin) ||
1565 ct->int_prod().exprs(0), -constant_factor, lin);
1566
1567 // Check for overflow.
1568 if (overflow ||
1569 PossibleIntegerOverflow(*context_->working_model, lin->vars(),
1570 lin->coeffs(), lin->domain(0))) {
1571 context_->working_model->mutable_constraints()->RemoveLast();
1572 // Re-add a new term with the constant factor.
1573 ct->mutable_int_prod()->add_exprs()->set_offset(constant_factor);
1574 } else { // Replace with a linear equation.
1576 context_->UpdateRuleStats("int_prod: linearize product by constant.");
1577 return RemoveConstraint(ct);
1578 }
1579 }
1580
1581 if (constant_factor != 1) {
1582 // Lets canonicalize the target by introducing a new variable if necessary.
1583 //
1584 // coeff * X + offset must be a multiple of constant_factor, so
1585 // we can rewrite X so that this property is clear.
1586 //
1587 // Note(user): it is important for this to have a restricted target domain
1588 // so we can choose a better representative.
1589 const LinearExpressionProto old_target = ct->int_prod().target();
1590 if (!context_->IsFixed(old_target)) {
1591 const int ref = old_target.vars(0);
1592 const int64_t coeff = old_target.coeffs(0);
1593 const int64_t offset = old_target.offset();
1594 if (!context_->CanonicalizeAffineVariable(ref, coeff, constant_factor,
1595 -offset)) {
1596 return false;
1597 }
1598 if (context_->IsFixed(ref)) {
1599 changed = true;
1600 }
1601 }
1602
1603 // This can happen during CanonicalizeAffineVariable().
1604 if (context_->IsFixed(old_target)) {
1605 const int64_t target_value = context_->FixedValue(old_target);
1606 if (target_value % constant_factor != 0) {
1607 return context_->NotifyThatModelIsUnsat(
1608 "int_prod: constant factor does not divide constant target");
1609 }
1610 changed = true;
1611 proto->clear_target();
1612 proto->mutable_target()->set_offset(target_value / constant_factor);
1613 context_->UpdateRuleStats(
1614 "int_prod: divide product and fixed target by constant factor");
1615 } else {
1616 // We use absl::int128 to be resistant to overflow here.
1617 const AffineRelation::Relation r =
1618 context_->GetAffineRelation(old_target.vars(0));
1619 const absl::int128 temp_coeff =
1620 absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff);
1621 CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0);
1622 const absl::int128 temp_offset =
1623 absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) +
1624 absl::int128(old_target.offset());
1625 CHECK_EQ(temp_offset % absl::int128(constant_factor), 0);
1626 const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor);
1627 const absl::int128 new_offset =
1628 temp_offset / absl::int128(constant_factor);
1629
1630 // TODO(user): We try to keep coeff/offset small, if this happens, it
1631 // probably means there is no feasible solution involving int64_t and that
1632 // do not causes overflow while evaluating it, but it is hard to be
1633 // exactly sure we are correct here since it depends on the evaluation
1634 // order. Similarly, by introducing intermediate variable we might loose
1635 // solution if this intermediate variable value do not fit on an int64_t.
1636 if (new_coeff > absl::int128(std::numeric_limits<int64_t>::max()) ||
1637 new_coeff < absl::int128(std::numeric_limits<int64_t>::min()) ||
1638 new_offset > absl::int128(std::numeric_limits<int64_t>::max()) ||
1639 new_offset < absl::int128(std::numeric_limits<int64_t>::min())) {
1640 return context_->NotifyThatModelIsUnsat(
1641 "int_prod: overflow during simplification.");
1642 }
1643
1644 // Rewrite the target.
1645 proto->mutable_target()->set_coeffs(0, static_cast<int64_t>(new_coeff));
1646 proto->mutable_target()->set_vars(0, r.representative);
1647 proto->mutable_target()->set_offset(static_cast<int64_t>(new_offset));
1648 context_->UpdateRuleStats("int_prod: divide product by constant factor");
1649 changed = true;
1650 }
1651 }
1652
1653 // Restrict the target domain if possible.
1654 Domain implied(1);
1655 bool is_square = false;
1656 if (ct->int_prod().exprs_size() == 2 &&
1657 LinearExpressionProtosAreEqual(ct->int_prod().exprs(0),
1658 ct->int_prod().exprs(1))) {
1659 is_square = true;
1660 implied =
1661 context_->DomainSuperSetOf(ct->int_prod().exprs(0)).SquareSuperset();
1662 } else {
1663 for (const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1664 implied =
1665 implied.ContinuousMultiplicationBy(context_->DomainSuperSetOf(expr));
1666 }
1667 }
1668 if (!context_->IntersectDomainWith(ct->int_prod().target(), implied,
1669 &domain_modified)) {
1670 return false;
1671 }
1672 if (domain_modified) {
1673 context_->UpdateRuleStats(absl::StrCat(
1674 is_square ? "int_square" : "int_prod", ": reduced target domain."));
1675 }
1676
1677 // y = x * x, we can reduce the domain of x from the domain of y.
1678 if (is_square) {
1679 const int64_t target_max = context_->MaxOf(ct->int_prod().target());
1680 DCHECK_GE(target_max, 0);
1681 const int64_t sqrt_max = FloorSquareRoot(target_max);
1682 bool expr_reduced = false;
1683 if (!context_->IntersectDomainWith(ct->int_prod().exprs(0),
1684 {-sqrt_max, sqrt_max}, &expr_reduced)) {
1685 return false;
1686 }
1687 if (expr_reduced) {
1688 context_->UpdateRuleStats("int_square: reduced expr domain.");
1689 }
1690 }
1691
1692 if (ct->int_prod().exprs_size() == 2) {
1693 LinearExpressionProto a = ct->int_prod().exprs(0);
1694 LinearExpressionProto b = ct->int_prod().exprs(1);
1695 const LinearExpressionProto product = ct->int_prod().target();
1698 a, product)) { // x = x * x, only true for {0, 1}.
1699 if (!context_->IntersectDomainWith(product, Domain(0, 1))) {
1700 return false;
1701 }
1702 context_->UpdateRuleStats("int_square: fix variable to zero or one.");
1703 return RemoveConstraint(ct);
1704 }
1705 }
1706
1707 // For now, we only presolve the case where all variables are Booleans.
1708 const LinearExpressionProto target_expr = ct->int_prod().target();
1709 int target;
1710 if (!context_->ExpressionIsALiteral(target_expr, &target)) {
1711 return changed;
1712 }
1713 std::vector<int> literals;
1714 for (const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1715 int lit;
1716 if (!context_->ExpressionIsALiteral(expr, &lit)) {
1717 return changed;
1718 }
1719 literals.push_back(lit);
1720 }
1721
1722 // This is a Boolean constraint!
1723 context_->UpdateRuleStats("int_prod: all Boolean.");
1724 {
1725 ConstraintProto* new_ct = context_->working_model->add_constraints();
1726 new_ct->add_enforcement_literal(target);
1727 auto* arg = new_ct->mutable_bool_and();
1728 for (const int lit : literals) {
1729 arg->add_literals(lit);
1730 }
1731 }
1732 {
1733 ConstraintProto* new_ct = context_->working_model->add_constraints();
1734 auto* arg = new_ct->mutable_bool_or();
1735 arg->add_literals(target);
1736 for (const int lit : literals) {
1737 arg->add_literals(NegatedRef(lit));
1738 }
1739 }
1741 return RemoveConstraint(ct);
1742}
1743
1744bool CpModelPresolver::PresolveIntDiv(int c, ConstraintProto* ct) {
1745 if (context_->ModelIsUnsat()) return false;
1746
1747 const LinearExpressionProto target = ct->int_div().target();
1748 const LinearExpressionProto expr = ct->int_div().exprs(0);
1749 const LinearExpressionProto div = ct->int_div().exprs(1);
1750
1751 if (LinearExpressionProtosAreEqual(expr, div)) {
1752 if (!context_->IntersectDomainWith(target, Domain(1))) {
1753 return false;
1754 }
1755 context_->UpdateRuleStats("int_div: y = x / x");
1756 return RemoveConstraint(ct);
1757 } else if (LinearExpressionProtosAreEqual(expr, div, -1)) {
1758 if (!context_->IntersectDomainWith(target, Domain(-1))) {
1759 return false;
1760 }
1761 context_->UpdateRuleStats("int_div: y = - x / x");
1762 return RemoveConstraint(ct);
1763 }
1764
1765 // Sometimes we have only a single variable appearing in the whole constraint.
1766 // If the domain is small enough, we can just restrict the domain and remove
1767 // the constraint.
1768 if (ct->enforcement_literal().empty() &&
1769 context_->ConstraintToVars(c).size() == 1) {
1770 const int var = context_->ConstraintToVars(c)[0];
1771 if (context_->DomainOf(var).Size() >= 100) {
1772 context_->UpdateRuleStats(
1773 "TODO int_div: single variable with large domain");
1774 } else {
1775 std::vector<int64_t> possible_values;
1776 for (const int64_t v : context_->DomainOf(var).Values()) {
1777 const int64_t target_v =
1778 EvaluateSingleVariableExpression(target, var, v);
1779 const int64_t expr_v = EvaluateSingleVariableExpression(expr, var, v);
1780 const int64_t div_v = EvaluateSingleVariableExpression(div, var, v);
1781 if (div_v == 0) continue;
1782 if (target_v == expr_v / div_v) {
1783 possible_values.push_back(v);
1784 }
1785 }
1786 (void)context_->IntersectDomainWith(var,
1787 Domain::FromValues(possible_values));
1788 context_->UpdateRuleStats("int_div: single variable");
1789 return RemoveConstraint(ct);
1790 }
1791 }
1792
1793 // For now, we only presolve the case where the divisor is constant.
1794 if (!context_->IsFixed(div)) return false;
1795
1796 const int64_t divisor = context_->FixedValue(div);
1797
1798 // Trivial case one: target = expr / +/-1.
1799 if (divisor == 1 || divisor == -1) {
1800 LinearConstraintProto* const lin =
1801 context_->working_model->add_constraints()->mutable_linear();
1802 lin->add_domain(0);
1803 lin->add_domain(0);
1805 AddLinearExpressionToLinearConstraint(target, -divisor, lin);
1807 context_->UpdateRuleStats("int_div: rewrite to equality");
1808 return RemoveConstraint(ct);
1809 }
1810
1811 // Reduce the domain of target.
1812 {
1813 bool domain_modified = false;
1814 const Domain target_implied_domain =
1815 context_->DomainSuperSetOf(expr).DivisionBy(divisor);
1816
1817 if (!context_->IntersectDomainWith(target, target_implied_domain,
1818 &domain_modified)) {
1819 return false;
1820 }
1821 if (domain_modified) {
1822 // Note: the case target is fixed has been processed before.
1823 if (target_implied_domain.IsFixed()) {
1824 context_->UpdateRuleStats(
1825 "int_div: target has been fixed by propagating X / cte");
1826 } else {
1827 context_->UpdateRuleStats(
1828 "int_div: updated domain of target in target = X / cte");
1829 }
1830 }
1831 }
1832
1833 // Trivial case three: fixed_target = expr / fixed_divisor.
1834 if (context_->IsFixed(target) &&
1835 CapAdd(1, CapProd(std::abs(divisor),
1836 1 + std::abs(context_->FixedValue(target)))) !=
1837 std::numeric_limits<int64_t>::max()) {
1838 int64_t t = context_->FixedValue(target);
1839 int64_t d = divisor;
1840 if (d < 0) {
1841 t = -t;
1842 d = -d;
1843 }
1844
1845 const Domain expr_implied_domain =
1846 t > 0
1847 ? Domain(t * d, (t + 1) * d - 1)
1848 : (t == 0 ? Domain(1 - d, d - 1) : Domain((t - 1) * d + 1, t * d));
1849 bool domain_modified = false;
1850 if (!context_->IntersectDomainWith(expr, expr_implied_domain,
1851 &domain_modified)) {
1852 return false;
1853 }
1854 if (domain_modified) {
1855 context_->UpdateRuleStats("int_div: target and divisor are fixed");
1856 } else {
1857 context_->UpdateRuleStats("int_div: always true");
1858 }
1859 return RemoveConstraint(ct);
1860 }
1861
1862 // Linearize if everything is positive, and we have no overflow.
1863 // TODO(user): Deal with other cases where there is no change of
1864 // sign. We can also deal with target = cte, div variable.
1865 if (context_->MinOf(target) >= 0 && context_->MinOf(expr) >= 0 &&
1866 divisor > 1 &&
1867 CapProd(divisor, context_->MaxOf(target)) !=
1868 std::numeric_limits<int64_t>::max()) {
1869 LinearConstraintProto* const lin =
1870 context_->working_model->add_constraints()->mutable_linear();
1871 lin->add_domain(0);
1872 lin->add_domain(divisor - 1);
1874 AddLinearExpressionToLinearConstraint(target, -divisor, lin);
1876 context_->UpdateRuleStats(
1877 "int_div: linearize positive division with a constant divisor");
1878
1879 return RemoveConstraint(ct);
1880 }
1881
1882 // TODO(user): reduce the domain of X by introducing an
1883 // InverseDivisionOfSortedDisjointIntervals().
1884 return false;
1885}
1886
1887bool CpModelPresolver::PresolveIntMod(int c, ConstraintProto* ct) {
1888 if (context_->ModelIsUnsat()) return false;
1889
1890 // TODO(user): Presolve f(X) = g(X) % fixed_mod.
1891 const LinearExpressionProto target = ct->int_mod().target();
1892 const LinearExpressionProto expr = ct->int_mod().exprs(0);
1893 const LinearExpressionProto mod = ct->int_mod().exprs(1);
1894
1895 if (context_->MinOf(target) > 0) {
1896 bool domain_changed = false;
1897 if (!context_->IntersectDomainWith(
1898 expr, Domain(0, std::numeric_limits<int64_t>::max()),
1899 &domain_changed)) {
1900 return false;
1901 }
1902 if (domain_changed) {
1903 context_->UpdateRuleStats(
1904 "int_mod: non negative target implies positive expression");
1905 }
1906 }
1907
1908 if (context_->MinOf(target) >= context_->MaxOf(mod) ||
1909 context_->MaxOf(target) <= -context_->MaxOf(mod)) {
1910 return context_->NotifyThatModelIsUnsat(
1911 "int_mod: incompatible target and mod");
1912 }
1913
1914 if (context_->MaxOf(target) < 0) {
1915 bool domain_changed = false;
1916 if (!context_->IntersectDomainWith(
1917 expr, Domain(std::numeric_limits<int64_t>::min(), 0),
1918 &domain_changed)) {
1919 return false;
1920 }
1921 if (domain_changed) {
1922 context_->UpdateRuleStats(
1923 "int_mod: non positive target implies negative expression");
1924 }
1925 }
1926
1927 if (context_->IsFixed(target) && context_->IsFixed(mod) &&
1928 context_->FixedValue(mod) > 1 && ct->enforcement_literal().empty() &&
1929 expr.vars().size() == 1) {
1930 // We can intersect the domain of expr with {k * mod + target}.
1931 const int64_t fixed_mod = context_->FixedValue(mod);
1932 const int64_t fixed_target = context_->FixedValue(target);
1933
1934 if (!context_->CanonicalizeAffineVariable(expr.vars(0), expr.coeffs(0),
1935 fixed_mod,
1936 fixed_target - expr.offset())) {
1937 return false;
1938 }
1939
1940 context_->UpdateRuleStats("int_mod: fixed mod and target");
1941 return RemoveConstraint(ct);
1942 }
1943
1944 bool domain_changed = false;
1945 if (!context_->IntersectDomainWith(
1946 target,
1948 context_->DomainSuperSetOf(mod)),
1949 &domain_changed)) {
1950 return false;
1951 }
1952
1953 if (domain_changed) {
1954 context_->UpdateRuleStats("int_mod: reduce target domain");
1955 }
1956
1957 // Remove the constraint if the target is removable.
1958 // This is triggered on the flatzinc rotating-workforce problems.
1959 //
1960 // TODO(user): We can deal with more cases, sometime even if the domain of
1961 // expr.vars(0) is large, the implied domain is not too complex.
1962 if (target.vars().size() == 1 && expr.vars().size() == 1 &&
1963 context_->DomainOf(expr.vars(0)).Size() < 100 && context_->IsFixed(mod) &&
1964 context_->VariableIsUniqueAndRemovable(target.vars(0)) &&
1965 target.vars(0) != expr.vars(0)) {
1966 const int64_t fixed_mod = context_->FixedValue(mod);
1967 std::vector<int64_t> values;
1968 const Domain dom = context_->DomainOf(target.vars(0));
1969 for (const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
1970 const int64_t rhs = (v * expr.coeffs(0) + expr.offset()) % fixed_mod;
1971 const int64_t target_term = rhs - target.offset();
1972 if (target_term % target.coeffs(0) != 0) continue;
1973 if (dom.Contains(target_term / target.coeffs(0))) {
1974 values.push_back(v);
1975 }
1976 }
1977
1978 context_->UpdateRuleStats("int_mod: remove singleton target");
1979 if (!context_->IntersectDomainWith(expr.vars(0),
1980 Domain::FromValues(values))) {
1981 return false;
1982 }
1983 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1984 ct->Clear();
1985 context_->UpdateConstraintVariableUsage(c);
1986 context_->MarkVariableAsRemoved(target.vars(0));
1987 return true;
1988 }
1989
1990 return false;
1991}
1992
1993// TODO(user): Now that everything has affine relations, we should maybe
1994// canonicalize all linear subexpression in a generic way.
1995bool CpModelPresolver::ExploitEquivalenceRelations(int c, ConstraintProto* ct) {
1996 bool changed = false;
1997
1998 // Optim: Special case for the linear constraint. We just remap the
1999 // enforcement literals, the normal variables will be replaced by their
2000 // representative in CanonicalizeLinear().
2001 if (ct->constraint_case() == ConstraintProto::kLinear) {
2002 for (int& ref : *ct->mutable_enforcement_literal()) {
2003 const int rep = this->context_->GetLiteralRepresentative(ref);
2004 if (rep != ref) {
2005 changed = true;
2006 ref = rep;
2007 }
2008 }
2009 return changed;
2010 }
2011
2012 // Optim: This extra loop is a lot faster than reparsing the variable from the
2013 // proto when there is nothing to do, which is quite often.
2014 bool work_to_do = false;
2015 for (const int var : context_->ConstraintToVars(c)) {
2016 const AffineRelation::Relation r = context_->GetAffineRelation(var);
2017 if (r.representative != var) {
2018 work_to_do = true;
2019 break;
2020 }
2021 }
2022 if (!work_to_do) return false;
2023
2024 // Remap literal and negated literal to their representative.
2026 [&changed, this](int* ref) {
2027 const int rep = this->context_->GetLiteralRepresentative(*ref);
2028 if (rep != *ref) {
2029 changed = true;
2030 *ref = rep;
2031 }
2032 },
2033 ct);
2034 return changed;
2035}
2036
2037bool CpModelPresolver::DivideLinearByGcd(ConstraintProto* ct) {
2038 if (context_->ModelIsUnsat()) return false;
2039
2040 // Compute the GCD of all coefficients.
2041 int64_t gcd = 0;
2042 const int num_vars = ct->linear().vars().size();
2043 for (int i = 0; i < num_vars; ++i) {
2044 const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2045 gcd = MathUtil::GCD64(gcd, magnitude);
2046 if (gcd == 1) break;
2047 }
2048 if (gcd > 1) {
2049 context_->UpdateRuleStats("linear: divide by GCD");
2050 for (int i = 0; i < num_vars; ++i) {
2051 ct->mutable_linear()->set_coeffs(i, ct->linear().coeffs(i) / gcd);
2052 }
2053 const Domain rhs = ReadDomainFromProto(ct->linear());
2054 FillDomainInProto(rhs.InverseMultiplicationBy(gcd), ct->mutable_linear());
2055 if (ct->linear().domain_size() == 0) {
2056 return MarkConstraintAsFalse(ct);
2057 }
2058 }
2059 return false;
2060}
2061
2062bool CpModelPresolver::CanonicalizeLinearExpression(
2063 const ConstraintProto& ct, LinearExpressionProto* exp) {
2064 return context_->CanonicalizeLinearExpression(ct.enforcement_literal(), exp);
2065}
2066
2067bool CpModelPresolver::CanonicalizeLinear(ConstraintProto* ct) {
2068 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
2069 if (context_->ModelIsUnsat()) return false;
2070
2071 if (ct->linear().domain().empty()) {
2072 context_->UpdateRuleStats("linear: no domain");
2073 return MarkConstraintAsFalse(ct);
2074 }
2075
2076 bool changed = context_->CanonicalizeLinearConstraint(ct);
2077 changed |= DivideLinearByGcd(ct);
2078
2079 // For duplicate detection, we always make the first coeff positive.
2080 //
2081 // TODO(user): Move that to context_->CanonicalizeLinearConstraint(), and do
2082 // the same for LinearExpressionProto.
2083 if (!ct->linear().coeffs().empty() && ct->linear().coeffs(0) < 0) {
2084 for (int64_t& ref_coeff : *ct->mutable_linear()->mutable_coeffs()) {
2085 ref_coeff = -ref_coeff;
2086 }
2088 ct->mutable_linear());
2089 }
2090
2091 return changed;
2092}
2093
2094bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto* ct) {
2095 if (ct->constraint_case() != ConstraintProto::kLinear ||
2096 context_->ModelIsUnsat()) {
2097 return false;
2098 }
2099
2100 absl::btree_set<int> index_to_erase;
2101 const int num_vars = ct->linear().vars().size();
2102 Domain rhs = ReadDomainFromProto(ct->linear());
2103
2104 // First pass. Process singleton column that are not in the objective. Note
2105 // that for postsolve, it is important that we process them in the same order
2106 // in which they will be removed.
2107 for (int i = 0; i < num_vars; ++i) {
2108 const int var = ct->linear().vars(i);
2109 const int64_t coeff = ct->linear().coeffs(i);
2110 CHECK(RefIsPositive(var));
2111 if (context_->VariableIsUniqueAndRemovable(var)) {
2112 // This is not needed for the code below, but in practice, removing
2113 // singleton with a large coefficient create holes in the constraint rhs
2114 // and we will need to add more variable to deal with that.
2115 // This works way better on timtab1CUTS.pb.gz for instance.
2116 if (std::abs(coeff) != 1) continue;
2117
2118 bool exact;
2119 const auto term_domain =
2120 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2121 if (!exact) continue;
2122
2123 // We do not do that if the domain of rhs becomes too complex.
2124 const Domain new_rhs = rhs.AdditionWith(term_domain);
2125 if (new_rhs.NumIntervals() > 100) continue;
2126
2127 // Note that we can't do that if we loose information in the
2128 // multiplication above because the new domain might not be as strict
2129 // as the initial constraint otherwise. TODO(user): because of the
2130 // addition, it might be possible to cover more cases though.
2131 context_->UpdateRuleStats("linear: singleton column");
2132 index_to_erase.insert(i);
2133 rhs = new_rhs;
2134 continue;
2135 }
2136 }
2137
2138 // If the whole linear is independent from the rest of the problem, we
2139 // can solve it now. If it is enforced, then each variable will have two
2140 // values: Its minimum one and one minimizing the objective under the
2141 // constraint. The switch can be controlled by a single Boolean.
2142 //
2143 // TODO(user): Cover more case like dedicated algorithm to solve for a small
2144 // number of variable that are faster than the DP we use here.
2145 if (index_to_erase.empty()) {
2146 int num_singletons = 0;
2147 for (const int var : ct->linear().vars()) {
2148 if (!RefIsPositive(var)) break;
2149 if (!context_->VariableWithCostIsUniqueAndRemovable(var) &&
2150 !context_->VariableIsUniqueAndRemovable(var)) {
2151 break;
2152 }
2153 ++num_singletons;
2154 }
2155 if (num_singletons == num_vars) {
2156 // Try to solve the equation.
2157 std::vector<Domain> domains;
2158 std::vector<int64_t> coeffs;
2159 std::vector<int64_t> costs;
2160 for (int i = 0; i < num_vars; ++i) {
2161 const int var = ct->linear().vars(i);
2162 CHECK(RefIsPositive(var));
2163 domains.push_back(context_->DomainOf(var));
2164 coeffs.push_back(ct->linear().coeffs(i));
2165 costs.push_back(context_->ObjectiveCoeff(var));
2166 }
2167 BasicKnapsackSolver solver;
2168 const auto& result = solver.Solve(domains, coeffs, costs,
2169 ReadDomainFromProto(ct->linear()));
2170 if (!result.solved) {
2171 context_->UpdateRuleStats(
2172 "TODO independent linear: minimize single linear constraint");
2173 } else if (result.infeasible) {
2174 context_->UpdateRuleStats(
2175 "independent linear: no DP solution to simple constraint");
2176 return MarkConstraintAsFalse(ct);
2177 } else {
2178 if (ct->enforcement_literal().empty()) {
2179 // Just fix everything.
2180 context_->UpdateRuleStats("independent linear: solved by DP");
2181 for (int i = 0; i < num_vars; ++i) {
2182 if (!context_->IntersectDomainWith(ct->linear().vars(i),
2183 Domain(result.solution[i]))) {
2184 return false;
2185 }
2186 }
2187 return RemoveConstraint(ct);
2188 }
2189
2190 // Each variable will take two values according to a single Boolean.
2191 int indicator;
2192 if (ct->enforcement_literal().size() == 1) {
2193 indicator = ct->enforcement_literal(0);
2194 } else {
2195 indicator = context_->NewBoolVar();
2196 auto* new_ct = context_->working_model->add_constraints();
2197 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2198 new_ct->mutable_bool_or()->add_literals(indicator);
2200 }
2201 for (int i = 0; i < num_vars; ++i) {
2202 const int64_t best_value =
2203 costs[i] > 0 ? domains[i].Min() : domains[i].Max();
2204 const int64_t other_value = result.solution[i];
2205 if (best_value == other_value) {
2206 if (!context_->IntersectDomainWith(ct->linear().vars(i),
2207 Domain(best_value))) {
2208 return false;
2209 }
2210 continue;
2211 }
2212 if (RefIsPositive(indicator)) {
2213 if (!context_->StoreAffineRelation(ct->linear().vars(i), indicator,
2214 other_value - best_value,
2215 best_value)) {
2216 return false;
2217 }
2218 } else {
2219 if (!context_->StoreAffineRelation(
2220 ct->linear().vars(i), PositiveRef(indicator),
2221 best_value - other_value, other_value)) {
2222 return false;
2223 }
2224 }
2225 }
2226 context_->UpdateRuleStats(
2227 "independent linear: with enforcement, but solved by DP");
2228 return RemoveConstraint(ct);
2229 }
2230 }
2231 }
2232
2233 // If we didn't find any, look for the one appearing in the objective.
2234 if (index_to_erase.empty()) {
2235 // Note that we only do that if we have a non-reified equality.
2236 if (context_->params().presolve_substitution_level() <= 0) return false;
2237 if (!ct->enforcement_literal().empty()) return false;
2238
2239 // If it is possible to do so, note that we can transform constraint into
2240 // equalities in PropagateDomainsInLinear().
2241 if (rhs.Min() != rhs.Max()) return false;
2242
2243 for (int i = 0; i < num_vars; ++i) {
2244 const int var = ct->linear().vars(i);
2245 const int64_t coeff = ct->linear().coeffs(i);
2246 CHECK(RefIsPositive(var));
2247
2248 // If the variable appear only in the objective and we have an equality,
2249 // we can transfer the cost to the rest of the linear expression, and
2250 // remove that variable. Note that this do not remove any feasible
2251 // solution and is not a "dual" reduction.
2252 //
2253 // Note that is similar to the substitution code in PresolveLinear() but
2254 // it doesn't require the variable to be implied free since we do not
2255 // remove the constraints afterwards, just the variable.
2256 if (!context_->VariableWithCostIsUnique(var)) continue;
2257 DCHECK(context_->ObjectiveMap().contains(var));
2258
2259 // We only support substitution that does not require to multiply the
2260 // objective by some factor.
2261 //
2262 // TODO(user): If the objective is a single variable, we can actually
2263 // "absorb" any factor into the objective scaling.
2264 const int64_t objective_coeff = context_->ObjectiveMap().at(var);
2265 CHECK_NE(coeff, 0);
2266 if (objective_coeff % coeff != 0) continue;
2267
2268 // TODO(user): We have an issue if objective coeff is not one, because
2269 // the RecomputeSingletonObjectiveDomain() do not properly put holes
2270 // in the objective domain, which might cause an issue. Note that this
2271 // presolve rule is actually almost never applied on the miplib.
2272 if (std::abs(objective_coeff) != 1) continue;
2273
2274 // We do not do that if the domain of rhs becomes too complex.
2275 bool exact;
2276 const auto term_domain =
2277 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2278 if (!exact) continue;
2279 const Domain new_rhs = rhs.AdditionWith(term_domain);
2280 if (new_rhs.NumIntervals() > 100) continue;
2281
2282 // Special case: If the objective was a single variable, we can transfer
2283 // the domain of var to the objective, and just completely remove this
2284 // equality constraint.
2285 //
2286 // TODO(user): Maybe if var has a complex domain, we might not want to
2287 // substitute it?
2288 if (context_->ObjectiveMap().size() == 1) {
2289 // This make sure the domain of var is restricted and the objective
2290 // domain updated.
2291 if (!context_->RecomputeSingletonObjectiveDomain()) {
2292 return true;
2293 }
2294
2295 // The function above might fix var, in which case, we just abort.
2296 if (context_->IsFixed(var)) continue;
2297
2298 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2299 if (context_->ModelIsUnsat()) return true;
2300 continue;
2301 }
2302
2303 context_->UpdateRuleStats("linear: singleton column define objective.");
2304 context_->MarkVariableAsRemoved(var);
2305 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2306 return RemoveConstraint(ct);
2307 }
2308
2309 // On supportcase20, this transformation make the LP relaxation way worse.
2310 // TODO(user): understand why.
2311 if (true) continue;
2312
2313 // Update the objective and remove the variable from its equality
2314 // constraint by expanding its rhs. This might fail if the new linear
2315 // objective expression can lead to overflow.
2316 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2317 if (context_->ModelIsUnsat()) return true;
2318 continue;
2319 }
2320
2321 context_->UpdateRuleStats(
2322 "linear: singleton column in equality and in objective.");
2323 rhs = new_rhs;
2324 index_to_erase.insert(i);
2325 break;
2326 }
2327 }
2328 if (index_to_erase.empty()) return false;
2329
2330 // Tricky: If we have a singleton variable in an enforced constraint, and at
2331 // postsolve the enforcement is false, we might just ignore the constraint.
2332 // This is fine, but we still need to assign any removed variable to a
2333 // feasible value, otherwise later postsolve rules might not work correctly.
2334 // Adding these linear1 achieve that.
2335 //
2336 // TODO(user): Alternatively, we could copy the constraint without the
2337 // enforcement to the mapping model, since singleton variable are supposed
2338 // to always have a feasible value anyway.
2339 if (!ct->enforcement_literal().empty()) {
2340 for (const int i : index_to_erase) {
2341 const int var = ct->linear().vars(i);
2342 auto* new_lin =
2343 context_->NewMappingConstraint(__FILE__, __LINE__)->mutable_linear();
2344 new_lin->add_vars(var);
2345 new_lin->add_coeffs(1);
2346 FillDomainInProto(context_->DomainOf(var), new_lin);
2347 }
2348 }
2349
2350 // TODO(user): we could add the constraint to mapping_model only once
2351 // instead of adding a reduced version of it each time a new singleton
2352 // variable appear in the same constraint later. That would work but would
2353 // also force the postsolve to take search decisions...
2354 *context_->NewMappingConstraint(__FILE__, __LINE__) = *ct;
2355
2356 int new_size = 0;
2357 for (int i = 0; i < num_vars; ++i) {
2358 if (index_to_erase.count(i)) {
2359 context_->MarkVariableAsRemoved(ct->linear().vars(i));
2360 continue;
2361 }
2362 ct->mutable_linear()->set_coeffs(new_size, ct->linear().coeffs(i));
2363 ct->mutable_linear()->set_vars(new_size, ct->linear().vars(i));
2364 ++new_size;
2365 }
2366 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
2367 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
2368 FillDomainInProto(rhs, ct->mutable_linear());
2369 DivideLinearByGcd(ct);
2370 return true;
2371}
2372
2373// If the gcd of all but one term (with index target_index) is not one, we can
2374// rewrite the last term using an affine representative.
2375bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality(
2376 int target_index, ConstraintProto* ct) {
2377 int64_t gcd = 0;
2378 const int num_variables = ct->linear().vars().size();
2379 for (int i = 0; i < num_variables; ++i) {
2380 if (i == target_index) continue;
2381 const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2382 gcd = MathUtil::GCD64(gcd, magnitude);
2383 if (gcd == 1) return false;
2384 }
2385
2386 // If we take the constraint % gcd, we have
2387 // ref * coeff % gcd = rhs % gcd
2388 CHECK_GT(gcd, 1);
2389 const int ref = ct->linear().vars(target_index);
2390 const int64_t coeff = ct->linear().coeffs(target_index);
2391 const int64_t rhs = ct->linear().domain(0);
2392
2393 // This should have been processed before by just dividing the whole
2394 // constraint by the gcd.
2395 if (coeff % gcd == 0) return false;
2396
2397 if (!context_->CanonicalizeAffineVariable(ref, coeff, gcd, rhs)) {
2398 return false;
2399 }
2400
2401 // We use the new variable in the constraint.
2402 // Note that we will divide everything by the gcd too.
2403 return CanonicalizeLinear(ct);
2404}
2405
2406namespace {
2407
2408bool IsLinearEqualityConstraint(const ConstraintProto& ct) {
2409 return ct.constraint_case() == ConstraintProto::kLinear &&
2410 ct.linear().domain().size() == 2 &&
2411 ct.linear().domain(0) == ct.linear().domain(1) &&
2412 ct.enforcement_literal().empty();
2413}
2414
2415} // namespace
2416
2417// Any equality must be true modulo n.
2418//
2419// If the gcd of all but one term is not one, we can rewrite the last term using
2420// an affine representative by considering the equality modulo that gcd.
2421// As an heuristic, we only test the smallest term or small primes 2, 3, and 5.
2422//
2423// We also handle the special case of having two non-zero literals modulo 2.
2424//
2425// TODO(user): Use more complex algo to detect all the cases? By splitting the
2426// constraint in two, and computing the gcd of each halves, we can reduce the
2427// problem to two problem of half size. So at least we can do it in O(n log n).
2428bool CpModelPresolver::PresolveLinearEqualityWithModulo(ConstraintProto* ct) {
2429 if (context_->ModelIsUnsat()) return false;
2430 if (!IsLinearEqualityConstraint(*ct)) return false;
2431
2432 const int num_variables = ct->linear().vars().size();
2433 if (num_variables < 2) return false;
2434
2435 std::vector<int> mod2_indices;
2436 std::vector<int> mod3_indices;
2437 std::vector<int> mod5_indices;
2438
2439 int64_t min_magnitude;
2440 int num_smallest = 0;
2441 int smallest_index;
2442 for (int i = 0; i < num_variables; ++i) {
2443 const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2444 if (num_smallest == 0 || magnitude < min_magnitude) {
2445 min_magnitude = magnitude;
2446 num_smallest = 1;
2447 smallest_index = i;
2448 } else if (magnitude == min_magnitude) {
2449 ++num_smallest;
2450 }
2451
2452 if (magnitude % 2 != 0) mod2_indices.push_back(i);
2453 if (magnitude % 3 != 0) mod3_indices.push_back(i);
2454 if (magnitude % 5 != 0) mod5_indices.push_back(i);
2455 }
2456
2457 if (mod2_indices.size() == 2) {
2458 bool ok = true;
2459 std::vector<int> literals;
2460 for (const int i : mod2_indices) {
2461 const int ref = ct->linear().vars(i);
2462 if (!context_->CanBeUsedAsLiteral(ref)) {
2463 ok = false;
2464 break;
2465 }
2466 literals.push_back(ref);
2467 }
2468 if (ok) {
2469 const int64_t rhs = std::abs(ct->linear().domain(0));
2470 context_->UpdateRuleStats("linear: only two odd Booleans in equality");
2471 if (rhs % 2) {
2472 context_->StoreBooleanEqualityRelation(literals[0],
2473 NegatedRef(literals[1]));
2474 } else {
2475 context_->StoreBooleanEqualityRelation(literals[0], literals[1]);
2476 }
2477 }
2478 }
2479
2480 // TODO(user): More than one reduction might be possible, so we will need
2481 // to call this again if we apply any of these reduction.
2482 if (mod2_indices.size() == 1) {
2483 return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0], ct);
2484 }
2485 if (mod3_indices.size() == 1) {
2486 return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0], ct);
2487 }
2488 if (mod5_indices.size() == 1) {
2489 return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0], ct);
2490 }
2491 if (num_smallest == 1) {
2492 return AddVarAffineRepresentativeFromLinearEquality(smallest_index, ct);
2493 }
2494
2495 return false;
2496}
2497
2498bool CpModelPresolver::PresolveLinearOfSizeOne(ConstraintProto* ct) {
2499 CHECK_EQ(ct->linear().vars().size(), 1);
2500 CHECK(RefIsPositive(ct->linear().vars(0)));
2501
2502 const int var = ct->linear().vars(0);
2503 const Domain var_domain = context_->DomainOf(var);
2504 const Domain rhs = ReadDomainFromProto(ct->linear())
2505 .InverseMultiplicationBy(ct->linear().coeffs(0))
2506 .IntersectionWith(var_domain);
2507 if (rhs.IsEmpty()) {
2508 context_->UpdateRuleStats("linear1: infeasible");
2509 return MarkConstraintAsFalse(ct);
2510 }
2511 if (rhs == context_->DomainOf(var)) {
2512 context_->UpdateRuleStats("linear1: always true");
2513 return RemoveConstraint(ct);
2514 }
2515
2516 // We can always canonicalize the constraint to a coefficient of 1.
2517 // Note that this should never trigger as we usually divide by gcd already.
2518 if (ct->linear().coeffs(0) != 1) {
2519 context_->UpdateRuleStats("linear1: canonicalized");
2520 ct->mutable_linear()->set_coeffs(0, 1);
2521 FillDomainInProto(rhs, ct->mutable_linear());
2522 }
2523
2524 // Size one constraint with no enforcement?
2525 if (!HasEnforcementLiteral(*ct)) {
2526 context_->UpdateRuleStats("linear1: without enforcement");
2527 if (!context_->IntersectDomainWith(var, rhs)) return false;
2528 return RemoveConstraint(ct);
2529 }
2530
2531 // This is just an implication, lets convert it right away.
2532 if (context_->CanBeUsedAsLiteral(var)) {
2533 DCHECK(rhs.IsFixed());
2534 if (rhs.FixedValue() == 1) {
2535 ct->mutable_bool_and()->add_literals(var);
2536 } else {
2537 CHECK_EQ(rhs.FixedValue(), 0);
2538 ct->mutable_bool_and()->add_literals(NegatedRef(var));
2539 }
2540
2541 // No var <-> constraint graph changes.
2542 // But this is no longer a linear1.
2543 return true;
2544 }
2545
2546 // Detect encoding.
2547 if (ct->enforcement_literal().size() == 1) {
2548 // If we already have an encoding literal, this constraint is really
2549 // an implication.
2550 const int lit = ct->enforcement_literal(0);
2551
2552 if (rhs.IsFixed()) {
2553 const int64_t value = rhs.FixedValue();
2554 int encoding_lit;
2555 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2556 if (lit == encoding_lit) return false;
2557 context_->AddImplication(lit, encoding_lit);
2559 ct->Clear();
2560 context_->UpdateRuleStats("linear1: transformed to implication");
2561 return true;
2562 } else {
2563 if (context_->StoreLiteralImpliesVarEqValue(lit, var, value)) {
2564 // The domain is not actually modified, but we want to rescan the
2565 // constraints linked to this variable.
2566 context_->modified_domains.Set(var);
2567 }
2569 }
2570 return false;
2571 }
2572
2573 const Domain complement = rhs.Complement().IntersectionWith(var_domain);
2574 if (complement.IsFixed()) {
2575 const int64_t value = complement.FixedValue();
2576 int encoding_lit;
2577 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2578 if (NegatedRef(lit) == encoding_lit) return false;
2579 context_->AddImplication(lit, NegatedRef(encoding_lit));
2581 ct->Clear();
2582 context_->UpdateRuleStats("linear1: transformed to implication");
2583 return true;
2584 } else {
2585 if (context_->StoreLiteralImpliesVarNEqValue(lit, var, value)) {
2586 // The domain is not actually modified, but we want to rescan the
2587 // constraints linked to this variable.
2588 context_->modified_domains.Set(var);
2589 }
2591 }
2592 return false;
2593 }
2594 }
2595
2596 return false;
2597}
2598
2599bool CpModelPresolver::PresolveLinearOfSizeTwo(ConstraintProto* ct) {
2600 DCHECK_EQ(ct->linear().vars().size(), 2);
2601
2602 const LinearConstraintProto& arg = ct->linear();
2603 const int var1 = arg.vars(0);
2604 const int var2 = arg.vars(1);
2605 const int64_t coeff1 = arg.coeffs(0);
2606 const int64_t coeff2 = arg.coeffs(1);
2607
2608 // If it is not an equality, we only presolve the constraint if one of
2609 // the variable is Boolean. Note that if both are Boolean, then a similar
2610 // reduction is done by PresolveLinearOnBooleans(). If we have an equality,
2611 // then the code below will do something stronger than this.
2612 //
2613 // TODO(user): We should probably instead generalize the code of
2614 // ExtractEnforcementLiteralFromLinearConstraint(), or just temporary
2615 // propagate domain of enforced linear constraints, to detect Boolean that
2616 // must be true or false. This way we can do the same for longer constraints.
2617 const bool is_equality =
2618 arg.domain_size() == 2 && arg.domain(0) == arg.domain(1);
2619 if (!is_equality) {
2620 int lit, var;
2621 int64_t value_on_true, coeff;
2622 if (context_->CanBeUsedAsLiteral(var1)) {
2623 lit = var1;
2624 value_on_true = coeff1;
2625 var = var2;
2626 coeff = coeff2;
2627 } else if (context_->CanBeUsedAsLiteral(var2)) {
2628 lit = var2;
2629 value_on_true = coeff2;
2630 var = var1;
2631 coeff = coeff1;
2632 } else {
2633 return false;
2634 }
2635 if (!RefIsPositive(lit)) return false;
2636
2637 const Domain rhs = ReadDomainFromProto(ct->linear());
2638 const Domain rhs_if_true =
2639 rhs.AdditionWith(Domain(-value_on_true)).InverseMultiplicationBy(coeff);
2640 const Domain rhs_if_false = rhs.InverseMultiplicationBy(coeff);
2641 const bool implied_false =
2642 context_->DomainOf(var).IntersectionWith(rhs_if_true).IsEmpty();
2643 const bool implied_true =
2644 context_->DomainOf(var).IntersectionWith(rhs_if_false).IsEmpty();
2645 if (implied_true && implied_false) {
2646 context_->UpdateRuleStats("linear2: infeasible.");
2647 return MarkConstraintAsFalse(ct);
2648 } else if (implied_true) {
2649 context_->UpdateRuleStats("linear2: Boolean with one feasible value.");
2650
2651 // => true.
2652 ConstraintProto* new_ct = context_->working_model->add_constraints();
2653 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2654 new_ct->mutable_bool_and()->add_literals(lit);
2656
2657 // Rewrite to => var in rhs_if_true.
2658 ct->mutable_linear()->Clear();
2659 ct->mutable_linear()->add_vars(var);
2660 ct->mutable_linear()->add_coeffs(1);
2661 FillDomainInProto(rhs_if_true, ct->mutable_linear());
2662 return PresolveLinearOfSizeOne(ct) || true;
2663 } else if (implied_false) {
2664 context_->UpdateRuleStats("linear2: Boolean with one feasible value.");
2665
2666 // => false.
2667 ConstraintProto* new_ct = context_->working_model->add_constraints();
2668 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2669 new_ct->mutable_bool_and()->add_literals(NegatedRef(lit));
2671
2672 // Rewrite to => var in rhs_if_false.
2673 ct->mutable_linear()->Clear();
2674 ct->mutable_linear()->add_vars(var);
2675 ct->mutable_linear()->add_coeffs(1);
2676 FillDomainInProto(rhs_if_false, ct->mutable_linear());
2677 return PresolveLinearOfSizeOne(ct) || true;
2678 } else if (ct->enforcement_literal().empty() &&
2679 !context_->CanBeUsedAsLiteral(var)) {
2680 // We currently only do that if there are no enforcement and we don't have
2681 // two Booleans as this can be presolved differently. We expand it into
2682 // two linear1 constraint that have a chance to be merged with other
2683 // "encoding" constraints.
2684 context_->UpdateRuleStats("linear2: contains a Boolean.");
2685
2686 // lit => var \in rhs_if_true
2687 const Domain var_domain = context_->DomainOf(var);
2688 if (!var_domain.IsIncludedIn(rhs_if_true)) {
2689 ConstraintProto* new_ct = context_->working_model->add_constraints();
2690 new_ct->add_enforcement_literal(lit);
2691 new_ct->mutable_linear()->add_vars(var);
2692 new_ct->mutable_linear()->add_coeffs(1);
2693 FillDomainInProto(rhs_if_true.IntersectionWith(var_domain),
2694 new_ct->mutable_linear());
2695 }
2696
2697 // NegatedRef(lit) => var \in rhs_if_false
2698 if (!var_domain.IsIncludedIn(rhs_if_false)) {
2699 ConstraintProto* new_ct = context_->working_model->add_constraints();
2700 new_ct->add_enforcement_literal(NegatedRef(lit));
2701 new_ct->mutable_linear()->add_vars(var);
2702 new_ct->mutable_linear()->add_coeffs(1);
2703 FillDomainInProto(rhs_if_false.IntersectionWith(var_domain),
2704 new_ct->mutable_linear());
2705 }
2706
2708 ct->Clear();
2709 return true;
2710 }
2711
2712 // Code below require equality.
2713 context_->UpdateRuleStats("TODO linear2: contains a Boolean.");
2714 return false;
2715 }
2716
2717 // We have: enforcement => (coeff1 * v1 + coeff2 * v2 == rhs).
2718 const int64_t rhs = arg.domain(0);
2719 if (ct->enforcement_literal().empty()) {
2720 // Detect affine relation.
2721 //
2722 // TODO(user): it might be better to first add only the affine relation with
2723 // a coefficient of magnitude 1, and later the one with larger coeffs.
2724 bool added = false;
2725 if (coeff1 == 1) {
2726 added = context_->StoreAffineRelation(var1, var2, -coeff2, rhs);
2727 } else if (coeff2 == 1) {
2728 added = context_->StoreAffineRelation(var2, var1, -coeff1, rhs);
2729 } else if (coeff1 == -1) {
2730 added = context_->StoreAffineRelation(var1, var2, coeff2, -rhs);
2731 } else if (coeff2 == -1) {
2732 added = context_->StoreAffineRelation(var2, var1, coeff1, -rhs);
2733 } else {
2734 // In this case, we can solve the diophantine equation, and write
2735 // both x and y in term of a new affine representative z.
2736 //
2737 // Note that PresolveLinearEqualityWithModulo() will have the same effect.
2738 //
2739 // We can also decide to fully expand the equality if the variables
2740 // are fully encoded.
2741 context_->UpdateRuleStats("TODO linear2: ax + by = cte");
2742 }
2743 if (added) return RemoveConstraint(ct);
2744 } else {
2745 // We look ahead to detect solutions to ax + by == cte.
2746 int64_t a = coeff1;
2747 int64_t b = coeff2;
2748 int64_t cte = rhs;
2749 int64_t x0 = 0;
2750 int64_t y0 = 0;
2751 if (!SolveDiophantineEquationOfSizeTwo(a, b, cte, x0, y0)) {
2752 context_->UpdateRuleStats(
2753 "linear2: implied ax + by = cte has no solutions");
2754 return MarkConstraintAsFalse(ct);
2755 }
2756 const Domain reduced_domain =
2757 context_->DomainOf(var1)
2758 .AdditionWith(Domain(-x0))
2760 .IntersectionWith(context_->DomainOf(var2)
2761 .AdditionWith(Domain(-y0))
2762 .InverseMultiplicationBy(-a));
2763
2764 if (reduced_domain.IsEmpty()) { // no solution
2765 context_->UpdateRuleStats(
2766 "linear2: implied ax + by = cte has no solutions");
2767 return MarkConstraintAsFalse(ct);
2768 }
2769
2770 if (reduced_domain.Size() == 1) {
2771 const int64_t z = reduced_domain.FixedValue();
2772 const int64_t value1 = x0 + b * z;
2773 const int64_t value2 = y0 - a * z;
2774
2775 DCHECK(context_->DomainContains(var1, value1));
2776 DCHECK(context_->DomainContains(var2, value2));
2777 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
2778
2779 ConstraintProto* imply1 = context_->working_model->add_constraints();
2780 *imply1->mutable_enforcement_literal() = ct->enforcement_literal();
2781 imply1->mutable_linear()->add_vars(var1);
2782 imply1->mutable_linear()->add_coeffs(1);
2783 imply1->mutable_linear()->add_domain(value1);
2784 imply1->mutable_linear()->add_domain(value1);
2785
2786 ConstraintProto* imply2 = context_->working_model->add_constraints();
2787 *imply2->mutable_enforcement_literal() = ct->enforcement_literal();
2788 imply2->mutable_linear()->add_vars(var2);
2789 imply2->mutable_linear()->add_coeffs(1);
2790 imply2->mutable_linear()->add_domain(value2);
2791 imply2->mutable_linear()->add_domain(value2);
2792 context_->UpdateRuleStats(
2793 "linear2: implied ax + by = cte has only one solution");
2795 return RemoveConstraint(ct);
2796 }
2797 }
2798
2799 return false;
2800}
2801
2802bool CpModelPresolver::PresolveSmallLinear(ConstraintProto* ct) {
2803 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
2804 if (context_->ModelIsUnsat()) return false;
2805
2806 if (ct->linear().vars().empty()) {
2807 context_->UpdateRuleStats("linear: empty");
2808 const Domain rhs = ReadDomainFromProto(ct->linear());
2809 if (rhs.Contains(0)) {
2810 return RemoveConstraint(ct);
2811 } else {
2812 return MarkConstraintAsFalse(ct);
2813 }
2814 } else if (ct->linear().vars().size() == 1) {
2815 return PresolveLinearOfSizeOne(ct);
2816 } else if (ct->linear().vars().size() == 2) {
2817 return PresolveLinearOfSizeTwo(ct);
2818 }
2819
2820 return false;
2821}
2822
2823bool CpModelPresolver::PresolveDiophantine(ConstraintProto* ct) {
2824 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
2825 if (ct->linear().vars().size() <= 1) return false;
2826
2827 if (context_->ModelIsUnsat()) return false;
2828
2829 const LinearConstraintProto& linear_constraint = ct->linear();
2830 if (linear_constraint.domain_size() != 2) return false;
2831 if (linear_constraint.domain(0) != linear_constraint.domain(1)) return false;
2832
2833 std::vector<int64_t> lbs(linear_constraint.vars_size());
2834 std::vector<int64_t> ubs(linear_constraint.vars_size());
2835 for (int i = 0; i < linear_constraint.vars_size(); ++i) {
2836 lbs[i] = context_->MinOf(linear_constraint.vars(i));
2837 ubs[i] = context_->MaxOf(linear_constraint.vars(i));
2838 }
2839 DiophantineSolution diophantine_solution = SolveDiophantine(
2840 linear_constraint.coeffs(), linear_constraint.domain(0), lbs, ubs);
2841
2842 if (!diophantine_solution.has_solutions) {
2843 context_->UpdateRuleStats("diophantine: equality has no solutions");
2844 return MarkConstraintAsFalse(ct);
2845 }
2846 if (diophantine_solution.no_reformulation_needed) return false;
2847 // Only first coefficients of kernel_basis elements and special_solution could
2848 // overflow int64_t due to the reduction applied in SolveDiophantineEquation,
2849 for (const std::vector<absl::int128>& b : diophantine_solution.kernel_basis) {
2850 if (!IsNegatableInt64(b[0])) {
2851 context_->UpdateRuleStats(
2852 "diophantine: couldn't apply due to int64_t overflow");
2853 return false;
2854 }
2855 }
2856 if (!IsNegatableInt64(diophantine_solution.special_solution[0])) {
2857 context_->UpdateRuleStats(
2858 "diophantine: couldn't apply due to int64_t overflow");
2859 return false;
2860 }
2861
2862 const int num_replaced_variables =
2863 static_cast<int>(diophantine_solution.special_solution.size());
2864 const int num_new_variables =
2865 static_cast<int>(diophantine_solution.kernel_vars_lbs.size());
2866 DCHECK_EQ(num_new_variables + 1, num_replaced_variables);
2867 for (int i = 0; i < num_new_variables; ++i) {
2868 if (!IsNegatableInt64(diophantine_solution.kernel_vars_lbs[i]) ||
2869 !IsNegatableInt64(diophantine_solution.kernel_vars_ubs[i])) {
2870 context_->UpdateRuleStats(
2871 "diophantine: couldn't apply due to int64_t overflow");
2872 return false;
2873 }
2874 }
2875 // TODO(user): Make sure the newly generated linear constraint
2876 // satisfy our no-overflow precondition on the min/max activity.
2877 // We should check that the model still satisfy conditions in
2878 // 3/ortools/sat/cp_model_checker.cc;l=165;bpv=0
2879
2880 // Create new variables.
2881 std::vector<int> new_variables(num_new_variables);
2882 for (int i = 0; i < num_new_variables; ++i) {
2883 new_variables[i] = context_->working_model->variables_size();
2884 IntegerVariableProto* var = context_->working_model->add_variables();
2885 var->add_domain(
2886 static_cast<int64_t>(diophantine_solution.kernel_vars_lbs[i]));
2887 var->add_domain(
2888 static_cast<int64_t>(diophantine_solution.kernel_vars_ubs[i]));
2889 if (!ct->name().empty()) {
2890 var->set_name(absl::StrCat("u_diophantine_", ct->name(), "_", i));
2891 }
2892 }
2893
2894 // For i = 0, ..., num_replaced_variables - 1, creates
2895 // x[i] = special_solution[i]
2896 // + sum(kernel_basis[k][i]*y[k], max(1, i) <= k < vars.size - 1)
2897 // where:
2898 // y[k] is the newly created variable if 0 <= k < num_new_variables
2899 // y[k] = x[index_permutation[k + 1]] otherwise.
2900 for (int i = 0; i < num_replaced_variables; ++i) {
2901 ConstraintProto* identity = context_->working_model->add_constraints();
2902 LinearConstraintProto* lin = identity->mutable_linear();
2903 if (!ct->name().empty()) {
2904 identity->set_name(absl::StrCat("c_diophantine_", ct->name(), "_", i));
2905 }
2906 *identity->mutable_enforcement_literal() = ct->enforcement_literal();
2907 lin->add_vars(
2908 linear_constraint.vars(diophantine_solution.index_permutation[i]));
2909 lin->add_coeffs(1);
2910 lin->add_domain(
2911 static_cast<int64_t>(diophantine_solution.special_solution[i]));
2912 lin->add_domain(
2913 static_cast<int64_t>(diophantine_solution.special_solution[i]));
2914 for (int j = std::max(1, i); j < num_replaced_variables; ++j) {
2915 lin->add_vars(new_variables[j - 1]);
2916 lin->add_coeffs(
2917 -static_cast<int64_t>(diophantine_solution.kernel_basis[j - 1][i]));
2918 }
2919 for (int j = num_replaced_variables; j < linear_constraint.vars_size();
2920 ++j) {
2921 lin->add_vars(
2922 linear_constraint.vars(diophantine_solution.index_permutation[j]));
2923 lin->add_coeffs(
2924 -static_cast<int64_t>(diophantine_solution.kernel_basis[j - 1][i]));
2925 }
2926
2927 // TODO(user): The domain in the proto are not necessarily up to date so
2928 // this might be stricter than necessary. Fix? It shouldn't matter too much
2929 // though.
2930 if (PossibleIntegerOverflow(*(context_->working_model), lin->vars(),
2931 lin->coeffs())) {
2932 context_->UpdateRuleStats(
2933 "diophantine: couldn't apply due to overflowing activity of new "
2934 "constraints");
2935 // Cancel working_model changes.
2936 context_->working_model->mutable_constraints()->DeleteSubrange(
2937 context_->working_model->constraints_size() - i - 1, i + 1);
2938 context_->working_model->mutable_variables()->DeleteSubrange(
2939 context_->working_model->variables_size() - num_new_variables,
2940 num_new_variables);
2941 return false;
2942 }
2943 }
2944 context_->InitializeNewDomains();
2945
2946 if (VLOG_IS_ON(2)) {
2947 std::string log_eq = absl::StrCat(linear_constraint.domain(0), " = ");
2948 const int terms_to_show = std::min<int>(15, linear_constraint.vars_size());
2949 for (int i = 0; i < terms_to_show; ++i) {
2950 if (i > 0) absl::StrAppend(&log_eq, " + ");
2951 absl::StrAppend(
2952 &log_eq,
2953 linear_constraint.coeffs(diophantine_solution.index_permutation[i]),
2954 " x",
2955 linear_constraint.vars(diophantine_solution.index_permutation[i]));
2956 }
2957 if (terms_to_show < linear_constraint.vars_size()) {
2958 absl::StrAppend(&log_eq, "+ ... (", linear_constraint.vars_size(),
2959 " terms)");
2960 }
2961 VLOG(2) << "[Diophantine] " << log_eq;
2962 }
2963
2964 context_->UpdateRuleStats("diophantine: reformulated equality");
2966 return RemoveConstraint(ct);
2967}
2968
2969// This tries to decompose the constraint into coeff * part1 + part2 and show
2970// that the value that part2 take is not important, thus the constraint can
2971// only be transformed on a constraint on the first part.
2972//
2973// TODO(user): Improve !! we miss simple case like x + 47 y + 50 z >= 50
2974// for positive variables. We should remove x, and ideally we should rewrite
2975// this as y + 2z >= 2 if we can show that its relaxation is just better?
2976// We should at least see that it is the same as 47y + 50 z >= 48.
2977//
2978// TODO(user): One easy algo is to first remove all enforcement term (even
2979// non-Boolean one) before applying the algo here and then re-linearize the
2980// non-Boolean terms.
2981void CpModelPresolver::TryToReduceCoefficientsOfLinearConstraint(
2982 int c, ConstraintProto* ct) {
2983 if (ct->constraint_case() != ConstraintProto::kLinear) return;
2984 if (context_->ModelIsUnsat()) return;
2985
2986 // Only consider "simple" constraints.
2987 const LinearConstraintProto& lin = ct->linear();
2988 if (lin.domain().size() != 2) return;
2989 const Domain rhs = ReadDomainFromProto(lin);
2990
2991 // Precompute a bunch of quantities and "canonicalize" the constraint.
2992 int64_t lb_sum = 0;
2993 int64_t ub_sum = 0;
2994 int64_t max_variation = 0;
2995
2996 rd_entries_.clear();
2997 rd_magnitudes_.clear();
2998 rd_lbs_.clear();
2999 rd_ubs_.clear();
3000
3001 int64_t max_magnitude = 0;
3002 const int num_terms = lin.vars().size();
3003 for (int i = 0; i < num_terms; ++i) {
3004 const int64_t coeff = lin.coeffs(i);
3005 const int64_t magnitude = std::abs(lin.coeffs(i));
3006 if (magnitude == 0) continue;
3007 max_magnitude = std::max(max_magnitude, magnitude);
3008
3009 int64_t lb;
3010 int64_t ub;
3011 if (coeff > 0) {
3012 lb = context_->MinOf(lin.vars(i));
3013 ub = context_->MaxOf(lin.vars(i));
3014 } else {
3015 lb = -context_->MaxOf(lin.vars(i));
3016 ub = -context_->MinOf(lin.vars(i));
3017 }
3018 lb_sum += lb * magnitude;
3019 ub_sum += ub * magnitude;
3020
3021 // Abort if fixed term, that might mess up code below.
3022 if (lb == ub) return;
3023
3024 rd_lbs_.push_back(lb);
3025 rd_ubs_.push_back(ub);
3026 rd_magnitudes_.push_back(magnitude);
3027 rd_entries_.push_back({magnitude, magnitude * (ub - lb), i});
3028 max_variation += rd_entries_.back().max_variation;
3029 }
3030
3031 // Mark trivially false constraint as such. This should have been already
3032 // done, but we require non-negative quantity below.
3033 if (lb_sum > rhs.Max() || rhs.Min() > ub_sum) {
3034 (void)MarkConstraintAsFalse(ct);
3035 context_->UpdateConstraintVariableUsage(c);
3036 return;
3037 }
3038 const IntegerValue rhs_ub(CapSub(rhs.Max(), lb_sum));
3039 const IntegerValue rhs_lb(CapSub(ub_sum, rhs.Min()));
3040 const bool use_ub = max_variation > rhs_ub;
3041 const bool use_lb = max_variation > rhs_lb;
3042 if (!use_ub && !use_lb) {
3043 (void)RemoveConstraint(ct);
3044 context_->UpdateConstraintVariableUsage(c);
3045 return;
3046 }
3047
3048 // No point doing more work for constraint with all coeff at +/-1.
3049 if (max_magnitude <= 1) return;
3050
3051 // TODO(user): All the lb/ub_feasible/infeasible class are updated in
3052 // exactly the same way. Find a more efficient algo?
3053 if (use_lb) {
3054 lb_feasible_.Reset(rhs_lb.value());
3055 lb_infeasible_.Reset(rhs.Min() - lb_sum - 1);
3056 }
3057 if (use_ub) {
3058 ub_feasible_.Reset(rhs_ub.value());
3059 ub_infeasible_.Reset(ub_sum - rhs.Max() - 1);
3060 }
3061
3062 // Process entries by decreasing magnitude. Update max_error to correspond
3063 // only to the sum of the not yet processed terms.
3064 uint64_t gcd = 0;
3065 int64_t max_error = max_variation;
3066 std::stable_sort(rd_entries_.begin(), rd_entries_.end(),
3067 [](const RdEntry& a, const RdEntry& b) {
3068 return a.magnitude > b.magnitude;
3069 });
3070 int64_t range = 0;
3071 rd_divisors_.clear();
3072 for (int i = 0; i < rd_entries_.size(); ++i) {
3073 const RdEntry& e = rd_entries_[i];
3074 gcd = MathUtil::GCD64(gcd, e.magnitude);
3075 max_error -= e.max_variation;
3076
3077 // We regroup all term with the same coefficient into one.
3078 //
3079 // TODO(user): I am not sure there is no possible simplification across two
3080 // term with the same coeff, but it should be rare if it ever happens.
3081 range += e.max_variation / e.magnitude;
3082 if (i + 1 < rd_entries_.size() &&
3083 e.magnitude == rd_entries_[i + 1].magnitude) {
3084 continue;
3085 }
3086 const int64_t saved_range = range;
3087 range = 0;
3088
3089 if (e.magnitude > 1) {
3090 if ((!use_ub ||
3091 max_error <= PositiveRemainder(rhs_ub, IntegerValue(e.magnitude))) &&
3092 (!use_lb ||
3093 max_error <= PositiveRemainder(rhs_lb, IntegerValue(e.magnitude)))) {
3094 rd_divisors_.push_back(e.magnitude);
3095 }
3096 }
3097
3098 bool simplify_lb = false;
3099 if (use_lb) {
3100 lb_feasible_.AddMultiples(e.magnitude, saved_range);
3101 lb_infeasible_.AddMultiples(e.magnitude, saved_range);
3102
3103 // For a <= constraint, the max_feasible + error is still feasible.
3104 if (CapAdd(lb_feasible_.CurrentMax(), max_error) <=
3105 lb_feasible_.Bound()) {
3106 simplify_lb = true;
3107 }
3108 // For a <= constraint describing the infeasible set, the max_infeasible +
3109 // error is still infeasible.
3110 if (CapAdd(lb_infeasible_.CurrentMax(), max_error) <=
3111 lb_infeasible_.Bound()) {
3112 simplify_lb = true;
3113 }
3114 } else {
3115 simplify_lb = true;
3116 }
3117 bool simplify_ub = false;
3118 if (use_ub) {
3119 ub_feasible_.AddMultiples(e.magnitude, saved_range);
3120 ub_infeasible_.AddMultiples(e.magnitude, saved_range);
3121 if (CapAdd(ub_feasible_.CurrentMax(), max_error) <=
3122 ub_feasible_.Bound()) {
3123 simplify_ub = true;
3124 }
3125 if (CapAdd(ub_infeasible_.CurrentMax(), max_error) <=
3126 ub_infeasible_.Bound()) {
3127 simplify_ub = true;
3128 }
3129 } else {
3130 simplify_ub = true;
3131 }
3132
3133 if (max_error == 0) break; // Last term.
3134 if (simplify_lb && simplify_ub) {
3135 // We have a simplification since the second part can be ignored.
3136 context_->UpdateRuleStats("linear: remove irrelevant part");
3137 int64_t shift_lb = 0;
3138 int64_t shift_ub = 0;
3139 rd_vars_.clear();
3140 rd_coeffs_.clear();
3141 for (int j = 0; j <= i; ++j) {
3142 const int index = rd_entries_[j].index;
3143 const int64_t m = rd_magnitudes_[index];
3144 shift_lb += rd_lbs_[index] * m;
3145 shift_ub += rd_ubs_[index] * m;
3146 rd_vars_.push_back(lin.vars(index));
3147 rd_coeffs_.push_back(lin.coeffs(index));
3148 }
3149 LinearConstraintProto* mut_lin = ct->mutable_linear();
3150 mut_lin->mutable_vars()->Assign(rd_vars_.begin(), rd_vars_.end());
3151 mut_lin->mutable_coeffs()->Assign(rd_coeffs_.begin(), rd_coeffs_.end());
3152
3153 // The constraint become:
3154 // sum ci (X - lb) <= rhs_ub
3155 // sum ci (ub - X) <= rhs_lb
3156 // sum ci ub - rhs_lb <= sum ci X <= rhs_ub + sum ci lb.
3157 const int64_t new_rhs_lb =
3158 use_lb ? shift_ub - lb_feasible_.CurrentMax() : shift_lb;
3159 const int64_t new_rhs_ub =
3160 use_ub ? shift_lb + ub_feasible_.CurrentMax() : shift_ub;
3161 if (new_rhs_lb > new_rhs_ub) {
3162 (void)MarkConstraintAsFalse(ct);
3163 context_->UpdateConstraintVariableUsage(c);
3164 return;
3165 }
3166 FillDomainInProto(Domain(new_rhs_lb, new_rhs_ub), mut_lin);
3167 DivideLinearByGcd(ct);
3168 context_->UpdateConstraintVariableUsage(c);
3169 return;
3170 }
3171 }
3172
3173 if (gcd > 1) {
3174 // This might happen as a result of extra reduction after we already tried
3175 // this reduction.
3176 if (DivideLinearByGcd(ct)) {
3177 context_->UpdateConstraintVariableUsage(c);
3178 }
3179 return;
3180 }
3181
3182 // We didn't remove any irrelevant part, but we might be able to tighten
3183 // the constraint bound.
3184 if ((use_lb && lb_feasible_.CurrentMax() < lb_feasible_.Bound()) ||
3185 (use_ub && ub_feasible_.CurrentMax() < ub_feasible_.Bound())) {
3186 context_->UpdateRuleStats("linear: reduce rhs with DP");
3187 const int64_t new_rhs_lb =
3188 use_lb ? ub_sum - lb_feasible_.CurrentMax() : lb_sum;
3189 const int64_t new_rhs_ub =
3190 use_ub ? lb_sum + ub_feasible_.CurrentMax() : ub_sum;
3191 if (new_rhs_lb > new_rhs_ub) {
3192 (void)MarkConstraintAsFalse(ct);
3193 context_->UpdateConstraintVariableUsage(c);
3194 return;
3195 }
3196 FillDomainInProto(Domain(new_rhs_lb, new_rhs_ub), ct->mutable_linear());
3197 }
3198
3199 // Limit the number of "divisor" we try for approximate gcd.
3200 if (rd_divisors_.size() > 3) rd_divisors_.resize(3);
3201 for (const int64_t divisor : rd_divisors_) {
3202 // Try the <= side first.
3203 int64_t new_ub;
3205 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, rhs.Max(), &new_ub)) {
3206 continue;
3207 }
3208
3209 // The other side.
3210 int64_t minus_new_lb;
3211 for (int i = 0; i < rd_lbs_.size(); ++i) {
3212 std::swap(rd_lbs_[i], rd_ubs_[i]);
3213 rd_lbs_[i] = -rd_lbs_[i];
3214 rd_ubs_[i] = -rd_ubs_[i];
3215 }
3217 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, -rhs.Min(),
3218 &minus_new_lb)) {
3219 for (int i = 0; i < rd_lbs_.size(); ++i) {
3220 std::swap(rd_lbs_[i], rd_ubs_[i]);
3221 rd_lbs_[i] = -rd_lbs_[i];
3222 rd_ubs_[i] = -rd_ubs_[i];
3223 }
3224 continue;
3225 }
3226
3227 // Rewrite the constraint !
3228 context_->UpdateRuleStats("linear: simplify using approximate gcd");
3229 int new_size = 0;
3230 LinearConstraintProto* mutable_linear = ct->mutable_linear();
3231 for (int i = 0; i < lin.coeffs().size(); ++i) {
3232 const int64_t new_coeff =
3233 ClosestMultiple(lin.coeffs(i), divisor) / divisor;
3234 if (new_coeff == 0) continue;
3235 mutable_linear->set_vars(new_size, lin.vars(i));
3236 mutable_linear->set_coeffs(new_size, new_coeff);
3237 ++new_size;
3238 }
3239 mutable_linear->mutable_vars()->Truncate(new_size);
3240 mutable_linear->mutable_coeffs()->Truncate(new_size);
3241 const Domain new_rhs = Domain(-minus_new_lb, new_ub);
3242 if (new_rhs.IsEmpty()) {
3243 (void)MarkConstraintAsFalse(ct);
3244 } else {
3245 FillDomainInProto(new_rhs, mutable_linear);
3246 }
3247 context_->UpdateConstraintVariableUsage(c);
3248 return;
3249 }
3250}
3251
3252namespace {
3253
3254// In the equation terms + coeff * var_domain \included rhs, returns true if can
3255// we always fix rhs to its min value for any value in terms. It is okay to
3256// not be as generic as possible here.
3257bool RhsCanBeFixedToMin(int64_t coeff, const Domain& var_domain,
3258 const Domain& terms, const Domain& rhs) {
3259 if (var_domain.NumIntervals() != 1) return false;
3260 if (std::abs(coeff) != 1) return false;
3261
3262 // If for all values in terms, there is one value below rhs.Min(), then
3263 // because we add only one integer interval, if there is a feasible value, it
3264 // can be at rhs.Min().
3265 //
3266 // TODO(user): generalize to larger coeff magnitude if rhs is also a multiple
3267 // or if terms is a multiple.
3268 if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
3269 return true;
3270 }
3271 if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
3272 return true;
3273 }
3274 return false;
3275}
3276
3277bool RhsCanBeFixedToMax(int64_t coeff, const Domain& var_domain,
3278 const Domain& terms, const Domain& rhs) {
3279 if (var_domain.NumIntervals() != 1) return false;
3280 if (std::abs(coeff) != 1) return false;
3281
3282 if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
3283 return true;
3284 }
3285 if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
3286 return true;
3287 }
3288 return false;
3289}
3290
3291int FixLiteralFromSet(const absl::flat_hash_set<int>& literals_at_true,
3292 LinearConstraintProto* linear) {
3293 int new_size = 0;
3294 int num_fixed = 0;
3295 const int num_terms = linear->vars().size();
3296 int64_t shift = 0;
3297 for (int i = 0; i < num_terms; ++i) {
3298 const int var = linear->vars(i);
3299 const int64_t coeff = linear->coeffs(i);
3300 if (literals_at_true.contains(var)) {
3301 // Var is at one.
3302 shift += coeff;
3303 ++num_fixed;
3304 } else if (!literals_at_true.contains(NegatedRef(var))) {
3305 linear->set_vars(new_size, var);
3306 linear->set_coeffs(new_size, coeff);
3307 ++new_size;
3308 } else {
3309 ++num_fixed;
3310 // Else the variable is at zero.
3311 }
3312 }
3313 linear->mutable_vars()->Truncate(new_size);
3314 linear->mutable_coeffs()->Truncate(new_size);
3315 if (shift != 0) {
3316 FillDomainInProto(ReadDomainFromProto(*linear).AdditionWith(Domain(-shift)),
3317 linear);
3318 }
3319 return num_fixed;
3320}
3321
3322} // namespace
3323
3324void CpModelPresolver::ProcessAtMostOneAndLinear() {
3325 if (time_limit_->LimitReached()) return;
3326 if (context_->ModelIsUnsat()) return;
3327 if (context_->params().presolve_inclusion_work_limit() == 0) return;
3328 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
3329
3330 ActivityBoundHelper amo_in_linear;
3331 amo_in_linear.AddAllAtMostOnes(*context_->working_model);
3332
3333 int num_changes = 0;
3334 const int num_constraints = context_->working_model->constraints_size();
3335 for (int c = 0; c < num_constraints; ++c) {
3336 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
3337 if (ct->constraint_case() != ConstraintProto::kLinear) continue;
3338
3339 // We loop if the constraint changed.
3340 for (int i = 0; i < 5; ++i) {
3341 const int old_size = ct->linear().vars().size();
3342 const int old_enf_size = ct->enforcement_literal().size();
3343 ProcessOneLinearWithAmo(c, ct, &amo_in_linear);
3344 if (context_->ModelIsUnsat()) return;
3345 if (ct->constraint_case() != ConstraintProto::kLinear) break;
3346 if (ct->linear().vars().size() == old_size &&
3347 ct->enforcement_literal().size() == old_enf_size) {
3348 break;
3349 }
3350 ++num_changes;
3351 }
3352 }
3353
3354 timer.AddCounter("num_changes", num_changes);
3355}
3356
3357// TODO(user): Similarly amo and bool_or intersection or amo and enforcement
3358// literals list can be presolved.
3359//
3360// TODO(user): This is stronger than the fully included case. Avoid having
3361// the second code?
3362void CpModelPresolver::ProcessOneLinearWithAmo(int ct_index,
3363 ConstraintProto* ct,
3364 ActivityBoundHelper* helper) {
3365 if (ct->constraint_case() != ConstraintProto::kLinear) return;
3366 if (ct->linear().vars().size() <= 1) return;
3367
3368 tmp_terms_.clear();
3369 temp_ct_.Clear();
3370 Domain non_boolean_domain(0);
3371 const int initial_size = ct->linear().vars().size();
3372 int64_t min_magnitude = std::numeric_limits<int64_t>::max();
3373 int64_t max_magnitude = 0;
3374 for (int i = 0; i < initial_size; ++i) {
3375 // TODO(user): Just do not use negative reference in linear!
3376 int ref = ct->linear().vars(i);
3377 int64_t coeff = ct->linear().coeffs(i);
3378 if (!RefIsPositive(ref)) {
3379 ref = NegatedRef(ref);
3380 coeff = -coeff;
3381 }
3382 if (context_->CanBeUsedAsLiteral(ref)) {
3383 tmp_terms_.push_back({ref, coeff});
3384 min_magnitude = std::min(min_magnitude, std::abs(coeff));
3385 max_magnitude = std::max(max_magnitude, std::abs(coeff));
3386 } else {
3387 non_boolean_domain =
3388 non_boolean_domain
3389 .AdditionWith(
3390 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
3391 .RelaxIfTooComplex();
3392 temp_ct_.mutable_linear()->add_vars(ref);
3393 temp_ct_.mutable_linear()->add_coeffs(coeff);
3394 }
3395 }
3396
3397 // Skip if there are no Booleans.
3398 if (tmp_terms_.empty()) return;
3399
3400 // Detect encoded AMO.
3401 //
3402 // TODO(user): Support more coefficient strengthening cases.
3403 // For instance on neos-954925.pb.gz we have stuff like:
3404 // 20 * (AMO1 + AMO2) - [coeff in 48 to 53] >= -15
3405 // this is really AMO1 + AMO2 - 2 * AMO3 >= 0.
3406 // Maybe if we reify the AMO to exactly one, this is visible since large
3407 // AMO can be rewriten with single variable (1 - extra var in exactly one).
3408 const Domain rhs = ReadDomainFromProto(ct->linear());
3409 if (non_boolean_domain == Domain(0) && rhs.NumIntervals() == 1 &&
3410 min_magnitude < max_magnitude) {
3411 int64_t min_activity = 0;
3412 int64_t max_activity = 0;
3413 for (const auto [ref, coeff] : tmp_terms_) {
3414 if (coeff > 0) {
3415 max_activity += coeff;
3416 } else {
3417 min_activity += coeff;
3418 }
3419 }
3420 const int64_t transformed_rhs = rhs.Max() - min_activity;
3421 if (min_activity >= rhs.Min() && max_magnitude <= transformed_rhs) {
3422 std::vector<int> literals;
3423 for (const auto [ref, coeff] : tmp_terms_) {
3424 if (coeff + min_magnitude > transformed_rhs) continue;
3425 literals.push_back(coeff > 0 ? ref : NegatedRef(ref));
3426 }
3427 if (helper->IsAmo(literals)) {
3428 // We actually have an at-most-one in disguise.
3429 context_->UpdateRuleStats("linear + amo: detect hidden AMO");
3430 int64_t shift = 0;
3431 for (int i = 0; i < initial_size; ++i) {
3432 CHECK(RefIsPositive(ct->linear().vars(i)));
3433 if (ct->linear().coeffs(i) > 0) {
3434 ct->mutable_linear()->set_coeffs(i, 1);
3435 } else {
3436 ct->mutable_linear()->set_coeffs(i, -1);
3437 shift -= 1;
3438 }
3439 }
3440 FillDomainInProto(Domain(shift, shift + 1), ct->mutable_linear());
3441 return;
3442 }
3443 }
3444 }
3445
3446 // Get more precise activity estimate based on at most one and heuristics.
3447 const int64_t min_bool_activity =
3448 helper->ComputeMinActivity(tmp_terms_, &conditional_mins_);
3449 const int64_t max_bool_activity =
3450 helper->ComputeMaxActivity(tmp_terms_, &conditional_maxs_);
3451
3452 // Detect trivially true/false constraint under these new bounds.
3453 // TODO(user): relax rhs if only one side is trivial.
3454 const Domain activity = non_boolean_domain.AdditionWith(
3455 Domain(min_bool_activity, max_bool_activity));
3456 if (activity.IntersectionWith(rhs).IsEmpty()) {
3457 // Note that this covers min_bool_activity > max_bool_activity.
3458 context_->UpdateRuleStats("linear + amo: infeasible linear constraint");
3459 (void)MarkConstraintAsFalse(ct);
3461 return;
3462 } else if (activity.IsIncludedIn(rhs)) {
3463 context_->UpdateRuleStats("linear + amo: trivial linear constraint");
3464 ct->Clear();
3466 return;
3467 }
3468
3469 // We can use the new bound to propagate other terms.
3470 if (ct->enforcement_literal().empty() && !temp_ct_.linear().vars().empty()) {
3472 rhs.AdditionWith(
3473 Domain(min_bool_activity, max_bool_activity).Negation()),
3474 temp_ct_.mutable_linear());
3475 if (!PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_)) {
3476 return;
3477 }
3478 }
3479
3480 // Extract enforcement or fix literal.
3481 //
3482 // TODO(user): Do not use domain fonction, can be slow.
3483 //
3484 // TODO(user): Actually we might make the linear relaxation worse by
3485 // extracting some of these enforcement, as those can be "lifted" booleans. We
3486 // currently deal with that in RemoveEnforcementThatMakesConstraintTrivial(),
3487 // but that might not be the most efficient.
3488 //
3489 // TODO(user): Another reason for making the LP worse is that if we replace
3490 // part of the constraint via FindBig*LinearOverlap() then our activity bounds
3491 // might not be as precise when we will linearize this constraint again.
3492 std::vector<int> new_enforcement;
3493 std::vector<int> must_be_true;
3494 for (int i = 0; i < tmp_terms_.size(); ++i) {
3495 const int ref = tmp_terms_[i].first;
3496
3497 const Domain bool0(conditional_mins_[i][0], conditional_maxs_[i][0]);
3498 const Domain activity0 = bool0.AdditionWith(non_boolean_domain);
3499 if (activity0.IntersectionWith(rhs).IsEmpty()) {
3500 // Must be 1.
3501 must_be_true.push_back(ref);
3502 } else if (activity0.IsIncludedIn(rhs)) {
3503 // Trivial constraint on 0.
3504 new_enforcement.push_back(ref);
3505 }
3506
3507 const Domain bool1(conditional_mins_[i][1], conditional_maxs_[i][1]);
3508 const Domain activity1 = bool1.AdditionWith(non_boolean_domain);
3509 if (activity1.IntersectionWith(rhs).IsEmpty()) {
3510 // Must be 0.
3511 must_be_true.push_back(NegatedRef(ref));
3512 } else if (activity1.IsIncludedIn(rhs)) {
3513 // Trivial constraint on 1.
3514 new_enforcement.push_back(NegatedRef(ref));
3515 }
3516 }
3517
3518 // Note that both list can be non empty, if for instance we have small * X +
3519 // big * Y + ... <= rhs and amo(X, Y). We could see that Y can never be true
3520 // and if X is true, then the constraint could be trivial.
3521 //
3522 // So we fix things first if we can.
3523 if (ct->enforcement_literal().empty() && !must_be_true.empty()) {
3524 // Note that our logic to do more presolve iteration depends on the
3525 // number of rule applied, so it is important to count this correctly.
3526 context_->UpdateRuleStats("linear + amo: fixed literal",
3527 must_be_true.size());
3528 for (const int lit : must_be_true) {
3529 if (!context_->SetLiteralToTrue(lit)) return;
3530 }
3531 CanonicalizeLinear(ct);
3533 return;
3534 }
3535
3536 if (!new_enforcement.empty()) {
3537 context_->UpdateRuleStats("linear + amo: extracted enforcement literal",
3538 new_enforcement.size());
3539 for (const int ref : new_enforcement) {
3540 ct->add_enforcement_literal(ref);
3541 }
3542 }
3543
3544 if (!ct->enforcement_literal().empty()) {
3545 const int old_enf_size = ct->enforcement_literal().size();
3546 if (!helper->PresolveEnforcement(ct->linear().vars(), ct, &temp_set_)) {
3547 context_->UpdateRuleStats("linear + amo: infeasible enforcement");
3548 ct->Clear();
3550 return;
3551 }
3552 if (ct->enforcement_literal().size() < old_enf_size) {
3553 context_->UpdateRuleStats("linear + amo: simplified enforcement list");
3555 }
3556
3557 for (const int lit : must_be_true) {
3558 if (temp_set_.contains(NegatedRef(lit))) {
3559 // A literal must be true but is incompatible with what the enforcement
3560 // implies. The constraint must be false!
3561 context_->UpdateRuleStats(
3562 "linear + amo: advanced infeasible linear constraint");
3563 (void)MarkConstraintAsFalse(ct);
3565 return;
3566 }
3567 }
3568
3569 // TODO(user): do that in more cases?
3570 if (ct->enforcement_literal().size() == 1 && !must_be_true.empty()) {
3571 // Add implication, and remove literal from the constraint in this case.
3572 // To remove them, we just add them to temp_set_ and FixLiteralFromSet()
3573 // will take care of it.
3574 context_->UpdateRuleStats("linear + amo: added implications");
3575 ConstraintProto* new_ct = context_->working_model->add_constraints();
3576 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
3577 for (const int lit : must_be_true) {
3578 new_ct->mutable_bool_and()->add_literals(lit);
3579 temp_set_.insert(lit);
3580 }
3582 }
3583
3584 const int num_fixed = FixLiteralFromSet(temp_set_, ct->mutable_linear());
3585 if (num_fixed > new_enforcement.size()) {
3586 context_->UpdateRuleStats(
3587 "linear + amo: fixed literal implied by enforcement");
3588 }
3589 if (num_fixed > 0) {
3591 }
3592 }
3593
3594 if (ct->linear().vars().empty()) {
3595 context_->UpdateRuleStats("linear + amo: empty after processing");
3596 PresolveSmallLinear(ct);
3598 return;
3599 }
3600
3601 // If the constraint is of size 1 or 2, we re-presolve it right away.
3602 if (initial_size != ct->linear().vars().size() && PresolveSmallLinear(ct)) {
3604 if (ct->constraint_case() != ConstraintProto::kLinear) return;
3605 }
3606
3607 // Detect enforcement literal that could actually be lifted, and as such can
3608 // just be removed from the enforcement list. Ideally, during relaxation we
3609 // would lift such Boolean again.
3610 //
3611 // Note that this code is independent from anything above.
3612 if (!ct->enforcement_literal().empty()) {
3613 // TODO(user): remove duplication with code above?
3614 tmp_terms_.clear();
3615 Domain non_boolean_domain(0);
3616 const int num_ct_terms = ct->linear().vars().size();
3617 for (int i = 0; i < num_ct_terms; ++i) {
3618 const int ref = ct->linear().vars(i);
3619 const int64_t coeff = ct->linear().coeffs(i);
3620 CHECK(RefIsPositive(ref));
3621 if (context_->CanBeUsedAsLiteral(ref)) {
3622 tmp_terms_.push_back({ref, coeff});
3623 } else {
3624 non_boolean_domain =
3625 non_boolean_domain
3626 .AdditionWith(
3627 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
3628 .RelaxIfTooComplex();
3629 }
3630 }
3631 const int num_removed = helper->RemoveEnforcementThatMakesConstraintTrivial(
3632 tmp_terms_, non_boolean_domain, ReadDomainFromProto(ct->linear()), ct);
3633 if (num_removed > 0) {
3634 context_->UpdateRuleStats("linear + amo: removed enforcement literal",
3635 num_removed);
3637 }
3638 }
3639}
3640
3641bool CpModelPresolver::PropagateDomainsInLinear(int ct_index,
3642 ConstraintProto* ct) {
3643 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
3644 if (context_->ModelIsUnsat()) return false;
3645
3646 // For fast mode.
3647 int64_t min_activity;
3648 int64_t max_activity;
3649
3650 // For slow mode.
3651 const int num_vars = ct->linear().vars_size();
3652 auto& term_domains = context_->tmp_term_domains;
3653 auto& left_domains = context_->tmp_left_domains;
3654 const bool slow_mode = num_vars < 10;
3655
3656 // Compute the implied rhs bounds from the variable ones.
3657 if (slow_mode) {
3658 term_domains.resize(num_vars + 1);
3659 left_domains.resize(num_vars + 1);
3660 left_domains[0] = Domain(0);
3661 term_domains[num_vars] = Domain(0);
3662 for (int i = 0; i < num_vars; ++i) {
3663 const int var = ct->linear().vars(i);
3664 const int64_t coeff = ct->linear().coeffs(i);
3665 DCHECK(RefIsPositive(var));
3666 term_domains[i] = context_->DomainOf(var).MultiplicationBy(coeff);
3667 left_domains[i + 1] =
3668 left_domains[i].AdditionWith(term_domains[i]).RelaxIfTooComplex();
3669 }
3670 } else {
3671 std::tie(min_activity, max_activity) =
3672 context_->ComputeMinMaxActivity(ct->linear());
3673 }
3674 const Domain& implied_rhs =
3675 slow_mode ? left_domains[num_vars] : Domain(min_activity, max_activity);
3676
3677 // Abort if trivial.
3678 const Domain old_rhs = ReadDomainFromProto(ct->linear());
3679 if (implied_rhs.IsIncludedIn(old_rhs)) {
3680 if (ct_index != -1) context_->UpdateRuleStats("linear: always true");
3681 return RemoveConstraint(ct);
3682 }
3683
3684 // Incorporate the implied rhs information.
3685 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
3686 if (rhs.IsEmpty()) {
3687 context_->UpdateRuleStats("linear: infeasible");
3688 return MarkConstraintAsFalse(ct);
3689 }
3690 if (rhs != old_rhs) {
3691 if (ct_index != -1) context_->UpdateRuleStats("linear: simplified rhs");
3692 }
3693 FillDomainInProto(rhs, ct->mutable_linear());
3694
3695 // Propagate the variable bounds.
3696 if (ct->enforcement_literal().size() > 1) return false;
3697
3698 bool new_bounds = false;
3699 bool recanonicalize = false;
3700 Domain negated_rhs = rhs.Negation();
3701 Domain right_domain(0);
3702 Domain new_domain;
3703 Domain activity_minus_term;
3704 for (int i = num_vars - 1; i >= 0; --i) {
3705 const int var = ct->linear().vars(i);
3706 const int64_t var_coeff = ct->linear().coeffs(i);
3707
3708 if (slow_mode) {
3709 right_domain =
3710 right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
3711 activity_minus_term = left_domains[i].AdditionWith(right_domain);
3712 } else {
3713 int64_t min_term = var_coeff * context_->MinOf(var);
3714 int64_t max_term = var_coeff * context_->MaxOf(var);
3715 if (var_coeff < 0) std::swap(min_term, max_term);
3716 activity_minus_term =
3717 Domain(min_activity - min_term, max_activity - max_term);
3718 }
3719 new_domain = activity_minus_term.AdditionWith(negated_rhs)
3720 .InverseMultiplicationBy(-var_coeff);
3721
3722 if (ct->enforcement_literal().empty()) {
3723 // Push the new domain.
3724 if (!context_->IntersectDomainWith(var, new_domain, &new_bounds)) {
3725 return true;
3726 }
3727 } else if (ct->enforcement_literal().size() == 1) {
3728 // We cannot push the new domain, but we can add some deduction.
3729 CHECK(RefIsPositive(var));
3730 if (!context_->DomainOfVarIsIncludedIn(var, new_domain)) {
3731 context_->deductions.AddDeduction(ct->enforcement_literal(0), var,
3732 new_domain);
3733 }
3734 }
3735
3736 if (context_->IsFixed(var)) {
3737 // This will make sure we remove that fixed variable from the constraint.
3738 recanonicalize = true;
3739 continue;
3740 }
3741
3742 // The other transformations below require a non-reified constraint.
3743 if (ct_index == -1) continue;
3744 if (!ct->enforcement_literal().empty()) continue;
3745
3746 // Given a variable that only appear in one constraint and in the
3747 // objective, for any feasible solution, it will be always better to move
3748 // this singleton variable as much as possible towards its good objective
3749 // direction. Sometime, we can detect that we will always be able to
3750 // do this until the only constraint of this singleton variable is tight.
3751 //
3752 // When this happens, we can make the constraint an equality. Note that it
3753 // might not always be good to restrict constraint like this, but in this
3754 // case, the RemoveSingletonInLinear() code should be able to remove this
3755 // variable altogether.
3756 if (rhs.Min() != rhs.Max() &&
3758 const int64_t obj_coeff = context_->ObjectiveMap().at(var);
3759 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
3760 bool fixed = false;
3761 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->DomainOf(var),
3762 activity_minus_term, rhs)) {
3763 rhs = Domain(rhs.Min());
3764 fixed = true;
3765 }
3766 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->DomainOf(var),
3767 activity_minus_term, rhs)) {
3768 rhs = Domain(rhs.Max());
3769 fixed = true;
3770 }
3771 if (fixed) {
3772 context_->UpdateRuleStats("linear: tightened into equality");
3773 FillDomainInProto(rhs, ct->mutable_linear());
3774 negated_rhs = rhs.Negation();
3775
3776 // Restart the loop.
3777 i = num_vars;
3778 right_domain = Domain(0);
3779 continue;
3780 }
3781 }
3782
3783 // Can we perform some substitution?
3784 //
3785 // TODO(user): there is no guarantee we will not miss some since we might
3786 // not reprocess a constraint once other have been deleted.
3787
3788 // Skip affine constraint. It is more efficient to substitute them lazily
3789 // when we process other constraints. Note that if we relax the fact that
3790 // we substitute only equalities, we can deal with inequality of size 2
3791 // here.
3792 if (ct->linear().vars().size() <= 2) continue;
3793
3794 // TODO(user): We actually do not need a strict equality when
3795 // keep_all_feasible_solutions is false, but that simplifies things as the
3796 // SubstituteVariable() function cannot fail this way.
3797 if (rhs.Min() != rhs.Max()) continue;
3798
3799 // NOTE: The mapping doesn't allow us to remove a variable if
3800 // 'keep_all_feasible_solutions' is true.
3801 if (context_->keep_all_feasible_solutions) continue;
3802
3803 // Only consider "implied free" variables. Note that the coefficient of
3804 // magnitude 1 is important otherwise we can't easily remove the
3805 // constraint since the fact that the sum of the other terms must be a
3806 // multiple of coeff will not be enforced anymore.
3807 if (std::abs(var_coeff) != 1) continue;
3808 if (context_->params().presolve_substitution_level() <= 0) continue;
3809
3810 // Only consider substitution that reduce the number of entries.
3811 const bool is_in_objective = context_->VarToConstraints(var).contains(-1);
3812 {
3813 int col_size = context_->VarToConstraints(var).size();
3814 if (is_in_objective) col_size--;
3815 const int row_size = ct->linear().vars_size();
3816
3817 // This is actually an upper bound on the number of entries added since
3818 // some of them might already be present.
3819 const int num_entries_added = (row_size - 1) * (col_size - 1);
3820 const int num_entries_removed = col_size + row_size - 1;
3821 if (num_entries_added > num_entries_removed) continue;
3822 }
3823
3824 // Check pre-conditions on all the constraints in which this variable
3825 // appear. Basically they must all be linear.
3826 std::vector<int> others;
3827 bool abort = false;
3828 for (const int c : context_->VarToConstraints(var)) {
3829 if (c == kObjectiveConstraint) continue;
3830 if (c == kAffineRelationConstraint) {
3831 abort = true;
3832 break;
3833 }
3834 if (c == ct_index) continue;
3835 if (context_->working_model->constraints(c).constraint_case() !=
3836 ConstraintProto::kLinear) {
3837 abort = true;
3838 break;
3839 }
3840 for (const int ref :
3841 context_->working_model->constraints(c).enforcement_literal()) {
3842 if (PositiveRef(ref) == var) {
3843 abort = true;
3844 break;
3845 }
3846 }
3847 if (abort) break;
3848 others.push_back(c);
3849 }
3850 if (abort) continue;
3851
3852 // If the domain implied by this constraint is the same as the current
3853 // domain of the variable, this variable is implied free. Otherwise, we
3854 // check if the intersection with the domain implied by another constraint
3855 // make it implied free.
3856 if (context_->DomainOf(var) != new_domain) {
3857 // We only do that for doubleton because we don't want the propagation to
3858 // be less strong. If we were to replace this variable in other constraint
3859 // the implied bound from the linear expression might not be as good.
3860 //
3861 // TODO(user): We still substitute even if this happens in the objective
3862 // though. Is that good?
3863 if (others.size() != 1) continue;
3864 const ConstraintProto& other_ct =
3865 context_->working_model->constraints(others.front());
3866 if (!other_ct.enforcement_literal().empty()) continue;
3867
3868 // Compute the implied domain using the other constraint.
3869 // We only do that if it is not too long to avoid quadratic worst case.
3870 const LinearConstraintProto& other_lin = other_ct.linear();
3871 if (other_lin.vars().size() > 100) continue;
3872 Domain implied = ReadDomainFromProto(other_lin);
3873 int64_t other_coeff = 0;
3874 for (int i = 0; i < other_lin.vars().size(); ++i) {
3875 const int v = other_lin.vars(i);
3876 const int64_t coeff = other_lin.coeffs(i);
3877 if (v == var) {
3878 // It is possible the constraint is not canonical if it wasn't
3879 // processed yet !
3880 other_coeff += coeff;
3881 } else {
3882 implied =
3883 implied
3884 .AdditionWith(context_->DomainOf(v).MultiplicationBy(-coeff))
3885 .RelaxIfTooComplex();
3886 }
3887 }
3888 if (other_coeff == 0) continue;
3889 implied = implied.InverseMultiplicationBy(other_coeff);
3890
3891 // Since we compute it, we can as well update the domain right now.
3892 // This is also needed for postsolve to have a tight domain.
3893 if (!context_->IntersectDomainWith(var, implied)) return false;
3894 if (context_->IsFixed(var)) continue;
3895 if (new_domain.IntersectionWith(implied) != context_->DomainOf(var)) {
3896 continue;
3897 }
3898
3899 context_->UpdateRuleStats("linear: doubleton free");
3900 }
3901
3902 // Substitute in objective.
3903 // This can fail in overflow corner cases, so we abort before doing any
3904 // actual changes.
3905 if (is_in_objective &&
3906 !context_->SubstituteVariableInObjective(var, var_coeff, *ct)) {
3907 continue;
3908 }
3909
3910 // Do the actual substitution.
3911 ConstraintProto copy_if_we_abort;
3912 absl::c_sort(others);
3913 for (const int c : others) {
3914 // TODO(user): The copy is needed to have a simpler overflow-checking
3915 // code were we check once the substitution is done. If needed we could
3916 // optimize that, but with more code.
3917 copy_if_we_abort = context_->working_model->constraints(c);
3918
3919 // In some corner cases, this might violate our overflow precondition or
3920 // even create an overflow. The danger is limited since the range of the
3921 // linear expression used in the definition do not exceed the domain of
3922 // the variable we substitute. But this is not the case for the doubleton
3923 // case above.
3924 if (!SubstituteVariable(
3925 var, var_coeff, *ct,
3926 context_->working_model->mutable_constraints(c))) {
3927 // The function above can fail because of overflow, but also if the
3928 // constraint was not canonicalized yet and the variable is actually not
3929 // there (we have var - var for instance).
3930 //
3931 // TODO(user): we canonicalize it right away, but I am not sure it is
3932 // really needed.
3933 if (CanonicalizeLinear(
3934 context_->working_model->mutable_constraints(c))) {
3935 context_->UpdateConstraintVariableUsage(c);
3936 }
3937 abort = true;
3938 break;
3939 }
3940
3942 *context_->working_model,
3943 context_->working_model->constraints(c).linear().vars(),
3944 context_->working_model->constraints(c).linear().coeffs())) {
3945 // Revert the change in this case.
3946 *context_->working_model->mutable_constraints(c) = copy_if_we_abort;
3947 abort = true;
3948 break;
3949 }
3950
3951 // TODO(user): We should re-enqueue these constraints for presolve.
3952 context_->UpdateConstraintVariableUsage(c);
3953 }
3954 if (abort) continue;
3955
3956 context_->UpdateRuleStats(
3957 absl::StrCat("linear: variable substitution ", others.size()));
3958
3959 // The variable now only appear in its definition and we can remove it
3960 // because it was implied free.
3961 //
3962 // Tricky: If the linear constraint contains other variables that are only
3963 // used here, then the postsolve needs more info. We do need to indicate
3964 // that whatever the value of those other variables, we will have a way to
3965 // assign var. We do that by putting it fist.
3966 CHECK_EQ(context_->VarToConstraints(var).size(), 1);
3967 context_->MarkVariableAsRemoved(var);
3968 ConstraintProto* mapping_ct =
3969 context_->NewMappingConstraint(__FILE__, __LINE__);
3970 *mapping_ct = *ct;
3971 LinearConstraintProto* mapping_linear_ct = mapping_ct->mutable_linear();
3972 std::swap(mapping_linear_ct->mutable_vars()->at(0),
3973 mapping_linear_ct->mutable_vars()->at(i));
3974 std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
3975 mapping_linear_ct->mutable_coeffs()->at(i));
3976 return RemoveConstraint(ct);
3977 }
3978
3979 // special case.
3980 if (ct_index == -1) {
3981 if (new_bounds) {
3982 context_->UpdateRuleStats(
3983 "linear: reduced variable domains in derived constraint");
3984 }
3985 return false;
3986 }
3987
3988 if (new_bounds) {
3989 context_->UpdateRuleStats("linear: reduced variable domains");
3990 }
3991 if (recanonicalize) return CanonicalizeLinear(ct);
3992 return false;
3993}
3994
3995// The constraint from its lower value is sum positive_coeff * X <= rhs.
3996// If from_lower_bound is false, then it is the constraint from its upper value.
3997void CpModelPresolver::LowerThanCoeffStrengthening(bool from_lower_bound,
3998 int64_t min_magnitude,
3999 int64_t rhs,
4000 ConstraintProto* ct) {
4001 const LinearConstraintProto& arg = ct->linear();
4002 const int64_t second_threshold = rhs - min_magnitude;
4003 const int num_vars = arg.vars_size();
4004
4005 // Special case:
4006 // - The terms above rhs must be fixed to zero.
4007 // - The terms in (second_threshold, rhs] can be fixed to rhs as
4008 // they will force all other terms to zero if not at zero themselves.
4009 // - If what is left can be simplified to a single coefficient, we can
4010 // put the constraint into a special form.
4011 //
4012 // TODO(user): More generally, if we ignore term that set everything else to
4013 // zero, we can preprocess the constraint left and then add them back. So we
4014 // can do all our other reduction like normal GCD or more advanced ones like
4015 // DP based or approximate GCD.
4016 if (min_magnitude <= second_threshold) {
4017 // Compute max_magnitude for the term <= second_threshold.
4018 int64_t max_magnitude_left = 0;
4019 int64_t max_activity_left = 0;
4020 int64_t activity_when_coeff_are_one = 0;
4021 int64_t gcd = 0;
4022 for (int i = 0; i < num_vars; ++i) {
4023 const int64_t magnitude = std::abs(arg.coeffs(i));
4024 if (magnitude <= second_threshold) {
4025 gcd = MathUtil::GCD64(gcd, magnitude);
4026 max_magnitude_left = std::max(max_magnitude_left, magnitude);
4027 const int64_t bound_diff =
4028 context_->MaxOf(arg.vars(i)) - context_->MinOf(arg.vars(i));
4029 activity_when_coeff_are_one += bound_diff;
4030 max_activity_left += magnitude * bound_diff;
4031 }
4032 }
4033 CHECK_GT(min_magnitude, 0);
4034 CHECK_LE(min_magnitude, max_magnitude_left);
4035
4036 // Not considering the variable that set everyone at zero when true:
4037 int64_t new_rhs = 0;
4038 bool set_all_to_one = false;
4039 if (max_activity_left <= rhs) {
4040 // We are left with a trivial constraint.
4041 context_->UpdateRuleStats("linear with partial amo: trivial");
4042 new_rhs = activity_when_coeff_are_one;
4043 set_all_to_one = true;
4044 } else if (rhs / min_magnitude == rhs / max_magnitude_left) {
4045 // We are left with a sum <= new_rhs constraint.
4046 context_->UpdateRuleStats("linear with partial amo: constant coeff");
4047 new_rhs = rhs / min_magnitude;
4048 set_all_to_one = true;
4049 } else if (gcd > 1) {
4050 // We are left with a constraint that can be simplified by gcd.
4051 context_->UpdateRuleStats("linear with partial amo: gcd");
4052 new_rhs = rhs / gcd;
4053 }
4054
4055 if (new_rhs > 0) {
4056 int64_t rhs_offset = 0;
4057 for (int i = 0; i < num_vars; ++i) {
4058 const int ref = arg.vars(i);
4059 const int64_t coeff = from_lower_bound ? arg.coeffs(i) : -arg.coeffs(i);
4060
4061 int64_t new_coeff;
4062 const int64_t magnitude = std::abs(coeff);
4063 if (magnitude > rhs) {
4064 new_coeff = new_rhs + 1;
4065 } else if (magnitude > second_threshold) {
4066 new_coeff = new_rhs;
4067 } else {
4068 new_coeff = set_all_to_one ? 1 : magnitude / gcd;
4069 }
4070
4071 // In the transformed domain we will always have
4072 // magnitude * (var - lb) or magnitude * (ub - var)
4073 if (coeff > 0) {
4074 ct->mutable_linear()->set_coeffs(i, new_coeff);
4075 rhs_offset += new_coeff * context_->MinOf(ref);
4076 } else {
4077 ct->mutable_linear()->set_coeffs(i, -new_coeff);
4078 rhs_offset -= new_coeff * context_->MaxOf(ref);
4079 }
4080 }
4081 FillDomainInProto(Domain(rhs_offset, new_rhs + rhs_offset),
4082 ct->mutable_linear());
4083 return;
4084 }
4085 }
4086
4087 int64_t rhs_offset = 0;
4088 for (int i = 0; i < num_vars; ++i) {
4089 int ref = arg.vars(i);
4090 int64_t coeff = arg.coeffs(i);
4091 if (coeff < 0) {
4092 ref = NegatedRef(ref);
4093 coeff = -coeff;
4094 }
4095
4096 if (coeff > rhs) {
4097 if (ct->enforcement_literal().empty()) {
4098 // Shifted variable must be zero.
4099 //
4100 // TODO(user): Note that here IntersectDomainWith() can only return
4101 // false if for some reason this variable has an affine representative
4102 // for which this fail. Ideally we should always replace/merge
4103 // representative right away, but this is a bit difficult to enforce
4104 // currently.
4105 context_->UpdateRuleStats("linear: fix variable to its bound.");
4106 if (!context_->IntersectDomainWith(
4107 ref, Domain(from_lower_bound ? context_->MinOf(ref)
4108 : context_->MaxOf(ref)))) {
4109 return;
4110 }
4111 }
4112
4113 // TODO(user): What to do with the coeff if there is enforcement?
4114 continue;
4115 }
4116 if (coeff > second_threshold && coeff < rhs) {
4117 context_->UpdateRuleStats(
4118 "linear: coefficient strengthening by increasing it.");
4119 if (from_lower_bound) {
4120 // coeff * (X - LB + LB) -> rhs * (X - LB) + coeff * LB
4121 rhs_offset -= (coeff - rhs) * context_->MinOf(ref);
4122 } else {
4123 // coeff * (X - UB + UB) -> rhs * (X - UB) + coeff * UB
4124 rhs_offset -= (coeff - rhs) * context_->MaxOf(ref);
4125 }
4126 ct->mutable_linear()->set_coeffs(i, arg.coeffs(i) > 0 ? rhs : -rhs);
4127 }
4128 }
4129 if (rhs_offset != 0) {
4130 FillDomainInProto(ReadDomainFromProto(arg).AdditionWith(Domain(rhs_offset)),
4131 ct->mutable_linear());
4132 }
4133}
4134
4135// Identify Boolean variable that makes the constraint always true when set to
4136// true or false. Moves such literal to the constraint enforcement literals
4137// list.
4138//
4139// We also generalize this to integer variable at one of their bound.
4140//
4141// This operation is similar to coefficient strengthening in the MIP world.
4142void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
4143 int ct_index, ConstraintProto* ct) {
4144 if (ct->constraint_case() != ConstraintProto::kLinear) return;
4145 if (context_->ModelIsUnsat()) return;
4146
4147 const LinearConstraintProto& arg = ct->linear();
4148 const int num_vars = arg.vars_size();
4149
4150 // No need to process size one constraints, they will be presolved separately.
4151 // We also do not want to split them in two.
4152 if (num_vars <= 1) return;
4153
4154 int64_t min_sum = 0;
4155 int64_t max_sum = 0;
4156 int64_t max_coeff_magnitude = 0;
4157 int64_t min_coeff_magnitude = std::numeric_limits<int64_t>::max();
4158 for (int i = 0; i < num_vars; ++i) {
4159 const int ref = arg.vars(i);
4160 const int64_t coeff = arg.coeffs(i);
4161 if (coeff > 0) {
4162 max_coeff_magnitude = std::max(max_coeff_magnitude, coeff);
4163 min_coeff_magnitude = std::min(min_coeff_magnitude, coeff);
4164 min_sum += coeff * context_->MinOf(ref);
4165 max_sum += coeff * context_->MaxOf(ref);
4166 } else {
4167 max_coeff_magnitude = std::max(max_coeff_magnitude, -coeff);
4168 min_coeff_magnitude = std::min(min_coeff_magnitude, -coeff);
4169 min_sum += coeff * context_->MaxOf(ref);
4170 max_sum += coeff * context_->MinOf(ref);
4171 }
4172 }
4173 if (max_coeff_magnitude == 1) return;
4174
4175 // We can only extract enforcement literals if the maximum coefficient
4176 // magnitude is large enough. Note that we handle complex domain.
4177 //
4178 // TODO(user): Depending on how we split below, the threshold are not the
4179 // same. This is maybe not too important, we just don't split as often as we
4180 // could, but it is still unclear if splitting is good.
4181 const auto& domain = ct->linear().domain();
4182 const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
4183 const int64_t lb_threshold = max_sum - domain[1];
4184 if (max_coeff_magnitude + min_coeff_magnitude <
4185 std::max(ub_threshold, lb_threshold)) {
4186 // We also have other kind of coefficient strengthening.
4187 // In something like 3x + 5y <= 6, the coefficient 5 can be changed to 6.
4188 // And in 5x + 12y <= 12, the coeff 5 can be changed to 6 (not sure how to
4189 // generalize this one).
4190 if (domain.size() == 2 && min_coeff_magnitude > 1 &&
4191 min_coeff_magnitude < max_coeff_magnitude) {
4192 const int64_t rhs_min = domain[0];
4193 const int64_t rhs_max = domain[1];
4194 if (min_sum >= rhs_min &&
4195 max_coeff_magnitude + min_coeff_magnitude > rhs_max - min_sum) {
4196 LowerThanCoeffStrengthening(/*from_lower_bound=*/true,
4197 min_coeff_magnitude, rhs_max - min_sum, ct);
4198 return;
4199 }
4200 if (max_sum <= rhs_max &&
4201 max_coeff_magnitude + min_coeff_magnitude > max_sum - rhs_min) {
4202 LowerThanCoeffStrengthening(/*from_lower_bound=*/false,
4203 min_coeff_magnitude, max_sum - rhs_min, ct);
4204 return;
4205 }
4206 }
4207 }
4208
4209 // We need the constraint to be only bounded on one side in order to extract
4210 // enforcement literal.
4211 //
4212 // If it is boxed and we know that some coefficient are big enough (see test
4213 // above), then we split the constraint in two. That might not seems always
4214 // good, but for the CP propagation engine, we don't loose anything by doing
4215 // so, and for the LP we will regroup the constraints if they still have the
4216 // exact same coeff after the presolve.
4217 //
4218 // TODO(user): Creating two new constraints and removing the current one might
4219 // not be the most efficient, but it simplify the presolve code by not having
4220 // to do anything special to trigger a new presolving of these constraints.
4221 // Try to improve if this becomes a problem.
4222 const Domain rhs_domain = ReadDomainFromProto(ct->linear());
4223 const bool lower_bounded = min_sum < rhs_domain.Min();
4224 const bool upper_bounded = max_sum > rhs_domain.Max();
4225 if (!lower_bounded && !upper_bounded) return;
4226 if (lower_bounded && upper_bounded) {
4227 // We disable this for now.
4228 if (true) return;
4229
4230 // Lets not split except if we extract enforcement.
4231 if (max_coeff_magnitude < std::max(ub_threshold, lb_threshold)) return;
4232
4233 context_->UpdateRuleStats("linear: split boxed constraint");
4234 ConstraintProto* new_ct1 = context_->working_model->add_constraints();
4235 *new_ct1 = *ct;
4236 if (!ct->name().empty()) {
4237 new_ct1->set_name(absl::StrCat(ct->name(), " (part 1)"));
4238 }
4239 FillDomainInProto(Domain(min_sum, rhs_domain.Max()),
4240 new_ct1->mutable_linear());
4241
4242 ConstraintProto* new_ct2 = context_->working_model->add_constraints();
4243 *new_ct2 = *ct;
4244 if (!ct->name().empty()) {
4245 new_ct2->set_name(absl::StrCat(ct->name(), " (part 2)"));
4246 }
4247 FillDomainInProto(rhs_domain.UnionWith(Domain(rhs_domain.Max(), max_sum)),
4248 new_ct2->mutable_linear());
4249
4251 ct->Clear();
4253 return;
4254 }
4255
4256 // Any coefficient greater than this will cause the constraint to be trivially
4257 // satisfied when the variable move away from its bound. Note that as we
4258 // remove coefficient, the threshold do not change!
4259 const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
4260
4261 // All coeffs in [second_threshold, threshold) can be reduced to
4262 // second_threshold.
4263 //
4264 // TODO(user): If 2 * min_coeff_magnitude >= bound, then the constraint can
4265 // be completely rewriten to 2 * (enforcement_part) + sum var >= 2 which is
4266 // what happen eventually when bound is even, but not if it is odd currently.
4267 int64_t second_threshold = std::max(CeilOfRatio(threshold, int64_t{2}),
4268 threshold - min_coeff_magnitude);
4269
4270 // Tricky: The second threshold only work if the domain is simple. If the
4271 // domain has holes, changing the coefficient might change whether the
4272 // variable can be at one or not by herself.
4273 //
4274 // TODO(user): We could still reduce it to the smaller value with same
4275 // feasibility.
4276 if (rhs_domain.NumIntervals() > 1) {
4277 second_threshold = threshold; // Disable.
4278 }
4279
4280 // Do we only extract Booleans?
4281 //
4282 // Note that for now the default is false, and also there are problem calling
4283 // GetOrCreateVarValueEncoding() after expansion because we might have removed
4284 // the variable used in the encoding.
4285 const bool only_extract_booleans =
4286 !context_->params().presolve_extract_integer_enforcement() ||
4287 context_->ModelIsExpanded();
4288
4289 // To avoid a quadratic loop, we will rewrite the linear expression at the
4290 // same time as we extract enforcement literals.
4291 int new_size = 0;
4292 int64_t rhs_offset = 0;
4293 bool some_integer_encoding_were_extracted = false;
4294 LinearConstraintProto* mutable_arg = ct->mutable_linear();
4295 for (int i = 0; i < arg.vars_size(); ++i) {
4296 int ref = arg.vars(i);
4297 int64_t coeff = arg.coeffs(i);
4298 if (coeff < 0) {
4299 ref = NegatedRef(ref);
4300 coeff = -coeff;
4301 }
4302
4303 // TODO(user): If the encoding Boolean already exist, we could extract
4304 // the non-Boolean enforcement term.
4305 const bool is_boolean = context_->CanBeUsedAsLiteral(ref);
4306 if (context_->IsFixed(ref) || coeff < threshold ||
4307 (only_extract_booleans && !is_boolean)) {
4308 mutable_arg->set_vars(new_size, mutable_arg->vars(i));
4309
4310 int64_t new_magnitude = std::abs(arg.coeffs(i));
4311 if (coeff > threshold) {
4312 // We keep this term but reduces its coeff.
4313 // This is only for the case where only_extract_booleans == true.
4314 new_magnitude = threshold;
4315 context_->UpdateRuleStats("linear: coefficient strenghtening.");
4316 } else if (coeff > second_threshold && coeff < threshold) {
4317 // This cover the special case where one big + on small is enough
4318 // to satisfy the constraint, we can reduce the big.
4319 new_magnitude = second_threshold;
4320 context_->UpdateRuleStats(
4321 "linear: advanced coefficient strenghtening.");
4322 }
4323 if (coeff != new_magnitude) {
4324 if (lower_bounded) {
4325 // coeff * (X - LB + LB) -> new_magnitude * (X - LB) + coeff * LB
4326 rhs_offset -= (coeff - new_magnitude) * context_->MinOf(ref);
4327 } else {
4328 // coeff * (X - UB + UB) -> new_magnitude * (X - UB) + coeff * UB
4329 rhs_offset -= (coeff - new_magnitude) * context_->MaxOf(ref);
4330 }
4331 }
4332
4333 mutable_arg->set_coeffs(
4334 new_size, arg.coeffs(i) > 0 ? new_magnitude : -new_magnitude);
4335 ++new_size;
4336 continue;
4337 }
4338
4339 if (is_boolean) {
4340 context_->UpdateRuleStats("linear: extracted enforcement literal");
4341 } else {
4342 some_integer_encoding_were_extracted = true;
4343 context_->UpdateRuleStats(
4344 "linear: extracted integer enforcement literal");
4345 }
4346 if (lower_bounded) {
4347 ct->add_enforcement_literal(is_boolean
4348 ? NegatedRef(ref)
4349 : context_->GetOrCreateVarValueEncoding(
4350 ref, context_->MinOf(ref)));
4351 rhs_offset -= coeff * context_->MinOf(ref);
4352 } else {
4353 ct->add_enforcement_literal(is_boolean
4354 ? ref
4355 : context_->GetOrCreateVarValueEncoding(
4356 ref, context_->MaxOf(ref)));
4357 rhs_offset -= coeff * context_->MaxOf(ref);
4358 }
4359 }
4360 mutable_arg->mutable_vars()->Truncate(new_size);
4361 mutable_arg->mutable_coeffs()->Truncate(new_size);
4362 FillDomainInProto(rhs_domain.AdditionWith(Domain(rhs_offset)), mutable_arg);
4363 if (some_integer_encoding_were_extracted || new_size == 1) {
4366 }
4367}
4368
4369void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto* ct) {
4370 if (context_->ModelIsUnsat()) return;
4371 if (HasEnforcementLiteral(*ct)) return;
4372 const Domain rhs = ReadDomainFromProto(ct->linear());
4373
4374 const LinearConstraintProto& arg = ct->linear();
4375 const int num_vars = arg.vars_size();
4376 int64_t min_sum = 0;
4377 int64_t max_sum = 0;
4378 for (int i = 0; i < num_vars; ++i) {
4379 const int ref = arg.vars(i);
4380 const int64_t coeff = arg.coeffs(i);
4381 const int64_t term_a = coeff * context_->MinOf(ref);
4382 const int64_t term_b = coeff * context_->MaxOf(ref);
4383 min_sum += std::min(term_a, term_b);
4384 max_sum += std::max(term_a, term_b);
4385 }
4386 for (const int type : {0, 1}) {
4387 std::vector<int> at_most_one;
4388 for (int i = 0; i < num_vars; ++i) {
4389 const int ref = arg.vars(i);
4390 const int64_t coeff = arg.coeffs(i);
4391 if (context_->MinOf(ref) != 0) continue;
4392 if (context_->MaxOf(ref) != 1) continue;
4393
4394 if (type == 0) {
4395 // TODO(user): we could add one more Boolean with a lower coeff as long
4396 // as we have lower_coeff + min_of_other_coeff > rhs.Max().
4397 if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
4398 at_most_one.push_back(coeff > 0 ? ref : NegatedRef(ref));
4399 }
4400 } else {
4401 if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
4402 at_most_one.push_back(coeff > 0 ? NegatedRef(ref) : ref);
4403 }
4404 }
4405 }
4406 if (at_most_one.size() > 1) {
4407 if (type == 0) {
4408 context_->UpdateRuleStats("linear: extracted at most one (max).");
4409 } else {
4410 context_->UpdateRuleStats("linear: extracted at most one (min).");
4411 }
4412 ConstraintProto* new_ct = context_->working_model->add_constraints();
4413 new_ct->set_name(ct->name());
4414 for (const int ref : at_most_one) {
4415 new_ct->mutable_at_most_one()->add_literals(ref);
4416 }
4418 }
4419 }
4420}
4421
4422// Convert some linear constraint involving only Booleans to their Boolean
4423// form.
4424bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto* ct) {
4425 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
4426 if (context_->ModelIsUnsat()) return false;
4427
4428 const LinearConstraintProto& arg = ct->linear();
4429 const int num_vars = arg.vars_size();
4430 int64_t min_coeff = std::numeric_limits<int64_t>::max();
4431 int64_t max_coeff = 0;
4432 int64_t min_sum = 0;
4433 int64_t max_sum = 0;
4434 for (int i = 0; i < num_vars; ++i) {
4435 // We assume we already ran PresolveLinear().
4436 const int var = arg.vars(i);
4437 const int64_t coeff = arg.coeffs(i);
4438 CHECK(RefIsPositive(var));
4439 CHECK_NE(coeff, 0);
4440 if (context_->MinOf(var) != 0) return false;
4441 if (context_->MaxOf(var) != 1) return false;
4442
4443 if (coeff > 0) {
4444 max_sum += coeff;
4445 min_coeff = std::min(min_coeff, coeff);
4446 max_coeff = std::max(max_coeff, coeff);
4447 } else {
4448 // We replace the Boolean ref, by a ref to its negation (1 - x).
4449 min_sum += coeff;
4450 min_coeff = std::min(min_coeff, -coeff);
4451 max_coeff = std::max(max_coeff, -coeff);
4452 }
4453 }
4454 CHECK_LE(min_coeff, max_coeff);
4455
4456 // Detect trivially true/false constraints. Note that this is not necessarily
4457 // detected by PresolveLinear(). We do that here because we assume below
4458 // that this cannot happen.
4459 //
4460 // TODO(user): this could be generalized to constraint not containing only
4461 // Booleans.
4462 const Domain rhs_domain = ReadDomainFromProto(arg);
4463 if ((!rhs_domain.Contains(min_sum) &&
4464 min_sum + min_coeff > rhs_domain.Max()) ||
4465 (!rhs_domain.Contains(max_sum) &&
4466 max_sum - min_coeff < rhs_domain.Min())) {
4467 context_->UpdateRuleStats("linear: all booleans and trivially false");
4468 return MarkConstraintAsFalse(ct);
4469 }
4470 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
4471 context_->UpdateRuleStats("linear: all booleans and trivially true");
4472 return RemoveConstraint(ct);
4473 }
4474
4475 // Detect clauses, reified ands, at_most_one.
4476 //
4477 // TODO(user): split a == 1 constraint or similar into a clause and an at
4478 // most one constraint?
4479 DCHECK(!rhs_domain.IsEmpty());
4480 if (min_sum + min_coeff > rhs_domain.Max()) {
4481 // All Boolean are false if the reified literal is true.
4482 context_->UpdateRuleStats("linear: negative reified and");
4483 const auto copy = arg;
4484 ct->mutable_bool_and()->clear_literals();
4485 for (int i = 0; i < num_vars; ++i) {
4486 ct->mutable_bool_and()->add_literals(
4487 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
4488 }
4489 PresolveBoolAnd(ct);
4490 return true;
4491 } else if (max_sum - min_coeff < rhs_domain.Min()) {
4492 // All Boolean are true if the reified literal is true.
4493 context_->UpdateRuleStats("linear: positive reified and");
4494 const auto copy = arg;
4495 ct->mutable_bool_and()->clear_literals();
4496 for (int i = 0; i < num_vars; ++i) {
4497 ct->mutable_bool_and()->add_literals(
4498 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
4499 }
4500 PresolveBoolAnd(ct);
4501 return true;
4502 } else if (min_sum + min_coeff >= rhs_domain.Min() &&
4503 rhs_domain.front().end >= max_sum) {
4504 // At least one Boolean is true.
4505 context_->UpdateRuleStats("linear: positive clause");
4506 const auto copy = arg;
4507 ct->mutable_bool_or()->clear_literals();
4508 for (int i = 0; i < num_vars; ++i) {
4509 ct->mutable_bool_or()->add_literals(
4510 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
4511 }
4512 PresolveBoolOr(ct);
4513 return true;
4514 } else if (max_sum - min_coeff <= rhs_domain.Max() &&
4515 rhs_domain.back().start <= min_sum) {
4516 // At least one Boolean is false.
4517 context_->UpdateRuleStats("linear: negative clause");
4518 const auto copy = arg;
4519 ct->mutable_bool_or()->clear_literals();
4520 for (int i = 0; i < num_vars; ++i) {
4521 ct->mutable_bool_or()->add_literals(
4522 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
4523 }
4524 PresolveBoolOr(ct);
4525 return true;
4526 } else if (!HasEnforcementLiteral(*ct) &&
4527 min_sum + max_coeff <= rhs_domain.Max() &&
4528 min_sum + 2 * min_coeff > rhs_domain.Max() &&
4529 rhs_domain.back().start <= min_sum) {
4530 // At most one Boolean is true.
4531 // TODO(user): Support enforced at most one.
4532 context_->UpdateRuleStats("linear: positive at most one");
4533 const auto copy = arg;
4534 ct->mutable_at_most_one()->clear_literals();
4535 for (int i = 0; i < num_vars; ++i) {
4536 ct->mutable_at_most_one()->add_literals(
4537 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
4538 }
4539 return true;
4540 } else if (!HasEnforcementLiteral(*ct) &&
4541 max_sum - max_coeff >= rhs_domain.Min() &&
4542 max_sum - 2 * min_coeff < rhs_domain.Min() &&
4543 rhs_domain.front().end >= max_sum) {
4544 // At most one Boolean is false.
4545 // TODO(user): Support enforced at most one.
4546 context_->UpdateRuleStats("linear: negative at most one");
4547 const auto copy = arg;
4548 ct->mutable_at_most_one()->clear_literals();
4549 for (int i = 0; i < num_vars; ++i) {
4550 ct->mutable_at_most_one()->add_literals(
4551 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
4552 }
4553 return true;
4554 } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
4555 min_sum < rhs_domain.Min() &&
4556 min_sum + min_coeff >= rhs_domain.Min() &&
4557 min_sum + 2 * min_coeff > rhs_domain.Max() &&
4558 min_sum + max_coeff <= rhs_domain.Max()) {
4559 // TODO(user): Support enforced exactly one.
4560 context_->UpdateRuleStats("linear: positive equal one");
4561 ConstraintProto* exactly_one = context_->working_model->add_constraints();
4562 exactly_one->set_name(ct->name());
4563 for (int i = 0; i < num_vars; ++i) {
4564 exactly_one->mutable_exactly_one()->add_literals(
4565 arg.coeffs(i) > 0 ? arg.vars(i) : NegatedRef(arg.vars(i)));
4566 }
4568 return RemoveConstraint(ct);
4569 } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
4570 max_sum > rhs_domain.Max() &&
4571 max_sum - min_coeff <= rhs_domain.Max() &&
4572 max_sum - 2 * min_coeff < rhs_domain.Min() &&
4573 max_sum - max_coeff >= rhs_domain.Min()) {
4574 // TODO(user): Support enforced exactly one.
4575 context_->UpdateRuleStats("linear: negative equal one");
4576 ConstraintProto* exactly_one = context_->working_model->add_constraints();
4577 exactly_one->set_name(ct->name());
4578 for (int i = 0; i < num_vars; ++i) {
4579 exactly_one->mutable_exactly_one()->add_literals(
4580 arg.coeffs(i) > 0 ? NegatedRef(arg.vars(i)) : arg.vars(i));
4581 }
4583 return RemoveConstraint(ct);
4584 }
4585
4586 // Expand small expression into clause.
4587 //
4588 // TODO(user): This is bad from a LP relaxation perspective. Do not do that
4589 // now? On another hand it is good for the SAT presolving.
4590 if (num_vars > 3) return false;
4591 context_->UpdateRuleStats("linear: small Boolean expression");
4592
4593 // Enumerate all possible value of the Booleans and add a clause if constraint
4594 // is false. TODO(user): the encoding could be made better in some cases.
4595 const int max_mask = (1 << arg.vars_size());
4596 for (int mask = 0; mask < max_mask; ++mask) {
4597 int64_t value = 0;
4598 for (int i = 0; i < num_vars; ++i) {
4599 if ((mask >> i) & 1) value += arg.coeffs(i);
4600 }
4601 if (rhs_domain.Contains(value)) continue;
4602
4603 // Add a new clause to exclude this bad assignment.
4604 ConstraintProto* new_ct = context_->working_model->add_constraints();
4605 auto* new_arg = new_ct->mutable_bool_or();
4606 if (HasEnforcementLiteral(*ct)) {
4607 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
4608 }
4609 for (int i = 0; i < num_vars; ++i) {
4610 new_arg->add_literals(((mask >> i) & 1) ? NegatedRef(arg.vars(i))
4611 : arg.vars(i));
4612 }
4613 }
4614
4616 return RemoveConstraint(ct);
4617}
4618
4619bool CpModelPresolver::PresolveInterval(int c, ConstraintProto* ct) {
4620 if (context_->ModelIsUnsat()) return false;
4621 IntervalConstraintProto* interval = ct->mutable_interval();
4622
4623 // If the size is < 0, then the interval cannot be performed.
4624 if (!ct->enforcement_literal().empty() && context_->SizeMax(c) < 0) {
4625 context_->UpdateRuleStats("interval: negative size implies unperformed");
4626 return MarkConstraintAsFalse(ct);
4627 }
4628
4629 if (ct->enforcement_literal().empty()) {
4630 bool domain_changed = false;
4631 // Size can't be negative.
4632 if (!context_->IntersectDomainWith(
4633 interval->size(), Domain(0, std::numeric_limits<int64_t>::max()),
4634 &domain_changed)) {
4635 return false;
4636 }
4637 if (domain_changed) {
4638 context_->UpdateRuleStats(
4639 "interval: performed intervals must have a positive size");
4640 }
4641 }
4642
4643 // Note that the linear relation is stored elsewhere, so it is safe to just
4644 // remove such special interval constraint.
4645 if (context_->ConstraintVariableGraphIsUpToDate() &&
4646 context_->IntervalUsage(c) == 0) {
4647 context_->UpdateRuleStats("intervals: removed unused interval");
4648 return RemoveConstraint(ct);
4649 }
4650
4651 bool changed = false;
4652 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_start());
4653 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_size());
4654 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_end());
4655 return changed;
4656}
4657
4658// TODO(user): avoid code duplication between expand and presolve.
4659bool CpModelPresolver::PresolveInverse(ConstraintProto* ct) {
4660 const int size = ct->inverse().f_direct().size();
4661 bool changed = false;
4662
4663 // Make sure the domains are included in [0, size - 1).
4664 for (const int ref : ct->inverse().f_direct()) {
4665 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
4666 VLOG(1) << "Empty domain for a variable in ExpandInverse()";
4667 return false;
4668 }
4669 }
4670 for (const int ref : ct->inverse().f_inverse()) {
4671 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
4672 VLOG(1) << "Empty domain for a variable in ExpandInverse()";
4673 return false;
4674 }
4675 }
4676
4677 // Detect duplicated variable.
4678 // Even with negated variables, the reduced domain in [0..size - 1]
4679 // implies that the constraint is infeasible if ref and its negation
4680 // appear together.
4681 {
4682 absl::flat_hash_set<int> direct_vars;
4683 for (const int ref : ct->inverse().f_direct()) {
4684 const auto [it, inserted] = direct_vars.insert(PositiveRef(ref));
4685 if (!inserted) {
4686 return context_->NotifyThatModelIsUnsat("inverse: duplicated variable");
4687 }
4688 }
4689
4690 absl::flat_hash_set<int> inverse_vars;
4691 for (const int ref : ct->inverse().f_inverse()) {
4692 const auto [it, inserted] = inverse_vars.insert(PositiveRef(ref));
4693 if (!inserted) {
4694 return context_->NotifyThatModelIsUnsat("inverse: duplicated variable");
4695 }
4696 }
4697 }
4698
4699 // Propagate from one vector to its counterpart.
4700 // Note this reaches the fixpoint as there is a one to one mapping between
4701 // (variable-value) pairs in each vector.
4702 const auto filter_inverse_domain =
4703 [this, size, &changed](const auto& direct, const auto& inverse) {
4704 // Build the set of values in the inverse vector.
4705 std::vector<absl::flat_hash_set<int64_t>> inverse_values(size);
4706 for (int i = 0; i < size; ++i) {
4707 const Domain domain = context_->DomainOf(inverse[i]);
4708 for (const int64_t j : domain.Values()) {
4709 inverse_values[i].insert(j);
4710 }
4711 }
4712
4713 // Propagate from the inverse vector to the direct vector. Reduce the
4714 // domains of each variable in the direct vector by checking that the
4715 // inverse value exists.
4716 std::vector<int64_t> possible_values;
4717 for (int i = 0; i < size; ++i) {
4718 possible_values.clear();
4719 const Domain domain = context_->DomainOf(direct[i]);
4720 bool removed_value = false;
4721 for (const int64_t j : domain.Values()) {
4722 if (inverse_values[j].contains(i)) {
4723 possible_values.push_back(j);
4724 } else {
4725 removed_value = true;
4726 }
4727 }
4728 if (removed_value) {
4729 changed = true;
4730 if (!context_->IntersectDomainWith(
4731 direct[i], Domain::FromValues(possible_values))) {
4732 VLOG(1) << "Empty domain for a variable in ExpandInverse()";
4733 return false;
4734 }
4735 }
4736 }
4737 return true;
4738 };
4739
4740 if (!filter_inverse_domain(ct->inverse().f_direct(),
4741 ct->inverse().f_inverse())) {
4742 return false;
4743 }
4744
4745 if (!filter_inverse_domain(ct->inverse().f_inverse(),
4746 ct->inverse().f_direct())) {
4747 return false;
4748 }
4749
4750 if (changed) {
4751 context_->UpdateRuleStats("inverse: reduce domains");
4752 }
4753
4754 return false;
4755}
4756
4757bool CpModelPresolver::PresolveElement(ConstraintProto* ct) {
4758 if (context_->ModelIsUnsat()) return false;
4759
4760 if (ct->element().vars().empty()) {
4761 context_->UpdateRuleStats("element: empty array");
4762 return context_->NotifyThatModelIsUnsat();
4763 }
4764
4765 const int index_ref = ct->element().index();
4766 const int target_ref = ct->element().target();
4767
4768 // TODO(user): think about this once we do have such constraint.
4769 if (HasEnforcementLiteral(*ct)) return false;
4770
4771 bool all_constants = true;
4772 std::vector<int64_t> constants;
4773 bool all_included_in_target_domain = true;
4774
4775 {
4776 if (!context_->IntersectDomainWith(
4777 index_ref, Domain(0, ct->element().vars_size() - 1))) {
4778 return false;
4779 }
4780
4781 // Filter impossible index values if index == +/- target
4782 //
4783 // Note that this must be done before the unique_index/target rule.
4784 if (PositiveRef(target_ref) == PositiveRef(index_ref)) {
4785 std::vector<int64_t> possible_indices;
4786 const Domain& index_domain = context_->DomainOf(index_ref);
4787 for (const int64_t index_value : index_domain.Values()) {
4788 const int ref = ct->element().vars(index_value);
4789 const int64_t target_value =
4790 target_ref == index_ref ? index_value : -index_value;
4791 if (context_->DomainContains(ref, target_value)) {
4792 possible_indices.push_back(target_value);
4793 }
4794 }
4795 if (possible_indices.size() < index_domain.Size()) {
4796 if (!context_->IntersectDomainWith(
4797 index_ref, Domain::FromValues(possible_indices))) {
4798 return true;
4799 }
4800 context_->UpdateRuleStats(
4801 "element: reduced index domain when target equals index");
4802 }
4803 }
4804
4805 // Filter possible index values. Accumulate variable domains to build
4806 // a possible target domain.
4807 Domain infered_domain;
4808 const Domain& initial_index_domain = context_->DomainOf(index_ref);
4809 const Domain& target_domain = context_->DomainOf(target_ref);
4810 std::vector<int64_t> possible_indices;
4811 for (const int64_t value : initial_index_domain.Values()) {
4812 CHECK_GE(value, 0);
4813 CHECK_LT(value, ct->element().vars_size());
4814 const int ref = ct->element().vars(value);
4815
4816 // We cover the corner cases where the possible domain is actually fixed.
4817 Domain domain = context_->DomainOf(ref);
4818 if (ref == index_ref) {
4819 domain = Domain(value);
4820 } else if (ref == NegatedRef(index_ref)) {
4821 domain = Domain(-value);
4822 } else if (ref == NegatedRef(target_ref)) {
4823 domain = Domain(0);
4824 }
4825
4826 if (domain.IntersectionWith(target_domain).IsEmpty()) continue;
4827 possible_indices.push_back(value);
4828 if (domain.IsFixed()) {
4829 constants.push_back(domain.Min());
4830 } else {
4831 all_constants = false;
4832 }
4833 if (!domain.IsIncludedIn(target_domain)) {
4834 all_included_in_target_domain = false;
4835 }
4836 infered_domain = infered_domain.UnionWith(domain);
4837 }
4838 if (possible_indices.size() < initial_index_domain.Size()) {
4839 if (!context_->IntersectDomainWith(
4840 index_ref, Domain::FromValues(possible_indices))) {
4841 return true;
4842 }
4843 context_->UpdateRuleStats("element: reduced index domain");
4844 }
4845 bool domain_modified = false;
4846 if (!context_->IntersectDomainWith(target_ref, infered_domain,
4847 &domain_modified)) {
4848 return true;
4849 }
4850 if (domain_modified) {
4851 context_->UpdateRuleStats("element: reduced target domain");
4852 }
4853 }
4854
4855 // If the index is fixed, this is a equality constraint.
4856 if (context_->IsFixed(index_ref)) {
4857 const int var = ct->element().vars(context_->MinOf(index_ref));
4858 if (var != target_ref) {
4859 LinearConstraintProto* const lin =
4860 context_->working_model->add_constraints()->mutable_linear();
4861 lin->add_vars(var);
4862 lin->add_coeffs(-1);
4863 lin->add_vars(target_ref);
4864 lin->add_coeffs(1);
4865 lin->add_domain(0);
4866 lin->add_domain(0);
4868 }
4869 context_->UpdateRuleStats("element: fixed index");
4870 return RemoveConstraint(ct);
4871 }
4872
4873 // If the accessible part of the array is made of a single constant value,
4874 // then we do not care about the index. And, because of the previous target
4875 // domain reduction, the target is also fixed.
4876 if (all_constants && context_->IsFixed(target_ref)) {
4877 context_->UpdateRuleStats("element: one value array");
4878 return RemoveConstraint(ct);
4879 }
4880
4881 // Special case when the index is boolean, and the array does not contain
4882 // variables.
4883 if (context_->MinOf(index_ref) == 0 && context_->MaxOf(index_ref) == 1 &&
4884 all_constants) {
4885 const int64_t v0 = constants[0];
4886 const int64_t v1 = constants[1];
4887
4888 LinearConstraintProto* const lin =
4889 context_->working_model->add_constraints()->mutable_linear();
4890 lin->add_vars(target_ref);
4891 lin->add_coeffs(1);
4892 lin->add_vars(index_ref);
4893 lin->add_coeffs(v0 - v1);
4894 lin->add_domain(v0);
4895 lin->add_domain(v0);
4897 context_->UpdateRuleStats("element: linearize constant element of size 2");
4898 return RemoveConstraint(ct);
4899 }
4900
4901 // If the index has a canonical affine representative, rewrite the element.
4902 const AffineRelation::Relation r_index =
4903 context_->GetAffineRelation(index_ref);
4904 if (r_index.representative != index_ref) {
4905 // Checks the domains are synchronized.
4906 if (context_->DomainOf(r_index.representative).Size() >
4907 context_->DomainOf(index_ref).Size()) {
4908 // Postpone, we will come back later when domains are synchronized.
4909 return true;
4910 }
4911 const int r_ref = r_index.representative;
4912 const int64_t r_min = context_->MinOf(r_ref);
4913 const int64_t r_max = context_->MaxOf(r_ref);
4914 const int array_size = ct->element().vars_size();
4915 if (r_min != 0) {
4916 context_->UpdateRuleStats("TODO element: representative has bad domain");
4917 } else if (r_index.offset >= 0 && r_index.offset < array_size &&
4918 r_index.offset + r_max * r_index.coeff >= 0 &&
4919 r_index.offset + r_max * r_index.coeff < array_size) {
4920 // This will happen eventually when domains are synchronized.
4921 ElementConstraintProto* const element =
4922 context_->working_model->add_constraints()->mutable_element();
4923 for (int64_t v = 0; v <= r_max; ++v) {
4924 const int64_t scaled_index = v * r_index.coeff + r_index.offset;
4925 CHECK_GE(scaled_index, 0);
4926 CHECK_LT(scaled_index, array_size);
4927 element->add_vars(ct->element().vars(scaled_index));
4928 }
4929 element->set_index(r_ref);
4930 element->set_target(target_ref);
4931
4932 if (r_index.coeff == 1) {
4933 context_->UpdateRuleStats("element: shifed index ");
4934 } else {
4935 context_->UpdateRuleStats("element: scaled index");
4936 }
4938 return RemoveConstraint(ct);
4939 }
4940 }
4941
4942 // Should have been taken care of earlier.
4943 DCHECK(!context_->IsFixed(index_ref));
4944
4945 // If a variable (target or index) appears only in this constraint, it does
4946 // not necessarily mean that we can remove the constraint, as the variable
4947 // can be used multiple times in the element. So let's count the local
4948 // uses of each variable.
4949 //
4950 // TODO(user): now that we used fixed values for these case, this is no longer
4951 // needed I think.
4952 absl::flat_hash_map<int, int> local_var_occurrence_counter;
4953 local_var_occurrence_counter[PositiveRef(index_ref)]++;
4954 local_var_occurrence_counter[PositiveRef(target_ref)]++;
4955
4956 for (const ClosedInterval interval : context_->DomainOf(index_ref)) {
4957 for (int64_t value = interval.start; value <= interval.end; ++value) {
4958 DCHECK_GE(value, 0);
4959 DCHECK_LT(value, ct->element().vars_size());
4960 const int ref = ct->element().vars(value);
4961 local_var_occurrence_counter[PositiveRef(ref)]++;
4962 }
4963 }
4964
4965 if (context_->VariableIsUniqueAndRemovable(index_ref) &&
4966 local_var_occurrence_counter.at(PositiveRef(index_ref)) == 1) {
4967 if (all_constants) {
4968 // This constraint is just here to reduce the domain of the target! We can
4969 // add it to the mapping_model to reconstruct the index value during
4970 // postsolve and get rid of it now.
4971 context_->UpdateRuleStats("element: trivial target domain reduction");
4972 context_->MarkVariableAsRemoved(index_ref);
4973 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
4974 return RemoveConstraint(ct);
4975 } else {
4976 context_->UpdateRuleStats("TODO element: index not used elsewhere");
4977 }
4978 }
4979
4980 if (!context_->IsFixed(target_ref) &&
4981 context_->VariableIsUniqueAndRemovable(target_ref) &&
4982 local_var_occurrence_counter.at(PositiveRef(target_ref)) == 1) {
4983 if (all_included_in_target_domain) {
4984 context_->UpdateRuleStats("element: trivial index domain reduction");
4985 context_->MarkVariableAsRemoved(target_ref);
4986 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
4987 return RemoveConstraint(ct);
4988 } else {
4989 context_->UpdateRuleStats("TODO element: target not used elsewhere");
4990 }
4991 }
4992
4993 return false;
4994}
4995
4996bool CpModelPresolver::PresolveTable(ConstraintProto* ct) {
4997 if (context_->ModelIsUnsat()) return false;
4998 if (ct->table().vars().empty()) {
4999 context_->UpdateRuleStats("table: empty constraint");
5000 return MarkConstraintAsFalse(ct);
5001 }
5002
5003 const int initial_num_vars = ct->table().vars_size();
5004 bool changed = true;
5005
5006 // Query existing affine relations.
5007 std::vector<AffineRelation::Relation> affine_relations;
5008 std::vector<int64_t> old_var_lb;
5009 std::vector<int64_t> old_var_ub;
5010 {
5011 for (int v = 0; v < initial_num_vars; ++v) {
5012 const int ref = ct->table().vars(v);
5013 AffineRelation::Relation r = context_->GetAffineRelation(ref);
5014 affine_relations.push_back(r);
5015 old_var_lb.push_back(context_->MinOf(ref));
5016 old_var_ub.push_back(context_->MaxOf(ref));
5017 if (r.representative != ref) {
5018 changed = true;
5019 ct->mutable_table()->set_vars(v, r.representative);
5020 context_->UpdateRuleStats(
5021 "table: replace variable by canonical affine one");
5022 }
5023 }
5024 }
5025
5026 // Check for duplicate occurrences of variables.
5027 // If the ith index is -1, then the variable is not a duplicate of a smaller
5028 // index variable. It if is != from -1, then the values stored is the new
5029 // index of the first occurrence of the variable.
5030 std::vector<int> old_index_of_duplicate_to_new_index_of_first_occurrence(
5031 initial_num_vars, -1);
5032 // If == -1, then the variable is a duplicate of a smaller index variable.
5033 std::vector<int> old_index_to_new_index(initial_num_vars, -1);
5034 int num_vars = 0;
5035 {
5036 absl::flat_hash_map<int, int> first_visit;
5037 for (int p = 0; p < initial_num_vars; ++p) {
5038 const int ref = ct->table().vars(p);
5039 const int var = PositiveRef(ref);
5040 const auto& it = first_visit.find(var);
5041 if (it != first_visit.end()) {
5042 const int previous = it->second;
5043 old_index_of_duplicate_to_new_index_of_first_occurrence[p] = previous;
5044 context_->UpdateRuleStats("table: duplicate variables");
5045 changed = true;
5046 } else {
5047 ct->mutable_table()->set_vars(num_vars, ref);
5048 first_visit[var] = num_vars;
5049 old_index_to_new_index[p] = num_vars;
5050 num_vars++;
5051 }
5052 }
5053
5054 if (num_vars < initial_num_vars) {
5055 ct->mutable_table()->mutable_vars()->Truncate(num_vars);
5056 }
5057 }
5058
5059 // Check each tuple for validity w.r.t. affine relations, variable domains,
5060 // and consistency with duplicate variables. Reduce the size of the tuple in
5061 // case of duplicate variables.
5062 std::vector<std::vector<int64_t>> new_tuples;
5063 const int initial_num_tuples = ct->table().values_size() / initial_num_vars;
5064 std::vector<absl::flat_hash_set<int64_t>> new_domains(num_vars);
5065
5066 {
5067 std::vector<int64_t> tuple(num_vars);
5068 new_tuples.reserve(initial_num_tuples);
5069 for (int i = 0; i < initial_num_tuples; ++i) {
5070 bool delete_row = false;
5071 std::string tmp;
5072 for (int j = 0; j < initial_num_vars; ++j) {
5073 const int64_t old_value = ct->table().values(i * initial_num_vars + j);
5074
5075 // Corner case to avoid overflow, assuming the domain where already
5076 // propagated between a variable and its affine representative.
5077 if (old_value < old_var_lb[j] || old_value > old_var_ub[j]) {
5078 delete_row = true;
5079 break;
5080 }
5081
5082 // Affine relations are defined on the initial variables.
5083 const AffineRelation::Relation& r = affine_relations[j];
5084 const int64_t value = (old_value - r.offset) / r.coeff;
5085 if (value * r.coeff + r.offset != old_value) {
5086 // Value not reachable by affine relation.
5087 delete_row = true;
5088 break;
5089 }
5090 const int mapped_position = old_index_to_new_index[j];
5091 if (mapped_position == -1) { // The current variable is duplicate.
5092 const int new_index_of_first_occurrence =
5093 old_index_of_duplicate_to_new_index_of_first_occurrence[j];
5094 if (value != tuple[new_index_of_first_occurrence]) {
5095 delete_row = true;
5096 break;
5097 }
5098 } else {
5099 const int ref = ct->table().vars(mapped_position);
5100 if (!context_->DomainContains(ref, value)) {
5101 delete_row = true;
5102 break;
5103 }
5104 tuple[mapped_position] = value;
5105 }
5106 }
5107 if (delete_row) {
5108 changed = true;
5109 continue;
5110 }
5111 new_tuples.push_back(tuple);
5112 for (int j = 0; j < num_vars; ++j) {
5113 new_domains[j].insert(tuple[j]);
5114 }
5115 }
5117 if (new_tuples.size() < initial_num_tuples) {
5118 context_->UpdateRuleStats("table: removed rows");
5119 }
5120 }
5121
5122 // Update the list of tuples if needed.
5123 if (changed) {
5124 ct->mutable_table()->clear_values();
5125 for (const std::vector<int64_t>& t : new_tuples) {
5126 for (const int64_t v : t) {
5127 ct->mutable_table()->add_values(v);
5128 }
5129 }
5130 }
5131
5132 // Nothing more to do for negated tables.
5133 if (ct->table().negated()) return changed;
5134
5135 // And for constraints with enforcement literals.
5136 if (HasEnforcementLiteral(*ct)) return false;
5137
5138 // Filter the variable domains.
5139 for (int j = 0; j < num_vars; ++j) {
5140 const int ref = ct->table().vars(j);
5141 if (!context_->IntersectDomainWith(
5142 PositiveRef(ref),
5143 Domain::FromValues(std::vector<int64_t>(new_domains[j].begin(),
5144 new_domains[j].end())),
5145 &changed)) {
5146 return true;
5147 }
5148 }
5149 if (changed) {
5150 context_->UpdateRuleStats("table: reduced variable domains");
5151 }
5152 if (num_vars == 1) {
5153 // Now that we properly update the domain, we can remove the constraint.
5154 context_->UpdateRuleStats("table: only one column!");
5155 return RemoveConstraint(ct);
5156 }
5157
5158 // Check that the table is not complete or just here to exclude a few tuples.
5159 double prod = 1.0;
5160 for (int j = 0; j < num_vars; ++j) prod *= new_domains[j].size();
5161 if (prod == new_tuples.size()) {
5162 context_->UpdateRuleStats("table: all tuples!");
5163 return RemoveConstraint(ct);
5164 }
5165
5166 // Convert to the negated table if we gain a lot of entries by doing so.
5167 // Note however that currently the negated table do not propagate as much as
5168 // it could.
5169 if (new_tuples.size() > 0.7 * prod) {
5170 // Enumerate all tuples.
5171 std::vector<std::vector<int64_t>> var_to_values(num_vars);
5172 for (int j = 0; j < num_vars; ++j) {
5173 var_to_values[j].assign(new_domains[j].begin(), new_domains[j].end());
5174 }
5175 std::vector<std::vector<int64_t>> all_tuples(prod);
5176 for (int i = 0; i < prod; ++i) {
5177 all_tuples[i].resize(num_vars);
5178 int index = i;
5179 for (int j = 0; j < num_vars; ++j) {
5180 all_tuples[i][j] = var_to_values[j][index % var_to_values[j].size()];
5181 index /= var_to_values[j].size();
5182 }
5183 }
5185
5186 // Compute the complement of new_tuples.
5187 std::vector<std::vector<int64_t>> diff(prod - new_tuples.size());
5188 std::set_difference(all_tuples.begin(), all_tuples.end(),
5189 new_tuples.begin(), new_tuples.end(), diff.begin());
5190
5191 // Negate the constraint.
5192 ct->mutable_table()->set_negated(!ct->table().negated());
5193 ct->mutable_table()->clear_values();
5194 for (const std::vector<int64_t>& t : diff) {
5195 for (const int64_t v : t) ct->mutable_table()->add_values(v);
5196 }
5197 context_->UpdateRuleStats("table: negated");
5198 }
5199 return changed;
5200}
5201
5202bool CpModelPresolver::PresolveAllDiff(ConstraintProto* ct) {
5203 if (context_->ModelIsUnsat()) return false;
5204 if (HasEnforcementLiteral(*ct)) return false;
5205
5206 AllDifferentConstraintProto& all_diff = *ct->mutable_all_diff();
5207
5208 bool constraint_has_changed = false;
5209 for (LinearExpressionProto& exp :
5210 *(ct->mutable_all_diff()->mutable_exprs())) {
5211 constraint_has_changed |= CanonicalizeLinearExpression(*ct, &exp);
5212 }
5213
5214 for (;;) {
5215 const int size = all_diff.exprs_size();
5216 if (size == 0) {
5217 context_->UpdateRuleStats("all_diff: empty constraint");
5218 return RemoveConstraint(ct);
5219 }
5220 if (size == 1) {
5221 context_->UpdateRuleStats("all_diff: only one variable");
5222 return RemoveConstraint(ct);
5223 }
5224
5225 bool something_was_propagated = false;
5226 std::vector<LinearExpressionProto> kept_expressions;
5227 for (int i = 0; i < size; ++i) {
5228 if (!context_->IsFixed(all_diff.exprs(i))) {
5229 kept_expressions.push_back(all_diff.exprs(i));
5230 continue;
5231 }
5232
5233 const int64_t value = context_->MinOf(all_diff.exprs(i));
5234 bool propagated = false;
5235 for (int j = 0; j < size; ++j) {
5236 if (i == j) continue;
5237 if (context_->DomainContains(all_diff.exprs(j), value)) {
5238 if (!context_->IntersectDomainWith(all_diff.exprs(j),
5239 Domain(value).Complement())) {
5240 return true;
5241 }
5242 propagated = true;
5243 }
5244 }
5245 if (propagated) {
5246 context_->UpdateRuleStats("all_diff: propagated fixed expressions");
5247 something_was_propagated = true;
5248 }
5249 }
5250
5251 // CanonicalizeLinearExpression() made sure that only positive variable
5252 // appears here, so this order will put expr and -expr one after the other.
5253 std::sort(
5254 kept_expressions.begin(), kept_expressions.end(),
5255 [](const LinearExpressionProto& expr_a,
5256 const LinearExpressionProto& expr_b) {
5257 DCHECK_EQ(expr_a.vars_size(), 1);
5258 DCHECK_EQ(expr_b.vars_size(), 1);
5259 const int ref_a = expr_a.vars(0);
5260 const int ref_b = expr_b.vars(0);
5261 const int64_t coeff_a = expr_a.coeffs(0);
5262 const int64_t coeff_b = expr_b.coeffs(0);
5263 const int64_t abs_coeff_a = std::abs(coeff_a);
5264 const int64_t abs_coeff_b = std::abs(coeff_b);
5265 const int64_t offset_a = expr_a.offset();
5266 const int64_t offset_b = expr_b.offset();
5267 const int64_t abs_offset_a = std::abs(offset_a);
5268 const int64_t abs_offset_b = std::abs(offset_b);
5269 return std::tie(ref_a, abs_coeff_a, coeff_a, abs_offset_a, offset_a) <
5270 std::tie(ref_b, abs_coeff_b, coeff_b, abs_offset_b, offset_b);
5271 });
5272
5273 // TODO(user): improve algorithm if of (a + offset) and (-a - offset)
5274 // might not be together if (a - offset) is present.
5275
5276 for (int i = 1; i < kept_expressions.size(); ++i) {
5277 if (LinearExpressionProtosAreEqual(kept_expressions[i],
5278 kept_expressions[i - 1], 1)) {
5279 return context_->NotifyThatModelIsUnsat(
5280 "Duplicate variable in all_diff");
5281 }
5282 if (LinearExpressionProtosAreEqual(kept_expressions[i],
5283 kept_expressions[i - 1], -1)) {
5284 bool domain_modified = false;
5285 if (!context_->IntersectDomainWith(kept_expressions[i],
5286 Domain(0).Complement(),
5287 &domain_modified)) {
5288 return false;
5289 }
5290 if (domain_modified) {
5291 context_->UpdateRuleStats(
5292 "all_diff: remove 0 from expression appearing with its "
5293 "opposite.");
5294 }
5295 }
5296 }
5297
5298 if (kept_expressions.size() < all_diff.exprs_size()) {
5299 all_diff.clear_exprs();
5300 for (const LinearExpressionProto& expr : kept_expressions) {
5301 *all_diff.add_exprs() = expr;
5302 }
5303 context_->UpdateRuleStats("all_diff: removed fixed variables");
5304 something_was_propagated = true;
5305 constraint_has_changed = true;
5306 if (kept_expressions.size() <= 1) continue;
5307 }
5308
5309 // Propagate mandatory value if the all diff is actually a permutation.
5310 CHECK_GE(all_diff.exprs_size(), 2);
5311 Domain domain = context_->DomainSuperSetOf(all_diff.exprs(0));
5312 for (int i = 1; i < all_diff.exprs_size(); ++i) {
5313 domain = domain.UnionWith(context_->DomainSuperSetOf(all_diff.exprs(i)));
5314 }
5315 if (all_diff.exprs_size() == domain.Size()) {
5316 absl::flat_hash_map<int64_t, std::vector<LinearExpressionProto>>
5317 value_to_exprs;
5318 for (const LinearExpressionProto& expr : all_diff.exprs()) {
5319 for (const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
5320 value_to_exprs[expr.coeffs(0) * v + expr.offset()].push_back(expr);
5321 }
5322 }
5323 bool propagated = false;
5324 for (const auto& it : value_to_exprs) {
5325 if (it.second.size() == 1 && !context_->IsFixed(it.second.front())) {
5326 const LinearExpressionProto& expr = it.second.front();
5327 if (!context_->IntersectDomainWith(expr, Domain(it.first))) {
5328 return true;
5329 }
5330 propagated = true;
5331 }
5332 }
5333 if (propagated) {
5334 context_->UpdateRuleStats(
5335 "all_diff: propagated mandatory values in permutation");
5336 something_was_propagated = true;
5337 }
5338 }
5339 if (!something_was_propagated) break;
5340 }
5341
5342 return constraint_has_changed;
5343}
5344
5345namespace {
5346
5347// Add the constraint (lhs => rhs) to the given proto. The hash map lhs ->
5348// bool_and constraint index is used to merge implications with the same lhs.
5349void AddImplication(int lhs, int rhs, CpModelProto* proto,
5350 absl::flat_hash_map<int, int>* ref_to_bool_and) {
5351 if (ref_to_bool_and->contains(lhs)) {
5352 const int ct_index = (*ref_to_bool_and)[lhs];
5353 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
5354 } else if (ref_to_bool_and->contains(NegatedRef(rhs))) {
5355 const int ct_index = (*ref_to_bool_and)[NegatedRef(rhs)];
5356 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
5357 NegatedRef(lhs));
5358 } else {
5359 (*ref_to_bool_and)[lhs] = proto->constraints_size();
5360 ConstraintProto* ct = proto->add_constraints();
5361 ct->add_enforcement_literal(lhs);
5362 ct->mutable_bool_and()->add_literals(rhs);
5363 }
5364}
5365
5366template <typename ClauseContainer>
5367void ExtractClauses(bool merge_into_bool_and,
5368 const std::vector<int>& index_mapping,
5369 const ClauseContainer& container, CpModelProto* proto) {
5370 // We regroup the "implication" into bool_and to have a more concise proto and
5371 // also for nicer information about the number of binary clauses.
5372 //
5373 // Important: however, we do not do that for the model used during postsolving
5374 // since the order of the constraints might be important there depending on
5375 // how we perform the postsolve.
5376 absl::flat_hash_map<int, int> ref_to_bool_and;
5377 for (int i = 0; i < container.NumClauses(); ++i) {
5378 const std::vector<Literal>& clause = container.Clause(i);
5379 if (clause.empty()) continue;
5380
5381 // bool_and.
5382 //
5383 // TODO(user): Be smarter in how we regroup clause of size 2?
5384 if (merge_into_bool_and && clause.size() == 2) {
5385 const int var_a = index_mapping[clause[0].Variable().value()];
5386 const int var_b = index_mapping[clause[1].Variable().value()];
5387 const int ref_a = clause[0].IsPositive() ? var_a : NegatedRef(var_a);
5388 const int ref_b = clause[1].IsPositive() ? var_b : NegatedRef(var_b);
5389 AddImplication(NegatedRef(ref_a), ref_b, proto, &ref_to_bool_and);
5390 continue;
5391 }
5392
5393 // bool_or.
5394 ConstraintProto* ct = proto->add_constraints();
5395 ct->mutable_bool_or()->mutable_literals()->Reserve(clause.size());
5396 for (const Literal l : clause) {
5397 const int var = index_mapping[l.Variable().value()];
5398 if (l.IsPositive()) {
5399 ct->mutable_bool_or()->add_literals(var);
5400 } else {
5401 ct->mutable_bool_or()->add_literals(NegatedRef(var));
5402 }
5403 }
5404 }
5405}
5406
5407} // namespace
5408
5409bool CpModelPresolver::PresolveNoOverlap(ConstraintProto* ct) {
5410 if (context_->ModelIsUnsat()) return false;
5411 NoOverlapConstraintProto* proto = ct->mutable_no_overlap();
5412 bool changed = false;
5413
5414 // Filter out absent intervals. Process duplicate intervals.
5415 {
5416 // Collect duplicate intervals.
5417 absl::flat_hash_set<int> visited_intervals;
5418 absl::flat_hash_set<int> duplicate_intervals;
5419 for (const int interval_index : proto->intervals()) {
5420 if (context_->ConstraintIsInactive(interval_index)) continue;
5421 if (!visited_intervals.insert(interval_index).second) {
5422 duplicate_intervals.insert(interval_index);
5423 }
5424 }
5425
5426 const int initial_num_intervals = proto->intervals_size();
5427 int new_size = 0;
5428 visited_intervals.clear();
5429
5430 for (int i = 0; i < initial_num_intervals; ++i) {
5431 const int interval_index = proto->intervals(i);
5432 if (context_->ConstraintIsInactive(interval_index)) continue;
5433
5434 if (duplicate_intervals.contains(interval_index)) {
5435 // Once processed, we can always remove further duplicates.
5436 if (!visited_intervals.insert(interval_index).second) continue;
5437
5438 ConstraintProto* interval_ct =
5439 context_->working_model->mutable_constraints(interval_index);
5440
5441 // Case 1: size > 0. Interval must be unperformed.
5442 if (context_->SizeMin(interval_index) > 0) {
5443 if (!MarkConstraintAsFalse(interval_ct)) {
5444 return false;
5445 }
5447 context_->UpdateRuleStats(
5448 "no_overlap: unperform duplicate non zero-sized intervals");
5449 // We can remove the interval from the no_overlap.
5450 continue;
5451 }
5452
5453 // No need to do anything if the size is 0.
5454 if (context_->SizeMax(interval_index) > 0) {
5455 // Case 2: interval is performed. Size must be set to 0.
5456 if (!context_->ConstraintIsOptional(interval_index)) {
5457 if (!context_->IntersectDomainWith(interval_ct->interval().size(),
5458 Domain(0))) {
5459 return false;
5460 }
5461 context_->UpdateRuleStats(
5462 "no_overlap: zero the size of performed duplicate intervals");
5463 // We still need to add the interval to the no_overlap as zero sized
5464 // intervals still cannot overlap with other intervals.
5465 } else { // Case 3: interval is optional and size can be > 0.
5466 const int performed_literal = interval_ct->enforcement_literal(0);
5467 ConstraintProto* size_eq_zero =
5468 context_->working_model->add_constraints();
5469 size_eq_zero->add_enforcement_literal(performed_literal);
5470 size_eq_zero->mutable_linear()->add_domain(0);
5471 size_eq_zero->mutable_linear()->add_domain(0);
5473 interval_ct->interval().size(), 1,
5474 size_eq_zero->mutable_linear());
5475 context_->UpdateRuleStats(
5476 "no_overlap: make duplicate intervals as unperformed or zero "
5477 "sized");
5478 }
5479 }
5480 }
5481
5482 proto->set_intervals(new_size++, interval_index);
5483 }
5484
5485 if (new_size < initial_num_intervals) {
5486 proto->mutable_intervals()->Truncate(new_size);
5487 context_->UpdateRuleStats("no_overlap: removed absent intervals");
5488 changed = true;
5489 }
5490 }
5491
5492 // Split constraints in disjoint sets.
5493 if (proto->intervals_size() > 1) {
5494 std::vector<IndexedInterval> indexed_intervals;
5495 for (int i = 0; i < proto->intervals().size(); ++i) {
5496 const int index = proto->intervals(i);
5497 indexed_intervals.push_back({index,
5498 IntegerValue(context_->StartMin(index)),
5499 IntegerValue(context_->EndMax(index))});
5500 }
5501 std::vector<std::vector<int>> components;
5502 GetOverlappingIntervalComponents(&indexed_intervals, &components);
5503
5504 if (components.size() > 1) {
5505 for (const std::vector<int>& intervals : components) {
5506 if (intervals.size() <= 1) continue;
5507
5508 NoOverlapConstraintProto* new_no_overlap =
5509 context_->working_model->add_constraints()->mutable_no_overlap();
5510 // Fill in the intervals. Unfortunately, the Assign() method does not
5511 // compile in or-tools.
5512 for (const int i : intervals) {
5513 new_no_overlap->add_intervals(i);
5514 }
5515 }
5517 context_->UpdateRuleStats("no_overlap: split into disjoint components");
5518 return RemoveConstraint(ct);
5519 }
5520 }
5521
5522 std::vector<int> constant_intervals;
5523 int64_t size_min_of_non_constant_intervals =
5524 std::numeric_limits<int64_t>::max();
5525 for (int i = 0; i < proto->intervals_size(); ++i) {
5526 const int interval_index = proto->intervals(i);
5527 if (context_->IntervalIsConstant(interval_index)) {
5528 constant_intervals.push_back(interval_index);
5529 } else {
5530 size_min_of_non_constant_intervals =
5531 std::min(size_min_of_non_constant_intervals,
5532 context_->SizeMin(interval_index));
5533 }
5534 }
5535
5536 bool move_constraint_last = false;
5537 if (!constant_intervals.empty()) {
5538 // Sort constant_intervals by start min.
5539 std::sort(constant_intervals.begin(), constant_intervals.end(),
5540 [this](int i1, int i2) {
5541 const int64_t s1 = context_->StartMin(i1);
5542 const int64_t e1 = context_->EndMax(i1);
5543 const int64_t s2 = context_->StartMin(i2);
5544 const int64_t e2 = context_->EndMax(i2);
5545 return std::tie(s1, e1) < std::tie(s2, e2);
5546 });
5547
5548 // Check for overlapping constant intervals. We need to check feasibility
5549 // before we simplify the constraint, as we might remove conflicting
5550 // overlapping constant intervals.
5551 for (int i = 0; i + 1 < constant_intervals.size(); ++i) {
5552 if (context_->EndMax(constant_intervals[i]) >
5553 context_->StartMin(constant_intervals[i + 1])) {
5554 context_->UpdateRuleStats("no_overlap: constant intervals overlap");
5555 return context_->NotifyThatModelIsUnsat();
5556 }
5557 }
5558
5559 if (constant_intervals.size() == proto->intervals_size()) {
5560 context_->UpdateRuleStats("no_overlap: no variable intervals");
5561 return RemoveConstraint(ct);
5562 }
5563
5564 absl::flat_hash_set<int> intervals_to_remove;
5565
5566 // If two constant intervals are separated by a gap smaller that the min
5567 // size of all non-constant intervals, then we can merge them.
5568 for (int i = 0; i + 1 < constant_intervals.size(); ++i) {
5569 const int start = i;
5570 while (i + 1 < constant_intervals.size() &&
5571 context_->StartMin(constant_intervals[i + 1]) -
5572 context_->EndMax(constant_intervals[i]) <
5573 size_min_of_non_constant_intervals) {
5574 i++;
5575 }
5576 if (i == start) continue;
5577 for (int j = start; j <= i; ++j) {
5578 intervals_to_remove.insert(constant_intervals[j]);
5579 }
5580 const int64_t new_start = context_->StartMin(constant_intervals[start]);
5581 const int64_t new_end = context_->EndMax(constant_intervals[i]);
5582 proto->add_intervals(context_->working_model->constraints_size());
5583 IntervalConstraintProto* new_interval =
5584 context_->working_model->add_constraints()->mutable_interval();
5585 new_interval->mutable_start()->set_offset(new_start);
5586 new_interval->mutable_size()->set_offset(new_end - new_start);
5587 new_interval->mutable_end()->set_offset(new_end);
5588 move_constraint_last = true;
5589 }
5590
5591 // Cleanup the original proto.
5592 if (!intervals_to_remove.empty()) {
5593 int new_size = 0;
5594 const int old_size = proto->intervals_size();
5595 for (int i = 0; i < old_size; ++i) {
5596 const int interval_index = proto->intervals(i);
5597 if (intervals_to_remove.contains(interval_index)) {
5598 continue;
5599 }
5600 proto->set_intervals(new_size++, interval_index);
5601 }
5602 CHECK_LT(new_size, old_size);
5603 proto->mutable_intervals()->Truncate(new_size);
5604 context_->UpdateRuleStats(
5605 "no_overlap: merge constant contiguous intervals");
5606 intervals_to_remove.clear();
5607 constant_intervals.clear();
5608 changed = true;
5610 }
5611 }
5612
5613 if (proto->intervals_size() == 1) {
5614 context_->UpdateRuleStats("no_overlap: only one interval");
5615 return RemoveConstraint(ct);
5616 }
5617 if (proto->intervals().empty()) {
5618 context_->UpdateRuleStats("no_overlap: no intervals");
5619 return RemoveConstraint(ct);
5620 }
5621
5622 // Unfortunately, because we want all intervals to appear before a constraint
5623 // that uses them, we need to move the constraint last when we merged constant
5624 // intervals.
5625 if (move_constraint_last) {
5626 changed = true;
5627 *context_->working_model->add_constraints() = *ct;
5629 return RemoveConstraint(ct);
5630 }
5631
5632 return changed;
5633}
5634
5635bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
5636 if (context_->ModelIsUnsat()) {
5637 return false;
5638 }
5639
5640 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
5641 const int initial_num_boxes = proto.x_intervals_size();
5642
5643 bool x_constant = true;
5644 bool y_constant = true;
5645 bool has_zero_sized_interval = false;
5646 bool has_potential_zero_sized_interval = false;
5647
5648 // Filter absent boxes.
5649 int new_size = 0;
5650 std::vector<Rectangle> bounding_boxes, fixed_boxes;
5651 std::vector<RectangleInRange> non_fixed_boxes;
5652 std::vector<int> active_boxes;
5653 absl::flat_hash_set<int> fixed_item_indexes;
5654 for (int i = 0; i < proto.x_intervals_size(); ++i) {
5655 const int x_interval_index = proto.x_intervals(i);
5656 const int y_interval_index = proto.y_intervals(i);
5657
5658 if (context_->ConstraintIsInactive(x_interval_index) ||
5659 context_->ConstraintIsInactive(y_interval_index)) {
5660 continue;
5661 }
5662
5663 ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index);
5664 ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index);
5665 bounding_boxes.push_back(
5666 {IntegerValue(context_->StartMin(x_interval_index)),
5667 IntegerValue(context_->EndMax(x_interval_index)),
5668 IntegerValue(context_->StartMin(y_interval_index)),
5669 IntegerValue(context_->EndMax(y_interval_index))});
5670 active_boxes.push_back(new_size);
5671 if (context_->IntervalIsConstant(x_interval_index) &&
5672 context_->IntervalIsConstant(y_interval_index) &&
5673 context_->SizeMax(x_interval_index) > 0 &&
5674 context_->SizeMax(y_interval_index) > 0) {
5675 fixed_boxes.push_back(bounding_boxes.back());
5676 fixed_item_indexes.insert(new_size);
5677 } else {
5678 non_fixed_boxes.push_back(
5679 {.box_index = new_size,
5680 .bounding_area = bounding_boxes.back(),
5681 .x_size = context_->SizeMin(x_interval_index),
5682 .y_size = context_->SizeMin(y_interval_index)});
5683 }
5684 new_size++;
5685
5686 if (x_constant && !context_->IntervalIsConstant(x_interval_index)) {
5687 x_constant = false;
5688 }
5689 if (y_constant && !context_->IntervalIsConstant(y_interval_index)) {
5690 y_constant = false;
5691 }
5692 if (context_->SizeMax(x_interval_index) == 0 ||
5693 context_->SizeMax(y_interval_index) == 0) {
5694 has_zero_sized_interval = true;
5695 }
5696 if (context_->SizeMin(x_interval_index) == 0 ||
5697 context_->SizeMin(y_interval_index) == 0) {
5698 has_potential_zero_sized_interval = true;
5699 }
5700 }
5701
5702 std::vector<absl::Span<int>> components = GetOverlappingRectangleComponents(
5703 bounding_boxes, absl::MakeSpan(active_boxes));
5704 // The result of GetOverlappingRectangleComponents() omit singleton components
5705 // thus to check whether a graph is fully connected we must check also the
5706 // size of the unique component.
5707 const bool is_fully_connected =
5708 components.size() == 1 && components[0].size() == active_boxes.size();
5709 if (!is_fully_connected) {
5710 for (const absl::Span<int> boxes : components) {
5711 if (boxes.size() <= 1) continue;
5712
5713 NoOverlap2DConstraintProto* new_no_overlap_2d =
5714 context_->working_model->add_constraints()->mutable_no_overlap_2d();
5715 for (const int b : boxes) {
5716 new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
5717 new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
5718 }
5719 }
5721 context_->UpdateRuleStats("no_overlap_2d: split into disjoint components");
5722 return RemoveConstraint(ct);
5723 }
5724
5725 // TODO(user): handle this case. See issue #4068.
5726 if (!has_zero_sized_interval && (x_constant || y_constant)) {
5727 context_->UpdateRuleStats(
5728 "no_overlap_2d: a dimension is constant, splitting into many "
5729 "no_overlaps");
5730 std::vector<IndexedInterval> indexed_intervals;
5731 for (int i = 0; i < new_size; ++i) {
5732 int x = proto.x_intervals(i);
5733 int y = proto.y_intervals(i);
5734 if (x_constant) std::swap(x, y);
5735 indexed_intervals.push_back({x, IntegerValue(context_->StartMin(y)),
5736 IntegerValue(context_->EndMax(y))});
5737 }
5738 std::vector<std::vector<int>> no_overlaps;
5739 ConstructOverlappingSets(/*already_sorted=*/false, &indexed_intervals,
5740 &no_overlaps);
5741 for (const std::vector<int>& no_overlap : no_overlaps) {
5742 ConstraintProto* new_ct = context_->working_model->add_constraints();
5743 // Unfortunately, the Assign() method does not work in or-tools as the
5744 // protobuf int32_t type is not the int type.
5745 for (const int i : no_overlap) {
5746 new_ct->mutable_no_overlap()->add_intervals(i);
5747 }
5748 }
5750 return RemoveConstraint(ct);
5751 }
5752
5753 if (new_size < initial_num_boxes) {
5754 context_->UpdateRuleStats("no_overlap_2d: removed inactive boxes");
5755 ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
5756 ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
5757 }
5758
5759 if (new_size == 0) {
5760 context_->UpdateRuleStats("no_overlap_2d: no boxes");
5761 return RemoveConstraint(ct);
5762 }
5763
5764 if (new_size == 1) {
5765 context_->UpdateRuleStats("no_overlap_2d: only one box");
5766 return RemoveConstraint(ct);
5767 }
5768
5769 // We check if the fixed boxes are not overlapping so downstream code can
5770 // assume it to be true.
5771 for (int i = 0; i < fixed_boxes.size(); ++i) {
5772 const Rectangle& fixed_box = fixed_boxes[i];
5773 for (int j = i + 1; j < fixed_boxes.size(); ++j) {
5774 const Rectangle& other_fixed_box = fixed_boxes[j];
5775 if (!fixed_box.IsDisjoint(other_fixed_box)) {
5776 return context_->NotifyThatModelIsUnsat(
5777 "Two fixed boxes in no_overlap_2d overlap");
5778 }
5779 }
5780 }
5781
5782 if (fixed_boxes.size() == active_boxes.size()) {
5783 context_->UpdateRuleStats("no_overlap_2d: all boxes are fixed");
5784 return RemoveConstraint(ct);
5785 }
5786
5787 // TODO(user): presolve the zero-size fixed items so they are disjoint from
5788 // the other fixed items. Then the following presolve is still valid. On the
5789 // other hand, we cannot do much with non-fixed zero-size items.
5790 if (!has_potential_zero_sized_interval && !fixed_boxes.empty()) {
5791 const bool presolved =
5792 PresolveFixed2dRectangles(non_fixed_boxes, &fixed_boxes);
5793 if (presolved) {
5794 NoOverlap2DConstraintProto new_no_overlap_2d;
5795
5796 // Replace the old fixed intervals by the new ones.
5797 const int old_size = proto.x_intervals_size();
5798 for (int i = 0; i < old_size; ++i) {
5799 if (fixed_item_indexes.contains(i)) {
5800 continue;
5801 }
5802 new_no_overlap_2d.add_x_intervals(proto.x_intervals(i));
5803 new_no_overlap_2d.add_y_intervals(proto.y_intervals(i));
5804 }
5805 for (const Rectangle& fixed_box : fixed_boxes) {
5806 const int item_x_interval =
5807 context_->working_model->constraints().size();
5808 IntervalConstraintProto* new_interval =
5809 context_->working_model->add_constraints()->mutable_interval();
5810 new_interval->mutable_start()->set_offset(fixed_box.x_min.value());
5811 new_interval->mutable_size()->set_offset(fixed_box.SizeX().value());
5812 new_interval->mutable_end()->set_offset(fixed_box.x_max.value());
5813
5814 const int item_y_interval =
5815 context_->working_model->constraints().size();
5816 new_interval =
5817 context_->working_model->add_constraints()->mutable_interval();
5818 new_interval->mutable_start()->set_offset(fixed_box.y_min.value());
5819 new_interval->mutable_size()->set_offset(fixed_box.SizeY().value());
5820 new_interval->mutable_end()->set_offset(fixed_box.y_max.value());
5821
5822 new_no_overlap_2d.add_x_intervals(item_x_interval);
5823 new_no_overlap_2d.add_y_intervals(item_y_interval);
5824 }
5825 context_->working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
5826 &new_no_overlap_2d);
5828 context_->UpdateRuleStats("no_overlap_2d: presolved fixed rectangles");
5829 return RemoveConstraint(ct);
5830 }
5831 }
5832 return new_size < initial_num_boxes;
5833}
5834
5835namespace {
5836LinearExpressionProto ConstantExpressionProto(int64_t value) {
5837 LinearExpressionProto expr;
5838 expr.set_offset(value);
5839 return expr;
5840}
5841} // namespace
5842
5843void CpModelPresolver::DetectDuplicateIntervals(
5844 int c, google::protobuf::RepeatedField<int32_t>* intervals) {
5845 interval_representative_.clear();
5846 bool changed = false;
5847 const int size = intervals->size();
5848 for (int i = 0; i < size; ++i) {
5849 const int index = (*intervals)[i];
5850 const auto [it, inserted] = interval_representative_.insert({index, index});
5851 if (it->second != index) {
5852 changed = true;
5853 intervals->Set(i, it->second);
5854 context_->UpdateRuleStats(
5855 "intervals: change duplicate index inside constraint");
5856 }
5857 }
5858 if (changed) context_->UpdateConstraintVariableUsage(c);
5859}
5860
5861bool CpModelPresolver::PresolveCumulative(ConstraintProto* ct) {
5862 if (context_->ModelIsUnsat()) return false;
5863
5864 CumulativeConstraintProto* proto = ct->mutable_cumulative();
5865
5866 bool changed = CanonicalizeLinearExpression(*ct, proto->mutable_capacity());
5867 for (LinearExpressionProto& exp :
5868 *(ct->mutable_cumulative()->mutable_demands())) {
5869 changed |= CanonicalizeLinearExpression(*ct, &exp);
5870 }
5871
5872 const int64_t capacity_max = context_->MaxOf(proto->capacity());
5873
5874 // Checks the capacity of the constraint.
5875 {
5876 bool domain_changed = false;
5877 if (!context_->IntersectDomainWith(
5878 proto->capacity(), Domain(0, capacity_max), &domain_changed)) {
5879 return true;
5880 }
5881 if (domain_changed) {
5882 context_->UpdateRuleStats("cumulative: trimmed negative capacity");
5883 }
5884 }
5885
5886 // Merge identical intervals if the demand can be merged and is still affine.
5887 //
5888 // TODO(user): We could also merge if the first entry is constant instead of
5889 // the second one. Or if the variable used for the demand is the same.
5890 {
5891 absl::flat_hash_map<int, int> interval_to_i;
5892 int new_size = 0;
5893 for (int i = 0; i < proto->intervals_size(); ++i) {
5894 const auto [it, inserted] =
5895 interval_to_i.insert({proto->intervals(i), new_size});
5896 if (!inserted) {
5897 if (context_->IsFixed(proto->demands(i))) {
5898 const int old_index = it->second;
5899 proto->mutable_demands(old_index)->set_offset(
5900 proto->demands(old_index).offset() +
5901 context_->FixedValue(proto->demands(i)));
5902 context_->UpdateRuleStats(
5903 "cumulative: merged demand of identical interval");
5904 continue;
5905 } else {
5906 context_->UpdateRuleStats(
5907 "TODO cumulative: merged demand of identical interval");
5908 }
5909 }
5910 proto->set_intervals(new_size, proto->intervals(i));
5911 *proto->mutable_demands(new_size) = proto->demands(i);
5912 ++new_size;
5913 }
5914 if (new_size < proto->intervals_size()) {
5915 changed = true;
5916 proto->mutable_intervals()->Truncate(new_size);
5917 proto->mutable_demands()->erase(
5918 proto->mutable_demands()->begin() + new_size,
5919 proto->mutable_demands()->end());
5920 }
5921 }
5922
5923 // Filter absent intervals, or zero demands, or demand incompatible with the
5924 // capacity.
5925 {
5926 int new_size = 0;
5927 int num_zero_demand_removed = 0;
5928 int num_zero_size_removed = 0;
5929 int num_incompatible_intervals = 0;
5930 for (int i = 0; i < proto->intervals_size(); ++i) {
5931 if (context_->ConstraintIsInactive(proto->intervals(i))) continue;
5932
5933 const LinearExpressionProto& demand_expr = proto->demands(i);
5934 const int64_t demand_max = context_->MaxOf(demand_expr);
5935 if (demand_max == 0) {
5936 num_zero_demand_removed++;
5937 continue;
5938 }
5939
5940 const int interval_index = proto->intervals(i);
5941 if (context_->SizeMax(interval_index) == 0) {
5942 // Size 0 intervals cannot contribute to a cumulative.
5943 num_zero_size_removed++;
5944 continue;
5945 }
5946
5947 // Inconsistent intervals cannot be performed.
5948 const int64_t start_min = context_->StartMin(interval_index);
5949 const int64_t end_max = context_->EndMax(interval_index);
5950 if (start_min > end_max) {
5951 if (context_->ConstraintIsOptional(interval_index)) {
5952 ConstraintProto* interval_ct =
5953 context_->working_model->mutable_constraints(interval_index);
5954 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
5955 const int literal = interval_ct->enforcement_literal(0);
5956 if (!context_->SetLiteralToFalse(literal)) {
5957 return true;
5958 }
5959 num_incompatible_intervals++;
5960 continue;
5961 } else {
5962 return context_->NotifyThatModelIsUnsat(
5963 "cumulative: inconsistent intervals cannot be performed.");
5964 }
5965 }
5966
5967 if (context_->MinOf(demand_expr) > capacity_max) {
5968 if (context_->ConstraintIsOptional(interval_index)) {
5969 if (context_->SizeMin(interval_index) > 0) {
5970 ConstraintProto* interval_ct =
5971 context_->working_model->mutable_constraints(interval_index);
5972 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
5973 const int literal = interval_ct->enforcement_literal(0);
5974 if (!context_->SetLiteralToFalse(literal)) {
5975 return true;
5976 }
5977 num_incompatible_intervals++;
5978 continue;
5979 }
5980 } else { // Interval performed.
5981 // Try to set the size to 0.
5982 const ConstraintProto& interval_ct =
5983 context_->working_model->constraints(interval_index);
5984 if (!context_->IntersectDomainWith(interval_ct.interval().size(),
5985 {0, 0})) {
5986 return true;
5987 }
5988 context_->UpdateRuleStats(
5989 "cumulative: zero size of performed demand that exceeds "
5990 "capacity");
5991 ++num_zero_demand_removed;
5992 continue;
5993 }
5994 }
5995
5996 proto->set_intervals(new_size, interval_index);
5997 *proto->mutable_demands(new_size) = proto->demands(i);
5998 new_size++;
5999 }
6000
6001 if (new_size < proto->intervals_size()) {
6002 changed = true;
6003 proto->mutable_intervals()->Truncate(new_size);
6004 proto->mutable_demands()->erase(
6005 proto->mutable_demands()->begin() + new_size,
6006 proto->mutable_demands()->end());
6007 }
6008
6009 if (num_zero_demand_removed > 0) {
6010 context_->UpdateRuleStats(
6011 "cumulative: removed intervals with no demands");
6012 }
6013 if (num_zero_size_removed > 0) {
6014 context_->UpdateRuleStats(
6015 "cumulative: removed intervals with a size of zero");
6016 }
6017 if (num_incompatible_intervals > 0) {
6018 context_->UpdateRuleStats(
6019 "cumulative: removed intervals that can't be performed");
6020 }
6021 }
6022
6023 // Checks the compatibility of demands w.r.t. the capacity.
6024 {
6025 for (int i = 0; i < proto->demands_size(); ++i) {
6026 const int interval = proto->intervals(i);
6027 const LinearExpressionProto& demand_expr = proto->demands(i);
6028 if (context_->ConstraintIsOptional(interval)) continue;
6029 if (context_->SizeMin(interval) == 0) continue;
6030 bool domain_changed = false;
6031 if (!context_->IntersectDomainWith(demand_expr, {0, capacity_max},
6032 &domain_changed)) {
6033 return true;
6034 }
6035 if (domain_changed) {
6036 context_->UpdateRuleStats(
6037 "cumulative: fit demand in [0..capacity_max]");
6038 }
6039 }
6040 }
6041
6042 // Split constraints in disjoint sets.
6043 //
6044 // TODO(user): This can be improved:
6045 // If we detect bridge nodes in the graph of overlapping components, we
6046 // can split the graph around the bridge and add the bridge node to both
6047 // side. Note that if it we take into account precedences between intervals,
6048 // we can detect more bridges.
6049 if (proto->intervals_size() > 1) {
6050 std::vector<IndexedInterval> indexed_intervals;
6051 for (int i = 0; i < proto->intervals().size(); ++i) {
6052 const int index = proto->intervals(i);
6053 indexed_intervals.push_back({i, IntegerValue(context_->StartMin(index)),
6054 IntegerValue(context_->EndMax(index))});
6055 }
6056 std::vector<std::vector<int>> components;
6057 GetOverlappingIntervalComponents(&indexed_intervals, &components);
6058
6059 if (components.size() > 1) {
6060 for (const std::vector<int>& component : components) {
6061 CumulativeConstraintProto* new_cumulative =
6062 context_->working_model->add_constraints()->mutable_cumulative();
6063 for (const int i : component) {
6064 new_cumulative->add_intervals(proto->intervals(i));
6065 *new_cumulative->add_demands() = proto->demands(i);
6066 }
6067 *new_cumulative->mutable_capacity() = proto->capacity();
6068 }
6070 context_->UpdateRuleStats("cumulative: split into disjoint components");
6071 return RemoveConstraint(ct);
6072 }
6073 }
6074
6075 // TODO(user): move the algorithmic part of what we do below in a
6076 // separate function to unit test it more properly.
6077 {
6078 // Build max load profiles.
6079 absl::btree_map<int64_t, int64_t> time_to_demand_deltas;
6080 const int64_t capacity_min = context_->MinOf(proto->capacity());
6081 for (int i = 0; i < proto->intervals_size(); ++i) {
6082 const int interval_index = proto->intervals(i);
6083 const int64_t demand_max = context_->MaxOf(proto->demands(i));
6084 time_to_demand_deltas[context_->StartMin(interval_index)] += demand_max;
6085 time_to_demand_deltas[context_->EndMax(interval_index)] -= demand_max;
6086 }
6087
6088 // We construct the profile which correspond to a set of [time, next_time)
6089 // to max_profile height. And for each time in our discrete set of
6090 // time_exprs (all the start_min and end_max) we count for how often the
6091 // height was above the capacity before this time.
6092 //
6093 // This rely on the iteration in sorted order.
6094 int num_possible_overloads = 0;
6095 int64_t current_load = 0;
6096 absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before;
6097 for (const auto& it : time_to_demand_deltas) {
6098 num_possible_overloads_before[it.first] = num_possible_overloads;
6099 current_load += it.second;
6100 if (current_load > capacity_min) {
6101 ++num_possible_overloads;
6102 }
6103 }
6104 CHECK_EQ(current_load, 0);
6105
6106 // No possible overload with the min capacity.
6107 if (num_possible_overloads == 0) {
6108 context_->UpdateRuleStats(
6109 "cumulative: max profile is always under the min capacity");
6110 return RemoveConstraint(ct);
6111 }
6112
6113 // An interval that does not intersect with the potential_overload_domains
6114 // cannot contribute to a conflict. We can safely remove them.
6115 //
6116 // This is an extension of the presolve rule from
6117 // "Presolving techniques and linear relaxations for cumulative
6118 // scheduling" PhD dissertation by Stefan Heinz, ZIB.
6119 int new_size = 0;
6120 for (int i = 0; i < proto->intervals_size(); ++i) {
6121 const int index = proto->intervals(i);
6122 const int64_t start_min = context_->StartMin(index);
6123 const int64_t end_max = context_->EndMax(index);
6124
6125 // In the cumulative, if start_min == end_max, the interval is of size
6126 // zero and we can just ignore it. If the model is unsat or the interval
6127 // must be absent (start_min > end_max), this should be dealt with at
6128 // the interval constraint level and we can just remove it from here.
6129 //
6130 // Note that currently, the interpretation for interval of length zero
6131 // is different for the no-overlap constraint.
6132 if (start_min >= end_max) continue;
6133
6134 // Note that by construction, both point are in the map. The formula
6135 // counts exactly for how many time_exprs in [start_min, end_max), we have
6136 // a point in our discrete set of time that exceeded the capacity. Because
6137 // we included all the relevant points, this works.
6138 const int num_diff = num_possible_overloads_before.at(end_max) -
6139 num_possible_overloads_before.at(start_min);
6140 if (num_diff == 0) continue;
6141
6142 proto->set_intervals(new_size, proto->intervals(i));
6143 *proto->mutable_demands(new_size) = proto->demands(i);
6144 new_size++;
6145 }
6146
6147 if (new_size < proto->intervals_size()) {
6148 changed = true;
6149 proto->mutable_intervals()->Truncate(new_size);
6150 proto->mutable_demands()->erase(
6151 proto->mutable_demands()->begin() + new_size,
6152 proto->mutable_demands()->end());
6153 context_->UpdateRuleStats(
6154 "cumulative: remove never conflicting intervals.");
6155 }
6156 }
6157
6158 if (proto->intervals().empty()) {
6159 context_->UpdateRuleStats("cumulative: no intervals");
6160 return RemoveConstraint(ct);
6161 }
6162
6163 {
6164 int64_t max_of_performed_demand_mins = 0;
6165 int64_t sum_of_max_demands = 0;
6166 for (int i = 0; i < proto->intervals_size(); ++i) {
6167 const int interval_index = proto->intervals(i);
6168 const ConstraintProto& interval_ct =
6169 context_->working_model->constraints(interval_index);
6170
6171 const LinearExpressionProto& demand_expr = proto->demands(i);
6172 sum_of_max_demands += context_->MaxOf(demand_expr);
6173
6174 if (interval_ct.enforcement_literal().empty() &&
6175 context_->SizeMin(interval_index) > 0) {
6176 max_of_performed_demand_mins = std::max(max_of_performed_demand_mins,
6177 context_->MinOf(demand_expr));
6178 }
6179 }
6180
6181 const LinearExpressionProto& capacity_expr = proto->capacity();
6182 if (max_of_performed_demand_mins > context_->MinOf(capacity_expr)) {
6183 context_->UpdateRuleStats("cumulative: propagate min capacity.");
6184 if (!context_->IntersectDomainWith(
6185 capacity_expr, Domain(max_of_performed_demand_mins,
6186 std::numeric_limits<int64_t>::max()))) {
6187 return true;
6188 }
6189 }
6190
6191 if (max_of_performed_demand_mins > context_->MaxOf(capacity_expr)) {
6192 context_->UpdateRuleStats("cumulative: cannot fit performed demands");
6193 return context_->NotifyThatModelIsUnsat();
6194 }
6195
6196 if (sum_of_max_demands <= context_->MinOf(capacity_expr)) {
6197 context_->UpdateRuleStats("cumulative: capacity exceeds sum of demands");
6198 return RemoveConstraint(ct);
6199 }
6200 }
6201
6202 if (context_->IsFixed(proto->capacity())) {
6203 int64_t gcd = 0;
6204 for (int i = 0; i < ct->cumulative().demands_size(); ++i) {
6205 const LinearExpressionProto& demand_expr = ct->cumulative().demands(i);
6206 if (!context_->IsFixed(demand_expr)) {
6207 // Abort if the demand is not fixed.
6208 gcd = 1;
6209 break;
6210 }
6211 gcd = MathUtil::GCD64(gcd, context_->MinOf(demand_expr));
6212 if (gcd == 1) break;
6213 }
6214 if (gcd > 1) {
6215 changed = true;
6216 for (int i = 0; i < ct->cumulative().demands_size(); ++i) {
6217 const int64_t demand = context_->MinOf(ct->cumulative().demands(i));
6218 *proto->mutable_demands(i) = ConstantExpressionProto(demand / gcd);
6219 }
6220
6221 const int64_t old_capacity = context_->MinOf(proto->capacity());
6222 *proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd);
6223 context_->UpdateRuleStats(
6224 "cumulative: divide demands and capacity by gcd");
6225 }
6226 }
6227
6228 const int num_intervals = proto->intervals_size();
6229 const LinearExpressionProto& capacity_expr = proto->capacity();
6230
6231 std::vector<LinearExpressionProto> start_exprs(num_intervals);
6232
6233 int num_duration_one = 0;
6234 int num_greater_half_capacity = 0;
6235
6236 bool has_optional_interval = false;
6237 for (int i = 0; i < num_intervals; ++i) {
6238 const int index = proto->intervals(i);
6239 // TODO(user): adapt in the presence of optional intervals.
6240 if (context_->ConstraintIsOptional(index)) has_optional_interval = true;
6241 const ConstraintProto& ct =
6242 context_->working_model->constraints(proto->intervals(i));
6243 const IntervalConstraintProto& interval = ct.interval();
6244 start_exprs[i] = interval.start();
6245
6246 const LinearExpressionProto& demand_expr = proto->demands(i);
6247 if (context_->SizeMin(index) == 1 && context_->SizeMax(index) == 1) {
6248 num_duration_one++;
6249 }
6250 if (context_->SizeMin(index) == 0) {
6251 // The behavior for zero-duration interval is currently not the same in
6252 // the no-overlap and the cumulative constraint.
6253 return changed;
6254 }
6255
6256 const int64_t demand_min = context_->MinOf(demand_expr);
6257 const int64_t demand_max = context_->MaxOf(demand_expr);
6258 if (demand_min > capacity_max / 2) {
6259 num_greater_half_capacity++;
6260 }
6261 if (demand_min > capacity_max) {
6262 context_->UpdateRuleStats("cumulative: demand_min exceeds capacity max");
6263 if (!context_->ConstraintIsOptional(index)) {
6264 return context_->NotifyThatModelIsUnsat();
6265 } else {
6266 CHECK_EQ(ct.enforcement_literal().size(), 1);
6267 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
6268 return true;
6269 }
6270 }
6271 return changed;
6272 } else if (demand_max > capacity_max) {
6273 if (ct.enforcement_literal().empty()) {
6274 context_->UpdateRuleStats(
6275 "cumulative: demand_max exceeds capacity max.");
6276 if (!context_->IntersectDomainWith(
6277 demand_expr,
6278 Domain(std::numeric_limits<int64_t>::min(), capacity_max))) {
6279 return true;
6280 }
6281 } else {
6282 // TODO(user): we abort because we cannot convert this to a no_overlap
6283 // for instance.
6284 context_->UpdateRuleStats(
6285 "cumulative: demand_max of optional interval exceeds capacity.");
6286 return changed;
6287 }
6288 }
6289 }
6290 if (num_greater_half_capacity == num_intervals) {
6291 if (num_duration_one == num_intervals && !has_optional_interval) {
6292 context_->UpdateRuleStats("cumulative: convert to all_different");
6293 ConstraintProto* new_ct = context_->working_model->add_constraints();
6294 auto* arg = new_ct->mutable_all_diff();
6295 for (const LinearExpressionProto& expr : start_exprs) {
6296 *arg->add_exprs() = expr;
6297 }
6298 if (!context_->IsFixed(capacity_expr)) {
6299 const int64_t capacity_min = context_->MinOf(capacity_expr);
6300 for (const LinearExpressionProto& expr : proto->demands()) {
6301 if (capacity_min >= context_->MaxOf(expr)) continue;
6302 LinearConstraintProto* fit =
6303 context_->working_model->add_constraints()->mutable_linear();
6304 fit->add_domain(0);
6305 fit->add_domain(std::numeric_limits<int64_t>::max());
6306 AddLinearExpressionToLinearConstraint(capacity_expr, 1, fit);
6308 }
6309 }
6311 return RemoveConstraint(ct);
6312 } else {
6313 context_->UpdateRuleStats("cumulative: convert to no_overlap");
6314 // Before we remove the cumulative, add constraints to enforce that the
6315 // capacity is greater than the demand of any performed intervals.
6316 for (int i = 0; i < proto->demands_size(); ++i) {
6317 const LinearExpressionProto& demand_expr = proto->demands(i);
6318 const int64_t demand_max = context_->MaxOf(demand_expr);
6319 if (demand_max > context_->MinOf(capacity_expr)) {
6320 ConstraintProto* capacity_gt =
6321 context_->working_model->add_constraints();
6322 *capacity_gt->mutable_enforcement_literal() =
6323 context_->working_model->constraints(proto->intervals(i))
6324 .enforcement_literal();
6325 capacity_gt->mutable_linear()->add_domain(0);
6326 capacity_gt->mutable_linear()->add_domain(
6327 std::numeric_limits<int64_t>::max());
6329 capacity_gt->mutable_linear());
6331 capacity_gt->mutable_linear());
6332 }
6333 }
6334
6335 ConstraintProto* new_ct = context_->working_model->add_constraints();
6336 auto* arg = new_ct->mutable_no_overlap();
6337 for (const int interval : proto->intervals()) {
6338 arg->add_intervals(interval);
6339 }
6341 return RemoveConstraint(ct);
6342 }
6343 }
6344
6345 return changed;
6346}
6347
6348bool CpModelPresolver::PresolveRoutes(ConstraintProto* ct) {
6349 if (context_->ModelIsUnsat()) return false;
6350 if (HasEnforcementLiteral(*ct)) return false;
6351 RoutesConstraintProto& proto = *ct->mutable_routes();
6352
6353 const int old_size = proto.literals_size();
6354 int new_size = 0;
6355 std::vector<bool> has_incoming_or_outgoing_arcs;
6356 const int num_arcs = proto.literals_size();
6357 for (int i = 0; i < num_arcs; ++i) {
6358 const int ref = proto.literals(i);
6359 const int tail = proto.tails(i);
6360 const int head = proto.heads(i);
6361
6362 if (tail >= has_incoming_or_outgoing_arcs.size()) {
6363 has_incoming_or_outgoing_arcs.resize(tail + 1, false);
6364 }
6365 if (head >= has_incoming_or_outgoing_arcs.size()) {
6366 has_incoming_or_outgoing_arcs.resize(head + 1, false);
6367 }
6368
6369 if (context_->LiteralIsFalse(ref)) {
6370 context_->UpdateRuleStats("routes: removed false arcs");
6371 continue;
6372 }
6373 proto.set_literals(new_size, ref);
6374 proto.set_tails(new_size, tail);
6375 proto.set_heads(new_size, head);
6376 ++new_size;
6377 has_incoming_or_outgoing_arcs[tail] = true;
6378 has_incoming_or_outgoing_arcs[head] = true;
6379 }
6380
6381 if (old_size > 0 && new_size == 0) {
6382 // A routes constraint cannot have a self loop on 0. Therefore, if there
6383 // were arcs, it means it contains non zero nodes. Without arc, the
6384 // constraint is unfeasible.
6385 return context_->NotifyThatModelIsUnsat(
6386 "routes: graph with nodes and no arcs");
6387 }
6388
6389 // if a node misses an incomping or outgoing arc, the model is trivially
6390 // infeasible.
6391 for (int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) {
6392 if (!has_incoming_or_outgoing_arcs[n]) {
6393 return context_->NotifyThatModelIsUnsat(absl::StrCat(
6394 "routes: node ", n, " misses incoming or outgoing arcs"));
6395 }
6396 }
6397
6398 if (new_size < num_arcs) {
6399 proto.mutable_literals()->Truncate(new_size);
6400 proto.mutable_tails()->Truncate(new_size);
6401 proto.mutable_heads()->Truncate(new_size);
6402 return true;
6403 }
6404
6405 return false;
6406}
6407
6408bool CpModelPresolver::PresolveCircuit(ConstraintProto* ct) {
6409 if (context_->ModelIsUnsat()) return false;
6410 if (HasEnforcementLiteral(*ct)) return false;
6411 CircuitConstraintProto& proto = *ct->mutable_circuit();
6412
6413 // The indexing might not be dense, so fix that first.
6414 ReindexArcs(ct->mutable_circuit()->mutable_tails(),
6415 ct->mutable_circuit()->mutable_heads());
6416
6417 // Convert the flat structure to a graph, note that we includes all the arcs
6418 // here (even if they are at false).
6419 std::vector<std::vector<int>> incoming_arcs;
6420 std::vector<std::vector<int>> outgoing_arcs;
6421 int num_nodes = 0;
6422 const int num_arcs = proto.literals_size();
6423 for (int i = 0; i < num_arcs; ++i) {
6424 const int ref = proto.literals(i);
6425 const int tail = proto.tails(i);
6426 const int head = proto.heads(i);
6427 num_nodes = std::max(num_nodes, std::max(tail, head) + 1);
6428 if (std::max(tail, head) >= incoming_arcs.size()) {
6429 incoming_arcs.resize(std::max(tail, head) + 1);
6430 outgoing_arcs.resize(std::max(tail, head) + 1);
6431 }
6432 incoming_arcs[head].push_back(ref);
6433 outgoing_arcs[tail].push_back(ref);
6434 }
6435
6436 // All the node must have some incoming and outgoing arcs.
6437 for (int i = 0; i < num_nodes; ++i) {
6438 if (incoming_arcs[i].empty() || outgoing_arcs[i].empty()) {
6439 return MarkConstraintAsFalse(ct);
6440 }
6441 }
6442
6443 // Note that it is important to reach the fixed point here:
6444 // One arc at true, then all other arc at false. This is because we rely
6445 // on this in case the circuit is fully specified below.
6446 //
6447 // TODO(user): Use a better complexity if needed.
6448 bool loop_again = true;
6449 int num_fixed_at_true = 0;
6450 while (loop_again) {
6451 loop_again = false;
6452 for (const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
6453 for (const std::vector<int>& refs : *node_to_refs) {
6454 if (refs.size() == 1) {
6455 if (!context_->LiteralIsTrue(refs.front())) {
6456 ++num_fixed_at_true;
6457 if (!context_->SetLiteralToTrue(refs.front())) return true;
6458 }
6459 continue;
6460 }
6461
6462 // At most one true, so if there is one, mark all the other to false.
6463 int num_true = 0;
6464 int true_ref;
6465 for (const int ref : refs) {
6466 if (context_->LiteralIsTrue(ref)) {
6467 ++num_true;
6468 true_ref = ref;
6469 break;
6470 }
6471 }
6472 if (num_true > 1) {
6473 return context_->NotifyThatModelIsUnsat();
6474 }
6475 if (num_true == 1) {
6476 for (const int ref : refs) {
6477 if (ref != true_ref) {
6478 if (!context_->IsFixed(ref)) {
6479 context_->UpdateRuleStats("circuit: set literal to false.");
6480 loop_again = true;
6481 }
6482 if (!context_->SetLiteralToFalse(ref)) return true;
6483 }
6484 }
6485 }
6486 }
6487 }
6488 }
6489 if (num_fixed_at_true > 0) {
6490 context_->UpdateRuleStats("circuit: fixed singleton arcs.");
6491 }
6492
6493 // Remove false arcs.
6494 int new_size = 0;
6495 int num_true = 0;
6496 int circuit_start = -1;
6497 std::vector<int> next(num_nodes, -1);
6498 std::vector<int> new_in_degree(num_nodes, 0);
6499 std::vector<int> new_out_degree(num_nodes, 0);
6500 for (int i = 0; i < num_arcs; ++i) {
6501 const int ref = proto.literals(i);
6502 if (context_->LiteralIsFalse(ref)) continue;
6503 if (context_->LiteralIsTrue(ref)) {
6504 if (next[proto.tails(i)] != -1) {
6505 return context_->NotifyThatModelIsUnsat();
6506 }
6507 next[proto.tails(i)] = proto.heads(i);
6508 if (proto.tails(i) != proto.heads(i)) {
6509 circuit_start = proto.tails(i);
6510 }
6511 ++num_true;
6512 }
6513 ++new_out_degree[proto.tails(i)];
6514 ++new_in_degree[proto.heads(i)];
6515 proto.set_tails(new_size, proto.tails(i));
6516 proto.set_heads(new_size, proto.heads(i));
6517 proto.set_literals(new_size, ref);
6518 ++new_size;
6519 }
6520
6521 // Detect infeasibility due to a node having no more incoming or outgoing arc.
6522 // This is a bit tricky because for now the meaning of the constraint says
6523 // that all nodes that appear in at least one of the arcs must be in the
6524 // circuit or have a self-arc. So if any such node ends up with an incoming or
6525 // outgoing degree of zero once we remove false arcs then the constraint is
6526 // infeasible!
6527 for (int i = 0; i < num_nodes; ++i) {
6528 if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
6529 return context_->NotifyThatModelIsUnsat();
6530 }
6531 }
6532
6533 // Test if a subcircuit is already present.
6534 if (circuit_start != -1) {
6535 std::vector<bool> visited(num_nodes, false);
6536 int current = circuit_start;
6537 while (current != -1 && !visited[current]) {
6538 visited[current] = true;
6539 current = next[current];
6540 }
6541 if (current == circuit_start) {
6542 // We have a sub-circuit! mark all other arc false except self-loop not in
6543 // circuit.
6544 std::vector<bool> has_self_arc(num_nodes, false);
6545 for (int i = 0; i < num_arcs; ++i) {
6546 if (visited[proto.tails(i)]) continue;
6547 if (proto.tails(i) == proto.heads(i)) {
6548 has_self_arc[proto.tails(i)] = true;
6549 if (!context_->SetLiteralToTrue(proto.literals(i))) return true;
6550 } else {
6551 if (!context_->SetLiteralToFalse(proto.literals(i))) return true;
6552 }
6553 }
6554 for (int n = 0; n < num_nodes; ++n) {
6555 if (!visited[n] && !has_self_arc[n]) {
6556 // We have a subircuit, but it doesn't cover all the mandatory nodes.
6557 return MarkConstraintAsFalse(ct);
6558 }
6559 }
6560 context_->UpdateRuleStats("circuit: fully specified.");
6561 return RemoveConstraint(ct);
6562 }
6563 } else {
6564 // All self loop?
6565 if (num_true == new_size) {
6566 context_->UpdateRuleStats("circuit: empty circuit.");
6567 return RemoveConstraint(ct);
6568 }
6569 }
6570
6571 // Look for in/out-degree of two, this will imply that one of the indicator
6572 // Boolean is equal to the negation of the other.
6573 for (int i = 0; i < num_nodes; ++i) {
6574 for (const std::vector<int>* arc_literals :
6575 {&incoming_arcs[i], &outgoing_arcs[i]}) {
6576 std::vector<int> literals;
6577 for (const int ref : *arc_literals) {
6578 if (context_->LiteralIsFalse(ref)) continue;
6579 if (context_->LiteralIsTrue(ref)) {
6580 literals.clear();
6581 break;
6582 }
6583 literals.push_back(ref);
6584 }
6585 if (literals.size() == 2 && literals[0] != NegatedRef(literals[1])) {
6586 context_->UpdateRuleStats("circuit: degree 2");
6587 context_->StoreBooleanEqualityRelation(literals[0],
6588 NegatedRef(literals[1]));
6589 }
6590 }
6591 }
6592
6593 // Truncate the circuit and return.
6594 if (new_size < num_arcs) {
6595 proto.mutable_tails()->Truncate(new_size);
6596 proto.mutable_heads()->Truncate(new_size);
6597 proto.mutable_literals()->Truncate(new_size);
6598 context_->UpdateRuleStats("circuit: removed false arcs.");
6599 return true;
6600 }
6601 return false;
6602}
6603
6604bool CpModelPresolver::PresolveAutomaton(ConstraintProto* ct) {
6605 if (context_->ModelIsUnsat()) return false;
6606 if (HasEnforcementLiteral(*ct)) return false;
6607 AutomatonConstraintProto& proto = *ct->mutable_automaton();
6608 if (proto.vars_size() == 0 || proto.transition_label_size() == 0) {
6609 return false;
6610 }
6611
6612 bool all_have_same_affine_relation = true;
6613 std::vector<AffineRelation::Relation> affine_relations;
6614 for (int v = 0; v < proto.vars_size(); ++v) {
6615 const int var = ct->automaton().vars(v);
6616 const AffineRelation::Relation r = context_->GetAffineRelation(var);
6617 affine_relations.push_back(r);
6618 if (r.representative == var) {
6619 all_have_same_affine_relation = false;
6620 break;
6621 }
6622 if (v > 0 && (r.coeff != affine_relations[v - 1].coeff ||
6623 r.offset != affine_relations[v - 1].offset)) {
6624 all_have_same_affine_relation = false;
6625 break;
6626 }
6627 }
6628
6629 if (all_have_same_affine_relation) { // Unscale labels.
6630 for (int v = 0; v < proto.vars_size(); ++v) {
6631 proto.set_vars(v, affine_relations[v].representative);
6632 }
6633 const AffineRelation::Relation rep = affine_relations.front();
6634 int new_size = 0;
6635 for (int t = 0; t < proto.transition_tail_size(); ++t) {
6636 const int64_t label = proto.transition_label(t);
6637 int64_t inverse_label = (label - rep.offset) / rep.coeff;
6638 if (inverse_label * rep.coeff + rep.offset == label) {
6639 if (new_size != t) {
6640 proto.set_transition_tail(new_size, proto.transition_tail(t));
6641 proto.set_transition_head(new_size, proto.transition_head(t));
6642 }
6643 proto.set_transition_label(new_size, inverse_label);
6644 new_size++;
6645 }
6646 }
6647 if (new_size < proto.transition_tail_size()) {
6648 proto.mutable_transition_tail()->Truncate(new_size);
6649 proto.mutable_transition_label()->Truncate(new_size);
6650 proto.mutable_transition_head()->Truncate(new_size);
6651 context_->UpdateRuleStats("automaton: remove invalid transitions");
6652 }
6653 context_->UpdateRuleStats("automaton: unscale all affine labels");
6654 return true;
6655 }
6656
6657 std::vector<absl::flat_hash_set<int64_t>> reachable_states;
6658 std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
6659 PropagateAutomaton(proto, *context_, &reachable_states, &reachable_labels);
6660
6661 // Filter domains and compute the union of all relevant labels.
6662 bool removed_values = false;
6663 Domain hull;
6664 for (int time = 0; time < reachable_labels.size(); ++time) {
6665 if (!context_->IntersectDomainWith(
6666 proto.vars(time),
6668 {reachable_labels[time].begin(), reachable_labels[time].end()}),
6669 &removed_values)) {
6670 return false;
6671 }
6672 hull = hull.UnionWith(context_->DomainOf(proto.vars(time)));
6673 }
6674 if (removed_values) {
6675 context_->UpdateRuleStats("automaton: reduced variable domains");
6676 }
6677
6678 // Only keep relevant transitions.
6679 int new_size = 0;
6680 for (int t = 0; t < proto.transition_tail_size(); ++t) {
6681 const int64_t label = proto.transition_label(t);
6682 if (hull.Contains(label)) {
6683 if (new_size != t) {
6684 proto.set_transition_tail(new_size, proto.transition_tail(t));
6685 proto.set_transition_label(new_size, label);
6686 proto.set_transition_head(new_size, proto.transition_head(t));
6687 }
6688 new_size++;
6689 }
6690 }
6691 if (new_size < proto.transition_tail_size()) {
6692 proto.mutable_transition_tail()->Truncate(new_size);
6693 proto.mutable_transition_label()->Truncate(new_size);
6694 proto.mutable_transition_head()->Truncate(new_size);
6695 context_->UpdateRuleStats("automaton: remove invalid transitions");
6696 return false;
6697 }
6698
6699 return false;
6700}
6701
6702bool CpModelPresolver::PresolveReservoir(ConstraintProto* ct) {
6703 if (context_->ModelIsUnsat()) return false;
6704 if (HasEnforcementLiteral(*ct)) return false;
6705
6706 ReservoirConstraintProto& proto = *ct->mutable_reservoir();
6707 bool changed = false;
6708 for (LinearExpressionProto& exp : *(proto.mutable_time_exprs())) {
6709 changed |= CanonicalizeLinearExpression(*ct, &exp);
6710 }
6711 for (LinearExpressionProto& exp : *(proto.mutable_level_changes())) {
6712 changed |= CanonicalizeLinearExpression(*ct, &exp);
6713 }
6714
6715 if (proto.active_literals().empty()) {
6716 const int true_literal = context_->GetTrueLiteral();
6717 for (int i = 0; i < proto.time_exprs_size(); ++i) {
6718 proto.add_active_literals(true_literal);
6719 }
6720 changed = true;
6721 }
6722
6723 const auto& demand_is_null = [&](int i) {
6724 return (context_->IsFixed(proto.level_changes(i)) &&
6725 context_->FixedValue(proto.level_changes(i)) == 0) ||
6726 context_->LiteralIsFalse(proto.active_literals(i));
6727 };
6728
6729 // Remove zero level_changes, and inactive events.
6730 int num_zeros = 0;
6731 for (int i = 0; i < proto.level_changes_size(); ++i) {
6732 if (demand_is_null(i)) num_zeros++;
6733 }
6734
6735 if (num_zeros > 0) { // Remove null events
6736 changed = true;
6737 int new_size = 0;
6738 for (int i = 0; i < proto.level_changes_size(); ++i) {
6739 if (demand_is_null(i)) continue;
6740 *proto.mutable_level_changes(new_size) = proto.level_changes(i);
6741 *proto.mutable_time_exprs(new_size) = proto.time_exprs(i);
6742 proto.set_active_literals(new_size, proto.active_literals(i));
6743 new_size++;
6744 }
6745
6746 proto.mutable_level_changes()->erase(
6747 proto.mutable_level_changes()->begin() + new_size,
6748 proto.mutable_level_changes()->end());
6749 proto.mutable_time_exprs()->erase(
6750 proto.mutable_time_exprs()->begin() + new_size,
6751 proto.mutable_time_exprs()->end());
6752 proto.mutable_active_literals()->Truncate(new_size);
6753
6754 context_->UpdateRuleStats(
6755 "reservoir: remove zero level_changes or inactive events.");
6756 }
6757
6758 // The rest of the presolve only applies if all demands are fixed.
6759 for (const LinearExpressionProto& level_change : proto.level_changes()) {
6760 if (!context_->IsFixed(level_change)) return changed;
6761 }
6762
6763 const int num_events = proto.level_changes_size();
6764 int64_t gcd = proto.level_changes().empty()
6765 ? 0
6766 : std::abs(context_->FixedValue(proto.level_changes(0)));
6767 int num_positives = 0;
6768 int num_negatives = 0;
6769 int64_t max_sum_of_positive_level_changes = 0;
6770 int64_t min_sum_of_negative_level_changes = 0;
6771 for (int i = 0; i < num_events; ++i) {
6772 const int64_t demand = context_->FixedValue(proto.level_changes(i));
6773 gcd = MathUtil::GCD64(gcd, std::abs(demand));
6774 if (demand > 0) {
6775 num_positives++;
6776 max_sum_of_positive_level_changes += demand;
6777 } else {
6778 DCHECK_LT(demand, 0);
6779 num_negatives++;
6780 min_sum_of_negative_level_changes += demand;
6781 }
6782 }
6783
6784 if (min_sum_of_negative_level_changes >= proto.min_level() &&
6785 max_sum_of_positive_level_changes <= proto.max_level()) {
6786 context_->UpdateRuleStats("reservoir: always feasible");
6787 return RemoveConstraint(ct);
6788 }
6789
6790 if (min_sum_of_negative_level_changes > proto.max_level() ||
6791 max_sum_of_positive_level_changes < proto.min_level()) {
6792 context_->UpdateRuleStats("reservoir: trivially infeasible");
6793 return context_->NotifyThatModelIsUnsat();
6794 }
6795
6796 if (min_sum_of_negative_level_changes > proto.min_level()) {
6797 proto.set_min_level(min_sum_of_negative_level_changes);
6798 context_->UpdateRuleStats(
6799 "reservoir: increase min_level to reachable value");
6800 }
6801
6802 if (max_sum_of_positive_level_changes < proto.max_level()) {
6803 proto.set_max_level(max_sum_of_positive_level_changes);
6804 context_->UpdateRuleStats("reservoir: reduce max_level to reachable value");
6805 }
6806
6807 if (proto.min_level() <= 0 && proto.max_level() >= 0 &&
6808 (num_positives == 0 || num_negatives == 0)) {
6809 // If all level_changes have the same sign, and if the initial state is
6810 // always feasible, we do not care about the order, just the sum.
6811 auto* const sum =
6812 context_->working_model->add_constraints()->mutable_linear();
6813 int64_t fixed_contrib = 0;
6814 for (int i = 0; i < proto.level_changes_size(); ++i) {
6815 const int64_t demand = context_->FixedValue(proto.level_changes(i));
6816 DCHECK_NE(demand, 0);
6817
6818 const int active = proto.active_literals(i);
6819 if (RefIsPositive(active)) {
6820 sum->add_vars(active);
6821 sum->add_coeffs(demand);
6822 } else {
6823 sum->add_vars(PositiveRef(active));
6824 sum->add_coeffs(-demand);
6825 fixed_contrib += demand;
6826 }
6827 }
6828 sum->add_domain(proto.min_level() - fixed_contrib);
6829 sum->add_domain(proto.max_level() - fixed_contrib);
6830 context_->UpdateRuleStats("reservoir: converted to linear");
6831 return RemoveConstraint(ct);
6832 }
6833
6834 if (gcd > 1) {
6835 for (int i = 0; i < proto.level_changes_size(); ++i) {
6836 proto.mutable_level_changes(i)->set_offset(
6837 context_->FixedValue(proto.level_changes(i)) / gcd);
6838 proto.mutable_level_changes(i)->clear_vars();
6839 proto.mutable_level_changes(i)->clear_coeffs();
6840 }
6841
6842 // Adjust min and max levels.
6843 // max level is always rounded down.
6844 // min level is always rounded up.
6845 const Domain reduced_domain = Domain({proto.min_level(), proto.max_level()})
6846 .InverseMultiplicationBy(gcd);
6847 proto.set_min_level(reduced_domain.Min());
6848 proto.set_max_level(reduced_domain.Max());
6849 context_->UpdateRuleStats(
6850 "reservoir: simplify level_changes and levels by gcd.");
6851 }
6852
6853 if (num_positives == 1 && num_negatives > 0) {
6854 context_->UpdateRuleStats(
6855 "TODO reservoir: one producer, multiple consumers.");
6856 }
6857
6858 absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set;
6859 for (int i = 0; i < proto.level_changes_size(); ++i) {
6860 const LinearExpressionProto& time = proto.time_exprs(i);
6861 const int var = context_->IsFixed(time) ? std::numeric_limits<int>::min()
6862 : time.vars(0);
6863 const int64_t coeff = context_->IsFixed(time) ? 0 : time.coeffs(0);
6864 const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple(
6865 var, coeff,
6866 context_->IsFixed(time) ? context_->FixedValue(time) : time.offset(),
6867 proto.active_literals(i));
6868 if (time_active_set.contains(key)) {
6869 context_->UpdateRuleStats("TODO reservoir: merge synchronized events.");
6870 break;
6871 } else {
6872 time_active_set.insert(key);
6873 }
6874 }
6875
6876 return changed;
6877}
6878
6879// TODO(user): It is probably more efficient to keep all the bool_and in a
6880// global place during all the presolve, and just output them at the end
6881// rather than modifying more than once the proto.
6882void CpModelPresolver::ConvertToBoolAnd() {
6883 absl::flat_hash_map<int, int> ref_to_bool_and;
6884 const int num_constraints = context_->working_model->constraints_size();
6885 std::vector<int> to_remove;
6886 for (int c = 0; c < num_constraints; ++c) {
6887 const ConstraintProto& ct = context_->working_model->constraints(c);
6888 if (HasEnforcementLiteral(ct)) continue;
6889
6890 if (ct.constraint_case() == ConstraintProto::kBoolOr &&
6891 ct.bool_or().literals().size() == 2) {
6892 AddImplication(NegatedRef(ct.bool_or().literals(0)),
6893 ct.bool_or().literals(1), context_->working_model,
6894 &ref_to_bool_and);
6895 to_remove.push_back(c);
6896 continue;
6897 }
6898
6899 if (ct.constraint_case() == ConstraintProto::kAtMostOne &&
6900 ct.at_most_one().literals().size() == 2) {
6901 AddImplication(ct.at_most_one().literals(0),
6902 NegatedRef(ct.at_most_one().literals(1)),
6903 context_->working_model, &ref_to_bool_and);
6904 to_remove.push_back(c);
6905 continue;
6906 }
6907 }
6908
6910 for (const int c : to_remove) {
6911 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
6912 CHECK(RemoveConstraint(ct));
6913 context_->UpdateConstraintVariableUsage(c);
6914 }
6915}
6916
6917// TODO(user): It might make sense to run this in parallel. The same apply for
6918// other expansive and self-contains steps like symmetry detection, etc...
6919void CpModelPresolver::Probe() {
6920 auto probing_timer =
6921 std::make_unique<PresolveTimer>(__FUNCTION__, logger_, time_limit_);
6922
6923 Model model;
6924 if (!LoadModelForProbing(context_, &model)) return;
6925
6926 // Probe.
6927 //
6928 // TODO(user): Compute the transitive reduction instead of just the
6929 // equivalences, and use the newly learned binary clauses?
6930 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
6931 auto* sat_solver = model.GetOrCreate<SatSolver>();
6932 auto* mapping = model.GetOrCreate<CpModelMapping>();
6933 auto* prober = model.GetOrCreate<Prober>();
6934
6935 // Try to detect trivial clauses thanks to implications.
6936 // This can be slow, so we bound the amount of work done.
6937 //
6938 // Idea: If we have l1, l2 in a bool_or and not(l1) => l2, the constraint is
6939 // always true.
6940 //
6941 // Correctness: Note that we always replace a clause with another one that
6942 // subsumes it. So we are correct even if new clauses are learned and used
6943 // for propagation along the way.
6944 //
6945 // TODO(user): Improve the algo?
6946 const auto& assignment = sat_solver->Assignment();
6947 prober->SetPropagationCallback([&](Literal decision) {
6948 if (probing_timer->WorkLimitIsReached()) return;
6949 const int decision_var =
6950 mapping->GetProtoVariableFromBooleanVariable(decision.Variable());
6951 if (decision_var < 0) return;
6952 probing_timer->TrackSimpleLoop(
6953 context_->VarToConstraints(decision_var).size());
6954 std::vector<int> to_update;
6955 for (const int c : context_->VarToConstraints(decision_var)) {
6956 if (c < 0) continue;
6957 const ConstraintProto& ct = context_->working_model->constraints(c);
6958 if (ct.enforcement_literal().size() > 2) {
6959 // Any l for which decision => l can be removed.
6960 //
6961 // If decision => not(l), constraint can never be satisfied. However
6962 // because we don't know if this constraint was part of the
6963 // propagation we replace it by an implication.
6964 //
6965 // TODO(user): remove duplication with code below.
6966 // TODO(user): If decision appear positively, we could potentially
6967 // remove a bunch of terms (all the ones involving variables implied
6968 // by the decision) from the inner constraint, especially in the
6969 // linear case.
6970 int decision_ref;
6971 int false_ref;
6972 bool decision_is_positive = false;
6973 bool has_false_literal = false;
6974 bool simplification_possible = false;
6975 probing_timer->TrackSimpleLoop(ct.enforcement_literal().size());
6976 for (const int ref : ct.enforcement_literal()) {
6977 const Literal lit = mapping->Literal(ref);
6978 if (PositiveRef(ref) == decision_var) {
6979 decision_ref = ref;
6980 decision_is_positive = assignment.LiteralIsTrue(lit);
6981 if (!decision_is_positive) break;
6982 continue;
6983 }
6984 if (assignment.LiteralIsFalse(lit)) {
6985 false_ref = ref;
6986 has_false_literal = true;
6987 } else if (assignment.LiteralIsTrue(lit)) {
6988 // If decision => l, we can remove l from the list.
6989 simplification_possible = true;
6990 }
6991 }
6992 if (!decision_is_positive) continue;
6993
6994 if (has_false_literal) {
6995 // Reduce to implication.
6996 auto* mutable_ct = context_->working_model->mutable_constraints(c);
6997 mutable_ct->Clear();
6998 mutable_ct->add_enforcement_literal(decision_ref);
6999 mutable_ct->mutable_bool_and()->add_literals(NegatedRef(false_ref));
7000 context_->UpdateRuleStats(
7001 "probing: reduced enforced constraint to implication.");
7002 to_update.push_back(c);
7003 continue;
7004 }
7005
7006 if (simplification_possible) {
7007 int new_size = 0;
7008 auto* mutable_enforcements =
7009 context_->working_model->mutable_constraints(c)
7010 ->mutable_enforcement_literal();
7011 for (const int ref : ct.enforcement_literal()) {
7012 if (PositiveRef(ref) != decision_var &&
7013 assignment.LiteralIsTrue(mapping->Literal(ref))) {
7014 continue;
7015 }
7016 mutable_enforcements->Set(new_size++, ref);
7017 }
7018 mutable_enforcements->Truncate(new_size);
7019 context_->UpdateRuleStats("probing: simplified enforcement list.");
7020 to_update.push_back(c);
7021 }
7022 continue;
7023 }
7024
7025 if (ct.constraint_case() != ConstraintProto::kBoolOr) continue;
7026 if (ct.bool_or().literals().size() <= 2) continue;
7027
7028 int decision_ref;
7029 int true_ref;
7030 bool decision_is_negative = false;
7031 bool has_true_literal = false;
7032 bool simplification_possible = false;
7033 probing_timer->TrackSimpleLoop(ct.bool_or().literals().size());
7034 for (const int ref : ct.bool_or().literals()) {
7035 const Literal lit = mapping->Literal(ref);
7036 if (PositiveRef(ref) == decision_var) {
7037 decision_ref = ref;
7038 decision_is_negative = assignment.LiteralIsFalse(lit);
7039 if (!decision_is_negative) break;
7040 continue;
7041 }
7042 if (assignment.LiteralIsTrue(lit)) {
7043 true_ref = ref;
7044 has_true_literal = true;
7045 } else if (assignment.LiteralIsFalse(lit)) {
7046 // If not(l1) => not(l2), we can remove l2 from the clause.
7047 simplification_possible = true;
7048 }
7049 }
7050 if (!decision_is_negative) continue;
7051
7052 if (has_true_literal) {
7053 // This will later be merged with the current implications and removed
7054 // if it is a duplicate.
7055 auto* mutable_bool_or =
7056 context_->working_model->mutable_constraints(c)->mutable_bool_or();
7057 mutable_bool_or->mutable_literals()->Clear();
7058 mutable_bool_or->add_literals(decision_ref);
7059 mutable_bool_or->add_literals(true_ref);
7060 context_->UpdateRuleStats("probing: bool_or reduced to implication");
7061 to_update.push_back(c);
7062 continue;
7063 }
7064
7065 if (simplification_possible) {
7066 int new_size = 0;
7067 auto* mutable_bool_or =
7068 context_->working_model->mutable_constraints(c)->mutable_bool_or();
7069 for (const int ref : ct.bool_or().literals()) {
7070 if (PositiveRef(ref) != decision_var &&
7071 assignment.LiteralIsFalse(mapping->Literal(ref))) {
7072 continue;
7073 }
7074 mutable_bool_or->set_literals(new_size++, ref);
7075 }
7076 mutable_bool_or->mutable_literals()->Truncate(new_size);
7077 context_->UpdateRuleStats("probing: simplified clauses.");
7078 to_update.push_back(c);
7079 }
7080 }
7081
7082 absl::c_sort(to_update);
7083 for (const int c : to_update) {
7084 context_->UpdateConstraintVariableUsage(c);
7085 }
7086 });
7087
7088 prober->ProbeBooleanVariables(
7089 context_->params().probing_deterministic_time_limit());
7090
7091 probing_timer->AddCounter("probed", prober->num_decisions());
7092 probing_timer->AddToWork(
7093 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
7094 if (sat_solver->ModelIsUnsat() || !implication_graph->DetectEquivalences()) {
7095 return (void)context_->NotifyThatModelIsUnsat("during probing");
7096 }
7097
7098 // Update the presolve context with fixed Boolean variables.
7099 int num_fixed = 0;
7100 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
7101 for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
7102 const Literal l = sat_solver->LiteralTrail()[i];
7103 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
7104 if (var >= 0) {
7105 const int ref = l.IsPositive() ? var : NegatedRef(var);
7106 if (context_->IsFixed(ref)) continue;
7107 ++num_fixed;
7108 if (!context_->SetLiteralToTrue(ref)) return;
7109 }
7110 }
7111 probing_timer->AddCounter("fixed_bools", num_fixed);
7112
7113 DetectDuplicateConstraintsWithDifferentEnforcements(
7114 mapping, implication_graph, model.GetOrCreate<Trail>());
7115
7116 int num_equiv = 0;
7117 int num_changed_bounds = 0;
7118 const int num_variables = context_->working_model->variables().size();
7119 auto* integer_trail = model.GetOrCreate<IntegerTrail>();
7120 for (int var = 0; var < num_variables; ++var) {
7121 // Restrict IntegerVariable domain.
7122 // Note that Boolean are already dealt with above.
7123 if (!mapping->IsBoolean(var)) {
7124 bool changed = false;
7125 if (!context_->IntersectDomainWith(
7126 var, integer_trail->InitialVariableDomain(mapping->Integer(var)),
7127 &changed)) {
7128 return;
7129 }
7130 if (changed) ++num_changed_bounds;
7131 continue;
7132 }
7133
7134 // Add Boolean equivalence relations.
7135 const Literal l = mapping->Literal(var);
7136 const Literal r = implication_graph->RepresentativeOf(l);
7137 if (r != l) {
7138 ++num_equiv;
7139 const int r_var =
7140 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
7141 CHECK_GE(r_var, 0);
7143 var, r.IsPositive() ? r_var : NegatedRef(r_var));
7144 }
7145 }
7146 probing_timer->AddCounter("new_bounds", num_changed_bounds);
7147 probing_timer->AddCounter("equiv", num_equiv);
7148 probing_timer->AddCounter("new_binary_clauses",
7149 prober->num_new_binary_clauses());
7150
7151 // Stop probing timer now and display info.
7152 probing_timer.reset();
7153
7154 // Run clique merging using detected implications from probing.
7155 if (context_->params().merge_at_most_one_work_limit() > 0.0) {
7156 PresolveTimer timer("MaxClique", logger_, time_limit_);
7157 std::vector<std::vector<Literal>> cliques;
7158
7159 int64_t num_literals_before = 0;
7160 const int num_constraints = context_->working_model->constraints_size();
7161 for (int c = 0; c < num_constraints; ++c) {
7162 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7163 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
7164 std::vector<Literal> clique;
7165 for (const int ref : ct->at_most_one().literals()) {
7166 clique.push_back(mapping->Literal(ref));
7167 }
7168 num_literals_before += clique.size();
7169 cliques.push_back(clique);
7170 ct->Clear();
7171 context_->UpdateConstraintVariableUsage(c);
7172 } else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
7173 if (ct->enforcement_literal().size() != 1) continue;
7174 const Literal enforcement =
7175 mapping->Literal(ct->enforcement_literal(0));
7176 for (const int ref : ct->bool_and().literals()) {
7177 if (ref == ct->enforcement_literal(0)) continue;
7178 num_literals_before += 2;
7179 cliques.push_back({enforcement, mapping->Literal(ref).Negated()});
7180 }
7181 ct->Clear();
7182 context_->UpdateConstraintVariableUsage(c);
7183 }
7184 }
7185 const int64_t num_old_cliques = cliques.size();
7186
7187 implication_graph->TransformIntoMaxCliques(
7188 &cliques,
7189 SafeDoubleToInt64(context_->params().merge_at_most_one_work_limit()));
7190
7191 // Note that because TransformIntoMaxCliques() extend cliques, we are ok
7192 // to ignore any unmapped literal. In case of equivalent literal, we always
7193 // use the smaller indices as a representative, so we should be good.
7194 int num_new_cliques = 0;
7195 int64_t num_literals_after = 0;
7196 for (const std::vector<Literal>& clique : cliques) {
7197 if (clique.empty()) continue;
7198 num_new_cliques++;
7199 num_literals_after += clique.size();
7200 ConstraintProto* ct = context_->working_model->add_constraints();
7201 for (const Literal literal : clique) {
7202 const int var =
7203 mapping->GetProtoVariableFromBooleanVariable(literal.Variable());
7204 if (var < 0) continue;
7205 if (literal.IsPositive()) {
7206 ct->mutable_at_most_one()->add_literals(var);
7207 } else {
7208 ct->mutable_at_most_one()->add_literals(NegatedRef(var));
7209 }
7210 }
7211
7212 // Make sure we do not have duplicate variable reference.
7213 PresolveAtMostOne(ct);
7214 }
7216 if (num_new_cliques != num_old_cliques) {
7217 context_->UpdateRuleStats("at_most_one: transformed into max clique.");
7218 }
7219
7220 if (num_old_cliques != num_new_cliques ||
7221 num_literals_before != num_literals_after) {
7222 timer.AddMessage(absl::StrCat("Merged ", num_old_cliques, "(",
7223 num_literals_before, " literals) into ",
7224 num_new_cliques, "(", num_literals_after,
7225 " literals) at_most_ones. "));
7226 }
7227 }
7228}
7229
7230namespace {
7231
7232bool FixFromAssignment(const VariablesAssignment& assignment,
7233 const std::vector<int>& var_mapping,
7234 PresolveContext* context) {
7235 const int num_vars = assignment.NumberOfVariables();
7236 for (int i = 0; i < num_vars; ++i) {
7237 const Literal lit(BooleanVariable(i), true);
7238 const int ref = var_mapping[i];
7239 if (assignment.LiteralIsTrue(lit)) {
7240 if (!context->SetLiteralToTrue(ref)) return false;
7241 } else if (assignment.LiteralIsFalse(lit)) {
7242 if (!context->SetLiteralToFalse(ref)) return false;
7243 }
7244 }
7245 return true;
7246}
7247
7248} // namespace
7249
7250// TODO(user): What to do with the at_most_one/exactly_one constraints?
7251// currently we do not take them into account here.
7252bool CpModelPresolver::PresolvePureSatPart() {
7253 // TODO(user): Reenable some SAT presolve with
7254 // keep_all_feasible_solutions set to true.
7255 if (context_->ModelIsUnsat() || context_->keep_all_feasible_solutions) {
7256 return true;
7257 }
7258
7259 // Compute a dense re-indexing for the Booleans of the problem.
7260 int num_variables = 0;
7261 int num_ignored_variables = 0;
7262 const int total_num_vars = context_->working_model->variables().size();
7263 std::vector<int> new_index(total_num_vars, -1);
7264 std::vector<int> new_to_old_index;
7265 for (int i = 0; i < total_num_vars; ++i) {
7266 if (!context_->CanBeUsedAsLiteral(i)) {
7267 ++num_ignored_variables;
7268 continue;
7269 }
7270
7271 // This is important to not assign variable in equivalence to random values.
7272 if (context_->VarToConstraints(i).empty()) continue;
7273
7274 new_to_old_index.push_back(i);
7275 new_index[i] = num_variables++;
7276 DCHECK_EQ(num_variables, new_to_old_index.size());
7277 }
7278
7279 // The conversion from proto index to remapped Literal.
7280 auto convert = [&new_index](int ref) {
7281 const int index = new_index[PositiveRef(ref)];
7282 DCHECK_NE(index, -1);
7283 return Literal(BooleanVariable(index), RefIsPositive(ref));
7284 };
7285
7286 // Load the pure-SAT part in a fresh Model.
7287 //
7288 // TODO(user): The removing and adding back of the same clause when nothing
7289 // happens in the presolve "seems" bad. That said, complexity wise, it is
7290 // a lot faster that what happens in the presolve though.
7291 //
7292 // TODO(user): Add the "small" at most one constraints to the SAT presolver by
7293 // expanding them to implications? that could remove a lot of clauses. Do that
7294 // when we are sure we don't load duplicates at_most_one/implications in the
7295 // solver. Ideally, the pure sat presolve could be improved to handle at most
7296 // one, and we could merge this with what the ProcessSetPPC() is doing.
7297 Model local_model;
7298 local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
7299 auto* sat_solver = local_model.GetOrCreate<SatSolver>();
7300 sat_solver->SetNumVariables(num_variables);
7301
7302 // Fix variables if any. Because we might not have reached the presove "fixed
7303 // point" above, some variable in the added clauses might be fixed. We need to
7304 // indicate this to the SAT presolver.
7305 for (const int var : new_to_old_index) {
7306 if (context_->IsFixed(var)) {
7307 if (context_->LiteralIsTrue(var)) {
7308 if (!sat_solver->AddUnitClause({convert(var)})) return false;
7309 } else {
7310 if (!sat_solver->AddUnitClause({convert(NegatedRef(var))})) {
7311 return false;
7312 }
7313 }
7314 }
7315 }
7316
7317 std::vector<Literal> clause;
7318 int num_removed_constraints = 0;
7319 int num_ignored_constraints = 0;
7320 for (int i = 0; i < context_->working_model->constraints_size(); ++i) {
7321 const ConstraintProto& ct = context_->working_model->constraints(i);
7322
7323 if (ct.constraint_case() == ConstraintProto::kBoolOr) {
7324 ++num_removed_constraints;
7325 clause.clear();
7326 for (const int ref : ct.bool_or().literals()) {
7327 clause.push_back(convert(ref));
7328 }
7329 for (const int ref : ct.enforcement_literal()) {
7330 clause.push_back(convert(ref).Negated());
7331 }
7332 sat_solver->AddProblemClause(clause, /*is_safe=*/false);
7333
7334 context_->working_model->mutable_constraints(i)->Clear();
7336 continue;
7337 }
7338
7339 if (ct.constraint_case() == ConstraintProto::kBoolAnd) {
7340 // We currently do not expand "complex" bool_and that would result
7341 // in too many literals.
7342 const int left_size = ct.enforcement_literal().size();
7343 const int right_size = ct.bool_and().literals().size();
7344 if (left_size > 1 && right_size > 1 &&
7345 (left_size + 1) * right_size > 10'000) {
7346 ++num_ignored_constraints;
7347 continue;
7348 }
7349
7350 ++num_removed_constraints;
7351 std::vector<Literal> clause;
7352 for (const int ref : ct.enforcement_literal()) {
7353 clause.push_back(convert(ref).Negated());
7354 }
7355 clause.push_back(Literal(kNoLiteralIndex)); // will be replaced below.
7356 for (const int ref : ct.bool_and().literals()) {
7357 clause.back() = convert(ref);
7358 sat_solver->AddProblemClause(clause, /*is_safe=*/false);
7359 }
7360
7361 context_->working_model->mutable_constraints(i)->Clear();
7363 continue;
7364 }
7365
7366 if (ct.constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
7367 continue;
7368 }
7369
7370 ++num_ignored_constraints;
7371 }
7372 if (sat_solver->ModelIsUnsat()) return false;
7373
7374 // Abort early if there was no Boolean constraints.
7375 if (num_removed_constraints == 0) return true;
7376
7377 // Mark the variables appearing elsewhere or in the objective as non-removable
7378 // by the sat presolver.
7379 //
7380 // TODO(user): do not remove variable that appear in the decision heuristic?
7381 // TODO(user): We could go further for variable with only one polarity by
7382 // removing variable from the objective if they can be set to their "low"
7383 // objective value, and also removing enforcement literal that can be set to
7384 // false and don't appear elsewhere.
7385 int num_in_extra_constraints = 0;
7386 std::vector<bool> can_be_removed(num_variables, false);
7387 for (int i = 0; i < num_variables; ++i) {
7388 const int var = new_to_old_index[i];
7389 if (context_->VarToConstraints(var).empty()) {
7390 can_be_removed[i] = true;
7391 } else {
7392 // That might correspond to the objective or a variable with an affine
7393 // relation that is still in the model.
7394 ++num_in_extra_constraints;
7395 }
7396 }
7397
7398 // The "full solver" postsolve does not support changing the value of a
7399 // variable from the solution of the presolved problem, and we do need this
7400 // for blocked clause. It should be possible to allow for this by adding extra
7401 // variable to the mapping model at presolve and some linking constraints, but
7402 // this is messy.
7403 //
7404 // We also disable this if the user asked for tightened domain as this might
7405 // fix variable to a potentially infeasible value, and just correct them later
7406 // during postsolve of a particular solution.
7407 SatParameters params = context_->params();
7408 if (params.debug_postsolve_with_full_solver() ||
7409 params.fill_tightened_domains_in_response()) {
7410 params.set_presolve_blocked_clause(false);
7411 }
7412
7413 SatPostsolver sat_postsolver(num_variables);
7414
7415 // If the problem is a pure-SAT problem, we run the new SAT presolver.
7416 // This takes more time but it is usually worthwile
7417 //
7418 // Note that the probing that it does is faster than the
7419 // ProbeAndFindEquivalentLiteral() call below, but does not do equivalence
7420 // detection as completely, so we still apply the other "probing" code
7421 // afterwards even if it will not fix more literals, but it will do one pass
7422 // of proper equivalence detection.
7424 if (!context_->params().debug_postsolve_with_full_solver() &&
7425 num_ignored_variables == 0 && num_ignored_constraints == 0 &&
7426 num_in_extra_constraints == 0) {
7427 // Some problems are formulated in such a way that our SAT heuristics
7428 // simply works without conflict. Get them out of the way first because it
7429 // is possible that the presolve lose this "lucky" ordering. This is in
7430 // particular the case on the SAT14.crafted.complete-xxx-... problems.
7431 if (!LookForTrivialSatSolution(/*deterministic_time_limit=*/1.0,
7432 &local_model, logger_)) {
7433 return false;
7434 }
7435 if (sat_solver->LiteralTrail().Index() == num_variables) {
7436 // Problem solved! We should be able to assign the solution.
7437 CHECK(FixFromAssignment(sat_solver->Assignment(), new_to_old_index,
7438 context_));
7439 return true;
7440 }
7441
7442 SatPresolveOptions options;
7443 options.log_info = true; // log_info;
7444 options.extract_binary_clauses_in_probing = false;
7445 options.use_transitive_reduction = false;
7446 options.deterministic_time_limit =
7447 context_->params().presolve_probing_deterministic_time_limit();
7448
7449 auto* inprocessing = local_model.GetOrCreate<Inprocessing>();
7450 inprocessing->ProvideLogger(logger_);
7451 if (!inprocessing->PresolveLoop(options)) return false;
7452 for (const auto& c : local_model.GetOrCreate<PostsolveClauses>()->clauses) {
7453 sat_postsolver.Add(c[0], c);
7454 }
7455
7456 // Probe + find equivalent literals.
7457 // TODO(user): Use a derived time limit in the probing phase.
7458 ProbeAndFindEquivalentLiteral(sat_solver, &sat_postsolver,
7459 /*drat_proof_handler=*/nullptr, &equiv_map,
7460 logger_);
7461 if (sat_solver->ModelIsUnsat()) return false;
7462 } else {
7463 // TODO(user): BVA takes time and does not seems to help on the minizinc
7464 // benchmarks. So we currently disable it, except if we are on a pure-SAT
7465 // problem, where we follow the default (true) or the user specified value.
7466 params.set_presolve_use_bva(false);
7467 }
7468
7469 // Update the time limit of the initial propagation.
7470 if (!sat_solver->ResetToLevelZero()) return false;
7471 time_limit_->AdvanceDeterministicTime(
7472 local_model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
7473
7474 // Apply the "old" SAT presolve.
7475 SatPresolver sat_presolver(&sat_postsolver, logger_);
7476 sat_presolver.SetNumVariables(num_variables);
7477 if (!equiv_map.empty()) {
7478 sat_presolver.SetEquivalentLiteralMapping(equiv_map);
7479 }
7480 sat_presolver.SetTimeLimit(time_limit_);
7481 sat_presolver.SetParameters(params);
7482
7483 // Load in the presolver.
7484 // Register the fixed variables with the postsolver.
7485 for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
7486 sat_postsolver.FixVariable(sat_solver->LiteralTrail()[i]);
7487 }
7488 sat_solver->ExtractClauses(&sat_presolver);
7489
7490 // Run the presolve for a small number of passes.
7491 // TODO(user): Add a local time limit? this can be slow on big SAT problem.
7492 for (int i = 0; i < 1; ++i) {
7493 const int old_num_clause = sat_postsolver.NumClauses();
7494 if (!sat_presolver.Presolve(can_be_removed)) return false;
7495 if (old_num_clause == sat_postsolver.NumClauses()) break;
7496 }
7497
7498 // Add any new variables to our internal structure.
7499 const int new_num_variables = sat_presolver.NumVariables();
7500 if (new_num_variables > num_variables) {
7501 VLOG(1) << "New variables added by the SAT presolver.";
7502 for (int i = num_variables; i < new_num_variables; ++i) {
7503 new_to_old_index.push_back(context_->working_model->variables().size());
7504 IntegerVariableProto* var_proto =
7505 context_->working_model->add_variables();
7506 var_proto->add_domain(0);
7507 var_proto->add_domain(1);
7508 }
7509 context_->InitializeNewDomains();
7510 }
7511
7512 // Fix variables if any.
7513 if (!FixFromAssignment(sat_postsolver.assignment(), new_to_old_index,
7514 context_)) {
7515 return false;
7516 }
7517
7518 // Add the presolver clauses back into the model.
7519 ExtractClauses(/*merge_into_bool_and=*/true, new_to_old_index, sat_presolver,
7520 context_->working_model);
7521
7522 // Update the constraints <-> variables graph.
7524
7525 // Add the sat_postsolver clauses to mapping_model.
7526 //
7527 // TODO(user): Mark removed variable as removed to detect any potential bugs.
7528 ExtractClauses(/*merge_into_bool_and=*/false, new_to_old_index,
7529 sat_postsolver, context_->mapping_model);
7530 return true;
7531}
7532
7533void CpModelPresolver::ShiftObjectiveWithExactlyOnes() {
7534 if (context_->ModelIsUnsat()) return;
7535
7536 // The objective is already loaded in the context, but we re-canonicalize
7537 // it with the latest information.
7538 if (!context_->CanonicalizeObjective()) {
7539 return;
7540 }
7541
7542 std::vector<int> exos;
7543 const int num_constraints = context_->working_model->constraints_size();
7544 for (int c = 0; c < num_constraints; ++c) {
7545 const ConstraintProto& ct = context_->working_model->constraints(c);
7546 if (!ct.enforcement_literal().empty()) continue;
7547 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
7548 exos.push_back(c);
7549 }
7550 }
7551
7552 // This is not the same from what we do in ExpandObjective() because we do not
7553 // make the minimum cost zero but the second minimum. Note that when we do
7554 // that, we still do not degrade the trivial objective bound as we would if we
7555 // went any further.
7556 //
7557 // One reason why this might be beneficial is that it lower the maximum cost
7558 // magnitude, making more Booleans with the same cost and thus simplifying
7559 // the core optimizer job. I am not 100% sure.
7560 //
7561 // TODO(user): We need to loop a few time to reach a fixed point. Understand
7562 // exactly if there is a fixed-point and how to reach it in a nicer way.
7563 int num_shifts = 0;
7564 for (int i = 0; i < 3; ++i) {
7565 for (const int c : exos) {
7566 const ConstraintProto& ct = context_->working_model->constraints(c);
7567 const int num_terms = ct.exactly_one().literals().size();
7568 if (num_terms <= 1) continue;
7569 int64_t min_obj = std::numeric_limits<int64_t>::max();
7570 int64_t second_min = std::numeric_limits<int64_t>::max();
7571 for (int i = 0; i < num_terms; ++i) {
7572 const int literal = ct.exactly_one().literals(i);
7573 const int64_t var_obj = context_->ObjectiveCoeff(PositiveRef(literal));
7574 const int64_t obj = RefIsPositive(literal) ? var_obj : -var_obj;
7575 if (obj < min_obj) {
7576 second_min = min_obj;
7577 min_obj = obj;
7578 } else if (obj < second_min) {
7579 second_min = obj;
7580 }
7581 }
7582 if (second_min == 0) continue;
7583 ++num_shifts;
7584 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
7585 second_min)) {
7586 if (context_->ModelIsUnsat()) return;
7587 continue;
7588 }
7589 }
7590 }
7591 if (num_shifts > 0) {
7592 context_->UpdateRuleStats("objective: shifted cost with exactly ones",
7593 num_shifts);
7594 }
7595}
7596
7597// Expand the objective expression in some easy cases.
7598//
7599// The ideas is to look at all the "tight" equality constraints. These should
7600// give a topological order on the variable in which we can perform
7601// substitution.
7602//
7603// Basically, we will only use constraints of the form X' = sum ci * Xi' with ci
7604// > 0 and the variable X' being shifted version >= 0. Note that if there is a
7605// cycle with these constraints, all variables involved must be equal to each
7606// other and likely zero. Otherwise, we can express everything in terms of the
7607// leaves.
7608//
7609// This assumes we are more or less at the propagation fix point, even if we
7610// try to address cases where we are not.
7611void CpModelPresolver::ExpandObjective() {
7612 if (time_limit_->LimitReached()) return;
7613 if (context_->ModelIsUnsat()) return;
7614 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
7615
7616 // The objective is already loaded in the context, but we re-canonicalize
7617 // it with the latest information.
7618 if (!context_->CanonicalizeObjective()) {
7619 return;
7620 }
7621
7622 const int num_variables = context_->working_model->variables_size();
7623 const int num_constraints = context_->working_model->constraints_size();
7624
7625 // We consider two types of shifted variables (X - LB(X)) and (UB(X) - X).
7626 const auto get_index = [](int var, bool to_lb) {
7627 return 2 * var + (to_lb ? 0 : 1);
7628 };
7629 const auto get_lit_index = [](int lit) {
7630 return RefIsPositive(lit) ? 2 * lit : 2 * PositiveRef(lit) + 1;
7631 };
7632 const int num_nodes = 2 * num_variables;
7633 std::vector<std::vector<int>> index_graph(num_nodes);
7634
7635 // TODO(user): instead compute how much each constraint can be further
7636 // expanded?
7637 std::vector<int> index_to_best_c(num_nodes, -1);
7638 std::vector<int> index_to_best_size(num_nodes, 0);
7639
7640 // Lets see first if there are "tight" constraint and for which variables.
7641 // We stop processing constraint if we have too many entries.
7642 int num_entries = 0;
7643 int num_propagations = 0;
7644 int num_tight_variables = 0;
7645 int num_tight_constraints = 0;
7646 const int kNumEntriesThreshold = 1e8;
7647 for (int c = 0; c < num_constraints; ++c) {
7648 if (num_entries > kNumEntriesThreshold) break;
7649
7650 const ConstraintProto& ct = context_->working_model->constraints(c);
7651 if (!ct.enforcement_literal().empty()) continue;
7652
7653 // Deal with exactly one.
7654 // An exactly one is always tight on the upper bound of one term.
7655 //
7656 // Note(user): This code assume there is no fixed variable in the exactly
7657 // one. We thus make sure the constraint is re-presolved if for some reason
7658 // we didn't reach the fixed point before calling this code.
7659 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
7660 if (PresolveExactlyOne(context_->working_model->mutable_constraints(c))) {
7661 context_->UpdateConstraintVariableUsage(c);
7662 }
7663 }
7664 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
7665 const int num_terms = ct.exactly_one().literals().size();
7666 ++num_tight_constraints;
7667 num_tight_variables += num_terms;
7668 for (int i = 0; i < num_terms; ++i) {
7669 if (num_entries > kNumEntriesThreshold) break;
7670 const int neg_index = get_lit_index(ct.exactly_one().literals(i)) ^ 1;
7671
7672 const int old_c = index_to_best_c[neg_index];
7673 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
7674 index_to_best_c[neg_index] = c;
7675 index_to_best_size[neg_index] = num_terms;
7676 }
7677
7678 for (int j = 0; j < num_terms; ++j) {
7679 if (j == i) continue;
7680 const int other_index = get_lit_index(ct.exactly_one().literals(j));
7681 ++num_entries;
7682 index_graph[neg_index].push_back(other_index);
7683 }
7684 }
7685 continue;
7686 }
7687
7688 // Skip everything that is not a linear equality constraint.
7689 if (!IsLinearEqualityConstraint(ct)) continue;
7690
7691 // Let see for which variable is it "tight". We need a coeff of 1, and that
7692 // the implied bounds match exactly.
7693 const auto [min_activity, max_activity] =
7694 context_->ComputeMinMaxActivity(ct.linear());
7695
7696 bool is_tight = false;
7697 const int64_t rhs = ct.linear().domain(0);
7698 const int num_terms = ct.linear().vars_size();
7699 for (int i = 0; i < num_terms; ++i) {
7700 const int var = ct.linear().vars(i);
7701 const int64_t coeff = ct.linear().coeffs(i);
7702 if (std::abs(coeff) != 1) continue;
7703 if (num_entries > kNumEntriesThreshold) break;
7704
7705 const int index = get_index(var, coeff > 0);
7706
7707 const int64_t var_range = context_->MaxOf(var) - context_->MinOf(var);
7708 const int64_t implied_shifted_ub = rhs - min_activity;
7709 if (implied_shifted_ub <= var_range) {
7710 if (implied_shifted_ub < var_range) ++num_propagations;
7711 is_tight = true;
7712 ++num_tight_variables;
7713
7714 const int neg_index = index ^ 1;
7715 const int old_c = index_to_best_c[neg_index];
7716 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
7717 index_to_best_c[neg_index] = c;
7718 index_to_best_size[neg_index] = num_terms;
7719 }
7720
7721 for (int j = 0; j < num_terms; ++j) {
7722 if (j == i) continue;
7723 const int other_index =
7724 get_index(ct.linear().vars(j), ct.linear().coeffs(j) > 0);
7725 ++num_entries;
7726 index_graph[neg_index].push_back(other_index);
7727 }
7728 }
7729 const int64_t implied_shifted_lb = max_activity - rhs;
7730 if (implied_shifted_lb <= var_range) {
7731 if (implied_shifted_lb < var_range) ++num_propagations;
7732 is_tight = true;
7733 ++num_tight_variables;
7734
7735 const int old_c = index_to_best_c[index];
7736 if (old_c == -1 || num_terms > index_to_best_size[index]) {
7737 index_to_best_c[index] = c;
7738 index_to_best_size[index] = num_terms;
7739 }
7740
7741 for (int j = 0; j < num_terms; ++j) {
7742 if (j == i) continue;
7743 const int other_index =
7744 get_index(ct.linear().vars(j), ct.linear().coeffs(j) < 0);
7745 ++num_entries;
7746 index_graph[index].push_back(other_index);
7747 }
7748 }
7749 }
7750 if (is_tight) ++num_tight_constraints;
7751 }
7752
7753 // Note(user): We assume the fixed point was already reached by the linear
7754 // presolve, so we don't add extra code here for that. But we still abort if
7755 // some are left to cover corner cases were linear a still not propagated.
7756 if (num_propagations > 0) {
7757 context_->UpdateRuleStats("TODO objective: propagation possible!");
7758 return;
7759 }
7760
7761 // In most cases, we should have no cycle and thus a topo order.
7762 //
7763 // In case there is a cycle, then all member of a strongly connected component
7764 // must be equivalent, this is because from X to Y, if we follow the chain we
7765 // will have X = non_negative_sum + Y and Y = non_negative_sum + X.
7766 //
7767 // Moreover, many shifted variables will need to be zero once we start to have
7768 // equivalence.
7769 //
7770 // TODO(user): Make the fixing to zero? or at least when this happen redo
7771 // a presolve pass?
7772 //
7773 // TODO(user): Densify index to only look at variable that can be substituted
7774 // further.
7775 const auto topo_order = util::graph::FastTopologicalSort(index_graph);
7776 if (!topo_order.ok()) {
7777 // Tricky: We need to cache all domains to derive the proper relations.
7778 // This is because StoreAffineRelation() might propagate them.
7779 std::vector<int64_t> var_min(num_variables);
7780 std::vector<int64_t> var_max(num_variables);
7781 for (int var = 0; var < num_variables; ++var) {
7782 var_min[var] = context_->MinOf(var);
7783 var_max[var] = context_->MaxOf(var);
7784 }
7785
7786 std::vector<std::vector<int>> components;
7787 FindStronglyConnectedComponents(static_cast<int>(index_graph.size()),
7788 index_graph, &components);
7789 for (const std::vector<int>& compo : components) {
7790 if (compo.size() == 1) continue;
7791
7792 const int rep_var = compo[0] / 2;
7793 const bool rep_to_lp = (compo[0] % 2) == 0;
7794 for (int i = 1; i < compo.size(); ++i) {
7795 const int var = compo[i] / 2;
7796 const bool to_lb = (compo[i] % 2) == 0;
7797
7798 // (rep - rep_lb) | (rep_ub - rep) == (var - var_lb) | (var_ub - var)
7799 // +/- rep = +/- var + offset.
7800 const int64_t rep_coeff = rep_to_lp ? 1 : -1;
7801 const int64_t var_coeff = to_lb ? 1 : -1;
7802 const int64_t offset =
7803 (to_lb ? -var_min[var] : var_max[var]) -
7804 (rep_to_lp ? -var_min[rep_var] : var_max[rep_var]);
7805 if (!context_->StoreAffineRelation(rep_var, var, rep_coeff * var_coeff,
7806 rep_coeff * offset)) {
7807 return;
7808 }
7809 }
7810 context_->UpdateRuleStats("objective: detected equivalence",
7811 compo.size() - 1);
7812 }
7813 return;
7814 }
7815
7816 // If the removed variable is now unique, we could remove it if it is implied
7817 // free. But this should already be done by RemoveSingletonInLinear(), so we
7818 // don't redo it here.
7819 int num_expands = 0;
7820 int num_issues = 0;
7821 for (const int index : *topo_order) {
7822 if (index_graph[index].empty()) continue;
7823
7824 const int var = index / 2;
7825 const int64_t obj_coeff = context_->ObjectiveCoeff(var);
7826 if (obj_coeff == 0) continue;
7827
7828 const bool to_lb = (index % 2) == 0;
7829 if (obj_coeff > 0 == to_lb) {
7830 const ConstraintProto& ct =
7831 context_->working_model->constraints(index_to_best_c[index]);
7832 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
7833 int64_t shift = 0;
7834 for (const int lit : ct.exactly_one().literals()) {
7835 if (PositiveRef(lit) == var) {
7836 shift = RefIsPositive(lit) ? obj_coeff : -obj_coeff;
7837 break;
7838 }
7839 }
7840 if (shift == 0) {
7841 ++num_issues;
7842 continue;
7843 }
7844 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
7845 shift)) {
7846 if (context_->ModelIsUnsat()) return;
7847 ++num_issues;
7848 continue;
7849 }
7850 CHECK_EQ(context_->ObjectiveCoeff(var), 0);
7851 ++num_expands;
7852 continue;
7853 }
7854
7855 int64_t objective_coeff_in_expanded_constraint = 0;
7856 const int num_terms = ct.linear().vars().size();
7857 for (int i = 0; i < num_terms; ++i) {
7858 if (ct.linear().vars(i) == var) {
7859 objective_coeff_in_expanded_constraint = ct.linear().coeffs(i);
7860 break;
7861 }
7862 }
7863 if (objective_coeff_in_expanded_constraint == 0) {
7864 ++num_issues;
7865 continue;
7866 }
7867
7868 if (!context_->SubstituteVariableInObjective(
7869 var, objective_coeff_in_expanded_constraint, ct)) {
7870 if (context_->ModelIsUnsat()) return;
7871 ++num_issues;
7872 continue;
7873 }
7874
7875 ++num_expands;
7876 }
7877 }
7878
7879 if (num_expands > 0) {
7880 context_->UpdateRuleStats("objective: expanded via tight equality",
7881 num_expands);
7882 }
7883
7884 timer.AddCounter("propagations", num_propagations);
7885 timer.AddCounter("entries", num_entries);
7886 timer.AddCounter("tight_variables", num_tight_variables);
7887 timer.AddCounter("tight_constraints", num_tight_constraints);
7888 timer.AddCounter("expands", num_expands);
7889 timer.AddCounter("issues", num_issues);
7890}
7891
7892void CpModelPresolver::MergeNoOverlapConstraints() {
7893 if (context_->ModelIsUnsat()) return;
7894
7895 const int num_constraints = context_->working_model->constraints_size();
7896 int old_num_no_overlaps = 0;
7897 int old_num_intervals = 0;
7898
7899 // Extract the no-overlap constraints.
7900 std::vector<int> disjunctive_index;
7901 std::vector<std::vector<Literal>> cliques;
7902 for (int c = 0; c < num_constraints; ++c) {
7903 const ConstraintProto& ct = context_->working_model->constraints(c);
7904 if (ct.constraint_case() != ConstraintProto::kNoOverlap) continue;
7905 std::vector<Literal> clique;
7906 for (const int i : ct.no_overlap().intervals()) {
7907 clique.push_back(Literal(BooleanVariable(i), true));
7908 }
7909 cliques.push_back(clique);
7910 disjunctive_index.push_back(c);
7911
7912 old_num_no_overlaps++;
7913 old_num_intervals += clique.size();
7914 }
7915 if (old_num_no_overlaps == 0) return;
7916
7917 // We reuse the max-clique code from sat.
7918 Model local_model;
7919 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
7920 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
7921 graph->Resize(num_constraints);
7922 for (const std::vector<Literal>& clique : cliques) {
7923 // All variables at false is always a valid solution of the local model,
7924 // so this should never return UNSAT.
7925 CHECK(graph->AddAtMostOne(clique));
7926 }
7927 CHECK(graph->DetectEquivalences());
7928 graph->TransformIntoMaxCliques(
7929 &cliques,
7930 SafeDoubleToInt64(context_->params().merge_no_overlap_work_limit()));
7931
7932 // Replace each no-overlap with an extended version, or remove if empty.
7933 int new_num_no_overlaps = 0;
7934 int new_num_intervals = 0;
7935 for (int i = 0; i < cliques.size(); ++i) {
7936 const int ct_index = disjunctive_index[i];
7937 ConstraintProto* ct =
7938 context_->working_model->mutable_constraints(ct_index);
7939 ct->Clear();
7940 if (cliques[i].empty()) continue;
7941 for (const Literal l : cliques[i]) {
7942 CHECK(l.IsPositive());
7943 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
7944 }
7945 new_num_no_overlaps++;
7946 new_num_intervals += cliques[i].size();
7947 }
7948 if (old_num_intervals != new_num_intervals ||
7949 old_num_no_overlaps != new_num_no_overlaps) {
7950 VLOG(1) << absl::StrCat("Merged ", old_num_no_overlaps, " no-overlaps (",
7951 old_num_intervals, " intervals) into ",
7952 new_num_no_overlaps, " no-overlaps (",
7953 new_num_intervals, " intervals).");
7954 context_->UpdateRuleStats("no_overlap: merged constraints");
7955 }
7956}
7957
7958// TODO(user): Should we take into account the exactly_one constraints? note
7959// that such constraint cannot be extended. If if a literal implies two literals
7960// at one inside an exactly one constraint then it must be false. Similarly if
7961// it implies all literals at zero inside the exactly one.
7962void CpModelPresolver::TransformIntoMaxCliques() {
7963 if (context_->ModelIsUnsat()) return;
7964 if (context_->params().merge_at_most_one_work_limit() <= 0.0) return;
7965
7966 auto convert = [](int ref) {
7967 if (RefIsPositive(ref)) return Literal(BooleanVariable(ref), true);
7968 return Literal(BooleanVariable(NegatedRef(ref)), false);
7969 };
7970 const int num_constraints = context_->working_model->constraints_size();
7971
7972 // Extract the bool_and and at_most_one constraints.
7973 // TODO(user): use probing info?
7974 std::vector<std::vector<Literal>> cliques;
7975
7976 for (int c = 0; c < num_constraints; ++c) {
7977 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7978 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
7979 std::vector<Literal> clique;
7980 for (const int ref : ct->at_most_one().literals()) {
7981 clique.push_back(convert(ref));
7982 }
7983 cliques.push_back(clique);
7984 if (RemoveConstraint(ct)) {
7985 context_->UpdateConstraintVariableUsage(c);
7986 }
7987 } else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
7988 if (ct->enforcement_literal().size() != 1) continue;
7989 const Literal enforcement = convert(ct->enforcement_literal(0));
7990 for (const int ref : ct->bool_and().literals()) {
7991 if (ref == ct->enforcement_literal(0)) continue;
7992 cliques.push_back({enforcement, convert(ref).Negated()});
7993 }
7994 if (RemoveConstraint(ct)) {
7995 context_->UpdateConstraintVariableUsage(c);
7996 }
7997 }
7998 }
7999
8000 int64_t num_literals_before = 0;
8001 const int num_old_cliques = cliques.size();
8002
8003 // We reuse the max-clique code from sat.
8004 Model local_model;
8005 const int num_variables = context_->working_model->variables().size();
8006 local_model.GetOrCreate<Trail>()->Resize(num_variables);
8007 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
8008 graph->Resize(num_variables);
8009 for (const std::vector<Literal>& clique : cliques) {
8010 num_literals_before += clique.size();
8011 if (!graph->AddAtMostOne(clique)) {
8012 return (void)context_->NotifyThatModelIsUnsat();
8013 }
8014 }
8015 if (!graph->DetectEquivalences()) {
8016 return (void)context_->NotifyThatModelIsUnsat();
8017 }
8018 graph->TransformIntoMaxCliques(
8019 &cliques,
8020 SafeDoubleToInt64(context_->params().merge_at_most_one_work_limit()));
8021
8022 // Add the Boolean variable equivalence detected by DetectEquivalences().
8023 // Those are needed because TransformIntoMaxCliques() will replace all
8024 // variable by its representative.
8025 for (int var = 0; var < num_variables; ++var) {
8026 const Literal l = Literal(BooleanVariable(var), true);
8027 if (graph->RepresentativeOf(l) != l) {
8028 const Literal r = graph->RepresentativeOf(l);
8030 var, r.IsPositive() ? r.Variable().value()
8031 : NegatedRef(r.Variable().value()));
8032 }
8033 }
8034
8035 int num_new_cliques = 0;
8036 int64_t num_literals_after = 0;
8037 for (const std::vector<Literal>& clique : cliques) {
8038 if (clique.empty()) continue;
8039 num_new_cliques++;
8040 num_literals_after += clique.size();
8041 ConstraintProto* ct = context_->working_model->add_constraints();
8042 for (const Literal literal : clique) {
8043 if (literal.IsPositive()) {
8044 ct->mutable_at_most_one()->add_literals(literal.Variable().value());
8045 } else {
8046 ct->mutable_at_most_one()->add_literals(
8047 NegatedRef(literal.Variable().value()));
8048 }
8049 }
8050
8051 // Make sure we do not have duplicate variable reference.
8052 PresolveAtMostOne(ct);
8053 }
8055 if (num_new_cliques != num_old_cliques) {
8056 context_->UpdateRuleStats("at_most_one: transformed into max clique.");
8057 }
8058
8059 if (num_old_cliques != num_new_cliques ||
8060 num_literals_before != num_literals_after) {
8061 SOLVER_LOG(logger_, "[MaxClique] Merged ", num_old_cliques, "(",
8062 num_literals_before, " literals) into ", num_new_cliques, "(",
8063 num_literals_after, " literals) at_most_ones.");
8064 }
8065}
8066
8068 if (context_->ModelIsUnsat()) return false;
8069 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8070
8071 // Generic presolve to exploit variable/literal equivalence.
8072 if (ExploitEquivalenceRelations(c, ct)) {
8073 context_->UpdateConstraintVariableUsage(c);
8074 }
8075
8076 // Generic presolve for reified constraint.
8077 if (PresolveEnforcementLiteral(ct)) {
8078 context_->UpdateConstraintVariableUsage(c);
8079 }
8080
8081 // Call the presolve function for this constraint if any.
8082 switch (ct->constraint_case()) {
8083 case ConstraintProto::kBoolOr:
8084 return PresolveBoolOr(ct);
8085 case ConstraintProto::kBoolAnd:
8086 return PresolveBoolAnd(ct);
8087 case ConstraintProto::kAtMostOne:
8088 return PresolveAtMostOne(ct);
8089 case ConstraintProto::kExactlyOne:
8090 return PresolveExactlyOne(ct);
8091 case ConstraintProto::kBoolXor:
8092 return PresolveBoolXor(ct);
8093 case ConstraintProto::kLinMax:
8094 if (CanonicalizeLinearArgument(*ct, ct->mutable_lin_max())) {
8095 context_->UpdateConstraintVariableUsage(c);
8096 }
8097 if (!DivideLinMaxByGcd(c, ct)) return false;
8098 return PresolveLinMax(ct);
8099 case ConstraintProto::kIntProd:
8100 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_prod())) {
8101 context_->UpdateConstraintVariableUsage(c);
8102 }
8103 return PresolveIntProd(ct);
8104 case ConstraintProto::kIntDiv:
8105 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_div())) {
8106 context_->UpdateConstraintVariableUsage(c);
8107 }
8108 return PresolveIntDiv(c, ct);
8109 case ConstraintProto::kIntMod:
8110 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_mod())) {
8111 context_->UpdateConstraintVariableUsage(c);
8112 }
8113 return PresolveIntMod(c, ct);
8114 case ConstraintProto::kLinear: {
8115 if (CanonicalizeLinear(ct)) {
8116 context_->UpdateConstraintVariableUsage(c);
8117 }
8118 if (PropagateDomainsInLinear(c, ct)) {
8119 context_->UpdateConstraintVariableUsage(c);
8120 }
8121 if (PresolveSmallLinear(ct)) {
8122 context_->UpdateConstraintVariableUsage(c);
8123 }
8124 if (PresolveLinearEqualityWithModulo(ct)) {
8125 context_->UpdateConstraintVariableUsage(c);
8126 }
8127 // We first propagate the domains before calling this presolve rule.
8128 if (RemoveSingletonInLinear(ct)) {
8129 context_->UpdateConstraintVariableUsage(c);
8130
8131 // There is no need to re-do a propagation here, but the constraint
8132 // size might have been reduced.
8133 if (PresolveSmallLinear(ct)) {
8134 context_->UpdateConstraintVariableUsage(c);
8135 }
8136 }
8137 if (PresolveSmallLinear(ct)) {
8138 context_->UpdateConstraintVariableUsage(c);
8139 }
8140 if (PresolveLinearOnBooleans(ct)) {
8141 context_->UpdateConstraintVariableUsage(c);
8142 }
8143
8144 // If we extracted some enforcement, we redo some presolve.
8145 const int old_num_enforcement_literals = ct->enforcement_literal_size();
8146 ExtractEnforcementLiteralFromLinearConstraint(c, ct);
8147 if (context_->ModelIsUnsat()) return false;
8148 if (ct->enforcement_literal_size() > old_num_enforcement_literals) {
8149 if (DivideLinearByGcd(ct)) {
8150 context_->UpdateConstraintVariableUsage(c);
8151 }
8152 if (PresolveSmallLinear(ct)) {
8153 context_->UpdateConstraintVariableUsage(c);
8154 }
8155 }
8156
8157 if (PresolveDiophantine(ct)) {
8158 context_->UpdateConstraintVariableUsage(c);
8159 }
8160
8161 TryToReduceCoefficientsOfLinearConstraint(c, ct);
8162 return false;
8163 }
8164 case ConstraintProto::kInterval:
8165 return PresolveInterval(c, ct);
8166 case ConstraintProto::kInverse:
8167 return PresolveInverse(ct);
8168 case ConstraintProto::kElement:
8169 return PresolveElement(ct);
8170 case ConstraintProto::kTable:
8171 return PresolveTable(ct);
8172 case ConstraintProto::kAllDiff:
8173 return PresolveAllDiff(ct);
8174 case ConstraintProto::kNoOverlap:
8175 DetectDuplicateIntervals(c,
8176 ct->mutable_no_overlap()->mutable_intervals());
8177 return PresolveNoOverlap(ct);
8178 case ConstraintProto::kNoOverlap2D:
8179 DetectDuplicateIntervals(
8180 c, ct->mutable_no_overlap_2d()->mutable_x_intervals());
8181 DetectDuplicateIntervals(
8182 c, ct->mutable_no_overlap_2d()->mutable_y_intervals());
8183 return PresolveNoOverlap2D(c, ct);
8184 case ConstraintProto::kCumulative:
8185 DetectDuplicateIntervals(c,
8186 ct->mutable_cumulative()->mutable_intervals());
8187 return PresolveCumulative(ct);
8188 case ConstraintProto::kCircuit:
8189 return PresolveCircuit(ct);
8190 case ConstraintProto::kRoutes:
8191 return PresolveRoutes(ct);
8192 case ConstraintProto::kAutomaton:
8193 return PresolveAutomaton(ct);
8194 case ConstraintProto::kReservoir:
8195 return PresolveReservoir(ct);
8196 default:
8197 return false;
8198 }
8199}
8200
8201// Returns false iff the model is UNSAT.
8202bool CpModelPresolver::ProcessSetPPCSubset(int subset_c, int superset_c,
8203 absl::flat_hash_set<int>* tmp_set,
8204 bool* remove_subset,
8205 bool* remove_superset,
8206 bool* stop_processing_superset) {
8207 ConstraintProto* subset_ct =
8208 context_->working_model->mutable_constraints(subset_c);
8209 ConstraintProto* superset_ct =
8210 context_->working_model->mutable_constraints(superset_c);
8211
8212 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
8213 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
8214 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
8215 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
8216 context_->UpdateRuleStats("setppc: bool_or in at_most_one.");
8217
8218 tmp_set->clear();
8219 if (subset_ct->constraint_case() == ConstraintProto::kBoolOr) {
8220 tmp_set->insert(subset_ct->bool_or().literals().begin(),
8221 subset_ct->bool_or().literals().end());
8222 } else {
8223 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
8224 subset_ct->exactly_one().literals().end());
8225 }
8226
8227 // Fix extras in superset_c to 0, note that these will be removed from the
8228 // constraint later.
8229 for (const int literal :
8230 superset_ct->constraint_case() == ConstraintProto::kAtMostOne
8231 ? superset_ct->at_most_one().literals()
8232 : superset_ct->exactly_one().literals()) {
8233 if (tmp_set->contains(literal)) continue;
8234 if (!context_->SetLiteralToFalse(literal)) return false;
8235 context_->UpdateRuleStats("setppc: fixed variables");
8236 }
8237
8238 // Change superset_c to exactly_one if not already.
8239 if (superset_ct->constraint_case() != ConstraintProto::kExactlyOne) {
8240 ConstraintProto copy = *superset_ct;
8241 (*superset_ct->mutable_exactly_one()->mutable_literals()) =
8242 copy.at_most_one().literals();
8243 }
8244
8245 *remove_subset = true;
8246 return true;
8247 }
8248
8249 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
8250 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
8251 superset_ct->constraint_case() == ConstraintProto::kBoolOr) {
8252 context_->UpdateRuleStats("setppc: removed dominated constraints");
8253 *remove_superset = true;
8254 return true;
8255 }
8256
8257 if (subset_ct->constraint_case() == ConstraintProto::kAtMostOne &&
8258 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
8259 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
8260 context_->UpdateRuleStats("setppc: removed dominated constraints");
8261 *remove_subset = true;
8262 return true;
8263 }
8264
8265 // Note(user): Only the exactly one should really be needed, the intersection
8266 // is taken care of by ProcessAtMostOneAndLinear() in a better way.
8267 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne &&
8268 superset_ct->constraint_case() == ConstraintProto::kLinear) {
8269 tmp_set->clear();
8270 int64_t min_sum = std::numeric_limits<int64_t>::max();
8271 int64_t max_sum = std::numeric_limits<int64_t>::min();
8272 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
8273 subset_ct->exactly_one().literals().end());
8274
8275 // Compute the min/max on the subset of the sum that correspond the exo.
8276 int num_matches = 0;
8277 temp_ct_.Clear();
8278 Domain reachable(0);
8279 std::vector<std::pair<int64_t, int>> coeff_counts;
8280 for (int i = 0; i < superset_ct->linear().vars().size(); ++i) {
8281 const int var = superset_ct->linear().vars(i);
8282 const int64_t coeff = superset_ct->linear().coeffs(i);
8283 if (tmp_set->contains(var)) {
8284 ++num_matches;
8285 min_sum = std::min(min_sum, coeff);
8286 max_sum = std::max(max_sum, coeff);
8287 coeff_counts.push_back({superset_ct->linear().coeffs(i), 1});
8288 } else {
8289 reachable =
8290 reachable
8291 .AdditionWith(
8292 context_->DomainOf(var).ContinuousMultiplicationBy(coeff))
8293 .RelaxIfTooComplex();
8294 temp_ct_.mutable_linear()->add_vars(var);
8295 temp_ct_.mutable_linear()->add_coeffs(coeff);
8296 }
8297 }
8298
8299 // If a linear constraint contains more than one at_most_one or exactly_one,
8300 // after processing one, we might no longer have an inclusion.
8301 //
8302 // TODO(user): If we have multiple disjoint inclusion, we can propagate
8303 // more. For instance on neos-1593097.mps we basically have a
8304 // weighted_sum_over_at_most_one1 >= weighted_sum_over_at_most_one2.
8305 if (num_matches != tmp_set->size()) return true;
8306 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne) {
8307 context_->UpdateRuleStats("setppc: exactly_one included in linear");
8308 } else {
8309 context_->UpdateRuleStats("setppc: at_most_one included in linear");
8310 }
8311
8312 reachable = reachable.AdditionWith(Domain(min_sum, max_sum));
8313 const Domain superset_rhs = ReadDomainFromProto(superset_ct->linear());
8314 if (reachable.IsIncludedIn(superset_rhs)) {
8315 // The constraint is trivial !
8316 context_->UpdateRuleStats("setppc: removed trivial linear constraint");
8317 *remove_superset = true;
8318 return true;
8319 }
8320 if (reachable.IntersectionWith(superset_rhs).IsEmpty()) {
8321 // TODO(user): constraint might become bool_or.
8322 context_->UpdateRuleStats("setppc: removed infeasible linear constraint");
8323 *stop_processing_superset = true;
8324 return MarkConstraintAsFalse(superset_ct);
8325 }
8326
8327 // We reuse the normal linear constraint code to propagate domains of
8328 // the other variable using the inclusion information.
8329 if (superset_ct->enforcement_literal().empty()) {
8330 CHECK_GT(num_matches, 0);
8331 FillDomainInProto(ReadDomainFromProto(superset_ct->linear())
8332 .AdditionWith(Domain(-max_sum, -min_sum)),
8333 temp_ct_.mutable_linear());
8334 PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_);
8335 }
8336
8337 // If we have an exactly one in a linear, we can shift the coefficients of
8338 // all these variables by any constant value. We select a value that reduces
8339 // the number of terms the most.
8340 std::sort(coeff_counts.begin(), coeff_counts.end());
8341 int new_size = 0;
8342 for (int i = 0; i < coeff_counts.size(); ++i) {
8343 if (new_size > 0 &&
8344 coeff_counts[i].first == coeff_counts[new_size - 1].first) {
8345 coeff_counts[new_size - 1].second++;
8346 continue;
8347 }
8348 coeff_counts[new_size++] = coeff_counts[i];
8349 }
8350 coeff_counts.resize(new_size);
8351 int64_t best = 0;
8352 int64_t best_count = 0;
8353 for (const auto [coeff, count] : coeff_counts) {
8354 if (count > best_count) {
8355 best = coeff;
8356 best_count = count;
8357 }
8358 }
8359 if (best != 0) {
8360 LinearConstraintProto new_ct = superset_ct->linear();
8361 int new_size = 0;
8362 for (int i = 0; i < new_ct.vars().size(); ++i) {
8363 const int var = new_ct.vars(i);
8364 int64_t coeff = new_ct.coeffs(i);
8365 if (tmp_set->contains(var)) {
8366 if (coeff == best) continue; // delete term.
8367 coeff -= best;
8368 }
8369 new_ct.set_vars(new_size, var);
8370 new_ct.set_coeffs(new_size, coeff);
8371 ++new_size;
8372 }
8373
8374 new_ct.mutable_vars()->Truncate(new_size);
8375 new_ct.mutable_coeffs()->Truncate(new_size);
8376 FillDomainInProto(ReadDomainFromProto(new_ct).AdditionWith(Domain(-best)),
8377 &new_ct);
8378 if (!PossibleIntegerOverflow(*context_->working_model, new_ct.vars(),
8379 new_ct.coeffs())) {
8380 *superset_ct->mutable_linear() = std::move(new_ct);
8381 context_->UpdateConstraintVariableUsage(superset_c);
8382 context_->UpdateRuleStats("setppc: reduced linear coefficients");
8383 }
8384 }
8385
8386 return true;
8387 }
8388
8389 // We can't deduce anything in the last remaining cases, like an at most one
8390 // in an at least one.
8391 return true;
8392}
8393
8394// TODO(user): TransformIntoMaxCliques() convert the bool_and to
8395// at_most_one, but maybe also duplicating them into bool_or would allow this
8396// function to do more presolving.
8397void CpModelPresolver::ProcessSetPPC() {
8398 if (time_limit_->LimitReached()) return;
8399 if (context_->ModelIsUnsat()) return;
8400 if (context_->params().presolve_inclusion_work_limit() == 0) return;
8401 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
8402
8403 // TODO(user): compute on the fly instead of temporary storing variables?
8404 CompactVectorVector<int> storage;
8405 InclusionDetector detector(storage);
8406 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
8407
8408 // We use an encoding of literal that allows to index arrays.
8409 std::vector<int> temp_literals;
8410 const int num_constraints = context_->working_model->constraints_size();
8411 std::vector<int> relevant_constraints;
8412 for (int c = 0; c < num_constraints; ++c) {
8413 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8414 const auto type = ct->constraint_case();
8415 if (type == ConstraintProto::kBoolOr ||
8416 type == ConstraintProto::kAtMostOne ||
8417 type == ConstraintProto::kExactlyOne) {
8418 // Because TransformIntoMaxCliques() can detect literal equivalence
8419 // relation, we make sure the constraints are presolved before being
8420 // inspected.
8421 if (PresolveOneConstraint(c)) {
8422 context_->UpdateConstraintVariableUsage(c);
8423 }
8424 if (context_->ModelIsUnsat()) return;
8425
8426 temp_literals.clear();
8427 for (const int ref :
8428 type == ConstraintProto::kAtMostOne ? ct->at_most_one().literals()
8429 : type == ConstraintProto::kBoolOr ? ct->bool_or().literals()
8430 : ct->exactly_one().literals()) {
8431 temp_literals.push_back(
8432 Literal(BooleanVariable(PositiveRef(ref)), RefIsPositive(ref))
8433 .Index()
8434 .value());
8435 }
8436 relevant_constraints.push_back(c);
8437 detector.AddPotentialSet(storage.Add(temp_literals));
8438 } else if (type == ConstraintProto::kLinear) {
8439 // We also want to test inclusion with the pseudo-Boolean part of
8440 // linear constraints of size at least 3. Exactly one of size two are
8441 // equivalent literals, and we already deal with this case.
8442 //
8443 // TODO(user): This is not ideal as we currently only process exactly one
8444 // included into linear, and we add overhead by detecting all the other
8445 // cases that we ignore later. That said, we could just propagate a bit
8446 // more the domain if we know at_least_one or at_most_one between literals
8447 // in a linear constraint.
8448 const int size = ct->linear().vars().size();
8449 if (size <= 2) continue;
8450
8451 // TODO(user): We only deal with positive var here. Ideally we should
8452 // match the VARIABLES of the at_most_one/exactly_one with the VARIABLES
8453 // of the linear, and complement all variable to have a literal inclusion.
8454 temp_literals.clear();
8455 for (int i = 0; i < size; ++i) {
8456 const int var = ct->linear().vars(i);
8457 if (!context_->CanBeUsedAsLiteral(var)) continue;
8458 if (!RefIsPositive(var)) continue;
8459 temp_literals.push_back(
8460 Literal(BooleanVariable(var), true).Index().value());
8461 }
8462 if (temp_literals.size() > 2) {
8463 // Note that we only care about the linear being the superset.
8464 relevant_constraints.push_back(c);
8465 detector.AddPotentialSuperset(storage.Add(temp_literals));
8466 }
8467 }
8468 }
8469
8470 absl::flat_hash_set<int> tmp_set;
8471 int64_t num_inclusions = 0;
8472 detector.DetectInclusions([&](int subset, int superset) {
8473 ++num_inclusions;
8474 bool remove_subset = false;
8475 bool remove_superset = false;
8476 bool stop_processing_superset = false;
8477 const int subset_c = relevant_constraints[subset];
8478 const int superset_c = relevant_constraints[superset];
8479 detector.IncreaseWorkDone(storage[subset].size());
8480 detector.IncreaseWorkDone(storage[superset].size());
8481 if (!ProcessSetPPCSubset(subset_c, superset_c, &tmp_set, &remove_subset,
8482 &remove_superset, &stop_processing_superset)) {
8483 detector.Stop();
8484 return;
8485 }
8486 if (remove_subset) {
8487 context_->working_model->mutable_constraints(subset_c)->Clear();
8488 context_->UpdateConstraintVariableUsage(subset_c);
8489 detector.StopProcessingCurrentSubset();
8490 }
8491 if (remove_superset) {
8492 context_->working_model->mutable_constraints(superset_c)->Clear();
8493 context_->UpdateConstraintVariableUsage(superset_c);
8494 detector.StopProcessingCurrentSuperset();
8495 }
8496 if (stop_processing_superset) {
8497 context_->UpdateConstraintVariableUsage(superset_c);
8498 detector.StopProcessingCurrentSuperset();
8499 }
8500 });
8501
8502 timer.AddToWork(detector.work_done() * 1e-9);
8503 timer.AddCounter("relevant_constraints", relevant_constraints.size());
8504 timer.AddCounter("num_inclusions", num_inclusions);
8505}
8506
8507void CpModelPresolver::DetectIncludedEnforcement() {
8508 if (time_limit_->LimitReached()) return;
8509 if (context_->ModelIsUnsat()) return;
8510 if (context_->params().presolve_inclusion_work_limit() == 0) return;
8511 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
8512
8513 // TODO(user): compute on the fly instead of temporary storing variables?
8514 std::vector<int> relevant_constraints;
8515 CompactVectorVector<int> storage;
8516 InclusionDetector detector(storage);
8517 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
8518
8519 std::vector<int> temp_literals;
8520 const int num_constraints = context_->working_model->constraints_size();
8521 for (int c = 0; c < num_constraints; ++c) {
8522 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8523 if (ct->enforcement_literal().size() <= 1) continue;
8524
8525 // Make sure there is no x => x.
8526 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
8527 if (PresolveOneConstraint(c)) {
8528 context_->UpdateConstraintVariableUsage(c);
8529 }
8530 if (context_->ModelIsUnsat()) return;
8531 }
8532
8533 // We use an encoding of literal that allows to index arrays.
8534 temp_literals.clear();
8535 for (const int ref : ct->enforcement_literal()) {
8536 temp_literals.push_back(
8537 Literal(BooleanVariable(PositiveRef(ref)), RefIsPositive(ref))
8538 .Index()
8539 .value());
8540 }
8541 relevant_constraints.push_back(c);
8542
8543 // We only deal with bool_and included in other. Not the other way around,
8544 // Altough linear enforcement included in bool_and does happen.
8545 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
8546 detector.AddPotentialSet(storage.Add(temp_literals));
8547 } else {
8548 detector.AddPotentialSuperset(storage.Add(temp_literals));
8549 }
8550 }
8551
8552 int64_t num_inclusions = 0;
8553 detector.DetectInclusions([&](int subset, int superset) {
8554 ++num_inclusions;
8555 const int subset_c = relevant_constraints[subset];
8556 const int superset_c = relevant_constraints[superset];
8557 ConstraintProto* subset_ct =
8558 context_->working_model->mutable_constraints(subset_c);
8559 ConstraintProto* superset_ct =
8560 context_->working_model->mutable_constraints(superset_c);
8561 if (subset_ct->constraint_case() != ConstraintProto::kBoolAnd) return;
8562
8563 context_->tmp_literal_set.clear();
8564 for (const int ref : subset_ct->bool_and().literals()) {
8565 context_->tmp_literal_set.insert(ref);
8566 }
8567
8568 // Filter superset enforcement.
8569 {
8570 int new_size = 0;
8571 for (const int ref : superset_ct->enforcement_literal()) {
8572 if (context_->tmp_literal_set.contains(ref)) {
8573 context_->UpdateRuleStats("bool_and: filtered enforcement");
8574 } else if (context_->tmp_literal_set.contains(NegatedRef(ref))) {
8575 context_->UpdateRuleStats("bool_and: never enforced");
8576 superset_ct->Clear();
8577 context_->UpdateConstraintVariableUsage(superset_c);
8578 detector.StopProcessingCurrentSuperset();
8579 return;
8580 } else {
8581 superset_ct->set_enforcement_literal(new_size++, ref);
8582 }
8583 }
8584 if (new_size < superset_ct->bool_and().literals().size()) {
8585 context_->UpdateConstraintVariableUsage(superset_c);
8586 superset_ct->mutable_enforcement_literal()->Truncate(new_size);
8587 }
8588 }
8589
8590 if (superset_ct->constraint_case() == ConstraintProto::kBoolAnd) {
8591 int new_size = 0;
8592 for (const int ref : superset_ct->bool_and().literals()) {
8593 if (context_->tmp_literal_set.contains(ref)) {
8594 context_->UpdateRuleStats("bool_and: filtered literal");
8595 } else if (context_->tmp_literal_set.contains(NegatedRef(ref))) {
8596 context_->UpdateRuleStats("bool_and: must be false");
8597 if (!MarkConstraintAsFalse(superset_ct)) return;
8598 context_->UpdateConstraintVariableUsage(superset_c);
8599 detector.StopProcessingCurrentSuperset();
8600 return;
8601 } else {
8602 superset_ct->mutable_bool_and()->set_literals(new_size++, ref);
8603 }
8604 }
8605 if (new_size < superset_ct->bool_and().literals().size()) {
8606 context_->UpdateConstraintVariableUsage(superset_c);
8607 superset_ct->mutable_bool_and()->mutable_literals()->Truncate(new_size);
8608 }
8609 }
8610
8611 if (superset_ct->constraint_case() == ConstraintProto::kLinear) {
8612 context_->UpdateRuleStats("TODO bool_and enforcement in linear enf");
8613 }
8614 });
8615
8616 timer.AddToWork(1e-9 * static_cast<double>(detector.work_done()));
8617 timer.AddCounter("relevant_constraints", relevant_constraints.size());
8618 timer.AddCounter("num_inclusions", num_inclusions);
8619}
8620
8621// Note that because we remove the linear constraint, this will not be called
8622// often, so it is okay to use "heavy" data structure here.
8623//
8624// TODO(user): in the at most one case, consider always creating an associated
8625// literal (l <=> var == rhs), and add the exactly_one = at_most_one U not(l)?
8626// This constraint is implicit from what we create, however internally we will
8627// not recover it easily, so we might not add the linear relaxation
8628// corresponding to the constraint we just removed.
8629bool CpModelPresolver::ProcessEncodingFromLinear(
8630 const int linear_encoding_ct_index,
8631 const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms,
8632 int64_t* num_multiple_terms) {
8633 // Preprocess exactly or at most one.
8634 bool in_exactly_one = false;
8635 absl::flat_hash_map<int, int> var_to_ref;
8636 if (at_most_or_exactly_one.constraint_case() == ConstraintProto::kAtMostOne) {
8637 for (const int ref : at_most_or_exactly_one.at_most_one().literals()) {
8638 CHECK(!var_to_ref.contains(PositiveRef(ref)));
8639 var_to_ref[PositiveRef(ref)] = ref;
8640 }
8641 } else {
8642 CHECK_EQ(at_most_or_exactly_one.constraint_case(),
8643 ConstraintProto::kExactlyOne);
8644 in_exactly_one = true;
8645 for (const int ref : at_most_or_exactly_one.exactly_one().literals()) {
8646 CHECK(!var_to_ref.contains(PositiveRef(ref)));
8647 var_to_ref[PositiveRef(ref)] = ref;
8648 }
8649 }
8650
8651 // Preprocess the linear constraints.
8652 const ConstraintProto& linear_encoding =
8653 context_->working_model->constraints(linear_encoding_ct_index);
8654 int64_t rhs = linear_encoding.linear().domain(0);
8655 int target_ref = std::numeric_limits<int>::min();
8656 std::vector<std::pair<int, int64_t>> ref_to_coeffs;
8657 const int num_terms = linear_encoding.linear().vars().size();
8658 for (int i = 0; i < num_terms; ++i) {
8659 const int ref = linear_encoding.linear().vars(i);
8660 const int64_t coeff = linear_encoding.linear().coeffs(i);
8661 const auto it = var_to_ref.find(PositiveRef(ref));
8662
8663 if (it == var_to_ref.end()) {
8664 CHECK_EQ(target_ref, std::numeric_limits<int>::min()) << "Uniqueness";
8665 CHECK_EQ(std::abs(coeff), 1);
8666 target_ref = coeff == 1 ? ref : NegatedRef(ref);
8667 continue;
8668 }
8669
8670 // We transform the constraint so that the Boolean reference match exactly
8671 // what is in the at most one.
8672 if (it->second == ref) {
8673 // The term in the constraint is the same as in the at_most_one.
8674 ref_to_coeffs.push_back({ref, coeff});
8675 } else {
8676 // We replace "coeff * ref" by "coeff - coeff * (1 - ref)"
8677 rhs -= coeff;
8678 ref_to_coeffs.push_back({NegatedRef(ref), -coeff});
8679 }
8680 }
8681 if (target_ref == std::numeric_limits<int>::min() ||
8682 context_->CanBeUsedAsLiteral(target_ref)) {
8683 // We didn't find the unique integer variable. This might have happenned
8684 // because by processing other encoding we might end up with a fully boolean
8685 // constraint. Just abort, it will be presolved later.
8686 context_->UpdateRuleStats("encoding: candidate linear is all Boolean now.");
8687 return true;
8688 }
8689
8690 // Extract the encoding.
8691 std::vector<int64_t> all_values;
8692 absl::btree_map<int64_t, std::vector<int>> value_to_refs;
8693 for (const auto& [ref, coeff] : ref_to_coeffs) {
8694 const int64_t value = rhs - coeff;
8695 all_values.push_back(value);
8696 value_to_refs[value].push_back(ref);
8697 var_to_ref.erase(PositiveRef(ref));
8698 }
8699 // The one not used "encodes" the rhs value.
8700 for (const auto& [var, ref] : var_to_ref) {
8701 all_values.push_back(rhs);
8702 value_to_refs[rhs].push_back(ref);
8703 }
8704 if (!in_exactly_one) {
8705 // To cover the corner case when the inclusion is an equality. For an at
8706 // most one, the rhs should be always reachable when all Boolean are false.
8707 all_values.push_back(rhs);
8708 }
8709
8710 // Make sure the target domain is up to date.
8711 const Domain new_domain = Domain::FromValues(all_values);
8712 bool domain_reduced = false;
8713 if (!context_->IntersectDomainWith(target_ref, new_domain, &domain_reduced)) {
8714 return false;
8715 }
8716 if (domain_reduced) {
8717 context_->UpdateRuleStats("encoding: reduced target domain");
8718 }
8719
8720 if (context_->CanBeUsedAsLiteral(target_ref)) {
8721 // If target is now a literal, lets not process it here.
8722 context_->UpdateRuleStats("encoding: candidate linear is all Boolean now.");
8723 return true;
8724 }
8725
8726 // Encode the encoding.
8727 absl::flat_hash_set<int64_t> value_set;
8728 for (const int64_t v : context_->DomainOf(target_ref).Values()) {
8729 value_set.insert(v);
8730 }
8731 for (auto& [value, literals] : value_to_refs) {
8732 // For determinism.
8733 absl::c_sort(literals);
8734
8735 // If the value is not in the domain, just set all literal to false.
8736 if (!value_set.contains(value)) {
8737 for (const int lit : literals) {
8738 if (!context_->SetLiteralToFalse(lit)) return false;
8739 }
8740 continue;
8741 }
8742
8743 if (literals.size() == 1 && (in_exactly_one || value != rhs)) {
8744 // Optimization if there is just one literal for this value.
8745 // Note that for the "at most one" case, we can't do that for the rhs.
8746 ++*num_unique_terms;
8747 if (!context_->InsertVarValueEncoding(literals[0], target_ref, value)) {
8748 return false;
8749 }
8750 } else {
8751 ++*num_multiple_terms;
8752 const int associated_lit =
8753 context_->GetOrCreateVarValueEncoding(target_ref, value);
8754 for (const int lit : literals) {
8755 context_->AddImplication(lit, associated_lit);
8756 }
8757
8758 // All false means associated_lit is false too.
8759 // But not for the rhs case if we are not in exactly one.
8760 if (in_exactly_one || value != rhs) {
8761 // TODO(user): Insted of bool_or + implications, we could add an
8762 // exactly one! Experiment with this. In particular it might capture
8763 // more structure for later heuristic to add the exactly one instead.
8764 // This also applies to automata/table/element expansion.
8765 auto* bool_or =
8766 context_->working_model->add_constraints()->mutable_bool_or();
8767 for (const int lit : literals) bool_or->add_literals(lit);
8768 bool_or->add_literals(NegatedRef(associated_lit));
8769 }
8770 }
8771 }
8772
8773 // Remove linear constraint now that it is fully encoded.
8774 context_->working_model->mutable_constraints(linear_encoding_ct_index)
8775 ->Clear();
8777 context_->UpdateConstraintVariableUsage(linear_encoding_ct_index);
8778 return true;
8779}
8780
8781void CpModelPresolver::DetectDuplicateConstraints() {
8782 if (time_limit_->LimitReached()) return;
8783 if (context_->ModelIsUnsat()) return;
8784 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
8785
8786 // We need the objective written for this.
8787 if (context_->working_model->has_objective()) {
8788 if (!context_->CanonicalizeObjective()) return;
8789 context_->WriteObjectiveToProto();
8790 }
8791
8792 // Remove duplicate constraints.
8793 // Note that at this point the objective in the proto should be up to date.
8794 //
8795 // TODO(user): We might want to do that earlier so that our count of variable
8796 // usage is not biased by duplicate constraints.
8797 const std::vector<std::pair<int, int>> duplicates =
8799 timer.AddCounter("duplicates", duplicates.size());
8800 for (const auto& [dup, rep] : duplicates) {
8801 // Note that it is important to look at the type of the representative in
8802 // case the constraint became empty.
8803 DCHECK_LT(kObjectiveConstraint, 0);
8804 const int type =
8807 : context_->working_model->constraints(rep).constraint_case();
8808
8809 // For linear constraint, we merge their rhs since it was ignored in the
8810 // FindDuplicateConstraints() call.
8811 if (type == ConstraintProto::kLinear) {
8812 const Domain rep_domain = ReadDomainFromProto(
8813 context_->working_model->constraints(rep).linear());
8814 const Domain d = ReadDomainFromProto(
8815 context_->working_model->constraints(dup).linear());
8816 if (rep_domain != d) {
8817 context_->UpdateRuleStats("duplicate: merged rhs of linear constraint");
8818 const Domain rhs = rep_domain.IntersectionWith(d);
8819 if (rhs.IsEmpty()) {
8820 if (!MarkConstraintAsFalse(
8821 context_->working_model->mutable_constraints(rep))) {
8822 SOLVER_LOG(logger_, "Unsat after merging two linear constraints");
8823 return;
8824 }
8825
8826 // The representative constraint is no longer a linear constraint,
8827 // so we will not enter this type case again and will just remove
8828 // all subsequent duplicate linear constraints.
8829 context_->UpdateConstraintVariableUsage(rep);
8830 continue;
8831 }
8832 FillDomainInProto(rhs, context_->working_model->mutable_constraints(rep)
8833 ->mutable_linear());
8834 }
8835 }
8836
8837 if (type == kObjectiveConstraint) {
8838 context_->UpdateRuleStats(
8839 "duplicate: linear constraint parallel to objective");
8840 const Domain objective_domain =
8841 ReadDomainFromProto(context_->working_model->objective());
8842 const Domain d = ReadDomainFromProto(
8843 context_->working_model->constraints(dup).linear());
8844 if (objective_domain != d) {
8845 context_->UpdateRuleStats("duplicate: updated objective domain");
8846 const Domain new_domain = objective_domain.IntersectionWith(d);
8847 if (new_domain.IsEmpty()) {
8848 return (void)context_->NotifyThatModelIsUnsat(
8849 "Constraint parallel to the objective makes the objective domain "
8850 "empty.");
8851 }
8852 FillDomainInProto(new_domain,
8853 context_->working_model->mutable_objective());
8854
8855 // TODO(user): this write/read is a bit unclean, but needed.
8856 context_->ReadObjectiveFromProto();
8857 }
8858 }
8859 context_->working_model->mutable_constraints(dup)->Clear();
8860 context_->UpdateConstraintVariableUsage(dup);
8861 context_->UpdateRuleStats("duplicate: removed constraint");
8862 }
8863}
8864
8865void CpModelPresolver::DetectDuplicateConstraintsWithDifferentEnforcements(
8866 const CpModelMapping* mapping, BinaryImplicationGraph* implication_graph,
8867 Trail* trail) {
8868 if (time_limit_->LimitReached()) return;
8869 if (context_->ModelIsUnsat()) return;
8870 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
8871
8872 // We need the objective written for this.
8873 if (context_->working_model->has_objective()) {
8874 if (!context_->CanonicalizeObjective()) return;
8875 context_->WriteObjectiveToProto();
8876 }
8877
8878 absl::flat_hash_set<Literal> enforcement_vars;
8879 std::vector<std::pair<Literal, Literal>> implications_used;
8880 // TODO(user): We can also do similar stuff to linear constraint that just
8881 // differ at a singleton variable. Or that are equalities. Like if expr + X =
8882 // cte and expr + Y = other_cte, we can see that X is in affine relation with
8883 // Y.
8884 const std::vector<std::pair<int, int>> duplicates_without_enforcement =
8885 FindDuplicateConstraints(*context_->working_model, true);
8886 timer.AddCounter("without_enforcements",
8887 duplicates_without_enforcement.size());
8888 for (const auto& [dup, rep] : duplicates_without_enforcement) {
8889 auto* dup_ct = context_->working_model->mutable_constraints(dup);
8890 auto* rep_ct = context_->working_model->mutable_constraints(rep);
8891 if (rep_ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
8892 continue;
8893 }
8894
8895 // If we have a trail, we can check if any variable of the enforcement is
8896 // fixed to false. This is useful for what follows since calling
8897 // implication_graph->DirectImplications() is invalid for fixed variables.
8898 if (trail != nullptr) {
8899 bool found_false_enforcement = false;
8900 for (const int c : {dup, rep}) {
8901 for (const int l :
8902 context_->working_model->constraints(c).enforcement_literal()) {
8903 if (trail->Assignment().LiteralIsFalse(mapping->Literal(l))) {
8904 found_false_enforcement = true;
8905 break;
8906 }
8907 }
8908 if (found_false_enforcement) {
8909 context_->UpdateRuleStats("enforcement: false literal");
8910 if (c == rep) {
8911 rep_ct->Swap(dup_ct);
8912 context_->UpdateConstraintVariableUsage(rep);
8913 }
8914 dup_ct->Clear();
8915 context_->UpdateConstraintVariableUsage(dup);
8916 break;
8917 }
8918 }
8919 if (found_false_enforcement) {
8920 continue;
8921 }
8922 }
8923
8924 // If one of them has no enforcement, then the other can be ignored.
8925 // We always keep rep, but clear its enforcement if any.
8926 if (dup_ct->enforcement_literal().empty() ||
8927 rep_ct->enforcement_literal().empty()) {
8928 context_->UpdateRuleStats("duplicate: removed enforced constraint");
8929 rep_ct->mutable_enforcement_literal()->Clear();
8930 context_->UpdateConstraintVariableUsage(rep);
8931 dup_ct->Clear();
8932 context_->UpdateConstraintVariableUsage(dup);
8933 continue;
8934 }
8935
8936 // Special case. This looks specific but users might reify with a cost
8937 // a duplicate constraint. In this case, no need to have two variables,
8938 // we can make them equal by duality argument.
8939 const int a = rep_ct->enforcement_literal(0);
8940 const int b = dup_ct->enforcement_literal(0);
8941 if (context_->IsFixed(a) || context_->IsFixed(b)) continue;
8942
8943 // TODO(user): Deal with more general situation? Note that we already
8944 // do something similar in dual_bound_strengthening.Strengthen() were we
8945 // are more general as we just require an unique blocking constraint rather
8946 // than a singleton variable.
8947 //
8948 // But we could detect that "a <=> constraint" and "b <=> constraint", then
8949 // we can also add the equality. Alternatively, we can just introduce a new
8950 // variable and merge all duplicate constraint into 1 + bunch of boolean
8951 // constraints liking enforcements.
8952 if (context_->VariableWithCostIsUniqueAndRemovable(a) &&
8954 // Both these case should be presolved before, but it is easy to deal with
8955 // if we encounter them here in some corner cases.
8956 bool skip = false;
8957 if (RefIsPositive(a) == context_->ObjectiveCoeff(PositiveRef(a)) > 0) {
8958 context_->UpdateRuleStats("duplicate: dual fixing enforcement.");
8959 if (!context_->SetLiteralToFalse(a)) return;
8960 skip = true;
8961 }
8962 if (RefIsPositive(b) == context_->ObjectiveCoeff(PositiveRef(b)) > 0) {
8963 context_->UpdateRuleStats("duplicate: dual fixing enforcement.");
8964 if (!context_->SetLiteralToFalse(b)) return;
8965 skip = true;
8966 }
8967 if (skip) continue;
8968
8969 // If there are more than one enforcement literal, then the Booleans
8970 // are not necessarily equivalent: if a constraint is disabled by other
8971 // literal, we don't want to put a or b at 1 and pay an extra cost.
8972 //
8973 // TODO(user): If a is alone, then b==1 can implies a == 1.
8974 // We can also replace [(b, others) => constraint] with (b, others) <=> a.
8975 //
8976 // TODO(user): If the other enforcements are the same, we can also add
8977 // the equivalence and remove the duplicate constraint.
8978 if (rep_ct->enforcement_literal().size() > 1 ||
8979 dup_ct->enforcement_literal().size() > 1) {
8980 context_->UpdateRuleStats(
8981 "TODO duplicate: identical constraint with unique enforcement "
8982 "cost");
8983 continue;
8984 }
8985
8986 // Sign is correct, i.e. ignoring the constraint is expensive.
8987 // The two enforcement can be made equivalent.
8988 context_->UpdateRuleStats("duplicate: dual equivalence of enforcement");
8989 context_->StoreBooleanEqualityRelation(a, b);
8990
8991 // We can also remove duplicate constraint now. It will be done later but
8992 // it seems more efficient to just do it now.
8993 if (dup_ct->enforcement_literal().size() == 1 &&
8994 rep_ct->enforcement_literal().size() == 1) {
8995 dup_ct->Clear();
8996 context_->UpdateConstraintVariableUsage(dup);
8997 continue;
8998 }
8999 }
9000
9001 // Check if the enforcement of one constraint implies the ones of the other.
9002 if (implication_graph != nullptr && mapping != nullptr &&
9003 trail != nullptr) {
9004 for (int i = 0; i < 2; i++) {
9005 // When A and B only differ on their enforcement literals and the
9006 // enforcements of constraint A implies the enforcements of constraint
9007 // B, then constraint A is redundant and we can remove it.
9008 const int c_a = i == 0 ? dup : rep;
9009 const int c_b = i == 0 ? rep : dup;
9010
9011 enforcement_vars.clear();
9012 implications_used.clear();
9013 for (const int proto_lit :
9014 context_->working_model->constraints(c_b).enforcement_literal()) {
9015 const Literal lit = mapping->Literal(proto_lit);
9016 if (trail->Assignment().LiteralIsTrue(lit)) continue;
9017 enforcement_vars.insert(lit);
9018 }
9019 for (const int proto_lit :
9020 context_->working_model->constraints(c_a).enforcement_literal()) {
9021 const Literal lit = mapping->Literal(proto_lit);
9022 if (trail->Assignment().LiteralIsTrue(lit)) continue;
9023 for (const Literal implication_lit :
9024 implication_graph->DirectImplications(lit)) {
9025 auto extracted = enforcement_vars.extract(implication_lit);
9026 if (!extracted.empty() && lit != implication_lit) {
9027 implications_used.push_back({lit, implication_lit});
9028 }
9029 }
9030 }
9031 if (enforcement_vars.empty()) {
9032 context_->UpdateRuleStats(
9033 "duplicate: identical constraint with implied enforcements");
9034 if (c_a == rep) {
9035 // We don't want to remove the representative element of the
9036 // duplicates detection, so swap the constraints.
9037 rep_ct->Swap(dup_ct);
9038 context_->UpdateConstraintVariableUsage(rep);
9039 }
9040 dup_ct->Clear();
9041 context_->UpdateConstraintVariableUsage(dup);
9042 // Subtle point: we need to add the implications we used back to the
9043 // graph. This is because in some case the implications are only true
9044 // in the presence of the "duplicated" constraints.
9045 for (const auto& [a, b] : implications_used) {
9046 const int var_a =
9047 mapping->GetProtoVariableFromBooleanVariable(a.Variable());
9048 const int proto_lit_a = a.IsPositive() ? var_a : NegatedRef(var_a);
9049 const int var_b =
9050 mapping->GetProtoVariableFromBooleanVariable(b.Variable());
9051 const int proto_lit_b = b.IsPositive() ? var_b : NegatedRef(var_b);
9052 context_->AddImplication(proto_lit_a, proto_lit_b);
9053 }
9055 break;
9056 }
9057 }
9058 }
9059 }
9060}
9061
9062void CpModelPresolver::DetectDifferentVariables() {
9063 if (time_limit_->LimitReached()) return;
9064 if (context_->ModelIsUnsat()) return;
9065 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9066
9067 // List the variable that are pairwise different, also store in offset[x, y]
9068 // the offsets such that x >= y + offset.second OR y >= x + offset.first.
9069 std::vector<std::pair<int, int>> different_vars;
9070 absl::flat_hash_map<std::pair<int, int>, std::pair<int64_t, int64_t>> offsets;
9071
9072 // Process the fact "v1 - v2 \in Domain".
9073 const auto process_difference = [&different_vars, &offsets](int v1, int v2,
9074 Domain d) {
9075 Domain exclusion = d.Complement().PartAroundZero();
9076 if (exclusion.IsEmpty()) return;
9077 if (v1 == v2) return;
9078 std::pair<int, int> key = {v1, v2};
9079 if (v1 > v2) {
9080 std::swap(key.first, key.second);
9081 exclusion = exclusion.Negation();
9082 }
9083
9084 // We have x - y not in exclusion,
9085 // so x - y > exclusion.Max() --> x > y + exclusion.Max();
9086 // OR x - y < exclusion.Min() --> y > x - exclusion.Min();
9087 different_vars.push_back(key);
9088 offsets[key] = {exclusion.Min() == std::numeric_limits<int64_t>::min()
9089 ? std::numeric_limits<int64_t>::max()
9090 : CapAdd(-exclusion.Min(), 1),
9091 CapAdd(exclusion.Max(), 1)};
9092 };
9093
9094 // Try to find identical linear constraint with incompatible domains.
9095 // This works really well on neos16.mps.gz where we have
9096 // a <=> x <= y
9097 // b <=> x >= y
9098 // and a => not(b),
9099 // Because of this presolve, we detect that not(a) => b and thus that a and
9100 // not(b) are equivalent. We can thus simplify the problem to just
9101 // a => x < y
9102 // not(a) => x > y
9103 //
9104 // TODO(user): On that same problem, we could actually just have x != y and
9105 // remove the enforcement literal that is just used for that. But then we
9106 // will just re-create it, since we don't have a native way to handle x != y.
9107 //
9108 // TODO(user): Again on neos16.mps, we actually have cliques of x != y so we
9109 // end up with a bunch of groups of 7 variables in [0, 6] that are all
9110 // different. If we can detect that, then we close the problem quickly instead
9111 // of not closing it.
9112 bool has_all_diff = false;
9113 bool has_no_overlap = false;
9114 std::vector<std::pair<uint64_t, int>> hashes;
9115 const int num_constraints = context_->working_model->constraints_size();
9116 for (int c = 0; c < num_constraints; ++c) {
9117 const ConstraintProto& ct = context_->working_model->constraints(c);
9118 if (ct.constraint_case() == ConstraintProto::kAllDiff) {
9119 has_all_diff = true;
9120 continue;
9121 }
9122 if (ct.constraint_case() == ConstraintProto::kNoOverlap) {
9123 has_no_overlap = true;
9124 continue;
9125 }
9126 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
9127 if (ct.linear().vars().size() == 1) continue;
9128
9129 // Detect direct encoding of x != y. Note that we also see that from x > y
9130 // and related.
9131 if (ct.linear().vars().size() == 2 && ct.enforcement_literal().empty() &&
9132 ct.linear().coeffs(0) == -ct.linear().coeffs(1)) {
9133 // We assume the constraint was already divided by its gcd.
9134 if (ct.linear().coeffs(0) == 1) {
9135 process_difference(ct.linear().vars(0), ct.linear().vars(1),
9136 ReadDomainFromProto(ct.linear()));
9137 } else if (ct.linear().coeffs(0) == -1) {
9138 process_difference(ct.linear().vars(1), ct.linear().vars(0),
9139 ReadDomainFromProto(ct.linear()).Negation());
9140 }
9141 }
9142
9143 // TODO(user): Handle this case?
9144 if (ct.enforcement_literal().size() > 1) continue;
9145
9146 uint64_t hash = kDefaultFingerprintSeed;
9147 hash = FingerprintRepeatedField(ct.linear().vars(), hash);
9148 hash = FingerprintRepeatedField(ct.linear().coeffs(), hash);
9149 hashes.push_back({hash, c});
9150 }
9151 std::sort(hashes.begin(), hashes.end());
9152 for (int next, start = 0; start < hashes.size(); start = next) {
9153 next = start + 1;
9154 while (next < hashes.size() && hashes[next].first == hashes[start].first) {
9155 ++next;
9156 }
9157 absl::Span<const std::pair<uint64_t, int>> range(&hashes[start],
9158 next - start);
9159 if (range.size() <= 1) continue;
9160 if (range.size() > 10) continue;
9161
9162 for (int i = 0; i < range.size(); ++i) {
9163 const ConstraintProto& ct1 =
9164 context_->working_model->constraints(range[i].second);
9165 const int num_terms = ct1.linear().vars().size();
9166 for (int j = i + 1; j < range.size(); ++j) {
9167 const ConstraintProto& ct2 =
9168 context_->working_model->constraints(range[j].second);
9169 if (ct2.linear().vars().size() != num_terms) continue;
9170 if (!ReadDomainFromProto(ct1.linear())
9172 .IsEmpty()) {
9173 continue;
9174 }
9175 if (absl::MakeSpan(ct1.linear().vars().data(), num_terms) !=
9176 absl::MakeSpan(ct2.linear().vars().data(), num_terms)) {
9177 continue;
9178 }
9179 if (absl::MakeSpan(ct1.linear().coeffs().data(), num_terms) !=
9180 absl::MakeSpan(ct2.linear().coeffs().data(), num_terms)) {
9181 continue;
9182 }
9183
9184 if (ct1.enforcement_literal().empty() &&
9185 ct2.enforcement_literal().empty()) {
9186 (void)context_->NotifyThatModelIsUnsat(
9187 "two incompatible linear constraint");
9188 return;
9189 }
9190 if (ct1.enforcement_literal().empty()) {
9191 context_->UpdateRuleStats(
9192 "incompatible linear: set enforcement to false");
9193 if (!context_->SetLiteralToFalse(ct2.enforcement_literal(0))) {
9194 return;
9195 }
9196 continue;
9197 }
9198 if (ct2.enforcement_literal().empty()) {
9199 context_->UpdateRuleStats(
9200 "incompatible linear: set enforcement to false");
9201 if (!context_->SetLiteralToFalse(ct1.enforcement_literal(0))) {
9202 return;
9203 }
9204 continue;
9205 }
9206
9207 const int lit1 = ct1.enforcement_literal(0);
9208 const int lit2 = ct2.enforcement_literal(0);
9209
9210 // Detect x != y via lit => x > y && not(lit) => x < y.
9211 if (ct1.linear().vars().size() == 2 &&
9212 ct1.linear().coeffs(0) == -ct1.linear().coeffs(1) &&
9213 lit1 == NegatedRef(lit2)) {
9214 // We have x - y in domain1 or in domain2, so it must be in the union.
9215 Domain union_of_domain =
9216 ReadDomainFromProto(ct1.linear())
9217 .UnionWith(ReadDomainFromProto(ct2.linear()));
9218
9219 // We assume the constraint was already divided by its gcd.
9220 if (ct1.linear().coeffs(0) == 1) {
9221 process_difference(ct1.linear().vars(0), ct1.linear().vars(1),
9222 std::move(union_of_domain));
9223 } else if (ct1.linear().coeffs(0) == -1) {
9224 process_difference(ct1.linear().vars(1), ct1.linear().vars(0),
9225 union_of_domain.Negation());
9226 }
9227 }
9228
9229 if (lit1 != NegatedRef(lit2)) {
9230 context_->UpdateRuleStats("incompatible linear: add implication");
9231 context_->AddImplication(lit1, NegatedRef(lit2));
9232 }
9233 }
9234 }
9235 }
9236
9237 // Detect all_different cliques.
9238 // We reuse the max-clique code from sat.
9239 //
9240 // TODO(user): To avoid doing that more than once, we only run it if there
9241 // is no all-diff in the model already. This is not perfect.
9242 //
9243 // Note(user): The all diff added here will not be expanded since we run this
9244 // after expansion. This is fragile though. Not even sure this is what we
9245 // want.
9246 //
9247 // TODO(user): Start with the existing all diff and expand them rather than
9248 // not running this if there are all_diff present.
9249 //
9250 // TODO(user): Only add them at the end of the presolve! it hurt our presolve
9251 // (like probing is slower) and only serve for linear relaxation.
9252 if (context_->params().infer_all_diffs() && !has_all_diff &&
9253 !has_no_overlap && different_vars.size() > 2) {
9254 WallTimer local_time;
9255 local_time.Start();
9256
9257 std::vector<std::vector<Literal>> cliques;
9258 absl::flat_hash_set<int> used_var;
9259
9260 Model local_model;
9261 const int num_variables = context_->working_model->variables().size();
9262 local_model.GetOrCreate<Trail>()->Resize(num_variables);
9263 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
9264 graph->Resize(num_variables);
9265 for (const auto [var1, var2] : different_vars) {
9266 if (!RefIsPositive(var1)) continue;
9267 if (!RefIsPositive(var2)) continue;
9268 if (var1 == var2) {
9269 (void)context_->NotifyThatModelIsUnsat("x != y with x == y");
9270 return;
9271 }
9272 // All variables at false is always a valid solution of the local model,
9273 // so this should never return UNSAT.
9274 CHECK(graph->AddAtMostOne({Literal(BooleanVariable(var1), true),
9275 Literal(BooleanVariable(var2), true)}));
9276 if (!used_var.contains(var1)) {
9277 used_var.insert(var1);
9278 cliques.push_back({Literal(BooleanVariable(var1), true),
9279 Literal(BooleanVariable(var2), true)});
9280 }
9281 if (!used_var.contains(var2)) {
9282 used_var.insert(var2);
9283 cliques.push_back({Literal(BooleanVariable(var1), true),
9284 Literal(BooleanVariable(var2), true)});
9285 }
9286 }
9287 CHECK(graph->DetectEquivalences());
9288 graph->TransformIntoMaxCliques(&cliques, 1e8);
9289
9290 int num_cliques = 0;
9291 int64_t cumulative_size = 0;
9292 for (std::vector<Literal>& clique : cliques) {
9293 if (clique.size() <= 2) continue;
9294
9295 ++num_cliques;
9296 cumulative_size += clique.size();
9297 std::sort(clique.begin(), clique.end());
9298
9299 // We have an all-diff, but inspect the offsets to see if we have a
9300 // disjunctive ! Note that this is quadratic, but no more complex than the
9301 // scan of the model we just did above, since we had one linear constraint
9302 // per entry.
9303 const int num_terms = clique.size();
9304 std::vector<int64_t> sizes(num_terms,
9305 std::numeric_limits<int64_t>::max());
9306 for (int i = 0; i < num_terms; ++i) {
9307 const int v1 = clique[i].Variable().value();
9308 for (int j = i + 1; j < num_terms; ++j) {
9309 const int v2 = clique[j].Variable().value();
9310 const auto [o1, o2] = offsets.at({v1, v2});
9311 sizes[i] = std::min(sizes[i], o1);
9312 sizes[j] = std::min(sizes[j], o2);
9313 }
9314 }
9315
9316 int num_greater_than_one = 0;
9317 int64_t issue = 0;
9318 for (int i = 0; i < num_terms; ++i) {
9319 CHECK_GE(sizes[i], 1);
9320 if (sizes[i] > 1) ++num_greater_than_one;
9321
9322 // When this happens, it means this interval can never be before
9323 // any other. We should probably handle this case better, but for now we
9324 // abort.
9325 issue = CapAdd(issue, sizes[i]);
9326 if (issue == std::numeric_limits<int64_t>::max()) {
9327 context_->UpdateRuleStats("TODO no_overlap: with task always last");
9328 num_greater_than_one = 0;
9329 break;
9330 }
9331 }
9332
9333 if (num_greater_than_one > 0) {
9334 // We have one size greater than 1, lets add a no_overlap!
9335 //
9336 // TODO(user): try to remove all the quadratic boolean and their
9337 // corresponding linear2 ? Any Boolean not used elsewhere could be
9338 // removed.
9339 context_->UpdateRuleStats(
9340 "no_overlap: inferred from x != y constraints");
9341
9342 std::vector<int> intervals;
9343 for (int i = 0; i < num_terms; ++i) {
9344 intervals.push_back(context_->working_model->constraints().size());
9345 auto* new_interval =
9346 context_->working_model->add_constraints()->mutable_interval();
9347 new_interval->mutable_start()->set_offset(0);
9348 new_interval->mutable_start()->add_coeffs(1);
9349 new_interval->mutable_start()->add_vars(clique[i].Variable().value());
9350
9351 new_interval->mutable_size()->set_offset(sizes[i]);
9352
9353 new_interval->mutable_end()->set_offset(sizes[i]);
9354 new_interval->mutable_end()->add_coeffs(1);
9355 new_interval->mutable_end()->add_vars(clique[i].Variable().value());
9356 }
9357 auto* new_ct =
9358 context_->working_model->add_constraints()->mutable_no_overlap();
9359 for (const int interval : intervals) {
9360 new_ct->add_intervals(interval);
9361 }
9362 } else {
9363 context_->UpdateRuleStats("all_diff: inferred from x != y constraints");
9364 auto* new_ct =
9365 context_->working_model->add_constraints()->mutable_all_diff();
9366 for (const Literal l : clique) {
9367 auto* expr = new_ct->add_exprs();
9368 expr->add_vars(l.Variable().value());
9369 expr->add_coeffs(1);
9370 }
9371 }
9372 }
9373
9374 timer.AddCounter("different", different_vars.size());
9375 timer.AddCounter("cliques", num_cliques);
9376 timer.AddCounter("size", cumulative_size);
9377 }
9378
9380}
9381
9382namespace {
9383
9384// Add factor * subset_ct to the given superset_ct.
9385void Substitute(int64_t factor,
9386 const absl::flat_hash_map<int, int64_t>& subset_coeff_map,
9387 const Domain& subset_rhs, const Domain& superset_rhs,
9388 LinearConstraintProto* mutable_linear) {
9389 int new_size = 0;
9390 const int old_size = mutable_linear->vars().size();
9391 for (int i = 0; i < old_size; ++i) {
9392 const int var = mutable_linear->vars(i);
9393 int64_t coeff = mutable_linear->coeffs(i);
9394 const auto it = subset_coeff_map.find(var);
9395 if (it != subset_coeff_map.end()) {
9396 coeff += factor * it->second;
9397 if (coeff == 0) continue;
9398 }
9399
9400 mutable_linear->set_vars(new_size, var);
9401 mutable_linear->set_coeffs(new_size, coeff);
9402 ++new_size;
9403 }
9404 mutable_linear->mutable_vars()->Truncate(new_size);
9405 mutable_linear->mutable_coeffs()->Truncate(new_size);
9407 superset_rhs.AdditionWith(subset_rhs.MultiplicationBy(factor)),
9408 mutable_linear);
9409}
9410
9411} // namespace
9412
9413void CpModelPresolver::DetectDominatedLinearConstraints() {
9414 if (time_limit_->LimitReached()) return;
9415 if (context_->ModelIsUnsat()) return;
9416 if (context_->params().presolve_inclusion_work_limit() == 0) return;
9417 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9418
9419 // Because we only deal with linear constraint and we want to ignore the
9420 // enforcement part, we reuse the variable list in the inclusion detector.
9421 // Note that we ignore "unclean" constraint, so we only have positive
9422 // reference there.
9423 class Storage {
9424 public:
9425 explicit Storage(CpModelProto* proto) : proto_(*proto) {}
9426 int size() const { return static_cast<int>(proto_.constraints().size()); }
9427 absl::Span<const int> operator[](int c) const {
9428 return absl::MakeSpan(proto_.constraints(c).linear().vars());
9429 }
9430
9431 private:
9432 const CpModelProto& proto_;
9433 };
9434 Storage storage(context_->working_model);
9435 InclusionDetector detector(storage);
9436 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
9437
9438 // Because we use the constraint <-> variable graph, we cannot modify it
9439 // during DetectInclusions(). So we delay the update of the graph.
9440 std::vector<int> constraint_indices_to_clean;
9441
9442 // Cache the linear expression domain.
9443 // TODO(user): maybe we should store this instead of recomputing it.
9444 absl::flat_hash_map<int, Domain> cached_expr_domain;
9445
9446 const int num_constraints = context_->working_model->constraints().size();
9447 for (int c = 0; c < num_constraints; ++c) {
9448 const ConstraintProto& ct = context_->working_model->constraints(c);
9449 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
9450
9451 // We only look at long enforced constraint to avoid all the linear of size
9452 // one or two which can be numerous.
9453 if (!ct.enforcement_literal().empty()) {
9454 if (ct.linear().vars().size() < 3) continue;
9455 }
9456
9457 if (!LinearConstraintIsClean(ct.linear())) {
9458 // This shouldn't happen except in potential corner cases were the
9459 // constraints were not canonicalized before this point. We just skip
9460 // such constraint.
9461 continue;
9462 }
9463
9464 detector.AddPotentialSet(c);
9465
9466 const auto [min_activity, max_activity] =
9467 context_->ComputeMinMaxActivity(ct.linear());
9468 cached_expr_domain[c] = Domain(min_activity, max_activity);
9469 }
9470
9471 int64_t num_inclusions = 0;
9472 absl::flat_hash_map<int, int64_t> coeff_map;
9473 detector.DetectInclusions([&](int subset_c, int superset_c) {
9474 ++num_inclusions;
9475
9476 // Store the coeff of the subset linear constraint in a map.
9477 const ConstraintProto& subset_ct =
9478 context_->working_model->constraints(subset_c);
9479 const LinearConstraintProto& subset_lin = subset_ct.linear();
9480 coeff_map.clear();
9481 detector.IncreaseWorkDone(subset_lin.vars().size());
9482 for (int i = 0; i < subset_lin.vars().size(); ++i) {
9483 coeff_map[subset_lin.vars(i)] = subset_lin.coeffs(i);
9484 }
9485
9486 // We have a perfect match if 'factor_a * subset == factor_b * superset' on
9487 // the common positions. Note that assuming subset has been gcd reduced,
9488 // there is not point considering factor_b != 1.
9489 bool perfect_match = true;
9490
9491 // Find interesting factor of the subset that cancels terms of the superset.
9492 int64_t factor = 0;
9493 int64_t min_pos_factor = std::numeric_limits<int64_t>::max();
9494 int64_t max_neg_factor = std::numeric_limits<int64_t>::min();
9495
9496 // Lets compute the implied domain of the linear expression
9497 // "superset - subset". Note that we actually do not need exact inclusion
9498 // for this algorithm to work, but it is an heuristic to not try it with
9499 // all pair of constraints.
9500 const ConstraintProto& superset_ct =
9501 context_->working_model->constraints(superset_c);
9502 const LinearConstraintProto& superset_lin = superset_ct.linear();
9503 int64_t diff_min_activity = 0;
9504 int64_t diff_max_activity = 0;
9505 detector.IncreaseWorkDone(superset_lin.vars().size());
9506 for (int i = 0; i < superset_lin.vars().size(); ++i) {
9507 const int var = superset_lin.vars(i);
9508 int64_t coeff = superset_lin.coeffs(i);
9509 const auto it = coeff_map.find(var);
9510
9511 if (it != coeff_map.end()) {
9512 const int64_t subset_coeff = it->second;
9513
9514 const int64_t div = coeff / subset_coeff;
9515 if (div > 0) {
9516 min_pos_factor = std::min(div, min_pos_factor);
9517 } else {
9518 max_neg_factor = std::max(div, max_neg_factor);
9519 }
9520
9521 if (perfect_match) {
9522 if (coeff % subset_coeff == 0) {
9523 if (factor == 0) {
9524 // Note that factor can be negative.
9525 factor = div;
9526 } else if (factor != div) {
9527 perfect_match = false;
9528 }
9529 } else {
9530 perfect_match = false;
9531 }
9532 }
9533
9534 // TODO(user): compute the factor first in case it is != 1 ?
9535 coeff -= subset_coeff;
9536 }
9537 if (coeff == 0) continue;
9538 context_->CappedUpdateMinMaxActivity(var, coeff, &diff_min_activity,
9539 &diff_max_activity);
9540 }
9541
9542 const Domain diff_domain(diff_min_activity, diff_max_activity);
9543 const Domain subset_rhs = ReadDomainFromProto(subset_lin);
9544 const Domain superset_rhs = ReadDomainFromProto(superset_lin);
9545
9546 // Case 1: superset is redundant.
9547 // We process this one first as it let us remove the longest constraint.
9548 //
9549 // Important: because of how we computed the inclusion, the diff_domain is
9550 // only valid if none of the enforcement appear in the subset.
9551 //
9552 // TODO(user): Compute the correct infered domain in this case.
9553 if (subset_ct.enforcement_literal().empty()) {
9554 const Domain implied_superset_domain =
9555 subset_rhs.AdditionWith(diff_domain)
9556 .IntersectionWith(cached_expr_domain[superset_c]);
9557 if (implied_superset_domain.IsIncludedIn(superset_rhs)) {
9558 context_->UpdateRuleStats(
9559 "linear inclusion: redundant containing constraint");
9560 context_->working_model->mutable_constraints(superset_c)->Clear();
9561 constraint_indices_to_clean.push_back(superset_c);
9562 detector.StopProcessingCurrentSuperset();
9563 return;
9564 }
9565 }
9566
9567 // Case 2: subset is redundant.
9568 if (superset_ct.enforcement_literal().empty()) {
9569 const Domain implied_subset_domain =
9570 superset_rhs.AdditionWith(diff_domain.Negation())
9571 .IntersectionWith(cached_expr_domain[subset_c]);
9572 if (implied_subset_domain.IsIncludedIn(subset_rhs)) {
9573 context_->UpdateRuleStats(
9574 "linear inclusion: redundant included constraint");
9575 context_->working_model->mutable_constraints(subset_c)->Clear();
9576 constraint_indices_to_clean.push_back(subset_c);
9577 detector.StopProcessingCurrentSubset();
9578 return;
9579 }
9580 }
9581
9582 // If the subset is an equality, and we can add a factor of it to the
9583 // superset so that the activity range is guaranteed to be tighter, we
9584 // always do it. This should both sparsify the problem but also lead to
9585 // tighter propagation.
9586 if (subset_rhs.IsFixed() && subset_ct.enforcement_literal().empty()) {
9587 const int64_t best_factor =
9588 max_neg_factor > -min_pos_factor ? max_neg_factor : min_pos_factor;
9589
9590 // Compute the activity range before and after. Because our pos/neg factor
9591 // are the smallest possible, if one is undefined then we are guaranteed
9592 // to be tighter, and do not need to compute this.
9593 //
9594 // TODO(user): can we compute the best factor that make this as tight as
9595 // possible instead? that looks doable.
9596 bool is_tigher = true;
9597 if (min_pos_factor != std::numeric_limits<int64_t>::max() &&
9598 max_neg_factor != std::numeric_limits<int64_t>::min()) {
9599 int64_t min_before = 0;
9600 int64_t max_before = 0;
9601 int64_t min_after = CapProd(best_factor, subset_rhs.FixedValue());
9602 int64_t max_after = min_after;
9603 for (int i = 0; i < superset_lin.vars().size(); ++i) {
9604 const int var = superset_lin.vars(i);
9605 const auto it = coeff_map.find(var);
9606 if (it == coeff_map.end()) continue;
9607
9608 const int64_t coeff_before = superset_lin.coeffs(i);
9609 const int64_t coeff_after = coeff_before - best_factor * it->second;
9610 context_->CappedUpdateMinMaxActivity(var, coeff_before, &min_before,
9611 &max_before);
9612 context_->CappedUpdateMinMaxActivity(var, coeff_after, &min_after,
9613 &max_after);
9614 }
9615 is_tigher = min_after >= min_before && max_after <= max_before;
9616 }
9617 if (is_tigher) {
9618 context_->UpdateRuleStats("linear inclusion: sparsify superset");
9619 Substitute(-best_factor, coeff_map, subset_rhs, superset_rhs,
9620 context_->working_model->mutable_constraints(superset_c)
9621 ->mutable_linear());
9622 constraint_indices_to_clean.push_back(superset_c);
9623 detector.StopProcessingCurrentSuperset();
9624 return;
9625 }
9626 }
9627
9628 // We do a bit more if we have an exact match and factor * subset is exactly
9629 // a subpart of the superset constraint.
9630 if (perfect_match && subset_ct.enforcement_literal().empty() &&
9631 superset_ct.enforcement_literal().empty()) {
9632 CHECK_NE(factor, 0);
9633
9634 // Propagate domain on the superset - subset variables.
9635 // TODO(user): We can probably still do that if the inclusion is not
9636 // perfect.
9637 temp_ct_.Clear();
9638 auto* mutable_linear = temp_ct_.mutable_linear();
9639 for (int i = 0; i < superset_lin.vars().size(); ++i) {
9640 const int var = superset_lin.vars(i);
9641 const int64_t coeff = superset_lin.coeffs(i);
9642 const auto it = coeff_map.find(var);
9643 if (it != coeff_map.end()) continue;
9644 mutable_linear->add_vars(var);
9645 mutable_linear->add_coeffs(coeff);
9646 }
9648 superset_rhs.AdditionWith(subset_rhs.MultiplicationBy(-factor)),
9649 mutable_linear);
9650 PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_);
9651 if (context_->ModelIsUnsat()) detector.Stop();
9652
9653 if (superset_rhs.IsFixed()) {
9654 if (subset_lin.vars().size() + 1 == superset_lin.vars().size()) {
9655 // Because we propagated the equation on the singleton variable above,
9656 // and we have an equality, the subset is redundant!
9657 context_->UpdateRuleStats(
9658 "linear inclusion: subset + singleton is equality");
9659 context_->working_model->mutable_constraints(subset_c)->Clear();
9660 constraint_indices_to_clean.push_back(subset_c);
9661 detector.StopProcessingCurrentSubset();
9662 return;
9663 }
9664
9665 // This one could make sense if subset is large vs superset.
9666 context_->UpdateRuleStats(
9667 "TODO linear inclusion: superset is equality");
9668 }
9669 }
9670 });
9671
9672 for (const int c : constraint_indices_to_clean) {
9673 context_->UpdateConstraintVariableUsage(c);
9674 }
9675
9676 timer.AddToWork(1e-9 * static_cast<double>(detector.work_done()));
9677 timer.AddCounter("relevant_constraints", detector.num_potential_supersets());
9678 timer.AddCounter("num_inclusions", num_inclusions);
9679 timer.AddCounter("num_redundant", constraint_indices_to_clean.size());
9680}
9681
9682// TODO(user): Also substitute if this appear in the objective?
9683// TODO(user): In some case we only need common_part <= new_var.
9684bool CpModelPresolver::RemoveCommonPart(
9685 const absl::flat_hash_map<int, int64_t>& common_var_coeff_map,
9686 const std::vector<std::pair<int, int64_t>>& block,
9687 ActivityBoundHelper* helper) {
9688 int new_var;
9689 int64_t gcd = 0;
9690 int64_t offset = 0;
9691
9692 // If the common part is expressable via one of the constraint in the block as
9693 // == gcd * X + offset, we can just use this variable instead of creating a
9694 // new variable.
9695 int definiting_equation = -1;
9696 for (const auto [c, multiple] : block) {
9697 const ConstraintProto& ct = context_->working_model->constraints(c);
9698 if (std::abs(multiple) != 1) continue;
9699 if (!IsLinearEqualityConstraint(ct)) continue;
9700 if (ct.linear().vars().size() != common_var_coeff_map.size() + 1) continue;
9701
9702 context_->UpdateRuleStats(
9703 "linear matrix: defining equation for common rectangle");
9704 definiting_equation = c;
9705
9706 // Find the missing term and its coefficient.
9707 int64_t coeff = 0;
9708 const int num_terms = ct.linear().vars().size();
9709 for (int k = 0; k < num_terms; ++k) {
9710 if (common_var_coeff_map.contains(ct.linear().vars(k))) continue;
9711 new_var = ct.linear().vars(k);
9712 coeff = ct.linear().coeffs(k);
9713 break;
9714 }
9715 CHECK_NE(coeff, 0);
9716
9717 // We have multiple * common + coeff * X = constant.
9718 // So common = multiple^-1 * constant - multiple^-1 * coeff * X;
9719 gcd = -multiple * coeff;
9720 offset = multiple * ct.linear().domain(0);
9721 break;
9722 }
9723
9724 // We need a new variable and defining equation.
9725 if (definiting_equation == -1) {
9726 offset = 0;
9727 int64_t min_activity = 0;
9728 int64_t max_activity = 0;
9729 tmp_terms_.clear();
9730 std::vector<std::pair<int, int64_t>> common_part;
9731 for (const auto [var, coeff] : common_var_coeff_map) {
9732 common_part.push_back({var, coeff});
9733 gcd = std::gcd(gcd, std::abs(coeff));
9734 if (context_->CanBeUsedAsLiteral(var) && !context_->IsFixed(var)) {
9735 tmp_terms_.push_back({var, coeff});
9736 continue;
9737 }
9738 if (coeff > 0) {
9739 min_activity += coeff * context_->MinOf(var);
9740 max_activity += coeff * context_->MaxOf(var);
9741 } else {
9742 min_activity += coeff * context_->MaxOf(var);
9743 max_activity += coeff * context_->MinOf(var);
9744 }
9745 }
9746
9747 // We isolated the Boolean in tmp_terms_, use the helper to get
9748 // more precise activity bounds. Note that while tmp_terms_ was built from
9749 // a hash map and is in an unspecified order, the Compute*Activity() helpers
9750 // will still return a deterministic result.
9751 if (!tmp_terms_.empty()) {
9752 min_activity += helper->ComputeMinActivity(tmp_terms_);
9753 max_activity += helper->ComputeMaxActivity(tmp_terms_);
9754 }
9755
9756 if (gcd > 1) {
9757 min_activity /= gcd;
9758 max_activity /= gcd;
9759 for (int i = 0; i < common_part.size(); ++i) {
9760 common_part[i].second /= gcd;
9761 }
9762 }
9763
9764 // TODO(user): use exactly one for the defining constraint here.
9765 if (max_activity == min_activity + 1) {
9766 context_->UpdateRuleStats("linear matrix: boolean common part");
9767 }
9768
9769 // Create new variable.
9770 std::sort(common_part.begin(), common_part.end());
9771 new_var = context_->NewIntVarWithDefinition(
9772 Domain(min_activity, max_activity), common_part);
9773
9774 // Create new linear constraint sum common_part = new_var
9775 auto* new_linear =
9776 context_->working_model->add_constraints()->mutable_linear();
9777 for (const auto [var, coeff] : common_part) {
9778 new_linear->add_vars(var);
9779 new_linear->add_coeffs(coeff);
9780 }
9781 new_linear->add_vars(new_var);
9782 new_linear->add_coeffs(-1);
9783 new_linear->add_domain(0);
9784 new_linear->add_domain(0);
9785 if (PossibleIntegerOverflow(*context_->working_model, new_linear->vars(),
9786 new_linear->coeffs())) {
9787 context_->UpdateRuleStats(
9788 "TODO linear matrix: possible overflow in common part!");
9789 context_->working_model->mutable_constraints()->RemoveLast();
9790 return false;
9791 }
9792
9794 }
9795
9796 // Replace in each constraint the common part by gcd * multiple * new_var !
9797 for (const auto [c, multiple] : block) {
9798 if (c == definiting_equation) continue;
9799
9800 auto* mutable_linear =
9801 context_->working_model->mutable_constraints(c)->mutable_linear();
9802 const int num_terms = mutable_linear->vars().size();
9803 int new_size = 0;
9804 bool new_var_already_seen = false;
9805 for (int k = 0; k < num_terms; ++k) {
9806 if (common_var_coeff_map.contains(mutable_linear->vars(k))) {
9807 CHECK_EQ(common_var_coeff_map.at(mutable_linear->vars(k)) * multiple,
9808 mutable_linear->coeffs(k));
9809 continue;
9810 }
9811
9812 // Tricky: the new variable can already be present in this expression!
9813 int64_t new_coeff = mutable_linear->coeffs(k);
9814 if (mutable_linear->vars(k) == new_var) {
9815 new_var_already_seen = true;
9816 new_coeff += gcd * multiple;
9817 if (new_coeff == 0) continue;
9818 }
9819
9820 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
9821 mutable_linear->set_coeffs(new_size, new_coeff);
9822 ++new_size;
9823 }
9824 mutable_linear->mutable_vars()->Truncate(new_size);
9825 mutable_linear->mutable_coeffs()->Truncate(new_size);
9826 if (!new_var_already_seen) {
9827 mutable_linear->add_vars(new_var);
9828 mutable_linear->add_coeffs(gcd * multiple);
9829 }
9830 if (offset != 0) {
9831 FillDomainInProto(ReadDomainFromProto(*mutable_linear)
9832 .AdditionWith(Domain(-offset * multiple)),
9833 mutable_linear);
9834 }
9835 context_->UpdateConstraintVariableUsage(c);
9836 }
9837 return true;
9838}
9839
9840namespace {
9841
9842int64_t FindVarCoeff(int var, const ConstraintProto& ct) {
9843 const int num_terms = ct.linear().vars().size();
9844 for (int k = 0; k < num_terms; ++k) {
9845 if (ct.linear().vars(k) == var) return ct.linear().coeffs(k);
9846 }
9847 return 0;
9848}
9849
9850int64_t ComputeNonZeroReduction(size_t block_size, size_t common_part_size) {
9851 // We replace the block by a column of new variable.
9852 // But we also need to define this new variable.
9853 return static_cast<int64_t>(block_size * (common_part_size - 1) -
9854 common_part_size - 1);
9855}
9856
9857} // namespace
9858
9859// The idea is to find a set of literal in AMO relationship that appear in
9860// many linear constraints. If this is the case, we can create a new variable to
9861// make an exactly one constraint, and replace it in the linear.
9862void CpModelPresolver::FindBigAtMostOneAndLinearOverlap(
9863 ActivityBoundHelper* helper) {
9864 if (time_limit_->LimitReached()) return;
9865 if (context_->ModelIsUnsat()) return;
9866 if (context_->params().presolve_inclusion_work_limit() == 0) return;
9867 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9868
9869 int64_t num_blocks = 0;
9870 int64_t nz_reduction = 0;
9871 std::vector<int> amo_cts;
9872 std::vector<int> amo_literals;
9873
9874 std::vector<int> common_part;
9875 std::vector<int> best_common_part;
9876
9877 std::vector<bool> common_part_sign;
9878 std::vector<bool> best_common_part_sign;
9879
9880 // We store for each var if the literal was positive or not.
9881 absl::flat_hash_map<int, bool> var_in_amo;
9882
9883 for (int x = 0; x < context_->working_model->variables().size(); ++x) {
9884 // We pick a variable x that appear in some AMO.
9885 if (time_limit_->LimitReached()) break;
9886 if (timer.WorkLimitIsReached()) break;
9887 if (helper->NumAmoForVariable(x) == 0) continue;
9888
9889 amo_cts.clear();
9890 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
9891 for (const int c : context_->VarToConstraints(x)) {
9892 if (c < 0) continue;
9893 const ConstraintProto& ct = context_->working_model->constraints(c);
9894 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
9895 amo_cts.push_back(c);
9896 } else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
9897 amo_cts.push_back(c);
9898 }
9899 }
9900 if (amo_cts.empty()) continue;
9901
9902 // Pick a random AMO containing x.
9903 //
9904 // TODO(user): better algo!
9905 //
9906 // Note that we don't care about the polarity, for each linear constraint,
9907 // if the coeff magnitude are the same, we will just have two values
9908 // controlled by whether the AMO (or EXO subset) is at one or zero.
9909 var_in_amo.clear();
9910 amo_literals.clear();
9911 common_part.clear();
9912 common_part_sign.clear();
9913 int base_ct_index;
9914 {
9915 // For determinism.
9916 std::sort(amo_cts.begin(), amo_cts.end());
9917 const int random_c =
9918 absl::Uniform<int>(*context_->random(), 0, amo_cts.size());
9919 base_ct_index = amo_cts[random_c];
9920 const ConstraintProto& ct =
9921 context_->working_model->constraints(base_ct_index);
9922 const auto& literals = ct.constraint_case() == ConstraintProto::kAtMostOne
9923 ? ct.at_most_one().literals()
9924 : ct.exactly_one().literals();
9925 timer.TrackSimpleLoop(5 * literals.size()); // hash insert are slow.
9926 for (const int literal : literals) {
9927 amo_literals.push_back(literal);
9928 common_part.push_back(PositiveRef(literal));
9929 common_part_sign.push_back(RefIsPositive(literal));
9930 const auto [_, inserted] =
9931 var_in_amo.insert({PositiveRef(literal), RefIsPositive(literal)});
9932 CHECK(inserted);
9933 }
9934 }
9935
9936 const int64_t x_multiplier = var_in_amo.at(x) ? 1 : -1;
9937
9938 // Collect linear constraints with at least two Boolean terms in var_in_amo
9939 // with the same coefficient than x.
9940 std::vector<int> block_cts;
9941 std::vector<int> linear_cts;
9942 int max_common_part = 0;
9943 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
9944 for (const int c : context_->VarToConstraints(x)) {
9945 if (c < 0) continue;
9946 const ConstraintProto& ct = context_->working_model->constraints(c);
9947 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
9948 const int num_terms = ct.linear().vars().size();
9949 if (num_terms < 2) continue;
9950
9951 timer.TrackSimpleLoop(2 * num_terms);
9952 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
9953 if (x_coeff == 0) continue; // could be in enforcement.
9954
9955 int num_in_amo = 0;
9956 for (int k = 0; k < num_terms; ++k) {
9957 const int var = ct.linear().vars(k);
9958 if (!RefIsPositive(var)) {
9959 num_in_amo = 0; // Abort.
9960 break;
9961 }
9962 const auto it = var_in_amo.find(var);
9963 if (it == var_in_amo.end()) continue;
9964 int64_t coeff = ct.linear().coeffs(k);
9965 if (!it->second) coeff = -coeff;
9966 if (coeff != x_coeff) continue;
9967 ++num_in_amo;
9968 }
9969 if (num_in_amo < 2) continue;
9970
9971 max_common_part += num_in_amo;
9972 if (num_in_amo == common_part.size()) {
9973 // This is a perfect match!
9974 block_cts.push_back(c);
9975 } else {
9976 linear_cts.push_back(c);
9977 }
9978 }
9979 if (linear_cts.empty() && block_cts.empty()) continue;
9980 if (max_common_part < 100) continue;
9981
9982 // Remember the best block encountered in the greedy algo below.
9983 // Note that we always start with the current perfect match.
9984 best_common_part = common_part;
9985 best_common_part_sign = common_part_sign;
9986 int best_block_size = block_cts.size();
9987 int best_saved_nz =
9988 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
9989
9990 // For determinism.
9991 std::sort(block_cts.begin(), block_cts.end());
9992 std::sort(linear_cts.begin(), linear_cts.end());
9993
9994 // We will just greedily compute a big block with a random order.
9995 // TODO(user): We could sort by match with the full constraint instead.
9996 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
9997 for (const int c : linear_cts) {
9998 const ConstraintProto& ct = context_->working_model->constraints(c);
9999 const int num_terms = ct.linear().vars().size();
10000 timer.TrackSimpleLoop(2 * num_terms);
10001 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
10002 CHECK_NE(x_coeff, 0);
10003
10004 common_part.clear();
10005 common_part_sign.clear();
10006 for (int k = 0; k < num_terms; ++k) {
10007 const int var = ct.linear().vars(k);
10008 const auto it = var_in_amo.find(var);
10009 if (it == var_in_amo.end()) continue;
10010 int64_t coeff = ct.linear().coeffs(k);
10011 if (!it->second) coeff = -coeff;
10012 if (coeff != x_coeff) continue;
10013 common_part.push_back(var);
10014 common_part_sign.push_back(it->second);
10015 }
10016 if (common_part.size() < 2) continue;
10017
10018 // Change var_in_amo;
10019 block_cts.push_back(c);
10020 if (common_part.size() < var_in_amo.size()) {
10021 var_in_amo.clear();
10022 for (int i = 0; i < common_part.size(); ++i) {
10023 var_in_amo[common_part[i]] = common_part_sign[i];
10024 }
10025 }
10026
10027 // We have a block that can be replaced with a single new boolean +
10028 // defining exo constraint. Note that we can also replace in the base
10029 // constraint, hence the +1 to the block size.
10030 const int64_t saved_nz =
10031 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
10032 if (saved_nz > best_saved_nz) {
10033 best_block_size = block_cts.size();
10034 best_saved_nz = saved_nz;
10035 best_common_part = common_part;
10036 best_common_part_sign = common_part_sign;
10037 }
10038 }
10039 if (best_saved_nz < 100) continue;
10040
10041 // Use the best rectangle.
10042 // We start with the full match.
10043 // TODO(user): maybe we should always just use this if it is large enough?
10044 block_cts.resize(best_block_size);
10045 var_in_amo.clear();
10046 for (int i = 0; i < best_common_part.size(); ++i) {
10047 var_in_amo[best_common_part[i]] = best_common_part_sign[i];
10048 }
10049
10050 ++num_blocks;
10051 nz_reduction += best_saved_nz;
10052 context_->UpdateRuleStats("linear matrix: common amo rectangle");
10053
10054 // First filter the amo.
10055 int new_size = 0;
10056 for (const int lit : amo_literals) {
10057 if (!var_in_amo.contains(PositiveRef(lit))) continue;
10058 amo_literals[new_size++] = lit;
10059 }
10060 if (new_size == amo_literals.size()) {
10061 const ConstraintProto& ct =
10062 context_->working_model->constraints(base_ct_index);
10063 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
10064 context_->UpdateRuleStats("TODO linear matrix: constant rectangle!");
10065 } else {
10066 context_->UpdateRuleStats(
10067 "TODO linear matrix: reuse defining constraint");
10068 }
10069 } else if (new_size + 1 == amo_literals.size()) {
10070 const ConstraintProto& ct =
10071 context_->working_model->constraints(base_ct_index);
10072 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
10073 context_->UpdateRuleStats("TODO linear matrix: reuse exo constraint");
10074 }
10075 }
10076 amo_literals.resize(new_size);
10077
10078 // Create a new literal that is one iff one of the literal in AMO is one.
10079 const int new_var = context_->NewBoolVarWithClause(amo_literals);
10080 {
10081 auto* new_exo =
10082 context_->working_model->add_constraints()->mutable_exactly_one();
10083 new_exo->mutable_literals()->Reserve(amo_literals.size() + 1);
10084 for (const int lit : amo_literals) {
10085 new_exo->add_literals(lit);
10086 }
10087 new_exo->add_literals(NegatedRef(new_var));
10089 }
10090
10091 // Filter the base amo/exo.
10092 {
10093 ConstraintProto* ct =
10094 context_->working_model->mutable_constraints(base_ct_index);
10095 auto* mutable_literals =
10096 ct->constraint_case() == ConstraintProto::kAtMostOne
10097 ? ct->mutable_at_most_one()->mutable_literals()
10098 : ct->mutable_exactly_one()->mutable_literals();
10099 int new_size = 0;
10100 for (const int lit : *mutable_literals) {
10101 if (var_in_amo.contains(PositiveRef(lit))) continue;
10102 (*mutable_literals)[new_size++] = lit;
10103 }
10104 (*mutable_literals)[new_size++] = new_var;
10105 mutable_literals->Truncate(new_size);
10106 context_->UpdateConstraintVariableUsage(base_ct_index);
10107 }
10108
10109 // Use this Boolean in all the linear constraints.
10110 for (const int c : block_cts) {
10111 auto* mutable_linear =
10112 context_->working_model->mutable_constraints(c)->mutable_linear();
10113
10114 // The removed expression will be (offset + coeff_x * new_bool).
10115 int64_t offset = 0;
10116 int64_t coeff_x = 0;
10117
10118 int new_size = 0;
10119 const int num_terms = mutable_linear->vars().size();
10120 for (int k = 0; k < num_terms; ++k) {
10121 const int var = mutable_linear->vars(k);
10122 CHECK(RefIsPositive(var));
10123 int64_t coeff = mutable_linear->coeffs(k);
10124 const auto it = var_in_amo.find(var);
10125 if (it != var_in_amo.end()) {
10126 if (it->second) {
10127 // default is zero, amo at one means we add coeff.
10128 } else {
10129 // term is -coeff * (1 - var) + coeff.
10130 // default is coeff, amo at 1 means we remove coeff.
10131 offset += coeff;
10132 coeff = -coeff;
10133 }
10134 if (coeff_x == 0) coeff_x = coeff;
10135 CHECK_EQ(coeff, coeff_x);
10136 continue;
10137 }
10138 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
10139 mutable_linear->set_coeffs(new_size, coeff);
10140 ++new_size;
10141 }
10142
10143 // Add the new term.
10144 mutable_linear->set_vars(new_size, new_var);
10145 mutable_linear->set_coeffs(new_size, coeff_x);
10146 ++new_size;
10147
10148 mutable_linear->mutable_vars()->Truncate(new_size);
10149 mutable_linear->mutable_coeffs()->Truncate(new_size);
10150 if (offset != 0) {
10152 ReadDomainFromProto(*mutable_linear).AdditionWith(Domain(-offset)),
10153 mutable_linear);
10154 }
10155 context_->UpdateConstraintVariableUsage(c);
10156 }
10157 }
10158
10159 timer.AddCounter("blocks", num_blocks);
10160 timer.AddCounter("saved_nz", nz_reduction);
10161 DCHECK(context_->ConstraintVariableUsageIsConsistent());
10162}
10163
10164// This helps on neos-5045105-creuse.pb.gz for instance.
10165void CpModelPresolver::FindBigVerticalLinearOverlap(
10166 ActivityBoundHelper* helper) {
10167 if (time_limit_->LimitReached()) return;
10168 if (context_->ModelIsUnsat()) return;
10169 if (context_->params().presolve_inclusion_work_limit() == 0) return;
10170 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10171
10172 int64_t num_blocks = 0;
10173 int64_t nz_reduction = 0;
10174 absl::flat_hash_map<int, int64_t> coeff_map;
10175 for (int x = 0; x < context_->working_model->variables().size(); ++x) {
10176 if (timer.WorkLimitIsReached()) break;
10177
10178 bool in_enforcement = false;
10179 std::vector<int> linear_cts;
10180 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
10181 for (const int c : context_->VarToConstraints(x)) {
10182 if (c < 0) continue;
10183 const ConstraintProto& ct = context_->working_model->constraints(c);
10184 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
10185
10186 const int num_terms = ct.linear().vars().size();
10187 if (num_terms < 2) continue;
10188 bool is_canonical = true;
10189 timer.TrackSimpleLoop(num_terms);
10190 for (int k = 0; k < num_terms; ++k) {
10191 if (!RefIsPositive(ct.linear().vars(k))) {
10192 is_canonical = false;
10193 break;
10194 }
10195 }
10196 if (!is_canonical) continue;
10197
10198 // We don't care about enforcement literal, but we don't want x inside.
10199 timer.TrackSimpleLoop(ct.enforcement_literal().size());
10200 for (const int lit : ct.enforcement_literal()) {
10201 if (PositiveRef(lit) == x) {
10202 in_enforcement = true;
10203 break;
10204 }
10205 }
10206
10207 // Note(user): We will actually abort right away in this case, but we
10208 // want work_done to be deterministic! so we do the work anyway.
10209 if (in_enforcement) continue;
10210 linear_cts.push_back(c);
10211 }
10212
10213 // If a Boolean is used in enforcement, we prefer not to combine it with
10214 // others. TODO(user): more generally ignore Boolean or only replace if
10215 // there is a big non-zero improvement.
10216 if (in_enforcement) continue;
10217 if (linear_cts.size() < 10) continue;
10218
10219 // For determinism.
10220 std::sort(linear_cts.begin(), linear_cts.end());
10221 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
10222
10223 // Now it is almost the same algo as for FindBigHorizontalLinearOverlap().
10224 // We greedely compute a "common" rectangle using the first constraint
10225 // as a "base" one. Note that if a aX + bY appear in the majority of
10226 // constraint, we have a good chance to find this block since we start by
10227 // a random constraint.
10228 coeff_map.clear();
10229
10230 std::vector<std::pair<int, int64_t>> block;
10231 std::vector<std::pair<int, int64_t>> common_part;
10232 for (const int c : linear_cts) {
10233 const ConstraintProto& ct = context_->working_model->constraints(c);
10234 const int num_terms = ct.linear().vars().size();
10235 timer.TrackSimpleLoop(num_terms);
10236
10237 // Compute the coeff of x.
10238 const int64_t x_coeff = FindVarCoeff(x, ct);
10239 if (x_coeff == 0) continue;
10240
10241 if (block.empty()) {
10242 // This is our base constraint.
10243 coeff_map.clear();
10244 for (int k = 0; k < num_terms; ++k) {
10245 coeff_map[ct.linear().vars(k)] = ct.linear().coeffs(k);
10246 }
10247 if (coeff_map.size() < 2) continue;
10248 block.push_back({c, x_coeff});
10249 continue;
10250 }
10251
10252 // We are looking for a common divisor of coeff_map and this constraint.
10253 const int64_t gcd =
10254 std::gcd(std::abs(coeff_map.at(x)), std::abs(x_coeff));
10255 const int64_t multiple_base = coeff_map.at(x) / gcd;
10256 const int64_t multiple_ct = x_coeff / gcd;
10257 common_part.clear();
10258 for (int k = 0; k < num_terms; ++k) {
10259 const int64_t coeff = ct.linear().coeffs(k);
10260 if (coeff % multiple_ct != 0) continue;
10261
10262 const auto it = coeff_map.find(ct.linear().vars(k));
10263 if (it == coeff_map.end()) continue;
10264 if (it->second % multiple_base != 0) continue;
10265 if (it->second / multiple_base != coeff / multiple_ct) continue;
10266
10267 common_part.push_back({ct.linear().vars(k), coeff / multiple_ct});
10268 }
10269
10270 // Skip bad constraint.
10271 if (common_part.size() < 2) continue;
10272
10273 // Update coeff_map.
10274 block.push_back({c, x_coeff});
10275 coeff_map.clear();
10276 for (const auto [var, coeff] : common_part) {
10277 coeff_map[var] = coeff;
10278 }
10279 }
10280
10281 // We have a candidate.
10282 const int64_t saved_nz =
10283 ComputeNonZeroReduction(block.size(), coeff_map.size());
10284 if (saved_nz < 30) continue;
10285
10286 // Fix multiples, currently this contain the coeff of x for each constraint.
10287 const int64_t base_x = coeff_map.at(x);
10288 for (auto& [c, multipier] : block) {
10289 CHECK_EQ(multipier % base_x, 0);
10290 multipier /= base_x;
10291 }
10292
10293 // Introduce new_var = coeff_map and perform the substitution.
10294 if (!RemoveCommonPart(coeff_map, block, helper)) continue;
10295 ++num_blocks;
10296 nz_reduction += saved_nz;
10297 context_->UpdateRuleStats("linear matrix: common vertical rectangle");
10298 }
10299
10300 timer.AddCounter("blocks", num_blocks);
10301 timer.AddCounter("saved_nz", nz_reduction);
10302 DCHECK(context_->ConstraintVariableUsageIsConsistent());
10303}
10304
10305// Note that internally, we already split long linear into smaller chunk, so
10306// it should be beneficial to identify common part between many linear
10307// constraint.
10308//
10309// Note(user): This was made to work on var-smallemery-m6j6.pb.gz, but applies
10310// to quite a few miplib problem. Try to improve the heuristics and algorithm to
10311// be faster and detect larger block.
10312void CpModelPresolver::FindBigHorizontalLinearOverlap(
10313 ActivityBoundHelper* helper) {
10314 if (time_limit_->LimitReached()) return;
10315 if (context_->ModelIsUnsat()) return;
10316 if (context_->params().presolve_inclusion_work_limit() == 0) return;
10317 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10318
10319 const int num_constraints = context_->working_model->constraints_size();
10320 std::vector<std::pair<int, int>> to_sort;
10321 for (int c = 0; c < num_constraints; ++c) {
10322 const ConstraintProto& ct = context_->working_model->constraints(c);
10323 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
10324 const int size = ct.linear().vars().size();
10325 if (size < 5) continue;
10326 to_sort.push_back({-size, c});
10327 }
10328 std::sort(to_sort.begin(), to_sort.end());
10329
10330 std::vector<int> sorted_linear;
10331 for (int i = 0; i < to_sort.size(); ++i) {
10332 sorted_linear.push_back(to_sort[i].second);
10333 }
10334
10335 // On large problem, using and hash_map can be slow, so we use the vector
10336 // version and for now fill the map only when doing the change.
10337 std::vector<int> var_to_coeff_non_zeros;
10338 std::vector<int64_t> var_to_coeff(context_->working_model->variables_size(),
10339 0);
10340
10341 int64_t num_blocks = 0;
10342 int64_t nz_reduction = 0;
10343 for (int i = 0; i < sorted_linear.size(); ++i) {
10344 const int c = sorted_linear[i];
10345 if (c < 0) continue;
10346 if (timer.WorkLimitIsReached()) break;
10347
10348 for (const int var : var_to_coeff_non_zeros) {
10349 var_to_coeff[var] = 0;
10350 }
10351 var_to_coeff_non_zeros.clear();
10352 {
10353 const ConstraintProto& ct = context_->working_model->constraints(c);
10354 const int num_terms = ct.linear().vars().size();
10355 timer.TrackSimpleLoop(num_terms);
10356 for (int k = 0; k < num_terms; ++k) {
10357 const int var = ct.linear().vars(k);
10358 var_to_coeff[var] = ct.linear().coeffs(k);
10359 var_to_coeff_non_zeros.push_back(var);
10360 }
10361 }
10362
10363 // Look for an initial overlap big enough.
10364 //
10365 // Note that because we construct it incrementally, we need the first two
10366 // constraint to have an overlap of at least half this.
10367 int saved_nz = 100;
10368 std::vector<int> used_sorted_linear = {i};
10369 std::vector<std::pair<int, int64_t>> block = {{c, 1}};
10370 std::vector<std::pair<int, int64_t>> common_part;
10371 std::vector<std::pair<int, int>> old_matches;
10372
10373 for (int j = 0; j < sorted_linear.size(); ++j) {
10374 if (i == j) continue;
10375 const int other_c = sorted_linear[j];
10376 if (other_c < 0) continue;
10377 const ConstraintProto& ct = context_->working_model->constraints(other_c);
10378
10379 // No need to continue if linear is not large enough.
10380 const int num_terms = ct.linear().vars().size();
10381 const int best_saved_nz =
10382 ComputeNonZeroReduction(block.size() + 1, num_terms);
10383 if (best_saved_nz <= saved_nz) break;
10384
10385 // This is the hot loop here.
10386 timer.TrackSimpleLoop(num_terms);
10387 common_part.clear();
10388 for (int k = 0; k < num_terms; ++k) {
10389 const int var = ct.linear().vars(k);
10390 if (var_to_coeff[var] == ct.linear().coeffs(k)) {
10391 common_part.push_back({var, ct.linear().coeffs(k)});
10392 }
10393 }
10394
10395 // We replace (new_block_size) * (common_size) by
10396 // 1/ and equation of size common_size + 1
10397 // 2/ new_block_size variable
10398 // So new_block_size * common_size - common_size - 1 - new_block_size
10399 // which is (new_block_size - 1) * (common_size - 1) - 2;
10400 const int64_t new_saved_nz =
10401 ComputeNonZeroReduction(block.size() + 1, common_part.size());
10402 if (new_saved_nz > saved_nz) {
10403 saved_nz = new_saved_nz;
10404 used_sorted_linear.push_back(j);
10405 block.push_back({other_c, 1});
10406
10407 // Rebuild the map.
10408 // TODO(user): We could only clear the non-common part.
10409 for (const int var : var_to_coeff_non_zeros) {
10410 var_to_coeff[var] = 0;
10411 }
10412 var_to_coeff_non_zeros.clear();
10413 for (const auto [var, coeff] : common_part) {
10414 var_to_coeff[var] = coeff;
10415 var_to_coeff_non_zeros.push_back(var);
10416 }
10417 } else {
10418 if (common_part.size() > 1) {
10419 old_matches.push_back({j, common_part.size()});
10420 }
10421 }
10422 }
10423
10424 // Introduce a new variable = common_part.
10425 // Use it in all linear constraint.
10426 if (block.size() > 1) {
10427 // Try to extend with exact matches that were skipped.
10428 const int match_size = var_to_coeff_non_zeros.size();
10429 for (const auto [index, old_match_size] : old_matches) {
10430 if (old_match_size < match_size) continue;
10431
10432 int new_match_size = 0;
10433 const int other_c = sorted_linear[index];
10434 const ConstraintProto& ct =
10435 context_->working_model->constraints(other_c);
10436 const int num_terms = ct.linear().vars().size();
10437 for (int k = 0; k < num_terms; ++k) {
10438 if (var_to_coeff[ct.linear().vars(k)] == ct.linear().coeffs(k)) {
10439 ++new_match_size;
10440 }
10441 }
10442 if (new_match_size == match_size) {
10443 context_->UpdateRuleStats(
10444 "linear matrix: common horizontal rectangle extension");
10445 used_sorted_linear.push_back(index);
10446 block.push_back({other_c, 1});
10447 }
10448 }
10449
10450 // TODO(user): avoid creating the map? this is not visible in profile
10451 // though since we only do it when a reduction is performed.
10452 absl::flat_hash_map<int, int64_t> coeff_map;
10453 for (const int var : var_to_coeff_non_zeros) {
10454 coeff_map[var] = var_to_coeff[var];
10455 }
10456 if (!RemoveCommonPart(coeff_map, block, helper)) continue;
10457
10458 ++num_blocks;
10459 nz_reduction += ComputeNonZeroReduction(block.size(), coeff_map.size());
10460 context_->UpdateRuleStats("linear matrix: common horizontal rectangle");
10461 for (const int i : used_sorted_linear) sorted_linear[i] = -1;
10462 }
10463 }
10464
10465 timer.AddCounter("blocks", num_blocks);
10466 timer.AddCounter("saved_nz", nz_reduction);
10467 timer.AddCounter("linears", sorted_linear.size());
10468 DCHECK(context_->ConstraintVariableUsageIsConsistent());
10469}
10470
10471// Find two linear constraints of the form:
10472// - term1 + identical_terms = rhs1
10473// - term2 + identical_terms = rhs2
10474// This allows to infer an affine relation, and remove one constraint and one
10475// variable.
10476void CpModelPresolver::FindAlmostIdenticalLinearConstraints() {
10477 if (time_limit_->LimitReached()) return;
10478 if (context_->ModelIsUnsat()) return;
10479
10480 // Work tracking is required, since in the worst case (n identical
10481 // constraints), we are in O(n^3). In practice we are way faster though. And
10482 // identical constraints should have already be removed when we call this.
10483 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10484
10485 // Only keep non-enforced linear equality of size > 2. Sort by size.
10486 std::vector<std::pair<int, int>> to_sort;
10487 const int num_constraints = context_->working_model->constraints_size();
10488 for (int c = 0; c < num_constraints; ++c) {
10489 const ConstraintProto& ct = context_->working_model->constraints(c);
10490 if (!IsLinearEqualityConstraint(ct)) continue;
10491 if (ct.linear().vars().size() <= 2) continue;
10492
10493 // Our canonicalization should sort constraints, we skip non-canonical ones.
10494 if (!std::is_sorted(ct.linear().vars().begin(), ct.linear().vars().end())) {
10495 continue;
10496 }
10497
10498 to_sort.push_back({ct.linear().vars().size(), c});
10499 }
10500 std::sort(to_sort.begin(), to_sort.end());
10501
10502 // One watcher data structure.
10503 // This is similar to what is used by the inclusion detector.
10504 std::vector<int> var_to_clear;
10505 std::vector<std::vector<std::pair<int, int64_t>>> var_to_ct_coeffs_;
10506 const int num_variables = context_->working_model->variables_size();
10507 var_to_ct_coeffs_.resize(num_variables);
10508
10509 int end;
10510 int num_tested_pairs = 0;
10511 int num_affine_relations = 0;
10512 for (int start = 0; start < to_sort.size(); start = end) {
10513 // Split by identical size.
10514 end = start + 1;
10515 const int length = to_sort[start].first;
10516 for (; end < to_sort.size(); ++end) {
10517 if (to_sort[end].first != length) break;
10518 }
10519 const int span_size = end - start;
10520 if (span_size == 1) continue;
10521
10522 // Watch one term of each constraint randomly.
10523 for (const int var : var_to_clear) var_to_ct_coeffs_[var].clear();
10524 var_to_clear.clear();
10525 for (int i = start; i < end; ++i) {
10526 const int c = to_sort[i].second;
10527 const LinearConstraintProto& lin =
10528 context_->working_model->constraints(c).linear();
10529 const int index =
10530 absl::Uniform<int>(*context_->random(), 0, lin.vars().size());
10531 const int var = lin.vars(index);
10532 if (var_to_ct_coeffs_[var].empty()) var_to_clear.push_back(var);
10533 var_to_ct_coeffs_[var].push_back({c, lin.coeffs(index)});
10534 }
10535
10536 // For each constraint, try other constraints that have at least one term in
10537 // common with the same coeff. Note that for two constraint of size 3, we
10538 // will miss a working pair only if we both watch the variable that is
10539 // different. So only with a probability (1/3)^2. Since we call this more
10540 // than once per presolve, we should be mostly good. For larger constraint,
10541 // we shouldn't miss much.
10542 for (int i1 = start; i1 < end; ++i1) {
10543 if (timer.WorkLimitIsReached()) break;
10544 const int c1 = to_sort[i1].second;
10545 const LinearConstraintProto& lin1 =
10546 context_->working_model->constraints(c1).linear();
10547 bool skip = false;
10548 for (int i = 0; !skip && i < lin1.vars().size(); ++i) {
10549 for (const auto [c2, coeff2] : var_to_ct_coeffs_[lin1.vars(i)]) {
10550 if (c2 == c1) continue;
10551
10552 // TODO(user): we could easily deal with * -1 or other multiples.
10553 if (coeff2 != lin1.coeffs(i)) continue;
10554 if (timer.WorkLimitIsReached()) break;
10555
10556 // Skip if we processed this earlier and deleted it.
10557 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
10558 if (ct2.constraint_case() != ConstraintProto::kLinear) continue;
10559 const LinearConstraintProto& lin2 =
10560 context_->working_model->constraints(c2).linear();
10561 if (lin2.vars().size() != length) continue;
10562
10563 // TODO(user): In practice LinearsDifferAtOneTerm() will abort
10564 // early if the constraints differ early, so we are even faster than
10565 // this.
10566 timer.TrackSimpleLoop(length);
10567
10568 ++num_tested_pairs;
10569 if (LinearsDifferAtOneTerm(lin1, lin2)) {
10570 // The two equalities only differ at one term !
10571 // do c1 -= c2 and presolve c1 right away.
10572 // We should detect new affine relation and remove it.
10573 auto* to_modify = context_->working_model->mutable_constraints(c1);
10575 -1, context_->working_model->constraints(c2), to_modify)) {
10576 continue;
10577 }
10578
10579 // Affine will be of size 2, but we might also have the same
10580 // variable with different coeff in both constraint, in which case
10581 // the linear will be of size 1.
10582 DCHECK_LE(to_modify->linear().vars().size(), 2);
10583
10584 ++num_affine_relations;
10585 context_->UpdateRuleStats(
10586 "linear: advanced affine relation from 2 constraints.");
10587
10588 // We should stop processing c1 since it should be empty afterward.
10589 DivideLinearByGcd(to_modify);
10590 PresolveSmallLinear(to_modify);
10591 context_->UpdateConstraintVariableUsage(c1);
10592 skip = true;
10593 break;
10594 }
10595 }
10596 }
10597 }
10598 }
10599
10600 timer.AddCounter("num_tested_pairs", num_tested_pairs);
10601 timer.AddCounter("found", num_affine_relations);
10602 DCHECK(context_->ConstraintVariableUsageIsConsistent());
10603}
10604
10605void CpModelPresolver::ExtractEncodingFromLinear() {
10606 if (time_limit_->LimitReached()) return;
10607 if (context_->ModelIsUnsat()) return;
10608 if (context_->params().presolve_inclusion_work_limit() == 0) return;
10609 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10610
10611 // TODO(user): compute on the fly instead of temporary storing variables?
10612 std::vector<int> relevant_constraints;
10613 CompactVectorVector<int> storage;
10614 InclusionDetector detector(storage);
10615 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
10616
10617 // Loop over the constraints and fill the structures above.
10618 //
10619 // TODO(user): Ideally we want to process exactly_one first in case a
10620 // linear constraint is both included in an at_most_one and an exactly_one.
10621 std::vector<int> vars;
10622 const int num_constraints = context_->working_model->constraints().size();
10623 for (int c = 0; c < num_constraints; ++c) {
10624 const ConstraintProto& ct = context_->working_model->constraints(c);
10625 switch (ct.constraint_case()) {
10626 case ConstraintProto::kAtMostOne: {
10627 vars.clear();
10628 for (const int ref : ct.at_most_one().literals()) {
10629 vars.push_back(PositiveRef(ref));
10630 }
10631 relevant_constraints.push_back(c);
10632 detector.AddPotentialSuperset(storage.Add(vars));
10633 break;
10634 }
10635 case ConstraintProto::kExactlyOne: {
10636 vars.clear();
10637 for (const int ref : ct.exactly_one().literals()) {
10638 vars.push_back(PositiveRef(ref));
10639 }
10640 relevant_constraints.push_back(c);
10641 detector.AddPotentialSuperset(storage.Add(vars));
10642 break;
10643 }
10644 case ConstraintProto::kLinear: {
10645 // We only consider equality with no enforcement.
10646 if (!IsLinearEqualityConstraint(ct)) continue;
10647
10648 // We also want a single non-Boolean.
10649 // Note that this assume the constraint is canonicalized.
10650 bool is_candidate = true;
10651 int num_integers = 0;
10652 vars.clear();
10653 const int num_terms = ct.linear().vars().size();
10654 for (int i = 0; i < num_terms; ++i) {
10655 const int ref = ct.linear().vars(i);
10656 if (context_->CanBeUsedAsLiteral(ref)) {
10657 vars.push_back(PositiveRef(ref));
10658 } else {
10659 ++num_integers;
10660 if (std::abs(ct.linear().coeffs(i)) != 1) {
10661 is_candidate = false;
10662 break;
10663 }
10664 if (num_integers == 2) {
10665 is_candidate = false;
10666 break;
10667 }
10668 }
10669 }
10670
10671 // We ignore cases with just one Boolean as this should be already dealt
10672 // with elsewhere.
10673 if (is_candidate && num_integers == 1 && vars.size() > 1) {
10674 relevant_constraints.push_back(c);
10675 detector.AddPotentialSubset(storage.Add(vars));
10676 }
10677 break;
10678 }
10679 default:
10680 break;
10681 }
10682 }
10683
10684 // Stats.
10685 int64_t num_exactly_one_encodings = 0;
10686 int64_t num_at_most_one_encodings = 0;
10687 int64_t num_literals = 0;
10688 int64_t num_unique_terms = 0;
10689 int64_t num_multiple_terms = 0;
10690
10691 detector.DetectInclusions([&](int subset, int superset) {
10692 const int subset_c = relevant_constraints[subset];
10693 const int superset_c = relevant_constraints[superset];
10694 const ConstraintProto& superset_ct =
10695 context_->working_model->constraints(superset_c);
10696 if (superset_ct.constraint_case() == ConstraintProto::kAtMostOne) {
10697 ++num_at_most_one_encodings;
10698 } else {
10699 ++num_exactly_one_encodings;
10700 }
10701 num_literals += storage[subset].size();
10702 context_->UpdateRuleStats("encoding: extracted from linear");
10703
10704 if (!ProcessEncodingFromLinear(subset_c, superset_ct, &num_unique_terms,
10705 &num_multiple_terms)) {
10706 detector.Stop(); // UNSAT.
10707 }
10708
10709 detector.StopProcessingCurrentSubset();
10710 });
10711
10712 timer.AddCounter("potential_supersets", detector.num_potential_supersets());
10713 timer.AddCounter("potential_subsets", detector.num_potential_subsets());
10714 timer.AddCounter("amo_encodings", num_at_most_one_encodings);
10715 timer.AddCounter("exo_encodings", num_exactly_one_encodings);
10716 timer.AddCounter("unique_terms", num_unique_terms);
10717 timer.AddCounter("multiple_terms", num_multiple_terms);
10718 timer.AddCounter("literals", num_literals);
10719}
10720
10721// Special case: if a literal l appear in exactly two constraints:
10722// - l => var in domain1
10723// - not(l) => var in domain2
10724// then we know that domain(var) is included in domain1 U domain2,
10725// and that the literal l can be removed (and determined at postsolve).
10726//
10727// TODO(user): This could be generalized further to linear of size > 1 if for
10728// example the terms are the same.
10729//
10730// We wait for the model expansion to take place in order to avoid removing
10731// encoding that will later be re-created during expansion.
10732void CpModelPresolver::LookAtVariableWithDegreeTwo(int var) {
10733 CHECK(RefIsPositive(var));
10734 CHECK(context_->ConstraintVariableGraphIsUpToDate());
10735 if (context_->ModelIsUnsat()) return;
10736 if (context_->keep_all_feasible_solutions) return;
10737 if (context_->IsFixed(var)) return;
10738 if (!context_->ModelIsExpanded()) return;
10739 if (!context_->CanBeUsedAsLiteral(var)) return;
10740
10741 // TODO(user): If var is in objective, we might be able to tighten domains.
10742 // ex: enf => x \in [0, 1]
10743 // not(enf) => x \in [1, 2]
10744 // The x can be removed from one place. Maybe just do <=> not in [0,1] with
10745 // dual code?
10746 if (context_->VarToConstraints(var).size() != 2) return;
10747
10748 bool abort = false;
10749 int ct_var = -1;
10750 Domain union_of_domain;
10751 int num_positive = 0;
10752 std::vector<int> constraint_indices_to_remove;
10753 for (const int c : context_->VarToConstraints(var)) {
10754 if (c < 0) {
10755 abort = true;
10756 break;
10757 }
10758 constraint_indices_to_remove.push_back(c);
10759 const ConstraintProto& ct = context_->working_model->constraints(c);
10760 if (ct.enforcement_literal().size() != 1 ||
10761 PositiveRef(ct.enforcement_literal(0)) != var ||
10762 ct.constraint_case() != ConstraintProto::kLinear ||
10763 ct.linear().vars().size() != 1) {
10764 abort = true;
10765 break;
10766 }
10767 if (ct.enforcement_literal(0) == var) ++num_positive;
10768 if (ct_var != -1 && PositiveRef(ct.linear().vars(0)) != ct_var) {
10769 abort = true;
10770 break;
10771 }
10772 ct_var = PositiveRef(ct.linear().vars(0));
10773 union_of_domain = union_of_domain.UnionWith(
10774 ReadDomainFromProto(ct.linear())
10775 .InverseMultiplicationBy(RefIsPositive(ct.linear().vars(0))
10776 ? ct.linear().coeffs(0)
10777 : -ct.linear().coeffs(0)));
10778 }
10779 if (abort) return;
10780 if (num_positive != 1) return;
10781 if (!context_->IntersectDomainWith(ct_var, union_of_domain)) return;
10782
10783 context_->UpdateRuleStats("variables: removable enforcement literal");
10784 absl::c_sort(constraint_indices_to_remove); // For determinism
10785 for (const int c : constraint_indices_to_remove) {
10786 context_->NewMappingConstraint(context_->working_model->constraints(c),
10787 __FILE__, __LINE__);
10788 context_->working_model->mutable_constraints(c)->Clear();
10789 context_->UpdateConstraintVariableUsage(c);
10790 }
10791 context_->MarkVariableAsRemoved(var);
10792}
10793
10794namespace {
10795
10796absl::Span<const int> AtMostOneOrExactlyOneLiterals(const ConstraintProto& ct) {
10797 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
10798 return {ct.at_most_one().literals()};
10799 } else {
10800 return {ct.exactly_one().literals()};
10801 }
10802}
10803
10804} // namespace
10805
10806void CpModelPresolver::ProcessVariableInTwoAtMostOrExactlyOne(int var) {
10807 DCHECK(RefIsPositive(var));
10808 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
10809 if (context_->ModelIsUnsat()) return;
10810 if (context_->IsFixed(var)) return;
10811 if (context_->VariableWasRemoved(var)) return;
10812 if (!context_->ModelIsExpanded()) return;
10813 if (!context_->CanBeUsedAsLiteral(var)) return;
10814
10815 int64_t cost = 0;
10816 if (context_->VarToConstraints(var).contains(kObjectiveConstraint)) {
10817 if (context_->VarToConstraints(var).size() != 3) return;
10818 cost = context_->ObjectiveMap().at(var);
10819 } else {
10820 if (context_->VarToConstraints(var).size() != 2) return;
10821 }
10822
10823 // We have a variable with a cost (or without) that appear in two constraints.
10824 // We want two at_most_one or exactly_one.
10825 // TODO(user): Also deal with bool_and.
10826 int c1 = -1;
10827 int c2 = -1;
10828 for (const int c : context_->VarToConstraints(var)) {
10829 if (c < 0) continue;
10830 const ConstraintProto& ct = context_->working_model->constraints(c);
10831 if (ct.constraint_case() != ConstraintProto::kAtMostOne &&
10832 ct.constraint_case() != ConstraintProto::kExactlyOne) {
10833 return;
10834 }
10835 if (c1 == -1) {
10836 c1 = c;
10837 } else {
10838 c2 = c;
10839 }
10840 }
10841
10842 // This can happen for variable in a kAffineRelationConstraint.
10843 if (c1 == -1 || c2 == -1) return;
10844
10845 // Tricky: We iterate on a map above, so the order is non-deterministic, we
10846 // do not want that, so we re-order the constraints.
10847 if (c1 > c2) std::swap(c1, c2);
10848
10849 // We can always sum the two constraints.
10850 // If var appear in one and not(var) in the other, the two term cancel out to
10851 // one, so we still have an <= 1 (or eventually a ==1 (see below).
10852 //
10853 // Note that if the constraint are of size one, they can just be preprocessed
10854 // individually and just be removed. So we abort here as the code below
10855 // is incorrect if new_ct is an empty constraint.
10856 context_->tmp_literals.clear();
10857 int c1_ref = std::numeric_limits<int>::min();
10858 const ConstraintProto& ct1 = context_->working_model->constraints(c1);
10859 if (AtMostOneOrExactlyOneLiterals(ct1).size() <= 1) return;
10860 for (const int lit : AtMostOneOrExactlyOneLiterals(ct1)) {
10861 if (PositiveRef(lit) == var) {
10862 c1_ref = lit;
10863 } else {
10864 context_->tmp_literals.push_back(lit);
10865 }
10866 }
10867 int c2_ref = std::numeric_limits<int>::min();
10868 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
10869 if (AtMostOneOrExactlyOneLiterals(ct2).size() <= 1) return;
10870 for (const int lit : AtMostOneOrExactlyOneLiterals(ct2)) {
10871 if (PositiveRef(lit) == var) {
10872 c2_ref = lit;
10873 } else {
10874 context_->tmp_literals.push_back(lit);
10875 }
10876 }
10877 DCHECK_NE(c1_ref, std::numeric_limits<int>::min());
10878 DCHECK_NE(c2_ref, std::numeric_limits<int>::min());
10879 if (c1_ref != NegatedRef(c2_ref)) return;
10880
10881 // If the cost is non-zero, we can use an exactly one to make it zero.
10882 // Use that exactly one in the postsolve to recover the value of var.
10883 int64_t cost_shift = 0;
10884 absl::Span<const int> literals;
10885 if (ct1.constraint_case() == ConstraintProto::kExactlyOne) {
10886 cost_shift = RefIsPositive(c1_ref) ? cost : -cost;
10887 literals = ct1.exactly_one().literals();
10888 } else if (ct2.constraint_case() == ConstraintProto::kExactlyOne) {
10889 cost_shift = RefIsPositive(c2_ref) ? cost : -cost;
10890 literals = ct2.exactly_one().literals();
10891 } else {
10892 // Dual argument. The one with a negative cost can be transformed to
10893 // an exactly one.
10894 // Tricky: if there is a cost, we don't want the objective to be
10895 // constraining to be able to do that.
10896 if (context_->keep_all_feasible_solutions) return;
10897 if (cost != 0 && context_->ObjectiveDomainIsConstraining()) return;
10898
10899 if (RefIsPositive(c1_ref) == (cost < 0)) {
10900 cost_shift = RefIsPositive(c1_ref) ? cost : -cost;
10901 literals = ct1.at_most_one().literals();
10902 } else {
10903 cost_shift = RefIsPositive(c2_ref) ? cost : -cost;
10904 literals = ct2.at_most_one().literals();
10905 }
10906 }
10907
10908 if (!context_->ShiftCostInExactlyOne(literals, cost_shift)) return;
10909 DCHECK(!context_->ObjectiveMap().contains(var));
10910 context_->NewMappingConstraint(__FILE__, __LINE__)
10911 ->mutable_exactly_one()
10912 ->mutable_literals()
10913 ->Assign(literals.begin(), literals.end());
10914
10915 // We can now replace the two constraint by a single one, and delete var!
10916 const int new_ct_index = context_->working_model->constraints().size();
10917 ConstraintProto* new_ct = context_->working_model->add_constraints();
10918 if (ct1.constraint_case() == ConstraintProto::kExactlyOne &&
10919 ct2.constraint_case() == ConstraintProto::kExactlyOne) {
10920 for (const int lit : context_->tmp_literals) {
10921 new_ct->mutable_exactly_one()->add_literals(lit);
10922 }
10923 } else {
10924 // At most one here is enough: if all zero, we can satisfy one of the
10925 // two exactly one at postsolve.
10926 for (const int lit : context_->tmp_literals) {
10927 new_ct->mutable_at_most_one()->add_literals(lit);
10928 }
10929 }
10930
10932 context_->working_model->mutable_constraints(c1)->Clear();
10933 context_->UpdateConstraintVariableUsage(c1);
10934 context_->working_model->mutable_constraints(c2)->Clear();
10935 context_->UpdateConstraintVariableUsage(c2);
10936
10937 context_->UpdateRuleStats(
10938 "at_most_one: resolved two constraints with opposite literal");
10939 context_->MarkVariableAsRemoved(var);
10940
10941 // TODO(user): If the merged list contains duplicates or literal that are
10942 // negation of other, we need to deal with that right away. For some reason
10943 // something is not robust to that it seems. Investigate & fix!
10944 DCHECK_NE(new_ct->constraint_case(), ConstraintProto::CONSTRAINT_NOT_SET);
10945 if (PresolveAtMostOrExactlyOne(new_ct)) {
10946 context_->UpdateConstraintVariableUsage(new_ct_index);
10947 }
10948}
10949
10950// If we have a bunch of constraint of the form literal => Y \in domain and
10951// another constraint Y = f(X), we can remove Y, that constraint, and transform
10952// all linear1 from constraining Y to constraining X.
10953//
10954// We can for instance do it for Y = abs(X) or Y = X^2 easily. More complex
10955// function might be trickier.
10956//
10957// Note that we can't always do it in the reverse direction though!
10958// If we have l => X = -1, we can't transfer that to abs(X) for instance, since
10959// X=1 will also map to abs(-1). We can only do it if for all implied domain D
10960// we have f^-1(f(D)) = D, which is not easy to check.
10961void CpModelPresolver::MaybeTransferLinear1ToAnotherVariable(int var) {
10962 // Find the extra constraint and do basic CHECKs.
10963 int other_c;
10964 int num_others = 0;
10965 std::vector<int> to_rewrite;
10966 for (const int c : context_->VarToConstraints(var)) {
10967 if (c >= 0) {
10968 const ConstraintProto& ct = context_->working_model->constraints(c);
10969 if (ct.constraint_case() == ConstraintProto::kLinear &&
10970 ct.linear().vars().size() == 1) {
10971 to_rewrite.push_back(c);
10972 continue;
10973 }
10974 }
10975 ++num_others;
10976 other_c = c;
10977 }
10978 if (num_others != 1) return;
10979 if (other_c < 0) return;
10980
10981 // In general constraint with more than two variable can't be removed.
10982 // Similarly for linear2 with non-fixed rhs as we would need to check the form
10983 // of all implied domain.
10984 const auto& other_ct = context_->working_model->constraints(other_c);
10985 if (context_->ConstraintToVars(other_c).size() != 2 ||
10986 !other_ct.enforcement_literal().empty() ||
10987 other_ct.constraint_case() == ConstraintProto::kLinear) {
10988 return;
10989 }
10990
10991 // This will be the rewriting function. It takes the implied domain of var
10992 // from linear1, and return a pair {new_var, new_var_implied_domain}.
10993 std::function<std::pair<int, Domain>(const Domain& implied)> transfer_f =
10994 nullptr;
10995
10996 // We only support a few cases.
10997 //
10998 // TODO(user): implement more! Note that the linear2 case was tempting, but if
10999 // we don't have an equality, we can't transfer, and if we do, we actually
11000 // have affine equivalence already.
11001 if (other_ct.constraint_case() == ConstraintProto::kLinMax &&
11002 other_ct.lin_max().target().vars().size() == 1 &&
11003 other_ct.lin_max().target().vars(0) == var &&
11004 std::abs(other_ct.lin_max().target().coeffs(0)) == 1 &&
11005 IsAffineIntAbs(other_ct)) {
11006 context_->UpdateRuleStats("linear1: transferred from abs(X) to X");
11007 const LinearExpressionProto& target = other_ct.lin_max().target();
11008 const LinearExpressionProto& expr = other_ct.lin_max().exprs(0);
11009 transfer_f = [target, expr](const Domain& implied) {
11010 Domain target_domain =
11011 implied.ContinuousMultiplicationBy(target.coeffs(0))
11012 .AdditionWith(Domain(target.offset()));
11013 target_domain =
11014 target_domain.IntersectionWith(Domain(0, target_domain.Max()));
11015
11016 // We have target = abs(expr).
11017 const Domain expr_domain =
11018 target_domain.UnionWith(target_domain.Negation());
11019 const Domain new_domain = expr_domain.AdditionWith(Domain(-expr.offset()))
11020 .InverseMultiplicationBy(expr.coeffs(0));
11021 return std::make_pair(expr.vars(0), new_domain);
11022 };
11023 }
11024
11025 if (transfer_f == nullptr) {
11026 context_->UpdateRuleStats(
11027 "TODO linear1: appear in only one extra 2-var constraint");
11028 return;
11029 }
11030
11031 // Applies transfer_f to all linear1.
11032 std::sort(to_rewrite.begin(), to_rewrite.end());
11033 const Domain var_domain = context_->DomainOf(var);
11034 for (const int c : to_rewrite) {
11035 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
11036 if (ct->linear().vars(0) != var || ct->linear().coeffs(0) != 1) {
11037 // This shouldn't happen.
11038 LOG(INFO) << "Aborted in MaybeTransferLinear1ToAnotherVariable()";
11039 return;
11040 }
11041
11042 const Domain implied =
11043 var_domain.IntersectionWith(ReadDomainFromProto(ct->linear()));
11044 auto [new_var, new_domain] = transfer_f(implied);
11045 const Domain current = context_->DomainOf(new_var);
11046 new_domain = new_domain.IntersectionWith(current);
11047 if (new_domain.IsEmpty()) {
11048 if (!MarkConstraintAsFalse(ct)) return;
11049 } else if (new_domain == current) {
11050 ct->Clear();
11051 } else {
11052 ct->mutable_linear()->set_vars(0, new_var);
11053 FillDomainInProto(new_domain, ct->mutable_linear());
11054 }
11055 context_->UpdateConstraintVariableUsage(c);
11056 }
11057
11058 // Copy other_ct to the mapping model and delete var!
11059 context_->NewMappingConstraint(other_ct, __FILE__, __LINE__);
11060 context_->working_model->mutable_constraints(other_c)->Clear();
11061 context_->UpdateConstraintVariableUsage(other_c);
11062 context_->MarkVariableAsRemoved(var);
11063}
11064
11065// TODO(user): We can still remove the variable even if we want to keep
11066// all feasible solutions for the cases when we have a full encoding.
11067//
11068// TODO(user): In fixed search, we disable this rule because we don't update
11069// the search strategy, but for some strategy we could.
11070//
11071// TODO(user): The hint might get lost if the encoding was created during
11072// the presolve.
11073void CpModelPresolver::ProcessVariableOnlyUsedInEncoding(int var) {
11074 if (context_->ModelIsUnsat()) return;
11075 if (context_->keep_all_feasible_solutions) return;
11076 if (context_->IsFixed(var)) return;
11077 if (context_->VariableWasRemoved(var)) return;
11078 if (context_->CanBeUsedAsLiteral(var)) return;
11079 if (context_->params().search_branching() == SatParameters::FIXED_SEARCH) {
11080 return;
11081 }
11082
11085 MaybeTransferLinear1ToAnotherVariable(var);
11086 return;
11087 }
11088 return;
11089 }
11090
11091 // If a variable var only appear in enf => var \in domain and in the
11092 // objective, we can remove its costs and the variable/constraint by
11093 // transferring part of the cost to the enforcement.
11094 //
11095 // More generally, we can reduce the domain to just two values. Later this
11096 // will be replaced by a Boolean, and the equivalence to the enforcement
11097 // literal will be added if it is unique.
11098 //
11099 // TODO(user): maybe we should do more here rather than delaying some
11100 // reduction. But then it is more code.
11102 int unique_c = -1;
11103 for (const int c : context_->VarToConstraints(var)) {
11104 if (c < 0) continue;
11105 CHECK_EQ(unique_c, -1);
11106 unique_c = c;
11107 }
11108 CHECK_NE(unique_c, -1);
11109 const ConstraintProto& ct = context_->working_model->constraints(unique_c);
11110 const int64_t cost = context_->ObjectiveCoeff(var);
11111 if (ct.linear().vars(0) == var) {
11112 const Domain implied = ReadDomainFromProto(ct.linear())
11113 .InverseMultiplicationBy(ct.linear().coeffs(0))
11114 .IntersectionWith(context_->DomainOf(var));
11115 if (implied.IsEmpty()) {
11116 if (!MarkConstraintAsFalse(
11117 context_->working_model->mutable_constraints(unique_c))) {
11118 return;
11119 }
11120 context_->UpdateConstraintVariableUsage(unique_c);
11121 return;
11122 }
11123
11124 int64_t value1, value2;
11125 if (cost == 0) {
11126 context_->UpdateRuleStats("variables: fix singleton var in linear1");
11127 return (void)context_->IntersectDomainWith(var, Domain(implied.Min()));
11128 } else if (cost > 0) {
11129 value1 = context_->MinOf(var);
11130 value2 = implied.Min();
11131 } else {
11132 value1 = context_->MaxOf(var);
11133 value2 = implied.Max();
11134 }
11135
11136 // Nothing else to do in this case, the constraint will be reduced to
11137 // a pure Boolean constraint later.
11138 context_->UpdateRuleStats("variables: reduced domain to two values");
11139 return (void)context_->IntersectDomainWith(
11140 var, Domain::FromValues({value1, value2}));
11141 }
11142 }
11143
11144 // We can currently only deal with the case where all encoding constraint
11145 // are of the form literal => var ==/!= value.
11146 // If they are more complex linear1 involved, we just abort.
11147 //
11148 // TODO(user): Also deal with the case all >= or <= where we can add a
11149 // serie of implication between all involved literals.
11150 absl::flat_hash_set<int64_t> values_set;
11151 absl::flat_hash_map<int64_t, std::vector<int>> value_to_equal_literals;
11152 absl::flat_hash_map<int64_t, std::vector<int>> value_to_not_equal_literals;
11153 bool abort = false;
11154 for (const int c : context_->VarToConstraints(var)) {
11155 if (c < 0) continue;
11156 const ConstraintProto& ct = context_->working_model->constraints(c);
11157 CHECK_EQ(ct.constraint_case(), ConstraintProto::kLinear);
11158 CHECK_EQ(ct.linear().vars().size(), 1);
11159 int64_t coeff = ct.linear().coeffs(0);
11160 if (std::abs(coeff) != 1 || ct.enforcement_literal().size() != 1) {
11161 abort = true;
11162 break;
11163 }
11164 if (!RefIsPositive(ct.linear().vars(0))) coeff *= 1;
11165 const int var = PositiveRef(ct.linear().vars(0));
11166 const Domain var_domain = context_->DomainOf(var);
11167 const Domain rhs = ReadDomainFromProto(ct.linear())
11169 .IntersectionWith(var_domain);
11170 if (rhs.IsEmpty()) {
11171 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
11172 return;
11173 }
11174 return;
11175 } else if (rhs.IsFixed()) {
11176 if (!var_domain.Contains(rhs.FixedValue())) {
11177 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
11178 return;
11179 }
11180 } else {
11181 values_set.insert(rhs.FixedValue());
11182 value_to_equal_literals[rhs.FixedValue()].push_back(
11183 ct.enforcement_literal(0));
11184 }
11185 } else {
11186 const Domain complement = var_domain.IntersectionWith(rhs.Complement());
11187 if (complement.IsEmpty()) {
11188 // TODO(user): This should be dealt with elsewhere.
11189 abort = true;
11190 break;
11191 }
11192 if (complement.IsFixed()) {
11193 if (var_domain.Contains(complement.FixedValue())) {
11194 values_set.insert(complement.FixedValue());
11195 value_to_not_equal_literals[complement.FixedValue()].push_back(
11196 ct.enforcement_literal(0));
11197 }
11198 } else {
11199 abort = true;
11200 break;
11201 }
11202 }
11203 }
11204 if (abort) {
11205 context_->UpdateRuleStats("TODO variables: only used in linear1.");
11206 return;
11207 } else if (value_to_not_equal_literals.empty() &&
11208 value_to_equal_literals.empty()) {
11209 // This is just a variable not used anywhere, it should be removed by
11210 // another part of the presolve.
11211 return;
11212 }
11213
11214 // For determinism, sort all the encoded values first.
11215 std::vector<int64_t> encoded_values(values_set.begin(), values_set.end());
11216 std::sort(encoded_values.begin(), encoded_values.end());
11217 CHECK(!encoded_values.empty());
11218 const bool is_fully_encoded =
11219 encoded_values.size() == context_->DomainOf(var).Size();
11220
11221 // Link all Boolean in our linear1 to the encoding literals. Note that we
11222 // should hopefully already have detected such literal before and this
11223 // should add trivial implications.
11224 for (const int64_t v : encoded_values) {
11225 const int encoding_lit = context_->GetOrCreateVarValueEncoding(var, v);
11226 const auto eq_it = value_to_equal_literals.find(v);
11227 if (eq_it != value_to_equal_literals.end()) {
11228 for (const int lit : eq_it->second) {
11229 context_->AddImplication(lit, encoding_lit);
11230 }
11231 }
11232 const auto neq_it = value_to_not_equal_literals.find(v);
11233 if (neq_it != value_to_not_equal_literals.end()) {
11234 for (const int lit : neq_it->second) {
11235 context_->AddImplication(lit, NegatedRef(encoding_lit));
11236 }
11237 }
11238 }
11240
11241 // This is the set of other values.
11242 Domain other_values;
11243 if (!is_fully_encoded) {
11244 other_values = context_->DomainOf(var).IntersectionWith(
11245 Domain::FromValues(encoded_values).Complement());
11246 }
11247
11248 // Update the objective if needed. Note that this operation can fail if
11249 // the new expression result in potential overflow.
11250 if (context_->VarToConstraints(var).contains(kObjectiveConstraint)) {
11251 int64_t min_value;
11252 const int64_t obj_coeff = context_->ObjectiveMap().at(var);
11253 if (is_fully_encoded) {
11254 // We substract the min_value from all coefficients.
11255 // This should reduce the objective size and helps with the bounds.
11256 min_value =
11257 obj_coeff > 0 ? encoded_values.front() : encoded_values.back();
11258 } else {
11259 // Tricky: We cannot just choose an arbitrary value if the objective has
11260 // a restrictive domain!
11261 if (context_->ObjectiveDomainIsConstraining() &&
11262 !other_values.IsFixed()) {
11263 context_->UpdateRuleStats(
11264 "TODO variables: only used in objective and in encoding");
11265 return;
11266 }
11267
11268 // Tricky: If the variable is not fully encoded, then when all
11269 // partial encoding literal are false, it must take the "best" value
11270 // in other_values. That depend on the sign of the objective coeff.
11271 //
11272 // We also restrict other value so that the postsolve code below
11273 // will fix the variable to the correct value when this happen.
11274 other_values =
11275 Domain(obj_coeff > 0 ? other_values.Min() : other_values.Max());
11276 min_value = other_values.FixedValue();
11277 }
11278
11279 // Checks for overflow before trying to substitute the variable in the
11280 // objective.
11281 int64_t accumulated = std::abs(min_value);
11282 for (const int64_t value : encoded_values) {
11283 accumulated = CapAdd(accumulated, std::abs(CapSub(value, min_value)));
11284 if (accumulated == std::numeric_limits<int64_t>::max()) {
11285 context_->UpdateRuleStats(
11286 "TODO variables: only used in objective and in encoding");
11287 return;
11288 }
11289 }
11290
11291 ConstraintProto encoding_ct;
11292 LinearConstraintProto* linear = encoding_ct.mutable_linear();
11293 const int64_t coeff_in_equality = -1;
11294 linear->add_vars(var);
11295 linear->add_coeffs(coeff_in_equality);
11296
11297 linear->add_domain(-min_value);
11298 linear->add_domain(-min_value);
11299 for (const int64_t value : encoded_values) {
11300 if (value == min_value) continue;
11301 const int enf = context_->GetOrCreateVarValueEncoding(var, value);
11302 const int64_t coeff = value - min_value;
11303 if (RefIsPositive(enf)) {
11304 linear->add_vars(enf);
11305 linear->add_coeffs(coeff);
11306 } else {
11307 // (1 - var) * coeff;
11308 linear->set_domain(0, encoding_ct.linear().domain(0) - coeff);
11309 linear->set_domain(1, encoding_ct.linear().domain(1) - coeff);
11310 linear->add_vars(PositiveRef(enf));
11311 linear->add_coeffs(-coeff);
11312 }
11313 }
11314 if (!context_->SubstituteVariableInObjective(var, coeff_in_equality,
11315 encoding_ct)) {
11316 context_->UpdateRuleStats(
11317 "TODO variables: only used in objective and in encoding");
11318 return;
11319 }
11320 context_->UpdateRuleStats(
11321 "variables: only used in objective and in encoding");
11322 } else {
11323 context_->UpdateRuleStats("variables: only used in encoding");
11324 }
11325
11326 // Clear all involved constraint.
11327 {
11328 std::vector<int> to_clear;
11329 for (const int c : context_->VarToConstraints(var)) {
11330 if (c >= 0) to_clear.push_back(c);
11331 }
11332 absl::c_sort(to_clear);
11333 for (const int c : to_clear) {
11334 if (c < 0) continue;
11335 context_->working_model->mutable_constraints(c)->Clear();
11336 context_->UpdateConstraintVariableUsage(c);
11337 }
11338 }
11339
11340 // This must be done after we removed all the constraint containing var.
11341 ConstraintProto* new_ct = context_->working_model->add_constraints();
11342 if (is_fully_encoded) {
11343 // The encoding is full: add an exactly one.
11344 for (const int64_t value : encoded_values) {
11345 new_ct->mutable_exactly_one()->add_literals(
11347 }
11348 PresolveExactlyOne(new_ct);
11349 } else {
11350 // If all literal are false, then var must take one of the other values.
11351 // Note that this one must be first in the mapping model, so that if any
11352 // of the literal was true, var was assigned to the correct value.
11353 ConstraintProto* mapping_ct =
11354 context_->NewMappingConstraint(__FILE__, __LINE__);
11355 mapping_ct->mutable_linear()->add_vars(var);
11356 mapping_ct->mutable_linear()->add_coeffs(1);
11357 FillDomainInProto(other_values, mapping_ct->mutable_linear());
11358
11359 for (const int64_t value : encoded_values) {
11360 const int literal = context_->GetOrCreateVarValueEncoding(var, value);
11361 mapping_ct->add_enforcement_literal(NegatedRef(literal));
11362 new_ct->mutable_at_most_one()->add_literals(literal);
11363 }
11364 PresolveAtMostOne(new_ct);
11365 }
11366
11367 // Add enough constraints to the mapping model to recover a valid value
11368 // for var when all the booleans are fixed.
11369 for (const int64_t value : encoded_values) {
11370 const int enf = context_->GetOrCreateVarValueEncoding(var, value);
11371 ConstraintProto* ct = context_->NewMappingConstraint(__FILE__, __LINE__);
11372 ct->add_enforcement_literal(enf);
11373 ct->mutable_linear()->add_vars(var);
11374 ct->mutable_linear()->add_coeffs(1);
11375 ct->mutable_linear()->add_domain(value);
11376 ct->mutable_linear()->add_domain(value);
11377 }
11378
11380 context_->MarkVariableAsRemoved(var);
11381}
11382
11383void CpModelPresolver::TryToSimplifyDomain(int var) {
11384 DCHECK(RefIsPositive(var));
11385 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
11386 if (context_->ModelIsUnsat()) return;
11387 if (context_->IsFixed(var)) return;
11388 if (context_->VariableWasRemoved(var)) return;
11389 if (context_->VariableIsNotUsedAnymore(var)) return;
11390
11391 const AffineRelation::Relation r = context_->GetAffineRelation(var);
11392 if (r.representative != var) return;
11393
11394 // Only process discrete domain.
11395 const Domain& domain = context_->DomainOf(var);
11396
11397 // Special case for non-Boolean domain of size 2.
11398 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
11400 return;
11401 }
11402
11403 if (domain.NumIntervals() != domain.Size()) return;
11404
11405 const int64_t var_min = domain.Min();
11406 int64_t gcd = domain[1].start - var_min;
11407 for (int index = 2; index < domain.NumIntervals(); ++index) {
11408 const ClosedInterval& i = domain[index];
11409 DCHECK_EQ(i.start, i.end);
11410 const int64_t shifted_value = i.start - var_min;
11411 DCHECK_GT(shifted_value, 0);
11412
11413 gcd = MathUtil::GCD64(gcd, shifted_value);
11414 if (gcd == 1) break;
11415 }
11416 if (gcd == 1) return;
11417
11418 // This does all the work since var * 1 % gcd = var_min % gcd.
11419 context_->CanonicalizeAffineVariable(var, 1, gcd, var_min);
11420}
11421
11422// Adds all affine relations to our model for the variables that are still used.
11423void CpModelPresolver::EncodeAllAffineRelations() {
11424 int64_t num_added = 0;
11425 for (int var = 0; var < context_->working_model->variables_size(); ++var) {
11426 if (context_->IsFixed(var)) continue;
11427
11428 const AffineRelation::Relation r = context_->GetAffineRelation(var);
11429 if (r.representative == var) continue;
11430
11431 // TODO(user): It seems some affine relation are still removable at this
11432 // stage even though they should be removed inside PresolveToFixPoint().
11433 // Investigate. For now, we just remove such relations.
11434 if (context_->VariableIsNotUsedAnymore(var)) continue;
11435 if (!PresolveAffineRelationIfAny(var)) break;
11436 if (context_->VariableIsNotUsedAnymore(var)) continue;
11437 if (context_->IsFixed(var)) continue;
11438
11439 ++num_added;
11440 ConstraintProto* ct = context_->working_model->add_constraints();
11441 auto* arg = ct->mutable_linear();
11442 arg->add_vars(var);
11443 arg->add_coeffs(1);
11444 arg->add_vars(r.representative);
11445 arg->add_coeffs(-r.coeff);
11446 arg->add_domain(r.offset);
11447 arg->add_domain(r.offset);
11449 }
11450
11451 // Now that we encoded all remaining affine relation with constraints, we
11452 // remove the special marker to have a proper constraint variable graph.
11454
11455 if (num_added > 0) {
11456 SOLVER_LOG(logger_, num_added, " affine relations still in the model.");
11457 }
11458}
11459
11460// Presolve a variable in relation with its representative.
11461bool CpModelPresolver::PresolveAffineRelationIfAny(int var) {
11462 const AffineRelation::Relation r = context_->GetAffineRelation(var);
11463 if (r.representative == var) return true;
11464
11465 // Propagate domains.
11466 if (!context_->PropagateAffineRelation(var)) return false;
11467
11468 // Once an affine relation is detected, the variables should be added to
11469 // the kAffineRelationConstraint. The only way to be unmarked is if the
11470 // variable do not appear in any other constraint and is not a representative,
11471 // in which case it should never be added back.
11472 if (context_->IsFixed(var)) return true;
11473 DCHECK(context_->VarToConstraints(var).contains(kAffineRelationConstraint));
11474 DCHECK(!context_->VariableIsNotUsedAnymore(r.representative));
11475
11476 // If var is no longer used, remove. Note that we can always do that since we
11477 // propagated the domain above and so we can find a feasible value for a for
11478 // any value of the representative.
11479 if (context_->VariableIsUnique(var)) {
11480 // Add relation with current representative to the mapping model.
11481 ConstraintProto* ct = context_->NewMappingConstraint(__FILE__, __LINE__);
11482 auto* arg = ct->mutable_linear();
11483 arg->add_vars(var);
11484 arg->add_coeffs(1);
11485 arg->add_vars(r.representative);
11486 arg->add_coeffs(-r.coeff);
11487 arg->add_domain(r.offset);
11488 arg->add_domain(r.offset);
11490 }
11491 return true;
11492}
11493
11494// Re-add to the queue the constraints that touch a variable that changed.
11495bool CpModelPresolver::ProcessChangedVariables(std::vector<bool>* in_queue,
11496 std::deque<int>* queue) {
11497 // TODO(user): Avoid reprocessing the constraints that changed the domain?
11498 if (context_->ModelIsUnsat()) return false;
11499 if (time_limit_->LimitReached()) return false;
11500 in_queue->resize(context_->working_model->constraints_size(), false);
11501 const auto& vector_that_can_grow_during_iter =
11503 for (int i = 0; i < vector_that_can_grow_during_iter.size(); ++i) {
11504 const int v = vector_that_can_grow_during_iter[i];
11505 if (context_->VariableIsNotUsedAnymore(v)) continue;
11506 if (context_->ModelIsUnsat()) return false;
11507 if (!PresolveAffineRelationIfAny(v)) return false;
11508 if (context_->VariableIsNotUsedAnymore(v)) continue;
11509
11510 TryToSimplifyDomain(v);
11511
11512 // TODO(user): Integrate these with TryToSimplifyDomain().
11513 if (context_->ModelIsUnsat()) return false;
11515
11516 if (!context_->CanonicalizeOneObjectiveVariable(v)) return false;
11517
11518 in_queue->resize(context_->working_model->constraints_size(), false);
11519 for (const int c : context_->VarToConstraints(v)) {
11520 if (c >= 0 && !(*in_queue)[c]) {
11521 (*in_queue)[c] = true;
11522 queue->push_back(c);
11523 }
11524 }
11525 }
11526 context_->modified_domains.SparseClearAll();
11527
11528 // Make sure the order is deterministic! because var_to_constraints[]
11529 // order changes from one run to the next.
11530 std::sort(queue->begin(), queue->end());
11531 return !queue->empty();
11532}
11533
11534void CpModelPresolver::PresolveToFixPoint() {
11535 if (time_limit_->LimitReached()) return;
11536 if (context_->ModelIsUnsat()) return;
11537 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11538
11539 // We do at most 2 tests per PresolveToFixPoint() call since this can be slow.
11540 int num_dominance_tests = 0;
11541 int num_dual_strengthening = 0;
11542
11543 // Limit on number of operations.
11544 const int64_t max_num_operations =
11545 context_->params().debug_max_num_presolve_operations() > 0
11546 ? context_->params().debug_max_num_presolve_operations()
11547 : std::numeric_limits<int64_t>::max();
11548
11549 // This is used for constraint having unique variables in them (i.e. not
11550 // appearing anywhere else) to not call the presolve more than once for this
11551 // reason.
11552 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
11553
11554 // The queue of "active" constraints, initialized to the non-empty ones.
11555 std::vector<bool> in_queue(context_->working_model->constraints_size(),
11556 false);
11557 std::deque<int> queue;
11558 for (int c = 0; c < in_queue.size(); ++c) {
11559 if (context_->working_model->constraints(c).constraint_case() !=
11560 ConstraintProto::CONSTRAINT_NOT_SET) {
11561 in_queue[c] = true;
11562 queue.push_back(c);
11563 }
11564 }
11565
11566 // When thinking about how the presolve works, it seems like a good idea to
11567 // process the "simple" constraints first in order to be more efficient.
11568 // In September 2019, experiment on the flatzinc problems shows no changes in
11569 // the results. We should actually count the number of rules triggered.
11570 if (context_->params().permute_presolve_constraint_order()) {
11571 std::shuffle(queue.begin(), queue.end(), *context_->random());
11572 } else {
11573 std::sort(queue.begin(), queue.end(), [this](int a, int b) {
11574 const int score_a = context_->ConstraintToVars(a).size();
11575 const int score_b = context_->ConstraintToVars(b).size();
11576 return score_a < score_b || (score_a == score_b && a < b);
11577 });
11578 }
11579
11580 // We put a hard limit on the number of loop to prevent some corner case with
11581 // propagation loops. Note that the limit is quite high so it shouldn't really
11582 // be reached in most situation.
11583 int num_loops = 0;
11584 constexpr int kMaxNumLoops = 1000;
11585 for (; num_loops < kMaxNumLoops && !queue.empty(); ++num_loops) {
11586 if (time_limit_->LimitReached()) break;
11587 if (context_->ModelIsUnsat()) break;
11588 if (context_->num_presolve_operations > max_num_operations) break;
11589
11590 // Empty the queue of single constraint presolve.
11591 while (!queue.empty() && !context_->ModelIsUnsat()) {
11592 if (time_limit_->LimitReached()) break;
11593 if (context_->num_presolve_operations > max_num_operations) break;
11594 const int c = queue.front();
11595 in_queue[c] = false;
11596 queue.pop_front();
11597
11598 const int old_num_constraint =
11599 context_->working_model->constraints_size();
11600 const bool changed = PresolveOneConstraint(c);
11601 if (context_->ModelIsUnsat()) {
11602 SOLVER_LOG(
11603 logger_, "Unsat after presolving constraint #", c,
11604 " (warning, dump might be inconsistent): ",
11605 ProtobufShortDebugString(context_->working_model->constraints(c)));
11606 }
11607
11608 // Add to the queue any newly created constraints.
11609 const int new_num_constraints =
11610 context_->working_model->constraints_size();
11611 if (new_num_constraints > old_num_constraint) {
11613 in_queue.resize(new_num_constraints, true);
11614 for (int c = old_num_constraint; c < new_num_constraints; ++c) {
11615 queue.push_back(c);
11616 }
11617 }
11618
11619 // TODO(user): Is seems safer to remove the changed Boolean and maybe
11620 // just compare the number of applied "rules" before/after.
11621 if (changed) {
11622 context_->UpdateConstraintVariableUsage(c);
11623 }
11624 }
11625
11626 if (context_->ModelIsUnsat()) return;
11627
11628 in_queue.resize(context_->working_model->constraints_size(), false);
11629 const auto& vector_that_can_grow_during_iter =
11631 for (int i = 0; i < vector_that_can_grow_during_iter.size(); ++i) {
11632 const int v = vector_that_can_grow_during_iter[i];
11633 if (context_->VariableIsNotUsedAnymore(v)) continue;
11634
11635 // Make sure all affine relations are propagated.
11636 // This also remove the relation if the degree is now one.
11637 if (context_->ModelIsUnsat()) return;
11638 if (!PresolveAffineRelationIfAny(v)) return;
11639
11640 const int degree = context_->VarToConstraints(v).size();
11641 if (degree == 0) continue;
11642 if (degree == 2) LookAtVariableWithDegreeTwo(v);
11643 if (degree == 2 || degree == 3) {
11644 // Tricky: this function can add new constraint.
11645 ProcessVariableInTwoAtMostOrExactlyOne(v);
11646 in_queue.resize(context_->working_model->constraints_size(), false);
11647 continue;
11648 }
11649
11650 // Re-add to the queue constraints that have unique variables. Note that
11651 // to not enter an infinite loop, we call each (var, constraint) pair at
11652 // most once.
11653 if (degree != 1) continue;
11654 const int c = *context_->VarToConstraints(v).begin();
11655 if (c < 0) continue;
11656
11657 // Note that to avoid bad complexity in problem like a TSP with just one
11658 // big constraint. we mark all the singleton variables of a constraint
11659 // even if this constraint is already in the queue.
11660 if (var_constraint_pair_already_called.contains(
11661 std::pair<int, int>(v, c))) {
11662 continue;
11663 }
11664 var_constraint_pair_already_called.insert({v, c});
11665
11666 if (!in_queue[c]) {
11667 in_queue[c] = true;
11668 queue.push_back(c);
11669 }
11670 }
11672
11673 if (ProcessChangedVariables(&in_queue, &queue)) continue;
11674
11675 // Deal with integer variable only appearing in an encoding.
11676 for (int v = 0; v < context_->working_model->variables().size(); ++v) {
11677 ProcessVariableOnlyUsedInEncoding(v);
11678 }
11679 if (ProcessChangedVariables(&in_queue, &queue)) continue;
11680
11681 // Perform dual reasoning.
11682 //
11683 // TODO(user): We can support assumptions but we need to not cut them out
11684 // of the feasible region.
11685 if (context_->keep_all_feasible_solutions) break;
11686 if (!context_->working_model->assumptions().empty()) break;
11687
11688 // Starts by the "faster" algo that exploit variables that can move freely
11689 // in one direction. Or variables that are just blocked by one constraint in
11690 // one direction.
11691 for (int i = 0; i < 10; ++i) {
11692 if (context_->ModelIsUnsat()) return;
11693 ++num_dual_strengthening;
11694 DualBoundStrengthening dual_bound_strengthening;
11695 ScanModelForDualBoundStrengthening(*context_, &dual_bound_strengthening);
11696 if (!dual_bound_strengthening.Strengthen(context_)) return;
11697 if (ProcessChangedVariables(&in_queue, &queue)) break;
11698
11699 // It is possible we deleted some constraint, but the queue is empty.
11700 // In this case we redo a pass of dual bound strenghtening as we might
11701 // perform more reduction.
11702 //
11703 // TODO(user): maybe we could reach fix point directly?
11704 if (dual_bound_strengthening.NumDeletedConstraints() == 0) break;
11705 }
11706 if (!queue.empty()) continue;
11707
11708 // Detect & exploit dominance between variables.
11709 // TODO(user): This can be slow, remove from fix-pint loop?
11710 if (num_dominance_tests++ < 2) {
11711 if (context_->ModelIsUnsat()) return;
11712 PresolveTimer timer("DetectDominanceRelations", logger_, time_limit_);
11713 VarDomination var_dom;
11714 ScanModelForDominanceDetection(*context_, &var_dom);
11715 if (!ExploitDominanceRelations(var_dom, context_)) return;
11716 if (ProcessChangedVariables(&in_queue, &queue)) continue;
11717 }
11718 }
11719
11720 if (context_->ModelIsUnsat()) return;
11721
11722 // Second "pass" for transformation better done after all of the above and
11723 // that do not need a fix-point loop.
11724 //
11725 // TODO(user): Also add deductions achieved during probing!
11726 //
11727 // TODO(user): ideally we should "wake-up" any constraint that contains an
11728 // absent interval in the main propagation loop above. But we currently don't
11729 // maintain such list.
11730 const int num_constraints = context_->working_model->constraints_size();
11731 for (int c = 0; c < num_constraints; ++c) {
11732 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
11733 switch (ct->constraint_case()) {
11734 case ConstraintProto::kNoOverlap:
11735 // Filter out absent intervals.
11736 if (PresolveNoOverlap(ct)) {
11737 context_->UpdateConstraintVariableUsage(c);
11738 }
11739 break;
11740 case ConstraintProto::kNoOverlap2D:
11741 // Filter out absent intervals.
11742 if (PresolveNoOverlap2D(c, ct)) {
11743 context_->UpdateConstraintVariableUsage(c);
11744 }
11745 break;
11746 case ConstraintProto::kCumulative:
11747 // Filter out absent intervals.
11748 if (PresolveCumulative(ct)) {
11749 context_->UpdateConstraintVariableUsage(c);
11750 }
11751 break;
11752 case ConstraintProto::kBoolOr: {
11753 // Try to infer domain reductions from clauses and the saved "implies in
11754 // domain" relations.
11755 for (const auto& pair :
11756 context_->deductions.ProcessClause(ct->bool_or().literals())) {
11757 bool modified = false;
11758 if (!context_->IntersectDomainWith(pair.first, pair.second,
11759 &modified)) {
11760 return;
11761 }
11762 if (modified) {
11763 context_->UpdateRuleStats("deductions: reduced variable domain");
11764 }
11765 }
11766 break;
11767 }
11768 default:
11769 break;
11770 }
11771 }
11772
11773 timer.AddCounter("num_loops", num_loops);
11774 timer.AddCounter("num_dual_strengthening", num_dual_strengthening);
11776}
11777
11779
11781 const CpModelProto& in_model) {
11782 if (context_->params().ignore_names()) {
11783 context_->working_model->clear_variables();
11784 context_->working_model->mutable_variables()->Reserve(
11785 in_model.variables_size());
11786 for (const IntegerVariableProto& var_proto : in_model.variables()) {
11787 *context_->working_model->add_variables()->mutable_domain() =
11788 var_proto.domain();
11789 }
11790 } else {
11791 *context_->working_model->mutable_variables() = in_model.variables();
11792 }
11793}
11794
11795void ModelCopy::CreateVariablesFromDomains(const std::vector<Domain>& domains) {
11796 for (const Domain& domain : domains) {
11797 FillDomainInProto(domain, context_->working_model->add_variables());
11798 }
11799}
11800
11801// TODO(user): Merge with the phase 1 of the presolve code.
11802//
11803// TODO(user): It seems easy to forget to update this if any new constraint
11804// contains an interval or if we add a field to an existing constraint. Find a
11805// way to remind contributor to not forget this.
11807 const CpModelProto& in_model, bool first_copy,
11808 std::function<bool(int)> active_constraints) {
11809 context_->InitializeNewDomains();
11810 const bool ignore_names = context_->params().ignore_names();
11811
11812 // If first_copy is true, we reorder the scheduling constraint to be sure they
11813 // refer to interval before them.
11814 std::vector<int> constraints_using_intervals;
11815
11816 starting_constraint_index_ = context_->working_model->constraints_size();
11817 for (int c = 0; c < in_model.constraints_size(); ++c) {
11818 if (active_constraints != nullptr && !active_constraints(c)) continue;
11819 const ConstraintProto& ct = in_model.constraints(c);
11820 if (first_copy) {
11821 if (!PrepareEnforcementCopyWithDup(ct)) continue;
11822 } else {
11823 if (!PrepareEnforcementCopy(ct)) continue;
11824 }
11825
11826 // TODO(user): if ignore_names is false, we should make sure the
11827 // name are properly copied by all these functions. Or we should never copy
11828 // name and have a separate if (!ignore_name) copy the name...
11829 switch (ct.constraint_case()) {
11830 case ConstraintProto::CONSTRAINT_NOT_SET:
11831 break;
11832 case ConstraintProto::kBoolOr:
11833 if (first_copy) {
11834 if (!CopyBoolOrWithDupSupport(ct)) return CreateUnsatModel(c, ct);
11835 } else {
11836 if (!CopyBoolOr(ct)) return CreateUnsatModel(c, ct);
11837 }
11838 break;
11839 case ConstraintProto::kBoolAnd:
11840 if (temp_enforcement_literals_.empty()) {
11841 for (const int lit : ct.bool_and().literals()) {
11842 context_->UpdateRuleStats("bool_and: non-reified.");
11843 if (!context_->SetLiteralToTrue(lit)) {
11844 return CreateUnsatModel(c, ct);
11845 }
11846 }
11847 } else if (first_copy) {
11848 if (!CopyBoolAndWithDupSupport(ct)) return CreateUnsatModel(c, ct);
11849 } else {
11850 if (!CopyBoolAnd(ct)) return CreateUnsatModel(c, ct);
11851 }
11852 break;
11853 case ConstraintProto::kLinear:
11854 if (!CopyLinear(ct)) return CreateUnsatModel(c, ct);
11855 break;
11856 case ConstraintProto::kAtMostOne:
11857 if (!CopyAtMostOne(ct)) return CreateUnsatModel(c, ct);
11858 break;
11859 case ConstraintProto::kExactlyOne:
11860 if (!CopyExactlyOne(ct)) return CreateUnsatModel(c, ct);
11861 break;
11862 case ConstraintProto::kInterval:
11863 if (!CopyInterval(ct, c, ignore_names)) return CreateUnsatModel(c, ct);
11864 if (first_copy) {
11865 AddLinearConstraintForInterval(ct);
11866 }
11867 break;
11868 case ConstraintProto::kNoOverlap:
11869 if (first_copy) {
11870 constraints_using_intervals.push_back(c);
11871 } else {
11872 CopyAndMapNoOverlap(ct);
11873 }
11874 break;
11875 case ConstraintProto::kNoOverlap2D:
11876 if (first_copy) {
11877 constraints_using_intervals.push_back(c);
11878 } else {
11879 CopyAndMapNoOverlap2D(ct);
11880 }
11881 break;
11882 case ConstraintProto::kCumulative:
11883 if (first_copy) {
11884 constraints_using_intervals.push_back(c);
11885 } else {
11886 CopyAndMapCumulative(ct);
11887 }
11888 break;
11889 default: {
11890 ConstraintProto* new_ct = context_->working_model->add_constraints();
11891 *new_ct = ct;
11892 if (ignore_names) {
11893 // TODO(user): find a better way than copy then clear_name()?
11894 new_ct->clear_name();
11895 }
11896 }
11897 }
11898 }
11899
11900 // This should be empty if first_copy is false.
11901 DCHECK(first_copy || constraints_using_intervals.empty());
11902 for (const int c : constraints_using_intervals) {
11903 const ConstraintProto& ct = in_model.constraints(c);
11904 switch (ct.constraint_case()) {
11905 case ConstraintProto::kNoOverlap:
11906 CopyAndMapNoOverlap(ct);
11907 break;
11908 case ConstraintProto::kNoOverlap2D:
11909 CopyAndMapNoOverlap2D(ct);
11910 break;
11911 case ConstraintProto::kCumulative:
11912 CopyAndMapCumulative(ct);
11913 break;
11914 default:
11915 LOG(DFATAL) << "Shouldn't be here.";
11916 }
11917 }
11918
11919 return true;
11920}
11921
11922bool ModelCopy::PrepareEnforcementCopy(const ConstraintProto& ct) {
11923 temp_enforcement_literals_.clear();
11924 for (const int lit : ct.enforcement_literal()) {
11925 if (context_->LiteralIsTrue(lit)) continue;
11926 if (context_->LiteralIsFalse(lit)) {
11927 context_->UpdateRuleStats("enforcement: always false");
11928 return false;
11929 }
11930 temp_enforcement_literals_.push_back(lit);
11931 }
11932 return true; // Continue processing.
11933}
11934
11935bool ModelCopy::PrepareEnforcementCopyWithDup(const ConstraintProto& ct) {
11936 temp_enforcement_literals_.clear();
11937 temp_enforcement_literals_set_.clear();
11938 for (const int lit : ct.enforcement_literal()) {
11939 if (context_->LiteralIsTrue(lit)) continue;
11940 if (temp_enforcement_literals_set_.contains(lit)) {
11941 context_->UpdateRuleStats("enforcement: removed duplicate literal");
11942 continue;
11943 }
11944
11945 // Cannot be satisfied.
11946 if (context_->LiteralIsFalse(lit)) {
11947 context_->UpdateRuleStats("enforcement: always false");
11948 return false;
11949 }
11950 if (temp_enforcement_literals_set_.contains(NegatedRef(lit))) {
11951 context_->UpdateRuleStats("enforcement: contains x and not(x)");
11952 return false;
11953 }
11954
11955 temp_enforcement_literals_.push_back(lit);
11956 temp_enforcement_literals_set_.insert(lit);
11957 }
11958 return true; // Continue processing.
11959}
11960
11961void ModelCopy::FinishEnforcementCopy(ConstraintProto* ct) {
11962 ct->mutable_enforcement_literal()->Add(temp_enforcement_literals_.begin(),
11963 temp_enforcement_literals_.end());
11964}
11965
11966bool ModelCopy::FinishBoolOrCopy() {
11967 if (temp_literals_.empty()) return false;
11968
11969 if (temp_literals_.size() == 1) {
11970 context_->UpdateRuleStats("bool_or: only one literal");
11971 return context_->SetLiteralToTrue(temp_literals_[0]);
11972 }
11973
11974 context_->working_model->add_constraints()
11975 ->mutable_bool_or()
11976 ->mutable_literals()
11977 ->Add(temp_literals_.begin(), temp_literals_.end());
11978 return true;
11979}
11980
11981bool ModelCopy::CopyBoolOr(const ConstraintProto& ct) {
11982 temp_literals_.clear();
11983 for (const int lit : temp_enforcement_literals_) {
11984 temp_literals_.push_back(NegatedRef(lit));
11985 }
11986 for (const int lit : ct.bool_or().literals()) {
11987 if (context_->LiteralIsTrue(lit)) {
11988 return true;
11989 }
11990 if (!context_->LiteralIsFalse(lit)) {
11991 temp_literals_.push_back(lit);
11992 }
11993 }
11994 return FinishBoolOrCopy();
11995}
11996
11997bool ModelCopy::CopyBoolOrWithDupSupport(const ConstraintProto& ct) {
11998 temp_literals_.clear();
11999 temp_literals_set_.clear();
12000 for (const int enforcement_lit : temp_enforcement_literals_) {
12001 // Having an enforcement literal is the same as having its negation on
12002 // the clause.
12003 const int lit = NegatedRef(enforcement_lit);
12004
12005 // Note that we already dealt with duplicate since we should have called
12006 // PrepareEnforcementCopyWithDup() in this case.
12007 temp_literals_set_.insert(lit);
12008 temp_literals_.push_back(lit);
12009 }
12010 for (const int lit : ct.bool_or().literals()) {
12011 if (context_->LiteralIsTrue(lit)) {
12012 context_->UpdateRuleStats("bool_or: always true");
12013 return true;
12014 }
12015 if (context_->LiteralIsFalse(lit)) continue;
12016 if (temp_literals_set_.contains(NegatedRef(lit))) {
12017 context_->UpdateRuleStats("bool_or: always true");
12018 return true;
12019 }
12020 const auto [it, inserted] = temp_literals_set_.insert(lit);
12021 if (inserted) temp_literals_.push_back(lit);
12022 }
12023 return FinishBoolOrCopy();
12024}
12025
12026bool ModelCopy::CopyBoolAnd(const ConstraintProto& ct) {
12027 bool at_least_one_false = false;
12028 int num_non_fixed_literals = 0;
12029 for (const int lit : ct.bool_and().literals()) {
12030 if (context_->LiteralIsFalse(lit)) {
12031 at_least_one_false = true;
12032 break;
12033 }
12034 if (!context_->LiteralIsTrue(lit)) {
12035 num_non_fixed_literals++;
12036 }
12037 }
12038
12039 if (at_least_one_false) {
12040 // One enforcement literal must be false.
12041 BoolArgumentProto* bool_or =
12042 context_->working_model->add_constraints()->mutable_bool_or();
12043 for (const int lit : temp_enforcement_literals_) {
12044 bool_or->add_literals(NegatedRef(lit));
12045 }
12046 return !bool_or->literals().empty();
12047 } else if (num_non_fixed_literals > 0) {
12048 ConstraintProto* new_ct = context_->working_model->add_constraints();
12049 FinishEnforcementCopy(new_ct);
12050 BoolArgumentProto* bool_and = new_ct->mutable_bool_and();
12051 bool_and->mutable_literals()->Reserve(num_non_fixed_literals);
12052 for (const int lit : ct.bool_and().literals()) {
12053 if (context_->LiteralIsTrue(lit)) continue;
12054 bool_and->add_literals(lit);
12055 }
12056 }
12057 return true;
12058}
12059
12060bool ModelCopy::CopyBoolAndWithDupSupport(const ConstraintProto& ct) {
12061 DCHECK(!temp_enforcement_literals_.empty());
12062
12063 bool at_least_one_false = false;
12064 temp_literals_.clear();
12065 temp_literals_set_.clear();
12066 for (const int lit : ct.bool_and().literals()) {
12067 if (context_->LiteralIsFalse(lit)) {
12068 context_->UpdateRuleStats("bool and: always false");
12069 at_least_one_false = true;
12070 break;
12071 }
12072 if (temp_literals_set_.contains(NegatedRef(lit))) {
12073 context_->UpdateRuleStats("bool and: => x and not(x) ");
12074 at_least_one_false = true;
12075 break;
12076 }
12077 if (temp_enforcement_literals_set_.contains(NegatedRef(lit))) {
12078 context_->UpdateRuleStats("bool and: not(x) => x");
12079 at_least_one_false = true;
12080 break;
12081 }
12082
12083 if (context_->LiteralIsTrue(lit)) continue;
12084 if (temp_enforcement_literals_set_.contains(lit)) {
12085 context_->UpdateRuleStats("bool and: x => x");
12086 continue;
12087 }
12088 const auto [it, inserted] = temp_literals_set_.insert(lit);
12089 if (inserted) temp_literals_.push_back(lit);
12090 }
12091
12092 if (at_least_one_false) {
12093 // One enforcement literal must be false.
12094 BoolArgumentProto* bool_or =
12095 context_->working_model->add_constraints()->mutable_bool_or();
12096 for (const int lit : temp_enforcement_literals_) {
12097 bool_or->add_literals(NegatedRef(lit));
12098 }
12099 return !bool_or->literals().empty();
12100 }
12101
12102 if (temp_literals_.empty()) {
12103 context_->UpdateRuleStats("bool and: empty");
12104 return true;
12105 }
12106
12107 // Copy.
12108 ConstraintProto* new_ct = context_->working_model->add_constraints();
12109 FinishEnforcementCopy(new_ct);
12110 new_ct->mutable_bool_and()->mutable_literals()->Add(temp_literals_.begin(),
12111 temp_literals_.end());
12112 return true;
12113}
12114
12115bool ModelCopy::CopyLinear(const ConstraintProto& ct) {
12116 non_fixed_variables_.clear();
12117 non_fixed_coefficients_.clear();
12118 int64_t offset = 0;
12119 int64_t min_activity = 0;
12120 int64_t max_activity = 0;
12121 for (int i = 0; i < ct.linear().vars_size(); ++i) {
12122 const int ref = ct.linear().vars(i);
12123 const int64_t coeff = ct.linear().coeffs(i);
12124 if (coeff == 0) continue;
12125 if (context_->IsFixed(ref)) {
12126 offset += coeff * context_->MinOf(ref);
12127 continue;
12128 }
12129
12130 if (coeff > 0) {
12131 min_activity += coeff * context_->MinOf(ref);
12132 max_activity += coeff * context_->MaxOf(ref);
12133 } else {
12134 min_activity += coeff * context_->MaxOf(ref);
12135 max_activity += coeff * context_->MinOf(ref);
12136 }
12137
12138 // Make sure we never have negative ref in a linear constraint.
12139 if (RefIsPositive(ref)) {
12140 non_fixed_variables_.push_back(ref);
12141 non_fixed_coefficients_.push_back(coeff);
12142 } else {
12143 non_fixed_variables_.push_back(NegatedRef(ref));
12144 non_fixed_coefficients_.push_back(-coeff);
12145 }
12146 }
12147
12148 const Domain implied(min_activity, max_activity);
12149 const Domain new_rhs =
12150 ReadDomainFromProto(ct.linear()).AdditionWith(Domain(-offset));
12151
12152 // Trivial constraint?
12153 if (implied.IsIncludedIn(new_rhs)) {
12154 context_->UpdateRuleStats("linear: always true");
12155 return true;
12156 }
12157
12158 // Constraint is false?
12159 if (implied.IntersectionWith(new_rhs).IsEmpty()) {
12160 if (ct.enforcement_literal().empty()) return false;
12161 temp_literals_.clear();
12162 for (const int literal : ct.enforcement_literal()) {
12163 if (!context_->LiteralIsTrue(literal)) {
12164 temp_literals_.push_back(NegatedRef(literal));
12165 }
12166 }
12167 context_->working_model->add_constraints()
12168 ->mutable_bool_or()
12169 ->mutable_literals()
12170 ->Add(temp_literals_.begin(), temp_literals_.end());
12171 return !temp_literals_.empty();
12172 }
12173
12174 ConstraintProto* new_ct = context_->working_model->add_constraints();
12175 FinishEnforcementCopy(new_ct);
12176 LinearConstraintProto* linear = new_ct->mutable_linear();
12177 linear->mutable_vars()->Add(non_fixed_variables_.begin(),
12178 non_fixed_variables_.end());
12179 linear->mutable_coeffs()->Add(non_fixed_coefficients_.begin(),
12180 non_fixed_coefficients_.end());
12181 FillDomainInProto(new_rhs, linear);
12182 return true;
12183}
12184
12185bool ModelCopy::CopyAtMostOne(const ConstraintProto& ct) {
12186 int num_true = 0;
12187 temp_literals_.clear();
12188 for (const int lit : ct.at_most_one().literals()) {
12189 if (context_->LiteralIsFalse(lit)) continue;
12190 temp_literals_.push_back(lit);
12191 if (context_->LiteralIsTrue(lit)) num_true++;
12192 }
12193
12194 if (temp_literals_.size() <= 1) return true;
12195 if (num_true > 1) return false;
12196
12197 // TODO(user): presolve if num_true == 1.
12198 ConstraintProto* new_ct = context_->working_model->add_constraints();
12199 FinishEnforcementCopy(new_ct);
12200 new_ct->mutable_at_most_one()->mutable_literals()->Add(temp_literals_.begin(),
12201 temp_literals_.end());
12202 return true;
12203}
12204
12205bool ModelCopy::CopyExactlyOne(const ConstraintProto& ct) {
12206 int num_true = 0;
12207 temp_literals_.clear();
12208 for (const int lit : ct.exactly_one().literals()) {
12209 if (context_->LiteralIsFalse(lit)) continue;
12210 temp_literals_.push_back(lit);
12211 if (context_->LiteralIsTrue(lit)) num_true++;
12212 }
12213
12214 if (temp_literals_.empty() || num_true > 1) return false;
12215 if (temp_literals_.size() == 1 && num_true == 1) return true;
12216
12217 // TODO(user): presolve if num_true == 1 and not everything is false.
12218 ConstraintProto* new_ct = context_->working_model->add_constraints();
12219 FinishEnforcementCopy(new_ct);
12220 new_ct->mutable_exactly_one()->mutable_literals()->Add(temp_literals_.begin(),
12221 temp_literals_.end());
12222 return true;
12223}
12224
12225bool ModelCopy::CopyInterval(const ConstraintProto& ct, int c,
12226 bool ignore_names) {
12227 CHECK_EQ(starting_constraint_index_, 0)
12228 << "Adding new interval constraints to partially filled model is not "
12229 "supported.";
12230 interval_mapping_[c] = context_->working_model->constraints_size();
12231 ConstraintProto* new_ct = context_->working_model->add_constraints();
12232 if (ignore_names) {
12233 *new_ct->mutable_enforcement_literal() = ct.enforcement_literal();
12234 *new_ct->mutable_interval() = ct.interval();
12235 } else {
12236 *new_ct = ct;
12237 }
12238
12239 return true;
12240}
12241
12242void ModelCopy::AddLinearConstraintForInterval(const ConstraintProto& ct) {
12243 // Add the linear constraint enforcement => (start + size == end).
12244 //
12245 // We rely on the presolve for simplification, but deal with the trivial
12246 // case of (start, offset, start + offset) here.
12247 const IntervalConstraintProto& itv = ct.interval();
12248 if (itv.size().vars().empty() &&
12249 itv.start().offset() + itv.size().offset() == itv.end().offset() &&
12250 absl::Span<const int>(itv.start().vars()) ==
12251 absl::Span<const int>(itv.end().vars()) &&
12252 absl::Span<const int64_t>(itv.start().coeffs()) ==
12253 absl::Span<const int64_t>(itv.end().coeffs())) {
12254 // Trivial constraint, nothing to do.
12255 } else {
12256 ConstraintProto* new_ct = context_->working_model->add_constraints();
12257 *new_ct->mutable_enforcement_literal() = ct.enforcement_literal();
12258
12259 LinearConstraintProto* mutable_linear = new_ct->mutable_linear();
12260 mutable_linear->add_domain(0);
12261 mutable_linear->add_domain(0);
12262 AddLinearExpressionToLinearConstraint(itv.start(), 1, mutable_linear);
12263 AddLinearExpressionToLinearConstraint(itv.size(), 1, mutable_linear);
12264 AddLinearExpressionToLinearConstraint(itv.end(), -1, mutable_linear);
12265 }
12266
12267 // An enforced interval must have is size non-negative.
12268 const LinearExpressionProto& size_expr = itv.size();
12269 if (context_->MinOf(size_expr) < 0) {
12270 ConstraintProto* new_ct = context_->working_model->add_constraints();
12271 *new_ct->mutable_enforcement_literal() = ct.enforcement_literal();
12272 *new_ct->mutable_linear()->mutable_vars() = size_expr.vars();
12273 *new_ct->mutable_linear()->mutable_coeffs() = size_expr.coeffs();
12274 new_ct->mutable_linear()->add_domain(-size_expr.offset());
12275 new_ct->mutable_linear()->add_domain(std::numeric_limits<int64_t>::max());
12276 }
12277}
12278
12279void ModelCopy::CopyAndMapNoOverlap(const ConstraintProto& ct) {
12280 // Note that we don't copy names or enforcement_literal (not supported) here.
12281 auto* new_ct =
12282 context_->working_model->add_constraints()->mutable_no_overlap();
12283 new_ct->mutable_intervals()->Reserve(ct.no_overlap().intervals().size());
12284 for (const int index : ct.no_overlap().intervals()) {
12285 const auto it = interval_mapping_.find(index);
12286 if (it == interval_mapping_.end()) continue;
12287 new_ct->add_intervals(it->second);
12288 }
12289}
12290
12291void ModelCopy::CopyAndMapNoOverlap2D(const ConstraintProto& ct) {
12292 // Note that we don't copy names or enforcement_literal (not supported) here.
12293 auto* new_ct =
12294 context_->working_model->add_constraints()->mutable_no_overlap_2d();
12295
12296 const int num_intervals = ct.no_overlap_2d().x_intervals().size();
12297 new_ct->mutable_x_intervals()->Reserve(num_intervals);
12298 new_ct->mutable_y_intervals()->Reserve(num_intervals);
12299 for (int i = 0; i < num_intervals; ++i) {
12300 const auto x_it = interval_mapping_.find(ct.no_overlap_2d().x_intervals(i));
12301 if (x_it == interval_mapping_.end()) continue;
12302 const auto y_it = interval_mapping_.find(ct.no_overlap_2d().y_intervals(i));
12303 if (y_it == interval_mapping_.end()) continue;
12304 new_ct->add_x_intervals(x_it->second);
12305 new_ct->add_y_intervals(y_it->second);
12306 }
12307}
12308
12309void ModelCopy::CopyAndMapCumulative(const ConstraintProto& ct) {
12310 // Note that we don't copy names or enforcement_literal (not supported) here.
12311 auto* new_ct =
12312 context_->working_model->add_constraints()->mutable_cumulative();
12313 *new_ct->mutable_capacity() = ct.cumulative().capacity();
12314
12315 const int num_intervals = ct.cumulative().intervals().size();
12316 new_ct->mutable_intervals()->Reserve(num_intervals);
12317 new_ct->mutable_demands()->Reserve(num_intervals);
12318 for (int i = 0; i < num_intervals; ++i) {
12319 const auto it = interval_mapping_.find(ct.cumulative().intervals(i));
12320 if (it == interval_mapping_.end()) continue;
12321 new_ct->add_intervals(it->second);
12322 *new_ct->add_demands() = ct.cumulative().demands(i);
12323 }
12324}
12325
12326bool ModelCopy::CreateUnsatModel(int c, const ConstraintProto& ct) {
12327 context_->working_model->mutable_constraints()->Clear();
12328 context_->working_model->add_constraints()->mutable_bool_or();
12329
12330 // If the model was already marked as unsat, we keep the old message and just
12331 // return. TODO(user): Append messages instead?
12332 if (context_->ModelIsUnsat()) return false;
12333
12334 std::string proto_string;
12335#if !defined(__PORTABLE_PLATFORM__)
12336 google::protobuf::TextFormat::Printer printer;
12337 SetupTextFormatPrinter(&printer);
12338 printer.PrintToString(ct, &proto_string);
12339#endif // !defined(__PORTABLE_PLATFORM__)
12340 std::string message = absl::StrCat(
12341 "proven during initial copy of constraint #", c, ":\n", proto_string);
12342 std::vector<int> vars = UsedVariables(ct);
12343 if (vars.size() < 10) {
12344 absl::StrAppend(&message, "With current variable domains:\n");
12345 for (const int var : vars) {
12346 absl::StrAppend(&message, "var:", var,
12347 " domain:", context_->DomainOf(var).ToString(), "\n");
12348 }
12349 }
12350 return context_->NotifyThatModelIsUnsat(message);
12351}
12352
12353bool ImportModelWithBasicPresolveIntoContext(const CpModelProto& in_model,
12355 ModelCopy copier(context);
12356 copier.ImportVariablesAndMaybeIgnoreNames(in_model);
12357 if (copier.ImportAndSimplifyConstraints(in_model, /*first_copy=*/true)) {
12359 context);
12360 return true;
12361 }
12362 return !context->ModelIsUnsat();
12363}
12364
12366 const CpModelProto& in_model, const std::vector<Domain>& domains,
12367 std::function<bool(int)> active_constraints, PresolveContext* context) {
12368 CHECK_EQ(domains.size(), in_model.variables_size());
12369 ModelCopy copier(context);
12370 copier.CreateVariablesFromDomains(domains);
12371 if (copier.ImportAndSimplifyConstraints(in_model, /*first_copy=*/false,
12372 active_constraints)) {
12374 context);
12375 return true;
12376 }
12377 return !context->ModelIsUnsat();
12378}
12379
12381 const CpModelProto& in_model, PresolveContext* context) {
12382 if (!in_model.name().empty()) {
12383 context->working_model->set_name(in_model.name());
12384 }
12385 if (in_model.has_objective()) {
12386 *context->working_model->mutable_objective() = in_model.objective();
12387 }
12388 if (in_model.has_floating_point_objective()) {
12389 *context->working_model->mutable_floating_point_objective() =
12390 in_model.floating_point_objective();
12391 }
12392 if (!in_model.search_strategy().empty()) {
12393 // We make sure we do not use the old variables field.
12394 *context->working_model->mutable_search_strategy() =
12395 in_model.search_strategy();
12396 for (DecisionStrategyProto& strategy :
12397 *context->working_model->mutable_search_strategy()) {
12398 if (!strategy.variables().empty()) {
12399 CHECK(strategy.exprs().empty());
12400 for (const int ref : strategy.variables()) {
12401 LinearExpressionProto* expr = strategy.add_exprs();
12402 expr->add_vars(PositiveRef(ref));
12403 expr->add_coeffs(RefIsPositive(ref) ? 1 : -1);
12404 }
12405 strategy.clear_variables();
12406 }
12407 }
12408 }
12409 if (!in_model.assumptions().empty()) {
12410 *context->working_model->mutable_assumptions() = in_model.assumptions();
12411 }
12412 if (in_model.has_symmetry()) {
12413 *context->working_model->mutable_symmetry() = in_model.symmetry();
12414 }
12415 if (in_model.has_solution_hint()) {
12416 *context->working_model->mutable_solution_hint() = in_model.solution_hint();
12417 }
12418}
12419
12420// TODO(user): Use better heuristic?
12421//
12422// TODO(user): This is similar to what Bounded variable addition (BVA) does.
12423// By adding a new variable, enforcement => literals becomes
12424// enforcement => x => literals, and we have one clause + #literals implication
12425// instead of #literals clauses. What BVA does in addition is to use the same
12426// x for other enforcement list if the rhs literals are shared.
12427void CpModelPresolver::MergeClauses() {
12428 if (context_->ModelIsUnsat()) return;
12429 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12430
12431 // Constraint index that changed.
12432 std::vector<int> to_clean;
12433
12434 // Keep a map from negation of enforcement_literal => bool_and ct index.
12435 absl::flat_hash_map<uint64_t, int> bool_and_map;
12436
12437 // First loop over the constraint:
12438 // - Register already existing bool_and.
12439 // - score at_most_ones literals.
12440 // - Record bool_or.
12441 const int num_variables = context_->working_model->variables_size();
12442 std::vector<int> bool_or_indices;
12443 std::vector<int64_t> literal_score(2 * num_variables, 0);
12444 const auto get_index = [](int ref) {
12445 return 2 * PositiveRef(ref) + (RefIsPositive(ref) ? 0 : 1);
12446 };
12447
12448 int64_t num_collisions = 0;
12449 int64_t num_merges = 0;
12450 int64_t num_saved_literals = 0;
12451 ClauseWithOneMissingHasher hasher(*context_->random());
12452 const int num_constraints = context_->working_model->constraints_size();
12453 for (int c = 0; c < num_constraints; ++c) {
12454 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12455 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
12456 if (ct->enforcement_literal().size() > 1) {
12457 // We need to sort the negated literals.
12458 std::sort(ct->mutable_enforcement_literal()->begin(),
12459 ct->mutable_enforcement_literal()->end(),
12460 std::greater<int>());
12461 const auto [it, inserted] = bool_and_map.insert(
12462 {hasher.HashOfNegatedLiterals(ct->enforcement_literal()), c});
12463 if (inserted) {
12464 to_clean.push_back(c);
12465 } else {
12466 // See if this is a true duplicate. If yes, merge rhs.
12467 ConstraintProto* other_ct =
12468 context_->working_model->mutable_constraints(it->second);
12469 const absl::Span<const int> s1(ct->enforcement_literal());
12470 const absl::Span<const int> s2(other_ct->enforcement_literal());
12471 if (s1 == s2) {
12472 context_->UpdateRuleStats(
12473 "bool_and: merged constraints with same enforcement");
12474 other_ct->mutable_bool_and()->mutable_literals()->Add(
12475 ct->bool_and().literals().begin(),
12476 ct->bool_and().literals().end());
12477 ct->Clear();
12478 context_->UpdateConstraintVariableUsage(c);
12479 }
12480 }
12481 }
12482 continue;
12483 }
12484 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
12485 const int size = ct->at_most_one().literals().size();
12486 for (const int ref : ct->at_most_one().literals()) {
12487 literal_score[get_index(ref)] += size;
12488 }
12489 continue;
12490 }
12491 if (ct->constraint_case() == ConstraintProto::kExactlyOne) {
12492 const int size = ct->exactly_one().literals().size();
12493 for (const int ref : ct->exactly_one().literals()) {
12494 literal_score[get_index(ref)] += size;
12495 }
12496 continue;
12497 }
12498
12499 if (ct->constraint_case() != ConstraintProto::kBoolOr) continue;
12500
12501 // Both of these test shouldn't happen, but we have them to be safe.
12502 if (!ct->enforcement_literal().empty()) continue;
12503 if (ct->bool_or().literals().size() <= 2) continue;
12504
12505 std::sort(ct->mutable_bool_or()->mutable_literals()->begin(),
12506 ct->mutable_bool_or()->mutable_literals()->end());
12507 hasher.RegisterClause(c, ct->bool_or().literals());
12508 bool_or_indices.push_back(c);
12509 }
12510
12511 for (const int c : bool_or_indices) {
12512 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12513
12514 bool merged = false;
12515 timer.TrackSimpleLoop(ct->bool_or().literals().size());
12516 if (timer.WorkLimitIsReached()) break;
12517 for (const int ref : ct->bool_or().literals()) {
12518 const uint64_t hash = hasher.HashWithout(c, ref);
12519 const auto it = bool_and_map.find(hash);
12520 if (it != bool_and_map.end()) {
12521 ++num_collisions;
12522 const int base_c = it->second;
12523 auto* and_ct = context_->working_model->mutable_constraints(base_c);
12525 ct->bool_or().literals(), and_ct->enforcement_literal(), ref)) {
12526 ++num_merges;
12527 num_saved_literals += ct->bool_or().literals().size() - 1;
12528 merged = true;
12529 and_ct->mutable_bool_and()->add_literals(ref);
12530 ct->Clear();
12531 context_->UpdateConstraintVariableUsage(c);
12532 break;
12533 }
12534 }
12535 }
12536
12537 if (!merged) {
12538 // heuristic: take first literal whose negation has highest score.
12539 int best_ref = ct->bool_or().literals(0);
12540 int64_t best_score = literal_score[get_index(NegatedRef(best_ref))];
12541 for (const int ref : ct->bool_or().literals()) {
12542 const int64_t score = literal_score[get_index(NegatedRef(ref))];
12543 if (score > best_score) {
12544 best_ref = ref;
12545 best_score = score;
12546 }
12547 }
12548
12549 const uint64_t hash = hasher.HashWithout(c, best_ref);
12550 const auto [_, inserted] = bool_and_map.insert({hash, c});
12551 if (inserted) {
12552 to_clean.push_back(c);
12553 context_->tmp_literals.clear();
12554 for (const int lit : ct->bool_or().literals()) {
12555 if (lit == best_ref) continue;
12556 context_->tmp_literals.push_back(NegatedRef(lit));
12557 }
12558 ct->Clear();
12559 ct->mutable_enforcement_literal()->Assign(
12560 context_->tmp_literals.begin(), context_->tmp_literals.end());
12561 ct->mutable_bool_and()->add_literals(best_ref);
12562 }
12563 }
12564 }
12565
12566 // Retransform to bool_or bool_and with a single rhs.
12567 for (const int c : to_clean) {
12568 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12569 if (ct->bool_and().literals().size() > 1) {
12570 context_->UpdateConstraintVariableUsage(c);
12571 continue;
12572 }
12573
12574 // We have a single bool_and, lets transform it back to single bool_or.
12575 context_->tmp_literals.clear();
12576 context_->tmp_literals.push_back(ct->bool_and().literals(0));
12577 for (const int ref : ct->enforcement_literal()) {
12578 context_->tmp_literals.push_back(NegatedRef(ref));
12579 }
12580 ct->Clear();
12581 ct->mutable_bool_or()->mutable_literals()->Assign(
12582 context_->tmp_literals.begin(), context_->tmp_literals.end());
12583 }
12584
12585 timer.AddCounter("num_collisions", num_collisions);
12586 timer.AddCounter("num_merges", num_merges);
12587 timer.AddCounter("num_saved_literals", num_saved_literals);
12588}
12589
12590// =============================================================================
12591// Public API.
12592// =============================================================================
12593
12594CpSolverStatus PresolveCpModel(PresolveContext* context,
12595 std::vector<int>* postsolve_mapping) {
12596 CpModelPresolver presolver(context, postsolve_mapping);
12597 return presolver.Presolve();
12598}
12599
12601 std::vector<int>* postsolve_mapping)
12602 : postsolve_mapping_(postsolve_mapping),
12603 context_(context),
12604 logger_(context->logger()),
12605 time_limit_(context->time_limit()),
12606 interval_representative_(context->working_model->constraints_size(),
12607 IntervalConstraintHash{context->working_model},
12608 IntervalConstraintEq{context->working_model}) {}
12609
12610CpSolverStatus CpModelPresolver::InfeasibleStatus() {
12611 if (logger_->LoggingIsEnabled()) context_->LogInfo();
12612 return CpSolverStatus::INFEASIBLE;
12613}
12614
12615void CpModelPresolver::InitializeMappingModelVariables() {
12616 // Sync the domains.
12617 for (int i = 0; i < context_->working_model->variables_size(); ++i) {
12618 FillDomainInProto(context_->DomainOf(i),
12619 context_->working_model->mutable_variables(i));
12620 DCHECK_GT(context_->working_model->variables(i).domain_size(), 0);
12621 }
12622
12623 // Set the variables of the mapping_model.
12624 context_->mapping_model->mutable_variables()->CopyFrom(
12625 context_->working_model->variables());
12626}
12627
12628// The presolve works as follow:
12629//
12630// First stage:
12631// We will process all active constraints until a fix point is reached. During
12632// this stage:
12633// - Variable will never be deleted, but their domain will be reduced.
12634// - Constraint will never be deleted (they will be marked as empty if needed).
12635// - New variables and new constraints can be added after the existing ones.
12636// - Constraints are added only when needed to the mapping_problem if they are
12637// needed during the postsolve.
12638//
12639// Second stage:
12640// - All the variables domain will be copied to the mapping_model.
12641// - Everything will be remapped so that only the variables appearing in some
12642// constraints will be kept and their index will be in [0, num_new_variables).
12643CpSolverStatus CpModelPresolver::Presolve() {
12644 // TODO(user): move in the context.
12646 context_->params().keep_all_feasible_solutions_in_presolve() ||
12647 context_->params().enumerate_all_solutions() ||
12648 !context_->working_model->assumptions().empty() ||
12649 !context_->params().cp_model_presolve();
12650
12651 // We copy the search strategy to the mapping_model.
12652 for (const auto& decision_strategy :
12653 context_->working_model->search_strategy()) {
12654 *(context_->mapping_model->add_search_strategy()) = decision_strategy;
12655 }
12656
12657 // Initialize the initial context.working_model domains.
12658 context_->InitializeNewDomains();
12659 context_->LoadSolutionHint();
12660
12661 // If the objective is a floating point one, we scale it.
12662 //
12663 // TODO(user): We should probably try to delay this even more. For that we
12664 // just need to isolate more the "dual" reduction that usually need to look at
12665 // the objective.
12666 if (context_->working_model->has_floating_point_objective()) {
12667 if (!context_->ScaleFloatingPointObjective()) {
12668 SOLVER_LOG(logger_,
12669 "The floating point objective cannot be scaled with enough "
12670 "precision");
12671 return CpSolverStatus::MODEL_INVALID;
12672 }
12673
12674 // At this point, we didn't create any new variables, so the integer
12675 // objective is in term of the orinal problem variables. We save it so that
12676 // we can expose to the user what exact objective we are actually
12677 // optimizing.
12678 *context_->mapping_model->mutable_objective() =
12679 context_->working_model->objective();
12680 }
12681
12682 // Initialize the objective and the constraint <-> variable graph.
12683 //
12684 // Note that we did some basic presolving during the first copy of the model.
12685 // This is important has initializing the constraint <-> variable graph can
12686 // be costly, so better to remove trivially feasible constraint for instance.
12687 context_->ReadObjectiveFromProto();
12688 if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
12691 DCHECK(context_->ConstraintVariableUsageIsConsistent());
12692
12693 // If presolve is false, just run expansion.
12694 if (!context_->params().cp_model_presolve()) {
12695 ExpandCpModel(context_);
12696 if (context_->ModelIsUnsat()) return InfeasibleStatus();
12697
12698 // We still write back the canonical objective has we don't deal well
12699 // with uninitialized domain or duplicate variables.
12700 if (context_->working_model->has_objective()) {
12701 context_->WriteObjectiveToProto();
12702 }
12703
12704 // We need to append all the variable equivalence that are still used!
12705 EncodeAllAffineRelations();
12706
12707 // Make sure we also have an initialized mapping model as we use this for
12708 // filling the tightened variables. Even without presolve, we do some
12709 // trivial presolving during the initial copy of the model, and expansion
12710 // might do more.
12711 InitializeMappingModelVariables();
12712
12713 if (logger_->LoggingIsEnabled()) context_->LogInfo();
12714 return CpSolverStatus::UNKNOWN;
12715 }
12716
12717 // Presolve all variable domain once. The PresolveToFixPoint() function will
12718 // only reprocess domain that changed.
12719 if (context_->ModelIsUnsat()) return InfeasibleStatus();
12720 for (int var = 0; var < context_->working_model->variables().size(); ++var) {
12721 if (context_->VariableIsNotUsedAnymore(var)) continue;
12722 if (!PresolveAffineRelationIfAny(var)) return InfeasibleStatus();
12723
12724 // Try to canonicalize the domain, note that we should have detected all
12725 // affine relations before, so we don't recreate "canononical" variables
12726 // if they already exist in the model.
12727 TryToSimplifyDomain(var);
12728 if (context_->ModelIsUnsat()) return InfeasibleStatus();
12730 }
12731 if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
12732
12733 // Main propagation loop.
12734 for (int iter = 0; iter < context_->params().max_presolve_iterations();
12735 ++iter) {
12736 if (time_limit_->LimitReached()) break;
12737 context_->UpdateRuleStats("presolve: iteration");
12738 const int64_t old_num_presolve_op = context_->num_presolve_operations;
12739
12740 // TODO(user): The presolve transformations we do after this is called might
12741 // result in even more presolve if we were to call this again! improve the
12742 // code. See for instance plusexample_6_sat.fzn were represolving the
12743 // presolved problem reduces it even more.
12744 PresolveToFixPoint();
12745
12746 // Call expansion.
12747 if (!context_->ModelIsExpanded()) {
12748 ExtractEncodingFromLinear();
12749 ExpandCpModel(context_);
12750 if (context_->ModelIsUnsat()) return InfeasibleStatus();
12751
12752 // TODO(user): Make sure we can't have duplicate in these constraint.
12753 // These are due to ExpandCpModel() were we create such constraint with
12754 // duplicate. The problem is that some code assumes these are presolved
12755 // before being called.
12756 const int num_constraints = context_->working_model->constraints().size();
12757 for (int c = 0; c < num_constraints; ++c) {
12758 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12759 const auto type = ct->constraint_case();
12760 if (type == ConstraintProto::kAtMostOne ||
12761 type == ConstraintProto::kExactlyOne) {
12762 if (PresolveOneConstraint(c)) {
12763 context_->UpdateConstraintVariableUsage(c);
12764 }
12765 if (context_->ModelIsUnsat()) return InfeasibleStatus();
12766 }
12767 }
12768
12769 // We need to re-evaluate the degree because some presolve rule only
12770 // run after expansion.
12771 const int num_vars = context_->working_model->variables().size();
12772 for (int var = 0; var < num_vars; ++var) {
12773 if (context_->VarToConstraints(var).size() <= 3) {
12775 }
12776 }
12777 }
12778 DCHECK(context_->ConstraintVariableUsageIsConsistent());
12779
12780 // We run the symmetry before more complex presolve rules as many of them
12781 // are heuristic based and might break the symmetry present in the original
12782 // problems. This happens for example on the flatzinc wordpress problem.
12783 //
12784 // TODO(user): Decide where is the best place for this.
12785 //
12786 // TODO(user): try not to break symmetry in our clique extension or other
12787 // more advanced presolve rule? Ideally we could even exploit them. But in
12788 // this case, it is still good to compute them early.
12789 if (context_->params().symmetry_level() > 0 && !context_->ModelIsUnsat() &&
12790 !time_limit_->LimitReached() &&
12791 !context_->keep_all_feasible_solutions) {
12793 }
12794
12795 // Runs SAT specific presolve on the pure-SAT part of the problem.
12796 // Note that because this can only remove/fix variable not used in the other
12797 // part of the problem, there is no need to redo more presolve afterwards.
12798 if (context_->params().cp_model_use_sat_presolve()) {
12799 if (!time_limit_->LimitReached()) {
12800 if (!PresolvePureSatPart()) {
12801 (void)context_->NotifyThatModelIsUnsat(
12802 "Proven Infeasible during SAT presolve");
12803 return InfeasibleStatus();
12804 }
12805 }
12806 }
12807
12808 // Extract redundant at most one constraint form the linear ones.
12809 //
12810 // TODO(user): more generally if we do some probing, the same relation will
12811 // be detected (and more). Also add an option to turn this off?
12812 //
12813 // TODO(user): instead of extracting at most one, extract pairwise conflicts
12814 // and add them to bool_and clauses? this is some sort of small scale
12815 // probing, but good for sat presolve and clique later?
12816 if (!context_->ModelIsUnsat() && iter == 0) {
12817 const int old_size = context_->working_model->constraints_size();
12818 for (int c = 0; c < old_size; ++c) {
12819 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12820 if (ct->constraint_case() != ConstraintProto::kLinear) continue;
12821 ExtractAtMostOneFromLinear(ct);
12822 }
12824 }
12825
12826 if (context_->params().cp_model_probing_level() > 0) {
12827 if (!time_limit_->LimitReached()) {
12828 Probe();
12829 PresolveToFixPoint();
12830 }
12831 } else {
12832 TransformIntoMaxCliques();
12833 }
12834
12835 // Deal with pair of constraints.
12836 //
12837 // TODO(user): revisit when different transformation appear.
12838 // TODO(user): merge these code instead of doing many passes?
12839 ProcessAtMostOneAndLinear();
12840 DetectDuplicateConstraints();
12841 DetectDuplicateConstraintsWithDifferentEnforcements();
12842 DetectDominatedLinearConstraints();
12843 DetectDifferentVariables();
12844 ProcessSetPPC();
12845
12846 if (context_->params().find_big_linear_overlap()) {
12847 FindAlmostIdenticalLinearConstraints();
12848
12849 ActivityBoundHelper activity_amo_helper;
12850 activity_amo_helper.AddAllAtMostOnes(*context_->working_model);
12851 FindBigAtMostOneAndLinearOverlap(&activity_amo_helper);
12852
12853 // Heuristic: vertical introduce smaller defining constraint and appear in
12854 // many constraints, so might be more constrained. We might also still
12855 // make horizontal rectangle with the variable introduced.
12856 FindBigVerticalLinearOverlap(&activity_amo_helper);
12857 FindBigHorizontalLinearOverlap(&activity_amo_helper);
12858 }
12859 if (context_->ModelIsUnsat()) return InfeasibleStatus();
12860
12861 // We do that after the duplicate, SAT and SetPPC constraints.
12862 if (!time_limit_->LimitReached()) {
12863 // Merge clauses that differ in just one literal.
12864 // Heuristic use at_most_one to try to tighten the initial LP Relaxation.
12865 MergeClauses();
12866 if (/*DISABLES CODE*/ (false)) DetectIncludedEnforcement();
12867 }
12868
12869 // The TransformIntoMaxCliques() call above transform all bool and into
12870 // at most one of size 2. This does the reverse and merge them.
12871 ConvertToBoolAnd();
12872
12873 // Call the main presolve to remove the fixed variables and do more
12874 // deductions.
12875 PresolveToFixPoint();
12876
12877 // Exit the loop if no operations were performed.
12878 //
12879 // TODO(user): try to be smarter and avoid looping again if little changed.
12880 const int64_t num_ops =
12881 context_->num_presolve_operations - old_num_presolve_op;
12882 if (num_ops == 0) break;
12883 }
12884 if (context_->ModelIsUnsat()) return InfeasibleStatus();
12885
12886 // Regroup no-overlaps into max-cliques.
12887 MergeNoOverlapConstraints();
12888 if (context_->ModelIsUnsat()) return InfeasibleStatus();
12889
12890 // Tries to spread the objective amongst many variables.
12891 // We re-do a canonicalization with the final linear expression.
12892 if (context_->working_model->has_objective()) {
12893 ExpandObjective();
12894 if (context_->ModelIsUnsat()) return InfeasibleStatus();
12895 ShiftObjectiveWithExactlyOnes();
12896 if (context_->ModelIsUnsat()) return InfeasibleStatus();
12897
12898 // We re-do a canonicalization with the final linear expression.
12899 if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
12900 context_->WriteObjectiveToProto();
12901 }
12902
12903 // Take care of linear constraint with a complex rhs.
12905
12906 // Adds all needed affine relation to context_->working_model.
12907 EncodeAllAffineRelations();
12908 if (context_->ModelIsUnsat()) return InfeasibleStatus();
12909
12910 // The strategy variable indices will be remapped in ApplyVariableMapping()
12911 // but first we use the representative of the affine relations for the
12912 // variables that are not present anymore.
12913 //
12914 // Note that we properly take into account the sign of the coefficient which
12915 // will result in the same domain reduction strategy. Moreover, if the
12916 // variable order is not CHOOSE_FIRST, then we also encode the associated
12917 // affine transformation in order to preserve the order.
12918 absl::flat_hash_set<int> used_variables;
12919 for (DecisionStrategyProto& strategy :
12920 *context_->working_model->mutable_search_strategy()) {
12921 CHECK(strategy.variables().empty());
12922 if (strategy.exprs().empty()) continue;
12923
12924 // Canonicalize each expression to use affine representative.
12925 ConstraintProto empy_enforcement;
12926 for (LinearExpressionProto& expr : *strategy.mutable_exprs()) {
12927 CanonicalizeLinearExpression(empy_enforcement, &expr);
12928 }
12929
12930 // Remove fixed expression and affine corresponding to same variables.
12931 int new_size = 0;
12932 for (const LinearExpressionProto& expr : strategy.exprs()) {
12933 if (context_->IsFixed(expr)) continue;
12934
12935 const auto [_, inserted] = used_variables.insert(expr.vars(0));
12936 if (!inserted) continue;
12937
12938 *strategy.mutable_exprs(new_size++) = expr;
12939 }
12940 google::protobuf::util::Truncate(strategy.mutable_exprs(), new_size);
12941 }
12942
12943 // Sync the domains and initialize the mapping model variables.
12944 InitializeMappingModelVariables();
12945
12946 // Remove all the unused variables from the presolved model.
12947 postsolve_mapping_->clear();
12948 std::vector<int> mapping(context_->working_model->variables_size(), -1);
12949 absl::flat_hash_map<int64_t, int> constant_to_index;
12950 int num_unused_variables = 0;
12951 for (int i = 0; i < context_->working_model->variables_size(); ++i) {
12952 if (mapping[i] != -1) continue; // Already mapped.
12953
12954 if (context_->VariableWasRemoved(i)) {
12955 // Heuristic: If a variable is removed and has a representative that is
12956 // not, we "move" the representative to the spot of that variable in the
12957 // original order. This is to preserve any info encoded in the variable
12958 // order by the modeler.
12959 const int r = PositiveRef(context_->GetAffineRelation(i).representative);
12960 if (mapping[r] == -1 && !context_->VariableIsNotUsedAnymore(r)) {
12961 mapping[r] = postsolve_mapping_->size();
12962 postsolve_mapping_->push_back(r);
12963 }
12964 continue;
12965 }
12966
12967 // Deal with unused variables.
12968 //
12969 // If the variable is not fixed, we have multiple feasible solution for
12970 // this variable, so we can't remove it if we want all of them.
12971 if (context_->VariableIsNotUsedAnymore(i) &&
12972 (!context_->keep_all_feasible_solutions || context_->IsFixed(i))) {
12973 // Tricky. Variables that where not removed by a presolve rule should be
12974 // fixed first during postsolve, so that more complex postsolve rules
12975 // can use their values. One way to do that is to fix them here.
12976 //
12977 // We prefer to fix them to zero if possible.
12978 ++num_unused_variables;
12979 FillDomainInProto(Domain(context_->DomainOf(i).SmallestValue()),
12980 context_->mapping_model->mutable_variables(i));
12981 continue;
12982 }
12983
12984 // Merge identical constant. Note that the only place were constant are
12985 // still left are in the circuit and route constraint for fixed arcs.
12986 if (context_->IsFixed(i)) {
12987 auto [it, inserted] = constant_to_index.insert(
12988 {context_->FixedValue(i), postsolve_mapping_->size()});
12989 if (!inserted) {
12990 mapping[i] = it->second;
12991 continue;
12992 }
12993 }
12994
12995 mapping[i] = postsolve_mapping_->size();
12996 postsolve_mapping_->push_back(i);
12997 }
12998 context_->UpdateRuleStats(absl::StrCat("presolve: ", num_unused_variables,
12999 " unused variables removed."));
13000
13001 if (context_->params().permute_variable_randomly()) {
13002 // The mapping might merge variable, so we have to be careful here.
13003 const int n = postsolve_mapping_->size();
13004 std::vector<int> perm(n);
13005 std::iota(perm.begin(), perm.end(), 0);
13006 std::shuffle(perm.begin(), perm.end(), *context_->random());
13007 for (int i = 0; i < context_->working_model->variables_size(); ++i) {
13008 if (mapping[i] != -1) mapping[i] = perm[mapping[i]];
13009 }
13010 std::vector<int> new_postsolve_mapping(n);
13011 for (int i = 0; i < n; ++i) {
13012 new_postsolve_mapping[perm[i]] = (*postsolve_mapping_)[i];
13013 }
13014 *postsolve_mapping_ = std::move(new_postsolve_mapping);
13015 }
13016
13017 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13018 ApplyVariableMapping(mapping, *context_);
13019
13020 // Compact all non-empty constraint at the beginning.
13022
13023 // Hack to display the number of deductions stored.
13024 if (context_->deductions.NumDeductions() > 0) {
13025 context_->UpdateRuleStats(absl::StrCat(
13026 "deductions: ", context_->deductions.NumDeductions(), " stored"));
13027 }
13028
13029 // Stats and checks.
13030 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13031
13032 // This is not supposed to happen, and is more indicative of an error than an
13033 // INVALID model. But for our no-overflow preconditions, we might run into bad
13034 // situation that causes the final model to be invalid.
13035 {
13036 const std::string error =
13037 ValidateCpModel(*context_->working_model, /*after_presolve=*/true);
13038 if (!error.empty()) {
13039 SOLVER_LOG(logger_, "Error while validating postsolved model: ", error);
13040 return CpSolverStatus::MODEL_INVALID;
13041 }
13042 }
13043 {
13044 const std::string error = ValidateCpModel(*context_->mapping_model);
13045 if (!error.empty()) {
13046 SOLVER_LOG(logger_,
13047 "Error while validating mapping_model model: ", error);
13048 return CpSolverStatus::MODEL_INVALID;
13049 }
13050 }
13051
13052 return CpSolverStatus::UNKNOWN;
13053}
13054
13055void ApplyVariableMapping(const std::vector<int>& mapping,
13056 const PresolveContext& context) {
13057 CpModelProto* proto = context.working_model;
13058
13059 // Remap all the variable/literal references in the constraints and the
13060 // enforcement literals in the variables.
13061 auto mapping_function = [&mapping](int* ref) {
13062 const int image = mapping[PositiveRef(*ref)];
13063 CHECK_GE(image, 0);
13064 *ref = RefIsPositive(*ref) ? image : NegatedRef(image);
13065 };
13066 for (ConstraintProto& ct_ref : *proto->mutable_constraints()) {
13067 ApplyToAllVariableIndices(mapping_function, &ct_ref);
13068 ApplyToAllLiteralIndices(mapping_function, &ct_ref);
13069 }
13070
13071 // Remap the objective variables.
13072 if (proto->has_objective()) {
13073 for (int& mutable_ref : *proto->mutable_objective()->mutable_vars()) {
13074 mapping_function(&mutable_ref);
13075 }
13076 }
13077
13078 // Remap the assumptions.
13079 for (int& mutable_ref : *proto->mutable_assumptions()) {
13080 mapping_function(&mutable_ref);
13081 }
13082
13083 // Remap the search decision heuristic.
13084 // Note that we delete any heuristic related to a removed variable.
13085 for (DecisionStrategyProto& strategy : *proto->mutable_search_strategy()) {
13086 int new_size = 0;
13087 for (LinearExpressionProto expr : strategy.exprs()) {
13088 DCHECK_EQ(expr.vars().size(), 1);
13089 const int image = mapping[expr.vars(0)];
13090 if (image >= 0) {
13091 expr.set_vars(0, image);
13092 *strategy.mutable_exprs(new_size++) = expr;
13093 }
13094 }
13095 google::protobuf::util::Truncate(strategy.mutable_exprs(), new_size);
13096 }
13097
13098 // Remove strategy with empty affine expression.
13099 {
13100 int new_size = 0;
13101 for (const DecisionStrategyProto& strategy : proto->search_strategy()) {
13102 if (strategy.exprs().empty()) continue;
13103 *proto->mutable_search_strategy(new_size++) = strategy;
13104 }
13105 google::protobuf::util::Truncate(proto->mutable_search_strategy(),
13106 new_size);
13107 }
13108
13109 // Remap the solution hint. Note that after remapping, we may have duplicate
13110 // variable, so we only keep the first occurrence.
13111 if (proto->has_solution_hint()) {
13112 absl::flat_hash_set<int> used_vars;
13113 auto* mutable_hint = proto->mutable_solution_hint();
13114 mutable_hint->clear_vars();
13115 mutable_hint->clear_values();
13116 const int num_vars = context.working_model->variables().size();
13117 for (int hinted_var = 0; hinted_var < num_vars; ++hinted_var) {
13118 if (!context.VarHasSolutionHint(hinted_var)) continue;
13119 int64_t hinted_value = context.SolutionHint(hinted_var);
13120
13121 // We always move a hint within bounds.
13122 // This also make sure a hint of INT_MIN or INT_MAX does not overflow.
13123 if (hinted_value < context.MinOf(hinted_var)) {
13124 hinted_value = context.MinOf(hinted_var);
13125 }
13126 if (hinted_value > context.MaxOf(hinted_var)) {
13127 hinted_value = context.MaxOf(hinted_var);
13128 }
13129
13130 // Note that if (hinted_value - r.offset) is not divisible by r.coeff,
13131 // then the hint is clearly infeasible, but we still set it to a "close"
13132 // value.
13133 const AffineRelation::Relation r = context.GetAffineRelation(hinted_var);
13134 const int var = r.representative;
13135 const int64_t value = (hinted_value - r.offset) / r.coeff;
13136
13137 const int image = mapping[var];
13138 if (image >= 0) {
13139 if (!used_vars.insert(image).second) continue;
13140 mutable_hint->add_vars(image);
13141 mutable_hint->add_values(value);
13142 }
13143 }
13144 }
13145
13146 // Move the variable definitions.
13147 std::vector<IntegerVariableProto> new_variables;
13148 for (int i = 0; i < mapping.size(); ++i) {
13149 const int image = mapping[i];
13150 if (image < 0) continue;
13151 if (image >= new_variables.size()) {
13152 new_variables.resize(image + 1, IntegerVariableProto());
13153 }
13154 new_variables[image].Swap(proto->mutable_variables(i));
13155 }
13156 proto->clear_variables();
13157 for (IntegerVariableProto& proto_ref : new_variables) {
13158 proto->add_variables()->Swap(&proto_ref);
13159 }
13160
13161 // Check that all variables are used.
13162 for (const IntegerVariableProto& v : proto->variables()) {
13163 CHECK_GT(v.domain_size(), 0);
13164 }
13165}
13166
13167namespace {
13168
13169// We ignore all the fields but the linear expression.
13170ConstraintProto CopyObjectiveForDuplicateDetection(
13171 const CpObjectiveProto& objective) {
13172 ConstraintProto copy;
13173 *copy.mutable_linear()->mutable_vars() = objective.vars();
13174 *copy.mutable_linear()->mutable_coeffs() = objective.coeffs();
13175 return copy;
13176}
13177
13178struct ConstraintHashForDuplicateDetection {
13179 const CpModelProto* working_model;
13180 bool ignore_enforcement;
13181 ConstraintProto objective_constraint;
13183 ConstraintHashForDuplicateDetection(const CpModelProto* working_model,
13184 bool ignore_enforcement)
13188 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
13189
13190 // We hash our mostly frequently used constraint directly without extra memory
13191 // allocation. We revert to a generic code using proto serialization for the
13192 // others.
13193 std::size_t operator()(int ct_idx) const {
13194 const ConstraintProto& ct = ct_idx == kObjectiveConstraint
13196 : working_model->constraints(ct_idx);
13197 const std::pair<ConstraintProto::ConstraintCase, absl::Span<const int>>
13198 type_and_enforcement = {ct.constraint_case(),
13200 ? absl::Span<const int>()
13201 : absl::MakeSpan(ct.enforcement_literal())};
13202 switch (ct.constraint_case()) {
13203 case ConstraintProto::kLinear:
13204 if (ignore_enforcement) {
13205 return absl::HashOf(type_and_enforcement,
13206 absl::MakeSpan(ct.linear().vars()),
13207 absl::MakeSpan(ct.linear().coeffs()),
13208 absl::MakeSpan(ct.linear().domain()));
13209 } else {
13210 // We ignore domain for linear constraint, because if the rest of the
13211 // constraint is the same we can just intersect them.
13212 return absl::HashOf(type_and_enforcement,
13213 absl::MakeSpan(ct.linear().vars()),
13214 absl::MakeSpan(ct.linear().coeffs()));
13215 }
13216 case ConstraintProto::kBoolAnd:
13217 return absl::HashOf(type_and_enforcement,
13218 absl::MakeSpan(ct.bool_and().literals()));
13219 case ConstraintProto::kBoolOr:
13220 return absl::HashOf(type_and_enforcement,
13221 absl::MakeSpan(ct.bool_or().literals()));
13222 case ConstraintProto::kAtMostOne:
13223 return absl::HashOf(type_and_enforcement,
13224 absl::MakeSpan(ct.at_most_one().literals()));
13225 case ConstraintProto::kExactlyOne:
13226 return absl::HashOf(type_and_enforcement,
13227 absl::MakeSpan(ct.exactly_one().literals()));
13228 default:
13229 ConstraintProto copy = ct;
13230 copy.clear_name();
13231 if (ignore_enforcement) {
13232 copy.mutable_enforcement_literal()->Clear();
13233 }
13234 return absl::HashOf(copy.SerializeAsString());
13235 }
13236 }
13237};
13238
13239struct ConstraintEqForDuplicateDetection {
13240 const CpModelProto* working_model;
13241 bool ignore_enforcement;
13242 ConstraintProto objective_constraint;
13243
13244 ConstraintEqForDuplicateDetection(const CpModelProto* working_model,
13245 bool ignore_enforcement)
13249 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
13250
13251 bool operator()(int a, int b) const {
13252 if (a == b) {
13253 return true;
13254 }
13255 const ConstraintProto& ct_a = a == kObjectiveConstraint
13257 : working_model->constraints(a);
13258 const ConstraintProto& ct_b = b == kObjectiveConstraint
13260 : working_model->constraints(b);
13261
13262 if (ct_a.constraint_case() != ct_b.constraint_case()) return false;
13263 if (!ignore_enforcement) {
13264 if (absl::MakeSpan(ct_a.enforcement_literal()) !=
13265 absl::MakeSpan(ct_b.enforcement_literal())) {
13266 return false;
13267 }
13268 }
13269 switch (ct_a.constraint_case()) {
13270 case ConstraintProto::kLinear:
13271 // As above, we ignore domain for linear constraint, because if the rest
13272 // of the constraint is the same we can just intersect them.
13273 if (ignore_enforcement && absl::MakeSpan(ct_a.linear().domain()) !=
13274 absl::MakeSpan(ct_b.linear().domain())) {
13275 return false;
13276 }
13277 return absl::MakeSpan(ct_a.linear().vars()) ==
13278 absl::MakeSpan(ct_b.linear().vars()) &&
13279 absl::MakeSpan(ct_a.linear().coeffs()) ==
13280 absl::MakeSpan(ct_b.linear().coeffs());
13281 case ConstraintProto::kBoolAnd:
13282 return absl::MakeSpan(ct_a.bool_and().literals()) ==
13283 absl::MakeSpan(ct_b.bool_and().literals());
13284 case ConstraintProto::kBoolOr:
13285 return absl::MakeSpan(ct_a.bool_or().literals()) ==
13286 absl::MakeSpan(ct_b.bool_or().literals());
13287 case ConstraintProto::kAtMostOne:
13288 return absl::MakeSpan(ct_a.at_most_one().literals()) ==
13289 absl::MakeSpan(ct_b.at_most_one().literals());
13290 case ConstraintProto::kExactlyOne:
13291 return absl::MakeSpan(ct_a.exactly_one().literals()) ==
13292 absl::MakeSpan(ct_b.exactly_one().literals());
13293 default:
13294 // Slow (hopefully comparably rare) path.
13295 ConstraintProto copy_a = ct_a;
13296 ConstraintProto copy_b = ct_b;
13297 copy_a.clear_name();
13298 copy_b.clear_name();
13299 if (ignore_enforcement) {
13300 copy_a.mutable_enforcement_literal()->Clear();
13301 copy_b.mutable_enforcement_literal()->Clear();
13302 }
13303 return copy_a.SerializeAsString() == copy_b.SerializeAsString();
13304 }
13305 }
13306};
13307
13308} // namespace
13309
13310std::vector<std::pair<int, int>> FindDuplicateConstraints(
13311 const CpModelProto& model_proto, bool ignore_enforcement) {
13312 std::vector<std::pair<int, int>> result;
13313
13314 // We use a map hash that uses the underlying constraint to compute the hash
13315 // and the equality for the indices.
13316 absl::flat_hash_map<int, int, ConstraintHashForDuplicateDetection,
13317 ConstraintEqForDuplicateDetection>
13318 equiv_constraints(
13319 model_proto.constraints_size(),
13320 ConstraintHashForDuplicateDetection{&model_proto, ignore_enforcement},
13321 ConstraintEqForDuplicateDetection{&model_proto, ignore_enforcement});
13322
13323 // Create a special representative for the linear objective.
13324 if (model_proto.has_objective() && !ignore_enforcement) {
13325 equiv_constraints[kObjectiveConstraint] = kObjectiveConstraint;
13326 }
13327
13328 const int num_constraints = model_proto.constraints().size();
13329 for (int c = 0; c < num_constraints; ++c) {
13330 const auto type = model_proto.constraints(c).constraint_case();
13331 if (type == ConstraintProto::CONSTRAINT_NOT_SET) continue;
13332
13333 // TODO(user): we could delete duplicate identical interval, but we need
13334 // to make sure reference to them are updated.
13335 if (type == ConstraintProto::kInterval) continue;
13336
13337 // Nothing we will presolve in this case.
13338 if (ignore_enforcement && type == ConstraintProto::kBoolAnd) continue;
13339
13340 const auto [it, inserted] = equiv_constraints.insert({c, c});
13341 if (it->second != c) {
13342 // Already present!
13343 result.push_back({c, it->second});
13344 }
13345 }
13346
13347 return result;
13348}
13349
13350namespace {
13351bool SimpleLinearExprEq(const LinearExpressionProto& a,
13352 const LinearExpressionProto& b) {
13353 return absl::MakeSpan(a.vars()) == absl::MakeSpan(b.vars()) &&
13354 absl::MakeSpan(a.coeffs()) == absl::MakeSpan(b.coeffs()) &&
13355 a.offset() == b.offset();
13356}
13357
13358std::size_t LinearExpressionHash(const LinearExpressionProto& expr) {
13359 return absl::HashOf(absl::MakeSpan(expr.vars()),
13360 absl::MakeSpan(expr.coeffs()), expr.offset());
13361}
13362
13363} // namespace
13364
13365bool CpModelPresolver::IntervalConstraintEq::operator()(int a, int b) const {
13366 const ConstraintProto& ct_a = working_model->constraints(a);
13367 const ConstraintProto& ct_b = working_model->constraints(b);
13368 return absl::MakeSpan(ct_a.enforcement_literal()) ==
13369 absl::MakeSpan(ct_b.enforcement_literal()) &&
13370 SimpleLinearExprEq(ct_a.interval().start(), ct_b.interval().start()) &&
13371 SimpleLinearExprEq(ct_a.interval().size(), ct_b.interval().size()) &&
13372 SimpleLinearExprEq(ct_a.interval().end(), ct_b.interval().end());
13373}
13374
13375std::size_t CpModelPresolver::IntervalConstraintHash::operator()(
13376 int ct_idx) const {
13377 const ConstraintProto& ct = working_model->constraints(ct_idx);
13378 return absl::HashOf(absl::MakeSpan(ct.enforcement_literal()),
13379 LinearExpressionHash(ct.interval().start()),
13380 LinearExpressionHash(ct.interval().size()),
13381 LinearExpressionHash(ct.interval().end()));
13382}
13383
13384} // namespace sat
13385} // namespace operations_research
IntegerValue y
IntegerValue size
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:32
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
static Domain FromValues(std::vector< int64_t > values)
Domain IntersectionWith(const Domain &domain) const
Domain ContinuousMultiplicationBy(int64_t coeff) const
DomainIteratorBeginEnd Values() const &
Domain PositiveModuloBySuperset(const Domain &modulo) const
Domain AdditionWith(const Domain &domain) const
bool IsIncludedIn(const Domain &domain) const
Domain DivisionBy(int64_t coeff) const
Domain UnionWith(const Domain &domain) const
Domain InverseMultiplicationBy(int64_t coeff) const
static int64_t GCD64(int64_t x, int64_t y)
Definition mathutil.h:108
bool LoggingIsEnabled() const
Returns true iff logging is enabled.
Definition logging.h:49
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition bitset.h:878
void Set(IntegerType index)
Definition bitset.h:864
void AdvanceDeterministicTime(double deterministic_duration)
Definition time_limit.h:183
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
bool PresolveOneConstraint(int c)
Executes presolve method for the given constraint. Public for testing only.
void RemoveEmptyConstraints()
Public for testing only.
int NumDeductions() const
Returns the total number of "deductions" stored by this class.
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
void AddDeduction(int literal_ref, int var, Domain domain)
void AddMultiples(int64_t coeff, int64_t max_value)
Adds [0, coeff, 2 * coeff, ... max_value * coeff].
Definition util.cc:532
void ImportVariablesAndMaybeIgnoreNames(const CpModelProto &in_model)
void CreateVariablesFromDomains(const std::vector< Domain > &domains)
bool ImportAndSimplifyConstraints(const CpModelProto &in_model, bool first_copy=false, std::function< bool(int)> active_constraints=nullptr)
DomainDeductions deductions
Advanced presolve. See this class comment.
ABSL_MUST_USE_RESULT bool CanonicalizeObjective(bool simplify_domain=true)
bool CanonicalizeAffineVariable(int ref, int64_t coeff, int64_t mod, int64_t rhs)
bool StoreBooleanEqualityRelation(int ref_a, int ref_b)
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
bool ExpressionIsALiteral(const LinearExpressionProto &expr, int *literal=nullptr) const
Returns true iff the expr is a literal (x or not(x)).
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64_t value)
SparseBitset< int > modified_domains
Each time a domain is modified this is set to true.
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
ABSL_MUST_USE_RESULT bool SubstituteVariableInObjective(int var_in_equality, int64_t coeff_in_equality, const ConstraintProto &equality)
bool IntervalIsConstant(int ct_ref) const
Helper to query the state of an interval.
bool ShiftCostInExactlyOne(absl::Span< const int > exactly_one, int64_t shift)
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
Returns false if the 'lit' doesn't have the desired value in the domain.
absl::Span< const int > ConstraintToVars(int c) const
int NewIntVarWithDefinition(const Domain &domain, absl::Span< const std::pair< int, int64_t > > definition)
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(absl::string_view message="")
int GetOrCreateVarValueEncoding(int ref, int64_t value)
ABSL_MUST_USE_RESULT bool CanonicalizeOneObjectiveVariable(int var)
bool ExpressionIsAffineBoolean(const LinearExpressionProto &expr) const
bool VariableIsOnlyUsedInLinear1AndOneExtraConstraint(int var) const
bool VariableIsOnlyUsedInEncodingAndMaybeInObjective(int var) const
void CappedUpdateMinMaxActivity(int var, int64_t coeff, int64_t *min_activity, int64_t *max_activity)
Utility function.
std::pair< int64_t, int64_t > ComputeMinMaxActivity(const ProtoWithVarsAndCoeffs &proto) const
void LogInfo()
Logs stats to the logger.
int NewBoolVarWithClause(absl::Span< const int > clause)
bool HasVarValueEncoding(int ref, int64_t value, int *literal=nullptr)
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
Return a super-set of the domain of the linear expression.
void InitializeNewDomains()
Creates the internal structure for any new variables in working_model.
bool VariableIsNotUsedAnymore(int ref) const
Returns true if this ref no longer appears in the model.
bool VariableIsUnique(int ref) const
Returns true if this ref only appear in one constraint.
ConstraintProto * NewMappingConstraint(absl::string_view file, int line)
ABSL_MUST_USE_RESULT bool ScaleFloatingPointObjective()
bool StoreAffineRelation(int ref_x, int ref_y, int64_t coeff, int64_t offset, bool debug_no_recursion=false)
int GetLiteralRepresentative(int ref) const
Returns the representative of a literal.
int LiteralForExpressionMax(const LinearExpressionProto &expr) const
std::vector< int > tmp_literals
Temporary storage.
bool CanonicalizeLinearExpression(absl::Span< const int > enforcements, LinearExpressionProto *expr)
bool DomainOfVarIsIncludedIn(int var, const Domain &domain)
This function takes a positive variable reference.
void UpdateRuleStats(const std::string &name, int num_times=1)
AffineRelation::Relation GetAffineRelation(int ref) const
Returns the representative of ref under the affine relations.
const absl::flat_hash_set< int > & VarToConstraints(int var) const
bool InsertVarValueEncoding(int literal, int var, int64_t value)
void UpdateNewConstraintsVariableUsage()
Calls UpdateConstraintVariableUsage() on all newly created constraints.
const absl::flat_hash_map< int, int64_t > & ObjectiveMap() const
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64_t value)
bool ExploitExactlyOneInObjective(absl::Span< const int > exactly_one)
bool DomainContains(int ref, int64_t value) const
bool CanonicalizeLinearConstraint(ConstraintProto *ct)
int64_t a
Definition table.cc:44
Block * next
CpModelProto proto
The output proto.
int interval_index
const CpModelProto * working_model
bool ignore_enforcement
ConstraintProto objective_constraint
GraphType graph
const std::string name
A name for logging purposes.
const Constraint * ct
int64_t value
IntVar * var
GRBmodel * model
GurobiMPCallbackContext * context
bool is_equality
int lit
int literal
int ct_index
time_limit
Definition solve.cc:22
int index
int64_t hash
void Truncate(RepeatedPtrField< T > *array, int new_size)
RepeatedPtrField version.
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
void ApplyVariableMapping(const std::vector< int > &mapping, const PresolveContext &context)
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
constexpr uint64_t kDefaultFingerprintSeed
Default seed for fingerprints.
uint64_t FingerprintRepeatedField(const google::protobuf::RepeatedField< T > &sequence, uint64_t seed)
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:209
int64_t FloorSquareRoot(int64_t a)
The argument must be non-negative.
Definition util.cc:256
IntType CeilOfRatio(IntType numerator, IntType denominator)
Definition util.h:729
bool ImportModelAndDomainsWithBasicPresolveIntoContext(const CpModelProto &in_model, const std::vector< Domain > &domains, std::function< bool(int)> active_constraints, PresolveContext *context)
bool HasEnforcementLiteral(const ConstraintProto &ct)
Small utility functions to deal with half-reified constraints.
DiophantineSolution SolveDiophantine(absl::Span< const int64_t > coeffs, int64_t rhs, absl::Span< const int64_t > var_lbs, absl::Span< const int64_t > var_ubs)
bool ClauseIsEnforcementImpliesLiteral(absl::Span< const int > clause, absl::Span< const int > enforcement, int literal)
void ConstructOverlappingSets(bool already_sorted, std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *result)
void ApplyToAllIntervalIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
bool SafeAddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
Same method, but returns if the addition was possible without overflowing.
bool ImportModelWithBasicPresolveIntoContext(const CpModelProto &in_model, PresolveContext *context)
bool IsNegatableInt64(absl::int128 x)
Tells whether a int128 can be casted to a int64_t that can be negated.
Definition util.h:700
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
Convenient wrapper to call the full presolve.
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
void ProbeAndFindEquivalentLiteral(SatSolver *solver, SatPostsolver *postsolver, DratProofHandler *drat_proof_handler, util_intops::StrongVector< LiteralIndex, LiteralIndex > *mapping, SolverLogger *logger)
bool LookForTrivialSatSolution(double deterministic_time_limit, Model *model, SolverLogger *logger)
Definition probing.cc:418
void ScanModelForDualBoundStrengthening(const PresolveContext &context, DualBoundStrengthening *dual_bound_strengthening)
Scan the model so that dual_bound_strengthening.Strenghten() works.
bool AddLinearConstraintMultiple(int64_t factor, const ConstraintProto &to_add, ConstraintProto *to_modify)
constexpr int kAffineRelationConstraint
std::vector< int > UsedVariables(const ConstraintProto &ct)
IntegerValue PositiveRemainder(IntegerValue dividend, IntegerValue positive_divisor)
Definition integer.h:153
int64_t SafeDoubleToInt64(double value)
Definition util.h:688
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
int ReindexArcs(IntContainer *tails, IntContainer *heads, absl::flat_hash_map< int, int > *mapping_output=nullptr)
Definition circuit.h:210
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(absl::Span< const Rectangle > rectangles, absl::Span< int > active_rectangles)
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset)
void FinalExpansionForLinearConstraint(PresolveContext *context)
constexpr int kObjectiveConstraint
We use some special constraint index in our variable <-> constraint graph.
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
bool ExpressionsContainsOnlyOneVar(const ExpressionList &exprs)
Returns true if there exactly one variable appearing in all the expressions.
int64_t ClosestMultiple(int64_t value, int64_t base)
Definition util.cc:273
void SetupTextFormatPrinter(google::protobuf::TextFormat::Printer *printer)
void ScanModelForDominanceDetection(PresolveContext &context, VarDomination *var_domination)
void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(const CpModelProto &in_model, PresolveContext *context)
Copies the non constraint, non variables part of the model.
bool LinearsDifferAtOneTerm(const LinearConstraintProto &lin1, const LinearConstraintProto &lin2)
bool ExpressionContainsSingleRef(const LinearExpressionProto &expr)
Returns true if a linear expression can be reduced to a single ref.
int64_t LinearExpressionGcd(const LinearExpressionProto &expr, int64_t gcd)
void ApplyToAllLiteralIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
InclusionDetector(const Storage &storage) -> InclusionDetector< Storage >
Deduction guide.
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:280
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
void PropagateAutomaton(const AutomatonConstraintProto &proto, const PresolveContext &context, std::vector< absl::flat_hash_set< int64_t > > *states, std::vector< absl::flat_hash_set< int64_t > > *labels)
Fills and propagates the set of reachable states/labels.
void GetOverlappingIntervalComponents(std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *components)
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
bool LinearExpressionProtosAreEqual(const LinearExpressionProto &a, const LinearExpressionProto &b, int64_t b_scaling)
Returns true iff a == b * b_scaling.
bool PresolveFixed2dRectangles(absl::Span< const RectangleInRange > non_fixed_boxes, std::vector< Rectangle > *fixed_boxes)
void ApplyToAllVariableIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
In SWIG mode, we don't want anything besides these top-level includes.
bool AtMinOrMaxInt64(int64_t x)
Checks if x is equal to the min or the max value of an int64_t.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
std::string ProtobufShortDebugString(const P &message)
Definition proto_utils.h:41
int64_t CapProd(int64_t x, int64_t y)
absl::StatusOr< std::vector< int > > FastTopologicalSort(const AdjacencyLists &adj)
if(!yyg->yy_init)
Definition parser.yy.cc:965
ColIndex representative
const Variable x
Definition qp_tests.cc:127
int64_t demand
Definition resource.cc:126
int64_t time
Definition resource.cc:1708
IntervalVar * interval
Definition resource.cc:101
int head
int tail
Rev< int64_t > end_max
Rev< int64_t > start_min
std::optional< int64_t > end
int64_t start
const std::optional< Range > & range
Definition statistics.cc:37
void FindStronglyConnectedComponents(NodeIndex num_nodes, const Graph &graph, SccOutput *components)
Simple wrapper function for most usage.
std::string message
Definition trace.cc:397
#define SOLVER_LOG(logger,...)
Definition logging.h:109