Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model_presolve.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <array>
18#include <cstdint>
19#include <cstdlib>
20#include <deque>
21#include <functional>
22#include <limits>
23#include <memory>
24#include <numeric>
25#include <optional>
26#include <string>
27#include <string_view>
28#include <tuple>
29#include <utility>
30#include <vector>
31
32#include "absl/algorithm/container.h"
33#include "absl/base/attributes.h"
34#include "absl/container/btree_map.h"
35#include "absl/container/btree_set.h"
36#include "absl/container/flat_hash_map.h"
37#include "absl/container/flat_hash_set.h"
38#include "absl/flags/flag.h"
39#include "absl/hash/hash.h"
40#include "absl/log/check.h"
41#include "absl/log/log.h"
42#include "absl/log/vlog_is_on.h"
43#include "absl/meta/type_traits.h"
44#include "absl/numeric/int128.h"
45#include "absl/random/distributions.h"
46#include "absl/status/statusor.h"
47#include "absl/strings/str_cat.h"
48#include "absl/types/span.h"
49#include "google/protobuf/arena.h"
50#include "google/protobuf/repeated_field.h"
51#include "google/protobuf/repeated_ptr_field.h"
57#include "ortools/base/timer.h"
62#include "ortools/sat/circuit.h"
63#include "ortools/sat/clause.h"
64#include "ortools/sat/cp_model.pb.h"
74#include "ortools/sat/integer.h"
76#include "ortools/sat/model.h"
79#include "ortools/sat/probing.h"
82#include "ortools/sat/sat_parameters.pb.h"
86#include "ortools/sat/util.h"
89#include "ortools/util/bitset.h"
95
96namespace operations_research {
97namespace sat {
98
99namespace {
100
101LinearExpression2 GetLinearExpression2FromProto(int a, int64_t coeff_a, int b,
102 int64_t coeff_b) {
103 LinearExpression2 result;
104 DCHECK(RefIsPositive(a));
105 DCHECK(RefIsPositive(b));
106 result.vars[0] = IntegerVariable(2 * a);
107 result.vars[1] = IntegerVariable(2 * b);
108 result.coeffs[0] = IntegerValue(coeff_a);
109 result.coeffs[1] = IntegerValue(coeff_b);
110 return result;
111}
112
113// TODO(user): Just make sure this invariant is enforced in all our linear
114// constraint after copy, and simplify the code!
115bool LinearConstraintIsClean(const LinearConstraintProto& linear) {
116 const int num_vars = linear.vars().size();
117 for (int i = 0; i < num_vars; ++i) {
118 if (!RefIsPositive(linear.vars(i))) return false;
119 if (linear.coeffs(i) == 0) return false;
120 }
121 return true;
122}
123
124} // namespace
125
126bool CpModelPresolver::RemoveConstraint(ConstraintProto* ct) {
127 ct->Clear();
128 return true;
129}
130
131// Remove all empty constraints and duplicated intervals. Note that we need to
132// remap the interval references.
133//
134// Now that they have served their purpose, we also remove dummy constraints,
135// otherwise that causes issue because our model are invalid in tests.
137 interval_representative_.clear();
138 std::vector<int> interval_mapping(context_->working_model->constraints_size(),
139 -1);
140 int new_num_constraints = 0;
141 const int old_num_non_empty_constraints =
142 context_->working_model->constraints_size();
143 for (int c = 0; c < old_num_non_empty_constraints; ++c) {
144 const auto type = context_->working_model->constraints(c).constraint_case();
145 if (type == ConstraintProto::CONSTRAINT_NOT_SET) continue;
146 if (type == ConstraintProto::kDummyConstraint) continue;
147 context_->working_model->mutable_constraints(new_num_constraints)
148 ->Swap(context_->working_model->mutable_constraints(c));
149 if (type == ConstraintProto::kInterval) {
150 // Warning: interval_representative_ holds a pointer to the working model
151 // to compute hashes, so we need to be careful about not changing a
152 // constraint after its index is added to the map.
153 const auto [it, inserted] = interval_representative_.insert(
154 {new_num_constraints, new_num_constraints});
155 interval_mapping[c] = it->second;
156 if (it->second != new_num_constraints) {
157 context_->UpdateRuleStats(
158 "intervals: change duplicate index across constraints");
159 continue;
160 }
161 }
162 new_num_constraints++;
163 }
165 context_->working_model->mutable_constraints(), new_num_constraints);
166 for (ConstraintProto& ct_ref :
167 *context_->working_model->mutable_constraints()) {
169 [&interval_mapping](int* ref) {
170 *ref = interval_mapping[*ref];
171 CHECK_NE(-1, *ref);
172 },
173 &ct_ref);
174 }
175}
176
177bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto* ct) {
178 if (context_->ModelIsUnsat()) return false;
179 if (!HasEnforcementLiteral(*ct)) return false;
180
181 int new_size = 0;
182 const int old_size = ct->enforcement_literal().size();
183 context_->tmp_literal_set.clear();
184 for (const int literal : ct->enforcement_literal()) {
185 if (context_->LiteralIsTrue(literal)) {
186 // We can remove a literal at true.
187 context_->UpdateRuleStats("enforcement: true literal");
188 continue;
189 }
190
191 if (context_->LiteralIsFalse(literal)) {
192 context_->UpdateRuleStats("enforcement: false literal");
193 return RemoveConstraint(ct);
194 }
195
196 if (context_->VariableIsUniqueAndRemovable(literal)) {
197 // We can simply set it to false and ignore the constraint in this case.
198 context_->UpdateRuleStats("enforcement: literal not used");
199 CHECK(context_->SetLiteralToFalse(literal));
200 return RemoveConstraint(ct);
201 }
202
203 // If the literal only appear in the objective, we might be able to fix it
204 // to false. TODO(user): generalize if the literal always appear with the
205 // same polarity.
206 if (context_->VariableWithCostIsUniqueAndRemovable(literal)) {
207 const int64_t obj_coeff =
208 context_->ObjectiveMap().at(PositiveRef(literal));
209 if (RefIsPositive(literal) == (obj_coeff > 0)) {
210 // It is just more advantageous to set it to false!
211 context_->UpdateRuleStats("enforcement: literal with unique direction");
212 CHECK(context_->SetLiteralToFalse(literal));
213 return RemoveConstraint(ct);
214 }
215 }
216
217 // Deals with duplicate literals.
218 //
219 // TODO(user): Ideally we could do that just once during the first copy,
220 // and later never create such constraint.
221 if (old_size > 1) {
222 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
223 if (!inserted) {
224 context_->UpdateRuleStats("enforcement: removed duplicate literal");
225 continue;
226 }
227 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
228 context_->UpdateRuleStats("enforcement: can never be true");
229 return RemoveConstraint(ct);
230 }
231 }
232
233 ct->set_enforcement_literal(new_size++, literal);
234 }
235 ct->mutable_enforcement_literal()->Truncate(new_size);
236 return new_size != old_size;
237}
238
239bool CpModelPresolver::PresolveBoolXor(ConstraintProto* ct) {
240 if (context_->ModelIsUnsat()) return false;
241 if (HasEnforcementLiteral(*ct)) return false;
242
243 int new_size = 0;
244 bool changed = false;
245 int num_true_literals = 0;
246 int true_literal = std::numeric_limits<int32_t>::min();
247 for (const int literal : ct->bool_xor().literals()) {
248 // TODO(user): More generally, if a variable appear in only bool xor
249 // constraints, we can simply eliminate it using linear algebra on Z/2Z.
250 // This should solve in polynomial time the parity-learning*.fzn problems
251 // for instance. This seems low priority, but it is also easy to do. Even
252 // better would be to have a dedicated propagator with all bool_xor
253 // constraints that do the necessary linear algebra.
254 if (context_->VariableIsUniqueAndRemovable(literal)) {
255 context_->UpdateRuleStats("TODO bool_xor: remove constraint");
256 }
257
258 if (context_->LiteralIsFalse(literal)) {
259 context_->UpdateRuleStats("bool_xor: remove false literal");
260 changed = true;
261 continue;
262 } else if (context_->LiteralIsTrue(literal)) {
263 true_literal = literal; // Keep if we need to put one back.
264 num_true_literals++;
265 continue;
266 }
267
268 ct->mutable_bool_xor()->set_literals(new_size++, literal);
269 }
270
271 if (new_size == 0) {
272 if (num_true_literals % 2 == 0) {
273 return context_->NotifyThatModelIsUnsat("bool_xor: always false");
274 } else {
275 context_->UpdateRuleStats("bool_xor: always true");
276 return RemoveConstraint(ct);
277 }
278 } else if (new_size == 1) { // We can fix the only active literal.
279 if (num_true_literals % 2 == 0) {
280 if (!context_->SetLiteralToTrue(ct->bool_xor().literals(0))) {
281 return context_->NotifyThatModelIsUnsat(
282 "bool_xor: cannot fix last literal");
283 }
284 } else {
285 if (!context_->SetLiteralToFalse(ct->bool_xor().literals(0))) {
286 return context_->NotifyThatModelIsUnsat(
287 "bool_xor: cannot fix last literal");
288 }
289 }
290 context_->UpdateRuleStats("bool_xor: one active literal");
291 return RemoveConstraint(ct);
292 } else if (new_size == 2) { // We can simplify the bool_xor.
293 const int a = ct->bool_xor().literals(0);
294 const int b = ct->bool_xor().literals(1);
295 if (a == b) {
296 if (num_true_literals % 2 == 0) {
297 return context_->NotifyThatModelIsUnsat("bool_xor: always false");
298 } else {
299 context_->UpdateRuleStats("bool_xor: always true");
300 return RemoveConstraint(ct);
301 }
302 }
303 if (a == NegatedRef(b)) {
304 if (num_true_literals % 2 == 1) {
305 return context_->NotifyThatModelIsUnsat("bool_xor: always false");
306 } else {
307 context_->UpdateRuleStats("bool_xor: always true");
308 return RemoveConstraint(ct);
309 }
310 }
311 if (num_true_literals % 2 == 0) { // a == not(b).
312 if (!context_->StoreBooleanEqualityRelation(a, NegatedRef(b))) {
313 return false;
314 }
315 } else { // a == b.
316 if (!context_->StoreBooleanEqualityRelation(a, b)) {
317 return false;
318 }
319 }
320 context_->UpdateNewConstraintsVariableUsage();
321 context_->UpdateRuleStats("bool_xor: two active literals");
322 return RemoveConstraint(ct);
323 }
324
325 if (num_true_literals % 2 == 1) {
326 CHECK_NE(true_literal, std::numeric_limits<int32_t>::min());
327 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
328 }
329 if (num_true_literals > 1) {
330 context_->UpdateRuleStats("bool_xor: remove even number of true literals");
331 changed = true;
332 }
333 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
334 return changed;
335}
336
337bool CpModelPresolver::PresolveBoolOr(ConstraintProto* ct) {
338 if (context_->ModelIsUnsat()) return false;
339
340 // Move the enforcement literal inside the clause if any. Note that we do not
341 // mark this as a change since the literal in the constraint are the same.
342 if (HasEnforcementLiteral(*ct)) {
343 context_->UpdateRuleStats("bool_or: removed enforcement literal");
344 for (const int literal : ct->enforcement_literal()) {
345 ct->mutable_bool_or()->add_literals(NegatedRef(literal));
346 }
347 ct->clear_enforcement_literal();
348 }
349
350 // Inspects the literals and deal with fixed ones.
351 //
352 // TODO(user): Because we remove literal on the first copy, maybe we can get
353 // rid of the set here. However we still need to be careful when remapping
354 // literals to their representatives.
355 bool changed = false;
356 context_->tmp_literals.clear();
357 context_->tmp_literal_set.clear();
358 for (const int literal : ct->bool_or().literals()) {
359 if (context_->LiteralIsFalse(literal)) {
360 changed = true;
361 continue;
362 }
363 if (context_->LiteralIsTrue(literal)) {
364 context_->UpdateRuleStats("bool_or: always true");
365 return RemoveConstraint(ct);
366 }
367 // We can just set the variable to true in this case since it is not
368 // used in any other constraint (note that we artificially bump the
369 // objective var usage by 1).
370 if (context_->VariableIsUniqueAndRemovable(literal)) {
371 context_->UpdateRuleStats("bool_or: singleton");
372 if (!context_->SetLiteralToTrue(literal)) return true;
373 return RemoveConstraint(ct);
374 }
375 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
376 context_->UpdateRuleStats("bool_or: always true");
377 return RemoveConstraint(ct);
378 }
379
380 if (context_->tmp_literal_set.contains(literal)) {
381 changed = true;
382 } else {
383 context_->tmp_literal_set.insert(literal);
384 context_->tmp_literals.push_back(literal);
385 }
386 }
387 context_->tmp_literal_set.clear();
388
389 if (context_->tmp_literals.empty()) {
390 context_->UpdateRuleStats("bool_or: empty");
391 return context_->NotifyThatModelIsUnsat();
392 }
393 if (context_->tmp_literals.size() == 1) {
394 context_->UpdateRuleStats("bool_or: only one literal");
395 if (!context_->SetLiteralToTrue(context_->tmp_literals[0])) return true;
396 return RemoveConstraint(ct);
397 }
398 if (context_->tmp_literals.size() == 2) {
399 // For consistency, we move all "implication" into half-reified bool_and.
400 // TODO(user): merge by enforcement literal and detect implication cycles.
401 context_->UpdateRuleStats("bool_or: implications");
402 ct->add_enforcement_literal(NegatedRef(context_->tmp_literals[0]));
403 ct->mutable_bool_and()->add_literals(context_->tmp_literals[1]);
404 return changed;
405 }
406
407 if (changed) {
408 context_->UpdateRuleStats("bool_or: fixed literals");
409 ct->mutable_bool_or()->mutable_literals()->Clear();
410 for (const int lit : context_->tmp_literals) {
411 ct->mutable_bool_or()->add_literals(lit);
412 }
413 }
414 return changed;
415}
416
417// Note this function does not update the constraint graph. It assumes this is
418// done elsewhere.
419ABSL_MUST_USE_RESULT bool CpModelPresolver::MarkConstraintAsFalse(
420 ConstraintProto* ct) {
421 if (HasEnforcementLiteral(*ct)) {
422 // Change the constraint to a bool_or.
423 ct->mutable_bool_or()->clear_literals();
424 for (const int lit : ct->enforcement_literal()) {
425 ct->mutable_bool_or()->add_literals(NegatedRef(lit));
426 }
427 ct->clear_enforcement_literal();
428 PresolveBoolOr(ct);
429 return true;
430 } else {
431 return context_->NotifyThatModelIsUnsat();
432 }
433}
434
435bool CpModelPresolver::PresolveBoolAnd(ConstraintProto* ct) {
436 if (context_->ModelIsUnsat()) return false;
437
438 if (!HasEnforcementLiteral(*ct)) {
439 context_->UpdateRuleStats("bool_and: non-reified.");
440 for (const int literal : ct->bool_and().literals()) {
441 if (!context_->SetLiteralToTrue(literal)) return true;
442 }
443 return RemoveConstraint(ct);
444 }
445
446 bool changed = false;
447 context_->tmp_literals.clear();
448 context_->tmp_literal_set.clear();
449 const absl::flat_hash_set<int> enforcement_literals_set(
450 ct->enforcement_literal().begin(), ct->enforcement_literal().end());
451 for (const int literal : ct->bool_and().literals()) {
452 if (context_->LiteralIsFalse(literal)) {
453 context_->UpdateRuleStats("bool_and: always false");
454 return MarkConstraintAsFalse(ct);
455 }
456 if (context_->LiteralIsTrue(literal)) {
457 changed = true;
458 continue;
459 }
460 if (enforcement_literals_set.contains(literal)) {
461 context_->UpdateRuleStats("bool_and: x => x");
462 changed = true;
463 continue;
464 }
465 if (enforcement_literals_set.contains(NegatedRef(literal))) {
466 context_->UpdateRuleStats("bool_and: x => not x");
467 return MarkConstraintAsFalse(ct);
468 }
469 if (context_->VariableIsUniqueAndRemovable(literal)) {
470 // This is a "dual" reduction.
471 changed = true;
472 context_->UpdateRuleStats(
473 "bool_and: setting unused literal in rhs to true");
474 if (!context_->SetLiteralToTrue(literal)) return true;
475 continue;
476 }
477
478 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
479 context_->UpdateRuleStats("bool_and: cannot be enforced");
480 return MarkConstraintAsFalse(ct);
481 }
482
483 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
484 if (inserted) {
485 context_->tmp_literals.push_back(literal);
486 } else {
487 changed = true;
488 context_->UpdateRuleStats("bool_and: removed duplicate literal");
489 }
490 }
491
492 // Note that this is not the same behavior as a bool_or:
493 // - bool_or means "at least one", so it is false if empty.
494 // - bool_and means "all literals inside true", so it is true if empty.
495 if (context_->tmp_literals.empty()) return RemoveConstraint(ct);
496
497 if (changed) {
498 ct->mutable_bool_and()->mutable_literals()->Clear();
499 for (const int lit : context_->tmp_literals) {
500 ct->mutable_bool_and()->add_literals(lit);
501 }
502 context_->UpdateRuleStats("bool_and: fixed literals");
503 }
504
505 // If a variable can move freely in one direction except for this constraint,
506 // we can make it an equality.
507 //
508 // TODO(user): also consider literal on the other side of the =>.
509 if (ct->enforcement_literal().size() == 1 &&
510 ct->bool_and().literals().size() == 1) {
511 const int enforcement = ct->enforcement_literal(0);
512 if (context_->VariableWithCostIsUniqueAndRemovable(enforcement)) {
513 int var = PositiveRef(enforcement);
514 int64_t obj_coeff = context_->ObjectiveMap().at(var);
515 if (!RefIsPositive(enforcement)) obj_coeff = -obj_coeff;
516
517 // The other case where the constraint is redundant is treated elsewhere.
518 if (obj_coeff < 0) {
519 context_->UpdateRuleStats("bool_and: dual equality.");
520 // Extending `ct` = "enforcement => implied_literal" to an equality can
521 // break the hint only if hint(implied_literal) = 1 and
522 // hint(enforcement) = 0. But in this case the `enforcement` hint can be
523 // increased to 1 to preserve the hint feasibility.
524 const int implied_literal = ct->bool_and().literals(0);
525 solution_crush_.SetLiteralToValueIf(enforcement, true, implied_literal);
526 if (!context_->StoreBooleanEqualityRelation(enforcement,
527 implied_literal)) {
528 return false;
529 }
530 }
531 }
532 }
533
534 return changed;
535}
536
537bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto* ct) {
538 bool is_at_most_one = ct->constraint_case() == ConstraintProto::kAtMostOne;
539 const std::string name = is_at_most_one ? "at_most_one: " : "exactly_one: ";
540 auto* literals = is_at_most_one
541 ? ct->mutable_at_most_one()->mutable_literals()
542 : ct->mutable_exactly_one()->mutable_literals();
543
544 // Having a canonical constraint is needed for duplicate detection.
545 // This also change how we regroup bool_and.
546 std::sort(literals->begin(), literals->end());
547
548 // Deal with duplicate variable reference.
549 context_->tmp_literal_set.clear();
550 for (const int literal : *literals) {
551 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
552 if (!inserted) {
553 if (!context_->SetLiteralToFalse(literal)) return false;
554 context_->UpdateRuleStats(absl::StrCat(name, "duplicate literals"));
555 }
556 if (context_->tmp_literal_set.contains(NegatedRef(literal))) {
557 int num_positive = 0;
558 int num_negative = 0;
559 for (const int other : *literals) {
560 if (PositiveRef(other) != PositiveRef(literal)) {
561 if (!context_->SetLiteralToFalse(other)) return false;
562 context_->UpdateRuleStats(absl::StrCat(name, "x and not(x)"));
563 } else {
564 if (other == literal) {
565 ++num_positive;
566 } else {
567 ++num_negative;
568 }
569 }
570 }
571
572 // This is tricky for the case where the at most one reduce to (lit,
573 // not(lit), not(lit)) for instance.
574 if (num_positive > 1 && !context_->SetLiteralToFalse(literal)) {
575 return false;
576 }
577 if (num_negative > 1 && !context_->SetLiteralToTrue(literal)) {
578 return false;
579 }
580 return RemoveConstraint(ct);
581 }
582 }
583
584 // We can always remove all singleton variables (with or without cost) in an
585 // at_most_one or exactly one. We collect them and deal with this at the end.
586 std::vector<std::pair<int, int64_t>> singleton_literal_with_cost;
587
588 // Remove fixed variables.
589 bool changed = false;
590 context_->tmp_literals.clear();
591 for (const int literal : *literals) {
592 if (context_->LiteralIsTrue(literal)) {
593 context_->UpdateRuleStats(absl::StrCat(name, "satisfied"));
594 for (const int other : *literals) {
595 if (other != literal) {
596 if (!context_->SetLiteralToFalse(other)) return false;
597 }
598 }
599 return RemoveConstraint(ct);
600 }
601
602 if (context_->LiteralIsFalse(literal)) {
603 changed = true;
604 continue;
605 }
606
607 // A singleton variable with or without cost can be removed. See below.
608 if (context_->VariableIsUniqueAndRemovable(literal)) {
609 // A variable that doesn't appear in the objective can be seen as
610 // appearing with a coefficient of zero.
611 singleton_literal_with_cost.push_back({literal, 0});
612 continue;
613 }
614 if (context_->VariableWithCostIsUniqueAndRemovable(literal)) {
615 const auto it = context_->ObjectiveMap().find(PositiveRef(literal));
616 DCHECK(it != context_->ObjectiveMap().end());
617 if (RefIsPositive(literal)) {
618 singleton_literal_with_cost.push_back({literal, it->second});
619 } else {
620 // Note that we actually just store the objective change if this literal
621 // is true compared to it being false.
622 singleton_literal_with_cost.push_back({literal, -it->second});
623 }
624 continue;
625 }
626
627 context_->tmp_literals.push_back(literal);
628 }
629
630 bool transform_to_at_most_one = false;
631 if (!singleton_literal_with_cost.empty()) {
632 changed = true;
633
634 // By domination argument, we can fix to false everything but the minimum.
635 if (singleton_literal_with_cost.size() > 1) {
636 std::stable_sort(
637 singleton_literal_with_cost.begin(),
638 singleton_literal_with_cost.end(),
639 [](const std::pair<int, int64_t>& a,
640 const std::pair<int, int64_t>& b) { return a.second < b.second; });
641 for (int i = 1; i < singleton_literal_with_cost.size(); ++i) {
642 context_->UpdateRuleStats("at_most_one: dominated singleton");
643 if (!context_->SetLiteralToFalse(
644 singleton_literal_with_cost[i].first)) {
645 return false;
646 }
647 }
648 singleton_literal_with_cost.resize(1);
649 }
650
651 const int literal = singleton_literal_with_cost[0].first;
652 const int64_t literal_cost = singleton_literal_with_cost[0].second;
653 if (is_at_most_one && literal_cost >= 0) {
654 // We can just always set it to false in this case.
655 context_->UpdateRuleStats("at_most_one: singleton");
656 if (!context_->SetLiteralToFalse(literal)) return false;
657 } else if (context_->ShiftCostInExactlyOne(*literals, literal_cost)) {
658 // We can make the constraint an exactly one if needed since it is always
659 // beneficial to set this literal to true if everything else is zero. Now
660 // that we have an exactly one, we can transfer the cost to the other
661 // terms. The objective of literal should become zero, and we can then
662 // decide its value at postsolve and just have an at most one on the other
663 // literals.
664 DCHECK(!context_->ObjectiveMap().contains(PositiveRef(literal)));
665
666 if (!is_at_most_one) transform_to_at_most_one = true;
667 is_at_most_one = true;
668
669 context_->UpdateRuleStats("exactly_one: singleton");
670 context_->MarkVariableAsRemoved(PositiveRef(literal));
671
672 // Put a constraint in the mapping proto for postsolve.
673 auto* mapping_exo = context_->NewMappingConstraint(__FILE__, __LINE__)
674 ->mutable_exactly_one();
675 for (const int lit : context_->tmp_literals) {
676 mapping_exo->add_literals(lit);
677 }
678 mapping_exo->add_literals(literal);
679 } else {
680 // If ShiftCostInExactlyOne() failed, keep the literal in the amo.
681 context_->tmp_literals.push_back(literal);
682 }
683 }
684
685 if (!is_at_most_one && !transform_to_at_most_one &&
686 context_->ExploitExactlyOneInObjective(context_->tmp_literals)) {
687 context_->UpdateRuleStats("exactly_one: simplified objective");
688 }
689
690 if (transform_to_at_most_one) {
691 CHECK(changed);
692 ct->Clear();
693 literals = ct->mutable_at_most_one()->mutable_literals();
694 }
695 if (changed) {
696 literals->Clear();
697 for (const int lit : context_->tmp_literals) {
698 literals->Add(lit);
699 }
700 context_->UpdateRuleStats(absl::StrCat(name, "removed literals"));
701 }
702 return changed;
703}
704
705bool CpModelPresolver::PresolveAtMostOne(ConstraintProto* ct) {
706 if (context_->ModelIsUnsat()) return false;
707
708 CHECK(!HasEnforcementLiteral(*ct));
709 const bool changed = PresolveAtMostOrExactlyOne(ct);
710 if (ct->constraint_case() != ConstraintProto::kAtMostOne) return changed;
711
712 // Size zero: ok.
713 const auto& literals = ct->at_most_one().literals();
714 if (literals.empty()) {
715 context_->UpdateRuleStats("at_most_one: empty or all false");
716 return RemoveConstraint(ct);
717 }
718
719 // Size one: always satisfied.
720 if (literals.size() == 1) {
721 context_->UpdateRuleStats("at_most_one: size one");
722 return RemoveConstraint(ct);
723 }
724
725 return changed;
726}
727
728bool CpModelPresolver::PresolveExactlyOne(ConstraintProto* ct) {
729 if (context_->ModelIsUnsat()) return false;
730 CHECK(!HasEnforcementLiteral(*ct));
731 const bool changed = PresolveAtMostOrExactlyOne(ct);
732 if (ct->constraint_case() != ConstraintProto::kExactlyOne) return changed;
733
734 // Size zero: UNSAT.
735 const auto& literals = ct->exactly_one().literals();
736 if (literals.empty()) {
737 return context_->NotifyThatModelIsUnsat("exactly_one: empty or all false");
738 }
739
740 // Size one: fix variable.
741 if (literals.size() == 1) {
742 context_->UpdateRuleStats("exactly_one: size one");
743 if (!context_->SetLiteralToTrue(literals[0])) return false;
744 return RemoveConstraint(ct);
745 }
746
747 // Size two: Equivalence.
748 if (literals.size() == 2) {
749 context_->UpdateRuleStats("exactly_one: size two");
750 if (!context_->StoreBooleanEqualityRelation(literals[0],
751 NegatedRef(literals[1]))) {
752 return false;
753 }
754 return RemoveConstraint(ct);
755 }
756
757 return changed;
758}
759
760bool CpModelPresolver::CanonicalizeLinearArgument(const ConstraintProto& ct,
761 LinearArgumentProto* proto) {
762 if (context_->ModelIsUnsat()) return false;
763
764 // Canonicalize all involved expression.
765 bool changed = CanonicalizeLinearExpression(ct, proto->mutable_target());
766 for (LinearExpressionProto& exp : *(proto->mutable_exprs())) {
767 changed |= CanonicalizeLinearExpression(ct, &exp);
768 }
769 return changed;
770}
771
772// Deal with X = lin_max(exprs) where all exprs are divisible by gcd.
773// X must be divisible also, and we can divide everything.
774bool CpModelPresolver::DivideLinMaxByGcd(int c, ConstraintProto* ct) {
775 LinearArgumentProto* lin_max = ct->mutable_lin_max();
776
777 // Compute gcd of exprs first.
778 int64_t gcd = 0;
779 for (const LinearExpressionProto& expr : lin_max->exprs()) {
780 gcd = LinearExpressionGcd(expr, gcd);
781 if (gcd == 1) break;
782 }
783 if (gcd <= 1) return true;
784
785 // TODO(user): deal with all UNSAT case.
786 // Also if the target is affine, we can canonicalize it.
787 const LinearExpressionProto& target = lin_max->target();
788 const int64_t old_gcd = gcd;
789 gcd = LinearExpressionGcd(target, gcd);
790 if (gcd != old_gcd) {
791 if (target.vars().empty()) {
792 return context_->NotifyThatModelIsUnsat("infeasible lin_max");
793 }
794
795 // If the target is affine, we can solve the diophantine equation and
796 // express the target in term of a new variable.
797 if (target.vars().size() == 1) {
798 gcd = old_gcd;
799 context_->UpdateRuleStats("lin_max: canonicalize target using gcd");
800 if (!context_->CanonicalizeAffineVariable(
801 target.vars(0), target.coeffs(0), gcd, -target.offset())) {
802 return false;
803 }
804 CanonicalizeLinearExpression(*ct, lin_max->mutable_target());
805 context_->UpdateConstraintVariableUsage(c);
806 CHECK_EQ(LinearExpressionGcd(target, gcd), gcd);
807 } else {
808 context_->UpdateRuleStats(
809 "TODO lin_max: lhs not trivially divisible by rhs gcd");
810 }
811 }
812 if (gcd <= 1) return true;
813
814 context_->UpdateRuleStats("lin_max: divising by gcd");
815 DivideLinearExpression(gcd, lin_max->mutable_target());
816 for (LinearExpressionProto& expr : *lin_max->mutable_exprs()) {
817 DivideLinearExpression(gcd, &expr);
818 }
819 return true;
820}
821
822namespace {
823
824int64_t EvaluateSingleVariableExpression(const LinearExpressionProto& expr,
825 int var, int64_t value) {
826 int64_t result = expr.offset();
827 for (int i = 0; i < expr.vars().size(); ++i) {
828 CHECK_EQ(expr.vars(i), var);
829 result += expr.coeffs(i) * value;
830 }
831 return result;
832}
833
834template <class ExpressionList>
835int GetFirstVar(ExpressionList exprs) {
836 for (const LinearExpressionProto& expr : exprs) {
837 for (const int var : expr.vars()) {
838 DCHECK(RefIsPositive(var));
839 return var;
840 }
841 }
842 return -1;
843}
844
845bool IsAffineIntAbs(const ConstraintProto& ct) {
846 if (ct.constraint_case() != ConstraintProto::kLinMax ||
847 ct.lin_max().exprs_size() != 2 || ct.lin_max().target().vars_size() > 1 ||
848 ct.lin_max().exprs(0).vars_size() != 1 ||
849 ct.lin_max().exprs(1).vars_size() != 1) {
850 return false;
851 }
852
853 const LinearArgumentProto& lin_max = ct.lin_max();
854 if (lin_max.exprs(0).offset() != -lin_max.exprs(1).offset()) return false;
855 if (PositiveRef(lin_max.exprs(0).vars(0)) !=
856 PositiveRef(lin_max.exprs(1).vars(0))) {
857 return false;
858 }
859
860 const int64_t left_coeff = RefIsPositive(lin_max.exprs(0).vars(0))
861 ? lin_max.exprs(0).coeffs(0)
862 : -lin_max.exprs(0).coeffs(0);
863 const int64_t right_coeff = RefIsPositive(lin_max.exprs(1).vars(0))
864 ? lin_max.exprs(1).coeffs(0)
865 : -lin_max.exprs(1).coeffs(0);
866 return left_coeff == -right_coeff;
867}
868
869} // namespace
870
871bool CpModelPresolver::PropagateAndReduceAffineMax(ConstraintProto* ct) {
872 // Get the unique variable appearing in the expressions.
873 const int unique_var = GetFirstVar(ct->lin_max().exprs());
874
875 const auto& lin_max = ct->lin_max();
876 const int num_exprs = lin_max.exprs_size();
877 const auto& target = lin_max.target();
878 std::vector<int> num_wins(num_exprs, 0);
879 std::vector<int64_t> reachable_target_values;
880 std::vector<int64_t> valid_variable_values;
881 std::vector<int64_t> tmp_values(num_exprs);
882
883 const bool target_has_same_unique_var =
884 target.vars_size() == 1 && target.vars(0) == unique_var;
885
886 CHECK_LE(context_->DomainOf(unique_var).Size(), 1000);
887
888 for (const int64_t value : context_->DomainOf(unique_var).Values()) {
889 int64_t current_max = std::numeric_limits<int64_t>::min();
890
891 // Fill tmp_values and compute current_max;
892 for (int i = 0; i < num_exprs; ++i) {
893 const int64_t v =
894 EvaluateSingleVariableExpression(lin_max.exprs(i), unique_var, value);
895 current_max = std::max(current_max, v);
896 tmp_values[i] = v;
897 }
898
899 // Check if any expr produced a value compatible with the target.
900 if (!context_->DomainContains(target, current_max)) continue;
901
902 // Special case: affine(x) == max(exprs(x)). We can check if the affine()
903 // and the max(exprs) are compatible.
904 if (target_has_same_unique_var &&
905 EvaluateSingleVariableExpression(target, unique_var, value) !=
906 current_max) {
907 continue;
908 }
909
910 valid_variable_values.push_back(value);
911 reachable_target_values.push_back(current_max);
912 for (int i = 0; i < num_exprs; ++i) {
913 DCHECK_LE(tmp_values[i], current_max);
914 if (tmp_values[i] == current_max) {
915 num_wins[i]++;
916 }
917 }
918 }
919
920 if (reachable_target_values.empty() || valid_variable_values.empty()) {
921 context_->UpdateRuleStats("lin_max: infeasible affine_max constraint");
922 return MarkConstraintAsFalse(ct);
923 }
924
925 {
926 bool reduced = false;
927 if (!context_->IntersectDomainWith(
928 target, Domain::FromValues(reachable_target_values), &reduced)) {
929 return true;
930 }
931 if (reduced) {
932 context_->UpdateRuleStats("lin_max: affine_max target domain reduced");
933 }
934 }
935
936 {
937 bool reduced = false;
938 if (!context_->IntersectDomainWith(
939 unique_var, Domain::FromValues(valid_variable_values), &reduced)) {
940 return true;
941 }
942 if (reduced) {
943 context_->UpdateRuleStats(
944 "lin_max: unique affine_max var domain reduced");
945 }
946 }
947
948 // If one expression always wins, even tied, we can eliminate all the others.
949 for (int i = 0; i < num_exprs; ++i) {
950 if (num_wins[i] == valid_variable_values.size()) {
951 const LinearExpressionProto winner_expr = lin_max.exprs(i);
952 ct->mutable_lin_max()->clear_exprs();
953 *ct->mutable_lin_max()->add_exprs() = winner_expr;
954 break;
955 }
956 }
957
958 bool changed = false;
959 if (ct->lin_max().exprs_size() > 1) {
960 int new_size = 0;
961 for (int i = 0; i < num_exprs; ++i) {
962 if (num_wins[i] == 0) continue;
963 *ct->mutable_lin_max()->mutable_exprs(new_size) = ct->lin_max().exprs(i);
964 new_size++;
965 }
966 if (new_size < ct->lin_max().exprs_size()) {
967 context_->UpdateRuleStats("lin_max: removed affine_max exprs");
968 google::protobuf::util::Truncate(ct->mutable_lin_max()->mutable_exprs(),
969 new_size);
970 changed = true;
971 }
972 }
973
974 if (context_->IsFixed(target)) {
975 context_->UpdateRuleStats("lin_max: fixed affine_max target");
976 return RemoveConstraint(ct);
977 }
978
979 if (target_has_same_unique_var) {
980 context_->UpdateRuleStats("lin_max: target_affine(x) = max(affine_i(x))");
981 return RemoveConstraint(ct);
982 }
983
984 // Remove the affine_max constraint if the target is removable and if domains
985 // have been propagated without loss. For now, we known that there is no loss
986 // if the target is a single ref. Since all the expression are affine, in this
987 // case we are fine.
988 if (ExpressionContainsSingleRef(target) &&
989 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
990 context_->MarkVariableAsRemoved(target.vars(0));
991 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
992 context_->UpdateRuleStats("lin_max: unused affine_max target");
993 return RemoveConstraint(ct);
994 }
995
996 return changed;
997}
998
999bool CpModelPresolver::PropagateAndReduceLinMax(ConstraintProto* ct) {
1000 const LinearExpressionProto& target = ct->lin_max().target();
1001
1002 // Compute the infered min/max of the target.
1003 // Update target domain (if it is not a complex expression).
1004 {
1005 int64_t infered_min = context_->MinOf(target);
1006 int64_t infered_max = std::numeric_limits<int64_t>::min();
1007 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1008 infered_min = std::max(infered_min, context_->MinOf(expr));
1009 infered_max = std::max(infered_max, context_->MaxOf(expr));
1010 }
1011
1012 if (target.vars().empty()) {
1013 if (!Domain(infered_min, infered_max).Contains(target.offset())) {
1014 context_->UpdateRuleStats("lin_max: infeasible");
1015 return MarkConstraintAsFalse(ct);
1016 }
1017 }
1018 if (target.vars().size() <= 1) { // Affine
1019 Domain rhs_domain;
1020 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1021 rhs_domain = rhs_domain.UnionWith(
1022 context_->DomainSuperSetOf(expr).IntersectionWith(
1023 {infered_min, infered_max}));
1024 }
1025 bool reduced = false;
1026 if (!context_->IntersectDomainWith(target, rhs_domain, &reduced)) {
1027 return true;
1028 }
1029 if (reduced) {
1030 context_->UpdateRuleStats("lin_max: target domain reduced");
1031 }
1032 }
1033 }
1034
1035 // Filter the expressions which are smaller than target_min.
1036 const int64_t target_min = context_->MinOf(target);
1037 bool changed = false;
1038 {
1039 // If one expression is >= target_min,
1040 // We can remove all the expression <= target min.
1041 //
1042 // Note that we must keep an expression >= target_min though, for corner
1043 // case like [2,3] = max([2], [0][3]);
1044 bool has_greater_or_equal_to_target_min = false;
1045 int64_t max_at_index_to_keep = std::numeric_limits<int64_t>::min();
1046 int index_to_keep = -1;
1047 for (int i = 0; i < ct->lin_max().exprs_size(); ++i) {
1048 const LinearExpressionProto& expr = ct->lin_max().exprs(i);
1049 if (context_->MinOf(expr) >= target_min) {
1050 const int64_t expr_max = context_->MaxOf(expr);
1051 if (expr_max > max_at_index_to_keep) {
1052 max_at_index_to_keep = expr_max;
1053 index_to_keep = i;
1054 }
1055 has_greater_or_equal_to_target_min = true;
1056 }
1057 }
1058
1059 int new_size = 0;
1060 for (int i = 0; i < ct->lin_max().exprs_size(); ++i) {
1061 const LinearExpressionProto& expr = ct->lin_max().exprs(i);
1062 const int64_t expr_max = context_->MaxOf(expr);
1063 // TODO(user): Also remove expression whose domain is incompatible with
1064 // the target even if the bounds are like [2] and [0][3]?
1065 if (expr_max < target_min) continue;
1066 if (expr_max == target_min && has_greater_or_equal_to_target_min &&
1067 i != index_to_keep) {
1068 continue;
1069 }
1070 *ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
1071 new_size++;
1072 }
1073 if (new_size < ct->lin_max().exprs_size()) {
1074 context_->UpdateRuleStats("lin_max: removed exprs");
1075 google::protobuf::util::Truncate(ct->mutable_lin_max()->mutable_exprs(),
1076 new_size);
1077 changed = true;
1078 }
1079 }
1080
1081 return changed;
1082}
1083
1084bool CpModelPresolver::PresolveLinMax(int c, ConstraintProto* ct) {
1085 if (context_->ModelIsUnsat()) return false;
1086 if (HasEnforcementLiteral(*ct)) return false;
1087 const LinearExpressionProto& target = ct->lin_max().target();
1088
1089 // x = max(x, xi...) => forall i, x >= xi.
1090 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1091 if (LinearExpressionProtosAreEqual(expr, target)) {
1092 for (const LinearExpressionProto& e : ct->lin_max().exprs()) {
1093 if (LinearExpressionProtosAreEqual(e, target)) continue;
1094 LinearConstraintProto* prec =
1095 context_->working_model->add_constraints()->mutable_linear();
1096 prec->add_domain(0);
1097 prec->add_domain(std::numeric_limits<int64_t>::max());
1100 }
1101 context_->UpdateRuleStats("lin_max: x = max(x, ...)");
1102 return RemoveConstraint(ct);
1103 }
1104 }
1105
1106 const bool is_one_var_affine_max =
1107 ExpressionsContainsOnlyOneVar(ct->lin_max().exprs()) &&
1108 ct->lin_max().target().vars_size() <= 1;
1109 bool unique_var_is_small_enough = false;
1110 const bool is_int_abs = IsAffineIntAbs(*ct);
1111
1112 if (is_one_var_affine_max) {
1113 const int unique_var = GetFirstVar(ct->lin_max().exprs());
1114 unique_var_is_small_enough = context_->DomainOf(unique_var).Size() <= 1000;
1115 }
1116
1117 bool changed;
1118 if (is_one_var_affine_max && unique_var_is_small_enough) {
1119 changed = PropagateAndReduceAffineMax(ct);
1120 } else if (is_int_abs) {
1121 changed = PropagateAndReduceIntAbs(ct);
1122 } else {
1123 changed = PropagateAndReduceLinMax(ct);
1124 }
1125
1126 if (context_->ModelIsUnsat()) return false;
1127 if (ct->constraint_case() != ConstraintProto::kLinMax) {
1128 // The constraint was removed by the propagate helpers.
1129 return changed;
1130 }
1131
1132 if (ct->lin_max().exprs().empty()) {
1133 context_->UpdateRuleStats("lin_max: no exprs");
1134 return MarkConstraintAsFalse(ct);
1135 }
1136
1137 // Try to reduce lin_max using known relation.
1138 if (ct->lin_max().exprs().size() < 10) {
1139 const int num_exprs = ct->lin_max().exprs().size();
1140
1141 bool simplified = false;
1142 std::vector<bool> can_be_removed(num_exprs, false);
1143 for (int i = 0; i < num_exprs; ++i) {
1144 if (ct->lin_max().exprs(i).vars().size() != 1) continue;
1145 for (int j = 0; j < num_exprs; ++j) {
1146 if (i == j) continue;
1147 if (can_be_removed[j]) continue;
1148
1149 // Note that we skip constant expressions as this should already be
1150 // handled when we compute the domain of each expression and remove
1151 // the ones that are smaller than the target.
1152 if (ct->lin_max().exprs(j).vars().size() != 1) continue;
1153
1154 // Do we know if expr(i) <= expr(j) ?
1155 const LinearExpression2 expr2 = GetLinearExpression2FromProto(
1156 ct->lin_max().exprs(i).vars(0), ct->lin_max().exprs(i).coeffs(0),
1157 ct->lin_max().exprs(j).vars(0), -ct->lin_max().exprs(j).coeffs(0));
1158 const IntegerValue lb = kMinIntegerValue;
1159 const IntegerValue ub(ct->lin_max().exprs(j).offset() -
1160 ct->lin_max().exprs(i).offset());
1161 const RelationStatus status = known_linear2_.GetStatus(expr2, lb, ub);
1162 if (status == RelationStatus::IS_TRUE) {
1163 simplified = true;
1164 can_be_removed[i] = true;
1165 break;
1166 }
1167 }
1168 }
1169
1170 if (simplified) {
1171 context_->UpdateRuleStats(
1172 "lin_max: removed expression smaller than others");
1173 int new_size = 0;
1174 for (int i = 0; i < num_exprs; ++i) {
1175 if (can_be_removed[i]) continue;
1176 *ct->mutable_lin_max()->mutable_exprs(new_size++) =
1177 ct->lin_max().exprs(i);
1178 }
1179 google::protobuf::util::Truncate(ct->mutable_lin_max()->mutable_exprs(),
1180 new_size);
1181 context_->UpdateConstraintVariableUsage(c);
1182 }
1183 }
1184
1185 // If only one is left, we can convert to an equality. Note that we create a
1186 // new constraint otherwise it might not be processed again.
1187 if (ct->lin_max().exprs().size() == 1) {
1188 context_->UpdateRuleStats("lin_max: converted to equality");
1189 ConstraintProto* new_ct = context_->working_model->add_constraints();
1190 *new_ct = *ct; // copy name and potential reification.
1191 auto* arg = new_ct->mutable_linear();
1192 const LinearExpressionProto& a = ct->lin_max().target();
1193 const LinearExpressionProto& b = ct->lin_max().exprs(0);
1194 for (int i = 0; i < a.vars().size(); ++i) {
1195 arg->add_vars(a.vars(i));
1196 arg->add_coeffs(a.coeffs(i));
1197 }
1198 for (int i = 0; i < b.vars().size(); ++i) {
1199 arg->add_vars(b.vars(i));
1200 arg->add_coeffs(-b.coeffs(i));
1201 }
1202 arg->add_domain(b.offset() - a.offset());
1203 arg->add_domain(b.offset() - a.offset());
1204 context_->UpdateNewConstraintsVariableUsage();
1205 return RemoveConstraint(ct);
1206 }
1207
1208 if (!DivideLinMaxByGcd(c, ct)) return false;
1209
1210 // Cut everything above the max if possible.
1211 // If one of the linear expression has many term and is above the max, we
1212 // abort early since none of the other rule can be applied.
1213 const int64_t target_min = context_->MinOf(target);
1214 const int64_t target_max = context_->MaxOf(target);
1215 {
1216 bool abort = false;
1217 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1218 const int64_t value_min = context_->MinOf(expr);
1219 bool modified = false;
1220 if (!context_->IntersectDomainWith(expr, Domain(value_min, target_max),
1221 &modified)) {
1222 return true;
1223 }
1224 if (modified) {
1225 context_->UpdateRuleStats("lin_max: reduced expression domain.");
1226 }
1227 const int64_t value_max = context_->MaxOf(expr);
1228 if (value_max > target_max) {
1229 context_->UpdateRuleStats("TODO lin_max: linear expression above max.");
1230 abort = true;
1231 }
1232 }
1233 if (abort) return changed;
1234 }
1235
1236 // Checks if the affine target domain is constraining.
1237 bool linear_target_domain_contains_max_domain = false;
1238 if (ExpressionContainsSingleRef(target)) { // target = +/- var.
1239 int64_t infered_min = std::numeric_limits<int64_t>::min();
1240 int64_t infered_max = std::numeric_limits<int64_t>::min();
1241 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1242 infered_min = std::max(infered_min, context_->MinOf(expr));
1243 infered_max = std::max(infered_max, context_->MaxOf(expr));
1244 }
1245 Domain rhs_domain;
1246 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1247 rhs_domain = rhs_domain.UnionWith(
1248 context_->DomainSuperSetOf(expr).IntersectionWith(
1249 {infered_min, infered_max}));
1250 }
1251
1252 // Checks if all values from the max(exprs) belong in the domain of the
1253 // target.
1254 // Note that the target is +/-var.
1255 DCHECK_EQ(std::abs(target.coeffs(0)), 1);
1256 const Domain target_domain =
1257 target.coeffs(0) == 1 ? context_->DomainOf(target.vars(0))
1258 : context_->DomainOf(target.vars(0)).Negation();
1259 linear_target_domain_contains_max_domain =
1260 rhs_domain.IsIncludedIn(target_domain);
1261 }
1262
1263 // Avoid to remove the constraint for special cases:
1264 // affine(x) = max(expr(x, ...), ...);
1265 //
1266 // TODO(user): We could presolve this, but there are a few type of cases.
1267 // for example:
1268 // - x = max(x + 3, ...) : infeasible.
1269 // - x = max(x - 2, ...) : reduce arity: x = max(...)
1270 // - x = max(2x, ...) we have x <= 0
1271 // - etc...
1272 // Actually, I think for the expr=affine' case, it reduces to:
1273 // affine(x) >= affine'(x)
1274 // affine(x) = max(...);
1275 if (linear_target_domain_contains_max_domain) {
1276 const int target_var = target.vars(0);
1277 bool abort = false;
1278 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1279 for (const int var : expr.vars()) {
1280 if (var == target_var &&
1281 !LinearExpressionProtosAreEqual(expr, target)) {
1282 abort = true;
1283 break;
1284 }
1285 }
1286 if (abort) break;
1287 }
1288 if (abort) {
1289 // Actually the expression can be more than affine.
1290 // We only know that the target is affine here.
1291 context_->UpdateRuleStats(
1292 "TODO lin_max: affine(x) = max(affine'(x), ...) !!");
1293 linear_target_domain_contains_max_domain = false;
1294 }
1295 }
1296
1297 // If the target is not used, and safe, we can remove the constraint.
1298 if (linear_target_domain_contains_max_domain &&
1299 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
1300 context_->UpdateRuleStats("lin_max: unused affine target");
1301 context_->MarkVariableAsRemoved(target.vars(0));
1302 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1303 return RemoveConstraint(ct);
1304 }
1305
1306 // If the target is only used in the objective, and safe, we can simplify the
1307 // constraint.
1308 if (linear_target_domain_contains_max_domain &&
1309 context_->VariableWithCostIsUniqueAndRemovable(target.vars(0)) &&
1310 (target.coeffs(0) > 0) ==
1311 (context_->ObjectiveCoeff(target.vars(0)) > 0)) {
1312 context_->UpdateRuleStats("lin_max: rewrite with precedences");
1313 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1314 LinearConstraintProto* prec =
1315 context_->working_model->add_constraints()->mutable_linear();
1316 prec->add_domain(0);
1317 prec->add_domain(std::numeric_limits<int64_t>::max());
1320 }
1321 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1322 return RemoveConstraint(ct);
1323 }
1324
1325 // Deal with fixed target case.
1326 if (target_min == target_max) {
1327 bool all_booleans = true;
1328 std::vector<int> literals;
1329 const int64_t fixed_target = target_min;
1330 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1331 const int64_t value_min = context_->MinOf(expr);
1332 const int64_t value_max = context_->MaxOf(expr);
1333 CHECK_LE(value_max, fixed_target) << "Presolved above";
1334 if (value_max < fixed_target) continue;
1335
1336 if (value_min == value_max && value_max == fixed_target) {
1337 context_->UpdateRuleStats("lin_max: always satisfied");
1338 return RemoveConstraint(ct);
1339 }
1340 if (context_->ExpressionIsAffineBoolean(expr)) {
1341 CHECK_EQ(value_max, fixed_target);
1342 literals.push_back(context_->LiteralForExpressionMax(expr));
1343 } else {
1344 all_booleans = false;
1345 }
1346 }
1347 if (all_booleans) {
1348 if (literals.empty()) {
1349 return MarkConstraintAsFalse(ct);
1350 }
1351
1352 // At least one true;
1353 context_->UpdateRuleStats("lin_max: fixed target and all booleans");
1354 for (const int lit : literals) {
1355 ct->mutable_bool_or()->add_literals(lit);
1356 }
1357 return true;
1358 }
1359 return changed;
1360 }
1361
1362 changed |= PresolveLinMaxWhenAllBoolean(ct);
1363 return changed;
1364}
1365
1366// If everything is Boolean and affine, do not use a lin max!
1367bool CpModelPresolver::PresolveLinMaxWhenAllBoolean(ConstraintProto* ct) {
1368 if (context_->ModelIsUnsat()) return false;
1369 if (HasEnforcementLiteral(*ct)) return false;
1370
1371 const LinearExpressionProto& target = ct->lin_max().target();
1372 if (!context_->ExpressionIsAffineBoolean(target)) return false;
1373
1374 const int64_t target_min = context_->MinOf(target);
1375 const int64_t target_max = context_->MaxOf(target);
1376 const int target_ref = context_->LiteralForExpressionMax(target);
1377
1378 bool min_is_reachable = false;
1379 std::vector<int> min_literals;
1380 std::vector<int> literals_above_min;
1381 std::vector<int> max_literals;
1382
1383 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1384 if (!context_->ExpressionIsAffineBoolean(expr)) return false;
1385 const int64_t value_min = context_->MinOf(expr);
1386 const int64_t value_max = context_->MaxOf(expr);
1387 const int ref = context_->LiteralForExpressionMax(expr);
1388
1389 // Get corner case out of the way, and wait for the constraint to be
1390 // processed again in these case.
1391 if (value_min > target_min) {
1392 context_->UpdateRuleStats("lin_max: fix target");
1393 (void)context_->SetLiteralToTrue(target_ref);
1394 return false;
1395 }
1396 if (value_max > target_max) {
1397 context_->UpdateRuleStats("lin_max: fix bool expr");
1398 (void)context_->SetLiteralToFalse(ref);
1399 return false;
1400 }
1401
1402 // expr is fixed.
1403 if (value_min == value_max) {
1404 if (value_min == target_min) min_is_reachable = true;
1405 continue;
1406 }
1407
1408 CHECK_LE(value_min, target_min);
1409 if (value_min == target_min) {
1410 min_literals.push_back(NegatedRef(ref));
1411 }
1412
1413 CHECK_LE(value_max, target_max);
1414 if (value_max == target_max) {
1415 max_literals.push_back(ref);
1416 literals_above_min.push_back(ref);
1417 } else if (value_max > target_min) {
1418 literals_above_min.push_back(ref);
1419 } else if (value_max == target_min) {
1420 min_literals.push_back(ref);
1421 }
1422 }
1423
1424 context_->UpdateRuleStats("lin_max: all Booleans.");
1425
1426 // target_ref => at_least_one(max_literals);
1427 ConstraintProto* clause = context_->working_model->add_constraints();
1428 clause->add_enforcement_literal(target_ref);
1429 clause->mutable_bool_or();
1430 for (const int lit : max_literals) {
1431 clause->mutable_bool_or()->add_literals(lit);
1432 }
1433
1434 // not(target_ref) => not(lit) for lit in literals_above_min
1435 for (const int lit : literals_above_min) {
1436 context_->AddImplication(lit, target_ref);
1437 }
1438
1439 if (!min_is_reachable) {
1440 // not(target_ref) => at_least_one(min_literals).
1441 ConstraintProto* clause = context_->working_model->add_constraints();
1442 clause->add_enforcement_literal(NegatedRef(target_ref));
1443 clause->mutable_bool_or();
1444 for (const int lit : min_literals) {
1445 clause->mutable_bool_or()->add_literals(lit);
1446 }
1447 }
1448
1449 context_->UpdateNewConstraintsVariableUsage();
1450 return RemoveConstraint(ct);
1451}
1452
1453// This presolve expect that the constraint only contains 1-var affine
1454// expressions.
1455bool CpModelPresolver::PropagateAndReduceIntAbs(ConstraintProto* ct) {
1456 CHECK_EQ(ct->enforcement_literal_size(), 0);
1457 if (context_->ModelIsUnsat()) return false;
1458 const LinearExpressionProto& target_expr = ct->lin_max().target();
1459 const LinearExpressionProto& expr = ct->lin_max().exprs(0);
1460 DCHECK_EQ(expr.vars_size(), 1);
1461
1462 // Propagate domain from the expression to the target.
1463 {
1464 const Domain expr_domain = context_->DomainSuperSetOf(expr);
1465 const Domain new_target_domain =
1466 expr_domain.UnionWith(expr_domain.Negation())
1467 .IntersectionWith({0, std::numeric_limits<int64_t>::max()});
1468 bool target_domain_modified = false;
1469 if (!context_->IntersectDomainWith(target_expr, new_target_domain,
1470 &target_domain_modified)) {
1471 return false;
1472 }
1473 if (expr_domain.IsFixed()) {
1474 context_->UpdateRuleStats("lin_max: fixed expression in int_abs");
1475 return RemoveConstraint(ct);
1476 }
1477 if (target_domain_modified) {
1478 context_->UpdateRuleStats("lin_max: propagate domain from x to abs(x)");
1479 }
1480 }
1481
1482 // Propagate from target domain to variable.
1483 {
1484 const Domain target_domain =
1485 context_->DomainSuperSetOf(target_expr)
1486 .IntersectionWith(Domain(0, std::numeric_limits<int64_t>::max()));
1487 const Domain new_expr_domain =
1488 target_domain.UnionWith(target_domain.Negation());
1489 bool expr_domain_modified = false;
1490 if (!context_->IntersectDomainWith(expr, new_expr_domain,
1491 &expr_domain_modified)) {
1492 return true;
1493 }
1494 // This is the only reason why we don't support fully generic linear
1495 // expression.
1496 if (context_->IsFixed(target_expr)) {
1497 context_->UpdateRuleStats("lin_max: fixed abs target");
1498 return RemoveConstraint(ct);
1499 }
1500 if (expr_domain_modified) {
1501 context_->UpdateRuleStats("lin_max: propagate domain from abs(x) to x");
1502 }
1503 }
1504
1505 // Convert to equality if the sign of expr is fixed.
1506 if (context_->MinOf(expr) >= 0) {
1507 context_->UpdateRuleStats("lin_max: converted abs to equality");
1508 ConstraintProto* new_ct = context_->working_model->add_constraints();
1509 new_ct->set_name(ct->name());
1510 auto* arg = new_ct->mutable_linear();
1511 arg->add_domain(0);
1512 arg->add_domain(0);
1513 AddLinearExpressionToLinearConstraint(target_expr, 1, arg);
1515 bool changed = false;
1516 if (!CanonicalizeLinear(new_ct, &changed)) {
1517 return true;
1518 }
1519 context_->UpdateNewConstraintsVariableUsage();
1520 return RemoveConstraint(ct);
1521 }
1522
1523 if (context_->MaxOf(expr) <= 0) {
1524 context_->UpdateRuleStats("lin_max: converted abs to equality");
1525 ConstraintProto* new_ct = context_->working_model->add_constraints();
1526 new_ct->set_name(ct->name());
1527 auto* arg = new_ct->mutable_linear();
1528 arg->add_domain(0);
1529 arg->add_domain(0);
1530 AddLinearExpressionToLinearConstraint(target_expr, 1, arg);
1532 bool changed = false;
1533 if (!CanonicalizeLinear(new_ct, &changed)) {
1534 return true;
1535 }
1536 context_->UpdateNewConstraintsVariableUsage();
1537 return RemoveConstraint(ct);
1538 }
1539
1540 // Remove the abs constraint if the target is removable and if domains have
1541 // been propagated without loss.
1542 // For now, we known that there is no loss if the target is a single ref.
1543 // Since all the expression are affine, in this case we are fine.
1544 if (ExpressionContainsSingleRef(target_expr) &&
1545 context_->VariableIsUniqueAndRemovable(target_expr.vars(0))) {
1546 context_->MarkVariableAsRemoved(target_expr.vars(0));
1547 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1548 context_->UpdateRuleStats("lin_max: unused abs target");
1549 return RemoveConstraint(ct);
1550 }
1551
1552 return false;
1553}
1554
1555Domain EvaluateImpliedIntProdDomain(const LinearArgumentProto& expr,
1556 const PresolveContext& context) {
1557 if (expr.exprs().size() == 2) {
1558 const LinearExpressionProto& expr0 = expr.exprs(0);
1559 const LinearExpressionProto& expr1 = expr.exprs(1);
1560 if (LinearExpressionProtosAreEqual(expr0, expr1)) {
1561 return context.DomainSuperSetOf(expr0).SquareSuperset();
1562 }
1563 if (expr0.vars().size() == 1 && expr1.vars().size() == 1 &&
1564 expr0.vars(0) == expr1.vars(0)) {
1565 return context.DomainOf(expr0.vars(0))
1566 .QuadraticSuperset(expr0.coeffs(0), expr0.offset(), expr1.coeffs(0),
1567 expr1.offset());
1568 }
1569 }
1570
1571 Domain implied(1);
1572 for (const LinearExpressionProto& expr : expr.exprs()) {
1573 implied =
1574 implied.ContinuousMultiplicationBy(context.DomainSuperSetOf(expr));
1575 }
1576 return implied;
1577}
1578
1579bool CpModelPresolver::PresolveIntProd(ConstraintProto* ct) {
1580 if (context_->ModelIsUnsat()) return false;
1581 if (HasEnforcementLiteral(*ct)) return false;
1582
1583 // Start by restricting the domain of target. We will be more precise later.
1584 bool domain_modified = false;
1585 Domain implied_domain =
1586 EvaluateImpliedIntProdDomain(ct->int_prod(), *context_);
1587 if (!context_->IntersectDomainWith(ct->int_prod().target(), implied_domain,
1588 &domain_modified)) {
1589 return false;
1590 }
1591
1592 // Remove a constraint if the target only appears in the constraint. For this
1593 // to be correct some conditions must be met:
1594 // - The target is an affine linear with coefficient -1 or 1.
1595 // - The target does not appear in the rhs (no x = (a*x + b) * ...).
1596 // - The target domain covers all the possible range of the rhs.
1597 if (ExpressionContainsSingleRef(ct->int_prod().target()) &&
1598 context_->VariableIsUniqueAndRemovable(ct->int_prod().target().vars(0)) &&
1599 std::abs(ct->int_prod().target().coeffs(0)) == 1) {
1600 const LinearExpressionProto& target = ct->int_prod().target();
1601 if (!absl::c_any_of(ct->int_prod().exprs(),
1602 [&target](const LinearExpressionProto& expr) {
1603 return absl::c_linear_search(expr.vars(),
1604 target.vars(0));
1605 })) {
1606 const Domain target_domain =
1607 Domain(target.offset())
1608 .AdditionWith(context_->DomainOf(target.vars(0)));
1609 if (implied_domain.IsIncludedIn(target_domain)) {
1610 context_->MarkVariableAsRemoved(ct->int_prod().target().vars(0));
1611 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1612 context_->UpdateRuleStats("int_prod: unused affine target");
1613 return RemoveConstraint(ct);
1614 }
1615 }
1616 }
1617
1618 // Remove constant expressions and compute the product of the max positive
1619 // divisor of each term.
1620 int64_t constant_factor = 1;
1621 int new_size = 0;
1622 bool changed = false;
1623 LinearArgumentProto* proto = ct->mutable_int_prod();
1624 for (int i = 0; i < ct->int_prod().exprs().size(); ++i) {
1625 LinearExpressionProto expr = ct->int_prod().exprs(i);
1626 if (context_->IsFixed(expr)) {
1627 const int64_t expr_value = context_->FixedValue(expr);
1628 constant_factor = CapProd(constant_factor, expr_value);
1629 context_->UpdateRuleStats("int_prod: removed constant expressions.");
1630 changed = true;
1631 } else {
1632 const int64_t expr_divisor = LinearExpressionGcd(expr);
1633 DivideLinearExpression(expr_divisor, &expr);
1634 constant_factor = CapProd(constant_factor, expr_divisor);
1635 *proto->mutable_exprs(new_size++) = expr;
1636 }
1637 }
1638 proto->mutable_exprs()->erase(proto->mutable_exprs()->begin() + new_size,
1639 proto->mutable_exprs()->end());
1640
1641 if (ct->int_prod().exprs().empty() || constant_factor == 0) {
1642 if (!context_->IntersectDomainWith(ct->int_prod().target(),
1643 Domain(constant_factor))) {
1644 return false;
1645 }
1646 context_->UpdateRuleStats("int_prod: constant product");
1647 return RemoveConstraint(ct);
1648 }
1649
1650 // If target is fixed to zero, we can forget the constant factor.
1651 if (context_->IsFixed(ct->int_prod().target()) &&
1652 context_->FixedValue(ct->int_prod().target()) == 0 &&
1653 constant_factor != 1) {
1654 context_->UpdateRuleStats("int_prod: simplify by constant factor");
1655 constant_factor = 1;
1656 }
1657
1658 // In this case, the only possible value that fit in the domains is zero.
1659 // We will check for UNSAT if zero is not achievable by the rhs below.
1660 if (AtMinOrMaxInt64(constant_factor)) {
1661 context_->UpdateRuleStats("int_prod: overflow if non zero");
1662 if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(0))) {
1663 return false;
1664 }
1665 constant_factor = 1;
1666 }
1667
1668 // Replace by linear if it cannot overflow.
1669 if (ct->int_prod().exprs().size() == 1) {
1670 LinearExpressionProto* const target =
1671 ct->mutable_int_prod()->mutable_target();
1672 LinearConstraintProto* const lin =
1673 context_->working_model->add_constraints()->mutable_linear();
1674
1675 if (context_->IsFixed(*target)) {
1676 int64_t target_value = context_->FixedValue(*target);
1677 if (target_value % constant_factor != 0) {
1678 return context_->NotifyThatModelIsUnsat(
1679 "int_prod: product incompatible with fixed target");
1680 }
1681 // expression == target_value / constant_factor.
1682 lin->add_domain(target_value / constant_factor);
1683 lin->add_domain(target_value / constant_factor);
1684 AddLinearExpressionToLinearConstraint(ct->int_prod().exprs(0), 1, lin);
1685 context_->UpdateNewConstraintsVariableUsage();
1686 context_->UpdateRuleStats("int_prod: expression is constant.");
1687 return RemoveConstraint(ct);
1688 }
1689
1690 const int64_t target_divisor = LinearExpressionGcd(*target);
1691
1692 // Reduce coefficients.
1693 const int64_t gcd =
1694 std::gcd(static_cast<uint64_t>(std::abs(constant_factor)),
1695 static_cast<uint64_t>(std::abs(target_divisor)));
1696 if (gcd != 1) {
1697 constant_factor /= gcd;
1698 DivideLinearExpression(gcd, target);
1699 }
1700
1701 // expression * constant_factor = target.
1702 lin->add_domain(0);
1703 lin->add_domain(0);
1704 const bool overflow = !SafeAddLinearExpressionToLinearConstraint(
1705 ct->int_prod().target(), 1, lin) ||
1707 ct->int_prod().exprs(0), -constant_factor, lin);
1708
1709 // Check for overflow.
1710 if (overflow ||
1711 PossibleIntegerOverflow(*context_->working_model, lin->vars(),
1712 lin->coeffs(), lin->domain(0))) {
1713 context_->working_model->mutable_constraints()->RemoveLast();
1714 // The constant factor will be handled by the creation of an affine
1715 // relation below.
1716 } else { // Replace with a linear equation.
1717 context_->UpdateNewConstraintsVariableUsage();
1718 context_->UpdateRuleStats("int_prod: linearize product by constant.");
1719 return RemoveConstraint(ct);
1720 }
1721 }
1722
1723 if (constant_factor != 1) {
1724 // Lets canonicalize the target by introducing a new variable if necessary.
1725 //
1726 // coeff * X + offset must be a multiple of constant_factor, so
1727 // we can rewrite X so that this property is clear.
1728 //
1729 // Note(user): it is important for this to have a restricted target domain
1730 // so we can choose a better representative.
1731 const LinearExpressionProto old_target = ct->int_prod().target();
1732 if (!context_->IsFixed(old_target)) {
1733 if (CapProd(constant_factor, std::max(context_->MaxOf(old_target),
1734 -context_->MinOf(old_target))) >=
1735 std::numeric_limits<int64_t>::max() / 2) {
1736 // Re-add a new term with the constant factor.
1737 ct->mutable_int_prod()->add_exprs()->set_offset(constant_factor);
1738 context_->UpdateRuleStats(
1739 "int_prod: overflow prevented creating a affine relation.");
1740 return true;
1741 }
1742 const int ref = old_target.vars(0);
1743 const int64_t coeff = old_target.coeffs(0);
1744 const int64_t offset = old_target.offset();
1745 if (!context_->CanonicalizeAffineVariable(ref, coeff, constant_factor,
1746 -offset)) {
1747 return false;
1748 }
1749 if (context_->IsFixed(ref)) {
1750 changed = true;
1751 }
1752 }
1753
1754 // This can happen during CanonicalizeAffineVariable().
1755 if (context_->IsFixed(old_target)) {
1756 const int64_t target_value = context_->FixedValue(old_target);
1757 if (target_value % constant_factor != 0) {
1758 return context_->NotifyThatModelIsUnsat(
1759 "int_prod: constant factor does not divide constant target");
1760 }
1761 changed = true;
1762 proto->clear_target();
1763 proto->mutable_target()->set_offset(target_value / constant_factor);
1764 context_->UpdateRuleStats(
1765 "int_prod: divide product and fixed target by constant factor");
1766 } else {
1767 // We use absl::int128 to be resistant to overflow here.
1768 const AffineRelation::Relation r =
1769 context_->GetAffineRelation(old_target.vars(0));
1770 const absl::int128 temp_coeff =
1771 absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff);
1772 CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0);
1773 const absl::int128 temp_offset =
1774 absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) +
1775 absl::int128(old_target.offset());
1776 CHECK_EQ(temp_offset % absl::int128(constant_factor), 0);
1777 const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor);
1778 const absl::int128 new_offset =
1779 temp_offset / absl::int128(constant_factor);
1780
1781 // TODO(user): We try to keep coeff/offset small, if this happens, it
1782 // probably means there is no feasible solution involving int64_t and that
1783 // do not causes overflow while evaluating it, but it is hard to be
1784 // exactly sure we are correct here since it depends on the evaluation
1785 // order. Similarly, by introducing intermediate variable we might loose
1786 // solution if this intermediate variable value do not fit on an int64_t.
1787 if (new_coeff > absl::int128(std::numeric_limits<int64_t>::max()) ||
1788 new_coeff < absl::int128(std::numeric_limits<int64_t>::min()) ||
1789 new_offset > absl::int128(std::numeric_limits<int64_t>::max()) ||
1790 new_offset < absl::int128(std::numeric_limits<int64_t>::min())) {
1791 return context_->NotifyThatModelIsUnsat(
1792 "int_prod: overflow during simplification.");
1793 }
1794
1795 // Rewrite the target.
1796 proto->mutable_target()->set_coeffs(0, static_cast<int64_t>(new_coeff));
1797 proto->mutable_target()->set_vars(0, r.representative);
1798 proto->mutable_target()->set_offset(static_cast<int64_t>(new_offset));
1799 context_->UpdateRuleStats("int_prod: divide product by constant factor");
1800 changed = true;
1801 }
1802 }
1803
1804 // Restrict the target domain if possible.
1805 implied_domain = EvaluateImpliedIntProdDomain(ct->int_prod(), *context_);
1806 const bool is_square = ct->int_prod().exprs_size() == 2 &&
1808 ct->int_prod().exprs(0), ct->int_prod().exprs(1));
1809 if (!context_->IntersectDomainWith(ct->int_prod().target(), implied_domain,
1810 &domain_modified)) {
1811 return false;
1812 }
1813 if (domain_modified) {
1814 context_->UpdateRuleStats(absl::StrCat(
1815 is_square ? "int_square" : "int_prod", ": reduced target domain."));
1816 }
1817
1818 // y = x * x, we can reduce the domain of x from the domain of y.
1819 if (is_square) {
1820 const int64_t target_max = context_->MaxOf(ct->int_prod().target());
1821 DCHECK_GE(target_max, 0);
1822 const int64_t sqrt_max = FloorSquareRoot(target_max);
1823 bool expr_reduced = false;
1824 if (!context_->IntersectDomainWith(ct->int_prod().exprs(0),
1825 {-sqrt_max, sqrt_max}, &expr_reduced)) {
1826 return false;
1827 }
1828 if (expr_reduced) {
1829 context_->UpdateRuleStats("int_square: reduced expr domain.");
1830 }
1831 }
1832
1833 if (ct->int_prod().exprs_size() == 2) {
1834 LinearExpressionProto a = ct->int_prod().exprs(0);
1835 LinearExpressionProto b = ct->int_prod().exprs(1);
1836 const LinearExpressionProto product = ct->int_prod().target();
1839 a, product)) { // x = x * x, only true for {0, 1}.
1840 if (!context_->IntersectDomainWith(product, Domain(0, 1))) {
1841 return false;
1842 }
1843 context_->UpdateRuleStats("int_square: fix variable to zero or one.");
1844 return RemoveConstraint(ct);
1845 }
1846 }
1847
1848 if (ct->int_prod().exprs().size() == 2) {
1849 const auto is_boolean_affine =
1850 [context = context_](const LinearExpressionProto& expr) {
1851 return expr.vars().size() == 1 && context->MinOf(expr.vars(0)) == 0 &&
1852 context->MaxOf(expr.vars(0)) == 1;
1853 };
1854 const LinearExpressionProto* boolean_linear = nullptr;
1855 const LinearExpressionProto* other_linear = nullptr;
1856 if (is_boolean_affine(ct->int_prod().exprs(0))) {
1857 boolean_linear = &ct->int_prod().exprs(0);
1858 other_linear = &ct->int_prod().exprs(1);
1859 } else if (is_boolean_affine(ct->int_prod().exprs(1))) {
1860 boolean_linear = &ct->int_prod().exprs(1);
1861 other_linear = &ct->int_prod().exprs(0);
1862 }
1863 if (boolean_linear) {
1864 // We have:
1865 // (u + b * v) * other_expr = B, where `b` is a boolean variable.
1866 //
1867 // We can rewrite this as:
1868 // u * other_expr = B, if b = false;
1869 // (u + v) * other_expr = B, if b = true
1870 ConstraintProto* constraint_for_false =
1871 context_->working_model->add_constraints();
1872 ConstraintProto* constraint_for_true =
1873 context_->working_model->add_constraints();
1874 constraint_for_true->add_enforcement_literal(boolean_linear->vars(0));
1875 constraint_for_false->add_enforcement_literal(
1876 NegatedRef(boolean_linear->vars(0)));
1877 LinearConstraintProto* linear_for_false =
1878 constraint_for_false->mutable_linear();
1879 LinearConstraintProto* linear_for_true =
1880 constraint_for_true->mutable_linear();
1881
1882 linear_for_false->add_domain(0);
1883 linear_for_false->add_domain(0);
1885 *other_linear, boolean_linear->offset(), linear_for_false);
1886 AddLinearExpressionToLinearConstraint(ct->int_prod().target(), -1,
1887 linear_for_false);
1888
1889 linear_for_true->add_domain(0);
1890 linear_for_true->add_domain(0);
1892 *other_linear, boolean_linear->offset() + boolean_linear->coeffs(0),
1893 linear_for_true);
1894 AddLinearExpressionToLinearConstraint(ct->int_prod().target(), -1,
1895 linear_for_true);
1896 context_->CanonicalizeLinearConstraint(constraint_for_false);
1897 context_->CanonicalizeLinearConstraint(constraint_for_true);
1898 if (PossibleIntegerOverflow(*context_->working_model,
1899 linear_for_false->vars(),
1900 linear_for_false->coeffs()) ||
1901 PossibleIntegerOverflow(*context_->working_model,
1902 linear_for_true->vars(),
1903 linear_for_true->coeffs())) {
1904 context_->working_model->mutable_constraints()->RemoveLast();
1905 context_->working_model->mutable_constraints()->RemoveLast();
1906 } else {
1907 context_->UpdateRuleStats("int_prod: boolean affine term");
1908 context_->UpdateNewConstraintsVariableUsage();
1909 return RemoveConstraint(ct);
1910 }
1911 }
1912 }
1913
1914 // For now, we only presolve the case where all variables are Booleans.
1915 const LinearExpressionProto target_expr = ct->int_prod().target();
1916 int target;
1917 if (!context_->ExpressionIsALiteral(target_expr, &target)) {
1918 return changed;
1919 }
1920 std::vector<int> literals;
1921 for (const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1922 int lit;
1923 if (!context_->ExpressionIsALiteral(expr, &lit)) {
1924 return changed;
1925 }
1926 literals.push_back(lit);
1927 }
1928
1929 // This is a Boolean constraint!
1930 context_->UpdateRuleStats("int_prod: all Boolean.");
1931 {
1932 ConstraintProto* new_ct = context_->working_model->add_constraints();
1933 new_ct->add_enforcement_literal(target);
1934 auto* arg = new_ct->mutable_bool_and();
1935 for (const int lit : literals) {
1936 arg->add_literals(lit);
1937 }
1938 }
1939 {
1940 ConstraintProto* new_ct = context_->working_model->add_constraints();
1941 auto* arg = new_ct->mutable_bool_or();
1942 arg->add_literals(target);
1943 for (const int lit : literals) {
1944 arg->add_literals(NegatedRef(lit));
1945 }
1946 }
1947 context_->UpdateNewConstraintsVariableUsage();
1948 return RemoveConstraint(ct);
1949}
1950
1951bool CpModelPresolver::PresolveIntDiv(int c, ConstraintProto* ct) {
1952 if (context_->ModelIsUnsat()) return false;
1953
1954 const LinearExpressionProto target = ct->int_div().target();
1955 const LinearExpressionProto expr = ct->int_div().exprs(0);
1956 const LinearExpressionProto div = ct->int_div().exprs(1);
1957
1958 if (LinearExpressionProtosAreEqual(expr, div)) {
1959 if (!context_->IntersectDomainWith(target, Domain(1))) {
1960 return false;
1961 }
1962 context_->UpdateRuleStats("int_div: y = x / x");
1963 return RemoveConstraint(ct);
1964 } else if (LinearExpressionProtosAreEqual(expr, div, -1)) {
1965 if (!context_->IntersectDomainWith(target, Domain(-1))) {
1966 return false;
1967 }
1968 context_->UpdateRuleStats("int_div: y = - x / x");
1969 return RemoveConstraint(ct);
1970 }
1971
1972 // Sometimes we have only a single variable appearing in the whole constraint.
1973 // If the domain is small enough, we can just restrict the domain and remove
1974 // the constraint.
1975 if (ct->enforcement_literal().empty() &&
1976 context_->ConstraintToVars(c).size() == 1) {
1977 const int var = context_->ConstraintToVars(c)[0];
1978 if (context_->DomainOf(var).Size() >= 100) {
1979 context_->UpdateRuleStats(
1980 "TODO int_div: single variable with large domain");
1981 } else {
1982 std::vector<int64_t> possible_values;
1983 for (const int64_t v : context_->DomainOf(var).Values()) {
1984 const int64_t target_v =
1985 EvaluateSingleVariableExpression(target, var, v);
1986 const int64_t expr_v = EvaluateSingleVariableExpression(expr, var, v);
1987 const int64_t div_v = EvaluateSingleVariableExpression(div, var, v);
1988 if (div_v == 0) continue;
1989 if (target_v == expr_v / div_v) {
1990 possible_values.push_back(v);
1991 }
1992 }
1993 (void)context_->IntersectDomainWith(var,
1994 Domain::FromValues(possible_values));
1995 context_->UpdateRuleStats("int_div: single variable");
1996 return RemoveConstraint(ct);
1997 }
1998 }
1999
2000 // For now, we only presolve the case where the divisor is constant.
2001 if (!context_->IsFixed(div)) return false;
2002
2003 const int64_t divisor = context_->FixedValue(div);
2004
2005 // Trivial case one: target = expr / +/-1.
2006 if (divisor == 1 || divisor == -1) {
2007 LinearConstraintProto* const lin =
2008 context_->working_model->add_constraints()->mutable_linear();
2009 lin->add_domain(0);
2010 lin->add_domain(0);
2012 AddLinearExpressionToLinearConstraint(target, -divisor, lin);
2013 context_->UpdateNewConstraintsVariableUsage();
2014 context_->UpdateRuleStats("int_div: rewrite to equality");
2015 return RemoveConstraint(ct);
2016 }
2017
2018 // Reduce the domain of target.
2019 {
2020 bool domain_modified = false;
2021 const Domain target_implied_domain =
2022 context_->DomainSuperSetOf(expr).DivisionBy(divisor);
2023
2024 if (!context_->IntersectDomainWith(target, target_implied_domain,
2025 &domain_modified)) {
2026 return false;
2027 }
2028 if (domain_modified) {
2029 // Note: the case target is fixed has been processed before.
2030 if (target_implied_domain.IsFixed()) {
2031 context_->UpdateRuleStats(
2032 "int_div: target has been fixed by propagating X / cte");
2033 } else {
2034 context_->UpdateRuleStats(
2035 "int_div: updated domain of target in target = X / cte");
2036 }
2037 }
2038 }
2039
2040 // Trivial case three: fixed_target = expr / fixed_divisor.
2041 if (context_->IsFixed(target) &&
2042 CapAdd(1, CapProd(std::abs(divisor),
2043 1 + std::abs(context_->FixedValue(target)))) !=
2044 std::numeric_limits<int64_t>::max()) {
2045 int64_t t = context_->FixedValue(target);
2046 int64_t d = divisor;
2047 if (d < 0) {
2048 t = -t;
2049 d = -d;
2050 }
2051
2052 const Domain expr_implied_domain =
2053 t > 0
2054 ? Domain(t * d, (t + 1) * d - 1)
2055 : (t == 0 ? Domain(1 - d, d - 1) : Domain((t - 1) * d + 1, t * d));
2056 bool domain_modified = false;
2057 if (!context_->IntersectDomainWith(expr, expr_implied_domain,
2058 &domain_modified)) {
2059 return false;
2060 }
2061 if (domain_modified) {
2062 context_->UpdateRuleStats("int_div: target and divisor are fixed");
2063 } else {
2064 context_->UpdateRuleStats("int_div: always true");
2065 }
2066 return RemoveConstraint(ct);
2067 }
2068
2069 // Linearize if everything is positive, and we have no overflow.
2070 // TODO(user): Deal with other cases where there is no change of
2071 // sign. We can also deal with target = cte, div variable.
2072 if (context_->MinOf(target) >= 0 && context_->MinOf(expr) >= 0 &&
2073 divisor > 1 &&
2074 CapProd(divisor, context_->MaxOf(target)) !=
2075 std::numeric_limits<int64_t>::max()) {
2076 LinearConstraintProto* const lin =
2077 context_->working_model->add_constraints()->mutable_linear();
2078 lin->add_domain(0);
2079 lin->add_domain(divisor - 1);
2081 AddLinearExpressionToLinearConstraint(target, -divisor, lin);
2082 context_->UpdateNewConstraintsVariableUsage();
2083 context_->UpdateRuleStats(
2084 "int_div: linearize positive division with a constant divisor");
2085
2086 return RemoveConstraint(ct);
2087 }
2088
2089 // TODO(user): reduce the domain of X by introducing an
2090 // InverseDivisionOfSortedDisjointIntervals().
2091 return false;
2092}
2093
2094bool CpModelPresolver::PresolveIntMod(int c, ConstraintProto* ct) {
2095 if (context_->ModelIsUnsat()) return false;
2096
2097 // TODO(user): Presolve f(X) = g(X) % fixed_mod.
2098 const LinearExpressionProto target = ct->int_mod().target();
2099 const LinearExpressionProto expr = ct->int_mod().exprs(0);
2100 const LinearExpressionProto mod = ct->int_mod().exprs(1);
2101
2102 if (context_->MinOf(target) > 0) {
2103 bool domain_changed = false;
2104 if (!context_->IntersectDomainWith(
2105 expr, Domain(0, std::numeric_limits<int64_t>::max()),
2106 &domain_changed)) {
2107 return false;
2108 }
2109 if (domain_changed) {
2110 context_->UpdateRuleStats(
2111 "int_mod: non negative target implies positive expression");
2112 }
2113 }
2114
2115 if (context_->MinOf(target) >= context_->MaxOf(mod) ||
2116 context_->MaxOf(target) <= -context_->MaxOf(mod)) {
2117 return context_->NotifyThatModelIsUnsat(
2118 "int_mod: incompatible target and mod");
2119 }
2120
2121 if (context_->MaxOf(target) < 0) {
2122 bool domain_changed = false;
2123 if (!context_->IntersectDomainWith(
2124 expr, Domain(std::numeric_limits<int64_t>::min(), 0),
2125 &domain_changed)) {
2126 return false;
2127 }
2128 if (domain_changed) {
2129 context_->UpdateRuleStats(
2130 "int_mod: non positive target implies negative expression");
2131 }
2132 }
2133
2134 if (context_->IsFixed(target) && context_->IsFixed(mod) &&
2135 context_->FixedValue(mod) > 1 && ct->enforcement_literal().empty() &&
2136 expr.vars().size() == 1) {
2137 // We can intersect the domain of expr with {k * mod + target}.
2138 const int64_t fixed_mod = context_->FixedValue(mod);
2139 const int64_t fixed_target = context_->FixedValue(target);
2140
2141 if (!context_->CanonicalizeAffineVariable(expr.vars(0), expr.coeffs(0),
2142 fixed_mod,
2143 fixed_target - expr.offset())) {
2144 return false;
2145 }
2146
2147 context_->UpdateRuleStats("int_mod: fixed mod and target");
2148 return RemoveConstraint(ct);
2149 }
2150
2151 bool domain_changed = false;
2152 if (!context_->IntersectDomainWith(
2153 target,
2154 context_->DomainSuperSetOf(expr).PositiveModuloBySuperset(
2155 context_->DomainSuperSetOf(mod)),
2156 &domain_changed)) {
2157 return false;
2158 }
2159
2160 if (domain_changed) {
2161 context_->UpdateRuleStats("int_mod: reduce target domain");
2162 }
2163
2164 // Remove the constraint if the target is removable.
2165 // This is triggered on the flatzinc rotating-workforce problems.
2166 //
2167 // TODO(user): We can deal with more cases, sometime even if the domain of
2168 // expr.vars(0) is large, the implied domain is not too complex.
2169 if (target.vars().size() == 1 && expr.vars().size() == 1 &&
2170 context_->DomainOf(expr.vars(0)).Size() < 100 && context_->IsFixed(mod) &&
2171 context_->VariableIsUniqueAndRemovable(target.vars(0)) &&
2172 target.vars(0) != expr.vars(0)) {
2173 const int64_t fixed_mod = context_->FixedValue(mod);
2174 std::vector<int64_t> values;
2175 const Domain dom = context_->DomainOf(target.vars(0));
2176 for (const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
2177 const int64_t rhs = (v * expr.coeffs(0) + expr.offset()) % fixed_mod;
2178 const int64_t target_term = rhs - target.offset();
2179 if (target_term % target.coeffs(0) != 0) continue;
2180 if (dom.Contains(target_term / target.coeffs(0))) {
2181 values.push_back(v);
2182 }
2183 }
2184
2185 context_->UpdateRuleStats("int_mod: remove singleton target");
2186 if (!context_->IntersectDomainWith(expr.vars(0),
2187 Domain::FromValues(values))) {
2188 return false;
2189 }
2190 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2191 ct->Clear();
2192 context_->UpdateConstraintVariableUsage(c);
2193 context_->MarkVariableAsRemoved(target.vars(0));
2194 return true;
2195 }
2196
2197 return false;
2198}
2199
2200// TODO(user): Now that everything has affine relations, we should maybe
2201// canonicalize all linear subexpression in a generic way.
2202bool CpModelPresolver::ExploitEquivalenceRelations(int c, ConstraintProto* ct) {
2203 bool changed = false;
2204
2205 // Optim: Special case for the linear constraint. We just remap the
2206 // enforcement literals, the normal variables will be replaced by their
2207 // representative in CanonicalizeLinear().
2208 if (ct->constraint_case() == ConstraintProto::kLinear) {
2209 for (int& ref : *ct->mutable_enforcement_literal()) {
2210 const int rep = this->context_->GetLiteralRepresentative(ref);
2211 if (rep != ref) {
2212 changed = true;
2213 ref = rep;
2214 }
2215 }
2216 return changed;
2217 }
2218
2219 // Optim: This extra loop is a lot faster than reparsing the variable from the
2220 // proto when there is nothing to do, which is quite often.
2221 bool work_to_do = false;
2222 for (const int var : context_->ConstraintToVars(c)) {
2223 const AffineRelation::Relation r = context_->GetAffineRelation(var);
2224 if (r.representative != var) {
2225 work_to_do = true;
2226 break;
2227 }
2228 }
2229 if (!work_to_do) return false;
2230
2231 // Remap literal and negated literal to their representative.
2233 [&changed, this](int* ref) {
2234 const int rep = this->context_->GetLiteralRepresentative(*ref);
2235 if (rep != *ref) {
2236 changed = true;
2237 *ref = rep;
2238 }
2239 },
2240 ct);
2241 return changed;
2242}
2243
2244bool CpModelPresolver::DivideLinearByGcd(ConstraintProto* ct) {
2245 if (context_->ModelIsUnsat()) return false;
2246
2247 // Compute the GCD of all coefficients.
2248 int64_t gcd = 0;
2249 const int num_vars = ct->linear().vars().size();
2250 for (int i = 0; i < num_vars; ++i) {
2251 const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2252 gcd = std::gcd(gcd, magnitude);
2253 if (gcd == 1) break;
2254 }
2255 if (gcd > 1) {
2256 context_->UpdateRuleStats("linear: divide by GCD");
2257 for (int i = 0; i < num_vars; ++i) {
2258 ct->mutable_linear()->set_coeffs(i, ct->linear().coeffs(i) / gcd);
2259 }
2260 const Domain rhs = ReadDomainFromProto(ct->linear());
2261 FillDomainInProto(rhs.InverseMultiplicationBy(gcd), ct->mutable_linear());
2262 if (ct->linear().domain_size() == 0) {
2263 return MarkConstraintAsFalse(ct);
2264 }
2265 }
2266 return false;
2267}
2268
2269bool CpModelPresolver::CanonicalizeLinearExpression(
2270 const ConstraintProto& ct, LinearExpressionProto* exp) {
2271 return context_->CanonicalizeLinearExpression(ct.enforcement_literal(), exp);
2272}
2273
2274bool CpModelPresolver::CanonicalizeLinear(ConstraintProto* ct, bool* changed) {
2275 if (ct->constraint_case() != ConstraintProto::kLinear) return true;
2276 if (context_->ModelIsUnsat()) return false;
2277
2278 if (ct->linear().domain().empty()) {
2279 context_->UpdateRuleStats("linear: no domain");
2280 *changed = true;
2281 return MarkConstraintAsFalse(ct);
2282 }
2283
2284 *changed = context_->CanonicalizeLinearConstraint(ct);
2285 *changed |= DivideLinearByGcd(ct);
2286
2287 // For duplicate detection, we always make the first coeff positive.
2288 //
2289 // TODO(user): Move that to context_->CanonicalizeLinearConstraint(), and do
2290 // the same for LinearExpressionProto.
2291 if (!ct->linear().coeffs().empty() && ct->linear().coeffs(0) < 0) {
2292 for (int64_t& ref_coeff : *ct->mutable_linear()->mutable_coeffs()) {
2293 ref_coeff = -ref_coeff;
2294 }
2296 ct->mutable_linear());
2297 }
2298
2299 return true;
2300}
2301
2302bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto* ct) {
2303 if (ct->constraint_case() != ConstraintProto::kLinear ||
2304 context_->ModelIsUnsat()) {
2305 return false;
2306 }
2307
2308 absl::btree_set<int> index_to_erase;
2309 const int num_vars = ct->linear().vars().size();
2310 Domain rhs = ReadDomainFromProto(ct->linear());
2311
2312 // First pass. Process singleton column that are not in the objective. Note
2313 // that for postsolve, it is important that we process them in the same order
2314 // in which they will be removed.
2315 for (int i = 0; i < num_vars; ++i) {
2316 const int var = ct->linear().vars(i);
2317 const int64_t coeff = ct->linear().coeffs(i);
2318 CHECK(RefIsPositive(var));
2319 if (context_->VariableIsUniqueAndRemovable(var)) {
2320 // This is not needed for the code below, but in practice, removing
2321 // singleton with a large coefficient create holes in the constraint rhs
2322 // and we will need to add more variable to deal with that.
2323 // This works way better on timtab1CUTS.pb.gz for instance.
2324 if (std::abs(coeff) != 1) continue;
2325
2326 bool exact;
2327 const auto term_domain =
2328 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2329 if (!exact) continue;
2330
2331 // We do not do that if the domain of rhs becomes too complex.
2332 const Domain new_rhs = rhs.AdditionWith(term_domain);
2333 if (new_rhs.NumIntervals() > 100) continue;
2334
2335 // Note that we can't do that if we loose information in the
2336 // multiplication above because the new domain might not be as strict
2337 // as the initial constraint otherwise. TODO(user): because of the
2338 // addition, it might be possible to cover more cases though.
2339 context_->UpdateRuleStats("linear: singleton column");
2340 index_to_erase.insert(i);
2341 rhs = new_rhs;
2342 continue;
2343 }
2344 }
2345
2346 // If the whole linear is independent from the rest of the problem, we
2347 // can solve it now. If it is enforced, then each variable will have two
2348 // values: Its minimum one and one minimizing the objective under the
2349 // constraint. The switch can be controlled by a single Boolean.
2350 //
2351 // TODO(user): Cover more case like dedicated algorithm to solve for a small
2352 // number of variable that are faster than the DP we use here.
2353 if (index_to_erase.empty()) {
2354 int num_singletons = 0;
2355 for (const int var : ct->linear().vars()) {
2356 if (!RefIsPositive(var)) break;
2357 if (!context_->VariableWithCostIsUniqueAndRemovable(var) &&
2358 !context_->VariableIsUniqueAndRemovable(var)) {
2359 break;
2360 }
2361 ++num_singletons;
2362 }
2363 if (num_singletons == num_vars) {
2364 // Try to solve the equation.
2365 std::vector<Domain> domains;
2366 std::vector<int64_t> coeffs;
2367 std::vector<int64_t> costs;
2368 for (int i = 0; i < num_vars; ++i) {
2369 const int var = ct->linear().vars(i);
2370 CHECK(RefIsPositive(var));
2371 domains.push_back(context_->DomainOf(var));
2372 coeffs.push_back(ct->linear().coeffs(i));
2373 costs.push_back(context_->ObjectiveCoeff(var));
2374 }
2375 BasicKnapsackSolver solver;
2376 const auto& result = solver.Solve(domains, coeffs, costs,
2377 ReadDomainFromProto(ct->linear()));
2378 if (!result.solved) {
2379 context_->UpdateRuleStats(
2380 "TODO independent linear: minimize single linear constraint");
2381 } else if (result.infeasible) {
2382 context_->UpdateRuleStats(
2383 "independent linear: no DP solution to simple constraint");
2384 return MarkConstraintAsFalse(ct);
2385 } else {
2386 if (ct->enforcement_literal().empty()) {
2387 // Just fix everything.
2388 context_->UpdateRuleStats("independent linear: solved by DP");
2389 for (int i = 0; i < num_vars; ++i) {
2390 if (!context_->IntersectDomainWith(ct->linear().vars(i),
2391 Domain(result.solution[i]))) {
2392 return false;
2393 }
2394 }
2395 return RemoveConstraint(ct);
2396 }
2397
2398 // Each variable will take two values according to a single Boolean.
2399 int indicator;
2400 if (ct->enforcement_literal().size() == 1) {
2401 indicator = ct->enforcement_literal(0);
2402 } else {
2403 indicator =
2404 context_->NewBoolVarWithConjunction(ct->enforcement_literal());
2405 auto* new_ct = context_->working_model->add_constraints();
2406 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2407 new_ct->mutable_bool_or()->add_literals(indicator);
2408 context_->UpdateNewConstraintsVariableUsage();
2409 }
2410 for (int i = 0; i < num_vars; ++i) {
2411 const int64_t best_value =
2412 costs[i] > 0 ? domains[i].Min() : domains[i].Max();
2413 const int64_t other_value = result.solution[i];
2414 if (best_value == other_value) {
2415 if (!context_->IntersectDomainWith(ct->linear().vars(i),
2416 Domain(best_value))) {
2417 return false;
2418 }
2419 continue;
2420 }
2421 solution_crush_.SetVarToConditionalValue(
2422 ct->linear().vars(i), {indicator}, other_value, best_value);
2423 if (RefIsPositive(indicator)) {
2424 if (!context_->StoreAffineRelation(ct->linear().vars(i), indicator,
2425 other_value - best_value,
2426 best_value)) {
2427 return false;
2428 }
2429 } else {
2430 if (!context_->StoreAffineRelation(
2431 ct->linear().vars(i), PositiveRef(indicator),
2432 best_value - other_value, other_value)) {
2433 return false;
2434 }
2435 }
2436 }
2437 context_->UpdateRuleStats(
2438 "independent linear: with enforcement, but solved by DP");
2439 return RemoveConstraint(ct);
2440 }
2441 }
2442 }
2443
2444 // If we didn't find any, look for the one appearing in the objective.
2445 if (index_to_erase.empty()) {
2446 // Note that we only do that if we have a non-reified equality.
2447 if (context_->params().presolve_substitution_level() <= 0) return false;
2448 if (!ct->enforcement_literal().empty()) return false;
2449
2450 // If it is possible to do so, note that we can transform constraint into
2451 // equalities in PropagateDomainsInLinear().
2452 if (rhs.Min() != rhs.Max()) return false;
2453
2454 for (int i = 0; i < num_vars; ++i) {
2455 const int var = ct->linear().vars(i);
2456 const int64_t coeff = ct->linear().coeffs(i);
2457 CHECK(RefIsPositive(var));
2458
2459 // If the variable appear only in the objective and we have an equality,
2460 // we can transfer the cost to the rest of the linear expression, and
2461 // remove that variable. Note that this do not remove any feasible
2462 // solution and is not a "dual" reduction.
2463 //
2464 // Note that is similar to the substitution code in PresolveLinear() but
2465 // it doesn't require the variable to be implied free since we do not
2466 // remove the constraints afterwards, just the variable.
2467 if (!context_->VariableWithCostIsUnique(var)) continue;
2468 DCHECK(context_->ObjectiveMap().contains(var));
2469
2470 // We only support substitution that does not require to multiply the
2471 // objective by some factor.
2472 //
2473 // TODO(user): If the objective is a single variable, we can actually
2474 // "absorb" any factor into the objective scaling.
2475 const int64_t objective_coeff = context_->ObjectiveMap().at(var);
2476 CHECK_NE(coeff, 0);
2477 if (objective_coeff % coeff != 0) continue;
2478
2479 // TODO(user): We have an issue if objective coeff is not one, because
2480 // the RecomputeSingletonObjectiveDomain() do not properly put holes
2481 // in the objective domain, which might cause an issue. Note that this
2482 // presolve rule is actually almost never applied on the miplib.
2483 if (std::abs(objective_coeff) != 1) continue;
2484
2485 // We do not do that if the domain of rhs becomes too complex.
2486 bool exact;
2487 const auto term_domain =
2488 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2489 if (!exact) continue;
2490 const Domain new_rhs = rhs.AdditionWith(term_domain);
2491 if (new_rhs.NumIntervals() > 100) continue;
2492
2493 // Special case: If the objective was a single variable, we can transfer
2494 // the domain of var to the objective, and just completely remove this
2495 // equality constraint.
2496 //
2497 // TODO(user): Maybe if var has a complex domain, we might not want to
2498 // substitute it?
2499 if (context_->ObjectiveMap().size() == 1) {
2500 // This make sure the domain of var is restricted and the objective
2501 // domain updated.
2502 if (!context_->RecomputeSingletonObjectiveDomain()) {
2503 return true;
2504 }
2505
2506 // The function above might fix var, in which case, we just abort.
2507 if (context_->IsFixed(var)) continue;
2508
2509 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2510 if (context_->ModelIsUnsat()) return true;
2511 continue;
2512 }
2513
2514 context_->UpdateRuleStats("linear: singleton column define objective.");
2515 context_->MarkVariableAsRemoved(var);
2516 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2517 return RemoveConstraint(ct);
2518 }
2519
2520 // On supportcase20, this transformation make the LP relaxation way worse.
2521 // TODO(user): understand why.
2522 if (true) continue;
2523
2524 // Update the objective and remove the variable from its equality
2525 // constraint by expanding its rhs. This might fail if the new linear
2526 // objective expression can lead to overflow.
2527 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2528 if (context_->ModelIsUnsat()) return true;
2529 continue;
2530 }
2531
2532 context_->UpdateRuleStats(
2533 "linear: singleton column in equality and in objective.");
2534 rhs = new_rhs;
2535 index_to_erase.insert(i);
2536 break;
2537 }
2538 }
2539 if (index_to_erase.empty()) return false;
2540
2541 // Tricky: If we have a singleton variable in an enforced constraint, and at
2542 // postsolve the enforcement is false, we might just ignore the constraint.
2543 // This is fine, but we still need to assign any removed variable to a
2544 // feasible value, otherwise later postsolve rules might not work correctly.
2545 // Adding these linear1 achieve that.
2546 //
2547 // TODO(user): Alternatively, we could copy the constraint without the
2548 // enforcement to the mapping model, since singleton variable are supposed
2549 // to always have a feasible value anyway.
2550 if (!ct->enforcement_literal().empty()) {
2551 for (const int i : index_to_erase) {
2552 const int var = ct->linear().vars(i);
2553 auto* new_lin =
2554 context_->NewMappingConstraint(__FILE__, __LINE__)->mutable_linear();
2555 new_lin->add_vars(var);
2556 new_lin->add_coeffs(1);
2557 FillDomainInProto(context_->DomainOf(var), new_lin);
2558 }
2559 }
2560
2561 // TODO(user): we could add the constraint to mapping_model only once
2562 // instead of adding a reduced version of it each time a new singleton
2563 // variable appear in the same constraint later. That would work but would
2564 // also force the postsolve to take search decisions...
2565 if (absl::GetFlag(FLAGS_cp_model_debug_postsolve)) {
2566 auto* new_ct = context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2567 const std::string name(new_ct->name());
2568 *new_ct = *ct;
2569 new_ct->set_name(absl::StrCat(ct->name(), " copy ", name));
2570 } else {
2571 *context_->NewMappingConstraint(*ct, __FILE__, __LINE__) = *ct;
2572 }
2573
2574 int new_size = 0;
2575 for (int i = 0; i < num_vars; ++i) {
2576 if (index_to_erase.count(i)) {
2577 context_->MarkVariableAsRemoved(ct->linear().vars(i));
2578 continue;
2579 }
2580 ct->mutable_linear()->set_coeffs(new_size, ct->linear().coeffs(i));
2581 ct->mutable_linear()->set_vars(new_size, ct->linear().vars(i));
2582 ++new_size;
2583 }
2584 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
2585 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
2586 FillDomainInProto(rhs, ct->mutable_linear());
2587 DivideLinearByGcd(ct);
2588 return true;
2589}
2590
2591// If the gcd of all but one term (with index target_index) is not one, we can
2592// rewrite the last term using an affine representative.
2593bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality(
2594 int target_index, ConstraintProto* ct) {
2595 int64_t gcd = 0;
2596 const int num_variables = ct->linear().vars().size();
2597 for (int i = 0; i < num_variables; ++i) {
2598 if (i == target_index) continue;
2599 const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2600 gcd = std::gcd(gcd, magnitude);
2601 if (gcd == 1) return false;
2602 }
2603
2604 // If we take the constraint % gcd, we have
2605 // ref * coeff % gcd = rhs % gcd
2606 CHECK_GT(gcd, 1);
2607 const int ref = ct->linear().vars(target_index);
2608 const int64_t coeff = ct->linear().coeffs(target_index);
2609 const int64_t rhs = ct->linear().domain(0);
2610
2611 // This should have been processed before by just dividing the whole
2612 // constraint by the gcd.
2613 if (coeff % gcd == 0) return false;
2614
2615 if (!context_->CanonicalizeAffineVariable(ref, coeff, gcd, rhs)) {
2616 return false;
2617 }
2618
2619 // We use the new variable in the constraint.
2620 // Note that we will divide everything by the gcd too.
2621 bool changed = false;
2622 (void)CanonicalizeLinear(ct, &changed);
2623 return changed;
2624}
2625
2626namespace {
2627
2628bool IsLinearEqualityConstraint(const ConstraintProto& ct) {
2629 return ct.constraint_case() == ConstraintProto::kLinear &&
2630 ct.linear().domain().size() == 2 &&
2631 ct.linear().domain(0) == ct.linear().domain(1) &&
2632 ct.enforcement_literal().empty();
2633}
2634
2635} // namespace
2636
2637// Any equality must be true modulo n.
2638//
2639// If the gcd of all but one term is not one, we can rewrite the last term using
2640// an affine representative by considering the equality modulo that gcd.
2641// As an heuristic, we only test the smallest term or small primes 2, 3, and 5.
2642//
2643// We also handle the special case of having two non-zero literals modulo 2.
2644//
2645// TODO(user): Use more complex algo to detect all the cases? By splitting the
2646// constraint in two, and computing the gcd of each halves, we can reduce the
2647// problem to two problem of half size. So at least we can do it in O(n log n).
2648bool CpModelPresolver::PresolveLinearEqualityWithModulo(ConstraintProto* ct) {
2649 if (context_->ModelIsUnsat()) return false;
2650 if (!IsLinearEqualityConstraint(*ct)) return false;
2651
2652 const int num_variables = ct->linear().vars().size();
2653 if (num_variables < 2) return false;
2654
2655 std::vector<int> mod2_indices;
2656 std::vector<int> mod3_indices;
2657 std::vector<int> mod5_indices;
2658
2659 int64_t min_magnitude;
2660 int num_smallest = 0;
2661 int smallest_index;
2662 for (int i = 0; i < num_variables; ++i) {
2663 const int64_t magnitude = std::abs(ct->linear().coeffs(i));
2664 if (num_smallest == 0 || magnitude < min_magnitude) {
2665 min_magnitude = magnitude;
2666 num_smallest = 1;
2667 smallest_index = i;
2668 } else if (magnitude == min_magnitude) {
2669 ++num_smallest;
2670 }
2671
2672 if (magnitude % 2 != 0) mod2_indices.push_back(i);
2673 if (magnitude % 3 != 0) mod3_indices.push_back(i);
2674 if (magnitude % 5 != 0) mod5_indices.push_back(i);
2675 }
2676
2677 if (mod2_indices.size() == 2) {
2678 bool ok = true;
2679 std::vector<int> literals;
2680 for (const int i : mod2_indices) {
2681 const int ref = ct->linear().vars(i);
2682 if (!context_->CanBeUsedAsLiteral(ref)) {
2683 ok = false;
2684 break;
2685 }
2686 literals.push_back(ref);
2687 }
2688 if (ok) {
2689 const int64_t rhs = std::abs(ct->linear().domain(0));
2690 context_->UpdateRuleStats("linear: only two odd Booleans in equality");
2691 if (rhs % 2) {
2692 if (!context_->StoreBooleanEqualityRelation(literals[0],
2693 NegatedRef(literals[1]))) {
2694 return false;
2695 }
2696 } else {
2697 if (!context_->StoreBooleanEqualityRelation(literals[0], literals[1])) {
2698 return false;
2699 }
2700 }
2701 }
2702 }
2703
2704 // TODO(user): More than one reduction might be possible, so we will need
2705 // to call this again if we apply any of these reduction.
2706 if (mod2_indices.size() == 1) {
2707 return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0], ct);
2708 }
2709 if (mod3_indices.size() == 1) {
2710 return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0], ct);
2711 }
2712 if (mod5_indices.size() == 1) {
2713 return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0], ct);
2714 }
2715 if (num_smallest == 1) {
2716 return AddVarAffineRepresentativeFromLinearEquality(smallest_index, ct);
2717 }
2718
2719 return false;
2720}
2721
2722bool CpModelPresolver::PresolveLinearOfSizeOne(ConstraintProto* ct) {
2723 CHECK_EQ(ct->linear().vars().size(), 1);
2724 CHECK(RefIsPositive(ct->linear().vars(0)));
2725
2726 const int var = ct->linear().vars(0);
2727 const Domain var_domain = context_->DomainOf(var);
2728 const Domain rhs = ReadDomainFromProto(ct->linear())
2729 .InverseMultiplicationBy(ct->linear().coeffs(0))
2730 .IntersectionWith(var_domain);
2731 if (rhs.IsEmpty()) {
2732 context_->UpdateRuleStats("linear1: infeasible");
2733 return MarkConstraintAsFalse(ct);
2734 }
2735 if (rhs == var_domain) {
2736 context_->UpdateRuleStats("linear1: always true");
2737 return RemoveConstraint(ct);
2738 }
2739
2740 // We can always canonicalize the constraint to a coefficient of 1.
2741 // Note that this should never trigger as we usually divide by gcd already.
2742 if (ct->linear().coeffs(0) != 1) {
2743 context_->UpdateRuleStats("linear1: canonicalized");
2744 ct->mutable_linear()->set_coeffs(0, 1);
2745 FillDomainInProto(rhs, ct->mutable_linear());
2746 }
2747
2748 // Size one constraint with no enforcement?
2749 if (!HasEnforcementLiteral(*ct)) {
2750 context_->UpdateRuleStats("linear1: without enforcement");
2751 if (!context_->IntersectDomainWith(var, rhs)) return false;
2752 return RemoveConstraint(ct);
2753 }
2754
2755 // This is just an implication, lets convert it right away.
2756 if (context_->CanBeUsedAsLiteral(var)) {
2757 DCHECK(rhs.IsFixed());
2758 if (rhs.FixedValue() == 1) {
2759 ct->mutable_bool_and()->add_literals(var);
2760 } else {
2761 CHECK_EQ(rhs.FixedValue(), 0);
2762 ct->mutable_bool_and()->add_literals(NegatedRef(var));
2763 }
2764
2765 // No var <-> constraint graph changes.
2766 // But this is no longer a linear1.
2767 return true;
2768 }
2769
2770 // Detect encoding.
2771 bool changed = false;
2772 if (ct->enforcement_literal().size() == 1) {
2773 // If we already have an encoding literal, this constraint is really
2774 // an implication.
2775 int lit = ct->enforcement_literal(0);
2776
2777 // For correctness below, it is important lit is the canonical literal,
2778 // otherwise we might remove the constraint even though it is the one
2779 // defining an encoding literal.
2780 const int representative = context_->GetLiteralRepresentative(lit);
2781 if (lit != representative) {
2782 lit = representative;
2783 ct->set_enforcement_literal(0, lit);
2784 context_->UpdateRuleStats("linear1: remapped enforcement literal");
2785 changed = true;
2786 }
2787
2788 if (rhs.IsFixed()) {
2789 const int64_t value = rhs.FixedValue();
2790 int encoding_lit;
2791 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2792 if (lit == encoding_lit) return changed;
2793 context_->AddImplication(lit, encoding_lit);
2794 context_->UpdateNewConstraintsVariableUsage();
2795 ct->Clear();
2796 context_->UpdateRuleStats("linear1: transformed to implication");
2797 return true;
2798 } else {
2799 if (context_->StoreLiteralImpliesVarEqValue(lit, var, value)) {
2800 // The domain is not actually modified, but we want to rescan the
2801 // constraints linked to this variable.
2802 context_->modified_domains.Set(var);
2803 }
2804 context_->UpdateNewConstraintsVariableUsage();
2805 }
2806 return changed;
2807 }
2808
2809 const Domain complement = rhs.Complement().IntersectionWith(var_domain);
2810 if (complement.IsFixed()) {
2811 const int64_t value = complement.FixedValue();
2812 int encoding_lit;
2813 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2814 if (NegatedRef(lit) == encoding_lit) return changed;
2815 context_->AddImplication(lit, NegatedRef(encoding_lit));
2816 context_->UpdateNewConstraintsVariableUsage();
2817 ct->Clear();
2818 context_->UpdateRuleStats("linear1: transformed to implication");
2819 return true;
2820 } else {
2821 if (context_->StoreLiteralImpliesVarNEqValue(lit, var, value)) {
2822 // The domain is not actually modified, but we want to rescan the
2823 // constraints linked to this variable.
2824 context_->modified_domains.Set(var);
2825 }
2826 context_->UpdateNewConstraintsVariableUsage();
2827 }
2828 return changed;
2829 }
2830 }
2831
2832 return changed;
2833}
2834
2835bool CpModelPresolver::PresolveLinearOfSizeTwo(ConstraintProto* ct) {
2836 DCHECK_EQ(ct->linear().vars().size(), 2);
2837
2838 const LinearConstraintProto& arg = ct->linear();
2839 const int var1 = arg.vars(0);
2840 const int var2 = arg.vars(1);
2841 const int64_t coeff1 = arg.coeffs(0);
2842 const int64_t coeff2 = arg.coeffs(1);
2843
2844 // Starts by updating our hash map of known relation.
2845 {
2846 const LinearExpression2 expr2 =
2847 GetLinearExpression2FromProto(var1, coeff1, var2, coeff2);
2848 const IntegerValue lb(arg.domain(0));
2849 const IntegerValue ub(arg.domain(arg.domain().size() - 1));
2850
2851 const RelationStatus status = known_linear2_.GetStatus(expr2, lb, ub);
2852 if (status == RelationStatus::IS_TRUE) {
2853 // Note that we don't track what constraint implied the relation, so we
2854 // cannot remove this constraint even if the relation is already known.
2855 //
2856 // However since we only add it if the relation is not
2857 // enforced, this should be correct.
2858 //
2859 // Tricky: If the constraint domain is not simple, we cannot really deduce
2860 // anything.
2861 if (!ct->enforcement_literal().empty() &&
2862 ct->linear().domain().size() == 2) {
2863 context_->UpdateRuleStats("linear2: already known enforced relation");
2864 return RemoveConstraint(ct);
2865 }
2866 } else if (status == RelationStatus::IS_FALSE) {
2867 context_->UpdateRuleStats("linear2: infeasible relation");
2868 return MarkConstraintAsFalse(ct);
2869 } else if (ct->enforcement_literal().empty()) {
2870 known_linear2_.Add(expr2, lb, ub);
2871 }
2872 }
2873
2874 // If it is not an equality, we only presolve the constraint if one of
2875 // the variable is Boolean. Note that if both are Boolean, then a similar
2876 // reduction is done by PresolveLinearOnBooleans(). If we have an equality,
2877 // then the code below will do something stronger than this.
2878 //
2879 // TODO(user): We should probably instead generalize the code of
2880 // ExtractEnforcementLiteralFromLinearConstraint(), or just temporary
2881 // propagate domain of enforced linear constraints, to detect Boolean that
2882 // must be true or false. This way we can do the same for longer constraints.
2883 const bool is_equality =
2884 arg.domain_size() == 2 && arg.domain(0) == arg.domain(1);
2885 if (!is_equality) {
2886 int lit, var;
2887 int64_t value_on_true, coeff;
2888 if (context_->CanBeUsedAsLiteral(var1)) {
2889 lit = var1;
2890 value_on_true = coeff1;
2891 var = var2;
2892 coeff = coeff2;
2893 } else if (context_->CanBeUsedAsLiteral(var2)) {
2894 lit = var2;
2895 value_on_true = coeff2;
2896 var = var1;
2897 coeff = coeff1;
2898 } else {
2899 return false;
2900 }
2901 if (!RefIsPositive(lit)) return false;
2902
2903 const Domain rhs = ReadDomainFromProto(ct->linear());
2904 const Domain rhs_if_true =
2905 rhs.AdditionWith(Domain(-value_on_true)).InverseMultiplicationBy(coeff);
2906 const Domain rhs_if_false = rhs.InverseMultiplicationBy(coeff);
2907 const bool implied_false =
2908 context_->DomainOf(var).IntersectionWith(rhs_if_true).IsEmpty();
2909 const bool implied_true =
2910 context_->DomainOf(var).IntersectionWith(rhs_if_false).IsEmpty();
2911 if (implied_true && implied_false) {
2912 context_->UpdateRuleStats("linear2: infeasible.");
2913 return MarkConstraintAsFalse(ct);
2914 } else if (implied_true) {
2915 context_->UpdateRuleStats("linear2: Boolean with one feasible value.");
2916
2917 // => true.
2918 ConstraintProto* new_ct = context_->working_model->add_constraints();
2919 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2920 new_ct->mutable_bool_and()->add_literals(lit);
2921 context_->UpdateNewConstraintsVariableUsage();
2922
2923 // Rewrite to => var in rhs_if_true.
2924 ct->mutable_linear()->Clear();
2925 ct->mutable_linear()->add_vars(var);
2926 ct->mutable_linear()->add_coeffs(1);
2927 FillDomainInProto(rhs_if_true, ct->mutable_linear());
2928 return PresolveLinearOfSizeOne(ct) || true;
2929 } else if (implied_false) {
2930 context_->UpdateRuleStats("linear2: Boolean with one feasible value.");
2931
2932 // => false.
2933 ConstraintProto* new_ct = context_->working_model->add_constraints();
2934 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2935 new_ct->mutable_bool_and()->add_literals(NegatedRef(lit));
2936 context_->UpdateNewConstraintsVariableUsage();
2937
2938 // Rewrite to => var in rhs_if_false.
2939 ct->mutable_linear()->Clear();
2940 ct->mutable_linear()->add_vars(var);
2941 ct->mutable_linear()->add_coeffs(1);
2942 FillDomainInProto(rhs_if_false, ct->mutable_linear());
2943 return PresolveLinearOfSizeOne(ct) || true;
2944 } else if (ct->enforcement_literal().empty() &&
2945 !context_->CanBeUsedAsLiteral(var)) {
2946 // We currently only do that if there are no enforcement and we don't have
2947 // two Booleans as this can be presolved differently. We expand it into
2948 // two linear1 constraint that have a chance to be merged with other
2949 // "encoding" constraints.
2950 context_->UpdateRuleStats("linear2: contains a Boolean.");
2951
2952 // lit => var \in rhs_if_true
2953 const Domain var_domain = context_->DomainOf(var);
2954 if (!var_domain.IsIncludedIn(rhs_if_true)) {
2955 ConstraintProto* new_ct = context_->working_model->add_constraints();
2956 new_ct->add_enforcement_literal(lit);
2957 new_ct->mutable_linear()->add_vars(var);
2958 new_ct->mutable_linear()->add_coeffs(1);
2959 FillDomainInProto(rhs_if_true.IntersectionWith(var_domain),
2960 new_ct->mutable_linear());
2961 }
2962
2963 // NegatedRef(lit) => var \in rhs_if_false
2964 if (!var_domain.IsIncludedIn(rhs_if_false)) {
2965 ConstraintProto* new_ct = context_->working_model->add_constraints();
2966 new_ct->add_enforcement_literal(NegatedRef(lit));
2967 new_ct->mutable_linear()->add_vars(var);
2968 new_ct->mutable_linear()->add_coeffs(1);
2969 FillDomainInProto(rhs_if_false.IntersectionWith(var_domain),
2970 new_ct->mutable_linear());
2971 }
2972
2973 context_->UpdateNewConstraintsVariableUsage();
2974 return RemoveConstraint(ct);
2975 }
2976
2977 // Code below require equality.
2978 context_->UpdateRuleStats("TODO linear2: contains a Boolean.");
2979 return false;
2980 }
2981
2982 // We have: enforcement => (coeff1 * v1 + coeff2 * v2 == rhs).
2983 const int64_t rhs = arg.domain(0);
2984 if (ct->enforcement_literal().empty()) {
2985 // Detect affine relation.
2986 //
2987 // TODO(user): it might be better to first add only the affine relation with
2988 // a coefficient of magnitude 1, and later the one with larger coeffs.
2989 bool added = false;
2990 if (coeff1 == 1) {
2991 added = context_->StoreAffineRelation(var1, var2, -coeff2, rhs);
2992 } else if (coeff2 == 1) {
2993 added = context_->StoreAffineRelation(var2, var1, -coeff1, rhs);
2994 } else if (coeff1 == -1) {
2995 added = context_->StoreAffineRelation(var1, var2, coeff2, -rhs);
2996 } else if (coeff2 == -1) {
2997 added = context_->StoreAffineRelation(var2, var1, coeff1, -rhs);
2998 } else {
2999 // In this case, we can solve the diophantine equation, and write
3000 // both x and y in term of a new affine representative z.
3001 //
3002 // Note that PresolveLinearEqualityWithModulo() will have the same effect.
3003 //
3004 // We can also decide to fully expand the equality if the variables
3005 // are fully encoded.
3006 context_->UpdateRuleStats("TODO linear2: ax + by = cte");
3007 }
3008 if (added) return RemoveConstraint(ct);
3009 } else {
3010 // We look ahead to detect solutions to ax + by == cte.
3011 int64_t a = coeff1;
3012 int64_t b = coeff2;
3013 int64_t cte = rhs;
3014 int64_t x0 = 0;
3015 int64_t y0 = 0;
3016 if (!SolveDiophantineEquationOfSizeTwo(a, b, cte, x0, y0)) {
3017 context_->UpdateRuleStats(
3018 "linear2: implied ax + by = cte has no solutions");
3019 return MarkConstraintAsFalse(ct);
3020 }
3021 const Domain reduced_domain =
3022 context_->DomainOf(var1)
3023 .AdditionWith(Domain(-x0))
3024 .InverseMultiplicationBy(b)
3025 .IntersectionWith(context_->DomainOf(var2)
3026 .AdditionWith(Domain(-y0))
3027 .InverseMultiplicationBy(-a));
3028
3029 if (reduced_domain.IsEmpty()) { // no solution
3030 context_->UpdateRuleStats(
3031 "linear2: implied ax + by = cte has no solutions");
3032 return MarkConstraintAsFalse(ct);
3033 }
3034
3035 if (reduced_domain.Size() == 1) {
3036 const int64_t z = reduced_domain.FixedValue();
3037 const int64_t value1 = x0 + b * z;
3038 const int64_t value2 = y0 - a * z;
3039
3040 DCHECK(context_->DomainContains(var1, value1));
3041 DCHECK(context_->DomainContains(var2, value2));
3042 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
3043
3044 ConstraintProto* imply1 = context_->working_model->add_constraints();
3045 *imply1->mutable_enforcement_literal() = ct->enforcement_literal();
3046 imply1->mutable_linear()->add_vars(var1);
3047 imply1->mutable_linear()->add_coeffs(1);
3048 imply1->mutable_linear()->add_domain(value1);
3049 imply1->mutable_linear()->add_domain(value1);
3050
3051 ConstraintProto* imply2 = context_->working_model->add_constraints();
3052 *imply2->mutable_enforcement_literal() = ct->enforcement_literal();
3053 imply2->mutable_linear()->add_vars(var2);
3054 imply2->mutable_linear()->add_coeffs(1);
3055 imply2->mutable_linear()->add_domain(value2);
3056 imply2->mutable_linear()->add_domain(value2);
3057 context_->UpdateRuleStats(
3058 "linear2: implied ax + by = cte has only one solution");
3059 context_->UpdateNewConstraintsVariableUsage();
3060 return RemoveConstraint(ct);
3061 }
3062 }
3063
3064 return false;
3065}
3066
3067bool CpModelPresolver::PresolveSmallLinear(ConstraintProto* ct) {
3068 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
3069 if (context_->ModelIsUnsat()) return false;
3070
3071 if (ct->linear().vars().empty()) {
3072 context_->UpdateRuleStats("linear: empty");
3073 const Domain rhs = ReadDomainFromProto(ct->linear());
3074 if (rhs.Contains(0)) {
3075 return RemoveConstraint(ct);
3076 } else {
3077 return MarkConstraintAsFalse(ct);
3078 }
3079 } else if (ct->linear().vars().size() == 1) {
3080 return PresolveLinearOfSizeOne(ct);
3081 } else if (ct->linear().vars().size() == 2) {
3082 return PresolveLinearOfSizeTwo(ct);
3083 }
3084
3085 return false;
3086}
3087
3088bool CpModelPresolver::PresolveDiophantine(ConstraintProto* ct) {
3089 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
3090 if (ct->linear().vars().size() <= 1) return false;
3091 if (context_->ModelIsUnsat()) return false;
3092 // The transformation can add extra variables, and creates duplicate solutions
3093 // when enumerate_all_solutions is true.
3094 if (context_->params().enumerate_all_solutions()) return false;
3095
3096 const LinearConstraintProto& linear_constraint = ct->linear();
3097 if (linear_constraint.domain_size() != 2) return false;
3098 if (linear_constraint.domain(0) != linear_constraint.domain(1)) return false;
3099
3100 std::vector<int64_t> lbs(linear_constraint.vars_size());
3101 std::vector<int64_t> ubs(linear_constraint.vars_size());
3102 for (int i = 0; i < linear_constraint.vars_size(); ++i) {
3103 lbs[i] = context_->MinOf(linear_constraint.vars(i));
3104 ubs[i] = context_->MaxOf(linear_constraint.vars(i));
3105 }
3106 DiophantineSolution diophantine_solution = SolveDiophantine(
3107 linear_constraint.coeffs(), linear_constraint.domain(0), lbs, ubs);
3108
3109 if (!diophantine_solution.has_solutions) {
3110 context_->UpdateRuleStats("diophantine: equality has no solutions");
3111 return MarkConstraintAsFalse(ct);
3112 }
3113 if (diophantine_solution.no_reformulation_needed) return false;
3114 // Only first coefficients of kernel_basis elements and special_solution could
3115 // overflow int64_t due to the reduction applied in SolveDiophantineEquation,
3116 for (const std::vector<absl::int128>& b : diophantine_solution.kernel_basis) {
3117 if (!IsNegatableInt64(b[0])) {
3118 context_->UpdateRuleStats(
3119 "diophantine: couldn't apply due to int64_t overflow");
3120 return false;
3121 }
3122 }
3123 if (!IsNegatableInt64(diophantine_solution.special_solution[0])) {
3124 context_->UpdateRuleStats(
3125 "diophantine: couldn't apply due to int64_t overflow");
3126 return false;
3127 }
3128
3129 const int num_replaced_variables =
3130 static_cast<int>(diophantine_solution.special_solution.size());
3131 const int num_new_variables =
3132 static_cast<int>(diophantine_solution.kernel_vars_lbs.size());
3133 DCHECK_EQ(num_new_variables + 1, num_replaced_variables);
3134 for (int i = 0; i < num_new_variables; ++i) {
3135 if (!IsNegatableInt64(diophantine_solution.kernel_vars_lbs[i]) ||
3136 !IsNegatableInt64(diophantine_solution.kernel_vars_ubs[i])) {
3137 context_->UpdateRuleStats(
3138 "diophantine: couldn't apply due to int64_t overflow");
3139 return false;
3140 }
3141 }
3142 // TODO(user): Make sure the newly generated linear constraint
3143 // satisfy our no-overflow precondition on the min/max activity.
3144 // We should check that the model still satisfy conditions in
3145 // 3/ortools/sat/cp_model_checker.cc;l=165;bpv=0
3146
3147 // Create new variables.
3148 std::vector<int> new_variables(num_new_variables);
3149 for (int i = 0; i < num_new_variables; ++i) {
3150 new_variables[i] = context_->working_model->variables_size();
3151 IntegerVariableProto* var = context_->working_model->add_variables();
3152 var->add_domain(
3153 static_cast<int64_t>(diophantine_solution.kernel_vars_lbs[i]));
3154 var->add_domain(
3155 static_cast<int64_t>(diophantine_solution.kernel_vars_ubs[i]));
3156 if (!ct->name().empty()) {
3157 var->set_name(absl::StrCat("u_diophantine_", ct->name(), "_", i));
3158 }
3159 }
3160
3161 // For i = 0, ..., num_replaced_variables - 1, creates
3162 // x[i] = special_solution[i]
3163 // + sum(kernel_basis[k][i]*y[k], max(1, i) <= k < vars.size - 1)
3164 // where:
3165 // y[k] is the newly created variable if 0 <= k < num_new_variables
3166 // y[k] = x[index_permutation[k + 1]] otherwise.
3167 for (int i = 0; i < num_replaced_variables; ++i) {
3168 ConstraintProto* identity = context_->working_model->add_constraints();
3169 LinearConstraintProto* lin = identity->mutable_linear();
3170 if (!ct->name().empty()) {
3171 identity->set_name(absl::StrCat("c_diophantine_", ct->name(), "_", i));
3172 }
3173 *identity->mutable_enforcement_literal() = ct->enforcement_literal();
3174 lin->add_vars(
3175 linear_constraint.vars(diophantine_solution.index_permutation[i]));
3176 lin->add_coeffs(1);
3177 lin->add_domain(
3178 static_cast<int64_t>(diophantine_solution.special_solution[i]));
3179 lin->add_domain(
3180 static_cast<int64_t>(diophantine_solution.special_solution[i]));
3181 for (int j = std::max(1, i); j < num_replaced_variables; ++j) {
3182 lin->add_vars(new_variables[j - 1]);
3183 lin->add_coeffs(
3184 -static_cast<int64_t>(diophantine_solution.kernel_basis[j - 1][i]));
3185 }
3186 for (int j = num_replaced_variables; j < linear_constraint.vars_size();
3187 ++j) {
3188 lin->add_vars(
3189 linear_constraint.vars(diophantine_solution.index_permutation[j]));
3190 lin->add_coeffs(
3191 -static_cast<int64_t>(diophantine_solution.kernel_basis[j - 1][i]));
3192 }
3193
3194 // TODO(user): The domain in the proto are not necessarily up to date so
3195 // this might be stricter than necessary. Fix? It shouldn't matter too much
3196 // though.
3197 if (PossibleIntegerOverflow(*(context_->working_model), lin->vars(),
3198 lin->coeffs())) {
3199 context_->UpdateRuleStats(
3200 "diophantine: couldn't apply due to overflowing activity of new "
3201 "constraints");
3202 // Cancel working_model changes.
3203 context_->working_model->mutable_constraints()->DeleteSubrange(
3204 context_->working_model->constraints_size() - i - 1, i + 1);
3205 context_->working_model->mutable_variables()->DeleteSubrange(
3206 context_->working_model->variables_size() - num_new_variables,
3207 num_new_variables);
3208 return false;
3209 }
3210 }
3211 context_->InitializeNewDomains();
3212 // Scan the new constraints added above in reverse order so that the hint of
3213 // `new_variables[k]` can be computed from the hint of the existing variables
3214 // and from the hints of `new_variables[k']`, with k' > k.
3215 const int num_constraints = context_->working_model->constraints_size();
3216 for (int i = 0; i < num_replaced_variables; ++i) {
3217 const LinearConstraintProto& linear =
3218 context_->working_model->constraints(num_constraints - 1 - i).linear();
3219 DCHECK(linear.domain_size() == 2 && linear.domain(0) == linear.domain(1));
3220 solution_crush_.SetVarToLinearConstraintSolution(
3221 std::nullopt, linear.vars(), linear.coeffs(), linear.domain(0));
3222 }
3223
3224 if (VLOG_IS_ON(2)) {
3225 std::string log_eq = absl::StrCat(linear_constraint.domain(0), " = ");
3226 const int terms_to_show = std::min<int>(15, linear_constraint.vars_size());
3227 for (int i = 0; i < terms_to_show; ++i) {
3228 if (i > 0) absl::StrAppend(&log_eq, " + ");
3229 absl::StrAppend(
3230 &log_eq,
3231 linear_constraint.coeffs(diophantine_solution.index_permutation[i]),
3232 " x",
3233 linear_constraint.vars(diophantine_solution.index_permutation[i]));
3234 }
3235 if (terms_to_show < linear_constraint.vars_size()) {
3236 absl::StrAppend(&log_eq, "+ ... (", linear_constraint.vars_size(),
3237 " terms)");
3238 }
3239 VLOG(2) << "[Diophantine] " << log_eq;
3240 }
3241
3242 context_->UpdateRuleStats("diophantine: reformulated equality");
3243 context_->UpdateNewConstraintsVariableUsage();
3244 return RemoveConstraint(ct);
3245}
3246
3247// This tries to decompose the constraint into coeff * part1 + part2 and show
3248// that the value that part2 take is not important, thus the constraint can
3249// only be transformed on a constraint on the first part.
3250//
3251// TODO(user): Improve !! we miss simple case like x + 47 y + 50 z >= 50
3252// for positive variables. We should remove x, and ideally we should rewrite
3253// this as y + 2z >= 2 if we can show that its relaxation is just better?
3254// We should at least see that it is the same as 47y + 50 z >= 48.
3255//
3256// TODO(user): One easy algo is to first remove all enforcement term (even
3257// non-Boolean one) before applying the algo here and then re-linearize the
3258// non-Boolean terms.
3259void CpModelPresolver::TryToReduceCoefficientsOfLinearConstraint(
3260 int c, ConstraintProto* ct) {
3261 if (ct->constraint_case() != ConstraintProto::kLinear) return;
3262 if (context_->ModelIsUnsat()) return;
3263
3264 // Only consider "simple" constraints.
3265 const LinearConstraintProto& lin = ct->linear();
3266 if (lin.domain().size() != 2) return;
3267 const Domain rhs = ReadDomainFromProto(lin);
3268
3269 // Precompute a bunch of quantities and "canonicalize" the constraint.
3270 int64_t lb_sum = 0;
3271 int64_t ub_sum = 0;
3272 int64_t max_variation = 0;
3273
3274 rd_entries_.clear();
3275 rd_magnitudes_.clear();
3276 rd_lbs_.clear();
3277 rd_ubs_.clear();
3278
3279 int64_t max_magnitude = 0;
3280 const int num_terms = lin.vars().size();
3281 for (int i = 0; i < num_terms; ++i) {
3282 const int64_t coeff = lin.coeffs(i);
3283 const int64_t magnitude = std::abs(lin.coeffs(i));
3284 if (magnitude == 0) continue;
3285 max_magnitude = std::max(max_magnitude, magnitude);
3286
3287 int64_t lb;
3288 int64_t ub;
3289 if (coeff > 0) {
3290 lb = context_->MinOf(lin.vars(i));
3291 ub = context_->MaxOf(lin.vars(i));
3292 } else {
3293 lb = -context_->MaxOf(lin.vars(i));
3294 ub = -context_->MinOf(lin.vars(i));
3295 }
3296 lb_sum += lb * magnitude;
3297 ub_sum += ub * magnitude;
3298
3299 // Abort if fixed term, that might mess up code below.
3300 if (lb == ub) return;
3301
3302 rd_lbs_.push_back(lb);
3303 rd_ubs_.push_back(ub);
3304 rd_magnitudes_.push_back(magnitude);
3305 rd_entries_.push_back({magnitude, magnitude * (ub - lb), i});
3306 max_variation += rd_entries_.back().max_variation;
3307 }
3308
3309 // Mark trivially false constraint as such. This should have been already
3310 // done, but we require non-negative quantity below.
3311 if (lb_sum > rhs.Max() || rhs.Min() > ub_sum) {
3312 (void)MarkConstraintAsFalse(ct);
3313 context_->UpdateConstraintVariableUsage(c);
3314 return;
3315 }
3316 const IntegerValue rhs_ub(CapSub(rhs.Max(), lb_sum));
3317 const IntegerValue rhs_lb(CapSub(ub_sum, rhs.Min()));
3318 const bool use_ub = max_variation > rhs_ub;
3319 const bool use_lb = max_variation > rhs_lb;
3320 if (!use_ub && !use_lb) {
3321 (void)RemoveConstraint(ct);
3322 context_->UpdateConstraintVariableUsage(c);
3323 return;
3324 }
3325
3326 // No point doing more work for constraint with all coeff at +/-1.
3327 if (max_magnitude <= 1) return;
3328
3329 // TODO(user): All the lb/ub_feasible/infeasible class are updated in
3330 // exactly the same way. Find a more efficient algo?
3331 if (use_lb) {
3332 lb_feasible_.Reset(rhs_lb.value());
3333 lb_infeasible_.Reset(rhs.Min() - lb_sum - 1);
3334 }
3335 if (use_ub) {
3336 ub_feasible_.Reset(rhs_ub.value());
3337 ub_infeasible_.Reset(ub_sum - rhs.Max() - 1);
3338 }
3339
3340 // Process entries by decreasing magnitude. Update max_error to correspond
3341 // only to the sum of the not yet processed terms.
3342 uint64_t gcd = 0;
3343 int64_t max_error = max_variation;
3344 std::stable_sort(rd_entries_.begin(), rd_entries_.end(),
3345 [](const RdEntry& a, const RdEntry& b) {
3346 return a.magnitude > b.magnitude;
3347 });
3348 int64_t range = 0;
3349 rd_divisors_.clear();
3350 for (int i = 0; i < rd_entries_.size(); ++i) {
3351 const RdEntry& e = rd_entries_[i];
3352 gcd = std::gcd(gcd, e.magnitude);
3353 max_error -= e.max_variation;
3354
3355 // We regroup all term with the same coefficient into one.
3356 //
3357 // TODO(user): I am not sure there is no possible simplification across two
3358 // term with the same coeff, but it should be rare if it ever happens.
3359 range += e.max_variation / e.magnitude;
3360 if (i + 1 < rd_entries_.size() &&
3361 e.magnitude == rd_entries_[i + 1].magnitude) {
3362 continue;
3363 }
3364 const int64_t saved_range = range;
3365 range = 0;
3366
3367 if (e.magnitude > 1) {
3368 if ((!use_ub ||
3369 max_error <= PositiveRemainder(rhs_ub, IntegerValue(e.magnitude))) &&
3370 (!use_lb ||
3371 max_error <= PositiveRemainder(rhs_lb, IntegerValue(e.magnitude)))) {
3372 rd_divisors_.push_back(e.magnitude);
3373 }
3374 }
3375
3376 bool simplify_lb = false;
3377 if (use_lb) {
3378 lb_feasible_.AddMultiples(e.magnitude, saved_range);
3379 lb_infeasible_.AddMultiples(e.magnitude, saved_range);
3380
3381 // For a <= constraint, the max_feasible + error is still feasible.
3382 if (CapAdd(lb_feasible_.CurrentMax(), max_error) <=
3383 lb_feasible_.Bound()) {
3384 simplify_lb = true;
3385 }
3386 // For a <= constraint describing the infeasible set, the max_infeasible +
3387 // error is still infeasible.
3388 if (CapAdd(lb_infeasible_.CurrentMax(), max_error) <=
3389 lb_infeasible_.Bound()) {
3390 simplify_lb = true;
3391 }
3392 } else {
3393 simplify_lb = true;
3394 }
3395 bool simplify_ub = false;
3396 if (use_ub) {
3397 ub_feasible_.AddMultiples(e.magnitude, saved_range);
3398 ub_infeasible_.AddMultiples(e.magnitude, saved_range);
3399 if (CapAdd(ub_feasible_.CurrentMax(), max_error) <=
3400 ub_feasible_.Bound()) {
3401 simplify_ub = true;
3402 }
3403 if (CapAdd(ub_infeasible_.CurrentMax(), max_error) <=
3404 ub_infeasible_.Bound()) {
3405 simplify_ub = true;
3406 }
3407 } else {
3408 simplify_ub = true;
3409 }
3410
3411 if (max_error == 0) break; // Last term.
3412 if (simplify_lb && simplify_ub) {
3413 // We have a simplification since the second part can be ignored.
3414 context_->UpdateRuleStats("linear: remove irrelevant part");
3415 int64_t shift_lb = 0;
3416 int64_t shift_ub = 0;
3417 rd_vars_.clear();
3418 rd_coeffs_.clear();
3419 for (int j = 0; j <= i; ++j) {
3420 const int index = rd_entries_[j].index;
3421 const int64_t m = rd_magnitudes_[index];
3422 shift_lb += rd_lbs_[index] * m;
3423 shift_ub += rd_ubs_[index] * m;
3424 rd_vars_.push_back(lin.vars(index));
3425 rd_coeffs_.push_back(lin.coeffs(index));
3426 }
3427 LinearConstraintProto* mut_lin = ct->mutable_linear();
3428 mut_lin->mutable_vars()->Assign(rd_vars_.begin(), rd_vars_.end());
3429 mut_lin->mutable_coeffs()->Assign(rd_coeffs_.begin(), rd_coeffs_.end());
3430
3431 // The constraint become:
3432 // sum ci (X - lb) <= rhs_ub
3433 // sum ci (ub - X) <= rhs_lb
3434 // sum ci ub - rhs_lb <= sum ci X <= rhs_ub + sum ci lb.
3435 const int64_t new_rhs_lb =
3436 use_lb ? shift_ub - lb_feasible_.CurrentMax() : shift_lb;
3437 const int64_t new_rhs_ub =
3438 use_ub ? shift_lb + ub_feasible_.CurrentMax() : shift_ub;
3439 if (new_rhs_lb > new_rhs_ub) {
3440 (void)MarkConstraintAsFalse(ct);
3441 context_->UpdateConstraintVariableUsage(c);
3442 return;
3443 }
3444 FillDomainInProto(Domain(new_rhs_lb, new_rhs_ub), mut_lin);
3445 DivideLinearByGcd(ct);
3446 context_->UpdateConstraintVariableUsage(c);
3447 return;
3448 }
3449 }
3450
3451 if (gcd > 1) {
3452 // This might happen as a result of extra reduction after we already tried
3453 // this reduction.
3454 if (DivideLinearByGcd(ct)) {
3455 context_->UpdateConstraintVariableUsage(c);
3456 }
3457 return;
3458 }
3459
3460 // We didn't remove any irrelevant part, but we might be able to tighten
3461 // the constraint bound.
3462 if ((use_lb && lb_feasible_.CurrentMax() < lb_feasible_.Bound()) ||
3463 (use_ub && ub_feasible_.CurrentMax() < ub_feasible_.Bound())) {
3464 context_->UpdateRuleStats("linear: reduce rhs with DP");
3465 const int64_t new_rhs_lb =
3466 use_lb ? ub_sum - lb_feasible_.CurrentMax() : lb_sum;
3467 const int64_t new_rhs_ub =
3468 use_ub ? lb_sum + ub_feasible_.CurrentMax() : ub_sum;
3469 if (new_rhs_lb > new_rhs_ub) {
3470 (void)MarkConstraintAsFalse(ct);
3471 context_->UpdateConstraintVariableUsage(c);
3472 return;
3473 }
3474 FillDomainInProto(Domain(new_rhs_lb, new_rhs_ub), ct->mutable_linear());
3475 }
3476
3477 // Limit the number of "divisor" we try for approximate gcd.
3478 if (rd_divisors_.size() > 3) rd_divisors_.resize(3);
3479 for (const int64_t divisor : rd_divisors_) {
3480 // Try the <= side first.
3481 int64_t new_ub;
3483 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, rhs.Max(), &new_ub)) {
3484 continue;
3485 }
3486
3487 // The other side.
3488 int64_t minus_new_lb;
3489 for (int i = 0; i < rd_lbs_.size(); ++i) {
3490 std::swap(rd_lbs_[i], rd_ubs_[i]);
3491 rd_lbs_[i] = -rd_lbs_[i];
3492 rd_ubs_[i] = -rd_ubs_[i];
3493 }
3495 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, -rhs.Min(),
3496 &minus_new_lb)) {
3497 for (int i = 0; i < rd_lbs_.size(); ++i) {
3498 std::swap(rd_lbs_[i], rd_ubs_[i]);
3499 rd_lbs_[i] = -rd_lbs_[i];
3500 rd_ubs_[i] = -rd_ubs_[i];
3501 }
3502 continue;
3503 }
3504
3505 // Rewrite the constraint !
3506 context_->UpdateRuleStats("linear: simplify using approximate gcd");
3507 int new_size = 0;
3508 LinearConstraintProto* mutable_linear = ct->mutable_linear();
3509 for (int i = 0; i < lin.coeffs().size(); ++i) {
3510 const int64_t new_coeff =
3511 ClosestMultiple(lin.coeffs(i), divisor) / divisor;
3512 if (new_coeff == 0) continue;
3513 mutable_linear->set_vars(new_size, lin.vars(i));
3514 mutable_linear->set_coeffs(new_size, new_coeff);
3515 ++new_size;
3516 }
3517 mutable_linear->mutable_vars()->Truncate(new_size);
3518 mutable_linear->mutable_coeffs()->Truncate(new_size);
3519 const Domain new_rhs = Domain(-minus_new_lb, new_ub);
3520 if (new_rhs.IsEmpty()) {
3521 (void)MarkConstraintAsFalse(ct);
3522 } else {
3523 FillDomainInProto(new_rhs, mutable_linear);
3524 }
3525 context_->UpdateConstraintVariableUsage(c);
3526 return;
3527 }
3528}
3529
3530namespace {
3531
3532// In the equation terms + coeff * var_domain \included rhs, returns true if can
3533// we always fix rhs to its min value for any value in terms. It is okay to
3534// not be as generic as possible here.
3535bool RhsCanBeFixedToMin(int64_t coeff, const Domain& var_domain,
3536 const Domain& terms, const Domain& rhs) {
3537 if (var_domain.NumIntervals() != 1) return false;
3538 if (std::abs(coeff) != 1) return false;
3539
3540 // If for all values in terms, there is one value below rhs.Min(), then
3541 // because we add only one integer interval, if there is a feasible value, it
3542 // can be at rhs.Min().
3543 //
3544 // TODO(user): generalize to larger coeff magnitude if rhs is also a multiple
3545 // or if terms is a multiple.
3546 if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
3547 return true;
3548 }
3549 if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
3550 return true;
3551 }
3552 return false;
3553}
3554
3555bool RhsCanBeFixedToMax(int64_t coeff, const Domain& var_domain,
3556 const Domain& terms, const Domain& rhs) {
3557 if (var_domain.NumIntervals() != 1) return false;
3558 if (std::abs(coeff) != 1) return false;
3559
3560 if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
3561 return true;
3562 }
3563 if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
3564 return true;
3565 }
3566 return false;
3567}
3568
3569int FixLiteralFromSet(const absl::flat_hash_set<int>& literals_at_true,
3570 LinearConstraintProto* linear) {
3571 int new_size = 0;
3572 int num_fixed = 0;
3573 const int num_terms = linear->vars().size();
3574 int64_t shift = 0;
3575 for (int i = 0; i < num_terms; ++i) {
3576 const int var = linear->vars(i);
3577 const int64_t coeff = linear->coeffs(i);
3578 if (literals_at_true.contains(var)) {
3579 // Var is at one.
3580 shift += coeff;
3581 ++num_fixed;
3582 } else if (!literals_at_true.contains(NegatedRef(var))) {
3583 linear->set_vars(new_size, var);
3584 linear->set_coeffs(new_size, coeff);
3585 ++new_size;
3586 } else {
3587 ++num_fixed;
3588 // Else the variable is at zero.
3589 }
3590 }
3591 linear->mutable_vars()->Truncate(new_size);
3592 linear->mutable_coeffs()->Truncate(new_size);
3593 if (shift != 0) {
3594 FillDomainInProto(ReadDomainFromProto(*linear).AdditionWith(Domain(-shift)),
3595 linear);
3596 }
3597 return num_fixed;
3598}
3599
3600} // namespace
3601
3602void CpModelPresolver::ProcessAtMostOneAndLinear() {
3603 if (time_limit_->LimitReached()) return;
3604 if (context_->ModelIsUnsat()) return;
3605 if (context_->params().presolve_inclusion_work_limit() == 0) return;
3606 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
3607
3608 ActivityBoundHelper amo_in_linear;
3609 amo_in_linear.AddAllAtMostOnes(*context_->working_model);
3610
3611 int num_changes = 0;
3612 const int num_constraints = context_->working_model->constraints_size();
3613 for (int c = 0; c < num_constraints; ++c) {
3614 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
3615 if (ct->constraint_case() != ConstraintProto::kLinear) continue;
3616
3617 // We loop if the constraint changed.
3618 for (int i = 0; i < 5; ++i) {
3619 const int old_size = ct->linear().vars().size();
3620 const int old_enf_size = ct->enforcement_literal().size();
3621 ProcessOneLinearWithAmo(c, ct, &amo_in_linear);
3622 if (context_->ModelIsUnsat()) return;
3623 if (ct->constraint_case() != ConstraintProto::kLinear) break;
3624 if (ct->linear().vars().size() == old_size &&
3625 ct->enforcement_literal().size() == old_enf_size) {
3626 break;
3627 }
3628 ++num_changes;
3629 }
3630 }
3631
3632 timer.AddCounter("num_changes", num_changes);
3633}
3634
3635// TODO(user): Similarly amo and bool_or intersection or amo and enforcement
3636// literals list can be presolved.
3637//
3638// TODO(user): This is stronger than the fully included case. Avoid having
3639// the second code?
3640void CpModelPresolver::ProcessOneLinearWithAmo(int ct_index,
3641 ConstraintProto* ct,
3642 ActivityBoundHelper* helper) {
3643 if (ct->constraint_case() != ConstraintProto::kLinear) return;
3644 if (ct->linear().vars().size() <= 1) return;
3645
3646 // TODO(user): It is possible in some corner-case that the linear constraint
3647 // is NOT canonicalized. This is because we might detect equivalence here and
3648 // we will continue with ProcessOneLinearWithAmo() in the parent loop.
3649 tmp_terms_.clear();
3650 temp_ct_.Clear();
3651 Domain non_boolean_domain(0);
3652 const int initial_size = ct->linear().vars().size();
3653 int64_t min_magnitude = std::numeric_limits<int64_t>::max();
3654 int64_t max_magnitude = 0;
3655 for (int i = 0; i < initial_size; ++i) {
3656 // TODO(user): Just do not use negative reference in linear!
3657 int ref = ct->linear().vars(i);
3658 int64_t coeff = ct->linear().coeffs(i);
3659 if (!RefIsPositive(ref)) {
3660 ref = NegatedRef(ref);
3661 coeff = -coeff;
3662 }
3663 if (context_->CanBeUsedAsLiteral(ref)) {
3664 tmp_terms_.push_back({ref, coeff});
3665 min_magnitude = std::min(min_magnitude, std::abs(coeff));
3666 max_magnitude = std::max(max_magnitude, std::abs(coeff));
3667 } else {
3668 non_boolean_domain =
3669 non_boolean_domain
3670 .AdditionWith(
3671 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
3672 .RelaxIfTooComplex();
3673 temp_ct_.mutable_linear()->add_vars(ref);
3674 temp_ct_.mutable_linear()->add_coeffs(coeff);
3675 }
3676 }
3677
3678 // Skip if there are no Booleans.
3679 if (tmp_terms_.empty()) return;
3680
3681 // Detect encoded AMO.
3682 //
3683 // TODO(user): Support more coefficient strengthening cases.
3684 // For instance on neos-954925.pb.gz we have stuff like:
3685 // 20 * (AMO1 + AMO2) - [coeff in 48 to 53] >= -15
3686 // this is really AMO1 + AMO2 - 2 * AMO3 >= 0.
3687 // Maybe if we reify the AMO to exactly one, this is visible since large
3688 // AMO can be rewriten with single variable (1 - extra var in exactly one).
3689 const Domain rhs = ReadDomainFromProto(ct->linear());
3690 if (non_boolean_domain == Domain(0) && rhs.NumIntervals() == 1 &&
3691 min_magnitude < max_magnitude) {
3692 int64_t min_activity = 0;
3693 int64_t max_activity = 0;
3694 for (const auto [ref, coeff] : tmp_terms_) {
3695 if (coeff > 0) {
3696 max_activity += coeff;
3697 } else {
3698 min_activity += coeff;
3699 }
3700 }
3701 const int64_t transformed_rhs = rhs.Max() - min_activity;
3702 if (min_activity >= rhs.Min() && max_magnitude <= transformed_rhs) {
3703 std::vector<int> literals;
3704 for (const auto [ref, coeff] : tmp_terms_) {
3705 if (coeff + min_magnitude > transformed_rhs) continue;
3706 literals.push_back(coeff > 0 ? ref : NegatedRef(ref));
3707 }
3708 if (helper->IsAmo(literals)) {
3709 // We actually have an at-most-one in disguise.
3710 context_->UpdateRuleStats("linear + amo: detect hidden AMO");
3711 int64_t shift = 0;
3712 for (int i = 0; i < initial_size; ++i) {
3713 CHECK(RefIsPositive(ct->linear().vars(i)));
3714 if (ct->linear().coeffs(i) > 0) {
3715 ct->mutable_linear()->set_coeffs(i, 1);
3716 } else {
3717 ct->mutable_linear()->set_coeffs(i, -1);
3718 shift -= 1;
3719 }
3720 }
3721 FillDomainInProto(Domain(shift, shift + 1), ct->mutable_linear());
3722 return;
3723 }
3724 }
3725 }
3726
3727 // Get more precise activity estimate based on at most one and heuristics.
3728 const int64_t min_bool_activity =
3729 helper->ComputeMinActivity(tmp_terms_, &conditional_mins_);
3730 const int64_t max_bool_activity =
3731 helper->ComputeMaxActivity(tmp_terms_, &conditional_maxs_);
3732
3733 // Detect trivially true/false constraint under these new bounds.
3734 // TODO(user): relax rhs if only one side is trivial.
3735 const Domain activity = non_boolean_domain.AdditionWith(
3736 Domain(min_bool_activity, max_bool_activity));
3737 if (activity.IntersectionWith(rhs).IsEmpty()) {
3738 // Note that this covers min_bool_activity > max_bool_activity.
3739 context_->UpdateRuleStats("linear + amo: infeasible linear constraint");
3740 (void)MarkConstraintAsFalse(ct);
3741 context_->UpdateConstraintVariableUsage(ct_index);
3742 return;
3743 } else if (activity.IsIncludedIn(rhs)) {
3744 context_->UpdateRuleStats("linear + amo: trivial linear constraint");
3745 ct->Clear();
3746 context_->UpdateConstraintVariableUsage(ct_index);
3747 return;
3748 }
3749
3750 // We can use the new bound to propagate other terms.
3751 if (ct->enforcement_literal().empty() && !temp_ct_.linear().vars().empty()) {
3753 rhs.AdditionWith(
3754 Domain(min_bool_activity, max_bool_activity).Negation()),
3755 temp_ct_.mutable_linear());
3756 if (!PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_)) {
3757 return;
3758 }
3759 if (context_->ModelIsUnsat()) return;
3760 }
3761
3762 // Extract enforcement or fix literal.
3763 //
3764 // TODO(user): Do not use domain fonction, can be slow.
3765 //
3766 // TODO(user): Actually we might make the linear relaxation worse by
3767 // extracting some of these enforcement, as those can be "lifted" booleans. We
3768 // currently deal with that in RemoveEnforcementThatMakesConstraintTrivial(),
3769 // but that might not be the most efficient.
3770 //
3771 // TODO(user): Another reason for making the LP worse is that if we replace
3772 // part of the constraint via FindBig*LinearOverlap() then our activity bounds
3773 // might not be as precise when we will linearize this constraint again.
3774 std::vector<int> new_enforcement;
3775 std::vector<int> must_be_true;
3776 for (int i = 0; i < tmp_terms_.size(); ++i) {
3777 const int ref = tmp_terms_[i].first;
3778
3779 const Domain bool0(conditional_mins_[i][0], conditional_maxs_[i][0]);
3780 const Domain activity0 = bool0.AdditionWith(non_boolean_domain);
3781 if (activity0.IntersectionWith(rhs).IsEmpty()) {
3782 // Must be 1.
3783 must_be_true.push_back(ref);
3784 } else if (activity0.IsIncludedIn(rhs)) {
3785 // Trivial constraint on 0.
3786 new_enforcement.push_back(ref);
3787 }
3788
3789 const Domain bool1(conditional_mins_[i][1], conditional_maxs_[i][1]);
3790 const Domain activity1 = bool1.AdditionWith(non_boolean_domain);
3791 if (activity1.IntersectionWith(rhs).IsEmpty()) {
3792 // Must be 0.
3793 must_be_true.push_back(NegatedRef(ref));
3794 } else if (activity1.IsIncludedIn(rhs)) {
3795 // Trivial constraint on 1.
3796 new_enforcement.push_back(NegatedRef(ref));
3797 }
3798 }
3799
3800 // Note that both list can be non empty, if for instance we have small * X +
3801 // big * Y + ... <= rhs and amo(X, Y). We could see that Y can never be true
3802 // and if X is true, then the constraint could be trivial.
3803 //
3804 // So we fix things first if we can.
3805 if (ct->enforcement_literal().empty() && !must_be_true.empty()) {
3806 // Note that our logic to do more presolve iteration depends on the
3807 // number of rule applied, so it is important to count this correctly.
3808 context_->UpdateRuleStats("linear + amo: fixed literal",
3809 must_be_true.size());
3810 for (const int lit : must_be_true) {
3811 if (!context_->SetLiteralToTrue(lit)) return;
3812 }
3813 bool changed = false;
3814 if (!CanonicalizeLinear(ct, &changed)) return;
3815 context_->UpdateConstraintVariableUsage(ct_index);
3816 return;
3817 }
3818
3819 if (!new_enforcement.empty()) {
3820 context_->UpdateRuleStats("linear + amo: extracted enforcement literal",
3821 new_enforcement.size());
3822 for (const int ref : new_enforcement) {
3823 ct->add_enforcement_literal(ref);
3824 }
3825 }
3826
3827 if (!ct->enforcement_literal().empty()) {
3828 const int old_enf_size = ct->enforcement_literal().size();
3829 if (!helper->PresolveEnforcement(ct->linear().vars(), ct, &temp_set_)) {
3830 context_->UpdateRuleStats("linear + amo: infeasible enforcement");
3831 ct->Clear();
3832 context_->UpdateConstraintVariableUsage(ct_index);
3833 return;
3834 }
3835 if (ct->enforcement_literal().size() < old_enf_size) {
3836 context_->UpdateRuleStats("linear + amo: simplified enforcement list");
3837 context_->UpdateConstraintVariableUsage(ct_index);
3838 }
3839
3840 for (const int lit : must_be_true) {
3841 if (temp_set_.contains(NegatedRef(lit))) {
3842 // A literal must be true but is incompatible with what the enforcement
3843 // implies. The constraint must be false!
3844 context_->UpdateRuleStats(
3845 "linear + amo: advanced infeasible linear constraint");
3846 (void)MarkConstraintAsFalse(ct);
3847 context_->UpdateConstraintVariableUsage(ct_index);
3848 return;
3849 }
3850 }
3851
3852 // TODO(user): do that in more cases?
3853 if (ct->enforcement_literal().size() == 1 && !must_be_true.empty()) {
3854 // Add implication, and remove literal from the constraint in this case.
3855 // To remove them, we just add them to temp_set_ and FixLiteralFromSet()
3856 // will take care of it.
3857 context_->UpdateRuleStats("linear + amo: added implications");
3858 ConstraintProto* new_ct = context_->working_model->add_constraints();
3859 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
3860 for (const int lit : must_be_true) {
3861 new_ct->mutable_bool_and()->add_literals(lit);
3862 temp_set_.insert(lit);
3863 }
3864 context_->UpdateNewConstraintsVariableUsage();
3865 }
3866
3867 const int num_fixed = FixLiteralFromSet(temp_set_, ct->mutable_linear());
3868 if (num_fixed > new_enforcement.size()) {
3869 context_->UpdateRuleStats(
3870 "linear + amo: fixed literal implied by enforcement");
3871 }
3872 if (num_fixed > 0) {
3873 context_->UpdateConstraintVariableUsage(ct_index);
3874 }
3875 }
3876
3877 if (ct->linear().vars().empty()) {
3878 context_->UpdateRuleStats("linear + amo: empty after processing");
3879 PresolveSmallLinear(ct);
3880 context_->UpdateConstraintVariableUsage(ct_index);
3881 return;
3882 }
3883
3884 // If the constraint is of size 1 or 2, we re-presolve it right away.
3885 if (initial_size != ct->linear().vars().size() && PresolveSmallLinear(ct)) {
3886 context_->UpdateConstraintVariableUsage(ct_index);
3887 if (ct->constraint_case() != ConstraintProto::kLinear) return;
3888 }
3889
3890 // Detect enforcement literal that could actually be lifted, and as such can
3891 // just be removed from the enforcement list. Ideally, during relaxation we
3892 // would lift such Boolean again.
3893 //
3894 // Note that this code is independent from anything above.
3895 if (!ct->enforcement_literal().empty()) {
3896 // TODO(user): remove duplication with code above?
3897 tmp_terms_.clear();
3898 Domain non_boolean_domain(0);
3899 const int num_ct_terms = ct->linear().vars().size();
3900 for (int i = 0; i < num_ct_terms; ++i) {
3901 const int ref = ct->linear().vars(i);
3902 const int64_t coeff = ct->linear().coeffs(i);
3903 CHECK(RefIsPositive(ref));
3904 if (context_->CanBeUsedAsLiteral(ref)) {
3905 tmp_terms_.push_back({ref, coeff});
3906 } else {
3907 non_boolean_domain =
3908 non_boolean_domain
3909 .AdditionWith(
3910 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
3911 .RelaxIfTooComplex();
3912 }
3913 }
3914 const int num_removed = helper->RemoveEnforcementThatMakesConstraintTrivial(
3915 tmp_terms_, non_boolean_domain, ReadDomainFromProto(ct->linear()), ct);
3916 if (num_removed > 0) {
3917 context_->UpdateRuleStats("linear + amo: removed enforcement literal",
3918 num_removed);
3919 context_->UpdateConstraintVariableUsage(ct_index);
3920 }
3921 }
3922}
3923
3924bool CpModelPresolver::PropagateDomainsInLinear(int ct_index,
3925 ConstraintProto* ct) {
3926 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
3927 if (context_->ModelIsUnsat()) return false;
3928
3929 // For fast mode.
3930 int64_t min_activity;
3931 int64_t max_activity;
3932
3933 // For slow mode.
3934 const int num_vars = ct->linear().vars_size();
3935 auto& term_domains = context_->tmp_term_domains;
3936 auto& left_domains = context_->tmp_left_domains;
3937 const bool slow_mode = num_vars < 10;
3938
3939 // Compute the implied rhs bounds from the variable ones.
3940 if (slow_mode) {
3941 term_domains.resize(num_vars + 1);
3942 left_domains.resize(num_vars + 1);
3943 left_domains[0] = Domain(0);
3944 term_domains[num_vars] = Domain(0);
3945 for (int i = 0; i < num_vars; ++i) {
3946 const int var = ct->linear().vars(i);
3947 const int64_t coeff = ct->linear().coeffs(i);
3948 DCHECK(RefIsPositive(var));
3949 term_domains[i] = context_->DomainOf(var).MultiplicationBy(coeff);
3950 left_domains[i + 1] =
3951 left_domains[i].AdditionWith(term_domains[i]).RelaxIfTooComplex();
3952 }
3953 } else {
3954 std::tie(min_activity, max_activity) =
3955 context_->ComputeMinMaxActivity(ct->linear());
3956 }
3957 const Domain& implied_rhs =
3958 slow_mode ? left_domains[num_vars] : Domain(min_activity, max_activity);
3959
3960 // Abort if trivial.
3961 const Domain old_rhs = ReadDomainFromProto(ct->linear());
3962 if (implied_rhs.IsIncludedIn(old_rhs)) {
3963 if (ct_index != -1) context_->UpdateRuleStats("linear: always true");
3964 return RemoveConstraint(ct);
3965 }
3966
3967 // Incorporate the implied rhs information.
3968 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
3969 if (rhs.IsEmpty()) {
3970 context_->UpdateRuleStats("linear: infeasible");
3971 return MarkConstraintAsFalse(ct);
3972 }
3973 if (rhs != old_rhs) {
3974 if (ct_index != -1) context_->UpdateRuleStats("linear: simplified rhs");
3975 }
3976 FillDomainInProto(rhs, ct->mutable_linear());
3977
3978 // Propagate the variable bounds.
3979 if (ct->enforcement_literal().size() > 1) return false;
3980
3981 bool new_bounds = false;
3982 bool recanonicalize = false;
3983 Domain negated_rhs = rhs.Negation();
3984 Domain right_domain(0);
3985 Domain new_domain;
3986 Domain activity_minus_term;
3987 for (int i = num_vars - 1; i >= 0; --i) {
3988 const int var = ct->linear().vars(i);
3989 const int64_t var_coeff = ct->linear().coeffs(i);
3990
3991 if (slow_mode) {
3992 right_domain =
3993 right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
3994 activity_minus_term = left_domains[i].AdditionWith(right_domain);
3995 } else {
3996 int64_t min_term = var_coeff * context_->MinOf(var);
3997 int64_t max_term = var_coeff * context_->MaxOf(var);
3998 if (var_coeff < 0) std::swap(min_term, max_term);
3999 activity_minus_term =
4000 Domain(min_activity - min_term, max_activity - max_term);
4001 }
4002 new_domain = activity_minus_term.AdditionWith(negated_rhs)
4003 .InverseMultiplicationBy(-var_coeff);
4004
4005 if (ct->enforcement_literal().empty()) {
4006 // Push the new domain.
4007 if (!context_->IntersectDomainWith(var, new_domain, &new_bounds)) {
4008 return true;
4009 }
4010 } else if (ct->enforcement_literal().size() == 1) {
4011 // We cannot push the new domain, but we can add some deduction.
4012 CHECK(RefIsPositive(var));
4013 if (!context_->DomainOfVarIsIncludedIn(var, new_domain)) {
4014 context_->deductions.AddDeduction(ct->enforcement_literal(0), var,
4015 new_domain);
4016 }
4017 }
4018
4019 if (context_->IsFixed(var)) {
4020 // This will make sure we remove that fixed variable from the constraint.
4021 recanonicalize = true;
4022 continue;
4023 }
4024
4025 // The other transformations below require a non-reified constraint.
4026 if (ct_index == -1) continue;
4027 if (!ct->enforcement_literal().empty()) continue;
4028
4029 // Given a variable that only appear in one constraint and in the
4030 // objective, for any feasible solution, it will be always better to move
4031 // this singleton variable as much as possible towards its good objective
4032 // direction. Sometime, we can detect that we will always be able to
4033 // do this until the only constraint of this singleton variable is tight.
4034 //
4035 // When this happens, we can make the constraint an equality. Note that it
4036 // might not always be good to restrict constraint like this, but in this
4037 // case, the RemoveSingletonInLinear() code should be able to remove this
4038 // variable altogether.
4039 if (rhs.Min() != rhs.Max() &&
4040 context_->VariableWithCostIsUniqueAndRemovable(var)) {
4041 const int64_t obj_coeff = context_->ObjectiveMap().at(var);
4042 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
4043 bool fixed = false;
4044 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->DomainOf(var),
4045 activity_minus_term, rhs)) {
4046 rhs = Domain(rhs.Min());
4047 fixed = true;
4048 }
4049 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->DomainOf(var),
4050 activity_minus_term, rhs)) {
4051 rhs = Domain(rhs.Max());
4052 fixed = true;
4053 }
4054 if (fixed) {
4055 context_->UpdateRuleStats("linear: tightened into equality");
4056 // Compute a new `var` hint so that the lhs of `ct` is equal to `rhs`.
4057 solution_crush_.SetVarToLinearConstraintSolution(
4058 i, ct->linear().vars(), ct->linear().coeffs(), rhs.FixedValue());
4059 FillDomainInProto(rhs, ct->mutable_linear());
4060 negated_rhs = rhs.Negation();
4061
4062 // Restart the loop.
4063 i = num_vars;
4064 right_domain = Domain(0);
4065 continue;
4066 }
4067 }
4068
4069 // Can we perform some substitution?
4070 //
4071 // TODO(user): there is no guarantee we will not miss some since we might
4072 // not reprocess a constraint once other have been deleted.
4073
4074 // Skip affine constraint. It is more efficient to substitute them lazily
4075 // when we process other constraints. Note that if we relax the fact that
4076 // we substitute only equalities, we can deal with inequality of size 2
4077 // here.
4078 if (ct->linear().vars().size() <= 2) continue;
4079
4080 // TODO(user): We actually do not need a strict equality when
4081 // keep_all_feasible_solutions is false, but that simplifies things as the
4082 // SubstituteVariable() function cannot fail this way.
4083 if (rhs.Min() != rhs.Max()) continue;
4084
4085 // NOTE: The mapping doesn't allow us to remove a variable if
4086 // keep_all_feasible_solutions is true.
4087 //
4088 // TODO(user): This shouldn't be necessary, but caused some failure on
4089 // IntModExpandTest.FzTest. Fix.
4090 if (context_->params().keep_all_feasible_solutions_in_presolve()) continue;
4091
4092 // Only consider "implied free" variables. Note that the coefficient of
4093 // magnitude 1 is important otherwise we can't easily remove the
4094 // constraint since the fact that the sum of the other terms must be a
4095 // multiple of coeff will not be enforced anymore.
4096 if (std::abs(var_coeff) != 1) continue;
4097 if (context_->params().presolve_substitution_level() <= 0) continue;
4098
4099 // Only consider substitution that reduce the number of entries.
4100 const bool is_in_objective = context_->VarToConstraints(var).contains(-1);
4101 {
4102 int col_size = context_->VarToConstraints(var).size();
4103 if (is_in_objective) col_size--;
4104 const int row_size = ct->linear().vars_size();
4105
4106 // This is actually an upper bound on the number of entries added since
4107 // some of them might already be present.
4108 const int num_entries_added = (row_size - 1) * (col_size - 1);
4109 const int num_entries_removed = col_size + row_size - 1;
4110 if (num_entries_added > num_entries_removed) continue;
4111 }
4112
4113 // Check pre-conditions on all the constraints in which this variable
4114 // appear. Basically they must all be linear.
4115 std::vector<int> others;
4116 bool abort = false;
4117 for (const int c : context_->VarToConstraints(var)) {
4118 if (c == kObjectiveConstraint) continue;
4119 if (c == kAffineRelationConstraint) {
4120 abort = true;
4121 break;
4122 }
4123 if (c == ct_index) continue;
4124 if (context_->working_model->constraints(c).constraint_case() !=
4125 ConstraintProto::kLinear) {
4126 abort = true;
4127 break;
4128 }
4129 for (const int ref :
4130 context_->working_model->constraints(c).enforcement_literal()) {
4131 if (PositiveRef(ref) == var) {
4132 abort = true;
4133 break;
4134 }
4135 }
4136 if (abort) break;
4137 others.push_back(c);
4138 }
4139 if (abort) continue;
4140
4141 // If the domain implied by this constraint is the same as the current
4142 // domain of the variable, this variable is implied free. Otherwise, we
4143 // check if the intersection with the domain implied by another constraint
4144 // make it implied free.
4145 if (context_->DomainOf(var) != new_domain) {
4146 // We only do that for doubleton because we don't want the propagation to
4147 // be less strong. If we were to replace this variable in other constraint
4148 // the implied bound from the linear expression might not be as good.
4149 //
4150 // TODO(user): We still substitute even if this happens in the objective
4151 // though. Is that good?
4152 if (others.size() != 1) continue;
4153 const ConstraintProto& other_ct =
4154 context_->working_model->constraints(others.front());
4155 if (!other_ct.enforcement_literal().empty()) continue;
4156
4157 // Compute the implied domain using the other constraint.
4158 // We only do that if it is not too long to avoid quadratic worst case.
4159 const LinearConstraintProto& other_lin = other_ct.linear();
4160 if (other_lin.vars().size() > 100) continue;
4161 Domain implied = ReadDomainFromProto(other_lin);
4162 int64_t other_coeff = 0;
4163 for (int i = 0; i < other_lin.vars().size(); ++i) {
4164 const int v = other_lin.vars(i);
4165 const int64_t coeff = other_lin.coeffs(i);
4166 if (v == var) {
4167 // It is possible the constraint is not canonical if it wasn't
4168 // processed yet !
4169 other_coeff += coeff;
4170 } else {
4171 implied =
4172 implied
4173 .AdditionWith(context_->DomainOf(v).MultiplicationBy(-coeff))
4174 .RelaxIfTooComplex();
4175 }
4176 }
4177 if (other_coeff == 0) continue;
4178 implied = implied.InverseMultiplicationBy(other_coeff);
4179
4180 // Since we compute it, we can as well update the domain right now.
4181 // This is also needed for postsolve to have a tight domain.
4182 if (!context_->IntersectDomainWith(var, implied)) return false;
4183 if (context_->IsFixed(var)) continue;
4184 if (new_domain.IntersectionWith(implied) != context_->DomainOf(var)) {
4185 continue;
4186 }
4187
4188 context_->UpdateRuleStats("linear: doubleton free");
4189 }
4190
4191 // Substitute in objective.
4192 // This can fail in overflow corner cases, so we abort before doing any
4193 // actual changes.
4194 if (is_in_objective &&
4195 !context_->SubstituteVariableInObjective(var, var_coeff, *ct)) {
4196 continue;
4197 }
4198
4199 // Do the actual substitution.
4200 ConstraintProto copy_if_we_abort;
4201 absl::c_sort(others);
4202 for (const int c : others) {
4203 // TODO(user): The copy is needed to have a simpler overflow-checking
4204 // code were we check once the substitution is done. If needed we could
4205 // optimize that, but with more code.
4206 copy_if_we_abort = context_->working_model->constraints(c);
4207
4208 // In some corner cases, this might violate our overflow precondition or
4209 // even create an overflow. The danger is limited since the range of the
4210 // linear expression used in the definition do not exceed the domain of
4211 // the variable we substitute. But this is not the case for the doubleton
4212 // case above.
4213 if (!SubstituteVariable(
4214 var, var_coeff, *ct,
4215 context_->working_model->mutable_constraints(c))) {
4216 // The function above can fail because of overflow, but also if the
4217 // constraint was not canonicalized yet and the variable is actually not
4218 // there (we have var - var for instance).
4219 //
4220 // TODO(user): we canonicalize it right away, but I am not sure it is
4221 // really needed.
4222 bool changed = false;
4223 if (!CanonicalizeLinear(context_->working_model->mutable_constraints(c),
4224 &changed)) {
4225 return true;
4226 }
4227 if (changed) {
4228 context_->UpdateConstraintVariableUsage(c);
4229 }
4230 abort = true;
4231 break;
4232 }
4233
4235 *context_->working_model,
4236 context_->working_model->constraints(c).linear().vars(),
4237 context_->working_model->constraints(c).linear().coeffs())) {
4238 // Revert the change in this case.
4239 *context_->working_model->mutable_constraints(c) = copy_if_we_abort;
4240 abort = true;
4241 break;
4242 }
4243
4244 // TODO(user): We should re-enqueue these constraints for presolve.
4245 context_->UpdateConstraintVariableUsage(c);
4246 }
4247 if (abort) continue;
4248
4249 context_->UpdateRuleStats(
4250 absl::StrCat("linear: variable substitution ", others.size()));
4251
4252 // The variable now only appear in its definition and we can remove it
4253 // because it was implied free.
4254 //
4255 // Tricky: If the linear constraint contains other variables that are only
4256 // used here, then the postsolve needs more info. We do need to indicate
4257 // that whatever the value of those other variables, we will have a way to
4258 // assign var. We do that by putting it fist.
4259 CHECK_EQ(context_->VarToConstraints(var).size(), 1);
4260 context_->MarkVariableAsRemoved(var);
4261 ConstraintProto* mapping_ct =
4262 context_->NewMappingConstraint(__FILE__, __LINE__);
4263 *mapping_ct = *ct;
4264 LinearConstraintProto* mapping_linear_ct = mapping_ct->mutable_linear();
4265 std::swap(mapping_linear_ct->mutable_vars()->at(0),
4266 mapping_linear_ct->mutable_vars()->at(i));
4267 std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
4268 mapping_linear_ct->mutable_coeffs()->at(i));
4269 return RemoveConstraint(ct);
4270 }
4271
4272 // special case.
4273 if (ct_index == -1) {
4274 if (new_bounds) {
4275 context_->UpdateRuleStats(
4276 "linear: reduced variable domains in derived constraint");
4277 }
4278 return false;
4279 }
4280
4281 if (new_bounds) {
4282 context_->UpdateRuleStats("linear: reduced variable domains");
4283 }
4284 if (recanonicalize) {
4285 bool changed = false;
4286 (void)CanonicalizeLinear(ct, &changed);
4287 return changed;
4288 }
4289 return false;
4290}
4291
4292// The constraint from its lower value is sum positive_coeff * X <= rhs.
4293// If from_lower_bound is false, then it is the constraint from its upper value.
4294void CpModelPresolver::LowerThanCoeffStrengthening(bool from_lower_bound,
4295 int64_t min_magnitude,
4296 int64_t rhs,
4297 ConstraintProto* ct) {
4298 const LinearConstraintProto& arg = ct->linear();
4299 const int64_t second_threshold = rhs - min_magnitude;
4300 const int num_vars = arg.vars_size();
4301
4302 // Special case:
4303 // - The terms above rhs must be fixed to zero.
4304 // - The terms in (second_threshold, rhs] can be fixed to rhs as
4305 // they will force all other terms to zero if not at zero themselves.
4306 // - If what is left can be simplified to a single coefficient, we can
4307 // put the constraint into a special form.
4308 //
4309 // TODO(user): More generally, if we ignore term that set everything else to
4310 // zero, we can preprocess the constraint left and then add them back. So we
4311 // can do all our other reduction like normal GCD or more advanced ones like
4312 // DP based or approximate GCD.
4313 if (min_magnitude <= second_threshold) {
4314 // Compute max_magnitude for the term <= second_threshold.
4315 int64_t max_magnitude_left = 0;
4316 int64_t max_activity_left = 0;
4317 int64_t activity_when_coeff_are_one = 0;
4318 int64_t gcd = 0;
4319 for (int i = 0; i < num_vars; ++i) {
4320 const int64_t magnitude = std::abs(arg.coeffs(i));
4321 if (magnitude <= second_threshold) {
4322 gcd = std::gcd(gcd, magnitude);
4323 max_magnitude_left = std::max(max_magnitude_left, magnitude);
4324 const int64_t bound_diff =
4325 context_->MaxOf(arg.vars(i)) - context_->MinOf(arg.vars(i));
4326 activity_when_coeff_are_one += bound_diff;
4327 max_activity_left += magnitude * bound_diff;
4328 }
4329 }
4330 CHECK_GT(min_magnitude, 0);
4331 CHECK_LE(min_magnitude, max_magnitude_left);
4332
4333 // Not considering the variable that set everyone at zero when true:
4334 int64_t new_rhs = 0;
4335 bool set_all_to_one = false;
4336 if (max_activity_left <= rhs) {
4337 // We are left with a trivial constraint.
4338 context_->UpdateRuleStats("linear with partial amo: trivial");
4339 new_rhs = activity_when_coeff_are_one;
4340 set_all_to_one = true;
4341 } else if (rhs / min_magnitude == rhs / max_magnitude_left) {
4342 // We are left with a sum <= new_rhs constraint.
4343 context_->UpdateRuleStats("linear with partial amo: constant coeff");
4344 new_rhs = rhs / min_magnitude;
4345 set_all_to_one = true;
4346 } else if (gcd > 1) {
4347 // We are left with a constraint that can be simplified by gcd.
4348 context_->UpdateRuleStats("linear with partial amo: gcd");
4349 new_rhs = rhs / gcd;
4350 }
4351
4352 if (new_rhs > 0) {
4353 int64_t rhs_offset = 0;
4354 for (int i = 0; i < num_vars; ++i) {
4355 const int ref = arg.vars(i);
4356 const int64_t coeff = from_lower_bound ? arg.coeffs(i) : -arg.coeffs(i);
4357
4358 int64_t new_coeff;
4359 const int64_t magnitude = std::abs(coeff);
4360 if (magnitude > rhs) {
4361 new_coeff = new_rhs + 1;
4362 } else if (magnitude > second_threshold) {
4363 new_coeff = new_rhs;
4364 } else {
4365 new_coeff = set_all_to_one ? 1 : magnitude / gcd;
4366 }
4367
4368 // In the transformed domain we will always have
4369 // magnitude * (var - lb) or magnitude * (ub - var)
4370 if (coeff > 0) {
4371 ct->mutable_linear()->set_coeffs(i, new_coeff);
4372 rhs_offset += new_coeff * context_->MinOf(ref);
4373 } else {
4374 ct->mutable_linear()->set_coeffs(i, -new_coeff);
4375 rhs_offset -= new_coeff * context_->MaxOf(ref);
4376 }
4377 }
4378 FillDomainInProto(Domain(rhs_offset, new_rhs + rhs_offset),
4379 ct->mutable_linear());
4380 return;
4381 }
4382 }
4383
4384 int64_t rhs_offset = 0;
4385 for (int i = 0; i < num_vars; ++i) {
4386 int ref = arg.vars(i);
4387 int64_t coeff = arg.coeffs(i);
4388 if (coeff < 0) {
4389 ref = NegatedRef(ref);
4390 coeff = -coeff;
4391 }
4392
4393 if (coeff > rhs) {
4394 if (ct->enforcement_literal().empty()) {
4395 // Shifted variable must be zero.
4396 //
4397 // TODO(user): Note that here IntersectDomainWith() can only return
4398 // false if for some reason this variable has an affine representative
4399 // for which this fail. Ideally we should always replace/merge
4400 // representative right away, but this is a bit difficult to enforce
4401 // currently.
4402 context_->UpdateRuleStats("linear: fix variable to its bound.");
4403 if (!context_->IntersectDomainWith(
4404 ref, Domain(from_lower_bound ? context_->MinOf(ref)
4405 : context_->MaxOf(ref)))) {
4406 return;
4407 }
4408 }
4409
4410 // TODO(user): What to do with the coeff if there is enforcement?
4411 continue;
4412 }
4413 if (coeff > second_threshold && coeff < rhs) {
4414 context_->UpdateRuleStats(
4415 "linear: coefficient strengthening by increasing it.");
4416 if (from_lower_bound) {
4417 // coeff * (X - LB + LB) -> rhs * (X - LB) + coeff * LB
4418 rhs_offset -= (coeff - rhs) * context_->MinOf(ref);
4419 } else {
4420 // coeff * (X - UB + UB) -> rhs * (X - UB) + coeff * UB
4421 rhs_offset -= (coeff - rhs) * context_->MaxOf(ref);
4422 }
4423 ct->mutable_linear()->set_coeffs(i, arg.coeffs(i) > 0 ? rhs : -rhs);
4424 }
4425 }
4426 if (rhs_offset != 0) {
4427 FillDomainInProto(ReadDomainFromProto(arg).AdditionWith(Domain(rhs_offset)),
4428 ct->mutable_linear());
4429 }
4430}
4431
4432// Identify Boolean variable that makes the constraint always true when set to
4433// true or false. Moves such literal to the constraint enforcement literals
4434// list.
4435//
4436// We also generalize this to integer variable at one of their bound.
4437//
4438// This operation is similar to coefficient strengthening in the MIP world.
4439void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
4440 int ct_index, ConstraintProto* ct) {
4441 if (ct->constraint_case() != ConstraintProto::kLinear) return;
4442 if (context_->ModelIsUnsat()) return;
4443
4444 const LinearConstraintProto& arg = ct->linear();
4445 const int num_vars = arg.vars_size();
4446
4447 // No need to process size one constraints, they will be presolved separately.
4448 // We also do not want to split them in two.
4449 if (num_vars <= 1) return;
4450
4451 int64_t min_sum = 0;
4452 int64_t max_sum = 0;
4453 int64_t max_coeff_magnitude = 0;
4454 int64_t min_coeff_magnitude = std::numeric_limits<int64_t>::max();
4455 for (int i = 0; i < num_vars; ++i) {
4456 const int ref = arg.vars(i);
4457 const int64_t coeff = arg.coeffs(i);
4458 if (coeff > 0) {
4459 max_coeff_magnitude = std::max(max_coeff_magnitude, coeff);
4460 min_coeff_magnitude = std::min(min_coeff_magnitude, coeff);
4461 min_sum += coeff * context_->MinOf(ref);
4462 max_sum += coeff * context_->MaxOf(ref);
4463 } else {
4464 max_coeff_magnitude = std::max(max_coeff_magnitude, -coeff);
4465 min_coeff_magnitude = std::min(min_coeff_magnitude, -coeff);
4466 min_sum += coeff * context_->MaxOf(ref);
4467 max_sum += coeff * context_->MinOf(ref);
4468 }
4469 }
4470 if (max_coeff_magnitude == 1) return;
4471
4472 // We can only extract enforcement literals if the maximum coefficient
4473 // magnitude is large enough. Note that we handle complex domain.
4474 //
4475 // TODO(user): Depending on how we split below, the threshold are not the
4476 // same. This is maybe not too important, we just don't split as often as we
4477 // could, but it is still unclear if splitting is good.
4478 const auto& domain = ct->linear().domain();
4479 const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
4480 const int64_t lb_threshold = max_sum - domain[1];
4481 if (max_coeff_magnitude + min_coeff_magnitude <
4482 std::max(ub_threshold, lb_threshold)) {
4483 // We also have other kind of coefficient strengthening.
4484 // In something like 3x + 5y <= 6, the coefficient 5 can be changed to 6.
4485 // And in 5x + 12y <= 12, the coeff 5 can be changed to 6 (not sure how to
4486 // generalize this one).
4487 if (domain.size() == 2 && min_coeff_magnitude > 1 &&
4488 min_coeff_magnitude < max_coeff_magnitude) {
4489 const int64_t rhs_min = domain[0];
4490 const int64_t rhs_max = domain[1];
4491 if (min_sum >= rhs_min &&
4492 max_coeff_magnitude + min_coeff_magnitude > rhs_max - min_sum) {
4493 LowerThanCoeffStrengthening(/*from_lower_bound=*/true,
4494 min_coeff_magnitude, rhs_max - min_sum, ct);
4495 return;
4496 }
4497 if (max_sum <= rhs_max &&
4498 max_coeff_magnitude + min_coeff_magnitude > max_sum - rhs_min) {
4499 LowerThanCoeffStrengthening(/*from_lower_bound=*/false,
4500 min_coeff_magnitude, max_sum - rhs_min, ct);
4501 return;
4502 }
4503 }
4504 }
4505
4506 // We need the constraint to be only bounded on one side in order to extract
4507 // enforcement literal.
4508 //
4509 // If it is boxed and we know that some coefficient are big enough (see test
4510 // above), then we split the constraint in two. That might not seems always
4511 // good, but for the CP propagation engine, we don't loose anything by doing
4512 // so, and for the LP we will regroup the constraints if they still have the
4513 // exact same coeff after the presolve.
4514 //
4515 // TODO(user): Creating two new constraints and removing the current one might
4516 // not be the most efficient, but it simplify the presolve code by not having
4517 // to do anything special to trigger a new presolving of these constraints.
4518 // Try to improve if this becomes a problem.
4519 const Domain rhs_domain = ReadDomainFromProto(ct->linear());
4520 const bool lower_bounded = min_sum < rhs_domain.Min();
4521 const bool upper_bounded = max_sum > rhs_domain.Max();
4522 if (!lower_bounded && !upper_bounded) return;
4523 if (lower_bounded && upper_bounded) {
4524 // We disable this for now.
4525 if (true) return;
4526
4527 // Lets not split except if we extract enforcement.
4528 if (max_coeff_magnitude < std::max(ub_threshold, lb_threshold)) return;
4529
4530 context_->UpdateRuleStats("linear: split boxed constraint");
4531 ConstraintProto* new_ct1 = context_->working_model->add_constraints();
4532 *new_ct1 = *ct;
4533 if (!ct->name().empty()) {
4534 new_ct1->set_name(absl::StrCat(ct->name(), " (part 1)"));
4535 }
4536 FillDomainInProto(Domain(min_sum, rhs_domain.Max()),
4537 new_ct1->mutable_linear());
4538
4539 ConstraintProto* new_ct2 = context_->working_model->add_constraints();
4540 *new_ct2 = *ct;
4541 if (!ct->name().empty()) {
4542 new_ct2->set_name(absl::StrCat(ct->name(), " (part 2)"));
4543 }
4544 FillDomainInProto(rhs_domain.UnionWith(Domain(rhs_domain.Max(), max_sum)),
4545 new_ct2->mutable_linear());
4546
4547 context_->UpdateNewConstraintsVariableUsage();
4548 ct->Clear();
4549 context_->UpdateConstraintVariableUsage(ct_index);
4550 return;
4551 }
4552
4553 // Any coefficient greater than this will cause the constraint to be trivially
4554 // satisfied when the variable move away from its bound. Note that as we
4555 // remove coefficient, the threshold do not change!
4556 const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
4557
4558 // All coeffs in [second_threshold, threshold) can be reduced to
4559 // second_threshold.
4560 //
4561 // TODO(user): If 2 * min_coeff_magnitude >= bound, then the constraint can
4562 // be completely rewriten to 2 * (enforcement_part) + sum var >= 2 which is
4563 // what happen eventually when bound is even, but not if it is odd currently.
4564 int64_t second_threshold =
4565 std::max(MathUtil::CeilOfRatio(threshold, int64_t{2}),
4566 threshold - min_coeff_magnitude);
4567
4568 // Tricky: The second threshold only work if the domain is simple. If the
4569 // domain has holes, changing the coefficient might change whether the
4570 // variable can be at one or not by herself.
4571 //
4572 // TODO(user): We could still reduce it to the smaller value with same
4573 // feasibility.
4574 if (rhs_domain.NumIntervals() > 1) {
4575 second_threshold = threshold; // Disable.
4576 }
4577
4578 // Do we only extract Booleans?
4579 //
4580 // Note that for now the default is false, and also there are problem calling
4581 // GetOrCreateVarValueEncoding() after expansion because we might have removed
4582 // the variable used in the encoding.
4583 const bool only_extract_booleans =
4584 !context_->params().presolve_extract_integer_enforcement() ||
4585 context_->ModelIsExpanded();
4586
4587 // To avoid a quadratic loop, we will rewrite the linear expression at the
4588 // same time as we extract enforcement literals.
4589 int new_size = 0;
4590 int64_t rhs_offset = 0;
4591 bool some_integer_encoding_were_extracted = false;
4592 LinearConstraintProto* mutable_arg = ct->mutable_linear();
4593 for (int i = 0; i < arg.vars_size(); ++i) {
4594 int ref = arg.vars(i);
4595 int64_t coeff = arg.coeffs(i);
4596 if (coeff < 0) {
4597 ref = NegatedRef(ref);
4598 coeff = -coeff;
4599 }
4600
4601 // TODO(user): If the encoding Boolean already exist, we could extract
4602 // the non-Boolean enforcement term.
4603 const bool is_boolean = context_->CanBeUsedAsLiteral(ref);
4604 if (context_->IsFixed(ref) || coeff < threshold ||
4605 (only_extract_booleans && !is_boolean)) {
4606 mutable_arg->set_vars(new_size, mutable_arg->vars(i));
4607
4608 int64_t new_magnitude = std::abs(arg.coeffs(i));
4609 if (coeff > threshold) {
4610 // We keep this term but reduces its coeff.
4611 // This is only for the case where only_extract_booleans == true.
4612 new_magnitude = threshold;
4613 context_->UpdateRuleStats("linear: coefficient strenghtening.");
4614 } else if (coeff > second_threshold && coeff < threshold) {
4615 // This cover the special case where one big + on small is enough
4616 // to satisfy the constraint, we can reduce the big.
4617 new_magnitude = second_threshold;
4618 context_->UpdateRuleStats(
4619 "linear: advanced coefficient strenghtening.");
4620 }
4621 if (coeff != new_magnitude) {
4622 if (lower_bounded) {
4623 // coeff * (X - LB + LB) -> new_magnitude * (X - LB) + coeff * LB
4624 rhs_offset -= (coeff - new_magnitude) * context_->MinOf(ref);
4625 } else {
4626 // coeff * (X - UB + UB) -> new_magnitude * (X - UB) + coeff * UB
4627 rhs_offset -= (coeff - new_magnitude) * context_->MaxOf(ref);
4628 }
4629 }
4630
4631 mutable_arg->set_coeffs(
4632 new_size, arg.coeffs(i) > 0 ? new_magnitude : -new_magnitude);
4633 ++new_size;
4634 continue;
4635 }
4636
4637 if (is_boolean) {
4638 context_->UpdateRuleStats("linear: extracted enforcement literal");
4639 } else {
4640 some_integer_encoding_were_extracted = true;
4641 context_->UpdateRuleStats(
4642 "linear: extracted integer enforcement literal");
4643 }
4644 if (lower_bounded) {
4645 ct->add_enforcement_literal(is_boolean
4646 ? NegatedRef(ref)
4647 : context_->GetOrCreateVarValueEncoding(
4648 ref, context_->MinOf(ref)));
4649 rhs_offset -= coeff * context_->MinOf(ref);
4650 } else {
4651 ct->add_enforcement_literal(is_boolean
4652 ? ref
4653 : context_->GetOrCreateVarValueEncoding(
4654 ref, context_->MaxOf(ref)));
4655 rhs_offset -= coeff * context_->MaxOf(ref);
4656 }
4657 }
4658 mutable_arg->mutable_vars()->Truncate(new_size);
4659 mutable_arg->mutable_coeffs()->Truncate(new_size);
4660 FillDomainInProto(rhs_domain.AdditionWith(Domain(rhs_offset)), mutable_arg);
4661 if (some_integer_encoding_were_extracted || new_size == 1) {
4662 context_->UpdateConstraintVariableUsage(ct_index);
4663 context_->UpdateNewConstraintsVariableUsage();
4664 }
4665}
4666
4667void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto* ct) {
4668 if (context_->ModelIsUnsat()) return;
4669 if (HasEnforcementLiteral(*ct)) return;
4670 const Domain rhs = ReadDomainFromProto(ct->linear());
4671
4672 const LinearConstraintProto& arg = ct->linear();
4673 const int num_vars = arg.vars_size();
4674 int64_t min_sum = 0;
4675 int64_t max_sum = 0;
4676 for (int i = 0; i < num_vars; ++i) {
4677 const int ref = arg.vars(i);
4678 const int64_t coeff = arg.coeffs(i);
4679 const int64_t term_a = coeff * context_->MinOf(ref);
4680 const int64_t term_b = coeff * context_->MaxOf(ref);
4681 min_sum += std::min(term_a, term_b);
4682 max_sum += std::max(term_a, term_b);
4683 }
4684 for (const int type : {0, 1}) {
4685 std::vector<int> at_most_one;
4686 for (int i = 0; i < num_vars; ++i) {
4687 const int ref = arg.vars(i);
4688 const int64_t coeff = arg.coeffs(i);
4689 if (context_->MinOf(ref) != 0) continue;
4690 if (context_->MaxOf(ref) != 1) continue;
4691
4692 if (type == 0) {
4693 // TODO(user): we could add one more Boolean with a lower coeff as long
4694 // as we have lower_coeff + min_of_other_coeff > rhs.Max().
4695 if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
4696 at_most_one.push_back(coeff > 0 ? ref : NegatedRef(ref));
4697 }
4698 } else {
4699 if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
4700 at_most_one.push_back(coeff > 0 ? NegatedRef(ref) : ref);
4701 }
4702 }
4703 }
4704 if (at_most_one.size() > 1) {
4705 if (type == 0) {
4706 context_->UpdateRuleStats("linear: extracted at most one (max).");
4707 } else {
4708 context_->UpdateRuleStats("linear: extracted at most one (min).");
4709 }
4710 ConstraintProto* new_ct = context_->working_model->add_constraints();
4711 new_ct->set_name(ct->name());
4712 for (const int ref : at_most_one) {
4713 new_ct->mutable_at_most_one()->add_literals(ref);
4714 }
4715 context_->UpdateNewConstraintsVariableUsage();
4716 }
4717 }
4718}
4719
4720// Convert some linear constraint involving only Booleans to their Boolean
4721// form.
4722bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto* ct) {
4723 if (ct->constraint_case() != ConstraintProto::kLinear) return false;
4724 if (context_->ModelIsUnsat()) return false;
4725
4726 // For special kind of constraint detection.
4727 int64_t sum_of_coeffs = 0;
4728 int num_positive = 0;
4729 int num_negative = 0;
4730
4731 const LinearConstraintProto& arg = ct->linear();
4732 const int num_vars = arg.vars_size();
4733 int64_t min_coeff = std::numeric_limits<int64_t>::max();
4734 int64_t max_coeff = 0;
4735 int64_t min_sum = 0;
4736 int64_t max_sum = 0;
4737 for (int i = 0; i < num_vars; ++i) {
4738 // We assume we already ran PresolveLinear().
4739 const int var = arg.vars(i);
4740 const int64_t coeff = arg.coeffs(i);
4741 CHECK(RefIsPositive(var));
4742 CHECK_NE(coeff, 0);
4743 if (context_->MinOf(var) != 0) return false;
4744 if (context_->MaxOf(var) != 1) return false;
4745
4746 sum_of_coeffs += coeff;
4747 if (coeff > 0) {
4748 ++num_positive;
4749 max_sum += coeff;
4750 min_coeff = std::min(min_coeff, coeff);
4751 max_coeff = std::max(max_coeff, coeff);
4752 } else {
4753 // We replace the Boolean ref, by a ref to its negation (1 - x).
4754 ++num_negative;
4755 min_sum += coeff;
4756 min_coeff = std::min(min_coeff, -coeff);
4757 max_coeff = std::max(max_coeff, -coeff);
4758 }
4759 }
4760 CHECK_LE(min_coeff, max_coeff);
4761
4762 // Detect trivially true/false constraints. Note that this is not necessarily
4763 // detected by PresolveLinear(). We do that here because we assume below
4764 // that this cannot happen.
4765 //
4766 // TODO(user): this could be generalized to constraint not containing only
4767 // Booleans.
4768 const Domain rhs_domain = ReadDomainFromProto(arg);
4769 if ((!rhs_domain.Contains(min_sum) &&
4770 min_sum + min_coeff > rhs_domain.Max()) ||
4771 (!rhs_domain.Contains(max_sum) &&
4772 max_sum - min_coeff < rhs_domain.Min())) {
4773 context_->UpdateRuleStats("linear: all booleans and trivially false");
4774 return MarkConstraintAsFalse(ct);
4775 }
4776 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
4777 context_->UpdateRuleStats("linear: all booleans and trivially true");
4778 return RemoveConstraint(ct);
4779 }
4780
4781 // This discover cases like "A + B + C - 3*D = 0"
4782 // where all Booleans must be equivalent!
4783 // This happens a lot on woodlands09.mps for instance.
4784 //
4785 // TODO(user): generalize if enforced?
4786 // TODO(user): generalize to other variant! Use DP to identify constraint with
4787 // just one or two solutions? or a few solution with same variable values?
4788 if (ct->enforcement_literal().empty() && sum_of_coeffs == 0 &&
4789 (num_negative == 1 || num_positive == 1) && rhs_domain.IsFixed() &&
4790 rhs_domain.FixedValue() == 0) {
4791 // This forces either all variable at 1 or all at zero.
4792 context_->UpdateRuleStats("linear: all equivalent!");
4793 for (int i = 1; i < num_vars; ++i) {
4794 if (!context_->StoreBooleanEqualityRelation(ct->linear().vars(0),
4795 ct->linear().vars(i))) {
4796 return false;
4797 }
4798 }
4799 return RemoveConstraint(ct);
4800 }
4801
4802 // Detect clauses, reified ands, at_most_one.
4803 //
4804 // TODO(user): split a == 1 constraint or similar into a clause and an at
4805 // most one constraint?
4806 DCHECK(!rhs_domain.IsEmpty());
4807 if (min_sum + min_coeff > rhs_domain.Max()) {
4808 // All Boolean are false if the reified literal is true.
4809 context_->UpdateRuleStats("linear: negative reified and");
4810 const auto copy = arg;
4811 ct->mutable_bool_and()->clear_literals();
4812 for (int i = 0; i < num_vars; ++i) {
4813 ct->mutable_bool_and()->add_literals(
4814 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
4815 }
4816 PresolveBoolAnd(ct);
4817 return true;
4818 } else if (max_sum - min_coeff < rhs_domain.Min()) {
4819 // All Boolean are true if the reified literal is true.
4820 context_->UpdateRuleStats("linear: positive reified and");
4821 const auto copy = arg;
4822 ct->mutable_bool_and()->clear_literals();
4823 for (int i = 0; i < num_vars; ++i) {
4824 ct->mutable_bool_and()->add_literals(
4825 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
4826 }
4827 PresolveBoolAnd(ct);
4828 return true;
4829 } else if (min_sum + min_coeff >= rhs_domain.Min() &&
4830 rhs_domain.front().end >= max_sum) {
4831 // At least one Boolean is true.
4832 context_->UpdateRuleStats("linear: positive clause");
4833 const auto copy = arg;
4834 ct->mutable_bool_or()->clear_literals();
4835 for (int i = 0; i < num_vars; ++i) {
4836 ct->mutable_bool_or()->add_literals(
4837 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
4838 }
4839 PresolveBoolOr(ct);
4840 return true;
4841 } else if (max_sum - min_coeff <= rhs_domain.Max() &&
4842 rhs_domain.back().start <= min_sum) {
4843 // At least one Boolean is false.
4844 context_->UpdateRuleStats("linear: negative clause");
4845 const auto copy = arg;
4846 ct->mutable_bool_or()->clear_literals();
4847 for (int i = 0; i < num_vars; ++i) {
4848 ct->mutable_bool_or()->add_literals(
4849 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
4850 }
4851 PresolveBoolOr(ct);
4852 return true;
4853 } else if (!HasEnforcementLiteral(*ct) &&
4854 min_sum + max_coeff <= rhs_domain.Max() &&
4855 min_sum + 2 * min_coeff > rhs_domain.Max() &&
4856 rhs_domain.back().start <= min_sum) {
4857 // At most one Boolean is true.
4858 // TODO(user): Support enforced at most one.
4859 context_->UpdateRuleStats("linear: positive at most one");
4860 const auto copy = arg;
4861 ct->mutable_at_most_one()->clear_literals();
4862 for (int i = 0; i < num_vars; ++i) {
4863 ct->mutable_at_most_one()->add_literals(
4864 copy.coeffs(i) > 0 ? copy.vars(i) : NegatedRef(copy.vars(i)));
4865 }
4866 return true;
4867 } else if (!HasEnforcementLiteral(*ct) &&
4868 max_sum - max_coeff >= rhs_domain.Min() &&
4869 max_sum - 2 * min_coeff < rhs_domain.Min() &&
4870 rhs_domain.front().end >= max_sum) {
4871 // At most one Boolean is false.
4872 // TODO(user): Support enforced at most one.
4873 context_->UpdateRuleStats("linear: negative at most one");
4874 const auto copy = arg;
4875 ct->mutable_at_most_one()->clear_literals();
4876 for (int i = 0; i < num_vars; ++i) {
4877 ct->mutable_at_most_one()->add_literals(
4878 copy.coeffs(i) > 0 ? NegatedRef(copy.vars(i)) : copy.vars(i));
4879 }
4880 return true;
4881 } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
4882 min_sum < rhs_domain.Min() &&
4883 min_sum + min_coeff >= rhs_domain.Min() &&
4884 min_sum + 2 * min_coeff > rhs_domain.Max() &&
4885 min_sum + max_coeff <= rhs_domain.Max()) {
4886 // TODO(user): Support enforced exactly one.
4887 context_->UpdateRuleStats("linear: positive equal one");
4888 ConstraintProto* exactly_one = context_->working_model->add_constraints();
4889 exactly_one->set_name(ct->name());
4890 for (int i = 0; i < num_vars; ++i) {
4891 exactly_one->mutable_exactly_one()->add_literals(
4892 arg.coeffs(i) > 0 ? arg.vars(i) : NegatedRef(arg.vars(i)));
4893 }
4894 context_->UpdateNewConstraintsVariableUsage();
4895 return RemoveConstraint(ct);
4896 } else if (!HasEnforcementLiteral(*ct) && rhs_domain.NumIntervals() == 1 &&
4897 max_sum > rhs_domain.Max() &&
4898 max_sum - min_coeff <= rhs_domain.Max() &&
4899 max_sum - 2 * min_coeff < rhs_domain.Min() &&
4900 max_sum - max_coeff >= rhs_domain.Min()) {
4901 // TODO(user): Support enforced exactly one.
4902 context_->UpdateRuleStats("linear: negative equal one");
4903 ConstraintProto* exactly_one = context_->working_model->add_constraints();
4904 exactly_one->set_name(ct->name());
4905 for (int i = 0; i < num_vars; ++i) {
4906 exactly_one->mutable_exactly_one()->add_literals(
4907 arg.coeffs(i) > 0 ? NegatedRef(arg.vars(i)) : arg.vars(i));
4908 }
4909 context_->UpdateNewConstraintsVariableUsage();
4910 return RemoveConstraint(ct);
4911 }
4912
4913 // Expand small expression into clause.
4914 //
4915 // TODO(user): This is bad from a LP relaxation perspective. Do not do that
4916 // now? On another hand it is good for the SAT presolving.
4917 if (num_vars > 3) return false;
4918 context_->UpdateRuleStats("linear: small Boolean expression");
4919
4920 // Enumerate all possible value of the Booleans and add a clause if constraint
4921 // is false. TODO(user): the encoding could be made better in some cases.
4922 const int max_mask = (1 << arg.vars_size());
4923 for (int mask = 0; mask < max_mask; ++mask) {
4924 int64_t value = 0;
4925 for (int i = 0; i < num_vars; ++i) {
4926 if ((mask >> i) & 1) value += arg.coeffs(i);
4927 }
4928 if (rhs_domain.Contains(value)) continue;
4929
4930 // Add a new clause to exclude this bad assignment.
4931 ConstraintProto* new_ct = context_->working_model->add_constraints();
4932 auto* new_arg = new_ct->mutable_bool_or();
4933 if (HasEnforcementLiteral(*ct)) {
4934 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
4935 }
4936 for (int i = 0; i < num_vars; ++i) {
4937 new_arg->add_literals(((mask >> i) & 1) ? NegatedRef(arg.vars(i))
4938 : arg.vars(i));
4939 }
4940 }
4941
4942 context_->UpdateNewConstraintsVariableUsage();
4943 return RemoveConstraint(ct);
4944}
4945
4946bool CpModelPresolver::PresolveInterval(int c, ConstraintProto* ct) {
4947 if (context_->ModelIsUnsat()) return false;
4948 IntervalConstraintProto* interval = ct->mutable_interval();
4949
4950 // If the size is < 0, then the interval cannot be performed.
4951 if (!ct->enforcement_literal().empty() && context_->SizeMax(c) < 0) {
4952 context_->UpdateRuleStats("interval: negative size implies unperformed");
4953 return MarkConstraintAsFalse(ct);
4954 }
4955
4956 if (ct->enforcement_literal().empty()) {
4957 bool domain_changed = false;
4958 // Size can't be negative.
4959 if (!context_->IntersectDomainWith(
4960 interval->size(), Domain(0, std::numeric_limits<int64_t>::max()),
4961 &domain_changed)) {
4962 return false;
4963 }
4964 if (domain_changed) {
4965 context_->UpdateRuleStats(
4966 "interval: performed intervals must have a positive size");
4967 }
4968 }
4969
4970 // Note that the linear relation is stored elsewhere, so it is safe to just
4971 // remove such special interval constraint.
4972 if (context_->ConstraintVariableGraphIsUpToDate() &&
4973 context_->IntervalUsage(c) == 0) {
4974 context_->UpdateRuleStats("intervals: removed unused interval");
4975 return RemoveConstraint(ct);
4976 }
4977
4978 bool changed = false;
4979 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_start());
4980 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_size());
4981 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_end());
4982 return changed;
4983}
4984
4985// TODO(user): avoid code duplication between expand and presolve.
4986bool CpModelPresolver::PresolveInverse(ConstraintProto* ct) {
4987 const int size = ct->inverse().f_direct().size();
4988 bool changed = false;
4989
4990 // Make sure the domains are included in [0, size - 1).
4991 for (const int ref : ct->inverse().f_direct()) {
4992 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
4993 VLOG(1) << "Empty domain for a variable in ExpandInverse()";
4994 return false;
4995 }
4996 }
4997 for (const int ref : ct->inverse().f_inverse()) {
4998 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
4999 VLOG(1) << "Empty domain for a variable in ExpandInverse()";
5000 return false;
5001 }
5002 }
5003
5004 // Detect duplicated variable.
5005 // Even with negated variables, the reduced domain in [0..size - 1]
5006 // implies that the constraint is infeasible if ref and its negation
5007 // appear together.
5008 {
5009 absl::flat_hash_set<int> direct_vars;
5010 for (const int ref : ct->inverse().f_direct()) {
5011 const auto [it, inserted] = direct_vars.insert(PositiveRef(ref));
5012 if (!inserted) {
5013 return context_->NotifyThatModelIsUnsat("inverse: duplicated variable");
5014 }
5015 }
5016
5017 absl::flat_hash_set<int> inverse_vars;
5018 for (const int ref : ct->inverse().f_inverse()) {
5019 const auto [it, inserted] = inverse_vars.insert(PositiveRef(ref));
5020 if (!inserted) {
5021 return context_->NotifyThatModelIsUnsat("inverse: duplicated variable");
5022 }
5023 }
5024 }
5025
5026 // Propagate from one vector to its counterpart.
5027 // Note this reaches the fixpoint as there is a one to one mapping between
5028 // (variable-value) pairs in each vector.
5029 const auto filter_inverse_domain =
5030 [this, size, &changed](const auto& direct, const auto& inverse) {
5031 // Build the set of values in the inverse vector.
5032 std::vector<absl::flat_hash_set<int64_t>> inverse_values(size);
5033 for (int i = 0; i < size; ++i) {
5034 const Domain domain = context_->DomainOf(inverse[i]);
5035 for (const int64_t j : domain.Values()) {
5036 inverse_values[i].insert(j);
5037 }
5038 }
5039
5040 // Propagate from the inverse vector to the direct vector. Reduce the
5041 // domains of each variable in the direct vector by checking that the
5042 // inverse value exists.
5043 std::vector<int64_t> possible_values;
5044 for (int i = 0; i < size; ++i) {
5045 possible_values.clear();
5046 const Domain domain = context_->DomainOf(direct[i]);
5047 bool removed_value = false;
5048 for (const int64_t j : domain.Values()) {
5049 if (inverse_values[j].contains(i)) {
5050 possible_values.push_back(j);
5051 } else {
5052 removed_value = true;
5053 }
5054 }
5055 if (removed_value) {
5056 changed = true;
5057 if (!context_->IntersectDomainWith(
5058 direct[i], Domain::FromValues(possible_values))) {
5059 VLOG(1) << "Empty domain for a variable in ExpandInverse()";
5060 return false;
5061 }
5062 }
5063 }
5064 return true;
5065 };
5066
5067 if (!filter_inverse_domain(ct->inverse().f_direct(),
5068 ct->inverse().f_inverse())) {
5069 return false;
5070 }
5071
5072 if (!filter_inverse_domain(ct->inverse().f_inverse(),
5073 ct->inverse().f_direct())) {
5074 return false;
5075 }
5076
5077 if (changed) {
5078 context_->UpdateRuleStats("inverse: reduce domains");
5079 }
5080
5081 return false;
5082}
5083
5084bool CpModelPresolver::PresolveElement(int c, ConstraintProto* ct) {
5085 if (context_->ModelIsUnsat()) return false;
5086
5087 if (ct->element().exprs().empty()) {
5088 context_->UpdateRuleStats("element: empty array");
5089 return context_->NotifyThatModelIsUnsat();
5090 }
5091
5092 bool changed = false;
5093 changed |= CanonicalizeLinearExpression(
5094 *ct, ct->mutable_element()->mutable_linear_index());
5095 changed |= CanonicalizeLinearExpression(
5096 *ct, ct->mutable_element()->mutable_linear_target());
5097 for (int i = 0; i < ct->element().exprs_size(); ++i) {
5098 changed |= CanonicalizeLinearExpression(
5099 *ct, ct->mutable_element()->mutable_exprs(i));
5100 }
5101
5102 const LinearExpressionProto& index = ct->element().linear_index();
5103 const LinearExpressionProto& target = ct->element().linear_target();
5104
5105 // TODO(user): think about this once we do have such constraint.
5106 if (HasEnforcementLiteral(*ct)) return false;
5107
5108 // Reduce index domain from the array size.
5109 {
5110 bool index_modified = false;
5111 if (!context_->IntersectDomainWith(
5112 index, Domain(0, ct->element().exprs_size() - 1),
5113 &index_modified)) {
5114 return false;
5115 }
5116 if (index_modified) {
5117 context_->UpdateRuleStats(
5118 "element: reduced index domain from array size");
5119 }
5120 }
5121
5122 // Special case if the index is fixed.
5123 if (context_->IsFixed(index)) {
5124 const int64_t index_value = context_->FixedValue(index);
5125 ConstraintProto* new_ct = context_->working_model->add_constraints();
5126 new_ct->mutable_linear()->add_domain(0);
5127 new_ct->mutable_linear()->add_domain(0);
5128 AddLinearExpressionToLinearConstraint(target, 1, new_ct->mutable_linear());
5129 AddLinearExpressionToLinearConstraint(ct->element().exprs(index_value), -1,
5130 new_ct->mutable_linear());
5131 context_->CanonicalizeLinearConstraint(new_ct);
5132 context_->UpdateNewConstraintsVariableUsage();
5133 context_->UpdateRuleStats("element: fixed index");
5134 return RemoveConstraint(ct);
5135 }
5136
5137 // We know index is not fixed.
5138 const int index_var = index.vars(0);
5139
5140 {
5141 // Cleanup the array: if exprs[i] contains index_var, fix its value.
5142 const Domain& index_var_domain = context_->DomainOf(index_var);
5143 std::vector<int64_t> reached_indices(ct->element().exprs_size(), false);
5144 for (const int64_t index_var_value : index_var_domain.Values()) {
5145 const int64_t index_value =
5146 AffineExpressionValueAt(index, index_var_value);
5147 reached_indices[index_value] = true;
5148 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5149 if (expr.vars_size() == 1 && expr.vars(0) == index_var) {
5150 const int64_t expr_value =
5151 AffineExpressionValueAt(expr, index_var_value);
5152 ct->mutable_element()->mutable_exprs(index_value)->clear_vars();
5153 ct->mutable_element()->mutable_exprs(index_value)->clear_coeffs();
5154 ct->mutable_element()
5155 ->mutable_exprs(index_value)
5156 ->set_offset(expr_value);
5157 changed = true;
5158 context_->UpdateRuleStats(
5159 "element: fix expression depending on the index");
5160 }
5161 }
5162
5163 // Cleanup the array: clear unreached expressions.
5164 for (int i = 0; i < ct->element().exprs_size(); ++i) {
5165 if (!reached_indices[i]) {
5166 ct->mutable_element()->mutable_exprs(i)->Clear();
5167 changed = true;
5168 }
5169 }
5170 }
5171
5172 // Canonicalization and cleanups of the expressions could have messed up the
5173 // var-constraint graph.
5174 if (changed) context_->UpdateConstraintVariableUsage(c);
5175
5176 // Reduces the domain of the index.
5177 {
5178 const Domain& index_var_domain = context_->DomainOf(index_var);
5179 const Domain& target_domain = context_->DomainSuperSetOf(target);
5180 std::vector<int64_t> possible_index_var_values;
5181 for (const int64_t index_var_value : index_var_domain.Values()) {
5182 const int64_t index_value =
5183 AffineExpressionValueAt(index, index_var_value);
5184 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5185
5186 bool is_possible_index;
5187 if (target.vars_size() == 1 && target.vars(0) == index_var) {
5188 // The target domain can be reduced if it shares its variable with the
5189 // index.
5190 is_possible_index = context_->DomainContains(
5191 expr, AffineExpressionValueAt(target, index_var_value));
5192 } else {
5193 const Domain target_var_domain =
5194 target.vars_size() == 1 ? context_->DomainOf(target.vars(0))
5195 : Domain(0);
5196 const Domain expr_var_domain = expr.vars_size() == 1
5197 ? context_->DomainOf(expr.vars(0))
5198 : Domain(0);
5199 const int64_t target_coeff =
5200 target.vars_size() == 1 ? target.coeffs(0) : 0;
5201 const int64_t expr_coeff = expr.vars_size() == 1 ? expr.coeffs(0) : 0;
5203 target_var_domain, target_coeff, expr_var_domain, -expr_coeff,
5204 -target.offset() + expr.offset());
5205 }
5206
5207 if (is_possible_index) {
5208 possible_index_var_values.push_back(index_var_value);
5209 } else {
5210 ct->mutable_element()->mutable_exprs(index_value)->Clear();
5211 changed = true;
5212 }
5213 }
5214 if (possible_index_var_values.size() < index_var_domain.Size()) {
5215 if (!context_->IntersectDomainWith(
5216 index_var, Domain::FromValues(possible_index_var_values))) {
5217 return true;
5218 }
5219 context_->UpdateRuleStats("element: reduced index domain ");
5220 // If the index is fixed, this is a equality constraint.
5221 if (context_->IsFixed(index)) {
5222 ConstraintProto* const eq = context_->working_model->add_constraints();
5223 eq->mutable_linear()->add_domain(0);
5224 eq->mutable_linear()->add_domain(0);
5225 AddLinearExpressionToLinearConstraint(target, 1, eq->mutable_linear());
5227 ct->element().exprs(context_->FixedValue(index)), -1,
5228 eq->mutable_linear());
5229 context_->CanonicalizeLinearConstraint(eq);
5230 context_->UpdateNewConstraintsVariableUsage();
5231 context_->UpdateRuleStats("element: fixed index");
5232 return RemoveConstraint(ct);
5233 }
5234 }
5235 }
5236
5237 bool all_included_in_target_domain = true;
5238 {
5239 // Accumulate expressions domains to build a superset of the target domain.
5240 Domain infered_domain;
5241 const Domain& index_var_domain = context_->DomainOf(index_var);
5242 const Domain& target_domain = context_->DomainSuperSetOf(target);
5243 for (const int64_t index_var_value : index_var_domain.Values()) {
5244 const int64_t index_value =
5245 AffineExpressionValueAt(index, index_var_value);
5246 CHECK_GE(index_value, 0);
5247 CHECK_LT(index_value, ct->element().exprs_size());
5248 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5249 const Domain expr_domain = context_->DomainSuperSetOf(expr);
5250 if (!expr_domain.IsIncludedIn(target_domain)) {
5251 all_included_in_target_domain = false;
5252 }
5253 infered_domain = infered_domain.UnionWith(expr_domain);
5254 }
5255
5256 bool domain_modified = false;
5257 if (!context_->IntersectDomainWith(target, infered_domain,
5258 &domain_modified)) {
5259 return true;
5260 }
5261 if (domain_modified) {
5262 context_->UpdateRuleStats("element: reduce target domain");
5263 }
5264 }
5265
5266 bool all_constants = true;
5267 {
5268 const Domain& index_var_domain = context_->DomainOf(index_var);
5269 std::vector<int64_t> expr_constants;
5270
5271 for (const int64_t index_var_value : index_var_domain.Values()) {
5272 const int64_t index_value =
5273 AffineExpressionValueAt(index, index_var_value);
5274 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5275 if (context_->IsFixed(expr)) {
5276 expr_constants.push_back(context_->FixedValue(expr));
5277 } else {
5278 all_constants = false;
5279 break;
5280 }
5281 }
5282 }
5283
5284 // Detect is the element can be rewritten as a * target + b * index == c.
5285 if (all_constants) {
5286 if (context_->IsFixed(target)) {
5287 // If the accessible part of the array is made of a single constant
5288 // value, then we do not care about the index. And, because of the
5289 // previous target domain reduction, the target is also fixed.
5290 context_->UpdateRuleStats("element: one value array");
5291 return RemoveConstraint(ct);
5292 }
5293 int64_t first_index_var_value;
5294 int64_t first_target_var_value;
5295 int64_t d_index = 0;
5296 int64_t d_target = 0;
5297 int num_terms = 0;
5298 bool is_affine = true;
5299 const Domain& index_var_domain = context_->DomainOf(index_var);
5300 for (const int64_t index_var_value : index_var_domain.Values()) {
5301 ++num_terms;
5302 const int64_t index_value =
5303 AffineExpressionValueAt(index, index_var_value);
5304 const int64_t expr_value =
5305 context_->FixedValue(ct->element().exprs(index_value));
5306 const int64_t target_var_value = GetInnerVarValue(target, expr_value);
5307 if (num_terms == 1) {
5308 first_index_var_value = index_var_value;
5309 first_target_var_value = target_var_value;
5310 } else if (num_terms == 2) {
5311 d_index = index_var_value - first_index_var_value;
5312 d_target = target_var_value - first_target_var_value;
5313 const int64_t gcd = std::gcd(d_index, d_target);
5314 d_index /= gcd;
5315 d_target /= gcd;
5316 } else {
5317 const int64_t offset = CapSub(
5318 CapProd(d_index, CapSub(target_var_value, first_target_var_value)),
5319 CapProd(d_target, CapSub(index_var_value, first_index_var_value)));
5320 if (offset != 0) {
5321 is_affine = false;
5322 break;
5323 }
5324 }
5325 }
5326 if (is_affine) {
5327 const int64_t offset = CapSub(CapProd(first_target_var_value, d_index),
5328 CapProd(first_index_var_value, d_target));
5329 if (!AtMinOrMaxInt64(offset)) {
5330 ConstraintProto* const lin = context_->working_model->add_constraints();
5331 lin->mutable_linear()->add_vars(target.vars(0));
5332 lin->mutable_linear()->add_coeffs(d_index);
5333 lin->mutable_linear()->add_vars(index_var);
5334 lin->mutable_linear()->add_coeffs(-d_target);
5335 lin->mutable_linear()->add_domain(offset);
5336 lin->mutable_linear()->add_domain(offset);
5337 context_->CanonicalizeLinearConstraint(lin);
5338 context_->UpdateNewConstraintsVariableUsage();
5339 context_->UpdateRuleStats("element: rewrite as affine constraint");
5340 return RemoveConstraint(ct);
5341 }
5342 }
5343 }
5344
5345 // If a variable (target or index) appears only in this constraint, it does
5346 // not necessarily mean that we can remove the constraint, as the variable
5347 // can be used multiple times in the element. So let's count the local
5348 // uses of each variable.
5349 //
5350 // TODO(user): now that we used fixed values for these case, this is no longer
5351 // needed I think.
5352 absl::flat_hash_map<int, int> local_var_occurrence_counter;
5353 {
5354 auto count = [&local_var_occurrence_counter](
5355 const LinearExpressionProto& expr) mutable {
5356 for (const int var : expr.vars()) {
5357 local_var_occurrence_counter[var]++;
5358 }
5359 };
5360 count(index);
5361 count(target);
5362 for (const int64_t index_var_value :
5363 context_->DomainOf(index_var).Values()) {
5364 count(
5365 ct->element().exprs(AffineExpressionValueAt(index, index_var_value)));
5366 }
5367 }
5368
5369 if (context_->VariableIsUniqueAndRemovable(index_var) &&
5370 local_var_occurrence_counter.at(index_var) == 1) {
5371 if (all_constants) {
5372 // This constraint is just here to reduce the domain of the target! We can
5373 // add it to the mapping_model to reconstruct the index value during
5374 // postsolve and get rid of it now.
5375 context_->UpdateRuleStats(
5376 "element: removed as the index is not used elsewhere");
5377 context_->MarkVariableAsRemoved(index_var);
5378 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
5379 return RemoveConstraint(ct);
5380 } else {
5381 context_->UpdateRuleStats("TODO element: index not used elsewhere");
5382 }
5383 }
5384
5385 if (target.vars_size() == 1 && !context_->IsFixed(target.vars(0)) &&
5386 context_->VariableIsUniqueAndRemovable(target.vars(0)) &&
5387 local_var_occurrence_counter.at(target.vars(0)) == 1) {
5388 if (all_included_in_target_domain && std::abs(target.coeffs(0)) == 1) {
5389 context_->UpdateRuleStats(
5390 "element: removed as the target is not used elsewhere");
5391 context_->MarkVariableAsRemoved(target.vars(0));
5392 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
5393 return RemoveConstraint(ct);
5394 } else {
5395 context_->UpdateRuleStats("TODO element: target not used elsewhere");
5396 }
5397 }
5398
5399 return changed;
5400}
5401
5402bool CpModelPresolver::PresolveTable(ConstraintProto* ct) {
5403 if (context_->ModelIsUnsat()) return false;
5404
5405 bool changed = false;
5406 for (int i = 0; i < ct->table().exprs_size(); ++i) {
5407 changed |= CanonicalizeLinearExpression(
5408 *ct, ct->mutable_table()->mutable_exprs(i));
5409 }
5410
5411 const int initial_num_exprs = ct->table().exprs_size();
5412 if (initial_num_exprs > 0) CanonicalizeTable(context_, ct);
5413 changed |= (ct->table().exprs_size() != initial_num_exprs);
5414
5415 if (ct->table().exprs().empty()) {
5416 context_->UpdateRuleStats("table: no expressions");
5417 return RemoveConstraint(ct);
5418 }
5419
5420 if (ct->table().values().empty()) {
5421 if (ct->table().negated()) {
5422 context_->UpdateRuleStats("table: negative table without tuples");
5423 return RemoveConstraint(ct);
5424 } else {
5425 context_->UpdateRuleStats("table: positive table without tuples");
5426 return MarkConstraintAsFalse(ct);
5427 }
5428 }
5429
5430 int num_fixed_exprs = 0;
5431 for (const LinearExpressionProto& expr : ct->table().exprs()) {
5432 if (context_->IsFixed(expr)) ++num_fixed_exprs;
5433 }
5434 if (num_fixed_exprs == ct->table().exprs_size()) {
5435 context_->UpdateRuleStats("table: all expressions are fixed");
5436 DCHECK_LE(ct->table().values_size(), num_fixed_exprs);
5437 if (ct->table().negated() == ct->table().values().empty()) {
5438 context_->UpdateRuleStats("table: always true");
5439 return RemoveConstraint(ct);
5440 } else {
5441 context_->UpdateRuleStats("table: always false");
5442 return MarkConstraintAsFalse(ct);
5443 }
5444 return RemoveConstraint(ct);
5445 }
5446
5447 if (num_fixed_exprs > 0) {
5448 CanonicalizeTable(context_, ct);
5449 }
5450
5451 // Nothing more to do for negated tables.
5452 if (ct->table().negated()) return changed;
5453
5454 // And for constraints with enforcement literals.
5455 if (HasEnforcementLiteral(*ct)) return changed;
5456
5457 // Filter the variables domains.
5458 const int num_exprs = ct->table().exprs_size();
5459 const int num_tuples = ct->table().values_size() / num_exprs;
5460 std::vector<std::vector<int64_t>> new_domains(num_exprs);
5461 for (int e = 0; e < num_exprs; ++e) {
5462 const LinearExpressionProto& expr = ct->table().exprs(e);
5463 if (context_->IsFixed(expr)) {
5464 new_domains[e].push_back(context_->FixedValue(expr));
5465 continue;
5466 }
5467
5468 for (int t = 0; t < num_tuples; ++t) {
5469 new_domains[e].push_back(ct->table().values(t * num_exprs + e));
5470 }
5471 gtl::STLSortAndRemoveDuplicates(&new_domains[e]);
5472 DCHECK_EQ(1, expr.vars_size());
5473 DCHECK_EQ(1, expr.coeffs(0));
5474 DCHECK_EQ(0, expr.offset());
5475 const int var = expr.vars(0);
5476 bool domain_modified = false;
5477 if (!context_->IntersectDomainWith(var, Domain::FromValues(new_domains[e]),
5478 &domain_modified)) {
5479 return true;
5480 }
5481 if (domain_modified) {
5482 context_->UpdateRuleStats("table: reduce variable domain");
5483 }
5484 }
5485
5486 if (num_exprs == 1) {
5487 // Now that we have properly updated the domain, we can remove the
5488 // constraint.
5489 context_->UpdateRuleStats("table: only one column!");
5490 return RemoveConstraint(ct);
5491 }
5492
5493 // Check that the table is not complete or just here to exclude a few tuples.
5494 double prod = 1.0;
5495 for (int e = 0; e < num_exprs; ++e) prod *= new_domains[e].size();
5496 if (prod == static_cast<double>(num_tuples)) {
5497 context_->UpdateRuleStats("table: all tuples!");
5498 return RemoveConstraint(ct);
5499 }
5500
5501 // Convert to the negated table if we gain a lot of entries by doing so.
5502 // Note however that currently the negated table do not propagate as much as
5503 // it could.
5504 if (static_cast<double>(num_tuples) > 0.7 * prod) {
5505 std::vector<std::vector<int64_t>> current_tuples(num_tuples);
5506 for (int t = 0; t < num_tuples; ++t) {
5507 current_tuples[t].resize(num_exprs);
5508 for (int e = 0; e < num_exprs; ++e) {
5509 current_tuples[t][e] = ct->table().values(t * num_exprs + e);
5510 }
5511 }
5512
5513 // Enumerate all possible tuples.
5514 std::vector<std::vector<int64_t>> var_to_values(num_exprs);
5515 for (int e = 0; e < num_exprs; ++e) {
5516 var_to_values[e].assign(new_domains[e].begin(), new_domains[e].end());
5517 }
5518 std::vector<std::vector<int64_t>> all_tuples(prod);
5519 for (int i = 0; i < prod; ++i) {
5520 all_tuples[i].resize(num_exprs);
5521 int index = i;
5522 for (int j = 0; j < num_exprs; ++j) {
5523 all_tuples[i][j] = var_to_values[j][index % var_to_values[j].size()];
5524 index /= var_to_values[j].size();
5525 }
5526 }
5528
5529 // Compute the complement of new_tuples.
5530 std::vector<std::vector<int64_t>> diff(prod - num_tuples);
5531 std::set_difference(all_tuples.begin(), all_tuples.end(),
5532 current_tuples.begin(), current_tuples.end(),
5533 diff.begin());
5534
5535 // Negate the constraint.
5536 ct->mutable_table()->set_negated(!ct->table().negated());
5537 ct->mutable_table()->clear_values();
5538 for (const std::vector<int64_t>& t : diff) {
5539 for (const int64_t v : t) ct->mutable_table()->add_values(v);
5540 }
5541 context_->UpdateRuleStats("table: negated");
5542 }
5543
5544 return changed;
5545}
5546
5547namespace {
5548
5549// A container that is valid if only one value was added.
5550class UniqueNonNegativeValue {
5551 public:
5552 void Add(int value) {
5553 DCHECK_GE(value, 0);
5554 if (value_ == -1) {
5555 value_ = value;
5556 } else {
5557 value_ = -2;
5558 }
5559 }
5560
5561 bool HasUniqueValue() const { return value_ >= 0; }
5562
5563 int64_t value() const {
5564 DCHECK(HasUniqueValue());
5565 return value_;
5566 }
5567
5568 private:
5569 int value_ = -1;
5570};
5571
5572} // namespace
5573
5574bool CpModelPresolver::PresolveAllDiff(ConstraintProto* ct) {
5575 if (context_->ModelIsUnsat()) return false;
5576 if (HasEnforcementLiteral(*ct)) return false;
5577
5578 AllDifferentConstraintProto& all_diff = *ct->mutable_all_diff();
5579
5580 bool variables_have_changed = false;
5581 for (LinearExpressionProto& exp :
5582 *(ct->mutable_all_diff()->mutable_exprs())) {
5583 variables_have_changed |= CanonicalizeLinearExpression(*ct, &exp);
5584 }
5585
5586 const int size = all_diff.exprs_size();
5587 if (size == 0) {
5588 context_->UpdateRuleStats("all_diff: empty constraint");
5589 return RemoveConstraint(ct);
5590 }
5591 if (size == 1) {
5592 context_->UpdateRuleStats("all_diff: one expression");
5593 return RemoveConstraint(ct);
5594 }
5595
5596 absl::flat_hash_set<int64_t> fixed_values;
5597 int new_size = 0;
5598 for (int i = 0; i < size; ++i) {
5599 if (!context_->IsFixed(all_diff.exprs(i))) {
5600 if (i != new_size) {
5601 *all_diff.mutable_exprs(new_size) = all_diff.exprs(i);
5602 }
5603 ++new_size;
5604 } else {
5605 const int64_t value = context_->FixedValue(all_diff.exprs(i));
5606 if (!fixed_values.insert(value).second) {
5607 return context_->NotifyThatModelIsUnsat(
5608 "all_diff: duplicate fixed values");
5609 }
5610 }
5611 }
5612
5613 if (new_size < size) {
5614 all_diff.mutable_exprs()->DeleteSubrange(new_size, size - new_size);
5615 context_->UpdateRuleStats("all_diff: remove fixed expressions");
5616 }
5617
5618 if (!fixed_values.empty()) {
5619 const Domain to_keep =
5620 Domain::FromValues({fixed_values.begin(), fixed_values.end()})
5621 .Complement();
5622 bool propagated = false;
5623 for (int i = 0; i < all_diff.exprs_size(); ++i) {
5624 if (!context_->IntersectDomainWith(all_diff.exprs(i), to_keep,
5625 &propagated)) {
5626 return true;
5627 }
5628 }
5629 if (propagated) {
5630 context_->UpdateRuleStats("all_diff: propagate fixed expressions");
5631 }
5632 }
5633
5634 // Detect duplicate expressions, and remove impossible values from expressions
5635 // with the same variable.
5636 // We use btree_map to have a deterministic order.
5637 absl::btree_map<int, std::vector<std::pair<int64_t, int64_t>>> terms;
5638 std::vector<int64_t> forbidden_values;
5639 for (const LinearExpressionProto& expr : all_diff.exprs()) {
5640 if (expr.vars_size() != 1) continue;
5641 terms[expr.vars(0)].push_back(
5642 std::make_pair(expr.coeffs(0), expr.offset()));
5643 }
5644 for (auto& [var, terms] : terms) {
5645 if (terms.size() == 1) continue;
5646 std::sort(terms.begin(), terms.end());
5647
5648 // Check for duplicate expressions.
5649 for (int i = 1; i < terms.size(); ++i) {
5650 if (terms[i] == terms[i - 1]) {
5651 return context_->NotifyThatModelIsUnsat(
5652 "all_diff: duplicate expressions");
5653 }
5654 }
5655
5656 // Remove impossible values from expressions with the same variable.
5657 // a * var + b == c * var + d
5658 // -> (a - c) * var = d - b
5659 // Therefore var cannot take the value (d - b) / (a - c) if integral.
5660 forbidden_values.clear();
5661 for (int i = 0; i + 1 < terms.size(); ++i) {
5662 for (int j = i + 1; j < terms.size(); ++j) {
5663 const int64_t coeff = terms[i].first - terms[j].first;
5664 if (coeff == 0) continue;
5665 const int64_t offset = terms[j].second - terms[i].second;
5666 const int64_t value = offset / coeff;
5667 if (value * coeff == offset) {
5668 forbidden_values.push_back(value);
5669 }
5670 }
5671 }
5672 if (!forbidden_values.empty()) {
5673 const Domain to_keep = Domain::FromValues(forbidden_values).Complement();
5674 bool propagated = false;
5675 if (!context_->IntersectDomainWith(var, to_keep, &propagated)) {
5676 return true;
5677 }
5678 if (propagated) {
5679 context_->UpdateRuleStats(
5680 "all_diff: propagate expressions with the same variable");
5681 }
5682 }
5683 }
5684
5685 // Propagate mandatory values if the all diff is actually a permutation.
5686 if (all_diff.exprs_size() >= 2 && all_diff.exprs_size() <= 512) {
5687 Domain union_of_domains = context_->DomainSuperSetOf(all_diff.exprs(0));
5688 for (int i = 1; i < all_diff.exprs_size(); ++i) {
5689 union_of_domains = union_of_domains.UnionWith(
5690 context_->DomainSuperSetOf(all_diff.exprs(i)));
5691 }
5692
5693 if (union_of_domains.Size() < all_diff.exprs_size()) {
5694 return context_->NotifyThatModelIsUnsat(
5695 "all_diff: more expressions than values");
5696 }
5697
5698 if (all_diff.exprs_size() == union_of_domains.Size()) {
5699 absl::btree_map<int64_t, UniqueNonNegativeValue> value_to_index;
5700 for (int i = 0; i < all_diff.exprs_size(); ++i) {
5701 const LinearExpressionProto& expr = all_diff.exprs(i);
5702 DCHECK_EQ(expr.vars_size(), 1);
5703 for (const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
5704 value_to_index[AffineExpressionValueAt(expr, v)].Add(i);
5705 }
5706 }
5707
5708 bool propagated = false;
5709 for (const auto& [value, unique_index] : value_to_index) {
5710 if (!unique_index.HasUniqueValue()) continue;
5711
5712 const LinearExpressionProto& expr =
5713 all_diff.exprs(unique_index.value());
5714 if (!context_->IntersectDomainWith(expr, Domain(value), &propagated)) {
5715 return true;
5716 }
5717 }
5718
5719 if (propagated) {
5720 context_->UpdateRuleStats(
5721 "all_diff: propagated mandatory values in permutation");
5722 }
5723 }
5724 }
5725
5726 return variables_have_changed;
5727}
5728
5729namespace {
5730
5731// Add the constraint (lhs => rhs) to the given proto. The hash map lhs ->
5732// bool_and constraint index is used to merge implications with the same lhs.
5733void AddImplication(int lhs, int rhs, CpModelProto* proto,
5734 absl::flat_hash_map<int, int>* ref_to_bool_and) {
5735 if (ref_to_bool_and->contains(lhs)) {
5736 const int ct_index = (*ref_to_bool_and)[lhs];
5737 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
5738 } else if (ref_to_bool_and->contains(NegatedRef(rhs))) {
5739 const int ct_index = (*ref_to_bool_and)[NegatedRef(rhs)];
5740 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
5741 NegatedRef(lhs));
5742 } else {
5743 (*ref_to_bool_and)[lhs] = proto->constraints_size();
5744 ConstraintProto* ct = proto->add_constraints();
5745 ct->add_enforcement_literal(lhs);
5746 ct->mutable_bool_and()->add_literals(rhs);
5747 }
5748}
5749
5750template <typename ClauseContainer>
5751void ExtractClauses(bool merge_into_bool_and,
5752 absl::Span<const int> index_mapping,
5753 const ClauseContainer& container, CpModelProto* proto,
5754 std::string_view debug_name = "") {
5755 // We regroup the "implication" into bool_and to have a more concise proto and
5756 // also for nicer information about the number of binary clauses.
5757 //
5758 // Important: however, we do not do that for the model used during postsolving
5759 // since the order of the constraints might be important there depending on
5760 // how we perform the postsolve.
5761 absl::flat_hash_map<int, int> ref_to_bool_and;
5762 for (int i = 0; i < container.NumClauses(); ++i) {
5763 const std::vector<Literal>& clause = container.Clause(i);
5764 if (clause.empty()) continue;
5765
5766 // bool_and.
5767 //
5768 // TODO(user): Be smarter in how we regroup clause of size 2?
5769 if (merge_into_bool_and && clause.size() == 2) {
5770 const int var_a = index_mapping[clause[0].Variable().value()];
5771 const int var_b = index_mapping[clause[1].Variable().value()];
5772 const int ref_a = clause[0].IsPositive() ? var_a : NegatedRef(var_a);
5773 const int ref_b = clause[1].IsPositive() ? var_b : NegatedRef(var_b);
5774 AddImplication(NegatedRef(ref_a), ref_b, proto, &ref_to_bool_and);
5775 continue;
5776 }
5777
5778 // bool_or.
5779 ConstraintProto* ct = proto->add_constraints();
5780 if (!debug_name.empty()) {
5781 ct->set_name(std::string(debug_name));
5782 }
5783 ct->mutable_bool_or()->mutable_literals()->Reserve(clause.size());
5784 for (const Literal l : clause) {
5785 const int var = index_mapping[l.Variable().value()];
5786 if (l.IsPositive()) {
5787 ct->mutable_bool_or()->add_literals(var);
5788 } else {
5789 ct->mutable_bool_or()->add_literals(NegatedRef(var));
5790 }
5791 }
5792 }
5793}
5794
5795} // namespace
5796
5797bool CpModelPresolver::PresolveNoOverlap(ConstraintProto* ct) {
5798 if (context_->ModelIsUnsat()) return false;
5799 NoOverlapConstraintProto* proto = ct->mutable_no_overlap();
5800 bool changed = false;
5801
5802 // Filter out absent intervals. Process duplicate intervals.
5803 {
5804 // Collect duplicate intervals.
5805 absl::flat_hash_set<int> visited_intervals;
5806 absl::flat_hash_set<int> duplicate_intervals;
5807 for (const int interval_index : proto->intervals()) {
5808 if (context_->ConstraintIsInactive(interval_index)) continue;
5809 if (!visited_intervals.insert(interval_index).second) {
5810 duplicate_intervals.insert(interval_index);
5811 }
5812 }
5813
5814 const int initial_num_intervals = proto->intervals_size();
5815 int new_size = 0;
5816 visited_intervals.clear();
5817
5818 for (int i = 0; i < initial_num_intervals; ++i) {
5819 const int interval_index = proto->intervals(i);
5820 if (context_->ConstraintIsInactive(interval_index)) continue;
5821
5822 if (duplicate_intervals.contains(interval_index)) {
5823 // Once processed, we can always remove further duplicates.
5824 if (!visited_intervals.insert(interval_index).second) continue;
5825
5826 ConstraintProto* interval_ct =
5827 context_->working_model->mutable_constraints(interval_index);
5828
5829 // Case 1: size > 0. Interval must be unperformed.
5830 if (context_->SizeMin(interval_index) > 0) {
5831 if (!MarkConstraintAsFalse(interval_ct)) {
5832 return false;
5833 }
5834 context_->UpdateConstraintVariableUsage(interval_index);
5835 context_->UpdateRuleStats(
5836 "no_overlap: unperform duplicate non zero-sized intervals");
5837 // We can remove the interval from the no_overlap.
5838 continue;
5839 }
5840
5841 // No need to do anything if the size is 0.
5842 if (context_->SizeMax(interval_index) > 0) {
5843 // Case 2: interval is performed. Size must be set to 0.
5844 if (!context_->ConstraintIsOptional(interval_index)) {
5845 if (!context_->IntersectDomainWith(interval_ct->interval().size(),
5846 Domain(0))) {
5847 return false;
5848 }
5849 context_->UpdateRuleStats(
5850 "no_overlap: zero the size of performed duplicate intervals");
5851 // We still need to add the interval to the no_overlap as zero sized
5852 // intervals still cannot overlap with other intervals.
5853 } else { // Case 3: interval is optional and size can be > 0.
5854 const int performed_literal = interval_ct->enforcement_literal(0);
5855 ConstraintProto* size_eq_zero =
5856 context_->working_model->add_constraints();
5857 size_eq_zero->add_enforcement_literal(performed_literal);
5858 size_eq_zero->mutable_linear()->add_domain(0);
5859 size_eq_zero->mutable_linear()->add_domain(0);
5861 interval_ct->interval().size(), 1,
5862 size_eq_zero->mutable_linear());
5863 context_->UpdateRuleStats(
5864 "no_overlap: make duplicate intervals as unperformed or zero "
5865 "sized");
5866 context_->UpdateNewConstraintsVariableUsage();
5867 }
5868 }
5869 }
5870
5871 proto->set_intervals(new_size++, interval_index);
5872 }
5873
5874 if (new_size < initial_num_intervals) {
5875 proto->mutable_intervals()->Truncate(new_size);
5876 context_->UpdateRuleStats("no_overlap: removed absent intervals");
5877 changed = true;
5878 }
5879 }
5880
5881 // Split constraints in disjoint sets.
5882 if (proto->intervals_size() > 1) {
5883 std::vector<IndexedInterval> indexed_intervals;
5884 for (int i = 0; i < proto->intervals().size(); ++i) {
5885 const int index = proto->intervals(i);
5886 indexed_intervals.push_back({index,
5887 IntegerValue(context_->StartMin(index)),
5888 IntegerValue(context_->EndMax(index))});
5889 }
5890 std::vector<std::vector<int>> components;
5891 GetOverlappingIntervalComponents(&indexed_intervals, &components);
5892
5893 if (components.size() > 1) {
5894 for (const std::vector<int>& intervals : components) {
5895 if (intervals.size() <= 1) continue;
5896
5897 NoOverlapConstraintProto* new_no_overlap =
5898 context_->working_model->add_constraints()->mutable_no_overlap();
5899 // Fill in the intervals. Unfortunately, the Assign() method does not
5900 // compile in or-tools.
5901 for (const int i : intervals) {
5902 new_no_overlap->add_intervals(i);
5903 }
5904 }
5905 context_->UpdateNewConstraintsVariableUsage();
5906 context_->UpdateRuleStats("no_overlap: split into disjoint components");
5907 return RemoveConstraint(ct);
5908 }
5909 }
5910
5911 std::vector<int> constant_intervals;
5912 int64_t size_min_of_non_constant_intervals =
5913 std::numeric_limits<int64_t>::max();
5914 for (int i = 0; i < proto->intervals_size(); ++i) {
5915 const int interval_index = proto->intervals(i);
5916 if (context_->IntervalIsConstant(interval_index)) {
5917 constant_intervals.push_back(interval_index);
5918 } else {
5919 size_min_of_non_constant_intervals =
5920 std::min(size_min_of_non_constant_intervals,
5921 context_->SizeMin(interval_index));
5922 }
5923 }
5924
5925 bool move_constraint_last = false;
5926 if (!constant_intervals.empty()) {
5927 // Sort constant_intervals by start min.
5928 std::sort(constant_intervals.begin(), constant_intervals.end(),
5929 [this](int i1, int i2) {
5930 const int64_t s1 = context_->StartMin(i1);
5931 const int64_t e1 = context_->EndMax(i1);
5932 const int64_t s2 = context_->StartMin(i2);
5933 const int64_t e2 = context_->EndMax(i2);
5934 return std::tie(s1, e1) < std::tie(s2, e2);
5935 });
5936
5937 // Check for overlapping constant intervals. We need to check feasibility
5938 // before we simplify the constraint, as we might remove conflicting
5939 // overlapping constant intervals.
5940 for (int i = 0; i + 1 < constant_intervals.size(); ++i) {
5941 if (context_->EndMax(constant_intervals[i]) >
5942 context_->StartMin(constant_intervals[i + 1])) {
5943 context_->UpdateRuleStats("no_overlap: constant intervals overlap");
5944 return context_->NotifyThatModelIsUnsat();
5945 }
5946 }
5947
5948 if (constant_intervals.size() == proto->intervals_size()) {
5949 context_->UpdateRuleStats("no_overlap: no variable intervals");
5950 return RemoveConstraint(ct);
5951 }
5952
5953 absl::flat_hash_set<int> intervals_to_remove;
5954
5955 // If two constant intervals are separated by a gap smaller that the min
5956 // size of all non-constant intervals, then we can merge them.
5957 for (int i = 0; i + 1 < constant_intervals.size(); ++i) {
5958 const int start = i;
5959 while (i + 1 < constant_intervals.size() &&
5960 context_->StartMin(constant_intervals[i + 1]) -
5961 context_->EndMax(constant_intervals[i]) <
5962 size_min_of_non_constant_intervals) {
5963 i++;
5964 }
5965 if (i == start) continue;
5966 for (int j = start; j <= i; ++j) {
5967 intervals_to_remove.insert(constant_intervals[j]);
5968 }
5969 const int64_t new_start = context_->StartMin(constant_intervals[start]);
5970 const int64_t new_end = context_->EndMax(constant_intervals[i]);
5971 proto->add_intervals(context_->working_model->constraints_size());
5972 IntervalConstraintProto* new_interval =
5973 context_->working_model->add_constraints()->mutable_interval();
5974 new_interval->mutable_start()->set_offset(new_start);
5975 new_interval->mutable_size()->set_offset(new_end - new_start);
5976 new_interval->mutable_end()->set_offset(new_end);
5977 move_constraint_last = true;
5978 }
5979
5980 // Cleanup the original proto.
5981 if (!intervals_to_remove.empty()) {
5982 int new_size = 0;
5983 const int old_size = proto->intervals_size();
5984 for (int i = 0; i < old_size; ++i) {
5985 const int interval_index = proto->intervals(i);
5986 if (intervals_to_remove.contains(interval_index)) {
5987 continue;
5988 }
5989 proto->set_intervals(new_size++, interval_index);
5990 }
5991 CHECK_LT(new_size, old_size);
5992 proto->mutable_intervals()->Truncate(new_size);
5993 context_->UpdateRuleStats(
5994 "no_overlap: merge constant contiguous intervals");
5995 intervals_to_remove.clear();
5996 constant_intervals.clear();
5997 changed = true;
5998 context_->UpdateNewConstraintsVariableUsage();
5999 }
6000 }
6001
6002 {
6003 // Special case for "all-diff" encoded as no-overlap.
6004 int num_size_zero_or_one = 0;
6005 for (const int index : proto->intervals()) {
6006 const IntervalConstraintProto& interval =
6007 context_->working_model->constraints(index).interval();
6008 const LinearExpressionProto& size = interval.size();
6009 if (size.vars().empty() && size.offset() >= 0 && size.offset() <= 1) {
6010 ++num_size_zero_or_one;
6011 } else {
6012 break; // early abort
6013 }
6014 }
6015 const int initial_num_intervals = proto->intervals().size();
6016 if (num_size_zero_or_one == initial_num_intervals) {
6017 // If there is only size one, we can remove the size zero as there is
6018 // no constraint on them.
6019 int new_size = 0;
6020 for (const int index : proto->intervals()) {
6021 const IntervalConstraintProto& interval =
6022 context_->working_model->constraints(index).interval();
6023 if (interval.size().offset() == 0) continue;
6024 proto->set_intervals(new_size++, index);
6025 }
6026 if (new_size < initial_num_intervals) {
6027 proto->mutable_intervals()->Truncate(new_size);
6028 changed = true;
6029 context_->UpdateRuleStats("no_overlap: removed size 0 from all diff.");
6030 }
6031 context_->UpdateRuleStats("TODO no_overlap: only size one !");
6032 }
6033 }
6034
6035 if (proto->intervals_size() == 1) {
6036 context_->UpdateRuleStats("no_overlap: only one interval");
6037 return RemoveConstraint(ct);
6038 }
6039 if (proto->intervals().empty()) {
6040 context_->UpdateRuleStats("no_overlap: no intervals");
6041 return RemoveConstraint(ct);
6042 }
6043
6044 // Unfortunately, because we want all intervals to appear before a constraint
6045 // that uses them, we need to move the constraint last when we merged constant
6046 // intervals.
6047 if (move_constraint_last) {
6048 changed = true;
6049 *context_->working_model->add_constraints() = *ct;
6050 context_->UpdateNewConstraintsVariableUsage();
6051 return RemoveConstraint(ct);
6052 }
6053
6054 return changed;
6055}
6056
6057bool CpModelPresolver::PresolveNoOverlap2DFramed(
6058 absl::Span<const Rectangle> fixed_boxes,
6059 absl::Span<const RectangleInRange> non_fixed_boxes, ConstraintProto* ct) {
6060 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6061
6062 DCHECK(!non_fixed_boxes.empty());
6063 Rectangle bounding_box = non_fixed_boxes[0].bounding_area;
6064 for (const RectangleInRange& box : non_fixed_boxes) {
6065 bounding_box.GrowToInclude(box.bounding_area);
6066 }
6067 std::vector<Rectangle> espace_for_single_box =
6068 FindEmptySpaces(bounding_box, {fixed_boxes.begin(), fixed_boxes.end()});
6069 // TODO(user): Find a faster way to see if fixed boxes are delimiting a
6070 // rectangle.
6071 std::vector<Rectangle> empty;
6072 ReduceNumberofBoxesGreedy(&espace_for_single_box, &empty);
6073 ReduceNumberOfBoxesExactMandatory(&espace_for_single_box, &empty);
6074 if (espace_for_single_box.size() != 1) {
6075 // Not a rectangular frame, since the inside is not a rectangle.
6076 return false;
6077 }
6078 Rectangle fixed_boxes_bb = fixed_boxes.front();
6079 for (const Rectangle& box : fixed_boxes) {
6080 fixed_boxes_bb.GrowToInclude(box);
6081 }
6082 const Rectangle framed_region = espace_for_single_box.front();
6083 for (const RectangleInRange& box : non_fixed_boxes) {
6084 if (!box.bounding_area.IsInsideOf(fixed_boxes_bb)) {
6085 // Something can be outside of the frame.
6086 return false;
6087 }
6088 if (non_fixed_boxes.size() > 1 &&
6089 (2 * box.x_size <= framed_region.SizeX() ||
6090 2 * box.y_size <= framed_region.SizeY())) {
6091 // We can fit two boxes in the delimited space between the fixed boxes, so
6092 // we cannot replace it by an at-most-one.
6093 return false;
6094 }
6095 const int x_interval_index = proto.x_intervals(box.box_index);
6096 const int y_interval_index = proto.y_intervals(box.box_index);
6097 if (!context_->working_model->constraints(x_interval_index)
6098 .enforcement_literal()
6099 .empty() &&
6100 !context_->working_model->constraints(y_interval_index)
6101 .enforcement_literal()
6102 .empty()) {
6103 if (context_->working_model->constraints(x_interval_index)
6104 .enforcement_literal(0) !=
6105 context_->working_model->constraints(y_interval_index)
6106 .enforcement_literal(0)) {
6107 // Two different enforcement literals.
6108 return false;
6109 }
6110 }
6111 }
6112 // All this no_overlap_2d constraint is doing is forcing at most one of
6113 // the non-fixed boxes to be in the `framed_region` rectangle. A
6114 // better representation of this is to simply enforce that the items fit
6115 // that rectangle with linear constraints and add a at-most-one constraint.
6116 std::vector<int> enforcement_literals_for_amo;
6117 bool has_mandatory = false;
6118 for (const RectangleInRange& box : non_fixed_boxes) {
6119 const int box_index = box.box_index;
6120 const int x_interval_index = proto.x_intervals(box_index);
6121 const int y_interval_index = proto.y_intervals(box_index);
6122 const ConstraintProto& x_interval_ct =
6123 context_->working_model->constraints(x_interval_index);
6124 const ConstraintProto& y_interval_ct =
6125 context_->working_model->constraints(y_interval_index);
6126 if (x_interval_ct.enforcement_literal().empty() &&
6127 y_interval_ct.enforcement_literal().empty()) {
6128 // Mandatory box, update the domains.
6129 if (has_mandatory) {
6130 return context_->NotifyThatModelIsUnsat(
6131 "Two mandatory boxes in the same space");
6132 }
6133 has_mandatory = true;
6134 if (!context_->IntersectDomainWith(x_interval_ct.interval().start(),
6135 Domain(framed_region.x_min.value(),
6136 framed_region.x_max.value()))) {
6137 return true;
6138 }
6139 if (!context_->IntersectDomainWith(x_interval_ct.interval().end(),
6140 Domain(framed_region.x_min.value(),
6141 framed_region.x_max.value()))) {
6142 return true;
6143 }
6144 if (!context_->IntersectDomainWith(y_interval_ct.interval().start(),
6145 Domain(framed_region.y_min.value(),
6146 framed_region.y_max.value()))) {
6147 return true;
6148 }
6149 if (!context_->IntersectDomainWith(y_interval_ct.interval().end(),
6150 Domain(framed_region.y_min.value(),
6151 framed_region.y_max.value()))) {
6152 return true;
6153 }
6154 } else {
6155 auto add_linear_constraint = [&](const ConstraintProto& interval_ct,
6156 int enforcement_literal,
6157 IntegerValue min, IntegerValue max) {
6158 // TODO(user): If size is constant add only one linear constraint
6159 // instead of two.
6160 context_->AddImplyInDomain(enforcement_literal,
6161 interval_ct.interval().start(),
6162 Domain(min.value(), max.value()));
6163 context_->AddImplyInDomain(enforcement_literal,
6164 interval_ct.interval().end(),
6165 Domain(min.value(), max.value()));
6166 };
6167 const int enforcement_literal =
6168 x_interval_ct.enforcement_literal().empty()
6169 ? y_interval_ct.enforcement_literal(0)
6170 : x_interval_ct.enforcement_literal(0);
6171 enforcement_literals_for_amo.push_back(enforcement_literal);
6172 add_linear_constraint(x_interval_ct, enforcement_literal,
6173 framed_region.x_min, framed_region.x_max);
6174 add_linear_constraint(y_interval_ct, enforcement_literal,
6175 framed_region.y_min, framed_region.y_max);
6176 }
6177 }
6178 if (has_mandatory) {
6179 for (const int lit : enforcement_literals_for_amo) {
6180 if (!context_->SetLiteralToFalse(lit)) {
6181 return true;
6182 }
6183 }
6184 } else if (enforcement_literals_for_amo.size() > 1) {
6185 context_->working_model->add_constraints()
6186 ->mutable_at_most_one()
6187 ->mutable_literals()
6188 ->Add(enforcement_literals_for_amo.begin(),
6189 enforcement_literals_for_amo.end());
6190 }
6191 context_->UpdateRuleStats("no_overlap_2d: at most one rectangle in region");
6192 context_->UpdateNewConstraintsVariableUsage();
6193 return RemoveConstraint(ct);
6194}
6195
6196bool CpModelPresolver::ExpandEncoded2DBinPacking(
6197 absl::Span<const Rectangle> fixed_boxes,
6198 absl::Span<const RectangleInRange> non_fixed_boxes, ConstraintProto* ct) {
6199 const Disjoint2dPackingResult disjoint_packing_presolve_result =
6201 non_fixed_boxes, fixed_boxes,
6202 context_->params()
6203 .maximum_regions_to_split_in_disconnected_no_overlap_2d());
6204 if (disjoint_packing_presolve_result.bins.empty()) return false;
6205
6206 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6207 std::vector<SolutionCrush::BoxInAreaLiteral> box_in_area_lits;
6208 absl::flat_hash_map<int, std::vector<int>> box_to_presence_literal;
6209 // For the boxes that are optional, add a presence literal for each box in a
6210 // fake "absent" bin.
6211 for (int idx = 0; idx < non_fixed_boxes.size(); ++idx) {
6212 const int b = non_fixed_boxes[idx].box_index;
6213 const ConstraintProto& x_interval_ct =
6214 context_->working_model->constraints(proto.x_intervals(b));
6215 const ConstraintProto& y_interval_ct =
6216 context_->working_model->constraints(proto.y_intervals(b));
6217 if (x_interval_ct.enforcement_literal().empty() &&
6218 y_interval_ct.enforcement_literal().empty()) {
6219 // Mandatory box, cannot be in the "absent" bin -1.
6220 continue;
6221 }
6222 int enforcement_literal = x_interval_ct.enforcement_literal().empty()
6223 ? y_interval_ct.enforcement_literal(0)
6224 : x_interval_ct.enforcement_literal(0);
6225 int potentially_other_enforcement_literal =
6226 y_interval_ct.enforcement_literal().empty()
6227 ? x_interval_ct.enforcement_literal(0)
6228 : y_interval_ct.enforcement_literal(0);
6229
6230 if (enforcement_literal == potentially_other_enforcement_literal) {
6231 // The box is in the "absent" bin -1.
6232 box_to_presence_literal[idx].push_back(NegatedRef(enforcement_literal));
6233 } else {
6234 const int interval_is_absent_literal =
6235 context_->NewBoolVarWithConjunction(
6236 {enforcement_literal, potentially_other_enforcement_literal});
6237
6238 BoolArgumentProto* bool_or =
6239 context_->working_model->add_constraints()->mutable_bool_or();
6240 bool_or->add_literals(NegatedRef(interval_is_absent_literal));
6241 for (const int lit :
6242 {enforcement_literal, potentially_other_enforcement_literal}) {
6243 context_->AddImplication(NegatedRef(interval_is_absent_literal), lit);
6244 bool_or->add_literals(NegatedRef(lit));
6245 }
6246 box_to_presence_literal[idx].push_back(interval_is_absent_literal);
6247 }
6248 }
6249 // Now create the literals "item i in bin j".
6250 for (int bin_index = 0;
6251 bin_index < disjoint_packing_presolve_result.bins.size(); ++bin_index) {
6252 const Disjoint2dPackingResult::Bin& bin =
6253 disjoint_packing_presolve_result.bins[bin_index];
6254 NoOverlap2DConstraintProto new_no_overlap_2d;
6255 for (const Rectangle& ret : bin.fixed_boxes) {
6256 new_no_overlap_2d.add_x_intervals(
6257 context_->working_model->constraints_size());
6258 new_no_overlap_2d.add_y_intervals(
6259 context_->working_model->constraints_size() + 1);
6260 IntervalConstraintProto* new_interval =
6261 context_->working_model->add_constraints()->mutable_interval();
6262 new_interval->mutable_start()->set_offset(ret.x_min.value());
6263 new_interval->mutable_size()->set_offset(ret.SizeX().value());
6264 new_interval->mutable_end()->set_offset(ret.x_max.value());
6265
6266 new_interval =
6267 context_->working_model->add_constraints()->mutable_interval();
6268 new_interval->mutable_start()->set_offset(ret.y_min.value());
6269 new_interval->mutable_size()->set_offset(ret.SizeY().value());
6270 new_interval->mutable_end()->set_offset(ret.y_max.value());
6271 }
6272 for (const int idx : bin.non_fixed_box_indexes) {
6273 int presence_in_box_lit = context_->NewBoolVar("binpacking");
6274 box_to_presence_literal[idx].push_back(presence_in_box_lit);
6275 const int b = non_fixed_boxes[idx].box_index;
6276 box_in_area_lits.push_back({.box_index = b,
6277 .area_index = bin_index,
6278 .literal = presence_in_box_lit});
6279 const ConstraintProto& x_interval_ct =
6280 context_->working_model->constraints(proto.x_intervals(b));
6281 const ConstraintProto& y_interval_ct =
6282 context_->working_model->constraints(proto.y_intervals(b));
6283 ConstraintProto* new_interval_x =
6284 context_->working_model->add_constraints();
6285 *new_interval_x = x_interval_ct;
6286 new_interval_x->clear_enforcement_literal();
6287 new_interval_x->add_enforcement_literal(presence_in_box_lit);
6288 ConstraintProto* new_interval_y =
6289 context_->working_model->add_constraints();
6290 *new_interval_y = y_interval_ct;
6291 new_interval_y->clear_enforcement_literal();
6292 new_interval_y->add_enforcement_literal(presence_in_box_lit);
6293 new_no_overlap_2d.add_x_intervals(
6294 context_->working_model->constraints_size() - 2);
6295 new_no_overlap_2d.add_y_intervals(
6296 context_->working_model->constraints_size() - 1);
6297 }
6298 context_->working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
6299 &new_no_overlap_2d);
6300 }
6301
6302 // Each box is in exactly one bin (including the fake "absent" bin).
6303 for (int box_index = 0; box_index < non_fixed_boxes.size(); ++box_index) {
6304 const std::vector<int>& presence_literals =
6305 box_to_presence_literal[box_index];
6306 if (presence_literals.empty()) {
6307 return context_->NotifyThatModelIsUnsat(
6308 "A mandatory box cannot be placed in any position");
6309 }
6310 auto* exactly_one =
6311 context_->working_model->add_constraints()->mutable_exactly_one();
6312 for (const int presence_literal : presence_literals) {
6313 exactly_one->add_literals(presence_literal);
6314 }
6315 }
6316 CompactVectorVector<int, Rectangle> areas;
6317 for (int bin_index = 0;
6318 bin_index < disjoint_packing_presolve_result.bins.size(); ++bin_index) {
6319 areas.Add(disjoint_packing_presolve_result.bins[bin_index].bin_area);
6320 }
6321 solution_crush_.AssignVariableToPackingArea(
6322 areas, *context_->working_model, proto.x_intervals(), proto.y_intervals(),
6323 box_in_area_lits);
6324 context_->UpdateNewConstraintsVariableUsage();
6325 context_->UpdateRuleStats(
6326 "no_overlap_2d: fixed boxes partition available space, converted "
6327 "to optional regions");
6328 return RemoveConstraint(ct);
6329}
6330
6331bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
6332 if (context_->ModelIsUnsat()) {
6333 return false;
6334 }
6335
6336 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6337 const int initial_num_boxes = proto.x_intervals_size();
6338
6339 bool x_constant = true;
6340 bool y_constant = true;
6341 bool has_zero_sized_interval = false;
6342 bool has_potential_zero_sized_interval = false;
6343
6344 // Filter absent boxes.
6345 int new_size = 0;
6346 std::vector<Rectangle> bounding_boxes, fixed_boxes, non_fixed_bounding_boxes;
6347 std::vector<RectangleInRange> non_fixed_boxes;
6348 absl::flat_hash_set<int> fixed_item_indexes;
6349 for (int i = 0; i < proto.x_intervals_size(); ++i) {
6350 const int x_interval_index = proto.x_intervals(i);
6351 const int y_interval_index = proto.y_intervals(i);
6352
6353 ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index);
6354 ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index);
6355
6356 // We don't want to fully presolve the intervals (intervals have their own
6357 // presolve), but we don't want to bother with negative sizes downstream in
6358 // this function.
6359 for (const int interval_index : {x_interval_index, y_interval_index}) {
6360 if (context_->StartMin(interval_index) >
6361 context_->EndMax(interval_index)) {
6362 const ConstraintProto* interval_ct =
6363 context_->working_model->mutable_constraints(interval_index);
6364 if (interval_ct->enforcement_literal_size() == 1) {
6365 const int literal = interval_ct->enforcement_literal(0);
6366 if (!context_->SetLiteralToFalse(literal)) {
6367 return true;
6368 }
6369 } else {
6370 return context_->NotifyThatModelIsUnsat(
6371 "no_overlap_2d: impossible interval.");
6372 }
6373 }
6374 }
6375
6376 if (context_->ConstraintIsInactive(x_interval_index) ||
6377 context_->ConstraintIsInactive(y_interval_index)) {
6378 continue;
6379 }
6380
6381 bounding_boxes.push_back(
6382 {IntegerValue(context_->StartMin(x_interval_index)),
6383 IntegerValue(context_->EndMax(x_interval_index)),
6384 IntegerValue(context_->StartMin(y_interval_index)),
6385 IntegerValue(context_->EndMax(y_interval_index))});
6386 if (context_->IntervalIsConstant(x_interval_index) &&
6387 context_->IntervalIsConstant(y_interval_index) &&
6388 context_->SizeMax(x_interval_index) > 0 &&
6389 context_->SizeMax(y_interval_index) > 0) {
6390 fixed_boxes.push_back(bounding_boxes.back());
6391 fixed_item_indexes.insert(new_size);
6392 } else {
6393 non_fixed_bounding_boxes.push_back(bounding_boxes.back());
6394 non_fixed_boxes.push_back(
6395 {.box_index = new_size,
6396 .bounding_area = bounding_boxes.back(),
6397 .x_size = std::max(int64_t{0}, context_->SizeMin(x_interval_index)),
6398 .y_size =
6399 std::max(int64_t{0}, context_->SizeMin(y_interval_index))});
6400 }
6401 new_size++;
6402
6403 if (x_constant && !context_->IntervalIsConstant(x_interval_index)) {
6404 x_constant = false;
6405 }
6406 if (y_constant && !context_->IntervalIsConstant(y_interval_index)) {
6407 y_constant = false;
6408 }
6409 if (context_->SizeMax(x_interval_index) <= 0 ||
6410 context_->SizeMax(y_interval_index) <= 0) {
6411 has_zero_sized_interval = true;
6412 }
6413 if (context_->SizeMin(x_interval_index) <= 0 ||
6414 context_->SizeMin(y_interval_index) <= 0) {
6415 has_potential_zero_sized_interval = true;
6416 }
6417 }
6418
6419 if (new_size < initial_num_boxes) {
6420 context_->UpdateRuleStats("no_overlap_2d: removed inactive boxes");
6421 ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
6422 ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
6423 }
6424
6425 if (new_size == 0) {
6426 context_->UpdateRuleStats("no_overlap_2d: no boxes");
6427 return RemoveConstraint(ct);
6428 }
6429
6430 if (new_size == 1) {
6431 context_->UpdateRuleStats("no_overlap_2d: only one box");
6432 return RemoveConstraint(ct);
6433 }
6434
6435 const CompactVectorVector<int> components =
6436 GetOverlappingRectangleComponents(bounding_boxes);
6437 if (components.size() > 1) {
6438 for (int i = 0; i < components.size(); ++i) {
6439 absl::Span<const int> boxes = components[i];
6440 if (boxes.size() <= 1) continue;
6441
6442 NoOverlap2DConstraintProto* new_no_overlap_2d =
6443 context_->working_model->add_constraints()->mutable_no_overlap_2d();
6444 for (const int b : boxes) {
6445 new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
6446 new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
6447 }
6448 }
6449 context_->UpdateNewConstraintsVariableUsage();
6450 context_->UpdateRuleStats("no_overlap_2d: split into disjoint components");
6451 return RemoveConstraint(ct);
6452 }
6453
6454 // TODO(user): handle this case. See issue #4068.
6455 if (!has_zero_sized_interval && (x_constant || y_constant)) {
6456 context_->UpdateRuleStats(
6457 "no_overlap_2d: a dimension is constant, splitting into many "
6458 "no_overlaps");
6459 std::vector<IndexedInterval> indexed_intervals;
6460 for (int i = 0; i < new_size; ++i) {
6461 int x = proto.x_intervals(i);
6462 int y = proto.y_intervals(i);
6463 if (x_constant) std::swap(x, y);
6464 indexed_intervals.push_back({x, IntegerValue(context_->StartMin(y)),
6465 IntegerValue(context_->EndMax(y))});
6466 }
6467 CompactVectorVector<int> no_overlaps;
6468 absl::c_sort(indexed_intervals, IndexedInterval::ComparatorByStart());
6469 ConstructOverlappingSets(absl::MakeSpan(indexed_intervals), &no_overlaps);
6470 for (int i = 0; i < no_overlaps.size(); ++i) {
6471 ConstraintProto* new_ct = context_->working_model->add_constraints();
6472 // Unfortunately, the Assign() method does not work in or-tools as the
6473 // protobuf int32_t type is not the int type.
6474 for (const int i : no_overlaps[i]) {
6475 new_ct->mutable_no_overlap()->add_intervals(i);
6476 }
6477 }
6478 context_->UpdateNewConstraintsVariableUsage();
6479 return RemoveConstraint(ct);
6480 }
6481
6482 // We check if the fixed boxes are not overlapping so downstream code can
6483 // assume it to be true.
6484 if (!FindPartialRectangleIntersections(fixed_boxes).empty()) {
6485 return context_->NotifyThatModelIsUnsat(
6486 "Two fixed boxes in no_overlap_2d overlap");
6487 }
6488
6489 if (non_fixed_bounding_boxes.empty()) {
6490 context_->UpdateRuleStats("no_overlap_2d: all boxes are fixed");
6491 return RemoveConstraint(ct);
6492 }
6493
6494 // TODO(user): presolve the zero-size fixed items so they are disjoint from
6495 // the other fixed items. Then the following presolve is still valid. On the
6496 // other hand, we cannot do much with non-fixed zero-size items.
6497 if (!has_potential_zero_sized_interval && !fixed_boxes.empty()) {
6498 const bool presolved =
6499 PresolveFixed2dRectangles(non_fixed_boxes, &fixed_boxes);
6500 if (presolved) {
6501 NoOverlap2DConstraintProto new_no_overlap_2d;
6502
6503 // Replace the old fixed intervals by the new ones.
6504 const int old_size = proto.x_intervals_size();
6505 for (int i = 0; i < old_size; ++i) {
6506 if (fixed_item_indexes.contains(i)) {
6507 continue;
6508 }
6509 new_no_overlap_2d.add_x_intervals(proto.x_intervals(i));
6510 new_no_overlap_2d.add_y_intervals(proto.y_intervals(i));
6511 }
6512 for (const Rectangle& fixed_box : fixed_boxes) {
6513 const int item_x_interval =
6514 context_->working_model->constraints().size();
6515 IntervalConstraintProto* new_interval =
6516 context_->working_model->add_constraints()->mutable_interval();
6517 new_interval->mutable_start()->set_offset(fixed_box.x_min.value());
6518 new_interval->mutable_size()->set_offset(fixed_box.SizeX().value());
6519 new_interval->mutable_end()->set_offset(fixed_box.x_max.value());
6520
6521 const int item_y_interval =
6522 context_->working_model->constraints().size();
6523 new_interval =
6524 context_->working_model->add_constraints()->mutable_interval();
6525 new_interval->mutable_start()->set_offset(fixed_box.y_min.value());
6526 new_interval->mutable_size()->set_offset(fixed_box.SizeY().value());
6527 new_interval->mutable_end()->set_offset(fixed_box.y_max.value());
6528
6529 new_no_overlap_2d.add_x_intervals(item_x_interval);
6530 new_no_overlap_2d.add_y_intervals(item_y_interval);
6531 }
6532 context_->working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
6533 &new_no_overlap_2d);
6534 context_->UpdateNewConstraintsVariableUsage();
6535 context_->UpdateRuleStats("no_overlap_2d: presolved fixed rectangles");
6536 return RemoveConstraint(ct);
6537 }
6538 }
6539
6540 if (!fixed_boxes.empty() && fixed_boxes.size() <= 4 &&
6541 !non_fixed_boxes.empty() && !has_potential_zero_sized_interval) {
6542 if (PresolveNoOverlap2DFramed(fixed_boxes, non_fixed_boxes, ct)) {
6543 return true;
6544 }
6545 }
6546
6547 // If the non-fixed boxes are disjoint but connected by fixed boxes, we can
6548 // split the constraint and duplicate the fixed boxes. To avoid duplicating
6549 // too many fixed boxes, we do this after we we applied the presolve reducing
6550 // their number to as few as possible.
6551 const CompactVectorVector<int> non_fixed_components =
6552 GetOverlappingRectangleComponents(non_fixed_bounding_boxes);
6553 if (non_fixed_components.size() > 1) {
6554 for (int i = 0; i < non_fixed_components.size(); ++i) {
6555 // Note: we care about components of size 1 because they might be
6556 // overlapping with the fixed boxes.
6557 absl::Span<const int> indexes = non_fixed_components[i];
6558
6559 NoOverlap2DConstraintProto* new_no_overlap_2d =
6560 context_->working_model->add_constraints()->mutable_no_overlap_2d();
6561 for (const int idx : indexes) {
6562 const int b = non_fixed_boxes[idx].box_index;
6563 new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
6564 new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
6565 }
6566 for (const int b : fixed_item_indexes) {
6567 new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
6568 new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
6569 }
6570 }
6571 context_->UpdateNewConstraintsVariableUsage();
6572 context_->UpdateRuleStats(
6573 "no_overlap_2d: split into disjoint components duplicating fixed "
6574 "boxes");
6575 return RemoveConstraint(ct);
6576 }
6577
6578 if (!has_potential_zero_sized_interval) {
6579 if (ExpandEncoded2DBinPacking(fixed_boxes, non_fixed_boxes, ct)) {
6580 return true;
6581 }
6582 }
6583 RunPropagatorsForConstraint(*ct);
6584 return new_size < initial_num_boxes;
6585}
6586
6587namespace {
6588LinearExpressionProto ConstantExpressionProto(int64_t value) {
6589 LinearExpressionProto expr;
6590 expr.set_offset(value);
6591 return expr;
6592}
6593} // namespace
6594
6595void CpModelPresolver::DetectDuplicateIntervals(
6596 int c, google::protobuf::RepeatedField<int32_t>* intervals) {
6597 interval_representative_.clear();
6598 bool changed = false;
6599 const int size = intervals->size();
6600 for (int i = 0; i < size; ++i) {
6601 const int index = (*intervals)[i];
6602 const auto [it, inserted] = interval_representative_.insert({index, index});
6603 if (it->second != index) {
6604 changed = true;
6605 intervals->Set(i, it->second);
6606 context_->UpdateRuleStats(
6607 "intervals: change duplicate index inside constraint");
6608 }
6609 }
6610 if (changed) context_->UpdateConstraintVariableUsage(c);
6611}
6612
6613bool CpModelPresolver::PresolveCumulative(ConstraintProto* ct) {
6614 if (context_->ModelIsUnsat()) return false;
6615
6616 CumulativeConstraintProto* proto = ct->mutable_cumulative();
6617
6618 bool changed = CanonicalizeLinearExpression(*ct, proto->mutable_capacity());
6619 for (LinearExpressionProto& exp :
6620 *(ct->mutable_cumulative()->mutable_demands())) {
6621 changed |= CanonicalizeLinearExpression(*ct, &exp);
6622 }
6623
6624 const int64_t capacity_max = context_->MaxOf(proto->capacity());
6625
6626 // Checks the capacity of the constraint.
6627 {
6628 bool domain_changed = false;
6629 if (!context_->IntersectDomainWith(
6630 proto->capacity(), Domain(0, capacity_max), &domain_changed)) {
6631 return true;
6632 }
6633 if (domain_changed) {
6634 context_->UpdateRuleStats("cumulative: trimmed negative capacity");
6635 }
6636 }
6637
6638 // Merge identical intervals if the demand can be merged and is still affine.
6639 //
6640 // TODO(user): We could also merge if the first entry is constant instead of
6641 // the second one. Or if the variable used for the demand is the same.
6642 {
6643 absl::flat_hash_map<int, int> interval_to_i;
6644 int new_size = 0;
6645 for (int i = 0; i < proto->intervals_size(); ++i) {
6646 const auto [it, inserted] =
6647 interval_to_i.insert({proto->intervals(i), new_size});
6648 if (!inserted) {
6649 if (context_->IsFixed(proto->demands(i))) {
6650 const int old_index = it->second;
6651 proto->mutable_demands(old_index)->set_offset(
6652 proto->demands(old_index).offset() +
6653 context_->FixedValue(proto->demands(i)));
6654 context_->UpdateRuleStats(
6655 "cumulative: merged demand of identical interval");
6656 continue;
6657 } else {
6658 context_->UpdateRuleStats(
6659 "TODO cumulative: merged demand of identical interval");
6660 }
6661 }
6662 proto->set_intervals(new_size, proto->intervals(i));
6663 *proto->mutable_demands(new_size) = proto->demands(i);
6664 ++new_size;
6665 }
6666 if (new_size < proto->intervals_size()) {
6667 changed = true;
6668 proto->mutable_intervals()->Truncate(new_size);
6669 proto->mutable_demands()->erase(
6670 proto->mutable_demands()->begin() + new_size,
6671 proto->mutable_demands()->end());
6672 }
6673 }
6674
6675 // Filter absent intervals, or zero demands, or demand incompatible with the
6676 // capacity.
6677 {
6678 int new_size = 0;
6679 int num_zero_demand_removed = 0;
6680 int num_zero_size_removed = 0;
6681 int num_incompatible_intervals = 0;
6682 for (int i = 0; i < proto->intervals_size(); ++i) {
6683 if (context_->ConstraintIsInactive(proto->intervals(i))) continue;
6684
6685 const LinearExpressionProto& demand_expr = proto->demands(i);
6686 const int64_t demand_max = context_->MaxOf(demand_expr);
6687 if (demand_max == 0) {
6688 num_zero_demand_removed++;
6689 continue;
6690 }
6691
6692 const int interval_index = proto->intervals(i);
6693 if (context_->SizeMax(interval_index) == 0) {
6694 // Size 0 intervals cannot contribute to a cumulative.
6695 num_zero_size_removed++;
6696 continue;
6697 }
6698
6699 // Inconsistent intervals cannot be performed.
6700 const int64_t start_min = context_->StartMin(interval_index);
6701 const int64_t end_max = context_->EndMax(interval_index);
6702 if (start_min > end_max) {
6703 if (context_->ConstraintIsOptional(interval_index)) {
6704 ConstraintProto* interval_ct =
6705 context_->working_model->mutable_constraints(interval_index);
6706 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
6707 const int literal = interval_ct->enforcement_literal(0);
6708 if (!context_->SetLiteralToFalse(literal)) {
6709 return true;
6710 }
6711 num_incompatible_intervals++;
6712 continue;
6713 } else {
6714 return context_->NotifyThatModelIsUnsat(
6715 "cumulative: inconsistent intervals cannot be performed.");
6716 }
6717 }
6718
6719 if (context_->MinOf(demand_expr) > capacity_max) {
6720 if (context_->ConstraintIsOptional(interval_index)) {
6721 if (context_->SizeMin(interval_index) > 0) {
6722 ConstraintProto* interval_ct =
6723 context_->working_model->mutable_constraints(interval_index);
6724 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
6725 const int literal = interval_ct->enforcement_literal(0);
6726 if (!context_->SetLiteralToFalse(literal)) {
6727 return true;
6728 }
6729 num_incompatible_intervals++;
6730 continue;
6731 }
6732 } else { // Interval performed.
6733 // Try to set the size to 0.
6734 const ConstraintProto& interval_ct =
6735 context_->working_model->constraints(interval_index);
6736 if (!context_->IntersectDomainWith(interval_ct.interval().size(),
6737 {0, 0})) {
6738 return true;
6739 }
6740 context_->UpdateRuleStats(
6741 "cumulative: zero size of performed demand that exceeds "
6742 "capacity");
6743 ++num_zero_demand_removed;
6744 continue;
6745 }
6746 }
6747
6748 proto->set_intervals(new_size, interval_index);
6749 *proto->mutable_demands(new_size) = proto->demands(i);
6750 new_size++;
6751 }
6752
6753 if (new_size < proto->intervals_size()) {
6754 changed = true;
6755 proto->mutable_intervals()->Truncate(new_size);
6756 proto->mutable_demands()->erase(
6757 proto->mutable_demands()->begin() + new_size,
6758 proto->mutable_demands()->end());
6759 }
6760
6761 if (num_zero_demand_removed > 0) {
6762 context_->UpdateRuleStats(
6763 "cumulative: removed intervals with no demands");
6764 }
6765 if (num_zero_size_removed > 0) {
6766 context_->UpdateRuleStats(
6767 "cumulative: removed intervals with a size of zero");
6768 }
6769 if (num_incompatible_intervals > 0) {
6770 context_->UpdateRuleStats(
6771 "cumulative: removed intervals that can't be performed");
6772 }
6773 }
6774
6775 // Checks the compatibility of demands w.r.t. the capacity.
6776 {
6777 for (int i = 0; i < proto->demands_size(); ++i) {
6778 const int interval = proto->intervals(i);
6779 const LinearExpressionProto& demand_expr = proto->demands(i);
6780 if (context_->ConstraintIsOptional(interval)) continue;
6781 if (context_->SizeMin(interval) <= 0) continue;
6782 bool domain_changed = false;
6783 if (!context_->IntersectDomainWith(demand_expr, {0, capacity_max},
6784 &domain_changed)) {
6785 return true;
6786 }
6787 if (domain_changed) {
6788 context_->UpdateRuleStats(
6789 "cumulative: fit demand in [0..capacity_max]");
6790 }
6791 }
6792 }
6793
6794 // Split constraints in disjoint sets.
6795 //
6796 // TODO(user): This can be improved:
6797 // If we detect bridge nodes in the graph of overlapping components, we
6798 // can split the graph around the bridge and add the bridge node to both
6799 // side. Note that if it we take into account precedences between intervals,
6800 // we can detect more bridges.
6801 if (proto->intervals_size() > 1) {
6802 std::vector<IndexedInterval> indexed_intervals;
6803 for (int i = 0; i < proto->intervals().size(); ++i) {
6804 const int index = proto->intervals(i);
6805 indexed_intervals.push_back({i, IntegerValue(context_->StartMin(index)),
6806 IntegerValue(context_->EndMax(index))});
6807 }
6808 std::vector<std::vector<int>> components;
6809 GetOverlappingIntervalComponents(&indexed_intervals, &components);
6810
6811 if (components.size() > 1) {
6812 for (const std::vector<int>& component : components) {
6813 CumulativeConstraintProto* new_cumulative =
6814 context_->working_model->add_constraints()->mutable_cumulative();
6815 for (const int i : component) {
6816 new_cumulative->add_intervals(proto->intervals(i));
6817 *new_cumulative->add_demands() = proto->demands(i);
6818 }
6819 *new_cumulative->mutable_capacity() = proto->capacity();
6820 }
6821 context_->UpdateNewConstraintsVariableUsage();
6822 context_->UpdateRuleStats("cumulative: split into disjoint components");
6823 return RemoveConstraint(ct);
6824 }
6825 }
6826
6827 // TODO(user): move the algorithmic part of what we do below in a
6828 // separate function to unit test it more properly.
6829 {
6830 // Build max load profiles.
6831 absl::btree_map<int64_t, int64_t> time_to_demand_deltas;
6832 const int64_t capacity_min = context_->MinOf(proto->capacity());
6833 for (int i = 0; i < proto->intervals_size(); ++i) {
6834 const int interval_index = proto->intervals(i);
6835 const int64_t demand_max = context_->MaxOf(proto->demands(i));
6836 time_to_demand_deltas[context_->StartMin(interval_index)] += demand_max;
6837 time_to_demand_deltas[context_->EndMax(interval_index)] -= demand_max;
6838 }
6839
6840 // We construct the profile which correspond to a set of [time, next_time)
6841 // to max_profile height. And for each time in our discrete set of
6842 // time_exprs (all the start_min and end_max) we count for how often the
6843 // height was above the capacity before this time.
6844 //
6845 // This rely on the iteration in sorted order.
6846 int num_possible_overloads = 0;
6847 int64_t current_load = 0;
6848 absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before;
6849 for (const auto& it : time_to_demand_deltas) {
6850 num_possible_overloads_before[it.first] = num_possible_overloads;
6851 current_load += it.second;
6852 if (current_load > capacity_min) {
6853 ++num_possible_overloads;
6854 }
6855 }
6856 CHECK_EQ(current_load, 0);
6857
6858 // No possible overload with the min capacity.
6859 if (num_possible_overloads == 0) {
6860 context_->UpdateRuleStats(
6861 "cumulative: max profile is always under the min capacity");
6862 return RemoveConstraint(ct);
6863 }
6864
6865 // An interval that does not intersect with the potential_overload_domains
6866 // cannot contribute to a conflict. We can safely remove them.
6867 //
6868 // This is an extension of the presolve rule from
6869 // "Presolving techniques and linear relaxations for cumulative
6870 // scheduling" PhD dissertation by Stefan Heinz, ZIB.
6871 int new_size = 0;
6872 for (int i = 0; i < proto->intervals_size(); ++i) {
6873 const int index = proto->intervals(i);
6874 const int64_t start_min = context_->StartMin(index);
6875 const int64_t end_max = context_->EndMax(index);
6876
6877 // In the cumulative, if start_min == end_max, the interval is of size
6878 // zero and we can just ignore it. If the model is unsat or the interval
6879 // must be absent (start_min > end_max), this should be dealt with at
6880 // the interval constraint level and we can just remove it from here.
6881 //
6882 // Note that currently, the interpretation for interval of length zero
6883 // is different for the no-overlap constraint.
6884 if (start_min >= end_max) continue;
6885
6886 // Note that by construction, both point are in the map. The formula
6887 // counts exactly for how many time_exprs in [start_min, end_max), we have
6888 // a point in our discrete set of time that exceeded the capacity. Because
6889 // we included all the relevant points, this works.
6890 const int num_diff = num_possible_overloads_before.at(end_max) -
6891 num_possible_overloads_before.at(start_min);
6892 if (num_diff == 0) continue;
6893
6894 proto->set_intervals(new_size, proto->intervals(i));
6895 *proto->mutable_demands(new_size) = proto->demands(i);
6896 new_size++;
6897 }
6898
6899 if (new_size < proto->intervals_size()) {
6900 changed = true;
6901 proto->mutable_intervals()->Truncate(new_size);
6902 proto->mutable_demands()->erase(
6903 proto->mutable_demands()->begin() + new_size,
6904 proto->mutable_demands()->end());
6905 context_->UpdateRuleStats(
6906 "cumulative: remove never conflicting intervals.");
6907 }
6908 }
6909
6910 if (proto->intervals().empty()) {
6911 context_->UpdateRuleStats("cumulative: no intervals");
6912 return RemoveConstraint(ct);
6913 }
6914
6915 {
6916 int64_t max_of_performed_demand_mins = 0;
6917 int64_t sum_of_max_demands = 0;
6918 for (int i = 0; i < proto->intervals_size(); ++i) {
6919 const int interval_index = proto->intervals(i);
6920 const ConstraintProto& interval_ct =
6921 context_->working_model->constraints(interval_index);
6922
6923 const LinearExpressionProto& demand_expr = proto->demands(i);
6924 sum_of_max_demands += context_->MaxOf(demand_expr);
6925
6926 if (interval_ct.enforcement_literal().empty() &&
6927 context_->SizeMin(interval_index) > 0) {
6928 max_of_performed_demand_mins = std::max(max_of_performed_demand_mins,
6929 context_->MinOf(demand_expr));
6930 }
6931 }
6932
6933 const LinearExpressionProto& capacity_expr = proto->capacity();
6934 if (max_of_performed_demand_mins > context_->MinOf(capacity_expr)) {
6935 context_->UpdateRuleStats("cumulative: propagate min capacity.");
6936 if (!context_->IntersectDomainWith(
6937 capacity_expr, Domain(max_of_performed_demand_mins,
6938 std::numeric_limits<int64_t>::max()))) {
6939 return true;
6940 }
6941 }
6942
6943 if (max_of_performed_demand_mins > context_->MaxOf(capacity_expr)) {
6944 context_->UpdateRuleStats("cumulative: cannot fit performed demands");
6945 return context_->NotifyThatModelIsUnsat();
6946 }
6947
6948 if (sum_of_max_demands <= context_->MinOf(capacity_expr)) {
6949 context_->UpdateRuleStats("cumulative: capacity exceeds sum of demands");
6950 return RemoveConstraint(ct);
6951 }
6952 }
6953
6954 if (context_->IsFixed(proto->capacity())) {
6955 int64_t gcd = 0;
6956 for (int i = 0; i < ct->cumulative().demands_size(); ++i) {
6957 const LinearExpressionProto& demand_expr = ct->cumulative().demands(i);
6958 if (!context_->IsFixed(demand_expr)) {
6959 // Abort if the demand is not fixed.
6960 gcd = 1;
6961 break;
6962 }
6963 gcd = std::gcd(gcd, context_->MinOf(demand_expr));
6964 if (gcd == 1) break;
6965 }
6966 if (gcd > 1) {
6967 changed = true;
6968 for (int i = 0; i < ct->cumulative().demands_size(); ++i) {
6969 const int64_t demand = context_->MinOf(ct->cumulative().demands(i));
6970 *proto->mutable_demands(i) = ConstantExpressionProto(demand / gcd);
6971 }
6972
6973 const int64_t old_capacity = context_->MinOf(proto->capacity());
6974 *proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd);
6975 context_->UpdateRuleStats(
6976 "cumulative: divide demands and capacity by gcd");
6977 }
6978 }
6979
6980 const int num_intervals = proto->intervals_size();
6981 const LinearExpressionProto& capacity_expr = proto->capacity();
6982
6983 std::vector<LinearExpressionProto> start_exprs(num_intervals);
6984
6985 int num_duration_one = 0;
6986 int num_greater_half_capacity = 0;
6987
6988 bool has_optional_interval = false;
6989 for (int i = 0; i < num_intervals; ++i) {
6990 const int index = proto->intervals(i);
6991 // TODO(user): adapt in the presence of optional intervals.
6992 if (context_->ConstraintIsOptional(index)) has_optional_interval = true;
6993 const ConstraintProto& ct =
6994 context_->working_model->constraints(proto->intervals(i));
6995 const IntervalConstraintProto& interval = ct.interval();
6996 start_exprs[i] = interval.start();
6997
6998 const LinearExpressionProto& demand_expr = proto->demands(i);
6999 if (context_->SizeMin(index) == 1 && context_->SizeMax(index) == 1) {
7000 num_duration_one++;
7001 }
7002 if (context_->SizeMin(index) == 0) {
7003 // The behavior for zero-duration interval is currently not the same in
7004 // the no-overlap and the cumulative constraint.
7005 return changed;
7006 }
7007
7008 const int64_t demand_min = context_->MinOf(demand_expr);
7009 const int64_t demand_max = context_->MaxOf(demand_expr);
7010 if (demand_min > capacity_max / 2) {
7011 num_greater_half_capacity++;
7012 }
7013 if (demand_min > capacity_max) {
7014 context_->UpdateRuleStats("cumulative: demand_min exceeds capacity max");
7015 if (!context_->ConstraintIsOptional(index)) {
7016 return context_->NotifyThatModelIsUnsat();
7017 } else {
7018 CHECK_EQ(ct.enforcement_literal().size(), 1);
7019 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
7020 return true;
7021 }
7022 }
7023 return changed;
7024 } else if (demand_max > capacity_max) {
7025 if (ct.enforcement_literal().empty()) {
7026 context_->UpdateRuleStats(
7027 "cumulative: demand_max exceeds capacity max.");
7028 if (!context_->IntersectDomainWith(
7029 demand_expr,
7030 Domain(std::numeric_limits<int64_t>::min(), capacity_max))) {
7031 return true;
7032 }
7033 } else {
7034 // TODO(user): we abort because we cannot convert this to a no_overlap
7035 // for instance.
7036 context_->UpdateRuleStats(
7037 "cumulative: demand_max of optional interval exceeds capacity.");
7038 return changed;
7039 }
7040 }
7041 }
7042 if (num_greater_half_capacity == num_intervals) {
7043 if (num_duration_one == num_intervals && !has_optional_interval) {
7044 context_->UpdateRuleStats("cumulative: convert to all_different");
7045 ConstraintProto* new_ct = context_->working_model->add_constraints();
7046 auto* arg = new_ct->mutable_all_diff();
7047 for (const LinearExpressionProto& expr : start_exprs) {
7048 *arg->add_exprs() = expr;
7049 }
7050 if (!context_->IsFixed(capacity_expr)) {
7051 const int64_t capacity_min = context_->MinOf(capacity_expr);
7052 for (const LinearExpressionProto& expr : proto->demands()) {
7053 if (capacity_min >= context_->MaxOf(expr)) continue;
7054 LinearConstraintProto* fit =
7055 context_->working_model->add_constraints()->mutable_linear();
7056 fit->add_domain(0);
7057 fit->add_domain(std::numeric_limits<int64_t>::max());
7058 AddLinearExpressionToLinearConstraint(capacity_expr, 1, fit);
7060 }
7061 }
7062 context_->UpdateNewConstraintsVariableUsage();
7063 return RemoveConstraint(ct);
7064 } else {
7065 context_->UpdateRuleStats("cumulative: convert to no_overlap");
7066 // Before we remove the cumulative, add constraints to enforce that the
7067 // capacity is greater than the demand of any performed intervals.
7068 for (int i = 0; i < proto->demands_size(); ++i) {
7069 const LinearExpressionProto& demand_expr = proto->demands(i);
7070 const int64_t demand_max = context_->MaxOf(demand_expr);
7071 if (demand_max > context_->MinOf(capacity_expr)) {
7072 ConstraintProto* capacity_gt =
7073 context_->working_model->add_constraints();
7074 *capacity_gt->mutable_enforcement_literal() =
7075 context_->working_model->constraints(proto->intervals(i))
7076 .enforcement_literal();
7077 capacity_gt->mutable_linear()->add_domain(0);
7078 capacity_gt->mutable_linear()->add_domain(
7079 std::numeric_limits<int64_t>::max());
7081 capacity_gt->mutable_linear());
7083 capacity_gt->mutable_linear());
7084 }
7085 }
7086
7087 ConstraintProto* new_ct = context_->working_model->add_constraints();
7088 auto* arg = new_ct->mutable_no_overlap();
7089 for (const int interval : proto->intervals()) {
7090 arg->add_intervals(interval);
7091 }
7092 context_->UpdateNewConstraintsVariableUsage();
7093 return RemoveConstraint(ct);
7094 }
7095 }
7096
7097 RunPropagatorsForConstraint(*ct);
7098 return changed;
7099}
7100
7101bool CpModelPresolver::PresolveRoutes(ConstraintProto* ct) {
7102 if (context_->ModelIsUnsat()) return false;
7103 if (HasEnforcementLiteral(*ct)) return false;
7104 RoutesConstraintProto& proto = *ct->mutable_routes();
7105
7106 const int old_size = proto.literals_size();
7107 int new_size = 0;
7108 std::vector<bool> has_incoming_or_outgoing_arcs;
7109 const int num_arcs = proto.literals_size();
7110 for (int i = 0; i < num_arcs; ++i) {
7111 const int ref = proto.literals(i);
7112 const int tail = proto.tails(i);
7113 const int head = proto.heads(i);
7114
7115 if (tail >= has_incoming_or_outgoing_arcs.size()) {
7116 has_incoming_or_outgoing_arcs.resize(tail + 1, false);
7117 }
7118 if (head >= has_incoming_or_outgoing_arcs.size()) {
7119 has_incoming_or_outgoing_arcs.resize(head + 1, false);
7120 }
7121
7122 if (context_->LiteralIsFalse(ref)) {
7123 context_->UpdateRuleStats("routes: removed false arcs");
7124 continue;
7125 }
7126 proto.set_literals(new_size, ref);
7127 proto.set_tails(new_size, tail);
7128 proto.set_heads(new_size, head);
7129 ++new_size;
7130 has_incoming_or_outgoing_arcs[tail] = true;
7131 has_incoming_or_outgoing_arcs[head] = true;
7132 }
7133
7134 if (old_size > 0 && new_size == 0) {
7135 // A routes constraint cannot have a self loop on 0. Therefore, if there
7136 // were arcs, it means it contains non zero nodes. Without arc, the
7137 // constraint is unfeasible.
7138 return context_->NotifyThatModelIsUnsat(
7139 "routes: graph with nodes and no arcs");
7140 }
7141
7142 // if a node misses an incomping or outgoing arc, the model is trivially
7143 // infeasible.
7144 for (int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) {
7145 if (!has_incoming_or_outgoing_arcs[n]) {
7146 return context_->NotifyThatModelIsUnsat(absl::StrCat(
7147 "routes: node ", n, " misses incoming or outgoing arcs"));
7148 }
7149 }
7150
7151 if (new_size < num_arcs) {
7152 proto.mutable_literals()->Truncate(new_size);
7153 proto.mutable_tails()->Truncate(new_size);
7154 proto.mutable_heads()->Truncate(new_size);
7155 return true;
7156 }
7157
7158 RunPropagatorsForConstraint(*ct);
7159 return false;
7160}
7161
7162bool CpModelPresolver::PresolveCircuit(ConstraintProto* ct) {
7163 if (context_->ModelIsUnsat()) return false;
7164 if (HasEnforcementLiteral(*ct)) return false;
7165 CircuitConstraintProto& proto = *ct->mutable_circuit();
7166
7167 // The indexing might not be dense, so fix that first.
7168 ReindexArcs(ct->mutable_circuit()->mutable_tails(),
7169 ct->mutable_circuit()->mutable_heads());
7170
7171 // Convert the flat structure to a graph, note that we includes all the arcs
7172 // here (even if they are at false).
7173 std::vector<std::vector<int>> incoming_arcs;
7174 std::vector<std::vector<int>> outgoing_arcs;
7175 int num_nodes = 0;
7176 const int num_arcs = proto.literals_size();
7177 for (int i = 0; i < num_arcs; ++i) {
7178 const int ref = proto.literals(i);
7179 const int tail = proto.tails(i);
7180 const int head = proto.heads(i);
7181 num_nodes = std::max(num_nodes, std::max(tail, head) + 1);
7182 if (std::max(tail, head) >= incoming_arcs.size()) {
7183 incoming_arcs.resize(std::max(tail, head) + 1);
7184 outgoing_arcs.resize(std::max(tail, head) + 1);
7185 }
7186 incoming_arcs[head].push_back(ref);
7187 outgoing_arcs[tail].push_back(ref);
7188 }
7189
7190 // All the node must have some incoming and outgoing arcs.
7191 for (int i = 0; i < num_nodes; ++i) {
7192 if (incoming_arcs[i].empty() || outgoing_arcs[i].empty()) {
7193 return MarkConstraintAsFalse(ct);
7194 }
7195 }
7196
7197 // Note that it is important to reach the fixed point here:
7198 // One arc at true, then all other arc at false. This is because we rely
7199 // on this in case the circuit is fully specified below.
7200 //
7201 // TODO(user): Use a better complexity if needed.
7202 bool loop_again = true;
7203 int num_fixed_at_true = 0;
7204 while (loop_again) {
7205 loop_again = false;
7206 for (const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
7207 for (const std::vector<int>& refs : *node_to_refs) {
7208 if (refs.size() == 1) {
7209 if (!context_->LiteralIsTrue(refs.front())) {
7210 ++num_fixed_at_true;
7211 if (!context_->SetLiteralToTrue(refs.front())) return true;
7212 }
7213 continue;
7214 }
7215
7216 // At most one true, so if there is one, mark all the other to false.
7217 int num_true = 0;
7218 int true_ref;
7219 for (const int ref : refs) {
7220 if (context_->LiteralIsTrue(ref)) {
7221 ++num_true;
7222 true_ref = ref;
7223 break;
7224 }
7225 }
7226 if (num_true > 1) {
7227 return context_->NotifyThatModelIsUnsat();
7228 }
7229 if (num_true == 1) {
7230 for (const int ref : refs) {
7231 if (ref != true_ref) {
7232 if (!context_->IsFixed(ref)) {
7233 context_->UpdateRuleStats("circuit: set literal to false.");
7234 loop_again = true;
7235 }
7236 if (!context_->SetLiteralToFalse(ref)) return true;
7237 }
7238 }
7239 }
7240 }
7241 }
7242 }
7243 if (num_fixed_at_true > 0) {
7244 context_->UpdateRuleStats("circuit: fixed singleton arcs.");
7245 }
7246
7247 // Remove false arcs.
7248 int new_size = 0;
7249 int num_true = 0;
7250 int circuit_start = -1;
7251 std::vector<int> next(num_nodes, -1);
7252 std::vector<int> new_in_degree(num_nodes, 0);
7253 std::vector<int> new_out_degree(num_nodes, 0);
7254 for (int i = 0; i < num_arcs; ++i) {
7255 const int ref = proto.literals(i);
7256 if (context_->LiteralIsFalse(ref)) continue;
7257 if (context_->LiteralIsTrue(ref)) {
7258 if (next[proto.tails(i)] != -1) {
7259 return context_->NotifyThatModelIsUnsat();
7260 }
7261 next[proto.tails(i)] = proto.heads(i);
7262 if (proto.tails(i) != proto.heads(i)) {
7263 circuit_start = proto.tails(i);
7264 }
7265 ++num_true;
7266 }
7267 ++new_out_degree[proto.tails(i)];
7268 ++new_in_degree[proto.heads(i)];
7269 proto.set_tails(new_size, proto.tails(i));
7270 proto.set_heads(new_size, proto.heads(i));
7271 proto.set_literals(new_size, ref);
7272 ++new_size;
7273 }
7274
7275 // Detect infeasibility due to a node having no more incoming or outgoing arc.
7276 // This is a bit tricky because for now the meaning of the constraint says
7277 // that all nodes that appear in at least one of the arcs must be in the
7278 // circuit or have a self-arc. So if any such node ends up with an incoming or
7279 // outgoing degree of zero once we remove false arcs then the constraint is
7280 // infeasible!
7281 for (int i = 0; i < num_nodes; ++i) {
7282 if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
7283 return context_->NotifyThatModelIsUnsat();
7284 }
7285 }
7286
7287 // Test if a subcircuit is already present.
7288 if (circuit_start != -1) {
7289 std::vector<bool> visited(num_nodes, false);
7290 int current = circuit_start;
7291 while (current != -1 && !visited[current]) {
7292 visited[current] = true;
7293 current = next[current];
7294 }
7295 if (current == circuit_start) {
7296 // We have a sub-circuit! mark all other arc false except self-loop not in
7297 // circuit.
7298 std::vector<bool> has_self_arc(num_nodes, false);
7299 for (int i = 0; i < num_arcs; ++i) {
7300 if (visited[proto.tails(i)]) continue;
7301 if (proto.tails(i) == proto.heads(i)) {
7302 has_self_arc[proto.tails(i)] = true;
7303 if (!context_->SetLiteralToTrue(proto.literals(i))) return true;
7304 } else {
7305 if (!context_->SetLiteralToFalse(proto.literals(i))) return true;
7306 }
7307 }
7308 for (int n = 0; n < num_nodes; ++n) {
7309 if (!visited[n] && !has_self_arc[n]) {
7310 // We have a subircuit, but it doesn't cover all the mandatory nodes.
7311 return MarkConstraintAsFalse(ct);
7312 }
7313 }
7314 context_->UpdateRuleStats("circuit: fully specified.");
7315 return RemoveConstraint(ct);
7316 }
7317 } else {
7318 // All self loop?
7319 if (num_true == new_size) {
7320 context_->UpdateRuleStats("circuit: empty circuit.");
7321 return RemoveConstraint(ct);
7322 }
7323 }
7324
7325 // Look for in/out-degree of two, this will imply that one of the indicator
7326 // Boolean is equal to the negation of the other.
7327 for (int i = 0; i < num_nodes; ++i) {
7328 for (const std::vector<int>* arc_literals :
7329 {&incoming_arcs[i], &outgoing_arcs[i]}) {
7330 std::vector<int> literals;
7331 for (const int ref : *arc_literals) {
7332 if (context_->LiteralIsFalse(ref)) continue;
7333 if (context_->LiteralIsTrue(ref)) {
7334 literals.clear();
7335 break;
7336 }
7337 literals.push_back(ref);
7338 }
7339 if (literals.size() == 2 && literals[0] != NegatedRef(literals[1])) {
7340 context_->UpdateRuleStats("circuit: degree 2");
7341 if (!context_->StoreBooleanEqualityRelation(literals[0],
7342 NegatedRef(literals[1]))) {
7343 return true;
7344 }
7345 }
7346 }
7347 }
7348
7349 // Truncate the circuit and return.
7350 if (new_size < num_arcs) {
7351 proto.mutable_tails()->Truncate(new_size);
7352 proto.mutable_heads()->Truncate(new_size);
7353 proto.mutable_literals()->Truncate(new_size);
7354 context_->UpdateRuleStats("circuit: removed false arcs.");
7355 return true;
7356 }
7357 RunPropagatorsForConstraint(*ct);
7358 return false;
7359}
7360
7361bool CpModelPresolver::PresolveAutomaton(ConstraintProto* ct) {
7362 if (context_->ModelIsUnsat()) return false;
7363 if (HasEnforcementLiteral(*ct)) return false;
7364
7365 AutomatonConstraintProto* proto = ct->mutable_automaton();
7366 if (proto->exprs_size() == 0 || proto->transition_label_size() == 0) {
7367 return false;
7368 }
7369
7370 bool changed = false;
7371 for (int i = 0; i < proto->exprs_size(); ++i) {
7372 changed |= CanonicalizeLinearExpression(*ct, proto->mutable_exprs(i));
7373 }
7374
7375 std::vector<absl::flat_hash_set<int64_t>> reachable_states;
7376 std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
7377 PropagateAutomaton(*proto, *context_, &reachable_states, &reachable_labels);
7378
7379 // Filter domains and compute the union of all relevant labels.
7380 for (int time = 0; time < reachable_labels.size(); ++time) {
7381 const LinearExpressionProto& expr = proto->exprs(time);
7382 if (context_->IsFixed(expr)) {
7383 if (!reachable_labels[time].contains(context_->FixedValue(expr))) {
7384 return MarkConstraintAsFalse(ct);
7385 }
7386 } else {
7387 std::vector<int64_t> unscaled_reachable_labels;
7388 for (const int64_t label : reachable_labels[time]) {
7389 unscaled_reachable_labels.push_back(GetInnerVarValue(expr, label));
7390 }
7391 bool removed_values = false;
7392 if (!context_->IntersectDomainWith(
7393 expr.vars(0), Domain::FromValues(unscaled_reachable_labels),
7394 &removed_values)) {
7395 return true;
7396 }
7397 if (removed_values) {
7398 context_->UpdateRuleStats("automaton: reduce variable domain");
7399 }
7400 }
7401 }
7402
7403 return changed;
7404}
7405
7406bool CpModelPresolver::PresolveReservoir(ConstraintProto* ct) {
7407 if (context_->ModelIsUnsat()) return false;
7408 if (HasEnforcementLiteral(*ct)) return false;
7409
7410 ReservoirConstraintProto& proto = *ct->mutable_reservoir();
7411 bool changed = false;
7412 for (LinearExpressionProto& exp : *(proto.mutable_time_exprs())) {
7413 changed |= CanonicalizeLinearExpression(*ct, &exp);
7414 }
7415 for (LinearExpressionProto& exp : *(proto.mutable_level_changes())) {
7416 changed |= CanonicalizeLinearExpression(*ct, &exp);
7417 }
7418
7419 if (proto.active_literals().empty()) {
7420 const int true_literal = context_->GetTrueLiteral();
7421 for (int i = 0; i < proto.time_exprs_size(); ++i) {
7422 proto.add_active_literals(true_literal);
7423 }
7424 changed = true;
7425 }
7426
7427 const auto& demand_is_null = [&](int i) {
7428 return (context_->IsFixed(proto.level_changes(i)) &&
7429 context_->FixedValue(proto.level_changes(i)) == 0) ||
7430 context_->LiteralIsFalse(proto.active_literals(i));
7431 };
7432
7433 // Remove zero level_changes, and inactive events.
7434 int num_zeros = 0;
7435 for (int i = 0; i < proto.level_changes_size(); ++i) {
7436 if (demand_is_null(i)) num_zeros++;
7437 }
7438
7439 if (num_zeros > 0) { // Remove null events
7440 changed = true;
7441 int new_size = 0;
7442 for (int i = 0; i < proto.level_changes_size(); ++i) {
7443 if (demand_is_null(i)) continue;
7444 *proto.mutable_level_changes(new_size) = proto.level_changes(i);
7445 *proto.mutable_time_exprs(new_size) = proto.time_exprs(i);
7446 proto.set_active_literals(new_size, proto.active_literals(i));
7447 new_size++;
7448 }
7449
7450 proto.mutable_level_changes()->erase(
7451 proto.mutable_level_changes()->begin() + new_size,
7452 proto.mutable_level_changes()->end());
7453 proto.mutable_time_exprs()->erase(
7454 proto.mutable_time_exprs()->begin() + new_size,
7455 proto.mutable_time_exprs()->end());
7456 proto.mutable_active_literals()->Truncate(new_size);
7457
7458 context_->UpdateRuleStats(
7459 "reservoir: remove zero level_changes or inactive events.");
7460 }
7461
7462 // The rest of the presolve only applies if all demands are fixed.
7463 for (const LinearExpressionProto& level_change : proto.level_changes()) {
7464 if (!context_->IsFixed(level_change)) return changed;
7465 }
7466
7467 const int num_events = proto.level_changes_size();
7468 int64_t gcd = proto.level_changes().empty()
7469 ? 0
7470 : std::abs(context_->FixedValue(proto.level_changes(0)));
7471 int num_positives = 0;
7472 int num_negatives = 0;
7473 int64_t max_sum_of_positive_level_changes = 0;
7474 int64_t min_sum_of_negative_level_changes = 0;
7475 for (int i = 0; i < num_events; ++i) {
7476 const int64_t demand = context_->FixedValue(proto.level_changes(i));
7477 gcd = std::gcd(gcd, std::abs(demand));
7478 if (demand > 0) {
7479 num_positives++;
7480 max_sum_of_positive_level_changes += demand;
7481 } else {
7482 DCHECK_LT(demand, 0);
7483 num_negatives++;
7484 min_sum_of_negative_level_changes += demand;
7485 }
7486 }
7487
7488 if (min_sum_of_negative_level_changes >= proto.min_level() &&
7489 max_sum_of_positive_level_changes <= proto.max_level()) {
7490 context_->UpdateRuleStats("reservoir: always feasible");
7491 return RemoveConstraint(ct);
7492 }
7493
7494 if (min_sum_of_negative_level_changes > proto.max_level() ||
7495 max_sum_of_positive_level_changes < proto.min_level()) {
7496 context_->UpdateRuleStats("reservoir: trivially infeasible");
7497 return context_->NotifyThatModelIsUnsat();
7498 }
7499
7500 if (min_sum_of_negative_level_changes > proto.min_level()) {
7501 proto.set_min_level(min_sum_of_negative_level_changes);
7502 context_->UpdateRuleStats(
7503 "reservoir: increase min_level to reachable value");
7504 }
7505
7506 if (max_sum_of_positive_level_changes < proto.max_level()) {
7507 proto.set_max_level(max_sum_of_positive_level_changes);
7508 context_->UpdateRuleStats("reservoir: reduce max_level to reachable value");
7509 }
7510
7511 if (proto.min_level() <= 0 && proto.max_level() >= 0 &&
7512 (num_positives == 0 || num_negatives == 0)) {
7513 // If all level_changes have the same sign, and if the initial state is
7514 // always feasible, we do not care about the order, just the sum.
7515 auto* const sum_ct = context_->working_model->add_constraints();
7516 auto* const sum = sum_ct->mutable_linear();
7517 int64_t fixed_contrib = 0;
7518 for (int i = 0; i < proto.level_changes_size(); ++i) {
7519 const int64_t demand = context_->FixedValue(proto.level_changes(i));
7520 DCHECK_NE(demand, 0);
7521
7522 const int active = proto.active_literals(i);
7523 if (RefIsPositive(active)) {
7524 sum->add_vars(active);
7525 sum->add_coeffs(demand);
7526 } else {
7527 sum->add_vars(PositiveRef(active));
7528 sum->add_coeffs(-demand);
7529 fixed_contrib += demand;
7530 }
7531 }
7532 sum->add_domain(proto.min_level() - fixed_contrib);
7533 sum->add_domain(proto.max_level() - fixed_contrib);
7534 context_->UpdateRuleStats("reservoir: converted to linear");
7535 bool changed = false;
7536 if (!CanonicalizeLinear(sum_ct, &changed)) {
7537 return true;
7538 }
7539 return RemoveConstraint(ct);
7540 }
7541
7542 if (gcd > 1) {
7543 for (int i = 0; i < proto.level_changes_size(); ++i) {
7544 proto.mutable_level_changes(i)->set_offset(
7545 context_->FixedValue(proto.level_changes(i)) / gcd);
7546 proto.mutable_level_changes(i)->clear_vars();
7547 proto.mutable_level_changes(i)->clear_coeffs();
7548 }
7549
7550 // Adjust min and max levels.
7551 // max level is always rounded down.
7552 // min level is always rounded up.
7553 const Domain reduced_domain = Domain({proto.min_level(), proto.max_level()})
7554 .InverseMultiplicationBy(gcd);
7555 proto.set_min_level(reduced_domain.Min());
7556 proto.set_max_level(reduced_domain.Max());
7557 context_->UpdateRuleStats(
7558 "reservoir: simplify level_changes and levels by gcd.");
7559 }
7560
7561 if (num_positives == 1 && num_negatives > 0) {
7562 context_->UpdateRuleStats(
7563 "TODO reservoir: one producer, multiple consumers.");
7564 }
7565
7566 absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set;
7567 for (int i = 0; i < proto.level_changes_size(); ++i) {
7568 const LinearExpressionProto& time = proto.time_exprs(i);
7569 const int var = context_->IsFixed(time) ? std::numeric_limits<int>::min()
7570 : time.vars(0);
7571 const int64_t coeff = context_->IsFixed(time) ? 0 : time.coeffs(0);
7572 const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple(
7573 var, coeff,
7574 context_->IsFixed(time) ? context_->FixedValue(time) : time.offset(),
7575 proto.active_literals(i));
7576 if (time_active_set.contains(key)) {
7577 context_->UpdateRuleStats("TODO reservoir: merge synchronized events.");
7578 break;
7579 } else {
7580 time_active_set.insert(key);
7581 }
7582 }
7583
7584 RunPropagatorsForConstraint(*ct);
7585 return changed;
7586}
7587
7588// TODO(user): It is probably more efficient to keep all the bool_and in a
7589// global place during all the presolve, and just output them at the end
7590// rather than modifying more than once the proto.
7591void CpModelPresolver::ConvertToBoolAnd() {
7592 absl::flat_hash_map<int, int> ref_to_bool_and;
7593 const int num_constraints = context_->working_model->constraints_size();
7594 std::vector<int> to_remove;
7595 for (int c = 0; c < num_constraints; ++c) {
7596 const ConstraintProto& ct = context_->working_model->constraints(c);
7597 if (HasEnforcementLiteral(ct)) continue;
7598
7599 if (ct.constraint_case() == ConstraintProto::kBoolOr &&
7600 ct.bool_or().literals().size() == 2) {
7601 AddImplication(NegatedRef(ct.bool_or().literals(0)),
7602 ct.bool_or().literals(1), context_->working_model,
7603 &ref_to_bool_and);
7604 to_remove.push_back(c);
7605 continue;
7606 }
7607
7608 if (ct.constraint_case() == ConstraintProto::kAtMostOne &&
7609 ct.at_most_one().literals().size() == 2) {
7610 AddImplication(ct.at_most_one().literals(0),
7611 NegatedRef(ct.at_most_one().literals(1)),
7612 context_->working_model, &ref_to_bool_and);
7613 to_remove.push_back(c);
7614 continue;
7615 }
7616 }
7617
7618 context_->UpdateNewConstraintsVariableUsage();
7619 for (const int c : to_remove) {
7620 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7621 CHECK(RemoveConstraint(ct));
7622 context_->UpdateConstraintVariableUsage(c);
7623 }
7624}
7625
7626void CpModelPresolver::RunPropagatorsForConstraint(const ConstraintProto& ct) {
7627 if (context_->ModelIsUnsat()) return;
7628
7629 Model model;
7630
7631 // Enable as many propagators as possible. We do not care if some propagator
7632 // is a bit slow or if the explanation is too big: anything that improves our
7633 // bounds is an improvement.
7634 SatParameters local_params;
7635 local_params.set_use_try_edge_reasoning_in_no_overlap_2d(true);
7636 local_params.set_exploit_all_precedences(true);
7637 local_params.set_use_hard_precedences_in_cumulative(true);
7638 local_params.set_max_num_intervals_for_timetable_edge_finding(1000);
7639 local_params.set_use_overload_checker_in_cumulative(true);
7640 local_params.set_use_strong_propagation_in_disjunctive(true);
7641 local_params.set_use_timetable_edge_finding_in_cumulative(true);
7642 local_params.set_max_pairs_pairwise_reasoning_in_no_overlap_2d(50000);
7643 local_params.set_use_timetabling_in_no_overlap_2d(true);
7644 local_params.set_use_energetic_reasoning_in_no_overlap_2d(true);
7645 local_params.set_use_area_energetic_reasoning_in_no_overlap_2d(true);
7646 local_params.set_use_conservative_scale_overload_checker(true);
7647 local_params.set_use_dual_scheduling_heuristics(true);
7648
7649 model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
7650 std::vector<int> variable_mapping;
7651 CreateValidModelWithSingleConstraint(ct, context_, &variable_mapping,
7652 &tmp_model_);
7653 DCHECK_EQ(ValidateCpModel(tmp_model_, false), "");
7654 if (!LoadModelForPresolve(tmp_model_, std::move(local_params), context_,
7655 &model, "single constraint")) {
7656 return;
7657 }
7658
7659 time_limit_->AdvanceDeterministicTime(
7660 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
7661 auto* mapping = model.GetOrCreate<CpModelMapping>();
7662 auto* integer_trail = model.GetOrCreate<IntegerTrail>();
7663 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
7664 auto* trail = model.GetOrCreate<Trail>();
7665
7666 int num_equiv = 0;
7667 int num_changed_bounds = 0;
7668 int num_fixed_bools = 0;
7669 for (int var = 0; var < variable_mapping.size(); ++var) {
7670 const int proto_var = variable_mapping[var];
7671 if (mapping->IsBoolean(var)) {
7672 const Literal l = mapping->Literal(var);
7673 if (trail->Assignment().LiteralIsFalse(l)) {
7674 if (!context_->SetLiteralToFalse(proto_var)) return;
7675 ++num_fixed_bools;
7676 continue;
7677 } else if (trail->Assignment().LiteralIsTrue(l)) {
7678 if (!context_->SetLiteralToTrue(proto_var)) return;
7679 ++num_fixed_bools;
7680 continue;
7681 }
7682 // Add Boolean equivalence relations.
7683 const Literal r = implication_graph->RepresentativeOf(l);
7684 if (r != l) {
7685 ++num_equiv;
7686 const int r_var =
7687 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
7688 if (r_var < 0) continue;
7689 if (!context_->StoreBooleanEqualityRelation(
7690 proto_var, r.IsPositive() ? r_var : NegatedRef(r_var))) {
7691 return;
7692 }
7693 }
7694 } else {
7695 // Restrict variable domain.
7696 bool changed = false;
7697 if (!context_->IntersectDomainWith(
7698 proto_var,
7699 integer_trail->InitialVariableDomain(mapping->Integer(var)),
7700 &changed)) {
7701 return;
7702 }
7703 if (changed) ++num_changed_bounds;
7704 }
7705 }
7706 if (num_changed_bounds > 0) {
7707 context_->UpdateRuleStats("propagators: changed bounds",
7708 num_changed_bounds);
7709 }
7710 if (num_fixed_bools > 0) {
7711 context_->UpdateRuleStats("propagators: fixed booleans", num_fixed_bools);
7712 }
7713}
7714
7715// TODO(user): It might make sense to run this in parallel. The same apply for
7716// other expansive and self-contains steps like symmetry detection, etc...
7717void CpModelPresolver::Probe() {
7718 auto probing_timer =
7719 std::make_unique<PresolveTimer>(__FUNCTION__, logger_, time_limit_);
7720
7721 Model model;
7722 if (!LoadModelForProbing(context_, &model)) return;
7723
7724 // Probe.
7725 //
7726 // TODO(user): Compute the transitive reduction instead of just the
7727 // equivalences, and use the newly learned binary clauses?
7728 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
7729 auto* sat_solver = model.GetOrCreate<SatSolver>();
7730 auto* mapping = model.GetOrCreate<CpModelMapping>();
7731 auto* prober = model.GetOrCreate<Prober>();
7732
7733 // Try to detect trivial clauses thanks to implications.
7734 // This can be slow, so we bound the amount of work done.
7735 //
7736 // Idea: If we have l1, l2 in a bool_or and not(l1) => l2, the constraint is
7737 // always true.
7738 //
7739 // Correctness: Note that we always replace a clause with another one that
7740 // subsumes it. So we are correct even if new clauses are learned and used
7741 // for propagation along the way.
7742 //
7743 // TODO(user): Improve the algo?
7744 const auto& assignment = sat_solver->Assignment();
7745 prober->SetPropagationCallback([&](Literal decision) {
7746 if (probing_timer->WorkLimitIsReached()) return;
7747 const int decision_var =
7748 mapping->GetProtoVariableFromBooleanVariable(decision.Variable());
7749 if (decision_var < 0) return;
7750 probing_timer->TrackSimpleLoop(
7751 context_->VarToConstraints(decision_var).size());
7752 std::vector<int> to_update;
7753 for (const int c : context_->VarToConstraints(decision_var)) {
7754 if (c < 0) continue;
7755 const ConstraintProto& ct = context_->working_model->constraints(c);
7756 if (ct.enforcement_literal().size() > 2) {
7757 // Any l for which decision => l can be removed.
7758 //
7759 // If decision => not(l), constraint can never be satisfied. However
7760 // because we don't know if this constraint was part of the
7761 // propagation we replace it by an implication.
7762 //
7763 // TODO(user): remove duplication with code below.
7764 // TODO(user): If decision appear positively, we could potentially
7765 // remove a bunch of terms (all the ones involving variables implied
7766 // by the decision) from the inner constraint, especially in the
7767 // linear case.
7768 int decision_ref;
7769 int false_ref;
7770 bool decision_is_positive = false;
7771 bool has_false_literal = false;
7772 bool simplification_possible = false;
7773 probing_timer->TrackSimpleLoop(ct.enforcement_literal().size());
7774 for (const int ref : ct.enforcement_literal()) {
7775 const Literal lit = mapping->Literal(ref);
7776 if (PositiveRef(ref) == decision_var) {
7777 decision_ref = ref;
7778 decision_is_positive = assignment.LiteralIsTrue(lit);
7779 if (!decision_is_positive) break;
7780 continue;
7781 }
7782 if (assignment.LiteralIsFalse(lit)) {
7783 false_ref = ref;
7784 has_false_literal = true;
7785 } else if (assignment.LiteralIsTrue(lit)) {
7786 // If decision => l, we can remove l from the list.
7787 simplification_possible = true;
7788 }
7789 }
7790 if (!decision_is_positive) continue;
7791
7792 if (has_false_literal) {
7793 // Reduce to implication.
7794 auto* mutable_ct = context_->working_model->mutable_constraints(c);
7795 mutable_ct->Clear();
7796 mutable_ct->add_enforcement_literal(decision_ref);
7797 mutable_ct->mutable_bool_and()->add_literals(NegatedRef(false_ref));
7798 context_->UpdateRuleStats(
7799 "probing: reduced enforced constraint to implication.");
7800 to_update.push_back(c);
7801 continue;
7802 }
7803
7804 if (simplification_possible) {
7805 int new_size = 0;
7806 auto* mutable_enforcements =
7807 context_->working_model->mutable_constraints(c)
7808 ->mutable_enforcement_literal();
7809 for (const int ref : ct.enforcement_literal()) {
7810 if (PositiveRef(ref) != decision_var &&
7811 assignment.LiteralIsTrue(mapping->Literal(ref))) {
7812 continue;
7813 }
7814 mutable_enforcements->Set(new_size++, ref);
7815 }
7816 mutable_enforcements->Truncate(new_size);
7817 context_->UpdateRuleStats("probing: simplified enforcement list.");
7818 to_update.push_back(c);
7819 }
7820 continue;
7821 }
7822
7823 if (ct.constraint_case() != ConstraintProto::kBoolOr) continue;
7824 if (ct.bool_or().literals().size() <= 2) continue;
7825
7826 int decision_ref;
7827 int true_ref;
7828 bool decision_is_negative = false;
7829 bool has_true_literal = false;
7830 bool simplification_possible = false;
7831 probing_timer->TrackSimpleLoop(ct.bool_or().literals().size());
7832 for (const int ref : ct.bool_or().literals()) {
7833 const Literal lit = mapping->Literal(ref);
7834 if (PositiveRef(ref) == decision_var) {
7835 decision_ref = ref;
7836 decision_is_negative = assignment.LiteralIsFalse(lit);
7837 if (!decision_is_negative) break;
7838 continue;
7839 }
7840 if (assignment.LiteralIsTrue(lit)) {
7841 true_ref = ref;
7842 has_true_literal = true;
7843 } else if (assignment.LiteralIsFalse(lit)) {
7844 // If not(l1) => not(l2), we can remove l2 from the clause.
7845 simplification_possible = true;
7846 }
7847 }
7848 if (!decision_is_negative) continue;
7849
7850 if (has_true_literal) {
7851 // This will later be merged with the current implications and removed
7852 // if it is a duplicate.
7853 auto* mutable_bool_or =
7854 context_->working_model->mutable_constraints(c)->mutable_bool_or();
7855 mutable_bool_or->mutable_literals()->Clear();
7856 mutable_bool_or->add_literals(decision_ref);
7857 mutable_bool_or->add_literals(true_ref);
7858 context_->UpdateRuleStats("probing: bool_or reduced to implication");
7859 to_update.push_back(c);
7860 continue;
7861 }
7862
7863 if (simplification_possible) {
7864 int new_size = 0;
7865 auto* mutable_bool_or =
7866 context_->working_model->mutable_constraints(c)->mutable_bool_or();
7867 for (const int ref : ct.bool_or().literals()) {
7868 if (PositiveRef(ref) != decision_var &&
7869 assignment.LiteralIsFalse(mapping->Literal(ref))) {
7870 continue;
7871 }
7872 mutable_bool_or->set_literals(new_size++, ref);
7873 }
7874 mutable_bool_or->mutable_literals()->Truncate(new_size);
7875 context_->UpdateRuleStats("probing: simplified clauses.");
7876 to_update.push_back(c);
7877 }
7878 }
7879
7880 absl::c_sort(to_update);
7881 for (const int c : to_update) {
7882 context_->UpdateConstraintVariableUsage(c);
7883 }
7884 });
7885
7886 prober->ProbeBooleanVariables(
7887 context_->params().probing_deterministic_time_limit());
7888
7889 probing_timer->AddCounter("probed", prober->num_decisions());
7890 probing_timer->AddToWork(
7891 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
7892 if (sat_solver->ModelIsUnsat() || !implication_graph->DetectEquivalences()) {
7893 return (void)context_->NotifyThatModelIsUnsat("during probing");
7894 }
7895
7896 time_limit_->ResetHistory();
7897
7898 // Update the presolve context with fixed Boolean variables.
7899 int num_fixed = 0;
7900 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
7901 for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
7902 const Literal l = sat_solver->LiteralTrail()[i];
7903 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
7904 if (var >= 0) {
7905 const int ref = l.IsPositive() ? var : NegatedRef(var);
7906 if (context_->IsFixed(ref)) continue;
7907 ++num_fixed;
7908 if (!context_->SetLiteralToTrue(ref)) return;
7909 }
7910 }
7911 probing_timer->AddCounter("fixed_bools", num_fixed);
7912
7913 int num_equiv = 0;
7914 int num_changed_bounds = 0;
7915 const int num_variables = context_->working_model->variables().size();
7916 auto* integer_trail = model.GetOrCreate<IntegerTrail>();
7917 for (int var = 0; var < num_variables; ++var) {
7918 // Restrict IntegerVariable domain.
7919 // Note that Boolean are already dealt with above.
7920 if (!mapping->IsBoolean(var)) {
7921 bool changed = false;
7922 if (!context_->IntersectDomainWith(
7923 var, integer_trail->InitialVariableDomain(mapping->Integer(var)),
7924 &changed)) {
7925 return;
7926 }
7927 if (changed) ++num_changed_bounds;
7928 continue;
7929 }
7930
7931 // Add Boolean equivalence relations.
7932 const Literal l = mapping->Literal(var);
7933 const Literal r = implication_graph->RepresentativeOf(l);
7934 if (r != l) {
7935 ++num_equiv;
7936 const int r_var =
7937 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
7938 CHECK_GE(r_var, 0);
7939 if (!context_->StoreBooleanEqualityRelation(
7940 var, r.IsPositive() ? r_var : NegatedRef(r_var))) {
7941 return;
7942 }
7943 }
7944 }
7945 probing_timer->AddCounter("new_bounds", num_changed_bounds);
7946 probing_timer->AddCounter("equiv", num_equiv);
7947 probing_timer->AddCounter("new_binary_clauses",
7948 prober->num_new_binary_clauses());
7949
7950 // Note that we prefer to run this after we exported all equivalence to the
7951 // context, so that our enforcement list can be presolved to the best of our
7952 // knowledge.
7953 DetectDuplicateConstraintsWithDifferentEnforcements(
7954 mapping, implication_graph, model.GetOrCreate<Trail>());
7955
7956 // Stop probing timer now and display info.
7957 probing_timer.reset();
7958
7959 // Run clique merging using detected implications from probing.
7960 if (context_->params().merge_at_most_one_work_limit() > 0.0) {
7961 PresolveTimer timer("MaxClique", logger_, time_limit_);
7962 std::vector<std::vector<Literal>> cliques;
7963 std::vector<int> clique_ct_index;
7964
7965 // TODO(user): On large model, most of the time is spend in this copy,
7966 // clearing and updating the constraint variable graph...
7967 int64_t num_literals_before = 0;
7968 const int num_constraints = context_->working_model->constraints_size();
7969 for (int c = 0; c < num_constraints; ++c) {
7970 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7971 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
7972 std::vector<Literal> clique;
7973 for (const int ref : ct->at_most_one().literals()) {
7974 clique.push_back(mapping->Literal(ref));
7975 }
7976 num_literals_before += clique.size();
7977 cliques.push_back(clique);
7978 ct->Clear();
7979 context_->UpdateConstraintVariableUsage(c);
7980 } else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
7981 if (ct->enforcement_literal().size() != 1) continue;
7982 const Literal enforcement =
7983 mapping->Literal(ct->enforcement_literal(0));
7984 for (const int ref : ct->bool_and().literals()) {
7985 if (ref == ct->enforcement_literal(0)) continue;
7986 num_literals_before += 2;
7987 cliques.push_back({enforcement, mapping->Literal(ref).Negated()});
7988 }
7989 ct->Clear();
7990 context_->UpdateConstraintVariableUsage(c);
7991 }
7992 }
7993 const int64_t num_old_cliques = cliques.size();
7994
7995 // We adapt the limit if there is a lot of literals in amo/implications.
7996 // Usually we can have big reduction on large problem so it seems
7997 // worthwhile.
7998 double limit = context_->params().merge_at_most_one_work_limit();
7999 if (num_literals_before > 1e6) {
8000 limit *= num_literals_before / 1e6;
8001 }
8002
8003 double dtime = 0.0;
8004 implication_graph->MergeAtMostOnes(absl::MakeSpan(cliques),
8005 SafeDoubleToInt64(limit), &dtime);
8006 timer.AddToWork(dtime);
8007
8008 // Note that because TransformIntoMaxCliques() extend cliques, we are ok
8009 // to ignore any unmapped literal. In case of equivalent literal, we always
8010 // use the smaller indices as a representative, so we should be good.
8011 int num_new_cliques = 0;
8012 int64_t num_literals_after = 0;
8013 for (const std::vector<Literal>& clique : cliques) {
8014 if (clique.empty()) continue;
8015 num_new_cliques++;
8016 num_literals_after += clique.size();
8017 ConstraintProto* ct = context_->working_model->add_constraints();
8018 for (const Literal literal : clique) {
8019 const int var =
8020 mapping->GetProtoVariableFromBooleanVariable(literal.Variable());
8021 if (var < 0) continue;
8022 if (literal.IsPositive()) {
8023 ct->mutable_at_most_one()->add_literals(var);
8024 } else {
8025 ct->mutable_at_most_one()->add_literals(NegatedRef(var));
8026 }
8027 }
8028
8029 // Make sure we do not have duplicate variable reference.
8030 PresolveAtMostOne(ct);
8031 }
8032 context_->UpdateNewConstraintsVariableUsage();
8033 if (num_new_cliques != num_old_cliques) {
8034 context_->UpdateRuleStats("at_most_one: transformed into max clique.");
8035 }
8036
8037 if (num_old_cliques != num_new_cliques ||
8038 num_literals_before != num_literals_after) {
8039 timer.AddMessage(absl::StrCat(
8040 "Merged ", FormatCounter(num_old_cliques), "(",
8041 FormatCounter(num_literals_before), " literals) into ",
8042 FormatCounter(num_new_cliques), "(",
8043 FormatCounter(num_literals_after), " literals) at_most_ones. "));
8044 }
8045 }
8046}
8047
8048namespace {
8049
8050bool FixFromAssignment(const VariablesAssignment& assignment,
8051 absl::Span<const int> var_mapping,
8052 PresolveContext* context) {
8053 const int num_vars = assignment.NumberOfVariables();
8054 for (int i = 0; i < num_vars; ++i) {
8055 const Literal lit(BooleanVariable(i), true);
8056 const int ref = var_mapping[i];
8057 if (assignment.LiteralIsTrue(lit)) {
8058 if (!context->SetLiteralToTrue(ref)) return false;
8059 } else if (assignment.LiteralIsFalse(lit)) {
8060 if (!context->SetLiteralToFalse(ref)) return false;
8061 }
8062 }
8063 return true;
8064}
8065
8066} // namespace
8067
8068// TODO(user): What to do with the at_most_one/exactly_one constraints?
8069// currently we do not take them into account here.
8070bool CpModelPresolver::PresolvePureSatPart() {
8071 // TODO(user): Reenable some SAT presolve with
8072 // keep_all_feasible_solutions set to true.
8073 if (context_->ModelIsUnsat()) return true;
8074 if (context_->params().keep_all_feasible_solutions_in_presolve()) return true;
8075
8076 // Compute a dense re-indexing for the Booleans of the problem.
8077 int num_variables = 0;
8078 int num_ignored_variables = 0;
8079 const int total_num_vars = context_->working_model->variables().size();
8080 std::vector<int> new_index(total_num_vars, -1);
8081 std::vector<int> new_to_old_index;
8082 for (int i = 0; i < total_num_vars; ++i) {
8083 if (!context_->CanBeUsedAsLiteral(i)) {
8084 ++num_ignored_variables;
8085 continue;
8086 }
8087
8088 // This is important to not assign variable in equivalence to random values.
8089 if (context_->VarToConstraints(i).empty()) continue;
8090
8091 new_to_old_index.push_back(i);
8092 new_index[i] = num_variables++;
8093 DCHECK_EQ(num_variables, new_to_old_index.size());
8094 }
8095
8096 // The conversion from proto index to remapped Literal.
8097 auto convert = [&new_index](int ref) {
8098 const int index = new_index[PositiveRef(ref)];
8099 DCHECK_NE(index, -1);
8100 return Literal(BooleanVariable(index), RefIsPositive(ref));
8101 };
8102
8103 // Load the pure-SAT part in a fresh Model.
8104 //
8105 // TODO(user): The removing and adding back of the same clause when nothing
8106 // happens in the presolve "seems" bad. That said, complexity wise, it is
8107 // a lot faster that what happens in the presolve though.
8108 //
8109 // TODO(user): Add the "small" at most one constraints to the SAT presolver by
8110 // expanding them to implications? that could remove a lot of clauses. Do that
8111 // when we are sure we don't load duplicates at_most_one/implications in the
8112 // solver. Ideally, the pure sat presolve could be improved to handle at most
8113 // one, and we could merge this with what the ProcessSetPPC() is doing.
8114 Model local_model;
8115 local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
8116 auto* sat_solver = local_model.GetOrCreate<SatSolver>();
8117 sat_solver->SetNumVariables(num_variables);
8118
8119 // Fix variables if any. Because we might not have reached the presove "fixed
8120 // point" above, some variable in the added clauses might be fixed. We need to
8121 // indicate this to the SAT presolver.
8122 for (const int var : new_to_old_index) {
8123 if (context_->IsFixed(var)) {
8124 if (context_->LiteralIsTrue(var)) {
8125 if (!sat_solver->AddUnitClause({convert(var)})) return false;
8126 } else {
8127 if (!sat_solver->AddUnitClause({convert(NegatedRef(var))})) {
8128 return false;
8129 }
8130 }
8131 }
8132 }
8133
8134 std::vector<Literal> clause;
8135 int num_removed_constraints = 0;
8136 int num_ignored_constraints = 0;
8137 for (int i = 0; i < context_->working_model->constraints_size(); ++i) {
8138 const ConstraintProto& ct = context_->working_model->constraints(i);
8139
8140 if (ct.constraint_case() == ConstraintProto::kBoolOr) {
8141 ++num_removed_constraints;
8142 clause.clear();
8143 for (const int ref : ct.bool_or().literals()) {
8144 clause.push_back(convert(ref));
8145 }
8146 for (const int ref : ct.enforcement_literal()) {
8147 clause.push_back(convert(ref).Negated());
8148 }
8149 sat_solver->AddProblemClause(clause);
8150
8151 context_->working_model->mutable_constraints(i)->Clear();
8152 context_->UpdateConstraintVariableUsage(i);
8153 continue;
8154 }
8155
8156 if (ct.constraint_case() == ConstraintProto::kBoolAnd) {
8157 // We currently do not expand "complex" bool_and that would result
8158 // in too many literals.
8159 const int left_size = ct.enforcement_literal().size();
8160 const int right_size = ct.bool_and().literals().size();
8161 if (left_size > 1 && right_size > 1 &&
8162 (left_size + 1) * right_size > 10'000) {
8163 ++num_ignored_constraints;
8164 continue;
8165 }
8166
8167 ++num_removed_constraints;
8168 std::vector<Literal> clause;
8169 for (const int ref : ct.enforcement_literal()) {
8170 clause.push_back(convert(ref).Negated());
8171 }
8172 clause.push_back(Literal(kNoLiteralIndex)); // will be replaced below.
8173 for (const int ref : ct.bool_and().literals()) {
8174 clause.back() = convert(ref);
8175 sat_solver->AddProblemClause(clause);
8176 }
8177
8178 context_->working_model->mutable_constraints(i)->Clear();
8179 context_->UpdateConstraintVariableUsage(i);
8180 continue;
8181 }
8182
8183 if (ct.constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
8184 continue;
8185 }
8186
8187 ++num_ignored_constraints;
8188 }
8189 if (sat_solver->ModelIsUnsat()) return false;
8190
8191 // Abort early if there was no Boolean constraints.
8192 if (num_removed_constraints == 0) return true;
8193
8194 // Mark the variables appearing elsewhere or in the objective as non-removable
8195 // by the sat presolver.
8196 //
8197 // TODO(user): do not remove variable that appear in the decision heuristic?
8198 // TODO(user): We could go further for variable with only one polarity by
8199 // removing variable from the objective if they can be set to their "low"
8200 // objective value, and also removing enforcement literal that can be set to
8201 // false and don't appear elsewhere.
8202 int num_in_extra_constraints = 0;
8203 std::vector<bool> can_be_removed(num_variables, false);
8204 for (int i = 0; i < num_variables; ++i) {
8205 const int var = new_to_old_index[i];
8206 if (context_->VarToConstraints(var).empty()) {
8207 can_be_removed[i] = true;
8208 } else {
8209 // That might correspond to the objective or a variable with an affine
8210 // relation that is still in the model.
8211 ++num_in_extra_constraints;
8212 }
8213 }
8214
8215 // The "full solver" postsolve does not support changing the value of a
8216 // variable from the solution of the presolved problem, and we do need this
8217 // for blocked clause. It should be possible to allow for this by adding extra
8218 // variable to the mapping model at presolve and some linking constraints, but
8219 // this is messy.
8220 //
8221 // We also disable this if the user asked for tightened domain as this might
8222 // fix variable to a potentially infeasible value, and just correct them later
8223 // during postsolve of a particular solution.
8224 SatParameters sat_params = context_->params();
8225 if (sat_params.debug_postsolve_with_full_solver() ||
8226 sat_params.fill_tightened_domains_in_response()) {
8227 sat_params.set_presolve_blocked_clause(false);
8228 }
8229
8230 // This option is only supported by the custom postsolve code.
8231 if (!sat_params.debug_postsolve_with_full_solver()) {
8232 sat_params.set_filter_sat_postsolve_clauses(true);
8233 }
8234
8235 SatPostsolver sat_postsolver(num_variables);
8236
8237 // If the problem is a pure-SAT problem, we run the new SAT presolver.
8238 // This takes more time but it is usually worthwile
8239 //
8240 // Note that the probing that it does is faster than the
8241 // ProbeAndFindEquivalentLiteral() call below, but does not do equivalence
8242 // detection as completely, so we still apply the other "probing" code
8243 // afterwards even if it will not fix more literals, but it will do one pass
8244 // of proper equivalence detection.
8245 util_intops::StrongVector<LiteralIndex, LiteralIndex> equiv_map;
8246 if (!context_->params().debug_postsolve_with_full_solver() &&
8247 num_ignored_variables == 0 && num_ignored_constraints == 0 &&
8248 num_in_extra_constraints == 0) {
8249 // Some problems are formulated in such a way that our SAT heuristics
8250 // simply works without conflict. Get them out of the way first because it
8251 // is possible that the presolve lose this "lucky" ordering. This is in
8252 // particular the case on the SAT14.crafted.complete-xxx-... problems.
8253 if (!LookForTrivialSatSolution(/*deterministic_time_limit=*/1.0,
8254 &local_model, logger_)) {
8255 return false;
8256 }
8257 if (sat_solver->LiteralTrail().Index() == num_variables) {
8258 // Problem solved! We should be able to assign the solution.
8259 CHECK(FixFromAssignment(sat_solver->Assignment(), new_to_old_index,
8260 context_));
8261 return true;
8262 }
8263
8264 SatPresolveOptions options;
8265 options.log_info = true; // log_info;
8266 options.extract_binary_clauses_in_probing = false;
8267 options.use_transitive_reduction = false;
8268 options.deterministic_time_limit =
8269 context_->params().presolve_probing_deterministic_time_limit();
8270
8271 auto* inprocessing = local_model.GetOrCreate<Inprocessing>();
8272 inprocessing->ProvideLogger(logger_);
8273 if (!inprocessing->PresolveLoop(options)) return false;
8274 for (const auto& c : local_model.GetOrCreate<PostsolveClauses>()->clauses) {
8275 sat_postsolver.Add(c[0], c);
8276 }
8277
8278 // Probe + find equivalent literals.
8279 // TODO(user): Use a derived time limit in the probing phase.
8280 ProbeAndFindEquivalentLiteral(sat_solver, &sat_postsolver,
8281 /*drat_proof_handler=*/nullptr, &equiv_map,
8282 logger_);
8283 if (sat_solver->ModelIsUnsat()) return false;
8284 } else {
8285 // TODO(user): BVA takes time and does not seems to help on the minizinc
8286 // benchmarks. So we currently disable it, except if we are on a pure-SAT
8287 // problem, where we follow the default (true) or the user specified value.
8288 sat_params.set_presolve_use_bva(false);
8289 }
8290
8291 // Disable BVA if we want to keep the symmetries.
8292 //
8293 // TODO(user): We could still do it, we just need to do in a symmetric way
8294 // and also update the generators to take into account the new variables. This
8295 // do not seems that easy.
8296 if (context_->params().keep_symmetry_in_presolve()) {
8297 sat_params.set_presolve_use_bva(false);
8298 }
8299
8300 // Update the time limit of the initial propagation.
8301 if (!sat_solver->ResetToLevelZero()) return false;
8302 time_limit_->AdvanceDeterministicTime(
8303 local_model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
8304
8305 // Apply the "old" SAT presolve.
8306 SatPresolver sat_presolver(&sat_postsolver, logger_);
8307 sat_presolver.SetNumVariables(num_variables);
8308 if (!equiv_map.empty()) {
8309 sat_presolver.SetEquivalentLiteralMapping(equiv_map);
8310 }
8311 sat_presolver.SetTimeLimit(time_limit_);
8312 sat_presolver.SetParameters(sat_params);
8313
8314 // Load in the presolver.
8315 // Register the fixed variables with the postsolver.
8316 for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
8317 sat_postsolver.FixVariable(sat_solver->LiteralTrail()[i]);
8318 }
8319 if (!sat_solver->ExtractClauses(&sat_presolver)) return false;
8320
8321 // Run the presolve for a small number of passes.
8322 // TODO(user): Add a local time limit? this can be slow on big SAT problem.
8323 for (int i = 0; i < 1; ++i) {
8324 const int old_num_clause = sat_postsolver.NumClauses();
8325 if (!sat_presolver.Presolve(can_be_removed)) return false;
8326 if (old_num_clause == sat_postsolver.NumClauses()) break;
8327 }
8328
8329 // Add any new variables to our internal structure.
8330 const int new_num_variables = sat_presolver.NumVariables();
8331 if (new_num_variables > num_variables) {
8332 VLOG(1) << "New variables added by the SAT presolver.";
8333 for (int i = num_variables; i < new_num_variables; ++i) {
8334 new_to_old_index.push_back(context_->working_model->variables().size());
8335 IntegerVariableProto* var_proto =
8336 context_->working_model->add_variables();
8337 var_proto->add_domain(0);
8338 var_proto->add_domain(1);
8339 }
8340 context_->InitializeNewDomains();
8341 }
8342
8343 // Fix variables if any.
8344 if (!FixFromAssignment(sat_postsolver.assignment(), new_to_old_index,
8345 context_)) {
8346 return false;
8347 }
8348
8349 // Add the presolver clauses back into the model.
8350 ExtractClauses(/*merge_into_bool_and=*/true, new_to_old_index, sat_presolver,
8351 context_->working_model);
8352
8353 // Update the constraints <-> variables graph.
8354 context_->UpdateNewConstraintsVariableUsage();
8355
8356 // We mark as removed any variables removed by the pure SAT presolve.
8357 // This is mainly to discover or avoid bug as we might have stale entries
8358 // in our encoding hash-map for instance.
8359 for (int i = 0; i < num_variables; ++i) {
8360 const int var = new_to_old_index[i];
8361 if (context_->VarToConstraints(var).empty()) {
8362 context_->MarkVariableAsRemoved(var);
8363 }
8364 }
8365
8366 // Add the sat_postsolver clauses to mapping_model.
8367 const std::string name =
8368 absl::GetFlag(FLAGS_cp_model_debug_postsolve) ? "sat_postsolver" : "";
8369 ExtractClauses(/*merge_into_bool_and=*/false, new_to_old_index,
8370 sat_postsolver, context_->mapping_model, name);
8371 return true;
8372}
8373
8374void CpModelPresolver::ShiftObjectiveWithExactlyOnes() {
8375 if (context_->ModelIsUnsat()) return;
8376
8377 // The objective is already loaded in the context, but we re-canonicalize
8378 // it with the latest information.
8379 if (!context_->CanonicalizeObjective()) {
8380 return;
8381 }
8382
8383 std::vector<int> exos;
8384 const int num_constraints = context_->working_model->constraints_size();
8385 for (int c = 0; c < num_constraints; ++c) {
8386 const ConstraintProto& ct = context_->working_model->constraints(c);
8387 if (!ct.enforcement_literal().empty()) continue;
8388 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8389 exos.push_back(c);
8390 }
8391 }
8392
8393 // This is not the same from what we do in ExpandObjective() because we do not
8394 // make the minimum cost zero but the second minimum. Note that when we do
8395 // that, we still do not degrade the trivial objective bound as we would if we
8396 // went any further.
8397 //
8398 // One reason why this might be beneficial is that it lower the maximum cost
8399 // magnitude, making more Booleans with the same cost and thus simplifying
8400 // the core optimizer job. I am not 100% sure.
8401 //
8402 // TODO(user): We need to loop a few time to reach a fixed point. Understand
8403 // exactly if there is a fixed-point and how to reach it in a nicer way.
8404 int num_shifts = 0;
8405 for (int i = 0; i < 3; ++i) {
8406 for (const int c : exos) {
8407 const ConstraintProto& ct = context_->working_model->constraints(c);
8408 const int num_terms = ct.exactly_one().literals().size();
8409 if (num_terms <= 1) continue;
8410 int64_t min_obj = std::numeric_limits<int64_t>::max();
8411 int64_t second_min = std::numeric_limits<int64_t>::max();
8412 for (int i = 0; i < num_terms; ++i) {
8413 const int literal = ct.exactly_one().literals(i);
8414 const int64_t var_obj = context_->ObjectiveCoeff(PositiveRef(literal));
8415 const int64_t obj = RefIsPositive(literal) ? var_obj : -var_obj;
8416 if (obj < min_obj) {
8417 second_min = min_obj;
8418 min_obj = obj;
8419 } else if (obj < second_min) {
8420 second_min = obj;
8421 }
8422 }
8423 if (second_min == 0) continue;
8424 ++num_shifts;
8425 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
8426 second_min)) {
8427 if (context_->ModelIsUnsat()) return;
8428 continue;
8429 }
8430 }
8431 }
8432 if (num_shifts > 0) {
8433 context_->UpdateRuleStats("objective: shifted cost with exactly ones",
8434 num_shifts);
8435 }
8436}
8437
8438bool CpModelPresolver::PropagateObjective() {
8439 if (!context_->working_model->has_objective()) return true;
8440 if (context_->ModelIsUnsat()) return false;
8441 context_->WriteObjectiveToProto();
8442
8443 int64_t min_activity = 0;
8444 int64_t max_variation = 0;
8445 const CpObjectiveProto& objective = context_->working_model->objective();
8446 const int num_terms = objective.vars().size();
8447 for (int i = 0; i < num_terms; ++i) {
8448 const int var = objective.vars(i);
8449 const int64_t coeff = objective.coeffs(i);
8450 CHECK(RefIsPositive(var));
8451 CHECK_NE(coeff, 0);
8452
8453 const int64_t domain_min = context_->MinOf(var);
8454 const int64_t domain_max = context_->MaxOf(var);
8455 if (coeff > 0) {
8456 min_activity += coeff * domain_min;
8457 } else {
8458 min_activity += coeff * domain_max;
8459 }
8460 const int64_t variation = std::abs(coeff) * (domain_max - domain_min);
8461 max_variation = std::max(max_variation, variation);
8462 }
8463
8464 // Infeasible ?
8465 const int64_t slack =
8466 CapSub(ReadDomainFromProto(objective).Max(), min_activity);
8467 if (slack < 0) {
8468 return context_->NotifyThatModelIsUnsat(
8469 "infeasible while propagating objective");
8470 }
8471
8472 // No propagation ?
8473 if (max_variation <= slack) return true;
8474
8475 int num_propagations = 0;
8476 for (int i = 0; i < num_terms; ++i) {
8477 const int var = objective.vars(i);
8478 const int64_t coeff = objective.coeffs(i);
8479 const int64_t domain_min = context_->MinOf(var);
8480 const int64_t domain_max = context_->MaxOf(var);
8481
8482 const int64_t new_diff = slack / std::abs(coeff);
8483 if (new_diff >= domain_max - domain_min) continue;
8484
8485 ++num_propagations;
8486 if (coeff > 0) {
8487 if (!context_->IntersectDomainWith(
8488 var, Domain(domain_min, domain_min + new_diff))) {
8489 return false;
8490 }
8491 } else {
8492 if (!context_->IntersectDomainWith(
8493 var, Domain(domain_max - new_diff, domain_max))) {
8494 return false;
8495 }
8496 }
8497 }
8498 CHECK_GT(num_propagations, 0);
8499
8500 context_->UpdateRuleStats("objective: restricted var domains by propagation",
8501 num_propagations);
8502 return true;
8503}
8504
8505// Expand the objective expression in some easy cases.
8506//
8507// The ideas is to look at all the "tight" equality constraints. These should
8508// give a topological order on the variable in which we can perform
8509// substitution.
8510//
8511// Basically, we will only use constraints of the form X' = sum ci * Xi' with ci
8512// > 0 and the variable X' being shifted version >= 0. Note that if there is a
8513// cycle with these constraints, all variables involved must be equal to each
8514// other and likely zero. Otherwise, we can express everything in terms of the
8515// leaves.
8516//
8517// This assumes we are more or less at the propagation fix point, even if we
8518// try to address cases where we are not.
8519void CpModelPresolver::ExpandObjective() {
8520 if (time_limit_->LimitReached()) return;
8521 if (context_->ModelIsUnsat()) return;
8522 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
8523
8524 // The objective is already loaded in the context, but we re-canonicalize
8525 // it with the latest information.
8526 if (!context_->CanonicalizeObjective()) {
8527 return;
8528 }
8529
8530 const int num_variables = context_->working_model->variables_size();
8531 const int num_constraints = context_->working_model->constraints_size();
8532
8533 // We consider two types of shifted variables (X - LB(X)) and (UB(X) - X).
8534 const auto get_index = [](int var, bool to_lb) {
8535 return 2 * var + (to_lb ? 0 : 1);
8536 };
8537 const auto get_lit_index = [](int lit) {
8538 return RefIsPositive(lit) ? 2 * lit : 2 * PositiveRef(lit) + 1;
8539 };
8540 const int num_nodes = 2 * num_variables;
8541 std::vector<std::vector<int>> index_graph(num_nodes);
8542
8543 // TODO(user): instead compute how much each constraint can be further
8544 // expanded?
8545 std::vector<int> index_to_best_c(num_nodes, -1);
8546 std::vector<int> index_to_best_size(num_nodes, 0);
8547
8548 // Lets see first if there are "tight" constraint and for which variables.
8549 // We stop processing constraint if we have too many entries.
8550 int num_entries = 0;
8551 int num_propagations = 0;
8552 int num_tight_variables = 0;
8553 int num_tight_constraints = 0;
8554 const int kNumEntriesThreshold = 1e8;
8555 for (int c = 0; c < num_constraints; ++c) {
8556 if (num_entries > kNumEntriesThreshold) break;
8557
8558 const ConstraintProto& ct = context_->working_model->constraints(c);
8559 if (!ct.enforcement_literal().empty()) continue;
8560
8561 // Deal with exactly one.
8562 // An exactly one is always tight on the upper bound of one term.
8563 //
8564 // Note(user): This code assume there is no fixed variable in the exactly
8565 // one. We thus make sure the constraint is re-presolved if for some reason
8566 // we didn't reach the fixed point before calling this code.
8567 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8568 if (PresolveExactlyOne(context_->working_model->mutable_constraints(c))) {
8569 context_->UpdateConstraintVariableUsage(c);
8570 }
8571 }
8572 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8573 const int num_terms = ct.exactly_one().literals().size();
8574 ++num_tight_constraints;
8575 num_tight_variables += num_terms;
8576 for (int i = 0; i < num_terms; ++i) {
8577 if (num_entries > kNumEntriesThreshold) break;
8578 const int neg_index = get_lit_index(ct.exactly_one().literals(i)) ^ 1;
8579
8580 const int old_c = index_to_best_c[neg_index];
8581 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
8582 index_to_best_c[neg_index] = c;
8583 index_to_best_size[neg_index] = num_terms;
8584 }
8585
8586 for (int j = 0; j < num_terms; ++j) {
8587 if (j == i) continue;
8588 const int other_index = get_lit_index(ct.exactly_one().literals(j));
8589 ++num_entries;
8590 index_graph[neg_index].push_back(other_index);
8591 }
8592 }
8593 continue;
8594 }
8595
8596 // Skip everything that is not a linear equality constraint.
8597 if (!IsLinearEqualityConstraint(ct)) continue;
8598
8599 // Let see for which variable is it "tight". We need a coeff of 1, and that
8600 // the implied bounds match exactly.
8601 const auto [min_activity, max_activity] =
8602 context_->ComputeMinMaxActivity(ct.linear());
8603
8604 bool is_tight = false;
8605 const int64_t rhs = ct.linear().domain(0);
8606 const int num_terms = ct.linear().vars_size();
8607 for (int i = 0; i < num_terms; ++i) {
8608 const int var = ct.linear().vars(i);
8609 const int64_t coeff = ct.linear().coeffs(i);
8610 if (std::abs(coeff) != 1) continue;
8611 if (num_entries > kNumEntriesThreshold) break;
8612
8613 const int index = get_index(var, coeff > 0);
8614
8615 const int64_t var_range = context_->MaxOf(var) - context_->MinOf(var);
8616 const int64_t implied_shifted_ub = rhs - min_activity;
8617 if (implied_shifted_ub <= var_range) {
8618 if (implied_shifted_ub < var_range) ++num_propagations;
8619 is_tight = true;
8620 ++num_tight_variables;
8621
8622 const int neg_index = index ^ 1;
8623 const int old_c = index_to_best_c[neg_index];
8624 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
8625 index_to_best_c[neg_index] = c;
8626 index_to_best_size[neg_index] = num_terms;
8627 }
8628
8629 for (int j = 0; j < num_terms; ++j) {
8630 if (j == i) continue;
8631 const int other_index =
8632 get_index(ct.linear().vars(j), ct.linear().coeffs(j) > 0);
8633 ++num_entries;
8634 index_graph[neg_index].push_back(other_index);
8635 }
8636 }
8637 const int64_t implied_shifted_lb = max_activity - rhs;
8638 if (implied_shifted_lb <= var_range) {
8639 if (implied_shifted_lb < var_range) ++num_propagations;
8640 is_tight = true;
8641 ++num_tight_variables;
8642
8643 const int old_c = index_to_best_c[index];
8644 if (old_c == -1 || num_terms > index_to_best_size[index]) {
8645 index_to_best_c[index] = c;
8646 index_to_best_size[index] = num_terms;
8647 }
8648
8649 for (int j = 0; j < num_terms; ++j) {
8650 if (j == i) continue;
8651 const int other_index =
8652 get_index(ct.linear().vars(j), ct.linear().coeffs(j) < 0);
8653 ++num_entries;
8654 index_graph[index].push_back(other_index);
8655 }
8656 }
8657 }
8658 if (is_tight) ++num_tight_constraints;
8659 }
8660
8661 // Note(user): We assume the fixed point was already reached by the linear
8662 // presolve, so we don't add extra code here for that. But we still abort if
8663 // some are left to cover corner cases were linear a still not propagated.
8664 if (num_propagations > 0) {
8665 context_->UpdateRuleStats("TODO objective: propagation possible!");
8666 return;
8667 }
8668
8669 // In most cases, we should have no cycle and thus a topo order.
8670 //
8671 // In case there is a cycle, then all member of a strongly connected component
8672 // must be equivalent, this is because from X to Y, if we follow the chain we
8673 // will have X = non_negative_sum + Y and Y = non_negative_sum + X.
8674 //
8675 // Moreover, many shifted variables will need to be zero once we start to have
8676 // equivalence.
8677 //
8678 // TODO(user): Make the fixing to zero? or at least when this happen redo
8679 // a presolve pass?
8680 //
8681 // TODO(user): Densify index to only look at variable that can be substituted
8682 // further.
8683 const auto topo_order = util::graph::FastTopologicalSort(index_graph);
8684 if (!topo_order.ok()) {
8685 // Tricky: We need to cache all domains to derive the proper relations.
8686 // This is because StoreAffineRelation() might propagate them.
8687 std::vector<int64_t> var_min(num_variables);
8688 std::vector<int64_t> var_max(num_variables);
8689 for (int var = 0; var < num_variables; ++var) {
8690 var_min[var] = context_->MinOf(var);
8691 var_max[var] = context_->MaxOf(var);
8692 }
8693
8694 std::vector<std::vector<int>> components;
8695 FindStronglyConnectedComponents(static_cast<int>(index_graph.size()),
8696 index_graph, &components);
8697 for (const std::vector<int>& compo : components) {
8698 if (compo.size() == 1) continue;
8699
8700 const int rep_var = compo[0] / 2;
8701 const bool rep_to_lp = (compo[0] % 2) == 0;
8702 for (int i = 1; i < compo.size(); ++i) {
8703 const int var = compo[i] / 2;
8704 const bool to_lb = (compo[i] % 2) == 0;
8705
8706 // (rep - rep_lb) | (rep_ub - rep) == (var - var_lb) | (var_ub - var)
8707 // +/- rep = +/- var + offset.
8708 const int64_t rep_coeff = rep_to_lp ? 1 : -1;
8709 const int64_t var_coeff = to_lb ? 1 : -1;
8710 const int64_t offset =
8711 (to_lb ? -var_min[var] : var_max[var]) -
8712 (rep_to_lp ? -var_min[rep_var] : var_max[rep_var]);
8713 if (!context_->StoreAffineRelation(rep_var, var, rep_coeff * var_coeff,
8714 rep_coeff * offset)) {
8715 return;
8716 }
8717 }
8718 context_->UpdateRuleStats("objective: detected equivalence",
8719 compo.size() - 1);
8720 }
8721 return;
8722 }
8723
8724 // If the removed variable is now unique, we could remove it if it is implied
8725 // free. But this should already be done by RemoveSingletonInLinear(), so we
8726 // don't redo it here.
8727 int num_expands = 0;
8728 int num_issues = 0;
8729 for (const int index : *topo_order) {
8730 if (index_graph[index].empty()) continue;
8731
8732 const int var = index / 2;
8733 const int64_t obj_coeff = context_->ObjectiveCoeff(var);
8734 if (obj_coeff == 0) continue;
8735
8736 const bool to_lb = (index % 2) == 0;
8737 if (obj_coeff > 0 == to_lb) {
8738 const ConstraintProto& ct =
8739 context_->working_model->constraints(index_to_best_c[index]);
8740 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8741 int64_t shift = 0;
8742 for (const int lit : ct.exactly_one().literals()) {
8743 if (PositiveRef(lit) == var) {
8744 shift = RefIsPositive(lit) ? obj_coeff : -obj_coeff;
8745 break;
8746 }
8747 }
8748 if (shift == 0) {
8749 ++num_issues;
8750 continue;
8751 }
8752 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
8753 shift)) {
8754 if (context_->ModelIsUnsat()) return;
8755 ++num_issues;
8756 continue;
8757 }
8758 CHECK_EQ(context_->ObjectiveCoeff(var), 0);
8759 ++num_expands;
8760 continue;
8761 }
8762
8763 int64_t objective_coeff_in_expanded_constraint = 0;
8764 const int num_terms = ct.linear().vars().size();
8765 for (int i = 0; i < num_terms; ++i) {
8766 if (ct.linear().vars(i) == var) {
8767 objective_coeff_in_expanded_constraint = ct.linear().coeffs(i);
8768 break;
8769 }
8770 }
8771 if (objective_coeff_in_expanded_constraint == 0) {
8772 ++num_issues;
8773 continue;
8774 }
8775
8776 if (!context_->SubstituteVariableInObjective(
8777 var, objective_coeff_in_expanded_constraint, ct)) {
8778 if (context_->ModelIsUnsat()) return;
8779 ++num_issues;
8780 continue;
8781 }
8782
8783 ++num_expands;
8784 }
8785 }
8786
8787 if (num_expands > 0) {
8788 context_->UpdateRuleStats("objective: expanded via tight equality",
8789 num_expands);
8790 }
8791
8792 timer.AddCounter("propagations", num_propagations);
8793 timer.AddCounter("entries", num_entries);
8794 timer.AddCounter("tight_variables", num_tight_variables);
8795 timer.AddCounter("tight_constraints", num_tight_constraints);
8796 timer.AddCounter("expands", num_expands);
8797 timer.AddCounter("issues", num_issues);
8798}
8799
8800void CpModelPresolver::MergeNoOverlapConstraints() {
8801 if (context_->ModelIsUnsat()) return;
8802 if (time_limit_->LimitReached()) return;
8803
8804 const int num_constraints = context_->working_model->constraints_size();
8805 int old_num_no_overlaps = 0;
8806 int old_num_intervals = 0;
8807
8808 // Extract the no-overlap constraints.
8809 std::vector<int> disjunctive_index;
8810 std::vector<std::vector<Literal>> cliques;
8811 for (int c = 0; c < num_constraints; ++c) {
8812 const ConstraintProto& ct = context_->working_model->constraints(c);
8813 if (ct.constraint_case() != ConstraintProto::kNoOverlap) continue;
8814 std::vector<Literal> clique;
8815 for (const int i : ct.no_overlap().intervals()) {
8816 clique.push_back(Literal(BooleanVariable(i), true));
8817 }
8818 cliques.push_back(clique);
8819 disjunctive_index.push_back(c);
8820
8821 old_num_no_overlaps++;
8822 old_num_intervals += clique.size();
8823 }
8824 if (old_num_no_overlaps == 0) return;
8825
8826 // We reuse the max-clique code from sat.
8827 Model local_model;
8828 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
8829 local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
8830 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
8831 graph->Resize(num_constraints);
8832 for (const std::vector<Literal>& clique : cliques) {
8833 // All variables at false is always a valid solution of the local model,
8834 // so this should never return UNSAT.
8835 CHECK(graph->AddAtMostOne(clique));
8836 }
8837 CHECK(graph->DetectEquivalences());
8838 graph->TransformIntoMaxCliques(
8839 &cliques,
8840 SafeDoubleToInt64(context_->params().merge_no_overlap_work_limit()));
8841
8842 // Replace each no-overlap with an extended version, or remove if empty.
8843 int new_num_no_overlaps = 0;
8844 int new_num_intervals = 0;
8845 for (int i = 0; i < cliques.size(); ++i) {
8846 const int ct_index = disjunctive_index[i];
8847 ConstraintProto* ct =
8848 context_->working_model->mutable_constraints(ct_index);
8849 ct->Clear();
8850 if (cliques[i].empty()) continue;
8851 for (const Literal l : cliques[i]) {
8852 CHECK(l.IsPositive());
8853 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
8854 }
8855 new_num_no_overlaps++;
8856 new_num_intervals += cliques[i].size();
8857 }
8858 if (old_num_intervals != new_num_intervals ||
8859 old_num_no_overlaps != new_num_no_overlaps) {
8860 VLOG(1) << absl::StrCat("Merged ", old_num_no_overlaps, " no-overlaps (",
8861 old_num_intervals, " intervals) into ",
8862 new_num_no_overlaps, " no-overlaps (",
8863 new_num_intervals, " intervals).");
8864 context_->UpdateRuleStats("no_overlap: merged constraints");
8865 }
8866 time_limit_->ResetHistory();
8867}
8868
8869// TODO(user): Should we take into account the exactly_one constraints? note
8870// that such constraint cannot be extended. If if a literal implies two literals
8871// at one inside an exactly one constraint then it must be false. Similarly if
8872// it implies all literals at zero inside the exactly one.
8873void CpModelPresolver::TransformIntoMaxCliques() {
8874 if (context_->ModelIsUnsat()) return;
8875 if (context_->params().merge_at_most_one_work_limit() <= 0.0) return;
8876
8877 auto convert = [](int ref) {
8878 if (RefIsPositive(ref)) return Literal(BooleanVariable(ref), true);
8879 return Literal(BooleanVariable(NegatedRef(ref)), false);
8880 };
8881 const int num_constraints = context_->working_model->constraints_size();
8882
8883 // Extract the bool_and and at_most_one constraints.
8884 // TODO(user): use probing info?
8885 std::vector<std::vector<Literal>> cliques;
8886
8887 for (int c = 0; c < num_constraints; ++c) {
8888 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8889 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
8890 std::vector<Literal> clique;
8891 for (const int ref : ct->at_most_one().literals()) {
8892 clique.push_back(convert(ref));
8893 }
8894 cliques.push_back(clique);
8895 if (RemoveConstraint(ct)) {
8896 context_->UpdateConstraintVariableUsage(c);
8897 }
8898 } else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
8899 if (ct->enforcement_literal().size() != 1) continue;
8900 const Literal enforcement = convert(ct->enforcement_literal(0));
8901 for (const int ref : ct->bool_and().literals()) {
8902 if (ref == ct->enforcement_literal(0)) continue;
8903 cliques.push_back({enforcement, convert(ref).Negated()});
8904 }
8905 if (RemoveConstraint(ct)) {
8906 context_->UpdateConstraintVariableUsage(c);
8907 }
8908 }
8909 }
8910
8911 int64_t num_literals_before = 0;
8912 const int num_old_cliques = cliques.size();
8913
8914 // We reuse the max-clique code from sat.
8915 Model local_model;
8916 const int num_variables = context_->working_model->variables().size();
8917 local_model.GetOrCreate<Trail>()->Resize(num_variables);
8918 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
8919 graph->Resize(num_variables);
8920 for (const std::vector<Literal>& clique : cliques) {
8921 num_literals_before += clique.size();
8922 if (!graph->AddAtMostOne(clique)) {
8923 return (void)context_->NotifyThatModelIsUnsat();
8924 }
8925 }
8926 if (!graph->DetectEquivalences()) {
8927 return (void)context_->NotifyThatModelIsUnsat();
8928 }
8929 graph->MergeAtMostOnes(
8930 absl::MakeSpan(cliques),
8931 SafeDoubleToInt64(context_->params().merge_at_most_one_work_limit()));
8932
8933 // Add the Boolean variable equivalence detected by DetectEquivalences().
8934 // Those are needed because TransformIntoMaxCliques() will replace all
8935 // variable by its representative.
8936 for (int var = 0; var < num_variables; ++var) {
8937 const Literal l = Literal(BooleanVariable(var), true);
8938 if (graph->RepresentativeOf(l) != l) {
8939 const Literal r = graph->RepresentativeOf(l);
8940 if (!context_->StoreBooleanEqualityRelation(
8941 var, r.IsPositive() ? r.Variable().value()
8942 : NegatedRef(r.Variable().value()))) {
8943 return;
8944 }
8945 }
8946 }
8947
8948 int num_new_cliques = 0;
8949 int64_t num_literals_after = 0;
8950 for (const std::vector<Literal>& clique : cliques) {
8951 if (clique.empty()) continue;
8952 num_new_cliques++;
8953 num_literals_after += clique.size();
8954 ConstraintProto* ct = context_->working_model->add_constraints();
8955 for (const Literal literal : clique) {
8956 if (literal.IsPositive()) {
8957 ct->mutable_at_most_one()->add_literals(literal.Variable().value());
8958 } else {
8959 ct->mutable_at_most_one()->add_literals(
8960 NegatedRef(literal.Variable().value()));
8961 }
8962 }
8963
8964 // Make sure we do not have duplicate variable reference.
8965 PresolveAtMostOne(ct);
8966 }
8967 context_->UpdateNewConstraintsVariableUsage();
8968 if (num_new_cliques != num_old_cliques) {
8969 context_->UpdateRuleStats("at_most_one: transformed into max clique.");
8970 }
8971
8972 if (num_old_cliques != num_new_cliques ||
8973 num_literals_before != num_literals_after) {
8974 SOLVER_LOG(logger_, "[MaxClique] Merged ", num_old_cliques, "(",
8975 num_literals_before, " literals) into ", num_new_cliques, "(",
8976 num_literals_after, " literals) at_most_ones.");
8977 }
8978}
8979
8981 if (context_->ModelIsUnsat()) return false;
8982 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8983
8984 // Generic presolve to exploit variable/literal equivalence.
8985 if (ExploitEquivalenceRelations(c, ct)) {
8986 context_->UpdateConstraintVariableUsage(c);
8987 }
8988
8989 // Generic presolve for reified constraint.
8990 if (PresolveEnforcementLiteral(ct)) {
8991 context_->UpdateConstraintVariableUsage(c);
8992 }
8993
8994 // Call the presolve function for this constraint if any.
8995 switch (ct->constraint_case()) {
8996 case ConstraintProto::kBoolOr:
8997 return PresolveBoolOr(ct);
8998 case ConstraintProto::kBoolAnd:
8999 return PresolveBoolAnd(ct);
9000 case ConstraintProto::kAtMostOne:
9001 return PresolveAtMostOne(ct);
9002 case ConstraintProto::kExactlyOne:
9003 return PresolveExactlyOne(ct);
9004 case ConstraintProto::kBoolXor:
9005 return PresolveBoolXor(ct);
9006 case ConstraintProto::kLinMax:
9007 if (CanonicalizeLinearArgument(*ct, ct->mutable_lin_max())) {
9008 context_->UpdateConstraintVariableUsage(c);
9009 }
9010 return PresolveLinMax(c, ct);
9011 case ConstraintProto::kIntProd:
9012 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_prod())) {
9013 context_->UpdateConstraintVariableUsage(c);
9014 }
9015 return PresolveIntProd(ct);
9016 case ConstraintProto::kIntDiv:
9017 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_div())) {
9018 context_->UpdateConstraintVariableUsage(c);
9019 }
9020 return PresolveIntDiv(c, ct);
9021 case ConstraintProto::kIntMod:
9022 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_mod())) {
9023 context_->UpdateConstraintVariableUsage(c);
9024 }
9025 return PresolveIntMod(c, ct);
9026 case ConstraintProto::kLinear: {
9027 bool changed = false;
9028 if (!CanonicalizeLinear(ct, &changed)) {
9029 return true;
9030 }
9031 if (changed) {
9032 context_->UpdateConstraintVariableUsage(c);
9033 }
9034 if (PropagateDomainsInLinear(c, ct)) {
9035 context_->UpdateConstraintVariableUsage(c);
9036 }
9037 if (PresolveSmallLinear(ct)) {
9038 context_->UpdateConstraintVariableUsage(c);
9039 }
9040 if (PresolveLinearEqualityWithModulo(ct)) {
9041 context_->UpdateConstraintVariableUsage(c);
9042 }
9043 // We first propagate the domains before calling this presolve rule.
9044 if (RemoveSingletonInLinear(ct)) {
9045 context_->UpdateConstraintVariableUsage(c);
9046
9047 // There is no need to re-do a propagation here, but the constraint
9048 // size might have been reduced.
9049 if (PresolveSmallLinear(ct)) {
9050 context_->UpdateConstraintVariableUsage(c);
9051 }
9052 }
9053 if (PresolveSmallLinear(ct)) {
9054 context_->UpdateConstraintVariableUsage(c);
9055 }
9056 if (PresolveLinearOnBooleans(ct)) {
9057 context_->UpdateConstraintVariableUsage(c);
9058 }
9059
9060 // If we extracted some enforcement, we redo some presolve.
9061 const int old_num_enforcement_literals = ct->enforcement_literal_size();
9062 ExtractEnforcementLiteralFromLinearConstraint(c, ct);
9063 if (context_->ModelIsUnsat()) return false;
9064 if (ct->enforcement_literal_size() > old_num_enforcement_literals) {
9065 if (DivideLinearByGcd(ct)) {
9066 context_->UpdateConstraintVariableUsage(c);
9067 }
9068 if (PresolveSmallLinear(ct)) {
9069 context_->UpdateConstraintVariableUsage(c);
9070 }
9071 }
9072
9073 if (PresolveDiophantine(ct)) {
9074 context_->UpdateConstraintVariableUsage(c);
9075 }
9076
9077 TryToReduceCoefficientsOfLinearConstraint(c, ct);
9078 return false;
9079 }
9080 case ConstraintProto::kInterval:
9081 return PresolveInterval(c, ct);
9082 case ConstraintProto::kInverse:
9083 return PresolveInverse(ct);
9084 case ConstraintProto::kElement:
9085 return PresolveElement(c, ct);
9086 case ConstraintProto::kTable:
9087 return PresolveTable(ct);
9088 case ConstraintProto::kAllDiff:
9089 return PresolveAllDiff(ct);
9090 case ConstraintProto::kNoOverlap:
9091 DetectDuplicateIntervals(c,
9092 ct->mutable_no_overlap()->mutable_intervals());
9093 return PresolveNoOverlap(ct);
9094 case ConstraintProto::kNoOverlap2D: {
9095 const bool changed = PresolveNoOverlap2D(c, ct);
9096 if (ct->constraint_case() == ConstraintProto::kNoOverlap2D) {
9097 // For 2D, we don't exploit index duplication between x/y so it is not
9098 // important to do it beforehand. Moreover in some situation
9099 // PresolveNoOverlap2D() remove a lot of interval, so better to do it
9100 // afterwards.
9101 DetectDuplicateIntervals(
9102 c, ct->mutable_no_overlap_2d()->mutable_x_intervals());
9103 DetectDuplicateIntervals(
9104 c, ct->mutable_no_overlap_2d()->mutable_y_intervals());
9105 }
9106 return changed;
9107 }
9108 case ConstraintProto::kCumulative:
9109 DetectDuplicateIntervals(c,
9110 ct->mutable_cumulative()->mutable_intervals());
9111 return PresolveCumulative(ct);
9112 case ConstraintProto::kCircuit:
9113 return PresolveCircuit(ct);
9114 case ConstraintProto::kRoutes:
9115 return PresolveRoutes(ct);
9116 case ConstraintProto::kAutomaton:
9117 return PresolveAutomaton(ct);
9118 case ConstraintProto::kReservoir:
9119 return PresolveReservoir(ct);
9120 default:
9121 return false;
9122 }
9123}
9124
9125// Returns false iff the model is UNSAT.
9126bool CpModelPresolver::ProcessSetPPCSubset(int subset_c, int superset_c,
9127 absl::flat_hash_set<int>* tmp_set,
9128 bool* remove_subset,
9129 bool* remove_superset,
9130 bool* stop_processing_superset) {
9131 ConstraintProto* subset_ct =
9132 context_->working_model->mutable_constraints(subset_c);
9133 ConstraintProto* superset_ct =
9134 context_->working_model->mutable_constraints(superset_c);
9135
9136 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
9137 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
9138 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
9139 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
9140 context_->UpdateRuleStats("setppc: bool_or in at_most_one.");
9141
9142 tmp_set->clear();
9143 if (subset_ct->constraint_case() == ConstraintProto::kBoolOr) {
9144 tmp_set->insert(subset_ct->bool_or().literals().begin(),
9145 subset_ct->bool_or().literals().end());
9146 } else {
9147 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
9148 subset_ct->exactly_one().literals().end());
9149 }
9150
9151 // Fix extras in superset_c to 0, note that these will be removed from the
9152 // constraint later.
9153 for (const int literal :
9154 superset_ct->constraint_case() == ConstraintProto::kAtMostOne
9155 ? superset_ct->at_most_one().literals()
9156 : superset_ct->exactly_one().literals()) {
9157 if (tmp_set->contains(literal)) continue;
9158 if (!context_->SetLiteralToFalse(literal)) return false;
9159 context_->UpdateRuleStats("setppc: fixed variables");
9160 }
9161
9162 // Change superset_c to exactly_one if not already.
9163 if (superset_ct->constraint_case() != ConstraintProto::kExactlyOne) {
9164 ConstraintProto copy = *superset_ct;
9165 (*superset_ct->mutable_exactly_one()->mutable_literals()) =
9166 copy.at_most_one().literals();
9167 }
9168
9169 *remove_subset = true;
9170 return true;
9171 }
9172
9173 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
9174 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
9175 superset_ct->constraint_case() == ConstraintProto::kBoolOr) {
9176 context_->UpdateRuleStats("setppc: removed dominated constraints");
9177 *remove_superset = true;
9178 return true;
9179 }
9180
9181 if (subset_ct->constraint_case() == ConstraintProto::kAtMostOne &&
9182 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
9183 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
9184 context_->UpdateRuleStats("setppc: removed dominated constraints");
9185 *remove_subset = true;
9186 return true;
9187 }
9188
9189 // Note(user): Only the exactly one should really be needed, the intersection
9190 // is taken care of by ProcessAtMostOneAndLinear() in a better way.
9191 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne &&
9192 superset_ct->constraint_case() == ConstraintProto::kLinear) {
9193 tmp_set->clear();
9194 int64_t min_sum = std::numeric_limits<int64_t>::max();
9195 int64_t max_sum = std::numeric_limits<int64_t>::min();
9196 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
9197 subset_ct->exactly_one().literals().end());
9198
9199 // Compute the min/max on the subset of the sum that correspond the exo.
9200 int num_matches = 0;
9201 temp_ct_.Clear();
9202 Domain reachable(0);
9203 std::vector<std::pair<int64_t, int>> coeff_counts;
9204 for (int i = 0; i < superset_ct->linear().vars().size(); ++i) {
9205 const int var = superset_ct->linear().vars(i);
9206 const int64_t coeff = superset_ct->linear().coeffs(i);
9207 if (tmp_set->contains(var)) {
9208 ++num_matches;
9209 min_sum = std::min(min_sum, coeff);
9210 max_sum = std::max(max_sum, coeff);
9211 coeff_counts.push_back({superset_ct->linear().coeffs(i), 1});
9212 } else {
9213 reachable =
9214 reachable
9215 .AdditionWith(
9216 context_->DomainOf(var).ContinuousMultiplicationBy(coeff))
9217 .RelaxIfTooComplex();
9218 temp_ct_.mutable_linear()->add_vars(var);
9219 temp_ct_.mutable_linear()->add_coeffs(coeff);
9220 }
9221 }
9222
9223 // If a linear constraint contains more than one at_most_one or exactly_one,
9224 // after processing one, we might no longer have an inclusion.
9225 //
9226 // TODO(user): If we have multiple disjoint inclusion, we can propagate
9227 // more. For instance on neos-1593097.mps we basically have a
9228 // weighted_sum_over_at_most_one1 >= weighted_sum_over_at_most_one2.
9229 if (num_matches != tmp_set->size()) return true;
9230 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne) {
9231 context_->UpdateRuleStats("setppc: exactly_one included in linear");
9232 } else {
9233 context_->UpdateRuleStats("setppc: at_most_one included in linear");
9234 }
9235
9236 reachable = reachable.AdditionWith(Domain(min_sum, max_sum));
9237 const Domain superset_rhs = ReadDomainFromProto(superset_ct->linear());
9238 if (reachable.IsIncludedIn(superset_rhs)) {
9239 // The constraint is trivial !
9240 context_->UpdateRuleStats("setppc: removed trivial linear constraint");
9241 *remove_superset = true;
9242 return true;
9243 }
9244 if (reachable.IntersectionWith(superset_rhs).IsEmpty()) {
9245 // TODO(user): constraint might become bool_or.
9246 context_->UpdateRuleStats("setppc: removed infeasible linear constraint");
9247 *stop_processing_superset = true;
9248 return MarkConstraintAsFalse(superset_ct);
9249 }
9250
9251 // We reuse the normal linear constraint code to propagate domains of
9252 // the other variable using the inclusion information.
9253 if (superset_ct->enforcement_literal().empty()) {
9254 CHECK_GT(num_matches, 0);
9255 FillDomainInProto(ReadDomainFromProto(superset_ct->linear())
9256 .AdditionWith(Domain(-max_sum, -min_sum)),
9257 temp_ct_.mutable_linear());
9258 PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_);
9259 }
9260
9261 // If we have an exactly one in a linear, we can shift the coefficients of
9262 // all these variables by any constant value. We select a value that reduces
9263 // the number of terms the most.
9264 std::sort(coeff_counts.begin(), coeff_counts.end());
9265 int new_size = 0;
9266 for (int i = 0; i < coeff_counts.size(); ++i) {
9267 if (new_size > 0 &&
9268 coeff_counts[i].first == coeff_counts[new_size - 1].first) {
9269 coeff_counts[new_size - 1].second++;
9270 continue;
9271 }
9272 coeff_counts[new_size++] = coeff_counts[i];
9273 }
9274 coeff_counts.resize(new_size);
9275 int64_t best = 0;
9276 int64_t best_count = 0;
9277 for (const auto [coeff, count] : coeff_counts) {
9278 if (count > best_count) {
9279 best = coeff;
9280 best_count = count;
9281 }
9282 }
9283 if (best != 0) {
9284 LinearConstraintProto new_ct = superset_ct->linear();
9285 int new_size = 0;
9286 for (int i = 0; i < new_ct.vars().size(); ++i) {
9287 const int var = new_ct.vars(i);
9288 int64_t coeff = new_ct.coeffs(i);
9289 if (tmp_set->contains(var)) {
9290 if (coeff == best) continue; // delete term.
9291 coeff -= best;
9292 }
9293 new_ct.set_vars(new_size, var);
9294 new_ct.set_coeffs(new_size, coeff);
9295 ++new_size;
9296 }
9297
9298 new_ct.mutable_vars()->Truncate(new_size);
9299 new_ct.mutable_coeffs()->Truncate(new_size);
9300 FillDomainInProto(ReadDomainFromProto(new_ct).AdditionWith(Domain(-best)),
9301 &new_ct);
9302 if (!PossibleIntegerOverflow(*context_->working_model, new_ct.vars(),
9303 new_ct.coeffs())) {
9304 *superset_ct->mutable_linear() = std::move(new_ct);
9305 context_->UpdateConstraintVariableUsage(superset_c);
9306 context_->UpdateRuleStats("setppc: reduced linear coefficients");
9307 }
9308 }
9309
9310 return true;
9311 }
9312
9313 // We can't deduce anything in the last remaining cases, like an at most one
9314 // in an at least one.
9315 return true;
9316}
9317
9318// TODO(user): TransformIntoMaxCliques() convert the bool_and to
9319// at_most_one, but maybe also duplicating them into bool_or would allow this
9320// function to do more presolving.
9321void CpModelPresolver::ProcessSetPPC() {
9322 if (time_limit_->LimitReached()) return;
9323 if (context_->ModelIsUnsat()) return;
9324 if (context_->params().presolve_inclusion_work_limit() == 0) return;
9325 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9326
9327 // TODO(user): compute on the fly instead of temporary storing variables?
9328 CompactVectorVector<int> storage;
9329 InclusionDetector detector(storage, time_limit_);
9330 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
9331
9332 // We use an encoding of literal that allows to index arrays.
9333 std::vector<int> temp_literals;
9334 const int num_constraints = context_->working_model->constraints_size();
9335 std::vector<int> relevant_constraints;
9336 for (int c = 0; c < num_constraints; ++c) {
9337 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9338 const auto type = ct->constraint_case();
9339 if (type == ConstraintProto::kBoolOr ||
9340 type == ConstraintProto::kAtMostOne ||
9341 type == ConstraintProto::kExactlyOne) {
9342 // Because TransformIntoMaxCliques() can detect literal equivalence
9343 // relation, we make sure the constraints are presolved before being
9344 // inspected.
9345 if (PresolveOneConstraint(c)) {
9346 context_->UpdateConstraintVariableUsage(c);
9347 }
9348 if (context_->ModelIsUnsat()) return;
9349
9350 temp_literals.clear();
9351 for (const int ref :
9352 type == ConstraintProto::kAtMostOne ? ct->at_most_one().literals()
9353 : type == ConstraintProto::kBoolOr ? ct->bool_or().literals()
9354 : ct->exactly_one().literals()) {
9355 temp_literals.push_back(
9356 Literal(BooleanVariable(PositiveRef(ref)), RefIsPositive(ref))
9357 .Index()
9358 .value());
9359 }
9360 relevant_constraints.push_back(c);
9361 detector.AddPotentialSet(storage.Add(temp_literals));
9362 } else if (type == ConstraintProto::kLinear) {
9363 // We also want to test inclusion with the pseudo-Boolean part of
9364 // linear constraints of size at least 3. Exactly one of size two are
9365 // equivalent literals, and we already deal with this case.
9366 //
9367 // TODO(user): This is not ideal as we currently only process exactly one
9368 // included into linear, and we add overhead by detecting all the other
9369 // cases that we ignore later. That said, we could just propagate a bit
9370 // more the domain if we know at_least_one or at_most_one between literals
9371 // in a linear constraint.
9372 const int size = ct->linear().vars().size();
9373 if (size <= 2) continue;
9374
9375 // TODO(user): We only deal with positive var here. Ideally we should
9376 // match the VARIABLES of the at_most_one/exactly_one with the VARIABLES
9377 // of the linear, and complement all variable to have a literal inclusion.
9378 temp_literals.clear();
9379 for (int i = 0; i < size; ++i) {
9380 const int var = ct->linear().vars(i);
9381 if (!context_->CanBeUsedAsLiteral(var)) continue;
9382 if (!RefIsPositive(var)) continue;
9383 temp_literals.push_back(
9384 Literal(BooleanVariable(var), true).Index().value());
9385 }
9386 if (temp_literals.size() > 2) {
9387 // Note that we only care about the linear being the superset.
9388 relevant_constraints.push_back(c);
9389 detector.AddPotentialSuperset(storage.Add(temp_literals));
9390 }
9391 }
9392 }
9393
9394 absl::flat_hash_set<int> tmp_set;
9395 int64_t num_inclusions = 0;
9396 detector.DetectInclusions([&](int subset, int superset) {
9397 ++num_inclusions;
9398 bool remove_subset = false;
9399 bool remove_superset = false;
9400 bool stop_processing_superset = false;
9401 const int subset_c = relevant_constraints[subset];
9402 const int superset_c = relevant_constraints[superset];
9403 detector.IncreaseWorkDone(storage[subset].size());
9404 detector.IncreaseWorkDone(storage[superset].size());
9405 if (!ProcessSetPPCSubset(subset_c, superset_c, &tmp_set, &remove_subset,
9406 &remove_superset, &stop_processing_superset)) {
9407 detector.Stop();
9408 return;
9409 }
9410 if (remove_subset) {
9411 context_->working_model->mutable_constraints(subset_c)->Clear();
9412 context_->UpdateConstraintVariableUsage(subset_c);
9413 detector.StopProcessingCurrentSubset();
9414 }
9415 if (remove_superset) {
9416 context_->working_model->mutable_constraints(superset_c)->Clear();
9417 context_->UpdateConstraintVariableUsage(superset_c);
9418 detector.StopProcessingCurrentSuperset();
9419 }
9420 if (stop_processing_superset) {
9421 context_->UpdateConstraintVariableUsage(superset_c);
9422 detector.StopProcessingCurrentSuperset();
9423 }
9424 });
9425
9426 timer.AddToWork(detector.work_done() * 1e-9);
9427 timer.AddCounter("relevant_constraints", relevant_constraints.size());
9428 timer.AddCounter("num_inclusions", num_inclusions);
9429}
9430
9431void CpModelPresolver::DetectIncludedEnforcement() {
9432 if (time_limit_->LimitReached()) return;
9433 if (context_->ModelIsUnsat()) return;
9434 if (context_->params().presolve_inclusion_work_limit() == 0) return;
9435 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9436
9437 // TODO(user): compute on the fly instead of temporary storing variables?
9438 std::vector<int> relevant_constraints;
9439 CompactVectorVector<int> storage;
9440 InclusionDetector detector(storage, time_limit_);
9441 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
9442
9443 std::vector<int> temp_literals;
9444 const int num_constraints = context_->working_model->constraints_size();
9445 for (int c = 0; c < num_constraints; ++c) {
9446 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9447 if (ct->enforcement_literal().size() <= 1) continue;
9448
9449 // Make sure there is no x => x.
9450 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
9451 if (PresolveOneConstraint(c)) {
9452 context_->UpdateConstraintVariableUsage(c);
9453 }
9454 if (context_->ModelIsUnsat()) return;
9455 }
9456
9457 // We use an encoding of literal that allows to index arrays.
9458 temp_literals.clear();
9459 for (const int ref : ct->enforcement_literal()) {
9460 temp_literals.push_back(
9461 Literal(BooleanVariable(PositiveRef(ref)), RefIsPositive(ref))
9462 .Index()
9463 .value());
9464 }
9465 relevant_constraints.push_back(c);
9466
9467 // We only deal with bool_and included in other. Not the other way around,
9468 // Altough linear enforcement included in bool_and does happen.
9469 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
9470 detector.AddPotentialSet(storage.Add(temp_literals));
9471 } else {
9472 detector.AddPotentialSuperset(storage.Add(temp_literals));
9473 }
9474 }
9475
9476 int64_t num_inclusions = 0;
9477 detector.DetectInclusions([&](int subset, int superset) {
9478 ++num_inclusions;
9479 const int subset_c = relevant_constraints[subset];
9480 const int superset_c = relevant_constraints[superset];
9481 ConstraintProto* subset_ct =
9482 context_->working_model->mutable_constraints(subset_c);
9483 ConstraintProto* superset_ct =
9484 context_->working_model->mutable_constraints(superset_c);
9485 if (subset_ct->constraint_case() != ConstraintProto::kBoolAnd) return;
9486
9487 context_->tmp_literal_set.clear();
9488 for (const int ref : subset_ct->bool_and().literals()) {
9489 context_->tmp_literal_set.insert(ref);
9490 }
9491
9492 // Filter superset enforcement.
9493 {
9494 int new_size = 0;
9495 for (const int ref : superset_ct->enforcement_literal()) {
9496 if (context_->tmp_literal_set.contains(ref)) {
9497 context_->UpdateRuleStats("bool_and: filtered enforcement");
9498 } else if (context_->tmp_literal_set.contains(NegatedRef(ref))) {
9499 context_->UpdateRuleStats("bool_and: never enforced");
9500 superset_ct->Clear();
9501 context_->UpdateConstraintVariableUsage(superset_c);
9502 detector.StopProcessingCurrentSuperset();
9503 return;
9504 } else {
9505 superset_ct->set_enforcement_literal(new_size++, ref);
9506 }
9507 }
9508 if (new_size < superset_ct->bool_and().literals().size()) {
9509 context_->UpdateConstraintVariableUsage(superset_c);
9510 superset_ct->mutable_enforcement_literal()->Truncate(new_size);
9511 }
9512 }
9513
9514 if (superset_ct->constraint_case() == ConstraintProto::kBoolAnd) {
9515 int new_size = 0;
9516 for (const int ref : superset_ct->bool_and().literals()) {
9517 if (context_->tmp_literal_set.contains(ref)) {
9518 context_->UpdateRuleStats("bool_and: filtered literal");
9519 } else if (context_->tmp_literal_set.contains(NegatedRef(ref))) {
9520 context_->UpdateRuleStats("bool_and: must be false");
9521 if (!MarkConstraintAsFalse(superset_ct)) return;
9522 context_->UpdateConstraintVariableUsage(superset_c);
9523 detector.StopProcessingCurrentSuperset();
9524 return;
9525 } else {
9526 superset_ct->mutable_bool_and()->set_literals(new_size++, ref);
9527 }
9528 }
9529 if (new_size < superset_ct->bool_and().literals().size()) {
9530 context_->UpdateConstraintVariableUsage(superset_c);
9531 superset_ct->mutable_bool_and()->mutable_literals()->Truncate(new_size);
9532 }
9533 }
9534
9535 if (superset_ct->constraint_case() == ConstraintProto::kLinear) {
9536 context_->UpdateRuleStats("TODO bool_and enforcement in linear enf");
9537 }
9538 });
9539
9540 timer.AddToWork(1e-9 * static_cast<double>(detector.work_done()));
9541 timer.AddCounter("relevant_constraints", relevant_constraints.size());
9542 timer.AddCounter("num_inclusions", num_inclusions);
9543}
9544
9545// Note that because we remove the linear constraint, this will not be called
9546// often, so it is okay to use "heavy" data structure here.
9547//
9548// TODO(user): in the at most one case, consider always creating an associated
9549// literal (l <=> var == rhs), and add the exactly_one = at_most_one U not(l)?
9550// This constraint is implicit from what we create, however internally we will
9551// not recover it easily, so we might not add the linear relaxation
9552// corresponding to the constraint we just removed.
9553bool CpModelPresolver::ProcessEncodingFromLinear(
9554 const int linear_encoding_ct_index,
9555 const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms,
9556 int64_t* num_multiple_terms) {
9557 // Preprocess exactly or at most one.
9558 bool in_exactly_one = false;
9559 absl::flat_hash_map<int, int> var_to_ref;
9560 if (at_most_or_exactly_one.constraint_case() == ConstraintProto::kAtMostOne) {
9561 for (const int ref : at_most_or_exactly_one.at_most_one().literals()) {
9562 CHECK(!var_to_ref.contains(PositiveRef(ref)));
9563 var_to_ref[PositiveRef(ref)] = ref;
9564 }
9565 } else {
9566 CHECK_EQ(at_most_or_exactly_one.constraint_case(),
9567 ConstraintProto::kExactlyOne);
9568 in_exactly_one = true;
9569 for (const int ref : at_most_or_exactly_one.exactly_one().literals()) {
9570 CHECK(!var_to_ref.contains(PositiveRef(ref)));
9571 var_to_ref[PositiveRef(ref)] = ref;
9572 }
9573 }
9574
9575 // Preprocess the linear constraints.
9576 const ConstraintProto& linear_encoding =
9577 context_->working_model->constraints(linear_encoding_ct_index);
9578 int64_t rhs = linear_encoding.linear().domain(0);
9579 int target_ref = std::numeric_limits<int>::min();
9580 std::vector<std::pair<int, int64_t>> ref_to_coeffs;
9581 const int num_terms = linear_encoding.linear().vars().size();
9582 for (int i = 0; i < num_terms; ++i) {
9583 const int ref = linear_encoding.linear().vars(i);
9584 const int64_t coeff = linear_encoding.linear().coeffs(i);
9585 const auto it = var_to_ref.find(PositiveRef(ref));
9586
9587 if (it == var_to_ref.end()) {
9588 CHECK_EQ(target_ref, std::numeric_limits<int>::min()) << "Uniqueness";
9589 CHECK_EQ(std::abs(coeff), 1);
9590 target_ref = coeff == 1 ? ref : NegatedRef(ref);
9591 continue;
9592 }
9593
9594 // We transform the constraint so that the Boolean reference match exactly
9595 // what is in the at most one.
9596 if (it->second == ref) {
9597 // The term in the constraint is the same as in the at_most_one.
9598 ref_to_coeffs.push_back({ref, coeff});
9599 } else {
9600 // We replace "coeff * ref" by "coeff - coeff * (1 - ref)"
9601 rhs -= coeff;
9602 ref_to_coeffs.push_back({NegatedRef(ref), -coeff});
9603 }
9604 }
9605 if (target_ref == std::numeric_limits<int>::min() ||
9606 context_->CanBeUsedAsLiteral(target_ref)) {
9607 // We didn't find the unique integer variable. This might have happenned
9608 // because by processing other encoding we might end up with a fully boolean
9609 // constraint. Just abort, it will be presolved later.
9610 context_->UpdateRuleStats("encoding: candidate linear is all Boolean now.");
9611 return true;
9612 }
9613
9614 // Extract the encoding.
9615 std::vector<int64_t> all_values;
9616 absl::btree_map<int64_t, std::vector<int>> value_to_refs;
9617 for (const auto& [ref, coeff] : ref_to_coeffs) {
9618 const int64_t value = rhs - coeff;
9619 all_values.push_back(value);
9620 value_to_refs[value].push_back(ref);
9621 var_to_ref.erase(PositiveRef(ref));
9622 }
9623 // The one not used "encodes" the rhs value.
9624 for (const auto& [var, ref] : var_to_ref) {
9625 all_values.push_back(rhs);
9626 value_to_refs[rhs].push_back(ref);
9627 }
9628 if (!in_exactly_one) {
9629 // To cover the corner case when the inclusion is an equality. For an at
9630 // most one, the rhs should be always reachable when all Boolean are false.
9631 all_values.push_back(rhs);
9632 }
9633
9634 // Make sure the target domain is up to date.
9635 const Domain new_domain = Domain::FromValues(all_values);
9636 bool domain_reduced = false;
9637 if (!context_->IntersectDomainWith(target_ref, new_domain, &domain_reduced)) {
9638 return false;
9639 }
9640 if (domain_reduced) {
9641 context_->UpdateRuleStats("encoding: reduced target domain");
9642 }
9643
9644 if (context_->CanBeUsedAsLiteral(target_ref)) {
9645 // If target is now a literal, lets not process it here.
9646 context_->UpdateRuleStats("encoding: candidate linear is all Boolean now.");
9647 return true;
9648 }
9649
9650 // Encode the encoding.
9651 absl::flat_hash_set<int64_t> value_set;
9652 for (const int64_t v : context_->DomainOf(target_ref).Values()) {
9653 value_set.insert(v);
9654 }
9655 for (auto& [value, literals] : value_to_refs) {
9656 // For determinism.
9657 absl::c_sort(literals);
9658
9659 // If the value is not in the domain, just set all literal to false.
9660 if (!value_set.contains(value)) {
9661 for (const int lit : literals) {
9662 if (!context_->SetLiteralToFalse(lit)) return false;
9663 }
9664 continue;
9665 }
9666
9667 if (literals.size() == 1 && (in_exactly_one || value != rhs)) {
9668 // Optimization if there is just one literal for this value.
9669 // Note that for the "at most one" case, we can't do that for the rhs.
9670 ++*num_unique_terms;
9671 if (!context_->InsertVarValueEncoding(literals[0], target_ref, value)) {
9672 return false;
9673 }
9674 } else {
9675 ++*num_multiple_terms;
9676 const int associated_lit =
9677 context_->GetOrCreateVarValueEncoding(target_ref, value);
9678 for (const int lit : literals) {
9679 context_->AddImplication(lit, associated_lit);
9680 }
9681
9682 // All false means associated_lit is false too.
9683 // But not for the rhs case if we are not in exactly one.
9684 if (in_exactly_one || value != rhs) {
9685 // TODO(user): Instead of bool_or + implications, we could add an
9686 // exactly one! Experiment with this. In particular it might capture
9687 // more structure for later heuristic to add the exactly one instead.
9688 // This also applies to automata/table/element expansion.
9689 auto* bool_or =
9690 context_->working_model->add_constraints()->mutable_bool_or();
9691 for (const int lit : literals) bool_or->add_literals(lit);
9692 bool_or->add_literals(NegatedRef(associated_lit));
9693 }
9694 }
9695 }
9696
9697 // Remove linear constraint now that it is fully encoded.
9698 context_->working_model->mutable_constraints(linear_encoding_ct_index)
9699 ->Clear();
9700 context_->UpdateNewConstraintsVariableUsage();
9701 context_->UpdateConstraintVariableUsage(linear_encoding_ct_index);
9702 return true;
9703}
9704
9707 CompactVectorVector<int, std::pair<int, int64_t>>* _column)
9708 : column(_column) {}
9709 std::size_t operator()(int c) const { return absl::HashOf((*column)[c]); }
9710
9712};
9713
9716 CompactVectorVector<int, std::pair<int, int64_t>>* _column)
9717 : column(_column) {}
9718 bool operator()(int a, int b) const {
9719 if (a == b) return true;
9720
9721 // We use absl::span<> comparison.
9722 return (*column)[a] == (*column)[b];
9723 }
9724
9726};
9727
9728// Note that our symmetry-detector will also identify full permutation group
9729// for these columns, but it is better to handle that even before. We can
9730// also detect variable with different domains but with indentical columns.
9732 if (time_limit_->LimitReached()) return;
9733 if (context_->ModelIsUnsat()) return;
9734 if (context_->params().keep_all_feasible_solutions_in_presolve()) return;
9735 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9736
9737 const int num_vars = context_->working_model->variables().size();
9738 const int num_constraints = context_->working_model->constraints().size();
9739
9740 // Our current implementation require almost a full copy.
9741 // First construct a transpose var to columns (constraint_index, coeff).
9742 std::vector<int> flat_vars;
9743 std::vector<std::pair<int, int64_t>> flat_terms;
9745
9746 // We will only support columns that include:
9747 // - objective
9748 // - linear (non-enforced part)
9749 // - at_most_one/exactly_one/clauses (but with positive variable only).
9750 //
9751 // TODO(user): deal with enforcement_literal, especially bool_and. It is a bit
9752 // annoying to have to deal with all kind of constraints. Maybe convert
9753 // bool_and to at_most_one first? We already do that in other places. Note
9754 // however that an at most one of size 2 means at most 2 columns can be
9755 // identical. If we have a bool and with many term on the left, all column
9756 // could be indentical, but we have to linearize the constraint first.
9757 std::vector<bool> appear_in_amo(num_vars, false);
9758 std::vector<bool> appear_in_bool_constraint(num_vars, false);
9759 for (int c = 0; c < num_constraints; ++c) {
9760 const ConstraintProto& ct = context_->working_model->constraints(c);
9761 absl::Span<const int> literals;
9762
9763 bool is_amo = false;
9764 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
9765 is_amo = true;
9766 literals = ct.at_most_one().literals();
9767 } else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
9768 is_amo = true; // That works here.
9769 literals = ct.exactly_one().literals();
9770 } else if (ct.constraint_case() == ConstraintProto::kBoolOr) {
9771 literals = ct.bool_or().literals();
9772 }
9773
9774 if (!literals.empty()) {
9775 for (const int lit : literals) {
9776 // It is okay to ignore terms (the columns will not be full).
9777 if (!RefIsPositive(lit)) continue;
9778 if (is_amo) appear_in_amo[lit] = true;
9779 appear_in_bool_constraint[lit] = true;
9780 flat_vars.push_back(lit);
9781 flat_terms.push_back({c, 1});
9782 }
9783 continue;
9784 }
9785
9786 if (ct.constraint_case() == ConstraintProto::kLinear) {
9787 const int num_terms = ct.linear().vars().size();
9788 for (int i = 0; i < num_terms; ++i) {
9789 const int var = ct.linear().vars(i);
9790 const int64_t coeff = ct.linear().coeffs(i);
9791 flat_vars.push_back(var);
9792 flat_terms.push_back({c, coeff});
9793 }
9794 continue;
9795 }
9796 }
9797
9798 // Use kObjectiveConstraint (-1) for the objective.
9799 //
9800 // TODO(user): deal with equivalent column with different objective value.
9801 // It might not be easy to presolve, but we can at least have a single
9802 // variable = sum of var appearing only in objective. And we can transfer the
9803 // min cost.
9804 if (context_->working_model->has_objective()) {
9805 context_->WriteObjectiveToProto();
9806 const int num_terms = context_->working_model->objective().vars().size();
9807 for (int i = 0; i < num_terms; ++i) {
9808 const int var = context_->working_model->objective().vars(i);
9809 const int64_t coeff = context_->working_model->objective().coeffs(i);
9810 flat_vars.push_back(var);
9811 flat_terms.push_back({kObjectiveConstraint, coeff});
9812 }
9813 }
9814
9815 // Now construct the graph.
9816 var_to_columns.ResetFromFlatMapping(flat_vars, flat_terms);
9817
9818 // Find duplicate columns using an hash map.
9819 // We only consider "full" columns.
9820 // var -> var_representative using columns hash/comparison.
9821 absl::flat_hash_map<int, int, ColumnHashForDuplicateDetection,
9823 duplicates(
9824 /*capacity=*/num_vars,
9825 ColumnHashForDuplicateDetection(&var_to_columns),
9826 ColumnEqForDuplicateDetection(&var_to_columns));
9827 std::vector<int> flat_duplicates;
9828 std::vector<int> flat_representatives;
9829 for (int var = 0; var < var_to_columns.size(); ++var) {
9830 const int size_seen = var_to_columns[var].size();
9831 if (size_seen == 0) continue;
9832 if (size_seen != context_->VarToConstraints(var).size()) continue;
9833
9834 // TODO(user): If we have duplicate columns appearing in Boolean constraint
9835 // we can only easily substitute if the sum of columns is a Boolean (i.e. if
9836 // it appear in an at most one or exactly one). Otherwise we will need to
9837 // transform such constraint to linear, do that?
9838 if (appear_in_bool_constraint[var] && !appear_in_amo[var]) {
9839 context_->UpdateRuleStats(
9840 "TODO duplicate: duplicate columns in Boolean constraints");
9841 continue;
9842 }
9843
9844 const auto [it, inserted] = duplicates.insert({var, var});
9845 if (!inserted) {
9846 flat_duplicates.push_back(var);
9847 flat_representatives.push_back(it->second);
9848 }
9849 }
9850
9851 // Process duplicates.
9852 int num_equivalent_classes = 0;
9854 rep_to_dups.ResetFromFlatMapping(flat_representatives, flat_duplicates);
9855 std::vector<std::pair<int, int64_t>> definition;
9856 std::vector<int> var_to_remove;
9857 std::vector<int> var_to_rep(num_vars, -1);
9858 for (int var = 0; var < rep_to_dups.size(); ++var) {
9859 if (rep_to_dups[var].empty()) continue;
9860
9861 // Since columns are the same, we can introduce a new variable = sum all
9862 // columns. Note that the linear expression will not overflow, but the
9863 // overflow check also requires that max_sum < int_max/2, which might
9864 // happen.
9865 //
9866 // In the corner case where there is a lot of holes in the domain, and the
9867 // sum domain is too complex, we skip. Hopefully this should be rare.
9868 definition.clear();
9869 definition.push_back({var, 1});
9870 Domain domain = context_->DomainOf(var);
9871 for (const int other_var : rep_to_dups[var]) {
9872 definition.push_back({other_var, 1});
9873 domain = domain.AdditionWith(context_->DomainOf(other_var));
9874 if (domain.NumIntervals() > 100) break;
9875 }
9876 if (domain.NumIntervals() > 100) {
9877 context_->UpdateRuleStats(
9878 "TODO duplicate: domain of the sum is too complex");
9879 continue;
9880 }
9881 if (appear_in_amo[var]) {
9882 domain = domain.IntersectionWith(Domain(0, 1));
9883 }
9884 const int new_var = context_->NewIntVarWithDefinition(
9885 domain, definition, /*append_constraint_to_mapping_model=*/true);
9886 if (new_var == -1) {
9887 context_->UpdateRuleStats("TODO duplicate: possible overflow");
9888 continue;
9889 }
9890
9891 var_to_remove.push_back(var);
9892 CHECK_EQ(var_to_rep[var], -1);
9893 var_to_rep[var] = new_var;
9894 for (const int other_var : rep_to_dups[var]) {
9895 var_to_remove.push_back(other_var);
9896 CHECK_EQ(var_to_rep[other_var], -1);
9897 var_to_rep[other_var] = new_var;
9898 }
9899
9900 // Deal with objective right away.
9901 const int64_t obj_coeff = context_->ObjectiveCoeff(var);
9902 if (obj_coeff != 0) {
9903 context_->RemoveVariableFromObjective(var);
9904 for (const int other_var : rep_to_dups[var]) {
9905 CHECK_EQ(context_->ObjectiveCoeff(other_var), obj_coeff);
9906 context_->RemoveVariableFromObjective(other_var);
9907 }
9908 context_->AddToObjective(new_var, obj_coeff);
9909 }
9910
9911 num_equivalent_classes++;
9912 }
9913
9914 // Lets rescan the model, and remove all variables, replacing them by
9915 // the sum. We do that in one O(model size) pass.
9916 if (!var_to_remove.empty()) {
9917 absl::flat_hash_set<int> seen;
9918 std::vector<std::pair<int, int64_t>> new_terms;
9919 for (int c = 0; c < num_constraints; ++c) {
9920 ConstraintProto* mutable_ct =
9921 context_->working_model->mutable_constraints(c);
9922
9923 seen.clear();
9924 new_terms.clear();
9925
9926 // Deal with bool case.
9927 // TODO(user): maybe converting to linear + single code is better?
9928 BoolArgumentProto* mutable_arg = nullptr;
9929 if (mutable_ct->constraint_case() == ConstraintProto::kAtMostOne) {
9930 mutable_arg = mutable_ct->mutable_at_most_one();
9931 } else if (mutable_ct->constraint_case() ==
9932 ConstraintProto::kExactlyOne) {
9933 mutable_arg = mutable_ct->mutable_exactly_one();
9934 } else if (mutable_ct->constraint_case() == ConstraintProto::kBoolOr) {
9935 mutable_arg = mutable_ct->mutable_bool_or();
9936 }
9937 if (mutable_arg != nullptr) {
9938 int new_size = 0;
9939 const int num_terms = mutable_arg->literals().size();
9940 for (int i = 0; i < num_terms; ++i) {
9941 const int lit = mutable_arg->literals(i);
9942 const int rep = var_to_rep[PositiveRef(lit)];
9943 if (rep != -1) {
9944 CHECK(RefIsPositive(lit));
9945 const auto [_, inserted] = seen.insert(rep);
9946 if (inserted) new_terms.push_back({rep, 1});
9947 continue;
9948 }
9949 mutable_arg->set_literals(new_size, lit);
9950 ++new_size;
9951 }
9952 if (new_size == num_terms) continue; // skip.
9953
9954 // TODO(user): clear amo/exo of size 1.
9955 mutable_arg->mutable_literals()->Truncate(new_size);
9956 for (const auto [var, coeff] : new_terms) {
9957 mutable_arg->add_literals(var);
9958 }
9959 context_->UpdateConstraintVariableUsage(c);
9960 continue;
9961 }
9962
9963 // Deal with linear case.
9964 if (mutable_ct->constraint_case() == ConstraintProto::kLinear) {
9965 int new_size = 0;
9966 LinearConstraintProto* mutable_linear = mutable_ct->mutable_linear();
9967 const int num_terms = mutable_linear->vars().size();
9968 for (int i = 0; i < num_terms; ++i) {
9969 const int var = mutable_linear->vars(i);
9970 const int64_t coeff = mutable_linear->coeffs(i);
9971 const int rep = var_to_rep[var];
9972 if (rep != -1) {
9973 const auto [_, inserted] = seen.insert(rep);
9974 if (inserted) new_terms.push_back({rep, coeff});
9975 continue;
9976 }
9977 mutable_linear->set_vars(new_size, var);
9978 mutable_linear->set_coeffs(new_size, coeff);
9979 ++new_size;
9980 }
9981 if (new_size == num_terms) continue; // skip.
9982
9983 mutable_linear->mutable_vars()->Truncate(new_size);
9984 mutable_linear->mutable_coeffs()->Truncate(new_size);
9985 for (const auto [var, coeff] : new_terms) {
9986 mutable_linear->add_vars(var);
9987 mutable_linear->add_coeffs(coeff);
9988 }
9989 context_->UpdateConstraintVariableUsage(c);
9990 continue;
9991 }
9992 }
9993 }
9994
9995 // We removed all occurrence of "var_to_remove" so we can remove them now.
9996 // Note that since we introduce a new variable per equivalence class, we
9997 // remove one less for each equivalent class.
9998 const int num_var_reduction = var_to_remove.size() - num_equivalent_classes;
9999 for (const int var : var_to_remove) {
10000 CHECK(context_->VarToConstraints(var).empty());
10001 context_->MarkVariableAsRemoved(var);
10002 }
10003 if (num_var_reduction > 0) {
10004 context_->UpdateRuleStats("duplicate: removed duplicated column",
10005 num_var_reduction);
10006 }
10007
10008 timer.AddCounter("num_equiv_classes", num_equivalent_classes);
10009 timer.AddCounter("num_removed_vars", num_var_reduction);
10010}
10011
10012void CpModelPresolver::DetectDuplicateConstraints() {
10013 if (time_limit_->LimitReached()) return;
10014 if (context_->ModelIsUnsat()) return;
10015 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10016
10017 // We need the objective written for this.
10018 if (context_->working_model->has_objective()) {
10019 if (!context_->CanonicalizeObjective()) return;
10020 context_->WriteObjectiveToProto();
10021 }
10022
10023 // If we detect duplicate intervals, we will remap constraints using them.
10024 std::vector<int> interval_mapping;
10025
10026 // Remove duplicate constraints.
10027 // Note that at this point the objective in the proto should be up to date.
10028 //
10029 // TODO(user): We might want to do that earlier so that our count of variable
10030 // usage is not biased by duplicate constraints.
10031 const std::vector<std::pair<int, int>> duplicates =
10033 timer.AddCounter("duplicates", duplicates.size());
10034 for (const auto& [dup, rep] : duplicates) {
10035 // Note that it is important to look at the type of the representative in
10036 // case the constraint became empty.
10037 DCHECK_LT(kObjectiveConstraint, 0);
10038 const int type =
10041 : context_->working_model->constraints(rep).constraint_case();
10042
10043 if (type == ConstraintProto::kInterval) {
10044 interval_mapping.resize(context_->working_model->constraints().size(),
10045 -1);
10046 CHECK_EQ(interval_mapping[rep], -1);
10047 interval_mapping[dup] = rep;
10048 }
10049
10050 // For linear constraint, we merge their rhs since it was ignored in the
10051 // FindDuplicateConstraints() call.
10052 if (type == ConstraintProto::kLinear) {
10053 const Domain rep_domain = ReadDomainFromProto(
10054 context_->working_model->constraints(rep).linear());
10055 const Domain d = ReadDomainFromProto(
10056 context_->working_model->constraints(dup).linear());
10057 if (rep_domain != d) {
10058 context_->UpdateRuleStats("duplicate: merged rhs of linear constraint");
10059 const Domain rhs = rep_domain.IntersectionWith(d);
10060 if (rhs.IsEmpty()) {
10061 if (!MarkConstraintAsFalse(
10062 context_->working_model->mutable_constraints(rep))) {
10063 SOLVER_LOG(logger_, "Unsat after merging two linear constraints");
10064 return;
10065 }
10066
10067 // The representative constraint is no longer a linear constraint,
10068 // so we will not enter this type case again and will just remove
10069 // all subsequent duplicate linear constraints.
10070 context_->UpdateConstraintVariableUsage(rep);
10071 continue;
10072 }
10073 FillDomainInProto(rhs, context_->working_model->mutable_constraints(rep)
10074 ->mutable_linear());
10075 }
10076 }
10077
10078 if (type == kObjectiveConstraint) {
10079 context_->UpdateRuleStats(
10080 "duplicate: linear constraint parallel to objective");
10081 const Domain objective_domain =
10082 ReadDomainFromProto(context_->working_model->objective());
10083 const Domain d = ReadDomainFromProto(
10084 context_->working_model->constraints(dup).linear());
10085 if (objective_domain != d) {
10086 context_->UpdateRuleStats("duplicate: updated objective domain");
10087 const Domain new_domain = objective_domain.IntersectionWith(d);
10088 if (new_domain.IsEmpty()) {
10089 return (void)context_->NotifyThatModelIsUnsat(
10090 "Constraint parallel to the objective makes the objective domain "
10091 "empty.");
10092 }
10093 FillDomainInProto(new_domain,
10094 context_->working_model->mutable_objective());
10095
10096 // TODO(user): this write/read is a bit unclean, but needed.
10097 context_->ReadObjectiveFromProto();
10098 }
10099 }
10100
10101 // Remove the duplicate constraint.
10102 context_->working_model->mutable_constraints(dup)->Clear();
10103 context_->UpdateConstraintVariableUsage(dup);
10104 context_->UpdateRuleStats("duplicate: removed constraint");
10105 }
10106
10107 if (!interval_mapping.empty()) {
10108 context_->UpdateRuleStats("duplicate: remapped duplicate intervals");
10109 const int num_constraints = context_->working_model->constraints().size();
10110 for (int c = 0; c < num_constraints; ++c) {
10111 bool changed = false;
10113 [&interval_mapping, &changed](int* ref) {
10114 const int new_ref = interval_mapping[*ref];
10115 if (new_ref != -1) {
10116 changed = true;
10117 *ref = new_ref;
10118 }
10119 },
10120 context_->working_model->mutable_constraints(c));
10121 if (changed) context_->UpdateConstraintVariableUsage(c);
10122 }
10123 }
10124}
10125
10126void CpModelPresolver::DetectDuplicateConstraintsWithDifferentEnforcements(
10127 const CpModelMapping* mapping, BinaryImplicationGraph* implication_graph,
10128 Trail* trail) {
10129 if (time_limit_->LimitReached()) return;
10130 if (context_->ModelIsUnsat()) return;
10131 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10132
10133 // We need the objective written for this.
10134 if (context_->working_model->has_objective()) {
10135 if (!context_->CanonicalizeObjective()) return;
10136 context_->WriteObjectiveToProto();
10137 }
10138
10139 absl::flat_hash_set<Literal> enforcement_vars;
10140 std::vector<std::pair<Literal, Literal>> implications_used;
10141 // TODO(user): We can also do similar stuff to linear constraint that just
10142 // differ at a singleton variable. Or that are equalities. Like if expr + X =
10143 // cte and expr + Y = other_cte, we can see that X is in affine relation with
10144 // Y.
10145 const std::vector<std::pair<int, int>> duplicates_without_enforcement =
10146 FindDuplicateConstraints(*context_->working_model, true);
10147 timer.AddCounter("without_enforcements",
10148 duplicates_without_enforcement.size());
10149 for (const auto& [dup, rep] : duplicates_without_enforcement) {
10150 auto* dup_ct = context_->working_model->mutable_constraints(dup);
10151 auto* rep_ct = context_->working_model->mutable_constraints(rep);
10152
10153 if (dup_ct->constraint_case() == ConstraintProto::kInterval) {
10154 context_->UpdateRuleStats(
10155 "TODO interval: same interval with different enforcement?");
10156 continue;
10157 }
10158
10159 // Make sure our enforcement list are up to date: nothing fixed and that
10160 // its uses the literal representatives.
10161 if (PresolveEnforcementLiteral(dup_ct)) {
10162 context_->UpdateConstraintVariableUsage(dup);
10163 }
10164 if (PresolveEnforcementLiteral(rep_ct)) {
10165 context_->UpdateConstraintVariableUsage(rep);
10166 }
10167
10168 // Skip this pair if one of the constraint was simplified
10169 if (rep_ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET ||
10170 dup_ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
10171 continue;
10172 }
10173
10174 // If one of them has no enforcement, then the other can be ignored.
10175 // We always keep rep, but clear its enforcement if any.
10176 if (dup_ct->enforcement_literal().empty() ||
10177 rep_ct->enforcement_literal().empty()) {
10178 context_->UpdateRuleStats("duplicate: removed enforced constraint");
10179 rep_ct->mutable_enforcement_literal()->Clear();
10180 context_->UpdateConstraintVariableUsage(rep);
10181 dup_ct->Clear();
10182 context_->UpdateConstraintVariableUsage(dup);
10183 continue;
10184 }
10185
10186 const int a = rep_ct->enforcement_literal(0);
10187 const int b = dup_ct->enforcement_literal(0);
10188
10189 if (a == NegatedRef(b) && rep_ct->enforcement_literal().size() == 1 &&
10190 dup_ct->enforcement_literal().size() == 1) {
10191 context_->UpdateRuleStats(
10192 "duplicate: both with enforcement and its negation");
10193 rep_ct->mutable_enforcement_literal()->Clear();
10194 context_->UpdateConstraintVariableUsage(rep);
10195 dup_ct->Clear();
10196 context_->UpdateConstraintVariableUsage(dup);
10197 continue;
10198 }
10199
10200 // Special case. This looks specific but users might reify with a cost
10201 // a duplicate constraint. In this case, no need to have two variables,
10202 // we can make them equal by duality argument.
10203 //
10204 // TODO(user): Deal with more general situation? Note that we already
10205 // do something similar in dual_bound_strengthening.Strengthen() were we
10206 // are more general as we just require an unique blocking constraint rather
10207 // than a singleton variable.
10208 //
10209 // But we could detect that "a <=> constraint" and "b <=> constraint", then
10210 // we can also add the equality. Alternatively, we can just introduce a new
10211 // variable and merge all duplicate constraint into 1 + bunch of boolean
10212 // constraints liking enforcements.
10213 if (context_->VariableWithCostIsUniqueAndRemovable(a) &&
10214 context_->VariableWithCostIsUniqueAndRemovable(b)) {
10215 // Both these case should be presolved before, but it is easy to deal with
10216 // if we encounter them here in some corner cases. And the code after
10217 // 'continue' uses this, in particular to update the hint.
10218 bool skip = false;
10219 if (RefIsPositive(a) == context_->ObjectiveCoeff(PositiveRef(a)) > 0) {
10220 context_->UpdateRuleStats("duplicate: dual fixing enforcement.");
10221 if (!context_->SetLiteralToFalse(a)) return;
10222 skip = true;
10223 }
10224 if (RefIsPositive(b) == context_->ObjectiveCoeff(PositiveRef(b)) > 0) {
10225 context_->UpdateRuleStats("duplicate: dual fixing enforcement.");
10226 if (!context_->SetLiteralToFalse(b)) return;
10227 skip = true;
10228 }
10229 if (skip) continue;
10230
10231 // If there are more than one enforcement literal, then the Booleans
10232 // are not necessarily equivalent: if a constraint is disabled by other
10233 // literal, we don't want to put a or b at 1 and pay an extra cost.
10234 //
10235 // TODO(user): If a is alone, then b==1 can implies a == 1.
10236 // We can also replace [(b, others) => constraint] with (b, others) <=> a.
10237 //
10238 // TODO(user): If the other enforcements are the same, we can also add
10239 // the equivalence and remove the duplicate constraint.
10240 if (rep_ct->enforcement_literal().size() > 1 ||
10241 dup_ct->enforcement_literal().size() > 1) {
10242 context_->UpdateRuleStats(
10243 "TODO duplicate: identical constraint with unique enforcement "
10244 "cost");
10245 continue;
10246 }
10247
10248 // Sign is correct, i.e. ignoring the constraint is expensive.
10249 // The two enforcement can be made equivalent.
10250 context_->UpdateRuleStats("duplicate: dual equivalence of enforcement");
10251 // If `a` and `b` hints are different then the whole hint satisfies
10252 // the enforced constraint. We can thus change them to true (this cannot
10253 // increase the objective value thanks to the `skip` test above -- the
10254 // objective domain is non-constraining, but this only guarantees that
10255 // singleton variables can freely *decrease* the objective).
10256 solution_crush_.UpdateLiteralsToFalseIfDifferent(NegatedRef(a),
10257 NegatedRef(b));
10258 if (!context_->StoreBooleanEqualityRelation(a, b)) return;
10259
10260 // We can also remove duplicate constraint now. It will be done later but
10261 // it seems more efficient to just do it now.
10262 if (dup_ct->enforcement_literal().size() == 1 &&
10263 rep_ct->enforcement_literal().size() == 1) {
10264 dup_ct->Clear();
10265 context_->UpdateConstraintVariableUsage(dup);
10266 continue;
10267 }
10268 }
10269
10270 // Check if the enforcement of one constraint implies the ones of the other.
10271 if (implication_graph != nullptr && mapping != nullptr &&
10272 trail != nullptr) {
10273 for (int i = 0; i < 2; i++) {
10274 // When A and B only differ on their enforcement literals and the
10275 // enforcements of constraint A implies the enforcements of constraint
10276 // B, then constraint A is redundant and we can remove it.
10277 const int c_a = i == 0 ? dup : rep;
10278 const int c_b = i == 0 ? rep : dup;
10279 const auto& ct_a = context_->working_model->constraints(c_a);
10280 const auto& ct_b = context_->working_model->constraints(c_b);
10281
10282 enforcement_vars.clear();
10283 implications_used.clear();
10284 for (const int proto_lit : ct_b.enforcement_literal()) {
10285 const Literal lit = mapping->Literal(proto_lit);
10286 DCHECK(!trail->Assignment().LiteralIsAssigned(lit));
10287 enforcement_vars.insert(lit);
10288 }
10289 for (const int proto_lit : ct_a.enforcement_literal()) {
10290 const Literal lit = mapping->Literal(proto_lit);
10291 DCHECK(!trail->Assignment().LiteralIsAssigned(lit));
10292 for (const Literal implication_lit :
10293 implication_graph->DirectImplications(lit)) {
10294 auto extracted = enforcement_vars.extract(implication_lit);
10295 if (!extracted.empty() && lit != implication_lit) {
10296 implications_used.push_back({lit, implication_lit});
10297 }
10298 }
10299 }
10300 if (enforcement_vars.empty()) {
10301 // Tricky: Because we keep track of literal <=> var == value, we
10302 // cannot easily simplify linear1 here. This is because a scenario
10303 // like this can happen:
10304 //
10305 // We have registered the fact that a <=> X=1 because we saw two
10306 // constraints a => X=1 and not(a) => X!= 1
10307 //
10308 // Now, we are here and we have:
10309 // a => X=1, b => X=1, a => b
10310 // So we rewrite this as
10311 // a => b, b => X=1
10312 //
10313 // But later, the PresolveLinearOfSizeOne() see
10314 // b => X=1 and just rewrite this as b => a since (a <=> X=1).
10315 // This is wrong because the constraint "b => X=1" is needed for the
10316 // equivalence (a <=> X=1), but we lost that fact.
10317 //
10318 // Note(user): In the scenario above we can see that a <=> b, and if
10319 // we know that fact, then the transformation is correctly handled.
10320 // The bug was triggered when the Probing finished early due to time
10321 // limit and we never detected that equivalence.
10322 //
10323 // TODO(user): Try to find a cleaner way to handle this. We could
10324 // query our HasVarValueEncoding() directly here and directly detect a
10325 // <=> b. However we also need to figure the case of
10326 // half-implications.
10327 {
10328 if (ct_a.constraint_case() == ConstraintProto::kLinear &&
10329 ct_a.linear().vars().size() == 1 &&
10330 ct_a.enforcement_literal().size() == 1) {
10331 const int var = ct_a.linear().vars(0);
10332 const Domain var_domain = context_->DomainOf(var);
10333 const Domain rhs =
10334 ReadDomainFromProto(ct_a.linear())
10335 .InverseMultiplicationBy(ct_a.linear().coeffs(0))
10336 .IntersectionWith(var_domain);
10337
10338 // IsFixed() do not work on empty domain.
10339 if (rhs.IsEmpty()) {
10340 context_->UpdateRuleStats("duplicate: linear1 infeasible");
10341 if (!MarkConstraintAsFalse(rep_ct)) return;
10342 if (!MarkConstraintAsFalse(dup_ct)) return;
10343 context_->UpdateConstraintVariableUsage(rep);
10344 context_->UpdateConstraintVariableUsage(dup);
10345 continue;
10346 }
10347 if (rhs == var_domain) {
10348 context_->UpdateRuleStats("duplicate: linear1 always true");
10349 rep_ct->Clear();
10350 dup_ct->Clear();
10351 context_->UpdateConstraintVariableUsage(rep);
10352 context_->UpdateConstraintVariableUsage(dup);
10353 continue;
10354 }
10355
10356 // We skip if it is a var == value or var != value constraint.
10357 if (rhs.IsFixed() ||
10358 rhs.Complement().IntersectionWith(var_domain).IsFixed()) {
10359 context_->UpdateRuleStats(
10360 "TODO duplicate: skipped identical encoding constraints");
10361 continue;
10362 }
10363 }
10364 }
10365
10366 context_->UpdateRuleStats(
10367 "duplicate: identical constraint with implied enforcements");
10368 if (c_a == rep) {
10369 // We don't want to remove the representative element of the
10370 // duplicates detection, so swap the constraints.
10371 rep_ct->Swap(dup_ct);
10372 context_->UpdateConstraintVariableUsage(rep);
10373 }
10374 dup_ct->Clear();
10375 context_->UpdateConstraintVariableUsage(dup);
10376 // Subtle point: we need to add the implications we used back to the
10377 // graph. This is because in some case the implications are only true
10378 // in the presence of the "duplicated" constraints.
10379 for (const auto& [a, b] : implications_used) {
10380 const int proto_lit_a = mapping->GetProtoLiteralFromLiteral(a);
10381 const int proto_lit_b = mapping->GetProtoLiteralFromLiteral(b);
10382 context_->AddImplication(proto_lit_a, proto_lit_b);
10383 }
10384 context_->UpdateNewConstraintsVariableUsage();
10385 break;
10386 }
10387 }
10388 }
10389 }
10390}
10391
10393 if (time_limit_->LimitReached()) return;
10394 if (context_->ModelIsUnsat()) return;
10395 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10396
10397 // List the variable that are pairwise different, also store in offset[x, y]
10398 // the offsets such that x >= y + offset.second OR y >= x + offset.first.
10399 std::vector<std::pair<int, int>> different_vars;
10400 absl::flat_hash_map<std::pair<int, int>, std::pair<int64_t, int64_t>> offsets;
10401
10402 // Process the fact "v1 - v2 \in Domain".
10403 const auto process_difference = [&different_vars, &offsets](int v1, int v2,
10404 Domain d) {
10405 Domain exclusion = d.Complement().PartAroundZero();
10406 if (exclusion.IsEmpty()) return;
10407 if (v1 == v2) return;
10408 std::pair<int, int> key = {v1, v2};
10409 if (v1 > v2) {
10410 std::swap(key.first, key.second);
10411 exclusion = exclusion.Negation();
10412 }
10413
10414 // We have x - y not in exclusion,
10415 // so x - y > exclusion.Max() --> x > y + exclusion.Max();
10416 // OR x - y < exclusion.Min() --> y > x - exclusion.Min();
10417 different_vars.push_back(key);
10418 offsets[key] = {exclusion.Min() == std::numeric_limits<int64_t>::min()
10419 ? std::numeric_limits<int64_t>::max()
10420 : CapAdd(-exclusion.Min(), 1),
10421 CapAdd(exclusion.Max(), 1)};
10422 };
10423
10424 // Try to find identical linear constraint with incompatible domains.
10425 // This works really well on neos16.mps.gz where we have
10426 // a <=> x <= y
10427 // b <=> x >= y
10428 // and a => not(b),
10429 // Because of this presolve, we detect that not(a) => b and thus that a and
10430 // not(b) are equivalent. We can thus simplify the problem to just
10431 // a => x < y
10432 // not(a) => x > y
10433 //
10434 // TODO(user): On that same problem, we could actually just have x != y and
10435 // remove the enforcement literal that is just used for that. But then we
10436 // will just re-create it, since we don't have a native way to handle x != y.
10437 //
10438 // TODO(user): Again on neos16.mps, we actually have cliques of x != y so we
10439 // end up with a bunch of groups of 7 variables in [0, 6] that are all
10440 // different. If we can detect that, then we close the problem quickly instead
10441 // of not closing it.
10442 bool has_all_diff = false;
10443 bool has_no_overlap = false;
10444 std::vector<std::pair<uint64_t, int>> hashes;
10445 const int num_constraints = context_->working_model->constraints_size();
10446 for (int c = 0; c < num_constraints; ++c) {
10447 const ConstraintProto& ct = context_->working_model->constraints(c);
10448 if (ct.constraint_case() == ConstraintProto::kAllDiff) {
10449 has_all_diff = true;
10450 continue;
10451 }
10452 if (ct.constraint_case() == ConstraintProto::kNoOverlap) {
10453 has_no_overlap = true;
10454 continue;
10455 }
10456 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
10457 if (ct.linear().vars().size() == 1) continue;
10458
10459 // Detect direct encoding of x != y. Note that we also see that from x > y
10460 // and related.
10461 if (ct.linear().vars().size() == 2 && ct.enforcement_literal().empty() &&
10462 ct.linear().coeffs(0) == -ct.linear().coeffs(1)) {
10463 // We assume the constraint was already divided by its gcd.
10464 if (ct.linear().coeffs(0) == 1) {
10465 process_difference(ct.linear().vars(0), ct.linear().vars(1),
10466 ReadDomainFromProto(ct.linear()));
10467 } else if (ct.linear().coeffs(0) == -1) {
10468 process_difference(ct.linear().vars(0), ct.linear().vars(1),
10469 ReadDomainFromProto(ct.linear()).Negation());
10470 }
10471 }
10472
10473 // TODO(user): Handle this case?
10474 if (ct.enforcement_literal().size() > 1) continue;
10475
10476 uint64_t hash = kDefaultFingerprintSeed;
10477 hash = FingerprintRepeatedField(ct.linear().vars(), hash);
10478 hash = FingerprintRepeatedField(ct.linear().coeffs(), hash);
10479 hashes.push_back({hash, c});
10480 }
10481 std::sort(hashes.begin(), hashes.end());
10482 for (int next, start = 0; start < hashes.size(); start = next) {
10483 next = start + 1;
10484 while (next < hashes.size() && hashes[next].first == hashes[start].first) {
10485 ++next;
10486 }
10487 absl::Span<const std::pair<uint64_t, int>> range(&hashes[start],
10488 next - start);
10489 if (range.size() <= 1) continue;
10490 if (range.size() > 10) continue;
10491
10492 for (int i = 0; i < range.size(); ++i) {
10493 const ConstraintProto& ct1 =
10494 context_->working_model->constraints(range[i].second);
10495 const int num_terms = ct1.linear().vars().size();
10496 for (int j = i + 1; j < range.size(); ++j) {
10497 const ConstraintProto& ct2 =
10498 context_->working_model->constraints(range[j].second);
10499 if (ct2.linear().vars().size() != num_terms) continue;
10500 if (!ReadDomainFromProto(ct1.linear())
10502 .IsEmpty()) {
10503 continue;
10504 }
10505 if (absl::MakeSpan(ct1.linear().vars().data(), num_terms) !=
10506 absl::MakeSpan(ct2.linear().vars().data(), num_terms)) {
10507 continue;
10508 }
10509 if (absl::MakeSpan(ct1.linear().coeffs().data(), num_terms) !=
10510 absl::MakeSpan(ct2.linear().coeffs().data(), num_terms)) {
10511 continue;
10512 }
10513
10514 if (ct1.enforcement_literal().empty() &&
10515 ct2.enforcement_literal().empty()) {
10516 (void)context_->NotifyThatModelIsUnsat(
10517 "two incompatible linear constraint");
10518 return;
10519 }
10520 if (ct1.enforcement_literal().empty()) {
10521 context_->UpdateRuleStats(
10522 "incompatible linear: set enforcement to false");
10523 if (!context_->SetLiteralToFalse(ct2.enforcement_literal(0))) {
10524 return;
10525 }
10526 continue;
10527 }
10528 if (ct2.enforcement_literal().empty()) {
10529 context_->UpdateRuleStats(
10530 "incompatible linear: set enforcement to false");
10531 if (!context_->SetLiteralToFalse(ct1.enforcement_literal(0))) {
10532 return;
10533 }
10534 continue;
10535 }
10536
10537 const int lit1 = ct1.enforcement_literal(0);
10538 const int lit2 = ct2.enforcement_literal(0);
10539
10540 // Detect x != y via lit => x > y && not(lit) => x < y.
10541 if (ct1.linear().vars().size() == 2 &&
10542 ct1.linear().coeffs(0) == -ct1.linear().coeffs(1) &&
10543 lit1 == NegatedRef(lit2)) {
10544 // We have x - y in domain1 or in domain2, so it must be in the union.
10545 Domain union_of_domain =
10546 ReadDomainFromProto(ct1.linear())
10547 .UnionWith(ReadDomainFromProto(ct2.linear()));
10548
10549 // We assume the constraint was already divided by its gcd.
10550 if (ct1.linear().coeffs(0) == 1) {
10551 process_difference(ct1.linear().vars(0), ct1.linear().vars(1),
10552 std::move(union_of_domain));
10553 } else if (ct1.linear().coeffs(0) == -1) {
10554 process_difference(ct1.linear().vars(0), ct1.linear().vars(1),
10555 union_of_domain.Negation());
10556 }
10557 }
10558
10559 if (lit1 != NegatedRef(lit2)) {
10560 context_->UpdateRuleStats("incompatible linear: add implication");
10561 context_->AddImplication(lit1, NegatedRef(lit2));
10562 }
10563 }
10564 }
10565 }
10566
10567 // Detect all_different cliques.
10568 // We reuse the max-clique code from sat.
10569 //
10570 // TODO(user): To avoid doing that more than once, we only run it if there
10571 // is no all-diff in the model already. This is not perfect.
10572 //
10573 // Note(user): The all diff added here will not be expanded since we run this
10574 // after expansion. This is fragile though. Not even sure this is what we
10575 // want.
10576 //
10577 // TODO(user): Start with the existing all diff and expand them rather than
10578 // not running this if there are all_diff present.
10579 //
10580 // TODO(user): Only add them at the end of the presolve! it hurt our presolve
10581 // (like probing is slower) and only serve for linear relaxation.
10582 if (context_->params().infer_all_diffs() && !has_all_diff &&
10583 !has_no_overlap && different_vars.size() > 2) {
10584 WallTimer local_time;
10585 local_time.Start();
10586
10587 std::vector<std::vector<Literal>> cliques;
10588 absl::flat_hash_set<int> used_var;
10589
10590 Model local_model;
10591 const int num_variables = context_->working_model->variables().size();
10592 local_model.GetOrCreate<Trail>()->Resize(num_variables);
10593 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
10594 graph->Resize(num_variables);
10595 for (const auto [var1, var2] : different_vars) {
10596 if (!RefIsPositive(var1)) continue;
10597 if (!RefIsPositive(var2)) continue;
10598 if (var1 == var2) {
10599 (void)context_->NotifyThatModelIsUnsat("x != y with x == y");
10600 return;
10601 }
10602 // All variables at false is always a valid solution of the local model,
10603 // so this should never return UNSAT.
10604 CHECK(graph->AddAtMostOne({Literal(BooleanVariable(var1), true),
10605 Literal(BooleanVariable(var2), true)}));
10606 if (!used_var.contains(var1)) {
10607 used_var.insert(var1);
10608 cliques.push_back({Literal(BooleanVariable(var1), true),
10609 Literal(BooleanVariable(var2), true)});
10610 }
10611 if (!used_var.contains(var2)) {
10612 used_var.insert(var2);
10613 cliques.push_back({Literal(BooleanVariable(var1), true),
10614 Literal(BooleanVariable(var2), true)});
10615 }
10616 }
10617 CHECK(graph->DetectEquivalences());
10618 graph->TransformIntoMaxCliques(&cliques, 1e8);
10619
10620 int num_cliques = 0;
10621 int64_t cumulative_size = 0;
10622 for (std::vector<Literal>& clique : cliques) {
10623 if (clique.size() <= 2) continue;
10624
10625 ++num_cliques;
10626 cumulative_size += clique.size();
10627 std::sort(clique.begin(), clique.end());
10628
10629 // We have an all-diff, but inspect the offsets to see if we have a
10630 // disjunctive ! Note that this is quadratic, but no more complex than the
10631 // scan of the model we just did above, since we had one linear constraint
10632 // per entry.
10633 const int num_terms = clique.size();
10634 std::vector<int64_t> sizes(num_terms,
10635 std::numeric_limits<int64_t>::max());
10636 for (int i = 0; i < num_terms; ++i) {
10637 const int v1 = clique[i].Variable().value();
10638 for (int j = i + 1; j < num_terms; ++j) {
10639 const int v2 = clique[j].Variable().value();
10640 const auto [o1, o2] = offsets.at({v1, v2});
10641 sizes[i] = std::min(sizes[i], o1);
10642 sizes[j] = std::min(sizes[j], o2);
10643 }
10644 }
10645
10646 int num_greater_than_one = 0;
10647 int64_t issue = 0;
10648 for (int i = 0; i < num_terms; ++i) {
10649 CHECK_GE(sizes[i], 1);
10650 if (sizes[i] > 1) ++num_greater_than_one;
10651
10652 // When this happens, it means this interval can never be before
10653 // any other. We should probably handle this case better, but for now we
10654 // abort.
10655 issue = CapAdd(issue, sizes[i]);
10656 if (issue == std::numeric_limits<int64_t>::max()) {
10657 context_->UpdateRuleStats("TODO no_overlap: with task always last");
10658 num_greater_than_one = 0;
10659 break;
10660 }
10661 }
10662
10663 if (num_greater_than_one > 0) {
10664 // We have one size greater than 1, lets add a no_overlap!
10665 //
10666 // TODO(user): try to remove all the quadratic boolean and their
10667 // corresponding linear2 ? Any Boolean not used elsewhere could be
10668 // removed.
10669 context_->UpdateRuleStats(
10670 "no_overlap: inferred from x != y constraints");
10671
10672 std::vector<int> intervals;
10673 for (int i = 0; i < num_terms; ++i) {
10674 intervals.push_back(context_->working_model->constraints().size());
10675 auto* new_interval =
10676 context_->working_model->add_constraints()->mutable_interval();
10677 new_interval->mutable_start()->set_offset(0);
10678 new_interval->mutable_start()->add_coeffs(1);
10679 new_interval->mutable_start()->add_vars(clique[i].Variable().value());
10680
10681 new_interval->mutable_size()->set_offset(sizes[i]);
10682
10683 new_interval->mutable_end()->set_offset(sizes[i]);
10684 new_interval->mutable_end()->add_coeffs(1);
10685 new_interval->mutable_end()->add_vars(clique[i].Variable().value());
10686 }
10687 auto* new_ct =
10688 context_->working_model->add_constraints()->mutable_no_overlap();
10689 for (const int interval : intervals) {
10690 new_ct->add_intervals(interval);
10691 }
10692 } else {
10693 context_->UpdateRuleStats("all_diff: inferred from x != y constraints");
10694 auto* new_ct =
10695 context_->working_model->add_constraints()->mutable_all_diff();
10696 for (const Literal l : clique) {
10697 auto* expr = new_ct->add_exprs();
10698 expr->add_vars(l.Variable().value());
10699 expr->add_coeffs(1);
10700 }
10701 }
10702 }
10703
10704 timer.AddCounter("different", different_vars.size());
10705 timer.AddCounter("cliques", num_cliques);
10706 timer.AddCounter("size", cumulative_size);
10707 }
10708
10709 context_->UpdateNewConstraintsVariableUsage();
10710}
10711
10712namespace {
10713
10714// Add factor * subset_ct to the given superset_ct.
10715void Substitute(int64_t factor,
10716 const absl::flat_hash_map<int, int64_t>& subset_coeff_map,
10717 const Domain& subset_rhs, const Domain& superset_rhs,
10718 LinearConstraintProto* mutable_linear) {
10719 int new_size = 0;
10720 const int old_size = mutable_linear->vars().size();
10721 for (int i = 0; i < old_size; ++i) {
10722 const int var = mutable_linear->vars(i);
10723 int64_t coeff = mutable_linear->coeffs(i);
10724 const auto it = subset_coeff_map.find(var);
10725 if (it != subset_coeff_map.end()) {
10726 coeff += factor * it->second;
10727 if (coeff == 0) continue;
10728 }
10729
10730 mutable_linear->set_vars(new_size, var);
10731 mutable_linear->set_coeffs(new_size, coeff);
10732 ++new_size;
10733 }
10734 mutable_linear->mutable_vars()->Truncate(new_size);
10735 mutable_linear->mutable_coeffs()->Truncate(new_size);
10737 superset_rhs.AdditionWith(subset_rhs.MultiplicationBy(factor)),
10738 mutable_linear);
10739}
10740
10741} // namespace
10742
10743void CpModelPresolver::DetectDominatedLinearConstraints() {
10744 if (time_limit_->LimitReached()) return;
10745 if (context_->ModelIsUnsat()) return;
10746 if (context_->params().presolve_inclusion_work_limit() == 0) return;
10747 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10748
10749 // Because we only deal with linear constraint and we want to ignore the
10750 // enforcement part, we reuse the variable list in the inclusion detector.
10751 // Note that we ignore "unclean" constraint, so we only have positive
10752 // reference there.
10753 class Storage {
10754 public:
10755 explicit Storage(CpModelProto* proto) : proto_(*proto) {}
10756 int size() const { return static_cast<int>(proto_.constraints().size()); }
10757 absl::Span<const int> operator[](int c) const {
10758 return absl::MakeSpan(proto_.constraints(c).linear().vars());
10759 }
10760
10761 private:
10762 const CpModelProto& proto_;
10763 };
10764 Storage storage(context_->working_model);
10765 InclusionDetector detector(storage, time_limit_);
10766 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
10767
10768 // Because we use the constraint <-> variable graph, we cannot modify it
10769 // during DetectInclusions(). So we delay the update of the graph.
10770 std::vector<int> constraint_indices_to_clean;
10771
10772 // Cache the linear expression domain.
10773 // TODO(user): maybe we should store this instead of recomputing it.
10774 absl::flat_hash_map<int, Domain> cached_expr_domain;
10775
10776 const int num_constraints = context_->working_model->constraints().size();
10777 for (int c = 0; c < num_constraints; ++c) {
10778 const ConstraintProto& ct = context_->working_model->constraints(c);
10779 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
10780
10781 // We only look at long enforced constraint to avoid all the linear of size
10782 // one or two which can be numerous.
10783 if (!ct.enforcement_literal().empty()) {
10784 if (ct.linear().vars().size() < 3) continue;
10785 }
10786
10787 if (!LinearConstraintIsClean(ct.linear())) {
10788 // This shouldn't happen except in potential corner cases were the
10789 // constraints were not canonicalized before this point. We just skip
10790 // such constraint.
10791 continue;
10792 }
10793
10794 detector.AddPotentialSet(c);
10795
10796 const auto [min_activity, max_activity] =
10797 context_->ComputeMinMaxActivity(ct.linear());
10798 cached_expr_domain[c] = Domain(min_activity, max_activity);
10799 }
10800
10801 int64_t num_inclusions = 0;
10802 absl::flat_hash_map<int, int64_t> coeff_map;
10803 detector.DetectInclusions([&](int subset_c, int superset_c) {
10804 ++num_inclusions;
10805
10806 // Store the coeff of the subset linear constraint in a map.
10807 const ConstraintProto& subset_ct =
10808 context_->working_model->constraints(subset_c);
10809 const LinearConstraintProto& subset_lin = subset_ct.linear();
10810 coeff_map.clear();
10811 detector.IncreaseWorkDone(subset_lin.vars().size());
10812 for (int i = 0; i < subset_lin.vars().size(); ++i) {
10813 coeff_map[subset_lin.vars(i)] = subset_lin.coeffs(i);
10814 }
10815
10816 // We have a perfect match if 'factor_a * subset == factor_b * superset' on
10817 // the common positions. Note that assuming subset has been gcd reduced,
10818 // there is not point considering factor_b != 1.
10819 bool perfect_match = true;
10820
10821 // Find interesting factor of the subset that cancels terms of the superset.
10822 int64_t factor = 0;
10823 int64_t min_pos_factor = std::numeric_limits<int64_t>::max();
10824 int64_t max_neg_factor = std::numeric_limits<int64_t>::min();
10825
10826 // Lets compute the implied domain of the linear expression
10827 // "superset - subset". Note that we actually do not need exact inclusion
10828 // for this algorithm to work, but it is an heuristic to not try it with
10829 // all pair of constraints.
10830 const ConstraintProto& superset_ct =
10831 context_->working_model->constraints(superset_c);
10832 const LinearConstraintProto& superset_lin = superset_ct.linear();
10833 int64_t diff_min_activity = 0;
10834 int64_t diff_max_activity = 0;
10835 detector.IncreaseWorkDone(superset_lin.vars().size());
10836 for (int i = 0; i < superset_lin.vars().size(); ++i) {
10837 const int var = superset_lin.vars(i);
10838 int64_t coeff = superset_lin.coeffs(i);
10839 const auto it = coeff_map.find(var);
10840
10841 if (it != coeff_map.end()) {
10842 const int64_t subset_coeff = it->second;
10843
10844 const int64_t div = coeff / subset_coeff;
10845 if (div > 0) {
10846 min_pos_factor = std::min(div, min_pos_factor);
10847 } else {
10848 max_neg_factor = std::max(div, max_neg_factor);
10849 }
10850
10851 if (perfect_match) {
10852 if (coeff % subset_coeff == 0) {
10853 if (factor == 0) {
10854 // Note that factor can be negative.
10855 factor = div;
10856 } else if (factor != div) {
10857 perfect_match = false;
10858 }
10859 } else {
10860 perfect_match = false;
10861 }
10862 }
10863
10864 // TODO(user): compute the factor first in case it is != 1 ?
10865 coeff -= subset_coeff;
10866 }
10867 if (coeff == 0) continue;
10868 context_->CappedUpdateMinMaxActivity(var, coeff, &diff_min_activity,
10869 &diff_max_activity);
10870 }
10871
10872 const Domain diff_domain(diff_min_activity, diff_max_activity);
10873 const Domain subset_rhs = ReadDomainFromProto(subset_lin);
10874 const Domain superset_rhs = ReadDomainFromProto(superset_lin);
10875
10876 // Case 1: superset is redundant.
10877 // We process this one first as it let us remove the longest constraint.
10878 //
10879 // Important: because of how we computed the inclusion, the diff_domain is
10880 // only valid if none of the enforcement appear in the subset.
10881 //
10882 // TODO(user): Compute the correct infered domain in this case.
10883 if (subset_ct.enforcement_literal().empty()) {
10884 const Domain implied_superset_domain =
10885 subset_rhs.AdditionWith(diff_domain)
10886 .IntersectionWith(cached_expr_domain[superset_c]);
10887 if (implied_superset_domain.IsIncludedIn(superset_rhs)) {
10888 context_->UpdateRuleStats(
10889 "linear inclusion: redundant containing constraint");
10890 context_->working_model->mutable_constraints(superset_c)->Clear();
10891 constraint_indices_to_clean.push_back(superset_c);
10892 detector.StopProcessingCurrentSuperset();
10893 return;
10894 }
10895 }
10896
10897 // Case 2: subset is redundant.
10898 if (superset_ct.enforcement_literal().empty()) {
10899 const Domain implied_subset_domain =
10900 superset_rhs.AdditionWith(diff_domain.Negation())
10901 .IntersectionWith(cached_expr_domain[subset_c]);
10902 if (implied_subset_domain.IsIncludedIn(subset_rhs)) {
10903 context_->UpdateRuleStats(
10904 "linear inclusion: redundant included constraint");
10905 context_->working_model->mutable_constraints(subset_c)->Clear();
10906 constraint_indices_to_clean.push_back(subset_c);
10907 detector.StopProcessingCurrentSubset();
10908 return;
10909 }
10910 }
10911
10912 // If the subset is an equality, and we can add a factor of it to the
10913 // superset so that the activity range is guaranteed to be tighter, we
10914 // always do it. This should both sparsify the problem but also lead to
10915 // tighter propagation.
10916 if (subset_rhs.IsFixed() && subset_ct.enforcement_literal().empty()) {
10917 const int64_t best_factor =
10918 max_neg_factor > -min_pos_factor ? max_neg_factor : min_pos_factor;
10919
10920 // Compute the activity range before and after. Because our pos/neg factor
10921 // are the smallest possible, if one is undefined then we are guaranteed
10922 // to be tighter, and do not need to compute this.
10923 //
10924 // TODO(user): can we compute the best factor that make this as tight as
10925 // possible instead? that looks doable.
10926 bool is_tigher = true;
10927 if (min_pos_factor != std::numeric_limits<int64_t>::max() &&
10928 max_neg_factor != std::numeric_limits<int64_t>::min()) {
10929 int64_t min_before = 0;
10930 int64_t max_before = 0;
10931 int64_t min_after = CapProd(best_factor, subset_rhs.FixedValue());
10932 int64_t max_after = min_after;
10933 for (int i = 0; i < superset_lin.vars().size(); ++i) {
10934 const int var = superset_lin.vars(i);
10935 const auto it = coeff_map.find(var);
10936 if (it == coeff_map.end()) continue;
10937
10938 const int64_t coeff_before = superset_lin.coeffs(i);
10939 const int64_t coeff_after = coeff_before - best_factor * it->second;
10940 context_->CappedUpdateMinMaxActivity(var, coeff_before, &min_before,
10941 &max_before);
10942 context_->CappedUpdateMinMaxActivity(var, coeff_after, &min_after,
10943 &max_after);
10944 }
10945 is_tigher = min_after >= min_before && max_after <= max_before;
10946 }
10947 if (is_tigher) {
10948 context_->UpdateRuleStats("linear inclusion: sparsify superset");
10949 Substitute(-best_factor, coeff_map, subset_rhs, superset_rhs,
10950 context_->working_model->mutable_constraints(superset_c)
10951 ->mutable_linear());
10952 constraint_indices_to_clean.push_back(superset_c);
10953 detector.StopProcessingCurrentSuperset();
10954 return;
10955 }
10956 }
10957
10958 // We do a bit more if we have an exact match and factor * subset is exactly
10959 // a subpart of the superset constraint.
10960 if (perfect_match && subset_ct.enforcement_literal().empty() &&
10961 superset_ct.enforcement_literal().empty()) {
10962 CHECK_NE(factor, 0);
10963
10964 // Propagate domain on the superset - subset variables.
10965 // TODO(user): We can probably still do that if the inclusion is not
10966 // perfect.
10967 temp_ct_.Clear();
10968 auto* mutable_linear = temp_ct_.mutable_linear();
10969 for (int i = 0; i < superset_lin.vars().size(); ++i) {
10970 const int var = superset_lin.vars(i);
10971 const int64_t coeff = superset_lin.coeffs(i);
10972 const auto it = coeff_map.find(var);
10973 if (it != coeff_map.end()) continue;
10974 mutable_linear->add_vars(var);
10975 mutable_linear->add_coeffs(coeff);
10976 }
10978 superset_rhs.AdditionWith(subset_rhs.MultiplicationBy(-factor)),
10979 mutable_linear);
10980 PropagateDomainsInLinear(/*ct_index=*/-1, &temp_ct_);
10981 if (context_->ModelIsUnsat()) detector.Stop();
10982
10983 if (superset_rhs.IsFixed()) {
10984 if (subset_lin.vars().size() + 1 == superset_lin.vars().size()) {
10985 // Because we propagated the equation on the singleton variable above,
10986 // and we have an equality, the subset is redundant!
10987 context_->UpdateRuleStats(
10988 "linear inclusion: subset + singleton is equality");
10989 context_->working_model->mutable_constraints(subset_c)->Clear();
10990 constraint_indices_to_clean.push_back(subset_c);
10991 detector.StopProcessingCurrentSubset();
10992 return;
10993 }
10994
10995 // This one could make sense if subset is large vs superset.
10996 context_->UpdateRuleStats(
10997 "TODO linear inclusion: superset is equality");
10998 }
10999 }
11000 });
11001
11002 for (const int c : constraint_indices_to_clean) {
11003 context_->UpdateConstraintVariableUsage(c);
11004 }
11005
11006 timer.AddToWork(1e-9 * static_cast<double>(detector.work_done()));
11007 timer.AddCounter("relevant_constraints", detector.num_potential_supersets());
11008 timer.AddCounter("num_inclusions", num_inclusions);
11009 timer.AddCounter("num_redundant", constraint_indices_to_clean.size());
11010}
11011
11012// TODO(user): Also substitute if this appear in the objective?
11013// TODO(user): In some case we only need common_part <= new_var.
11014bool CpModelPresolver::RemoveCommonPart(
11015 const absl::flat_hash_map<int, int64_t>& common_var_coeff_map,
11016 absl::Span<const std::pair<int, int64_t>> block,
11017 ActivityBoundHelper* helper) {
11018 int new_var;
11019 int64_t gcd = 0;
11020 int64_t offset = 0;
11021
11022 // If the common part is expressable via one of the constraint in the block as
11023 // == gcd * X + offset, we can just use this variable instead of creating a
11024 // new variable.
11025 int definiting_equation = -1;
11026 for (const auto [c, multiple] : block) {
11027 const ConstraintProto& ct = context_->working_model->constraints(c);
11028 if (std::abs(multiple) != 1) continue;
11029 if (!IsLinearEqualityConstraint(ct)) continue;
11030 if (ct.linear().vars().size() != common_var_coeff_map.size() + 1) continue;
11031
11032 context_->UpdateRuleStats(
11033 "linear matrix: defining equation for common rectangle");
11034 definiting_equation = c;
11035
11036 // Find the missing term and its coefficient.
11037 int64_t coeff = 0;
11038 const int num_terms = ct.linear().vars().size();
11039 for (int k = 0; k < num_terms; ++k) {
11040 if (common_var_coeff_map.contains(ct.linear().vars(k))) continue;
11041 new_var = ct.linear().vars(k);
11042 coeff = ct.linear().coeffs(k);
11043 break;
11044 }
11045 CHECK_NE(coeff, 0);
11046
11047 // We have multiple * common + coeff * X = constant.
11048 // So common = multiple^-1 * constant - multiple^-1 * coeff * X;
11049 gcd = -multiple * coeff;
11050 offset = multiple * ct.linear().domain(0);
11051 break;
11052 }
11053
11054 // We need a new variable and defining equation.
11055 if (definiting_equation == -1) {
11056 offset = 0;
11057 int64_t min_activity = 0;
11058 int64_t max_activity = 0;
11059 tmp_terms_.clear();
11060 std::vector<std::pair<int, int64_t>> common_part;
11061 for (const auto [var, coeff] : common_var_coeff_map) {
11062 common_part.push_back({var, coeff});
11063 gcd = std::gcd(gcd, std::abs(coeff));
11064 if (context_->CanBeUsedAsLiteral(var) && !context_->IsFixed(var)) {
11065 tmp_terms_.push_back({var, coeff});
11066 continue;
11067 }
11068 if (coeff > 0) {
11069 min_activity += coeff * context_->MinOf(var);
11070 max_activity += coeff * context_->MaxOf(var);
11071 } else {
11072 min_activity += coeff * context_->MaxOf(var);
11073 max_activity += coeff * context_->MinOf(var);
11074 }
11075 }
11076
11077 // We isolated the Boolean in tmp_terms_, use the helper to get
11078 // more precise activity bounds. Note that while tmp_terms_ was built from
11079 // a hash map and is in an unspecified order, the Compute*Activity() helpers
11080 // will still return a deterministic result.
11081 if (!tmp_terms_.empty()) {
11082 min_activity += helper->ComputeMinActivity(tmp_terms_);
11083 max_activity += helper->ComputeMaxActivity(tmp_terms_);
11084 }
11085
11086 if (gcd > 1) {
11087 min_activity /= gcd;
11088 max_activity /= gcd;
11089 for (int i = 0; i < common_part.size(); ++i) {
11090 common_part[i].second /= gcd;
11091 }
11092 }
11093
11094 // Create new variable.
11095 std::sort(common_part.begin(), common_part.end());
11096 new_var = context_->NewIntVarWithDefinition(
11097 Domain(min_activity, max_activity), common_part);
11098 if (new_var == -1) return false;
11099 }
11100
11101 // Replace in each constraint the common part by gcd * multiple * new_var !
11102 for (const auto [c, multiple] : block) {
11103 if (c == definiting_equation) continue;
11104
11105 auto* mutable_linear =
11106 context_->working_model->mutable_constraints(c)->mutable_linear();
11107 const int num_terms = mutable_linear->vars().size();
11108 int new_size = 0;
11109 bool new_var_already_seen = false;
11110 for (int k = 0; k < num_terms; ++k) {
11111 if (common_var_coeff_map.contains(mutable_linear->vars(k))) {
11112 CHECK_EQ(common_var_coeff_map.at(mutable_linear->vars(k)) * multiple,
11113 mutable_linear->coeffs(k));
11114 continue;
11115 }
11116
11117 // Tricky: the new variable can already be present in this expression!
11118 int64_t new_coeff = mutable_linear->coeffs(k);
11119 if (mutable_linear->vars(k) == new_var) {
11120 new_var_already_seen = true;
11121 new_coeff += gcd * multiple;
11122 if (new_coeff == 0) continue;
11123 }
11124
11125 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
11126 mutable_linear->set_coeffs(new_size, new_coeff);
11127 ++new_size;
11128 }
11129 mutable_linear->mutable_vars()->Truncate(new_size);
11130 mutable_linear->mutable_coeffs()->Truncate(new_size);
11131 if (!new_var_already_seen) {
11132 mutable_linear->add_vars(new_var);
11133 mutable_linear->add_coeffs(gcd * multiple);
11134 }
11135 if (offset != 0) {
11136 FillDomainInProto(ReadDomainFromProto(*mutable_linear)
11137 .AdditionWith(Domain(-offset * multiple)),
11138 mutable_linear);
11139 }
11140 context_->UpdateConstraintVariableUsage(c);
11141 }
11142 return true;
11143}
11144
11145namespace {
11146
11147int64_t FindVarCoeff(int var, const ConstraintProto& ct) {
11148 const int num_terms = ct.linear().vars().size();
11149 for (int k = 0; k < num_terms; ++k) {
11150 if (ct.linear().vars(k) == var) return ct.linear().coeffs(k);
11151 }
11152 return 0;
11153}
11154
11155int64_t ComputeNonZeroReduction(size_t block_size, size_t common_part_size) {
11156 // We replace the block by a column of new variable.
11157 // But we also need to define this new variable.
11158 return static_cast<int64_t>(block_size * (common_part_size - 1) -
11159 common_part_size - 1);
11160}
11161
11162} // namespace
11163
11164// The idea is to find a set of literal in AMO relationship that appear in
11165// many linear constraints. If this is the case, we can create a new variable to
11166// make an exactly one constraint, and replace it in the linear.
11167void CpModelPresolver::FindBigAtMostOneAndLinearOverlap(
11168 ActivityBoundHelper* helper) {
11169 if (time_limit_->LimitReached()) return;
11170 if (context_->ModelIsUnsat()) return;
11171 if (context_->params().presolve_inclusion_work_limit() == 0) return;
11172 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11173
11174 int64_t num_blocks = 0;
11175 int64_t nz_reduction = 0;
11176 std::vector<int> amo_cts;
11177 std::vector<int> amo_literals;
11178
11179 std::vector<int> common_part;
11180 std::vector<int> best_common_part;
11181
11182 std::vector<bool> common_part_sign;
11183 std::vector<bool> best_common_part_sign;
11184
11185 // We store for each var if the literal was positive or not.
11186 absl::flat_hash_map<int, bool> var_in_amo;
11187
11188 for (int x = 0; x < context_->working_model->variables().size(); ++x) {
11189 // We pick a variable x that appear in some AMO.
11190 if (time_limit_->LimitReached()) break;
11191 if (timer.WorkLimitIsReached()) break;
11192 if (helper->NumAmoForVariable(x) == 0) continue;
11193
11194 amo_cts.clear();
11195 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11196 for (const int c : context_->VarToConstraints(x)) {
11197 if (c < 0) continue;
11198 const ConstraintProto& ct = context_->working_model->constraints(c);
11199 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
11200 amo_cts.push_back(c);
11201 } else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
11202 amo_cts.push_back(c);
11203 }
11204 }
11205 if (amo_cts.empty()) continue;
11206
11207 // Pick a random AMO containing x.
11208 //
11209 // TODO(user): better algo!
11210 //
11211 // Note that we don't care about the polarity, for each linear constraint,
11212 // if the coeff magnitude are the same, we will just have two values
11213 // controlled by whether the AMO (or EXO subset) is at one or zero.
11214 var_in_amo.clear();
11215 amo_literals.clear();
11216 common_part.clear();
11217 common_part_sign.clear();
11218 int base_ct_index;
11219 {
11220 // For determinism.
11221 std::sort(amo_cts.begin(), amo_cts.end());
11222 const int random_c =
11223 absl::Uniform<int>(*context_->random(), 0, amo_cts.size());
11224 base_ct_index = amo_cts[random_c];
11225 const ConstraintProto& ct =
11226 context_->working_model->constraints(base_ct_index);
11227 const auto& literals = ct.constraint_case() == ConstraintProto::kAtMostOne
11228 ? ct.at_most_one().literals()
11229 : ct.exactly_one().literals();
11230 timer.TrackSimpleLoop(5 * literals.size()); // hash insert are slow.
11231 for (const int literal : literals) {
11232 amo_literals.push_back(literal);
11233 common_part.push_back(PositiveRef(literal));
11234 common_part_sign.push_back(RefIsPositive(literal));
11235 const auto [_, inserted] =
11236 var_in_amo.insert({PositiveRef(literal), RefIsPositive(literal)});
11237 CHECK(inserted);
11238 }
11239 }
11240
11241 const int64_t x_multiplier = var_in_amo.at(x) ? 1 : -1;
11242
11243 // Collect linear constraints with at least two Boolean terms in var_in_amo
11244 // with the same coefficient than x.
11245 std::vector<int> block_cts;
11246 std::vector<int> linear_cts;
11247 int max_common_part = 0;
11248 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11249 for (const int c : context_->VarToConstraints(x)) {
11250 if (c < 0) continue;
11251 const ConstraintProto& ct = context_->working_model->constraints(c);
11252 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
11253 const int num_terms = ct.linear().vars().size();
11254 if (num_terms < 2) continue;
11255
11256 timer.TrackSimpleLoop(2 * num_terms);
11257 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
11258 if (x_coeff == 0) continue; // could be in enforcement.
11259
11260 int num_in_amo = 0;
11261 for (int k = 0; k < num_terms; ++k) {
11262 const int var = ct.linear().vars(k);
11263 if (!RefIsPositive(var)) {
11264 num_in_amo = 0; // Abort.
11265 break;
11266 }
11267 const auto it = var_in_amo.find(var);
11268 if (it == var_in_amo.end()) continue;
11269 int64_t coeff = ct.linear().coeffs(k);
11270 if (!it->second) coeff = -coeff;
11271 if (coeff != x_coeff) continue;
11272 ++num_in_amo;
11273 }
11274 if (num_in_amo < 2) continue;
11275
11276 max_common_part += num_in_amo;
11277 if (num_in_amo == common_part.size()) {
11278 // This is a perfect match!
11279 block_cts.push_back(c);
11280 } else {
11281 linear_cts.push_back(c);
11282 }
11283 }
11284 if (linear_cts.empty() && block_cts.empty()) continue;
11285 if (max_common_part < 100) continue;
11286
11287 // Remember the best block encountered in the greedy algo below.
11288 // Note that we always start with the current perfect match.
11289 best_common_part = common_part;
11290 best_common_part_sign = common_part_sign;
11291 int best_block_size = block_cts.size();
11292 int best_saved_nz =
11293 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
11294
11295 // For determinism.
11296 std::sort(block_cts.begin(), block_cts.end());
11297 std::sort(linear_cts.begin(), linear_cts.end());
11298
11299 // We will just greedily compute a big block with a random order.
11300 // TODO(user): We could sort by match with the full constraint instead.
11301 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
11302 for (const int c : linear_cts) {
11303 const ConstraintProto& ct = context_->working_model->constraints(c);
11304 const int num_terms = ct.linear().vars().size();
11305 timer.TrackSimpleLoop(2 * num_terms);
11306 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
11307 CHECK_NE(x_coeff, 0);
11308
11309 common_part.clear();
11310 common_part_sign.clear();
11311 for (int k = 0; k < num_terms; ++k) {
11312 const int var = ct.linear().vars(k);
11313 const auto it = var_in_amo.find(var);
11314 if (it == var_in_amo.end()) continue;
11315 int64_t coeff = ct.linear().coeffs(k);
11316 if (!it->second) coeff = -coeff;
11317 if (coeff != x_coeff) continue;
11318 common_part.push_back(var);
11319 common_part_sign.push_back(it->second);
11320 }
11321 if (common_part.size() < 2) continue;
11322
11323 // Change var_in_amo;
11324 block_cts.push_back(c);
11325 if (common_part.size() < var_in_amo.size()) {
11326 var_in_amo.clear();
11327 for (int i = 0; i < common_part.size(); ++i) {
11328 var_in_amo[common_part[i]] = common_part_sign[i];
11329 }
11330 }
11331
11332 // We have a block that can be replaced with a single new boolean +
11333 // defining exo constraint. Note that we can also replace in the base
11334 // constraint, hence the +1 to the block size.
11335 const int64_t saved_nz =
11336 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
11337 if (saved_nz > best_saved_nz) {
11338 best_block_size = block_cts.size();
11339 best_saved_nz = saved_nz;
11340 best_common_part = common_part;
11341 best_common_part_sign = common_part_sign;
11342 }
11343 }
11344 if (best_saved_nz < 100) continue;
11345
11346 // Use the best rectangle.
11347 // We start with the full match.
11348 // TODO(user): maybe we should always just use this if it is large enough?
11349 block_cts.resize(best_block_size);
11350 var_in_amo.clear();
11351 for (int i = 0; i < best_common_part.size(); ++i) {
11352 var_in_amo[best_common_part[i]] = best_common_part_sign[i];
11353 }
11354
11355 ++num_blocks;
11356 nz_reduction += best_saved_nz;
11357 context_->UpdateRuleStats("linear matrix: common amo rectangle");
11358
11359 // First filter the amo.
11360 int new_size = 0;
11361 for (const int lit : amo_literals) {
11362 if (!var_in_amo.contains(PositiveRef(lit))) continue;
11363 amo_literals[new_size++] = lit;
11364 }
11365 if (new_size == amo_literals.size()) {
11366 const ConstraintProto& ct =
11367 context_->working_model->constraints(base_ct_index);
11368 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
11369 context_->UpdateRuleStats("TODO linear matrix: constant rectangle!");
11370 } else {
11371 context_->UpdateRuleStats(
11372 "TODO linear matrix: reuse defining constraint");
11373 }
11374 } else if (new_size + 1 == amo_literals.size()) {
11375 const ConstraintProto& ct =
11376 context_->working_model->constraints(base_ct_index);
11377 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
11378 context_->UpdateRuleStats("TODO linear matrix: reuse exo constraint");
11379 }
11380 }
11381 amo_literals.resize(new_size);
11382
11383 // Create a new literal that is one iff one of the literal in AMO is one.
11384 const int new_var = context_->NewBoolVarWithClause(amo_literals);
11385 {
11386 auto* new_exo =
11387 context_->working_model->add_constraints()->mutable_exactly_one();
11388 new_exo->mutable_literals()->Reserve(amo_literals.size() + 1);
11389 for (const int lit : amo_literals) {
11390 new_exo->add_literals(lit);
11391 }
11392 new_exo->add_literals(NegatedRef(new_var));
11393 context_->UpdateNewConstraintsVariableUsage();
11394 }
11395
11396 // Filter the base amo/exo.
11397 {
11398 ConstraintProto* ct =
11399 context_->working_model->mutable_constraints(base_ct_index);
11400 auto* mutable_literals =
11401 ct->constraint_case() == ConstraintProto::kAtMostOne
11402 ? ct->mutable_at_most_one()->mutable_literals()
11403 : ct->mutable_exactly_one()->mutable_literals();
11404 int new_size = 0;
11405 for (const int lit : *mutable_literals) {
11406 if (var_in_amo.contains(PositiveRef(lit))) continue;
11407 (*mutable_literals)[new_size++] = lit;
11408 }
11409 (*mutable_literals)[new_size++] = new_var;
11410 mutable_literals->Truncate(new_size);
11411 context_->UpdateConstraintVariableUsage(base_ct_index);
11412 }
11413
11414 // Use this Boolean in all the linear constraints.
11415 for (const int c : block_cts) {
11416 auto* mutable_linear =
11417 context_->working_model->mutable_constraints(c)->mutable_linear();
11418
11419 // The removed expression will be (offset + coeff_x * new_bool).
11420 int64_t offset = 0;
11421 int64_t coeff_x = 0;
11422
11423 int new_size = 0;
11424 const int num_terms = mutable_linear->vars().size();
11425 for (int k = 0; k < num_terms; ++k) {
11426 const int var = mutable_linear->vars(k);
11427 CHECK(RefIsPositive(var));
11428 int64_t coeff = mutable_linear->coeffs(k);
11429 const auto it = var_in_amo.find(var);
11430 if (it != var_in_amo.end()) {
11431 if (it->second) {
11432 // default is zero, amo at one means we add coeff.
11433 } else {
11434 // term is -coeff * (1 - var) + coeff.
11435 // default is coeff, amo at 1 means we remove coeff.
11436 offset += coeff;
11437 coeff = -coeff;
11438 }
11439 if (coeff_x == 0) coeff_x = coeff;
11440 CHECK_EQ(coeff, coeff_x);
11441 continue;
11442 }
11443 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
11444 mutable_linear->set_coeffs(new_size, coeff);
11445 ++new_size;
11446 }
11447
11448 // Add the new term.
11449 mutable_linear->set_vars(new_size, new_var);
11450 mutable_linear->set_coeffs(new_size, coeff_x);
11451 ++new_size;
11452
11453 mutable_linear->mutable_vars()->Truncate(new_size);
11454 mutable_linear->mutable_coeffs()->Truncate(new_size);
11455 if (offset != 0) {
11457 ReadDomainFromProto(*mutable_linear).AdditionWith(Domain(-offset)),
11458 mutable_linear);
11459 }
11460 context_->UpdateConstraintVariableUsage(c);
11461 }
11462 }
11463
11464 timer.AddCounter("blocks", num_blocks);
11465 timer.AddCounter("saved_nz", nz_reduction);
11466 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11467}
11468
11469// This helps on neos-5045105-creuse.pb.gz for instance.
11470void CpModelPresolver::FindBigVerticalLinearOverlap(
11471 ActivityBoundHelper* helper) {
11472 if (time_limit_->LimitReached()) return;
11473 if (context_->ModelIsUnsat()) return;
11474 if (context_->params().presolve_inclusion_work_limit() == 0) return;
11475 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11476
11477 int64_t num_blocks = 0;
11478 int64_t nz_reduction = 0;
11479 absl::flat_hash_map<int, int64_t> coeff_map;
11480 for (int x = 0; x < context_->working_model->variables().size(); ++x) {
11481 if (timer.WorkLimitIsReached()) break;
11482
11483 bool in_enforcement = false;
11484 std::vector<int> linear_cts;
11485 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11486 for (const int c : context_->VarToConstraints(x)) {
11487 if (c < 0) continue;
11488 const ConstraintProto& ct = context_->working_model->constraints(c);
11489 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
11490
11491 const int num_terms = ct.linear().vars().size();
11492 if (num_terms < 2) continue;
11493 bool is_canonical = true;
11494 timer.TrackSimpleLoop(num_terms);
11495 for (int k = 0; k < num_terms; ++k) {
11496 if (!RefIsPositive(ct.linear().vars(k))) {
11497 is_canonical = false;
11498 break;
11499 }
11500 }
11501 if (!is_canonical) continue;
11502
11503 // We don't care about enforcement literal, but we don't want x inside.
11504 timer.TrackSimpleLoop(ct.enforcement_literal().size());
11505 for (const int lit : ct.enforcement_literal()) {
11506 if (PositiveRef(lit) == x) {
11507 in_enforcement = true;
11508 break;
11509 }
11510 }
11511
11512 // Note(user): We will actually abort right away in this case, but we
11513 // want work_done to be deterministic! so we do the work anyway.
11514 if (in_enforcement) continue;
11515 linear_cts.push_back(c);
11516 }
11517
11518 // If a Boolean is used in enforcement, we prefer not to combine it with
11519 // others. TODO(user): more generally ignore Boolean or only replace if
11520 // there is a big non-zero improvement.
11521 if (in_enforcement) continue;
11522 if (linear_cts.size() < 10) continue;
11523
11524 // For determinism.
11525 std::sort(linear_cts.begin(), linear_cts.end());
11526 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
11527
11528 // Now it is almost the same algo as for FindBigHorizontalLinearOverlap().
11529 // We greedely compute a "common" rectangle using the first constraint
11530 // as a "base" one. Note that if a aX + bY appear in the majority of
11531 // constraint, we have a good chance to find this block since we start by
11532 // a random constraint.
11533 coeff_map.clear();
11534
11535 std::vector<std::pair<int, int64_t>> block;
11536 std::vector<std::pair<int, int64_t>> common_part;
11537 for (const int c : linear_cts) {
11538 const ConstraintProto& ct = context_->working_model->constraints(c);
11539 const int num_terms = ct.linear().vars().size();
11540 timer.TrackSimpleLoop(num_terms);
11541
11542 // Compute the coeff of x.
11543 const int64_t x_coeff = FindVarCoeff(x, ct);
11544 if (x_coeff == 0) continue;
11545
11546 if (block.empty()) {
11547 // This is our base constraint.
11548 coeff_map.clear();
11549 for (int k = 0; k < num_terms; ++k) {
11550 coeff_map[ct.linear().vars(k)] = ct.linear().coeffs(k);
11551 }
11552 if (coeff_map.size() < 2) continue;
11553 block.push_back({c, x_coeff});
11554 continue;
11555 }
11556
11557 // We are looking for a common divisor of coeff_map and this constraint.
11558 const int64_t gcd =
11559 std::gcd(std::abs(coeff_map.at(x)), std::abs(x_coeff));
11560 const int64_t multiple_base = coeff_map.at(x) / gcd;
11561 const int64_t multiple_ct = x_coeff / gcd;
11562 common_part.clear();
11563 for (int k = 0; k < num_terms; ++k) {
11564 const int64_t coeff = ct.linear().coeffs(k);
11565 if (coeff % multiple_ct != 0) continue;
11566
11567 const auto it = coeff_map.find(ct.linear().vars(k));
11568 if (it == coeff_map.end()) continue;
11569 if (it->second % multiple_base != 0) continue;
11570 if (it->second / multiple_base != coeff / multiple_ct) continue;
11571
11572 common_part.push_back({ct.linear().vars(k), coeff / multiple_ct});
11573 }
11574
11575 // Skip bad constraint.
11576 if (common_part.size() < 2) continue;
11577
11578 // Update coeff_map.
11579 block.push_back({c, x_coeff});
11580 coeff_map.clear();
11581 for (const auto [var, coeff] : common_part) {
11582 coeff_map[var] = coeff;
11583 }
11584 }
11585
11586 // We have a candidate.
11587 const int64_t saved_nz =
11588 ComputeNonZeroReduction(block.size(), coeff_map.size());
11589 if (saved_nz < 30) continue;
11590
11591 // Fix multiples, currently this contain the coeff of x for each constraint.
11592 const int64_t base_x = coeff_map.at(x);
11593 for (auto& [c, multipier] : block) {
11594 CHECK_EQ(multipier % base_x, 0);
11595 multipier /= base_x;
11596 }
11597
11598 // Introduce new_var = coeff_map and perform the substitution.
11599 if (!RemoveCommonPart(coeff_map, block, helper)) continue;
11600 ++num_blocks;
11601 nz_reduction += saved_nz;
11602 context_->UpdateRuleStats("linear matrix: common vertical rectangle");
11603 }
11604
11605 timer.AddCounter("blocks", num_blocks);
11606 timer.AddCounter("saved_nz", nz_reduction);
11607 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11608}
11609
11610// Note that internally, we already split long linear into smaller chunk, so
11611// it should be beneficial to identify common part between many linear
11612// constraint.
11613//
11614// Note(user): This was made to work on var-smallemery-m6j6.pb.gz, but applies
11615// to quite a few miplib problem. Try to improve the heuristics and algorithm to
11616// be faster and detect larger block.
11617void CpModelPresolver::FindBigHorizontalLinearOverlap(
11618 ActivityBoundHelper* helper) {
11619 if (time_limit_->LimitReached()) return;
11620 if (context_->ModelIsUnsat()) return;
11621 if (context_->params().presolve_inclusion_work_limit() == 0) return;
11622 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11623
11624 const int num_constraints = context_->working_model->constraints_size();
11625 std::vector<std::pair<int, int>> to_sort;
11626 for (int c = 0; c < num_constraints; ++c) {
11627 const ConstraintProto& ct = context_->working_model->constraints(c);
11628 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
11629 const int size = ct.linear().vars().size();
11630 if (size < 5) continue;
11631 to_sort.push_back({-size, c});
11632 }
11633 std::sort(to_sort.begin(), to_sort.end());
11634
11635 std::vector<int> sorted_linear;
11636 for (int i = 0; i < to_sort.size(); ++i) {
11637 sorted_linear.push_back(to_sort[i].second);
11638 }
11639
11640 // On large problem, using and hash_map can be slow, so we use the vector
11641 // version and for now fill the map only when doing the change.
11642 std::vector<int> var_to_coeff_non_zeros;
11643 std::vector<int64_t> var_to_coeff(context_->working_model->variables_size(),
11644 0);
11645
11646 int64_t num_blocks = 0;
11647 int64_t nz_reduction = 0;
11648 for (int i = 0; i < sorted_linear.size(); ++i) {
11649 const int c = sorted_linear[i];
11650 if (c < 0) continue;
11651 if (timer.WorkLimitIsReached()) break;
11652
11653 for (const int var : var_to_coeff_non_zeros) {
11654 var_to_coeff[var] = 0;
11655 }
11656 var_to_coeff_non_zeros.clear();
11657 {
11658 const ConstraintProto& ct = context_->working_model->constraints(c);
11659 const int num_terms = ct.linear().vars().size();
11660 timer.TrackSimpleLoop(num_terms);
11661 for (int k = 0; k < num_terms; ++k) {
11662 const int var = ct.linear().vars(k);
11663 var_to_coeff[var] = ct.linear().coeffs(k);
11664 var_to_coeff_non_zeros.push_back(var);
11665 }
11666 }
11667
11668 // Look for an initial overlap big enough.
11669 //
11670 // Note that because we construct it incrementally, we need the first two
11671 // constraint to have an overlap of at least half this.
11672 int saved_nz = 100;
11673 std::vector<int> used_sorted_linear = {i};
11674 std::vector<std::pair<int, int64_t>> block = {{c, 1}};
11675 std::vector<std::pair<int, int64_t>> common_part;
11676 std::vector<std::pair<int, int>> old_matches;
11677
11678 for (int j = 0; j < sorted_linear.size(); ++j) {
11679 if (i == j) continue;
11680 const int other_c = sorted_linear[j];
11681 if (other_c < 0) continue;
11682 const ConstraintProto& ct = context_->working_model->constraints(other_c);
11683
11684 // No need to continue if linear is not large enough.
11685 const int num_terms = ct.linear().vars().size();
11686 const int best_saved_nz =
11687 ComputeNonZeroReduction(block.size() + 1, num_terms);
11688 if (best_saved_nz <= saved_nz) break;
11689
11690 // This is the hot loop here.
11691 timer.TrackSimpleLoop(num_terms);
11692 common_part.clear();
11693 for (int k = 0; k < num_terms; ++k) {
11694 const int var = ct.linear().vars(k);
11695 if (var_to_coeff[var] == ct.linear().coeffs(k)) {
11696 common_part.push_back({var, ct.linear().coeffs(k)});
11697 }
11698 }
11699
11700 // We replace (new_block_size) * (common_size) by
11701 // 1/ and equation of size common_size + 1
11702 // 2/ new_block_size variable
11703 // So new_block_size * common_size - common_size - 1 - new_block_size
11704 // which is (new_block_size - 1) * (common_size - 1) - 2;
11705 const int64_t new_saved_nz =
11706 ComputeNonZeroReduction(block.size() + 1, common_part.size());
11707 if (new_saved_nz > saved_nz) {
11708 saved_nz = new_saved_nz;
11709 used_sorted_linear.push_back(j);
11710 block.push_back({other_c, 1});
11711
11712 // Rebuild the map.
11713 // TODO(user): We could only clear the non-common part.
11714 for (const int var : var_to_coeff_non_zeros) {
11715 var_to_coeff[var] = 0;
11716 }
11717 var_to_coeff_non_zeros.clear();
11718 for (const auto [var, coeff] : common_part) {
11719 var_to_coeff[var] = coeff;
11720 var_to_coeff_non_zeros.push_back(var);
11721 }
11722 } else {
11723 if (common_part.size() > 1) {
11724 old_matches.push_back({j, common_part.size()});
11725 }
11726 }
11727 }
11728
11729 // Introduce a new variable = common_part.
11730 // Use it in all linear constraint.
11731 if (block.size() > 1) {
11732 // Try to extend with exact matches that were skipped.
11733 const int match_size = var_to_coeff_non_zeros.size();
11734 for (const auto [index, old_match_size] : old_matches) {
11735 if (old_match_size < match_size) continue;
11736
11737 int new_match_size = 0;
11738 const int other_c = sorted_linear[index];
11739 const ConstraintProto& ct =
11740 context_->working_model->constraints(other_c);
11741 const int num_terms = ct.linear().vars().size();
11742 for (int k = 0; k < num_terms; ++k) {
11743 if (var_to_coeff[ct.linear().vars(k)] == ct.linear().coeffs(k)) {
11744 ++new_match_size;
11745 }
11746 }
11747 if (new_match_size == match_size) {
11748 context_->UpdateRuleStats(
11749 "linear matrix: common horizontal rectangle extension");
11750 used_sorted_linear.push_back(index);
11751 block.push_back({other_c, 1});
11752 }
11753 }
11754
11755 // TODO(user): avoid creating the map? this is not visible in profile
11756 // though since we only do it when a reduction is performed.
11757 absl::flat_hash_map<int, int64_t> coeff_map;
11758 for (const int var : var_to_coeff_non_zeros) {
11759 coeff_map[var] = var_to_coeff[var];
11760 }
11761 if (!RemoveCommonPart(coeff_map, block, helper)) continue;
11762
11763 ++num_blocks;
11764 nz_reduction += ComputeNonZeroReduction(block.size(), coeff_map.size());
11765 context_->UpdateRuleStats("linear matrix: common horizontal rectangle");
11766 for (const int i : used_sorted_linear) sorted_linear[i] = -1;
11767 }
11768 }
11769
11770 timer.AddCounter("blocks", num_blocks);
11771 timer.AddCounter("saved_nz", nz_reduction);
11772 timer.AddCounter("linears", sorted_linear.size());
11773 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11774}
11775
11776// Find two linear constraints of the form:
11777// - term1 + identical_terms = rhs1
11778// - term2 + identical_terms = rhs2
11779// This allows to infer an affine relation, and remove one constraint and one
11780// variable.
11781void CpModelPresolver::FindAlmostIdenticalLinearConstraints() {
11782 if (time_limit_->LimitReached()) return;
11783 if (context_->ModelIsUnsat()) return;
11784
11785 // Work tracking is required, since in the worst case (n identical
11786 // constraints), we are in O(n^3). In practice we are way faster though. And
11787 // identical constraints should have already be removed when we call this.
11788 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11789
11790 // Only keep non-enforced linear equality of size > 2. Sort by size.
11791 std::vector<std::pair<int, int>> to_sort;
11792 const int num_constraints = context_->working_model->constraints_size();
11793 for (int c = 0; c < num_constraints; ++c) {
11794 const ConstraintProto& ct = context_->working_model->constraints(c);
11795 if (!IsLinearEqualityConstraint(ct)) continue;
11796 if (ct.linear().vars().size() <= 2) continue;
11797
11798 // Our canonicalization should sort constraints, we skip non-canonical ones.
11799 if (!std::is_sorted(ct.linear().vars().begin(), ct.linear().vars().end())) {
11800 continue;
11801 }
11802
11803 to_sort.push_back({ct.linear().vars().size(), c});
11804 }
11805 std::sort(to_sort.begin(), to_sort.end());
11806
11807 // One watcher data structure.
11808 // This is similar to what is used by the inclusion detector.
11809 std::vector<int> var_to_clear;
11810 std::vector<std::vector<std::pair<int, int64_t>>> var_to_ct_coeffs_;
11811 const int num_variables = context_->working_model->variables_size();
11812 var_to_ct_coeffs_.resize(num_variables);
11813
11814 int end;
11815 int num_tested_pairs = 0;
11816 int num_affine_relations = 0;
11817 for (int start = 0; start < to_sort.size(); start = end) {
11818 // Split by identical size.
11819 end = start + 1;
11820 const int length = to_sort[start].first;
11821 for (; end < to_sort.size(); ++end) {
11822 if (to_sort[end].first != length) break;
11823 }
11824 const int span_size = end - start;
11825 if (span_size == 1) continue;
11826
11827 // Watch one term of each constraint randomly.
11828 for (const int var : var_to_clear) var_to_ct_coeffs_[var].clear();
11829 var_to_clear.clear();
11830 for (int i = start; i < end; ++i) {
11831 const int c = to_sort[i].second;
11832 const LinearConstraintProto& lin =
11833 context_->working_model->constraints(c).linear();
11834 const int index =
11835 absl::Uniform<int>(*context_->random(), 0, lin.vars().size());
11836 const int var = lin.vars(index);
11837 if (var_to_ct_coeffs_[var].empty()) var_to_clear.push_back(var);
11838 var_to_ct_coeffs_[var].push_back({c, lin.coeffs(index)});
11839 }
11840
11841 // For each constraint, try other constraints that have at least one term in
11842 // common with the same coeff. Note that for two constraint of size 3, we
11843 // will miss a working pair only if we both watch the variable that is
11844 // different. So only with a probability (1/3)^2. Since we call this more
11845 // than once per presolve, we should be mostly good. For larger constraint,
11846 // we shouldn't miss much.
11847 for (int i1 = start; i1 < end; ++i1) {
11848 if (timer.WorkLimitIsReached()) break;
11849 const int c1 = to_sort[i1].second;
11850 const LinearConstraintProto& lin1 =
11851 context_->working_model->constraints(c1).linear();
11852 bool skip = false;
11853 for (int i = 0; !skip && i < lin1.vars().size(); ++i) {
11854 for (const auto [c2, coeff2] : var_to_ct_coeffs_[lin1.vars(i)]) {
11855 if (c2 == c1) continue;
11856
11857 // TODO(user): we could easily deal with * -1 or other multiples.
11858 if (coeff2 != lin1.coeffs(i)) continue;
11859 if (timer.WorkLimitIsReached()) break;
11860
11861 // Skip if we processed this earlier and deleted it.
11862 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
11863 if (ct2.constraint_case() != ConstraintProto::kLinear) continue;
11864 const LinearConstraintProto& lin2 =
11865 context_->working_model->constraints(c2).linear();
11866 if (lin2.vars().size() != length) continue;
11867
11868 // TODO(user): In practice LinearsDifferAtOneTerm() will abort
11869 // early if the constraints differ early, so we are even faster than
11870 // this.
11871 timer.TrackSimpleLoop(length);
11872
11873 ++num_tested_pairs;
11874 if (LinearsDifferAtOneTerm(lin1, lin2)) {
11875 // The two equalities only differ at one term !
11876 // do c1 -= c2 and presolve c1 right away.
11877 // We should detect new affine relation and remove it.
11878 auto* to_modify = context_->working_model->mutable_constraints(c1);
11880 -1, context_->working_model->constraints(c2), to_modify)) {
11881 continue;
11882 }
11883
11884 // Affine will be of size 2, but we might also have the same
11885 // variable with different coeff in both constraint, in which case
11886 // the linear will be of size 1.
11887 DCHECK_LE(to_modify->linear().vars().size(), 2);
11888
11889 ++num_affine_relations;
11890 context_->UpdateRuleStats(
11891 "linear: advanced affine relation from 2 constraints.");
11892
11893 // We should stop processing c1 since it should be empty afterward.
11894 DivideLinearByGcd(to_modify);
11895 PresolveSmallLinear(to_modify);
11896 context_->UpdateConstraintVariableUsage(c1);
11897 skip = true;
11898 break;
11899 }
11900 }
11901 }
11902 }
11903 }
11904
11905 timer.AddCounter("num_tested_pairs", num_tested_pairs);
11906 timer.AddCounter("found", num_affine_relations);
11907 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11908}
11909
11910void CpModelPresolver::ExtractEncodingFromLinear() {
11911 if (time_limit_->LimitReached()) return;
11912 if (context_->ModelIsUnsat()) return;
11913 if (context_->params().presolve_inclusion_work_limit() == 0) return;
11914 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11915
11916 // TODO(user): compute on the fly instead of temporary storing variables?
11917 std::vector<int> relevant_constraints;
11918 CompactVectorVector<int> storage;
11919 InclusionDetector detector(storage, time_limit_);
11920 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
11921
11922 // Loop over the constraints and fill the structures above.
11923 //
11924 // TODO(user): Ideally we want to process exactly_one first in case a
11925 // linear constraint is both included in an at_most_one and an exactly_one.
11926 std::vector<int> vars;
11927 const int num_constraints = context_->working_model->constraints().size();
11928 for (int c = 0; c < num_constraints; ++c) {
11929 const ConstraintProto& ct = context_->working_model->constraints(c);
11930 switch (ct.constraint_case()) {
11931 case ConstraintProto::kAtMostOne: {
11932 vars.clear();
11933 for (const int ref : ct.at_most_one().literals()) {
11934 vars.push_back(PositiveRef(ref));
11935 }
11936 relevant_constraints.push_back(c);
11937 detector.AddPotentialSuperset(storage.Add(vars));
11938 break;
11939 }
11940 case ConstraintProto::kExactlyOne: {
11941 vars.clear();
11942 for (const int ref : ct.exactly_one().literals()) {
11943 vars.push_back(PositiveRef(ref));
11944 }
11945 relevant_constraints.push_back(c);
11946 detector.AddPotentialSuperset(storage.Add(vars));
11947 break;
11948 }
11949 case ConstraintProto::kLinear: {
11950 // We only consider equality with no enforcement.
11951 if (!IsLinearEqualityConstraint(ct)) continue;
11952
11953 // We also want a single non-Boolean.
11954 // Note that this assume the constraint is canonicalized.
11955 bool is_candidate = true;
11956 int num_integers = 0;
11957 vars.clear();
11958 const int num_terms = ct.linear().vars().size();
11959 for (int i = 0; i < num_terms; ++i) {
11960 const int ref = ct.linear().vars(i);
11961 if (context_->CanBeUsedAsLiteral(ref)) {
11962 vars.push_back(PositiveRef(ref));
11963 } else {
11964 ++num_integers;
11965 if (std::abs(ct.linear().coeffs(i)) != 1) {
11966 is_candidate = false;
11967 break;
11968 }
11969 if (num_integers == 2) {
11970 is_candidate = false;
11971 break;
11972 }
11973 }
11974 }
11975
11976 // We ignore cases with just one Boolean as this should be already dealt
11977 // with elsewhere.
11978 if (is_candidate && num_integers == 1 && vars.size() > 1) {
11979 relevant_constraints.push_back(c);
11980 detector.AddPotentialSubset(storage.Add(vars));
11981 }
11982 break;
11983 }
11984 default:
11985 break;
11986 }
11987 }
11988
11989 // Stats.
11990 int64_t num_exactly_one_encodings = 0;
11991 int64_t num_at_most_one_encodings = 0;
11992 int64_t num_literals = 0;
11993 int64_t num_unique_terms = 0;
11994 int64_t num_multiple_terms = 0;
11995
11996 detector.DetectInclusions([&](int subset, int superset) {
11997 const int subset_c = relevant_constraints[subset];
11998 const int superset_c = relevant_constraints[superset];
11999 const ConstraintProto& superset_ct =
12000 context_->working_model->constraints(superset_c);
12001 if (superset_ct.constraint_case() == ConstraintProto::kAtMostOne) {
12002 ++num_at_most_one_encodings;
12003 } else {
12004 ++num_exactly_one_encodings;
12005 }
12006 num_literals += storage[subset].size();
12007 context_->UpdateRuleStats("encoding: extracted from linear");
12008
12009 if (!ProcessEncodingFromLinear(subset_c, superset_ct, &num_unique_terms,
12010 &num_multiple_terms)) {
12011 detector.Stop(); // UNSAT.
12012 }
12013
12014 detector.StopProcessingCurrentSubset();
12015 });
12016
12017 timer.AddCounter("potential_supersets", detector.num_potential_supersets());
12018 timer.AddCounter("potential_subsets", detector.num_potential_subsets());
12019 timer.AddCounter("amo_encodings", num_at_most_one_encodings);
12020 timer.AddCounter("exo_encodings", num_exactly_one_encodings);
12021 timer.AddCounter("unique_terms", num_unique_terms);
12022 timer.AddCounter("multiple_terms", num_multiple_terms);
12023 timer.AddCounter("literals", num_literals);
12024}
12025
12026// Special case: if a literal l appear in exactly two constraints:
12027// - l => var in domain1
12028// - not(l) => var in domain2
12029// then we know that domain(var) is included in domain1 U domain2,
12030// and that the literal l can be removed (and determined at postsolve).
12031//
12032// TODO(user): This could be generalized further to linear of size > 1 if for
12033// example the terms are the same.
12034//
12035// We wait for the model expansion to take place in order to avoid removing
12036// encoding that will later be re-created during expansion.
12037void CpModelPresolver::LookAtVariableWithDegreeTwo(int var) {
12038 CHECK(RefIsPositive(var));
12039 CHECK(context_->ConstraintVariableGraphIsUpToDate());
12040 if (context_->ModelIsUnsat()) return;
12041 if (context_->params().keep_all_feasible_solutions_in_presolve()) return;
12042 if (context_->IsFixed(var)) return;
12043 if (!context_->ModelIsExpanded()) return;
12044 if (!context_->CanBeUsedAsLiteral(var)) return;
12045
12046 // TODO(user): If var is in objective, we might be able to tighten domains.
12047 // ex: enf => x \in [0, 1]
12048 // not(enf) => x \in [1, 2]
12049 // The x can be removed from one place. Maybe just do <=> not in [0,1] with
12050 // dual code?
12051 if (context_->VarToConstraints(var).size() != 2) return;
12052
12053 bool abort = false;
12054 int ct_var = -1;
12055 Domain union_of_domain;
12056 int num_positive = 0;
12057 std::vector<int> constraint_indices_to_remove;
12058 for (const int c : context_->VarToConstraints(var)) {
12059 if (c < 0) {
12060 abort = true;
12061 break;
12062 }
12063 constraint_indices_to_remove.push_back(c);
12064 const ConstraintProto& ct = context_->working_model->constraints(c);
12065 if (ct.enforcement_literal().size() != 1 ||
12066 PositiveRef(ct.enforcement_literal(0)) != var ||
12067 ct.constraint_case() != ConstraintProto::kLinear ||
12068 ct.linear().vars().size() != 1) {
12069 abort = true;
12070 break;
12071 }
12072 if (ct.enforcement_literal(0) == var) ++num_positive;
12073 if (ct_var != -1 && PositiveRef(ct.linear().vars(0)) != ct_var) {
12074 abort = true;
12075 break;
12076 }
12077 ct_var = PositiveRef(ct.linear().vars(0));
12078 union_of_domain = union_of_domain.UnionWith(
12079 ReadDomainFromProto(ct.linear())
12080 .InverseMultiplicationBy(RefIsPositive(ct.linear().vars(0))
12081 ? ct.linear().coeffs(0)
12082 : -ct.linear().coeffs(0)));
12083 }
12084 if (abort) return;
12085 if (num_positive != 1) return;
12086 if (!context_->IntersectDomainWith(ct_var, union_of_domain)) return;
12087
12088 context_->UpdateRuleStats("variables: removable enforcement literal");
12089 absl::c_sort(constraint_indices_to_remove); // For determinism
12090
12091 // Note(user): Only one constraint should be enough given how the postsolve
12092 // work. However that will not work for the case where we postsolve by solving
12093 // the mapping model (debug_postsolve_with_full_solver:true).
12094 for (const int c : constraint_indices_to_remove) {
12095 context_->NewMappingConstraint(context_->working_model->constraints(c),
12096 __FILE__, __LINE__);
12097 context_->working_model->mutable_constraints(c)->Clear();
12098 context_->UpdateConstraintVariableUsage(c);
12099 }
12100 context_->MarkVariableAsRemoved(var);
12101}
12102
12103namespace {
12104
12105absl::Span<const int> AtMostOneOrExactlyOneLiterals(const ConstraintProto& ct) {
12106 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
12107 return {ct.at_most_one().literals()};
12108 } else {
12109 return {ct.exactly_one().literals()};
12110 }
12111}
12112
12113} // namespace
12114
12115void CpModelPresolver::ProcessVariableInTwoAtMostOrExactlyOne(int var) {
12116 DCHECK(RefIsPositive(var));
12117 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
12118 if (context_->ModelIsUnsat()) return;
12119 if (context_->IsFixed(var)) return;
12120 if (context_->VariableWasRemoved(var)) return;
12121 if (!context_->ModelIsExpanded()) return;
12122 if (!context_->CanBeUsedAsLiteral(var)) return;
12123
12124 int64_t cost = 0;
12125 if (context_->VarToConstraints(var).contains(kObjectiveConstraint)) {
12126 if (context_->VarToConstraints(var).size() != 3) return;
12127 cost = context_->ObjectiveMap().at(var);
12128 } else {
12129 if (context_->VarToConstraints(var).size() != 2) return;
12130 }
12131
12132 // We have a variable with a cost (or without) that appear in two constraints.
12133 // We want two at_most_one or exactly_one.
12134 // TODO(user): Also deal with bool_and.
12135 int c1 = -1;
12136 int c2 = -1;
12137 for (const int c : context_->VarToConstraints(var)) {
12138 if (c < 0) continue;
12139 const ConstraintProto& ct = context_->working_model->constraints(c);
12140 if (ct.constraint_case() != ConstraintProto::kAtMostOne &&
12141 ct.constraint_case() != ConstraintProto::kExactlyOne) {
12142 return;
12143 }
12144 if (c1 == -1) {
12145 c1 = c;
12146 } else {
12147 c2 = c;
12148 }
12149 }
12150
12151 // This can happen for variable in a kAffineRelationConstraint.
12152 if (c1 == -1 || c2 == -1) return;
12153
12154 // Tricky: We iterate on a map above, so the order is non-deterministic, we
12155 // do not want that, so we re-order the constraints.
12156 if (c1 > c2) std::swap(c1, c2);
12157
12158 // We can always sum the two constraints.
12159 // If var appear in one and not(var) in the other, the two term cancel out to
12160 // one, so we still have an <= 1 (or eventually a ==1 (see below).
12161 //
12162 // Note that if the constraint are of size one, they can just be preprocessed
12163 // individually and just be removed. So we abort here as the code below
12164 // is incorrect if new_ct is an empty constraint.
12165 context_->tmp_literals.clear();
12166 int c1_ref = std::numeric_limits<int>::min();
12167 const ConstraintProto& ct1 = context_->working_model->constraints(c1);
12168 if (AtMostOneOrExactlyOneLiterals(ct1).size() <= 1) return;
12169 for (const int lit : AtMostOneOrExactlyOneLiterals(ct1)) {
12170 if (PositiveRef(lit) == var) {
12171 c1_ref = lit;
12172 } else {
12173 context_->tmp_literals.push_back(lit);
12174 }
12175 }
12176 int c2_ref = std::numeric_limits<int>::min();
12177 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
12178 if (AtMostOneOrExactlyOneLiterals(ct2).size() <= 1) return;
12179 for (const int lit : AtMostOneOrExactlyOneLiterals(ct2)) {
12180 if (PositiveRef(lit) == var) {
12181 c2_ref = lit;
12182 } else {
12183 context_->tmp_literals.push_back(lit);
12184 }
12185 }
12186 DCHECK_NE(c1_ref, std::numeric_limits<int>::min());
12187 DCHECK_NE(c2_ref, std::numeric_limits<int>::min());
12188 if (c1_ref != NegatedRef(c2_ref)) return;
12189
12190 // If the cost is non-zero, we can use an exactly one to make it zero.
12191 // Use that exactly one in the postsolve to recover the value of var.
12192 int64_t cost_shift = 0;
12193 absl::Span<const int> literals;
12194 if (ct1.constraint_case() == ConstraintProto::kExactlyOne) {
12195 cost_shift = RefIsPositive(c1_ref) ? cost : -cost;
12196 literals = ct1.exactly_one().literals();
12197 } else if (ct2.constraint_case() == ConstraintProto::kExactlyOne) {
12198 cost_shift = RefIsPositive(c2_ref) ? cost : -cost;
12199 literals = ct2.exactly_one().literals();
12200 } else {
12201 // Dual argument. The one with a negative cost can be transformed to
12202 // an exactly one.
12203 // Tricky: if there is a cost, we don't want the objective to be
12204 // constraining to be able to do that.
12205 if (context_->params().keep_all_feasible_solutions_in_presolve()) return;
12206 if (context_->params().keep_symmetry_in_presolve()) return;
12207 if (cost != 0 && context_->ObjectiveDomainIsConstraining()) return;
12208
12209 if (RefIsPositive(c1_ref) == (cost < 0)) {
12210 cost_shift = RefIsPositive(c1_ref) ? cost : -cost;
12211 literals = ct1.at_most_one().literals();
12212 } else {
12213 cost_shift = RefIsPositive(c2_ref) ? cost : -cost;
12214 literals = ct2.at_most_one().literals();
12215 }
12216 }
12217
12218 if (!context_->ShiftCostInExactlyOne(literals, cost_shift)) return;
12219 DCHECK(!context_->ObjectiveMap().contains(var));
12220 context_->NewMappingConstraint(__FILE__, __LINE__)
12221 ->mutable_exactly_one()
12222 ->mutable_literals()
12223 ->Assign(literals.begin(), literals.end());
12224
12225 // We can now replace the two constraint by a single one, and delete var!
12226 const int new_ct_index = context_->working_model->constraints().size();
12227 ConstraintProto* new_ct = context_->working_model->add_constraints();
12228 if (ct1.constraint_case() == ConstraintProto::kExactlyOne &&
12229 ct2.constraint_case() == ConstraintProto::kExactlyOne) {
12230 for (const int lit : context_->tmp_literals) {
12231 new_ct->mutable_exactly_one()->add_literals(lit);
12232 }
12233 } else {
12234 // At most one here is enough: if all zero, we can satisfy one of the
12235 // two exactly one at postsolve.
12236 for (const int lit : context_->tmp_literals) {
12237 new_ct->mutable_at_most_one()->add_literals(lit);
12238 }
12239 }
12240
12241 context_->UpdateNewConstraintsVariableUsage();
12242 context_->working_model->mutable_constraints(c1)->Clear();
12243 context_->UpdateConstraintVariableUsage(c1);
12244 context_->working_model->mutable_constraints(c2)->Clear();
12245 context_->UpdateConstraintVariableUsage(c2);
12246
12247 context_->UpdateRuleStats(
12248 "at_most_one: resolved two constraints with opposite literal");
12249 context_->MarkVariableAsRemoved(var);
12250
12251 // TODO(user): If the merged list contains duplicates or literal that are
12252 // negation of other, we need to deal with that right away. For some reason
12253 // something is not robust to that it seems. Investigate & fix!
12254 DCHECK_NE(new_ct->constraint_case(), ConstraintProto::CONSTRAINT_NOT_SET);
12255 if (PresolveAtMostOrExactlyOne(new_ct)) {
12256 context_->UpdateConstraintVariableUsage(new_ct_index);
12257 }
12258}
12259
12260// If we have a bunch of constraint of the form literal => Y \in domain and
12261// another constraint Y = f(X), we can remove Y, that constraint, and transform
12262// all linear1 from constraining Y to constraining X.
12263//
12264// We can for instance do it for Y = abs(X) or Y = X^2 easily. More complex
12265// function might be trickier.
12266//
12267// Note that we can't always do it in the reverse direction though!
12268// If we have l => X = -1, we can't transfer that to abs(X) for instance, since
12269// X=1 will also map to abs(-1). We can only do it if for all implied domain D
12270// we have f^-1(f(D)) = D, which is not easy to check.
12271void CpModelPresolver::MaybeTransferLinear1ToAnotherVariable(int var) {
12272 // Find the extra constraint and do basic CHECKs.
12273 int other_c;
12274 int num_others = 0;
12275 std::vector<int> to_rewrite;
12276 for (const int c : context_->VarToConstraints(var)) {
12277 if (c >= 0) {
12278 const ConstraintProto& ct = context_->working_model->constraints(c);
12279 if (ct.constraint_case() == ConstraintProto::kLinear &&
12280 ct.linear().vars().size() == 1) {
12281 to_rewrite.push_back(c);
12282 continue;
12283 }
12284 }
12285 ++num_others;
12286 other_c = c;
12287 }
12288 if (num_others != 1) return;
12289 if (other_c < 0) return;
12290
12291 // In general constraint with more than two variable can't be removed.
12292 // Similarly for linear2 with non-fixed rhs as we would need to check the form
12293 // of all implied domain.
12294 const auto& other_ct = context_->working_model->constraints(other_c);
12295 if (context_->ConstraintToVars(other_c).size() != 2 ||
12296 !other_ct.enforcement_literal().empty() ||
12297 other_ct.constraint_case() == ConstraintProto::kLinear) {
12298 return;
12299 }
12300
12301 // This will be the rewriting function. It takes the implied domain of var
12302 // from linear1, and return a pair {new_var, new_var_implied_domain}.
12303 std::function<std::pair<int, Domain>(const Domain& implied)> transfer_f =
12304 nullptr;
12305
12306 // We only support a few cases.
12307 //
12308 // TODO(user): implement more! Note that the linear2 case was tempting, but if
12309 // we don't have an equality, we can't transfer, and if we do, we actually
12310 // have affine equivalence already.
12311 if (other_ct.constraint_case() == ConstraintProto::kLinMax &&
12312 other_ct.lin_max().target().vars().size() == 1 &&
12313 other_ct.lin_max().target().vars(0) == var &&
12314 std::abs(other_ct.lin_max().target().coeffs(0)) == 1 &&
12315 IsAffineIntAbs(other_ct)) {
12316 context_->UpdateRuleStats("linear1: transferred from abs(X) to X");
12317 const LinearExpressionProto& target = other_ct.lin_max().target();
12318 const LinearExpressionProto& expr = other_ct.lin_max().exprs(0);
12319 transfer_f = [target = target, expr = expr](const Domain& implied) {
12320 Domain target_domain =
12321 implied.ContinuousMultiplicationBy(target.coeffs(0))
12322 .AdditionWith(Domain(target.offset()));
12323 target_domain = target_domain.IntersectionWith(
12324 Domain(0, std::numeric_limits<int64_t>::max()));
12325
12326 // We have target = abs(expr).
12327 const Domain expr_domain =
12328 target_domain.UnionWith(target_domain.Negation());
12329 const Domain new_domain = expr_domain.AdditionWith(Domain(-expr.offset()))
12330 .InverseMultiplicationBy(expr.coeffs(0));
12331 return std::make_pair(expr.vars(0), new_domain);
12332 };
12333 }
12334
12335 if (transfer_f == nullptr) {
12336 context_->UpdateRuleStats(
12337 "TODO linear1: appear in only one extra 2-var constraint");
12338 return;
12339 }
12340
12341 // Applies transfer_f to all linear1.
12342 std::sort(to_rewrite.begin(), to_rewrite.end());
12343 const Domain var_domain = context_->DomainOf(var);
12344 for (const int c : to_rewrite) {
12345 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12346 if (ct->linear().vars(0) != var || ct->linear().coeffs(0) != 1) {
12347 // This shouldn't happen.
12348 LOG(INFO) << "Aborted in MaybeTransferLinear1ToAnotherVariable()";
12349 return;
12350 }
12351
12352 const Domain implied =
12353 var_domain.IntersectionWith(ReadDomainFromProto(ct->linear()));
12354 auto [new_var, new_domain] = transfer_f(implied);
12355 const Domain current = context_->DomainOf(new_var);
12356 new_domain = new_domain.IntersectionWith(current);
12357 if (new_domain.IsEmpty()) {
12358 if (!MarkConstraintAsFalse(ct)) return;
12359 } else if (new_domain == current) {
12360 ct->Clear();
12361 } else {
12362 ct->mutable_linear()->set_vars(0, new_var);
12363 FillDomainInProto(new_domain, ct->mutable_linear());
12364 }
12365 context_->UpdateConstraintVariableUsage(c);
12366 }
12367
12368 // Copy other_ct to the mapping model and delete var!
12369 context_->NewMappingConstraint(other_ct, __FILE__, __LINE__);
12370 context_->working_model->mutable_constraints(other_c)->Clear();
12371 context_->UpdateConstraintVariableUsage(other_c);
12372 context_->MarkVariableAsRemoved(var);
12373}
12374
12375// TODO(user): We can still remove the variable even if we want to keep
12376// all feasible solutions for the cases when we have a full encoding.
12377// Similarly this shouldn't break symmetry, but we do need to do it for all
12378// symmetric variable at once.
12379//
12380// TODO(user): In fixed search, we disable this rule because we don't update
12381// the search strategy, but for some strategy we could.
12382//
12383// TODO(user): The hint might get lost if the encoding was created during
12384// the presolve.
12385void CpModelPresolver::ProcessVariableOnlyUsedInEncoding(int var) {
12386 if (context_->ModelIsUnsat()) return;
12387 if (context_->params().keep_all_feasible_solutions_in_presolve()) return;
12388 if (context_->params().keep_symmetry_in_presolve()) return;
12389 if (context_->IsFixed(var)) return;
12390 if (context_->VariableWasRemoved(var)) return;
12391 if (context_->CanBeUsedAsLiteral(var)) return;
12392 if (context_->params().search_branching() == SatParameters::FIXED_SEARCH) {
12393 return;
12394 }
12395
12396 if (!context_->VariableIsOnlyUsedInEncodingAndMaybeInObjective(var)) {
12397 if (context_->VariableIsOnlyUsedInLinear1AndOneExtraConstraint(var)) {
12398 MaybeTransferLinear1ToAnotherVariable(var);
12399 return;
12400 }
12401 return;
12402 }
12403
12404 // If a variable var only appear in enf => var \in domain and in the
12405 // objective, we can remove its costs and the variable/constraint by
12406 // transferring part of the cost to the enforcement.
12407 //
12408 // More generally, we can reduce the domain to just two values. Later this
12409 // will be replaced by a Boolean, and the equivalence to the enforcement
12410 // literal will be added if it is unique.
12411 //
12412 // TODO(user): maybe we should do more here rather than delaying some
12413 // reduction. But then it is more code.
12414 if (context_->VariableWithCostIsUniqueAndRemovable(var)) {
12415 int unique_c = -1;
12416 for (const int c : context_->VarToConstraints(var)) {
12417 if (c < 0) continue;
12418 CHECK_EQ(unique_c, -1);
12419 unique_c = c;
12420 }
12421 CHECK_NE(unique_c, -1);
12422 const ConstraintProto& ct = context_->working_model->constraints(unique_c);
12423 const int64_t cost = context_->ObjectiveCoeff(var);
12424 if (ct.linear().vars(0) == var) {
12425 const Domain implied = ReadDomainFromProto(ct.linear())
12426 .InverseMultiplicationBy(ct.linear().coeffs(0))
12427 .IntersectionWith(context_->DomainOf(var));
12428 if (implied.IsEmpty()) {
12429 if (!MarkConstraintAsFalse(
12430 context_->working_model->mutable_constraints(unique_c))) {
12431 return;
12432 }
12433 context_->UpdateConstraintVariableUsage(unique_c);
12434 return;
12435 }
12436
12437 int64_t value1, value2;
12438 if (cost == 0) {
12439 context_->UpdateRuleStats("variables: fix singleton var in linear1");
12440 return (void)context_->IntersectDomainWith(var, Domain(implied.Min()));
12441 } else if (cost > 0) {
12442 value1 = context_->MinOf(var);
12443 value2 = implied.Min();
12444 } else {
12445 value1 = context_->MaxOf(var);
12446 value2 = implied.Max();
12447 }
12448
12449 // Nothing else to do in this case, the constraint will be reduced to
12450 // a pure Boolean constraint later.
12451 context_->UpdateRuleStats("variables: reduced domain to two values");
12452 // If the hint enforces `ct`, then the hint of `var` must be in the
12453 // implied domain. In any case its new value must not increase the
12454 // objective value (the objective domain is non-constraining, but this
12455 // only guarantees that `var` can freely *decrease* the objective). The
12456 // code below ensures this (`value2` is the 'cheapest' value the implied
12457 // domain, and `value1` the cheapest value in the variable's domain).
12458 solution_crush_.SetVarToConditionalValue(var, ct.enforcement_literal(),
12459 value2, value1);
12460 return (void)context_->IntersectDomainWith(
12461 var, Domain::FromValues({value1, value2}));
12462 }
12463 }
12464
12465 // We can currently only deal with the case where all encoding constraint
12466 // are of the form literal => var ==/!= value.
12467 // If they are more complex linear1 involved, we just abort.
12468 //
12469 // TODO(user): Also deal with the case all >= or <= where we can add a
12470 // serie of implication between all involved literals.
12471 absl::flat_hash_set<int64_t> values_set;
12472 absl::flat_hash_map<int64_t, std::vector<int>> value_to_equal_literals;
12473 absl::flat_hash_map<int64_t, std::vector<int>> value_to_not_equal_literals;
12474 bool abort = false;
12475 for (const int c : context_->VarToConstraints(var)) {
12476 if (c < 0) continue;
12477 const ConstraintProto& ct = context_->working_model->constraints(c);
12478 CHECK_EQ(ct.constraint_case(), ConstraintProto::kLinear);
12479 CHECK_EQ(ct.linear().vars().size(), 1);
12480 int64_t coeff = ct.linear().coeffs(0);
12481 if (std::abs(coeff) != 1 || ct.enforcement_literal().size() != 1) {
12482 abort = true;
12483 break;
12484 }
12485 if (!RefIsPositive(ct.linear().vars(0))) coeff *= 1;
12486 const int var = PositiveRef(ct.linear().vars(0));
12487 const Domain var_domain = context_->DomainOf(var);
12488 const Domain rhs = ReadDomainFromProto(ct.linear())
12490 .IntersectionWith(var_domain);
12491 if (rhs.IsEmpty()) {
12492 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
12493 return;
12494 }
12495 return;
12496 } else if (rhs.IsFixed()) {
12497 if (!var_domain.Contains(rhs.FixedValue())) {
12498 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
12499 return;
12500 }
12501 } else {
12502 values_set.insert(rhs.FixedValue());
12503 value_to_equal_literals[rhs.FixedValue()].push_back(
12504 ct.enforcement_literal(0));
12505 }
12506 } else {
12507 const Domain complement = var_domain.IntersectionWith(rhs.Complement());
12508 if (complement.IsEmpty()) {
12509 // TODO(user): This should be dealt with elsewhere.
12510 abort = true;
12511 break;
12512 }
12513 if (complement.IsFixed()) {
12514 if (var_domain.Contains(complement.FixedValue())) {
12515 values_set.insert(complement.FixedValue());
12516 value_to_not_equal_literals[complement.FixedValue()].push_back(
12517 ct.enforcement_literal(0));
12518 }
12519 } else {
12520 abort = true;
12521 break;
12522 }
12523 }
12524 }
12525 if (abort) {
12526 context_->UpdateRuleStats("TODO variables: only used in linear1.");
12527 return;
12528 } else if (value_to_not_equal_literals.empty() &&
12529 value_to_equal_literals.empty()) {
12530 // This is just a variable not used anywhere, it should be removed by
12531 // another part of the presolve.
12532 return;
12533 }
12534
12535 // For determinism, sort all the encoded values first.
12536 std::vector<int64_t> encoded_values(values_set.begin(), values_set.end());
12537 std::sort(encoded_values.begin(), encoded_values.end());
12538 CHECK(!encoded_values.empty());
12539 const bool is_fully_encoded =
12540 encoded_values.size() == context_->DomainOf(var).Size();
12541
12542 // Link all Boolean in our linear1 to the encoding literals. Note that we
12543 // should hopefully already have detected such literal before and this
12544 // should add trivial implications.
12545 for (const int64_t v : encoded_values) {
12546 const int encoding_lit = context_->GetOrCreateVarValueEncoding(var, v);
12547 const auto eq_it = value_to_equal_literals.find(v);
12548 if (eq_it != value_to_equal_literals.end()) {
12549 absl::c_sort(eq_it->second);
12550 for (const int lit : eq_it->second) {
12551 context_->AddImplication(lit, encoding_lit);
12552 }
12553 }
12554 const auto neq_it = value_to_not_equal_literals.find(v);
12555 if (neq_it != value_to_not_equal_literals.end()) {
12556 absl::c_sort(neq_it->second);
12557 for (const int lit : neq_it->second) {
12558 context_->AddImplication(lit, NegatedRef(encoding_lit));
12559 }
12560 }
12561 }
12562 context_->UpdateNewConstraintsVariableUsage();
12563
12564 // This is the set of other values.
12565 Domain other_values;
12566 if (!is_fully_encoded) {
12567 other_values = context_->DomainOf(var).IntersectionWith(
12568 Domain::FromValues(encoded_values).Complement());
12569 }
12570
12571 // Update the objective if needed. Note that this operation can fail if
12572 // the new expression result in potential overflow.
12573 if (context_->VarToConstraints(var).contains(kObjectiveConstraint)) {
12574 int64_t min_value;
12575 const int64_t obj_coeff = context_->ObjectiveMap().at(var);
12576 if (is_fully_encoded) {
12577 // We substract the min_value from all coefficients.
12578 // This should reduce the objective size and helps with the bounds.
12579 min_value =
12580 obj_coeff > 0 ? encoded_values.front() : encoded_values.back();
12581 } else {
12582 // Tricky: We cannot just choose an arbitrary value if the objective has
12583 // a restrictive domain!
12584 if (context_->ObjectiveDomainIsConstraining() &&
12585 !other_values.IsFixed()) {
12586 context_->UpdateRuleStats(
12587 "TODO variables: only used in objective and in encoding");
12588 return;
12589 }
12590
12591 // Tricky: If the variable is not fully encoded, then when all
12592 // partial encoding literal are false, it must take the "best" value
12593 // in other_values. That depend on the sign of the objective coeff.
12594 //
12595 // We also restrict other value so that the postsolve code below
12596 // will fix the variable to the correct value when this happen.
12597 other_values =
12598 Domain(obj_coeff > 0 ? other_values.Min() : other_values.Max());
12599 min_value = other_values.FixedValue();
12600 }
12601
12602 // Checks for overflow before trying to substitute the variable in the
12603 // objective.
12604 int64_t accumulated = std::abs(min_value);
12605 for (const int64_t value : encoded_values) {
12606 accumulated = CapAdd(accumulated, std::abs(CapSub(value, min_value)));
12607 if (accumulated == std::numeric_limits<int64_t>::max()) {
12608 context_->UpdateRuleStats(
12609 "TODO variables: only used in objective and in encoding");
12610 return;
12611 }
12612 }
12613
12614 ConstraintProto encoding_ct;
12615 LinearConstraintProto* linear = encoding_ct.mutable_linear();
12616 const int64_t coeff_in_equality = -1;
12617 linear->add_vars(var);
12618 linear->add_coeffs(coeff_in_equality);
12619
12620 linear->add_domain(-min_value);
12621 linear->add_domain(-min_value);
12622 for (const int64_t value : encoded_values) {
12623 if (value == min_value) continue;
12624 const int enf = context_->GetOrCreateVarValueEncoding(var, value);
12625 const int64_t coeff = value - min_value;
12626 if (RefIsPositive(enf)) {
12627 linear->add_vars(enf);
12628 linear->add_coeffs(coeff);
12629 } else {
12630 // (1 - var) * coeff;
12631 linear->set_domain(0, encoding_ct.linear().domain(0) - coeff);
12632 linear->set_domain(1, encoding_ct.linear().domain(1) - coeff);
12633 linear->add_vars(PositiveRef(enf));
12634 linear->add_coeffs(-coeff);
12635 }
12636 }
12637 if (!context_->SubstituteVariableInObjective(var, coeff_in_equality,
12638 encoding_ct)) {
12639 context_->UpdateRuleStats(
12640 "TODO variables: only used in objective and in encoding");
12641 return;
12642 }
12643 context_->UpdateRuleStats(
12644 "variables: only used in objective and in encoding");
12645 } else {
12646 context_->UpdateRuleStats("variables: only used in encoding");
12647 }
12648
12649 // Clear all involved constraint.
12650 {
12651 std::vector<int> to_clear;
12652 for (const int c : context_->VarToConstraints(var)) {
12653 if (c >= 0) to_clear.push_back(c);
12654 }
12655 absl::c_sort(to_clear);
12656 for (const int c : to_clear) {
12657 if (c < 0) continue;
12658 context_->working_model->mutable_constraints(c)->Clear();
12659 context_->UpdateConstraintVariableUsage(c);
12660 }
12661 }
12662
12663 // See below. This is used for the mapping constraint.
12664 int64_t special_value = 0;
12665
12666 // This must be done after we removed all the constraint containing var.
12667 ConstraintProto* new_ct = context_->working_model->add_constraints();
12668 if (is_fully_encoded) {
12669 // The encoding is full: add an exactly one.
12670 for (const int64_t value : encoded_values) {
12671 new_ct->mutable_exactly_one()->add_literals(
12672 context_->GetOrCreateVarValueEncoding(var, value));
12673 }
12674 PresolveExactlyOne(new_ct);
12675 } else {
12676 // Add an at most one.
12677 for (const int64_t value : encoded_values) {
12678 new_ct->mutable_at_most_one()->add_literals(
12679 context_->GetOrCreateVarValueEncoding(var, value));
12680 }
12681 PresolveAtMostOne(new_ct);
12682
12683 // Pick a "special_value" that our variable can take when all the bi are
12684 // false.
12685 special_value = other_values.SmallestValue();
12686 }
12687 if (context_->ModelIsUnsat()) return;
12688
12689 // To simplify the postsolve, we output a single constraint to infer X from
12690 // the bi: X = sum bi * (Vi - special_value) + special_value
12691 ConstraintProto* mapping_ct =
12692 context_->NewMappingConstraint(__FILE__, __LINE__);
12693 mapping_ct->mutable_linear()->add_vars(var);
12694 mapping_ct->mutable_linear()->add_coeffs(1);
12695 int64_t offset = special_value;
12696 for (const int64_t value : encoded_values) {
12697 const int literal = context_->GetOrCreateVarValueEncoding(var, value);
12698 const int64_t coeff = (value - special_value);
12699 if (RefIsPositive(literal)) {
12700 mapping_ct->mutable_linear()->add_vars(literal);
12701 mapping_ct->mutable_linear()->add_coeffs(-coeff);
12702 } else {
12703 offset += coeff;
12704 mapping_ct->mutable_linear()->add_vars(PositiveRef(literal));
12705 mapping_ct->mutable_linear()->add_coeffs(coeff);
12706 }
12707 }
12708 mapping_ct->mutable_linear()->add_domain(offset);
12709 mapping_ct->mutable_linear()->add_domain(offset);
12710
12711 context_->UpdateNewConstraintsVariableUsage();
12712 context_->MarkVariableAsRemoved(var);
12713}
12714
12715void CpModelPresolver::TryToSimplifyDomain(int var) {
12716 DCHECK(RefIsPositive(var));
12717 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
12718 if (context_->ModelIsUnsat()) return;
12719 if (context_->IsFixed(var)) return;
12720 if (context_->VariableWasRemoved(var)) return;
12721 if (context_->VariableIsNotUsedAnymore(var)) return;
12722
12723 const AffineRelation::Relation r = context_->GetAffineRelation(var);
12724 if (r.representative != var) return;
12725
12726 // Only process discrete domain.
12727 const Domain& domain = context_->DomainOf(var);
12728
12729 // Special case for non-Boolean domain of size 2.
12730 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
12731 context_->CanonicalizeDomainOfSizeTwo(var);
12732 return;
12733 }
12734
12735 if (domain.NumIntervals() != domain.Size()) return;
12736
12737 const int64_t var_min = domain.Min();
12738 int64_t gcd = domain[1].start - var_min;
12739 for (int index = 2; index < domain.NumIntervals(); ++index) {
12740 const ClosedInterval& i = domain[index];
12741 DCHECK_EQ(i.start, i.end);
12742 const int64_t shifted_value = i.start - var_min;
12743 DCHECK_GT(shifted_value, 0);
12744
12745 gcd = std::gcd(gcd, shifted_value);
12746 if (gcd == 1) break;
12747 }
12748 if (gcd == 1) return;
12749
12750 // This does all the work since var * 1 % gcd = var_min % gcd.
12751 context_->CanonicalizeAffineVariable(var, 1, gcd, var_min);
12752}
12753
12754// Adds all affine relations to our model for the variables that are still used.
12755void CpModelPresolver::EncodeAllAffineRelations() {
12756 int64_t num_added = 0;
12757 for (int var = 0; var < context_->working_model->variables_size(); ++var) {
12758 if (context_->IsFixed(var)) continue;
12759
12760 const AffineRelation::Relation r = context_->GetAffineRelation(var);
12761 if (r.representative == var) continue;
12762
12763 // TODO(user): It seems some affine relation are still removable at this
12764 // stage even though they should be removed inside PresolveToFixPoint().
12765 // Investigate. For now, we just remove such relations.
12766 if (context_->VariableIsNotUsedAnymore(var)) continue;
12767 if (!PresolveAffineRelationIfAny(var)) break;
12768 if (context_->VariableIsNotUsedAnymore(var)) continue;
12769 if (context_->IsFixed(var)) continue;
12770
12771 ++num_added;
12772 ConstraintProto* ct = context_->working_model->add_constraints();
12773 auto* arg = ct->mutable_linear();
12774 arg->add_vars(var);
12775 arg->add_coeffs(1);
12776 arg->add_vars(r.representative);
12777 arg->add_coeffs(-r.coeff);
12778 arg->add_domain(r.offset);
12779 arg->add_domain(r.offset);
12780 context_->UpdateNewConstraintsVariableUsage();
12781 }
12782
12783 // Now that we encoded all remaining affine relation with constraints, we
12784 // remove the special marker to have a proper constraint variable graph.
12785 context_->RemoveAllVariablesFromAffineRelationConstraint();
12786
12787 if (num_added > 0) {
12788 SOLVER_LOG(logger_, num_added, " affine relations still in the model.");
12789 }
12790}
12791
12792// Presolve a variable in relation with its representative.
12793bool CpModelPresolver::PresolveAffineRelationIfAny(int var) {
12794 const AffineRelation::Relation r = context_->GetAffineRelation(var);
12795 if (r.representative == var) return true;
12796
12797 // Propagate domains.
12798 if (!context_->PropagateAffineRelation(var)) return false;
12799
12800 // Once an affine relation is detected, the variables should be added to
12801 // the kAffineRelationConstraint. The only way to be unmarked is if the
12802 // variable do not appear in any other constraint and is not a representative,
12803 // in which case it should never be added back.
12804 if (context_->IsFixed(var)) return true;
12805 DCHECK(context_->VarToConstraints(var).contains(kAffineRelationConstraint));
12806 DCHECK(!context_->VariableIsNotUsedAnymore(r.representative));
12807
12808 // If var is no longer used, remove. Note that we can always do that since we
12809 // propagated the domain above and so we can find a feasible value for a for
12810 // any value of the representative.
12811 context_->RemoveNonRepresentativeAffineVariableIfUnused(var);
12812 return true;
12813}
12814
12815// Re-add to the queue the constraints that touch a variable that changed.
12816bool CpModelPresolver::ProcessChangedVariables(std::vector<bool>* in_queue,
12817 std::deque<int>* queue) {
12818 // TODO(user): Avoid reprocessing the constraints that changed the domain?
12819 if (context_->ModelIsUnsat()) return false;
12820 if (time_limit_->LimitReached()) return false;
12821 in_queue->resize(context_->working_model->constraints_size(), false);
12822 const auto& vector_that_can_grow_during_iter =
12823 context_->modified_domains.PositionsSetAtLeastOnce();
12824 for (int i = 0; i < vector_that_can_grow_during_iter.size(); ++i) {
12825 const int v = vector_that_can_grow_during_iter[i];
12826 context_->modified_domains.Clear(v);
12827 if (context_->VariableIsNotUsedAnymore(v)) continue;
12828 if (context_->ModelIsUnsat()) return false;
12829 if (!PresolveAffineRelationIfAny(v)) return false;
12830 if (context_->VariableIsNotUsedAnymore(v)) continue;
12831
12832 TryToSimplifyDomain(v);
12833
12834 // TODO(user): Integrate these with TryToSimplifyDomain().
12835 if (context_->ModelIsUnsat()) return false;
12836 context_->UpdateNewConstraintsVariableUsage();
12837
12838 if (!context_->CanonicalizeOneObjectiveVariable(v)) return false;
12839
12840 in_queue->resize(context_->working_model->constraints_size(), false);
12841 for (const int c : context_->VarToConstraints(v)) {
12842 if (c >= 0 && !(*in_queue)[c]) {
12843 (*in_queue)[c] = true;
12844 queue->push_back(c);
12845 }
12846 }
12847 }
12848 context_->modified_domains.ResetAllToFalse();
12849
12850 // Make sure the order is deterministic! because var_to_constraints[]
12851 // order changes from one run to the next.
12852 std::sort(queue->begin(), queue->end());
12853 return !queue->empty();
12854}
12855
12856void CpModelPresolver::PresolveToFixPoint() {
12857 if (time_limit_->LimitReached()) return;
12858 if (context_->ModelIsUnsat()) return;
12859 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12860
12861 // We do at most 2 tests per PresolveToFixPoint() call since this can be slow.
12862 int num_dominance_tests = 0;
12863 int num_dual_strengthening = 0;
12864
12865 // Limit on number of operations.
12866 const int64_t max_num_operations =
12867 context_->params().debug_max_num_presolve_operations() > 0
12868 ? context_->params().debug_max_num_presolve_operations()
12869 : std::numeric_limits<int64_t>::max();
12870
12871 // This is used for constraint having unique variables in them (i.e. not
12872 // appearing anywhere else) to not call the presolve more than once for this
12873 // reason.
12874 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
12875
12876 // The queue of "active" constraints, initialized to the non-empty ones.
12877 std::vector<bool> in_queue(context_->working_model->constraints_size(),
12878 false);
12879 std::deque<int> queue;
12880 for (int c = 0; c < in_queue.size(); ++c) {
12881 if (context_->working_model->constraints(c).constraint_case() !=
12882 ConstraintProto::CONSTRAINT_NOT_SET) {
12883 in_queue[c] = true;
12884 queue.push_back(c);
12885 }
12886 }
12887
12888 // When thinking about how the presolve works, it seems like a good idea to
12889 // process the "simple" constraints first in order to be more efficient.
12890 // In September 2019, experiment on the flatzinc problems shows no changes in
12891 // the results. We should actually count the number of rules triggered.
12892 if (context_->params().permute_presolve_constraint_order()) {
12893 std::shuffle(queue.begin(), queue.end(), *context_->random());
12894 } else {
12895 std::sort(queue.begin(), queue.end(), [this](int a, int b) {
12896 const int score_a = context_->ConstraintToVars(a).size();
12897 const int score_b = context_->ConstraintToVars(b).size();
12898 return score_a < score_b || (score_a == score_b && a < b);
12899 });
12900 }
12901
12902 // We put a hard limit on the number of loop to prevent some corner case with
12903 // propagation loops. Note that the limit is quite high so it shouldn't really
12904 // be reached in most situation.
12905 int num_loops = 0;
12906 constexpr int kMaxNumLoops = 1000;
12907 for (; num_loops < kMaxNumLoops && !queue.empty(); ++num_loops) {
12908 if (time_limit_->LimitReached()) break;
12909 if (context_->ModelIsUnsat()) break;
12910 if (context_->num_presolve_operations > max_num_operations) break;
12911
12912 // Empty the queue of single constraint presolve.
12913 while (!queue.empty() && !context_->ModelIsUnsat()) {
12914 if (time_limit_->LimitReached()) break;
12915 if (context_->num_presolve_operations > max_num_operations) break;
12916 const int c = queue.front();
12917 in_queue[c] = false;
12918 queue.pop_front();
12919
12920 const int old_num_constraint =
12921 context_->working_model->constraints_size();
12922 const bool changed = PresolveOneConstraint(c);
12923 if (context_->ModelIsUnsat()) {
12924 SOLVER_LOG(
12925 logger_, "Unsat after presolving constraint #", c,
12926 " (warning, dump might be inconsistent): ",
12927 ProtobufShortDebugString(context_->working_model->constraints(c)));
12928 }
12929
12930 // Add to the queue any newly created constraints.
12931 const int new_num_constraints =
12932 context_->working_model->constraints_size();
12933 if (new_num_constraints > old_num_constraint) {
12934 context_->UpdateNewConstraintsVariableUsage();
12935 in_queue.resize(new_num_constraints, true);
12936 for (int c = old_num_constraint; c < new_num_constraints; ++c) {
12937 queue.push_back(c);
12938 }
12939 }
12940
12941 // TODO(user): Is seems safer to remove the changed Boolean and maybe
12942 // just compare the number of applied "rules" before/after.
12943 if (changed) {
12944 context_->UpdateConstraintVariableUsage(c);
12945 }
12946 }
12947
12948 if (context_->ModelIsUnsat()) return;
12949
12950 in_queue.resize(context_->working_model->constraints_size(), false);
12951 const auto& vector_that_can_grow_during_iter =
12952 context_->var_with_reduced_small_degree.PositionsSetAtLeastOnce();
12953 for (int i = 0; i < vector_that_can_grow_during_iter.size(); ++i) {
12954 const int v = vector_that_can_grow_during_iter[i];
12955 if (context_->VariableIsNotUsedAnymore(v)) continue;
12956
12957 // Remove the variable from the set to allow it to be pushed again.
12958 // This is necessary since a few affine logic needs to add the same
12959 // variable back to a second pass of processing.
12960 context_->var_with_reduced_small_degree.Clear(v);
12961
12962 // Make sure all affine relations are propagated.
12963 // This also remove the relation if the degree is now one.
12964 if (context_->ModelIsUnsat()) return;
12965 if (!PresolveAffineRelationIfAny(v)) return;
12966
12967 const int degree = context_->VarToConstraints(v).size();
12968 if (degree == 0) continue;
12969 if (degree == 2) LookAtVariableWithDegreeTwo(v);
12970 if (degree == 2 || degree == 3) {
12971 // Tricky: this function can add new constraint.
12972 ProcessVariableInTwoAtMostOrExactlyOne(v);
12973 in_queue.resize(context_->working_model->constraints_size(), false);
12974 continue;
12975 }
12976
12977 // Re-add to the queue constraints that have unique variables. Note that
12978 // to not enter an infinite loop, we call each (var, constraint) pair at
12979 // most once.
12980 if (degree != 1) continue;
12981 const int c = *context_->VarToConstraints(v).begin();
12982 if (c < 0) continue;
12983
12984 // Note that to avoid bad complexity in problem like a TSP with just one
12985 // big constraint. we mark all the singleton variables of a constraint
12986 // even if this constraint is already in the queue.
12987 if (var_constraint_pair_already_called.contains(
12988 std::pair<int, int>(v, c))) {
12989 continue;
12990 }
12991 var_constraint_pair_already_called.insert({v, c});
12992
12993 if (!in_queue[c]) {
12994 in_queue[c] = true;
12995 queue.push_back(c);
12996 }
12997 }
12998 context_->var_with_reduced_small_degree.ResetAllToFalse();
12999
13000 if (ProcessChangedVariables(&in_queue, &queue)) continue;
13001
13002 DCHECK(!context_->HasUnusedAffineVariable());
13003
13004 // Deal with integer variable only appearing in an encoding.
13005 for (int v = 0; v < context_->working_model->variables().size(); ++v) {
13006 ProcessVariableOnlyUsedInEncoding(v);
13007 }
13008 if (ProcessChangedVariables(&in_queue, &queue)) continue;
13009
13010 // Perform dual reasoning.
13011 //
13012 // TODO(user): We can support assumptions but we need to not cut them out
13013 // of the feasible region.
13014 if (context_->params().keep_all_feasible_solutions_in_presolve()) break;
13015 if (!context_->working_model->assumptions().empty()) break;
13016
13017 // Starts by the "faster" algo that exploit variables that can move freely
13018 // in one direction. Or variables that are just blocked by one constraint in
13019 // one direction.
13020 for (int i = 0; i < 10; ++i) {
13021 if (context_->ModelIsUnsat()) return;
13022 ++num_dual_strengthening;
13023 DualBoundStrengthening dual_bound_strengthening;
13024 ScanModelForDualBoundStrengthening(*context_, &dual_bound_strengthening);
13025
13026 // TODO(user): Make sure that if we fix one variable, we fix its full
13027 // symmetric orbit. There should be no reason that we don't do that
13028 // though.
13029 if (!dual_bound_strengthening.Strengthen(context_)) return;
13030 if (ProcessChangedVariables(&in_queue, &queue)) break;
13031
13032 // It is possible we deleted some constraint, but the queue is empty.
13033 // In this case we redo a pass of dual bound strenghtening as we might
13034 // perform more reduction.
13035 //
13036 // TODO(user): maybe we could reach fix point directly?
13037 if (dual_bound_strengthening.NumDeletedConstraints() == 0) break;
13038 }
13039 if (!queue.empty()) continue;
13040
13041 // Dominance reasoning will likely break symmetry.
13042 // TODO(user): We can apply the one that do not break any though, or the
13043 // operations that are safe.
13044 if (context_->params().keep_symmetry_in_presolve()) break;
13045
13046 // Detect & exploit dominance between variables.
13047 // TODO(user): This can be slow, remove from fix-pint loop?
13048 if (num_dominance_tests++ < 2) {
13049 if (context_->ModelIsUnsat()) return;
13050 PresolveTimer timer("DetectDominanceRelations", logger_, time_limit_);
13051 VarDomination var_dom;
13052 ScanModelForDominanceDetection(*context_, &var_dom);
13053 if (!ExploitDominanceRelations(var_dom, context_)) return;
13054 if (ProcessChangedVariables(&in_queue, &queue)) continue;
13055 }
13056 }
13057
13058 if (context_->ModelIsUnsat()) return;
13059
13060 // Second "pass" for transformation better done after all of the above and
13061 // that do not need a fix-point loop.
13062 //
13063 // TODO(user): Also add deductions achieved during probing!
13064 //
13065 // TODO(user): ideally we should "wake-up" any constraint that contains an
13066 // absent interval in the main propagation loop above. But we currently don't
13067 // maintain such list.
13068 const int num_constraints = context_->working_model->constraints_size();
13069 for (int c = 0; c < num_constraints; ++c) {
13070 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13071 switch (ct->constraint_case()) {
13072 case ConstraintProto::kNoOverlap:
13073 // Filter out absent intervals.
13074 if (PresolveNoOverlap(ct)) {
13075 context_->UpdateConstraintVariableUsage(c);
13076 }
13077 break;
13078 case ConstraintProto::kNoOverlap2D:
13079 // Filter out absent intervals.
13080 if (PresolveNoOverlap2D(c, ct)) {
13081 context_->UpdateConstraintVariableUsage(c);
13082 }
13083 break;
13084 case ConstraintProto::kCumulative:
13085 // Filter out absent intervals.
13086 if (PresolveCumulative(ct)) {
13087 context_->UpdateConstraintVariableUsage(c);
13088 }
13089 break;
13090 case ConstraintProto::kBoolOr: {
13091 // Try to infer domain reductions from clauses and the saved "implies in
13092 // domain" relations.
13093 for (const auto& pair :
13094 context_->deductions.ProcessClause(ct->bool_or().literals())) {
13095 bool modified = false;
13096 if (!context_->IntersectDomainWith(pair.first, pair.second,
13097 &modified)) {
13098 return;
13099 }
13100 if (modified) {
13101 context_->UpdateRuleStats("deductions: reduced variable domain");
13102 }
13103 }
13104 break;
13105 }
13106 default:
13107 break;
13108 }
13109 }
13110
13111 timer.AddCounter("num_loops", num_loops);
13112 timer.AddCounter("num_dual_strengthening", num_dual_strengthening);
13113 context_->deductions.MarkProcessingAsDoneForNow();
13114}
13115
13116// TODO(user): Use better heuristic?
13117//
13118// TODO(user): This is similar to what Bounded variable addition (BVA) does.
13119// By adding a new variable, enforcement => literals becomes
13120// enforcement => x => literals, and we have one clause + #literals implication
13121// instead of #literals clauses. What BVA does in addition is to use the same
13122// x for other enforcement list if the rhs literals are shared.
13123void CpModelPresolver::MergeClauses() {
13124 if (context_->ModelIsUnsat()) return;
13125 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
13126
13127 // Constraint index that changed.
13128 std::vector<int> to_clean;
13129
13130 // Keep a map from negation of enforcement_literal => bool_and ct index.
13131 absl::flat_hash_map<uint64_t, int> bool_and_map;
13132
13133 // First loop over the constraint:
13134 // - Register already existing bool_and.
13135 // - score at_most_ones literals.
13136 // - Record bool_or.
13137 const int num_variables = context_->working_model->variables_size();
13138 std::vector<int> bool_or_indices;
13139 std::vector<int64_t> literal_score(2 * num_variables, 0);
13140 const auto get_index = [](int ref) {
13141 return 2 * PositiveRef(ref) + (RefIsPositive(ref) ? 0 : 1);
13142 };
13143
13144 int64_t num_collisions = 0;
13145 int64_t num_merges = 0;
13146 int64_t num_saved_literals = 0;
13147 ClauseWithOneMissingHasher hasher(*context_->random());
13148 const int num_constraints = context_->working_model->constraints_size();
13149 for (int c = 0; c < num_constraints; ++c) {
13150 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13151 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
13152 if (ct->enforcement_literal().size() > 1) {
13153 // We need to sort the negated literals.
13154 std::sort(ct->mutable_enforcement_literal()->begin(),
13155 ct->mutable_enforcement_literal()->end(),
13156 std::greater<int>());
13157 const auto [it, inserted] = bool_and_map.insert(
13158 {hasher.HashOfNegatedLiterals(ct->enforcement_literal()), c});
13159 if (inserted) {
13160 to_clean.push_back(c);
13161 } else {
13162 // See if this is a true duplicate. If yes, merge rhs.
13163 ConstraintProto* other_ct =
13164 context_->working_model->mutable_constraints(it->second);
13165 const absl::Span<const int> s1(ct->enforcement_literal());
13166 const absl::Span<const int> s2(other_ct->enforcement_literal());
13167 if (s1 == s2) {
13168 context_->UpdateRuleStats(
13169 "bool_and: merged constraints with same enforcement");
13170 other_ct->mutable_bool_and()->mutable_literals()->Add(
13171 ct->bool_and().literals().begin(),
13172 ct->bool_and().literals().end());
13173 ct->Clear();
13174 context_->UpdateConstraintVariableUsage(c);
13175 }
13176 }
13177 }
13178 continue;
13179 }
13180 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
13181 const int size = ct->at_most_one().literals().size();
13182 for (const int ref : ct->at_most_one().literals()) {
13183 literal_score[get_index(ref)] += size;
13184 }
13185 continue;
13186 }
13187 if (ct->constraint_case() == ConstraintProto::kExactlyOne) {
13188 const int size = ct->exactly_one().literals().size();
13189 for (const int ref : ct->exactly_one().literals()) {
13190 literal_score[get_index(ref)] += size;
13191 }
13192 continue;
13193 }
13194
13195 if (ct->constraint_case() != ConstraintProto::kBoolOr) continue;
13196
13197 // Both of these test shouldn't happen, but we have them to be safe.
13198 if (!ct->enforcement_literal().empty()) continue;
13199 if (ct->bool_or().literals().size() <= 2) continue;
13200
13201 std::sort(ct->mutable_bool_or()->mutable_literals()->begin(),
13202 ct->mutable_bool_or()->mutable_literals()->end());
13203 hasher.RegisterClause(c, ct->bool_or().literals());
13204 bool_or_indices.push_back(c);
13205 }
13206
13207 for (const int c : bool_or_indices) {
13208 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13209
13210 bool merged = false;
13211 timer.TrackSimpleLoop(ct->bool_or().literals().size());
13212 if (timer.WorkLimitIsReached()) break;
13213 for (const int ref : ct->bool_or().literals()) {
13214 const uint64_t hash = hasher.HashWithout(c, ref);
13215 const auto it = bool_and_map.find(hash);
13216 if (it != bool_and_map.end()) {
13217 ++num_collisions;
13218 const int base_c = it->second;
13219 auto* and_ct = context_->working_model->mutable_constraints(base_c);
13221 ct->bool_or().literals(), and_ct->enforcement_literal(), ref)) {
13222 ++num_merges;
13223 num_saved_literals += ct->bool_or().literals().size() - 1;
13224 merged = true;
13225 and_ct->mutable_bool_and()->add_literals(ref);
13226 ct->Clear();
13227 context_->UpdateConstraintVariableUsage(c);
13228 break;
13229 }
13230 }
13231 }
13232
13233 if (!merged) {
13234 // heuristic: take first literal whose negation has highest score.
13235 int best_ref = ct->bool_or().literals(0);
13236 int64_t best_score = literal_score[get_index(NegatedRef(best_ref))];
13237 for (const int ref : ct->bool_or().literals()) {
13238 const int64_t score = literal_score[get_index(NegatedRef(ref))];
13239 if (score > best_score) {
13240 best_ref = ref;
13241 best_score = score;
13242 }
13243 }
13244
13245 const uint64_t hash = hasher.HashWithout(c, best_ref);
13246 const auto [_, inserted] = bool_and_map.insert({hash, c});
13247 if (inserted) {
13248 to_clean.push_back(c);
13249 context_->tmp_literals.clear();
13250 for (const int lit : ct->bool_or().literals()) {
13251 if (lit == best_ref) continue;
13252 context_->tmp_literals.push_back(NegatedRef(lit));
13253 }
13254 ct->Clear();
13255 ct->mutable_enforcement_literal()->Assign(
13256 context_->tmp_literals.begin(), context_->tmp_literals.end());
13257 ct->mutable_bool_and()->add_literals(best_ref);
13258 }
13259 }
13260 }
13261
13262 // Retransform to bool_or bool_and with a single rhs.
13263 for (const int c : to_clean) {
13264 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13265 if (ct->bool_and().literals().size() > 1) {
13266 context_->UpdateConstraintVariableUsage(c);
13267 continue;
13268 }
13269
13270 // We have a single bool_and, lets transform it back to single bool_or.
13271 context_->tmp_literals.clear();
13272 context_->tmp_literals.push_back(ct->bool_and().literals(0));
13273 for (const int ref : ct->enforcement_literal()) {
13274 context_->tmp_literals.push_back(NegatedRef(ref));
13275 }
13276 ct->Clear();
13277 ct->mutable_bool_or()->mutable_literals()->Assign(
13278 context_->tmp_literals.begin(), context_->tmp_literals.end());
13279 }
13280
13281 timer.AddCounter("num_collisions", num_collisions);
13282 timer.AddCounter("num_merges", num_merges);
13283 timer.AddCounter("num_saved_literals", num_saved_literals);
13284}
13285
13286// =============================================================================
13287// Public API.
13288// =============================================================================
13289
13290CpSolverStatus PresolveCpModel(PresolveContext* context,
13291 std::vector<int>* postsolve_mapping) {
13292 CpModelPresolver presolver(context, postsolve_mapping);
13293 return presolver.Presolve();
13294}
13295
13296CpModelPresolver::CpModelPresolver(PresolveContext* context,
13297 std::vector<int>* postsolve_mapping)
13298 : postsolve_mapping_(postsolve_mapping),
13299 context_(context),
13300 solution_crush_(context->solution_crush()),
13301 logger_(context->logger()),
13302 time_limit_(context->time_limit()),
13303 interval_representative_(context->working_model->constraints_size(),
13304 IntervalConstraintHash{context->working_model},
13305 IntervalConstraintEq{context->working_model}) {}
13306
13307CpSolverStatus CpModelPresolver::InfeasibleStatus() {
13308 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13309 return CpSolverStatus::INFEASIBLE;
13310}
13311
13312// At the end of presolve, the mapping model is initialized to contains all
13313// the variable from the original model + the one created during presolve
13314// expand. It also contains the tightened domains.
13315namespace {
13316void InitializeMappingModelVariables(absl::Span<const Domain> domains,
13317 std::vector<int>* fixed_postsolve_mapping,
13318 CpModelProto* mapping_proto) {
13319 // Extend the fixed mapping to take into account all newly created variable
13320 // since the time it was constructed.
13321 int old_num_variables = mapping_proto->variables().size();
13322 while (fixed_postsolve_mapping->size() < domains.size()) {
13323 mapping_proto->add_variables();
13324 fixed_postsolve_mapping->push_back(old_num_variables++);
13325 DCHECK_EQ(old_num_variables, mapping_proto->variables().size());
13326 }
13327
13328 // Overwrite the domains.
13329 //
13330 // Note that if the fixed_postsolve_mapping was not null, the mapping model
13331 // should contains the original variable domains at the time the fixed mapping
13332 // was computed.
13333 for (int i = 0; i < domains.size(); ++i) {
13334 FillDomainInProto(domains[i], mapping_proto->mutable_variables(
13335 (*fixed_postsolve_mapping)[i]));
13336 }
13337
13338 // Remap the mapping proto.
13339 // We only deal with constraint here, do not touch the rest.
13340 //
13341 // TODO(user): Maybe we should have a real "postsolve" proto so we can
13342 // interleave postsolve "constraint" and remapping phase. This would allow to
13343 // do that in the middle of the presolve. But maybe this is not as impactful.
13344 auto mapping_function = [fixed_postsolve_mapping](int* ref) {
13345 const int image = (*fixed_postsolve_mapping)[PositiveRef(*ref)];
13346 CHECK_GE(image, 0);
13347 *ref = RefIsPositive(*ref) ? image : NegatedRef(image);
13348 };
13349 for (ConstraintProto& ct_ref : *mapping_proto->mutable_constraints()) {
13350 ApplyToAllVariableIndices(mapping_function, &ct_ref);
13351 ApplyToAllLiteralIndices(mapping_function, &ct_ref);
13352 }
13353}
13354} // namespace
13355
13356void CpModelPresolver::ExpandCpModelAndCanonicalizeConstraints() {
13357 const int num_constraints_before_expansion =
13358 context_->working_model->constraints_size();
13359 ExpandCpModel(context_);
13360 if (context_->ModelIsUnsat()) return;
13361
13362 // TODO(user): Make sure we can't have duplicate in these constraint.
13363 // These are due to ExpandCpModel() were we create such constraint with
13364 // duplicate. The problem is that some code assumes these are presolved
13365 // before being called.
13366 const int num_constraints = context_->working_model->constraints().size();
13367 for (int c = num_constraints_before_expansion; c < num_constraints; ++c) {
13368 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13369 const auto type = ct->constraint_case();
13370 if (type == ConstraintProto::kAtMostOne ||
13371 type == ConstraintProto::kExactlyOne) {
13372 if (PresolveOneConstraint(c)) {
13373 context_->UpdateConstraintVariableUsage(c);
13374 }
13375 if (context_->ModelIsUnsat()) return;
13376 } else if (type == ConstraintProto::kLinear) {
13377 bool changed = false;
13378 if (!CanonicalizeLinear(ct, &changed)) {
13379 return;
13380 }
13381 if (changed) {
13382 context_->UpdateConstraintVariableUsage(c);
13383 }
13384 }
13385 }
13386}
13387
13388namespace {
13389
13390// Updates the solution hint in the proto with the crushed solution values.
13391void UpdateHintInProto(PresolveContext* context) {
13392 if (context->ModelIsUnsat()) return;
13393
13394 SolutionCrush& crush = context->solution_crush();
13395 if (!crush.SolutionIsLoaded()) return;
13396 const int num_vars = context->working_model->variables().size();
13397 for (int i = 0; i < num_vars; ++i) {
13398 // If the initial hint is incomplete or infeasible, the crushed hint might
13399 // contain values outside of their respective domains (see SolutionCrush).
13400 crush.SetOrUpdateVarToDomain(i, context->DomainOf(i));
13401 }
13402 // If the time limit is reached, the presolved model might still contain
13403 // non-representative "affine" variables.
13404 for (int i = 0; i < num_vars; ++i) {
13405 const auto relation = context->GetAffineRelation(i);
13406 if (relation.representative != i) {
13407 crush.SetVarToLinearExpression(
13408 i, {{relation.representative, relation.coeff}}, relation.offset);
13409 }
13410 }
13411 crush.StoreSolutionAsHint(*context->working_model);
13412}
13413
13414// Canonicalizes the routes constraints node expressions. In particular,
13415// replaces the variables in these expressions with their representative.
13416void CanonicalizeRoutesConstraintNodeExpressions(PresolveContext* context) {
13417 CpModelProto& proto = *context->working_model;
13418 for (ConstraintProto& ct_ref : *proto.mutable_constraints()) {
13419 if (ct_ref.constraint_case() != ConstraintProto::kRoutes) continue;
13420 for (RoutesConstraintProto::NodeExpressions& node_exprs :
13421 *ct_ref.mutable_routes()->mutable_dimensions()) {
13422 for (LinearExpressionProto& expr : *node_exprs.mutable_exprs()) {
13423 context->CanonicalizeLinearExpression({}, &expr);
13424 }
13425 }
13426 }
13427}
13428
13429} // namespace
13430
13431// The presolve works as follow:
13432//
13433// First stage:
13434// We will process all active constraints until a fix point is reached. During
13435// this stage:
13436// - Variable will never be deleted, but their domain will be reduced.
13437// - Constraint will never be deleted (they will be marked as empty if needed).
13438// - New variables and new constraints can be added after the existing ones.
13439// - Constraints are added only when needed to the mapping_problem if they are
13440// needed during the postsolve.
13441//
13442// Second stage:
13443// - All the variables domain will be copied to the mapping_model.
13444// - Everything will be remapped so that only the variables appearing in some
13445// constraints will be kept and their index will be in [0, num_new_variables).
13446CpSolverStatus CpModelPresolver::Presolve() {
13447 context_->InitializeNewDomains();
13449 // If the objective is a floating point one, we scale it.
13450 //
13451 // TODO(user): We should probably try to delay this even more. For that we
13452 // just need to isolate more the "dual" reduction that usually need to look at
13453 // the objective.
13454 if (context_->working_model->has_floating_point_objective()) {
13455 context_->WriteVariableDomainsToProto();
13456 if (!ScaleFloatingPointObjective(context_->params(), logger_,
13457 context_->working_model)) {
13458 SOLVER_LOG(logger_,
13459 "The floating point objective cannot be scaled with enough "
13460 "precision");
13461 return CpSolverStatus::MODEL_INVALID;
13462 }
13463
13464 // At this point, we didn't create any new variables, so the integer
13465 // objective is in term of the orinal problem variables. We save it so that
13466 // we can expose to the user what exact objective we are actually
13467 // optimizing.
13468 *context_->mapping_model->mutable_objective() =
13469 context_->working_model->objective();
13470 }
13471
13472 // If there is a large proprotion of fixed variables, lets remap the model
13473 // before we start the actual presolve. This is useful for LNS in particular.
13474 //
13475 // fixed_postsolve_mapping[i] will contains the original index of the variable
13476 // that will be at position i after MaybeRemoveFixedVariables(). If the
13477 // mapping is left empty, it will be set to the identity mapping later by
13478 // InitializeMappingModelVariables().
13479 std::vector<int> fixed_postsolve_mapping;
13480 if (!MaybeRemoveFixedVariables(&fixed_postsolve_mapping)) {
13481 return InfeasibleStatus();
13482 }
13483
13484 // Initialize the initial context.working_model domains.
13485 // Initialize the objective and the constraint <-> variable graph.
13486 //
13487 // Note that we did some basic presolving during the first copy of the model.
13488 // This is important has initializing the constraint <-> variable graph can
13489 // be costly, so better to remove trivially feasible constraint for instance.
13490 context_->InitializeNewDomains();
13491 context_->LoadSolutionHint();
13492 context_->ReadObjectiveFromProto();
13493 if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
13494
13497 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13498
13499 // If presolve is false, just run expansion.
13500 if (!context_->params().cp_model_presolve()) {
13501 for (ConstraintProto& ct :
13502 *context_->working_model->mutable_constraints()) {
13503 if (ct.constraint_case() == ConstraintProto::kLinear) {
13504 context_->CanonicalizeLinearConstraint(&ct);
13505 }
13506 }
13507
13508 if (!solution_crush_.SolutionIsLoaded()) {
13509 context_->LoadSolutionHint();
13510 }
13511 ExpandCpModelAndCanonicalizeConstraints();
13512 UpdateHintInProto(context_);
13513 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13514
13515 // We still write back the canonical objective has we don't deal well
13516 // with uninitialized domain or duplicate variables.
13517 if (context_->working_model->has_objective()) {
13518 context_->WriteObjectiveToProto();
13519 }
13520
13521 // We need to append all the variable equivalence that are still used!
13522 EncodeAllAffineRelations();
13523
13524 // Make sure we also have an initialized mapping model as we use this for
13525 // filling the tightened variables. Even without presolve, we do some
13526 // trivial presolving during the initial copy of the model, and expansion
13527 // might do more.
13528 context_->WriteVariableDomainsToProto();
13529 InitializeMappingModelVariables(context_->AllDomains(),
13530 &fixed_postsolve_mapping,
13531 context_->mapping_model);
13532
13533 // We don't want to run postsolve when the presolve is disabled, but the
13534 // expansion might have added some constraints to the mapping model. To
13535 // restore correctness, we merge them with the working model.
13536 if (!context_->mapping_model->constraints().empty()) {
13537 context_->UpdateRuleStats(
13538 "TODO: mapping model not empty with presolve disabled");
13539 context_->working_model->mutable_constraints()->MergeFrom(
13540 context_->mapping_model->constraints());
13541 context_->mapping_model->clear_constraints();
13542 }
13543
13544 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13545 return CpSolverStatus::UNKNOWN;
13546 }
13547
13548 // Presolve all variable domain once. The PresolveToFixPoint() function will
13549 // only reprocess domain that changed.
13550 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13551 for (int var = 0; var < context_->working_model->variables().size(); ++var) {
13552 if (context_->VariableIsNotUsedAnymore(var)) continue;
13553 if (!PresolveAffineRelationIfAny(var)) return InfeasibleStatus();
13554
13555 // Try to canonicalize the domain, note that we should have detected all
13556 // affine relations before, so we don't recreate "canononical" variables
13557 // if they already exist in the model.
13558 TryToSimplifyDomain(var);
13559 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13560 context_->UpdateNewConstraintsVariableUsage();
13561 }
13562 if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
13563
13564 // Main propagation loop.
13565 for (int iter = 0; iter < context_->params().max_presolve_iterations();
13566 ++iter) {
13567 if (time_limit_->LimitReached()) break;
13568 context_->UpdateRuleStats("presolve: iteration");
13569 const int64_t old_num_presolve_op = context_->num_presolve_operations;
13570
13571 // Propagate the objective.
13572 if (!PropagateObjective()) return InfeasibleStatus();
13573
13574 // TODO(user): The presolve transformations we do after this is called might
13575 // result in even more presolve if we were to call this again! improve the
13576 // code. See for instance plusexample_6_sat.fzn were represolving the
13577 // presolved problem reduces it even more.
13578 PresolveToFixPoint();
13579
13580 // Call expansion.
13581 if (!context_->ModelIsExpanded()) {
13582 ExtractEncodingFromLinear();
13583 ExpandCpModelAndCanonicalizeConstraints();
13584 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13585 // We need to re-evaluate the degree because some presolve rule only
13586 // run after expansion.
13587 const int num_vars = context_->working_model->variables().size();
13588 for (int var = 0; var < num_vars; ++var) {
13589 if (context_->VarToConstraints(var).size() <= 3) {
13590 context_->var_with_reduced_small_degree.Set(var);
13591 }
13592 }
13593 }
13594 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13595
13596 // We run the symmetry before more complex presolve rules as many of them
13597 // are heuristic based and might break the symmetry present in the original
13598 // problems. This happens for example on the flatzinc wordpress problem.
13599 //
13600 // TODO(user): Decide where is the best place for this.
13601 //
13602 // TODO(user): try not to break symmetry in our clique extension or other
13603 // more advanced presolve rule? Ideally we could even exploit them. But in
13604 // this case, it is still good to compute them early.
13605 if (context_->params().symmetry_level() > 0 && !context_->ModelIsUnsat() &&
13606 !time_limit_->LimitReached()) {
13607 // Both kind of duplications might introduce a lot of symmetries and we
13608 // want to do that before we even compute them.
13610 DetectDuplicateConstraints();
13611 if (context_->params().keep_symmetry_in_presolve()) {
13612 // If the presolve always keep symmetry, we compute it once and for all.
13613 if (!context_->working_model->has_symmetry()) {
13614 DetectAndAddSymmetryToProto(context_->params(),
13615 context_->working_model, logger_,
13616 context_->time_limit());
13617 }
13618
13619 // We distinguish an empty symmetry message meaning that symmetry were
13620 // computed and there is none, and the absence of symmetry message
13621 // meaning we don't know.
13622 //
13623 // TODO(user): Maybe this is a bit brittle. Also move this logic to
13624 // DetectAndAddSymmetryToProto() ?
13625 if (!context_->working_model->has_symmetry()) {
13626 context_->working_model->mutable_symmetry()->Clear();
13627 }
13628 } else if (!context_->params()
13629 .keep_all_feasible_solutions_in_presolve()) {
13631 }
13632 }
13633
13634 // Runs SAT specific presolve on the pure-SAT part of the problem.
13635 // Note that because this can only remove/fix variable not used in the other
13636 // part of the problem, there is no need to redo more presolve afterwards.
13637 if (context_->params().cp_model_use_sat_presolve()) {
13638 if (!time_limit_->LimitReached()) {
13639 if (!PresolvePureSatPart()) {
13640 (void)context_->NotifyThatModelIsUnsat(
13641 "Proven Infeasible during SAT presolve");
13642 return InfeasibleStatus();
13643 }
13644 }
13645 }
13646
13647 // Extract redundant at most one constraint from the linear ones.
13648 //
13649 // TODO(user): more generally if we do some probing, the same relation will
13650 // be detected (and more). Also add an option to turn this off?
13651 //
13652 // TODO(user): instead of extracting at most one, extract pairwise conflicts
13653 // and add them to bool_and clauses? this is some sort of small scale
13654 // probing, but good for sat presolve and clique later?
13655 if (!context_->ModelIsUnsat() && iter == 0) {
13656 const int old_size = context_->working_model->constraints_size();
13657 for (int c = 0; c < old_size; ++c) {
13658 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13659 if (ct->constraint_case() != ConstraintProto::kLinear) continue;
13660 ExtractAtMostOneFromLinear(ct);
13661 }
13662 context_->UpdateNewConstraintsVariableUsage();
13663 }
13664
13665 if (context_->params().cp_model_probing_level() > 0) {
13666 if (!time_limit_->LimitReached()) {
13667 Probe();
13668 PresolveToFixPoint();
13669 }
13670 } else {
13671 TransformIntoMaxCliques();
13672 }
13673
13674 // Deal with pair of constraints.
13675 //
13676 // TODO(user): revisit when different transformation appear.
13677 // TODO(user): merge these code instead of doing many passes?
13678 ProcessAtMostOneAndLinear();
13679 DetectDuplicateConstraints();
13680 DetectDuplicateConstraintsWithDifferentEnforcements();
13681 DetectDominatedLinearConstraints();
13683 ProcessSetPPC();
13684
13685 // These operations might break symmetry. Or at the very least, the newly
13686 // created variable must be incorporated in the generators.
13687 if (context_->params().find_big_linear_overlap() &&
13688 !context_->params().keep_symmetry_in_presolve()) {
13689 FindAlmostIdenticalLinearConstraints();
13690
13691 ActivityBoundHelper activity_amo_helper;
13692 activity_amo_helper.AddAllAtMostOnes(*context_->working_model);
13693 FindBigAtMostOneAndLinearOverlap(&activity_amo_helper);
13694
13695 // Heuristic: vertical introduce smaller defining constraint and appear in
13696 // many constraints, so might be more constrained. We might also still
13697 // make horizontal rectangle with the variable introduced.
13698 FindBigVerticalLinearOverlap(&activity_amo_helper);
13699 FindBigHorizontalLinearOverlap(&activity_amo_helper);
13700 }
13701 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13702
13703 // We do that after the duplicate, SAT and SetPPC constraints.
13704 if (!time_limit_->LimitReached()) {
13705 // Merge clauses that differ in just one literal.
13706 // Heuristic use at_most_one to try to tighten the initial LP Relaxation.
13707 MergeClauses();
13708 if (/*DISABLES CODE*/ (false)) DetectIncludedEnforcement();
13709 }
13710
13711 // The TransformIntoMaxCliques() call above transform all bool and into
13712 // at most one of size 2. This does the reverse and merge them.
13713 ConvertToBoolAnd();
13714
13715 // Call the main presolve to remove the fixed variables and do more
13716 // deductions.
13717 PresolveToFixPoint();
13718
13719 // Exit the loop if no operations were performed.
13720 //
13721 // TODO(user): try to be smarter and avoid looping again if little changed.
13722 const int64_t num_ops =
13723 context_->num_presolve_operations - old_num_presolve_op;
13724 if (num_ops == 0) break;
13725 }
13726 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13727
13728 // Regroup no-overlaps into max-cliques.
13729 MergeNoOverlapConstraints();
13730 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13731
13732 // Tries to spread the objective amongst many variables.
13733 // We re-do a canonicalization with the final linear expression.
13734 if (context_->working_model->has_objective()) {
13735 if (!context_->params().keep_symmetry_in_presolve()) {
13736 ExpandObjective();
13737 if (!context_->modified_domains.PositionsSetAtLeastOnce().empty()) {
13738 // If we have fixed variables or created new affine relations, there
13739 // might be more things to presolve.
13740 PresolveToFixPoint();
13741 }
13742 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13743 ShiftObjectiveWithExactlyOnes();
13744 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13745 }
13746
13747 // We re-do a canonicalization with the final linear expression.
13748 if (!context_->CanonicalizeObjective()) return InfeasibleStatus();
13749 context_->WriteObjectiveToProto();
13750 }
13751
13752 // Now that everything that could possibly be fixed was fixed, make sure we
13753 // don't leave any linear constraint with fixed variables.
13754 for (int c = 0; c < context_->working_model->constraints_size(); ++c) {
13755 ConstraintProto& ct = *context_->working_model->mutable_constraints(c);
13756 bool need_canonicalize = false;
13757 if (ct.constraint_case() == ConstraintProto::kLinear) {
13758 for (const int v : ct.linear().vars()) {
13759 if (context_->IsFixed(v)) {
13760 need_canonicalize = true;
13761 break;
13762 }
13763 }
13764 }
13765 if (need_canonicalize) {
13766 bool changed = false;
13767 if (!CanonicalizeLinear(&ct, &changed)) {
13768 return InfeasibleStatus();
13769 }
13770 if (changed) {
13771 context_->UpdateConstraintVariableUsage(c);
13772 }
13773 }
13774 }
13775
13776 // Take care of linear constraint with a complex rhs.
13778
13779 // Adds all needed affine relation to context_->working_model.
13780 EncodeAllAffineRelations();
13781 if (context_->ModelIsUnsat()) return InfeasibleStatus();
13782
13783 // If we have symmetry information, lets filter it.
13784 if (context_->working_model->has_symmetry()) {
13786 context_->working_model->mutable_symmetry(), context_)) {
13787 return InfeasibleStatus();
13788 }
13789 }
13790
13791 // The strategy variable indices will be remapped in ApplyVariableMapping()
13792 // but first we use the representative of the affine relations for the
13793 // variables that are not present anymore.
13794 //
13795 // Note that we properly take into account the sign of the coefficient which
13796 // will result in the same domain reduction strategy. Moreover, if the
13797 // variable order is not CHOOSE_FIRST, then we also encode the associated
13798 // affine transformation in order to preserve the order.
13799 absl::flat_hash_set<int> used_variables;
13800 for (DecisionStrategyProto& strategy :
13801 *context_->working_model->mutable_search_strategy()) {
13802 CHECK(strategy.variables().empty());
13803 if (strategy.exprs().empty()) continue;
13804
13805 // Canonicalize each expression to use affine representative.
13806 ConstraintProto empy_enforcement;
13807 for (LinearExpressionProto& expr : *strategy.mutable_exprs()) {
13808 CanonicalizeLinearExpression(empy_enforcement, &expr);
13809 }
13810
13811 // Remove fixed expression and affine corresponding to same variables.
13812 int new_size = 0;
13813 for (const LinearExpressionProto& expr : strategy.exprs()) {
13814 if (context_->IsFixed(expr)) continue;
13815
13816 const auto [_, inserted] = used_variables.insert(expr.vars(0));
13817 if (!inserted) continue;
13818
13819 *strategy.mutable_exprs(new_size++) = expr;
13820 }
13821 google::protobuf::util::Truncate(strategy.mutable_exprs(), new_size);
13822 }
13823
13824 // Sync the domains and initialize the mapping model variables.
13825 context_->WriteVariableDomainsToProto();
13826 InitializeMappingModelVariables(context_->AllDomains(),
13827 &fixed_postsolve_mapping,
13828 context_->mapping_model);
13829
13830 // Remove all the unused variables from the presolved model.
13831 postsolve_mapping_->clear();
13832 std::vector<int> mapping(context_->working_model->variables_size(), -1);
13833 absl::flat_hash_map<int64_t, int> constant_to_index;
13834 int num_unused_variables = 0;
13835 for (int i = 0; i < context_->working_model->variables_size(); ++i) {
13836 if (mapping[i] != -1) continue; // Already mapped.
13837
13838 if (context_->VariableWasRemoved(i)) {
13839 // Heuristic: If a variable is removed and has a representative that is
13840 // not, we "move" the representative to the spot of that variable in the
13841 // original order. This is to preserve any info encoded in the variable
13842 // order by the modeler.
13843 const int r = PositiveRef(context_->GetAffineRelation(i).representative);
13844 if (mapping[r] == -1 && !context_->VariableIsNotUsedAnymore(r)) {
13845 mapping[r] = postsolve_mapping_->size();
13846 postsolve_mapping_->push_back(fixed_postsolve_mapping[r]);
13847 }
13848 continue;
13849 }
13850
13851 // Deal with unused variables.
13852 //
13853 // If the variable is not fixed, we have multiple feasible solution for
13854 // this variable, so we can't remove it if we want all of them.
13855 if (context_->VariableIsNotUsedAnymore(i) &&
13856 (!context_->params().keep_all_feasible_solutions_in_presolve() ||
13857 context_->IsFixed(i))) {
13858 // Tricky. Variables that were not removed by a presolve rule should be
13859 // fixed first during postsolve, so that more complex postsolve rules
13860 // can use their values. One way to do that is to fix them here.
13861 //
13862 // We prefer to fix them to zero if possible.
13863 ++num_unused_variables;
13864 FillDomainInProto(Domain(context_->DomainOf(i).SmallestValue()),
13865 context_->mapping_model->mutable_variables(
13866 fixed_postsolve_mapping[i]));
13867 continue;
13868 }
13869
13870 // Merge identical constant. Note that the only place were constant are
13871 // still left are in the circuit and route constraint for fixed arcs.
13872 if (context_->IsFixed(i)) {
13873 auto [it, inserted] = constant_to_index.insert(
13874 {context_->FixedValue(i), postsolve_mapping_->size()});
13875 if (!inserted) {
13876 mapping[i] = it->second;
13877 continue;
13878 }
13879 }
13880
13881 mapping[i] = postsolve_mapping_->size();
13882 postsolve_mapping_->push_back(fixed_postsolve_mapping[i]);
13883 }
13884 context_->UpdateRuleStats(absl::StrCat("presolve: ", num_unused_variables,
13885 " unused variables removed."));
13886
13887 if (context_->params().permute_variable_randomly()) {
13888 // The mapping might merge variable, so we have to be careful here.
13889 const int n = postsolve_mapping_->size();
13890 std::vector<int> perm(n);
13891 std::iota(perm.begin(), perm.end(), 0);
13892 std::shuffle(perm.begin(), perm.end(), *context_->random());
13893 for (int i = 0; i < context_->working_model->variables_size(); ++i) {
13894 if (mapping[i] != -1) mapping[i] = perm[mapping[i]];
13895 }
13896 std::vector<int> new_postsolve_mapping(n);
13897 for (int i = 0; i < n; ++i) {
13898 new_postsolve_mapping[perm[i]] = (*postsolve_mapping_)[i];
13899 }
13900 *postsolve_mapping_ = std::move(new_postsolve_mapping);
13901 }
13902
13903 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13904 CanonicalizeRoutesConstraintNodeExpressions(context_);
13905 UpdateHintInProto(context_);
13906 const int old_size = postsolve_mapping_->size();
13907 ApplyVariableMapping(context_, absl::MakeSpan(mapping), postsolve_mapping_);
13908 CHECK_EQ(old_size, postsolve_mapping_->size());
13909
13910 // Compact all non-empty constraint at the beginning.
13912
13913 // Hack to display the number of deductions stored.
13914 if (context_->deductions.NumDeductions() > 0) {
13915 context_->UpdateRuleStats(absl::StrCat(
13916 "deductions: ", context_->deductions.NumDeductions(), " stored"));
13917 }
13918
13919 // Stats and checks.
13920 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13921
13922 // This is not supposed to happen, and is more indicative of an error than an
13923 // INVALID model. But for our no-overflow preconditions, we might run into bad
13924 // situation that causes the final model to be invalid.
13925 {
13926 const std::string error =
13927 ValidateCpModel(*context_->working_model, /*after_presolve=*/true);
13928 if (!error.empty()) {
13929 SOLVER_LOG(logger_, "Error while validating postsolved model: ", error);
13930 return CpSolverStatus::MODEL_INVALID;
13931 }
13932 }
13933 {
13934 const std::string error = ValidateCpModel(*context_->mapping_model);
13935 if (!error.empty()) {
13936 SOLVER_LOG(logger_,
13937 "Error while validating mapping_model model: ", error);
13938 return CpSolverStatus::MODEL_INVALID;
13939 }
13940 }
13941
13942 return CpSolverStatus::UNKNOWN;
13943}
13944
13945void ApplyVariableMapping(PresolveContext* context, absl::Span<int> mapping,
13946 std::vector<int>* reverse_mapping) {
13947 CpModelProto* proto = context->working_model;
13948 // Remap all the variable/literal references in the constraints and the
13949 // enforcement literals in the variables.
13950 auto mapping_function = [mapping, reverse_mapping](int* ref) mutable {
13951 const int var = PositiveRef(*ref);
13952 int image = mapping[var];
13953 if (image < 0) {
13954 // We extend the mapping if this variable is still used.
13955 image = mapping[var] = reverse_mapping->size();
13956 reverse_mapping->push_back(var);
13957 }
13958 *ref = RefIsPositive(*ref) ? image : NegatedRef(image);
13959 };
13960 for (ConstraintProto& ct_ref : *proto->mutable_constraints()) {
13961 ApplyToAllVariableIndices(mapping_function, &ct_ref);
13962 ApplyToAllLiteralIndices(mapping_function, &ct_ref);
13963 if (ct_ref.constraint_case() == ConstraintProto::kRoutes) {
13964 for (RoutesConstraintProto::NodeExpressions& node_exprs :
13965 *ct_ref.mutable_routes()->mutable_dimensions()) {
13966 for (LinearExpressionProto& expr : *node_exprs.mutable_exprs()) {
13967 if (expr.vars().empty()) continue;
13968 DCHECK_EQ(expr.vars().size(), 1);
13969 const int ref = expr.vars(0);
13970 if (context->IsFixed(ref)) {
13971 expr.set_offset(expr.offset() +
13972 context->FixedValue(ref) * expr.coeffs(0));
13973 expr.clear_vars();
13974 expr.clear_coeffs();
13975 continue;
13976 }
13977 const int image = mapping[PositiveRef(ref)];
13978 if (image < 0) {
13979 // TODO(user): is this correct? may this lead to incorrect cuts
13980 // in routing_cuts.cc in some cases?
13981 expr.clear_vars();
13982 expr.clear_coeffs();
13983 continue;
13984 }
13985 expr.set_vars(0, RefIsPositive(ref) ? image : NegatedRef(image));
13986 }
13987 }
13988 }
13989 }
13990
13991 // Remap the objective variables.
13992 if (proto->has_objective()) {
13993 for (int& mutable_ref : *proto->mutable_objective()->mutable_vars()) {
13994 mapping_function(&mutable_ref);
13995 }
13996 }
13997
13998 // Remap the assumptions.
13999 for (int& mutable_ref : *proto->mutable_assumptions()) {
14000 mapping_function(&mutable_ref);
14001 }
14002
14003 // Remap the symmetries. Note that we should have properly dealt with fixed
14004 // orbit and such in FilterOrbitOnUnusedOrFixedVariables().
14005 if (proto->has_symmetry()) {
14006 for (SparsePermutationProto& generator :
14007 *proto->mutable_symmetry()->mutable_permutations()) {
14008 for (int& var : *generator.mutable_support()) {
14009 mapping_function(&var);
14010 }
14011 }
14012
14013 // We clear the orbitope info (we don't really use it after presolve).
14014 proto->mutable_symmetry()->clear_orbitopes();
14015 }
14016
14017 // Note: For the rest of the mapping, if mapping[i] is -1, we can just ignore
14018 // the variable instead of trying to map it.
14019
14020 // Remap the search decision heuristic.
14021 // Note that we delete any heuristic related to a removed variable.
14022 for (DecisionStrategyProto& strategy : *proto->mutable_search_strategy()) {
14023 int new_size = 0;
14024 for (LinearExpressionProto expr : strategy.exprs()) {
14025 DCHECK_EQ(expr.vars().size(), 1);
14026 const int image = mapping[expr.vars(0)];
14027 if (image >= 0) {
14028 expr.set_vars(0, image);
14029 *strategy.mutable_exprs(new_size++) = expr;
14030 }
14031 }
14032 google::protobuf::util::Truncate(strategy.mutable_exprs(), new_size);
14033 }
14034
14035 // Remove strategy with empty affine expression.
14036 {
14037 int new_size = 0;
14038 for (const DecisionStrategyProto& strategy : proto->search_strategy()) {
14039 if (strategy.exprs().empty()) continue;
14040 *proto->mutable_search_strategy(new_size++) = strategy;
14041 }
14042 google::protobuf::util::Truncate(proto->mutable_search_strategy(),
14043 new_size);
14044 }
14045
14046 // Remap the solution hint.
14047 if (proto->has_solution_hint()) {
14048 auto* mutable_hint = proto->mutable_solution_hint();
14049
14050 // Note that after remapping, we may have duplicate variables. For instance,
14051 // identical constant variables are mapped to a single one. So we make sure
14052 // we don't output duplicates here and just keep the first occurrence.
14053 absl::flat_hash_set<int> hinted_images;
14054
14055 int new_size = 0;
14056 const int old_size = mutable_hint->vars().size();
14057 for (int i = 0; i < old_size; ++i) {
14058 const int hinted_var = mutable_hint->vars(i);
14059 const int64_t hinted_value = mutable_hint->values(i);
14060 const int image = mapping[hinted_var];
14061 if (image >= 0) {
14062 if (!hinted_images.insert(image).second) continue;
14063 mutable_hint->set_vars(new_size, image);
14064 mutable_hint->set_values(new_size, hinted_value);
14065 ++new_size;
14066 }
14067 }
14068 mutable_hint->mutable_vars()->Truncate(new_size);
14069 mutable_hint->mutable_values()->Truncate(new_size);
14070 }
14071
14072 // Move the variable definitions.
14073 google::protobuf::RepeatedPtrField<IntegerVariableProto>
14074 new_variables_storage;
14075 google::protobuf::RepeatedPtrField<IntegerVariableProto>* new_variables;
14076 if (proto->GetArena() == nullptr) {
14077 new_variables = &new_variables_storage;
14078 } else {
14079 new_variables = google::protobuf::Arena::Create<
14080 google::protobuf::RepeatedPtrField<IntegerVariableProto>>(
14081 proto->GetArena());
14082 }
14083 for (int i = 0; i < mapping.size(); ++i) {
14084 const int image = mapping[i];
14085 if (image < 0) continue;
14086 while (image >= new_variables->size()) {
14087 new_variables->Add();
14088 }
14089 (*new_variables)[image].Swap(proto->mutable_variables(i));
14090 }
14091 proto->mutable_variables()->Swap(new_variables);
14092
14093 // Check that all variables have a non-empty domain.
14094 for (const IntegerVariableProto& v : proto->variables()) {
14095 CHECK_GT(v.domain_size(), 0);
14096 }
14097}
14098
14099bool CpModelPresolver::MaybeRemoveFixedVariables(
14100 std::vector<int>* postsolve_mapping) {
14101 postsolve_mapping->clear();
14102 if (!context_->params().remove_fixed_variables_early()) return true;
14103 if (!context_->params().cp_model_presolve()) return true;
14104
14105 // This is supposed to be already called, but it is a no-opt if this was the
14106 // case, and it comment nicely that we do require domains to be up to date
14107 // in the context.
14108 context_->InitializeNewDomains();
14109 if (context_->ModelIsUnsat()) return false;
14110
14111 // Initialize the mapping to remove all fixed variables.
14112 const int num_vars = context_->working_model->variables().size();
14113 std::vector<int> mapping(num_vars, -1);
14114 for (int i = 0; i < num_vars; ++i) {
14115 if (context_->IsFixed(i)) continue;
14116 mapping[i] = postsolve_mapping->size();
14117 postsolve_mapping->push_back(i);
14118 }
14119
14120 // Lets only do this if the proportion of fixed variables is large enough.
14121 const int num_fixed = num_vars - postsolve_mapping->size();
14122 if (num_fixed < 1000 || num_fixed * 2 <= num_vars) {
14123 postsolve_mapping->clear();
14124 return true;
14125 }
14126
14127 // TODO(user): Right now the copy does not remove fixed variables from the
14128 // objective, but ReadObjectiveFromProto() does it. Maybe we should just not
14129 // copy them in the first place.
14130 if (context_->working_model->has_objective()) {
14131 context_->ReadObjectiveFromProto();
14132 if (!context_->CanonicalizeObjective()) return false;
14133 if (!PropagateObjective()) return false;
14134 if (context_->ModelIsUnsat()) return false;
14135 context_->WriteObjectiveToProto();
14136 }
14137
14138 // Copy the current domains into the mapping model.
14139 // Note that we are not sure the domain where properly written.
14140 context_->WriteVariableDomainsToProto();
14141 *context_->mapping_model->mutable_variables() =
14142 context_->working_model->variables();
14143
14144 SOLVER_LOG(logger_, "Large number of fixed variables ",
14145 FormatCounter(num_fixed), " / ", FormatCounter(num_vars),
14146 ", doing a first remapping phase to go down to ",
14147 FormatCounter(postsolve_mapping->size()), " variables.");
14148
14149 // Perform the actual mapping.
14150 // Note that this might re-add fixed variable that are still used.
14151 const int old_size = postsolve_mapping->size();
14152 ApplyVariableMapping(context_, absl::MakeSpan(mapping), postsolve_mapping);
14153 if (postsolve_mapping->size() > old_size) {
14154 const int new_extra = postsolve_mapping->size() - old_size;
14155 SOLVER_LOG(logger_, "TODO: ", new_extra,
14156 " fixed variables still required in the model!");
14157 }
14158
14159 // Reset some part of the context, the caller re-reads the new domains.
14160 context_->ResetAfterCopy();
14161 return true;
14162}
14163
14164namespace {
14165
14166// We ignore all the fields but the linear expression.
14167ConstraintProto CopyObjectiveForDuplicateDetection(
14168 const CpObjectiveProto& objective) {
14169 ConstraintProto copy;
14170 *copy.mutable_linear()->mutable_vars() = objective.vars();
14171 *copy.mutable_linear()->mutable_coeffs() = objective.coeffs();
14172 return copy;
14173}
14174
14175struct ConstraintHashForDuplicateDetection {
14176 const CpModelProto* working_model;
14177 bool ignore_enforcement;
14178 ConstraintProto objective_constraint;
14179
14180 ConstraintHashForDuplicateDetection(const CpModelProto* working_model,
14181 bool ignore_enforcement)
14182 : working_model(working_model),
14183 ignore_enforcement(ignore_enforcement),
14184 objective_constraint(
14185 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
14186
14187 // We hash our mostly frequently used constraint directly without extra memory
14188 // allocation. We revert to a generic code using proto serialization for the
14189 // others.
14190 std::size_t operator()(int ct_idx) const {
14191 const ConstraintProto& ct = ct_idx == kObjectiveConstraint
14192 ? objective_constraint
14193 : working_model->constraints(ct_idx);
14194 const std::pair<ConstraintProto::ConstraintCase, absl::Span<const int>>
14195 type_and_enforcement = {ct.constraint_case(),
14196 ignore_enforcement
14197 ? absl::Span<const int>()
14198 : absl::MakeSpan(ct.enforcement_literal())};
14199 switch (ct.constraint_case()) {
14200 case ConstraintProto::kLinear:
14201 if (ignore_enforcement) {
14202 return absl::HashOf(type_and_enforcement,
14203 absl::MakeSpan(ct.linear().vars()),
14204 absl::MakeSpan(ct.linear().coeffs()),
14205 absl::MakeSpan(ct.linear().domain()));
14206 } else {
14207 // We ignore domain for linear constraint, because if the rest of the
14208 // constraint is the same we can just intersect them.
14209 return absl::HashOf(type_and_enforcement,
14210 absl::MakeSpan(ct.linear().vars()),
14211 absl::MakeSpan(ct.linear().coeffs()));
14212 }
14213 case ConstraintProto::kBoolAnd:
14214 return absl::HashOf(type_and_enforcement,
14215 absl::MakeSpan(ct.bool_and().literals()));
14216 case ConstraintProto::kBoolOr:
14217 return absl::HashOf(type_and_enforcement,
14218 absl::MakeSpan(ct.bool_or().literals()));
14219 case ConstraintProto::kAtMostOne:
14220 return absl::HashOf(type_and_enforcement,
14221 absl::MakeSpan(ct.at_most_one().literals()));
14222 case ConstraintProto::kExactlyOne:
14223 return absl::HashOf(type_and_enforcement,
14224 absl::MakeSpan(ct.exactly_one().literals()));
14225 default:
14226 ConstraintProto copy = ct;
14227 copy.clear_name();
14228 if (ignore_enforcement) {
14229 copy.mutable_enforcement_literal()->Clear();
14230 }
14231 return absl::HashOf(copy.SerializeAsString());
14232 }
14233 }
14234};
14235
14236struct ConstraintEqForDuplicateDetection {
14237 const CpModelProto* working_model;
14238 bool ignore_enforcement;
14239 ConstraintProto objective_constraint;
14240
14241 ConstraintEqForDuplicateDetection(const CpModelProto* working_model,
14242 bool ignore_enforcement)
14243 : working_model(working_model),
14244 ignore_enforcement(ignore_enforcement),
14245 objective_constraint(
14246 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
14247
14248 bool operator()(int a, int b) const {
14249 if (a == b) {
14250 return true;
14251 }
14252 const ConstraintProto& ct_a = a == kObjectiveConstraint
14253 ? objective_constraint
14254 : working_model->constraints(a);
14255 const ConstraintProto& ct_b = b == kObjectiveConstraint
14256 ? objective_constraint
14257 : working_model->constraints(b);
14258
14259 if (ct_a.constraint_case() != ct_b.constraint_case()) return false;
14260 if (!ignore_enforcement) {
14261 if (absl::MakeSpan(ct_a.enforcement_literal()) !=
14262 absl::MakeSpan(ct_b.enforcement_literal())) {
14263 return false;
14264 }
14265 }
14266 switch (ct_a.constraint_case()) {
14267 case ConstraintProto::kLinear:
14268 // As above, we ignore domain for linear constraint, because if the rest
14269 // of the constraint is the same we can just intersect them.
14270 if (ignore_enforcement && absl::MakeSpan(ct_a.linear().domain()) !=
14271 absl::MakeSpan(ct_b.linear().domain())) {
14272 return false;
14273 }
14274 return absl::MakeSpan(ct_a.linear().vars()) ==
14275 absl::MakeSpan(ct_b.linear().vars()) &&
14276 absl::MakeSpan(ct_a.linear().coeffs()) ==
14277 absl::MakeSpan(ct_b.linear().coeffs());
14278 case ConstraintProto::kBoolAnd:
14279 return absl::MakeSpan(ct_a.bool_and().literals()) ==
14280 absl::MakeSpan(ct_b.bool_and().literals());
14281 case ConstraintProto::kBoolOr:
14282 return absl::MakeSpan(ct_a.bool_or().literals()) ==
14283 absl::MakeSpan(ct_b.bool_or().literals());
14284 case ConstraintProto::kAtMostOne:
14285 return absl::MakeSpan(ct_a.at_most_one().literals()) ==
14286 absl::MakeSpan(ct_b.at_most_one().literals());
14287 case ConstraintProto::kExactlyOne:
14288 return absl::MakeSpan(ct_a.exactly_one().literals()) ==
14289 absl::MakeSpan(ct_b.exactly_one().literals());
14290 default:
14291 // Slow (hopefully comparably rare) path.
14292 ConstraintProto copy_a = ct_a;
14293 ConstraintProto copy_b = ct_b;
14294 copy_a.clear_name();
14295 copy_b.clear_name();
14296 if (ignore_enforcement) {
14297 copy_a.mutable_enforcement_literal()->Clear();
14298 copy_b.mutable_enforcement_literal()->Clear();
14299 }
14300 return copy_a.SerializeAsString() == copy_b.SerializeAsString();
14301 }
14302 }
14303};
14304
14305} // namespace
14306
14307std::vector<std::pair<int, int>> FindDuplicateConstraints(
14308 const CpModelProto& model_proto, bool ignore_enforcement) {
14309 std::vector<std::pair<int, int>> result;
14310
14311 // We use a map hash that uses the underlying constraint to compute the hash
14312 // and the equality for the indices.
14313 absl::flat_hash_map<int, int, ConstraintHashForDuplicateDetection,
14314 ConstraintEqForDuplicateDetection>
14315 equiv_constraints(
14316 model_proto.constraints_size(),
14317 ConstraintHashForDuplicateDetection{&model_proto, ignore_enforcement},
14318 ConstraintEqForDuplicateDetection{&model_proto, ignore_enforcement});
14319
14320 // Create a special representative for the linear objective.
14321 if (model_proto.has_objective() && !ignore_enforcement) {
14322 equiv_constraints[kObjectiveConstraint] = kObjectiveConstraint;
14323 }
14324
14325 const int num_constraints = model_proto.constraints().size();
14326 for (int c = 0; c < num_constraints; ++c) {
14327 const auto type = model_proto.constraints(c).constraint_case();
14328 if (type == ConstraintProto::CONSTRAINT_NOT_SET) continue;
14329
14330 // Nothing we will presolve in this case.
14331 if (ignore_enforcement && type == ConstraintProto::kBoolAnd) continue;
14332
14333 const auto [it, inserted] = equiv_constraints.insert({c, c});
14334 if (it->second != c) {
14335 // Already present!
14336 result.push_back({c, it->second});
14337 }
14338 }
14339
14340 return result;
14341}
14342
14343namespace {
14344bool SimpleLinearExprEq(const LinearExpressionProto& a,
14345 const LinearExpressionProto& b) {
14346 return absl::MakeSpan(a.vars()) == absl::MakeSpan(b.vars()) &&
14347 absl::MakeSpan(a.coeffs()) == absl::MakeSpan(b.coeffs()) &&
14348 a.offset() == b.offset();
14349}
14350
14351std::size_t LinearExpressionHash(const LinearExpressionProto& expr) {
14352 return absl::HashOf(absl::MakeSpan(expr.vars()),
14353 absl::MakeSpan(expr.coeffs()), expr.offset());
14354}
14355
14356} // namespace
14357
14358bool CpModelPresolver::IntervalConstraintEq::operator()(int a, int b) const {
14359 const ConstraintProto& ct_a = working_model->constraints(a);
14360 const ConstraintProto& ct_b = working_model->constraints(b);
14361 return absl::MakeSpan(ct_a.enforcement_literal()) ==
14362 absl::MakeSpan(ct_b.enforcement_literal()) &&
14363 SimpleLinearExprEq(ct_a.interval().start(), ct_b.interval().start()) &&
14364 SimpleLinearExprEq(ct_a.interval().size(), ct_b.interval().size()) &&
14365 SimpleLinearExprEq(ct_a.interval().end(), ct_b.interval().end());
14366}
14367
14368std::size_t CpModelPresolver::IntervalConstraintHash::operator()(
14369 int ct_idx) const {
14370 const ConstraintProto& ct = working_model->constraints(ct_idx);
14371 return absl::HashOf(absl::MakeSpan(ct.enforcement_literal()),
14372 LinearExpressionHash(ct.interval().start()),
14373 LinearExpressionHash(ct.interval().size()),
14374 LinearExpressionHash(ct.interval().end()));
14375}
14376
14377} // namespace sat
14378} // namespace operations_research
const std::vector< Variable * > & variables() const
--— Accessors and mutators --—
Definition model.h:372
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:30
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
static Domain FromValues(std::vector< int64_t > values)
Domain IntersectionWith(const Domain &domain) const
Domain QuadraticSuperset(int64_t a, int64_t b, int64_t c, int64_t d) const
Domain ContinuousMultiplicationBy(int64_t coeff) const
Domain AdditionWith(const Domain &domain) const
bool IsIncludedIn(const Domain &domain) const
Domain UnionWith(const Domain &domain) const
Domain InverseMultiplicationBy(int64_t coeff) const
static IntegralType CeilOfRatio(IntegralType numerator, IntegralType denominator)
Definition mathutil.h:39
bool LoggingIsEnabled() const
Returns true iff logging is enabled.
Definition logging.h:50
void Resize(int num_variables)
Resizes the data structure.
Definition clause.cc:541
void ResetFromFlatMapping(Keys keys, Values values, int minimum_num_nodes=0)
Definition util.h:852
size_t size() const
Size of the "key" space, always in [0, size()).
Definition util.h:841
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
void DetectDifferentVariables()
Detects variable that must take different values.
bool PresolveOneConstraint(int c)
Executes presolve method for the given constraint. Public for testing only.
void RemoveEmptyConstraints()
Visible for testing.
ABSL_MUST_USE_RESULT bool CanonicalizeObjective(bool simplify_domain=true)
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
Returns false if the 'lit' doesn't have the desired value in the domain.
void LogInfo()
Logs stats to the logger.
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
Return a super-set of the domain of the linear expression.
void InitializeNewDomains()
Creates the internal structure for any new variables in working_model.
ConstraintProto * NewMappingConstraint(absl::string_view file, int line)
void UpdateRuleStats(const std::string &name, int num_times=1)
void UpdateNewConstraintsVariableUsage()
Calls UpdateConstraintVariableUsage() on all newly created constraints.
bool CanonicalizeLinearConstraint(ConstraintProto *ct)
void AddCounter(std::string name, int64_t count)
void StoreSolutionAsHint(CpModelProto &model) const
Stores the solution as a hint in the given model.
time_limit
Definition solve.cc:22
void Truncate(RepeatedPtrField< T > *array, int new_size)
RepeatedPtrField version.
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:55
bool ReduceNumberOfBoxesExactMandatory(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
constexpr uint64_t kDefaultFingerprintSeed
Default seed for fingerprints.
uint64_t FingerprintRepeatedField(const google::protobuf::RepeatedField< T > &sequence, uint64_t seed)
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
const LiteralIndex kNoLiteralIndex(-1)
std::vector< std::pair< int, int > > FindDuplicateConstraints(const CpModelProto &model_proto, bool ignore_enforcement)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
void ExpandCpModel(PresolveContext *context)
bool DetectAndExploitSymmetriesInPresolve(PresolveContext *context)
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
Definition util.cc:204
CompactVectorVector< int > GetOverlappingRectangleComponents(absl::Span< const Rectangle > rectangles)
Domain EvaluateImpliedIntProdDomain(const LinearArgumentProto &expr, const PresolveContext &context)
int64_t FloorSquareRoot(int64_t a)
The argument must be non-negative.
Definition util.cc:300
bool HasEnforcementLiteral(const ConstraintProto &ct)
Small utility functions to deal with half-reified constraints.
DiophantineSolution SolveDiophantine(absl::Span< const int64_t > coeffs, int64_t rhs, absl::Span< const int64_t > var_lbs, absl::Span< const int64_t > var_ubs)
bool ClauseIsEnforcementImpliesLiteral(absl::Span< const int > clause, absl::Span< const int > enforcement, int literal)
void ApplyToAllIntervalIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
bool SafeAddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
Same method, but returns if the addition was possible without overflowing.
bool IsNegatableInt64(absl::int128 x)
Tells whether a int128 can be casted to a int64_t that can be negated.
Definition util.h:749
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
int64_t GetInnerVarValue(const LinearExpressionProto &expr, int64_t value)
std::vector< Rectangle > FindEmptySpaces(const Rectangle &bounding_box, std::vector< Rectangle > ocupied_rectangles)
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
Convenient wrapper to call the full presolve.
bool ScaleFloatingPointObjective(const SatParameters &params, SolverLogger *logger, CpModelProto *proto)
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset, std::pair< int64_t, int64_t > *implied_domain)
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
bool FilterOrbitOnUnusedOrFixedVariables(SymmetryProto *symmetry, PresolveContext *context)
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:422
void ScanModelForDualBoundStrengthening(const PresolveContext &context, DualBoundStrengthening *dual_bound_strengthening)
Scan the model so that dual_bound_strengthening.Strenghten() works.
void ConstructOverlappingSets(absl::Span< IndexedInterval > intervals, CompactVectorVector< int > *result, absl::Span< const int > order)
bool AddLinearConstraintMultiple(int64_t factor, const ConstraintProto &to_add, ConstraintProto *to_modify)
Disjoint2dPackingResult DetectDisjointRegionIn2dPacking(absl::Span< const RectangleInRange > non_fixed_boxes, absl::Span< const Rectangle > fixed_boxes, int max_num_components)
constexpr int kAffineRelationConstraint
IntegerValue PositiveRemainder(IntegerValue dividend, IntegerValue positive_divisor)
int64_t SafeDoubleToInt64(double value)
Definition util.h:737
std::string FormatCounter(int64_t num)
Prints a positive number with separators for easier reading (ex: 1'348'065).
Definition util.cc:44
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
int ReindexArcs(IntContainer *tails, IntContainer *heads, absl::flat_hash_map< int, int > *mapping_output=nullptr)
Definition circuit.h:208
void FinalExpansionForLinearConstraint(PresolveContext *context)
constexpr int kObjectiveConstraint
We use some special constraint index in our variable <-> constraint graph.
bool DiophantineEquationOfSizeTwoHasSolutionInDomain(const Domain &x, int64_t a, const Domain &y, int64_t b, int64_t cte)
Definition util.cc:248
int64_t AffineExpressionValueAt(const LinearExpressionProto &expr, int64_t value)
Evaluates an affine expression at the given value.
bool ReduceNumberofBoxesGreedy(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
void ApplyVariableMapping(PresolveContext *context, absl::Span< int > mapping, std::vector< int > *reverse_mapping)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
bool ExpressionsContainsOnlyOneVar(const ExpressionList &exprs)
Returns true if there exactly one variable appearing in all the expressions.
int64_t ClosestMultiple(int64_t value, int64_t base)
Definition util.cc:317
void ScanModelForDominanceDetection(PresolveContext &context, VarDomination *var_domination)
bool LinearsDifferAtOneTerm(const LinearConstraintProto &lin1, const LinearConstraintProto &lin2)
bool ExpressionContainsSingleRef(const LinearExpressionProto &expr)
Returns true if a linear expression can be reduced to a single ref.
int64_t LinearExpressionGcd(const LinearExpressionProto &expr, int64_t gcd)
void DetectAndAddSymmetryToProto(const SatParameters &params, CpModelProto *proto, SolverLogger *logger, TimeLimit *time_limit)
Detects symmetries and fill the symmetry field.
void ApplyToAllLiteralIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
InclusionDetector(const Storage &storage) -> InclusionDetector< Storage >
Deduction guide.
std::vector< std::pair< int, int > > FindPartialRectangleIntersections(absl::Span< const Rectangle > rectangles)
void DivideLinearExpression(int64_t divisor, LinearExpressionProto *expr)
bool LinearInequalityCanBeReducedWithClosestMultiple(int64_t base, absl::Span< const int64_t > coeffs, absl::Span< const int64_t > lbs, absl::Span< const int64_t > ubs, int64_t rhs, int64_t *new_rhs)
Definition util.cc:324
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
void PropagateAutomaton(const AutomatonConstraintProto &proto, const PresolveContext &context, std::vector< absl::flat_hash_set< int64_t > > *states, std::vector< absl::flat_hash_set< int64_t > > *labels)
Fills and propagates the set of reachable states/labels.
void GetOverlappingIntervalComponents(std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *components)
void CreateValidModelWithSingleConstraint(const ConstraintProto &ct, const PresolveContext *context, std::vector< int > *variable_mapping, CpModelProto *mini_model)
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
bool LinearExpressionProtosAreEqual(const LinearExpressionProto &a, const LinearExpressionProto &b, int64_t b_scaling)
Returns true iff a == b * b_scaling.
bool PresolveFixed2dRectangles(absl::Span< const RectangleInRange > non_fixed_boxes, std::vector< Rectangle > *fixed_boxes)
void ApplyToAllVariableIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
bool LoadModelForPresolve(const CpModelProto &model_proto, SatParameters params, PresolveContext *context, Model *local_model, absl::string_view name_for_logging)
void CanonicalizeTable(PresolveContext *context, ConstraintProto *ct)
In SWIG mode, we don't want anything besides these top-level includes.
bool AtMinOrMaxInt64(int64_t x)
Checks if x is equal to the min or the max value of an int64_t.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
ClosedInterval::Iterator end(ClosedInterval interval)
std::string ProtobufShortDebugString(const P &message)
Definition proto_utils.h:46
int64_t CapProd(int64_t x, int64_t y)
ClosedInterval::Iterator begin(ClosedInterval interval)
absl::StatusOr< std::vector< typename util::GraphTraits< AdjacencyLists >::NodeIndex > > FastTopologicalSort(const AdjacencyLists &adj)
if(!yyg->yy_init)
Definition parser.yy.cc:966
void FindStronglyConnectedComponents(NodeIndex num_nodes, const Graph &graph, SccOutput *components)
Simple wrapper function for most usage.
Definition model.h:51
int64_t Max() const
Returns the max of the domain.
Definition model.cc:346
bool empty() const
Definition model.cc:335
int64_t Min() const
Returns the min of the domain.
Definition model.cc:340
bool Contains(int64_t value) const
Various inclusion tests on a domain.
Definition model.cc:363
CompactVectorVector< int, std::pair< int, int64_t > > * column
ColumnEqForDuplicateDetection(CompactVectorVector< int, std::pair< int, int64_t > > *_column)
ColumnHashForDuplicateDetection(CompactVectorVector< int, std::pair< int, int64_t > > *_column)
CompactVectorVector< int, std::pair< int, int64_t > > * column
#define SOLVER_LOG(logger,...)
Definition logging.h:110