Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
integer.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
14#include "ortools/sat/integer.h"
15
16#include <algorithm>
17#include <cstdint>
18#include <deque>
19#include <functional>
20#include <limits>
21#include <optional>
22#include <ostream>
23#include <string>
24#include <utility>
25#include <vector>
26
27#include "absl/base/attributes.h"
28#include "absl/cleanup/cleanup.h"
29#include "absl/container/btree_map.h"
30#include "absl/container/flat_hash_map.h"
31#include "absl/container/inlined_vector.h"
32#include "absl/log/check.h"
33#include "absl/strings/str_cat.h"
34#include "absl/types/span.h"
38#include "ortools/sat/model.h"
42#include "ortools/util/bitset.h"
43#include "ortools/util/rev.h"
48
49namespace operations_research {
50namespace sat {
51
52std::vector<IntegerVariable> NegationOf(
53 absl::Span<const IntegerVariable> vars) {
54 std::vector<IntegerVariable> result(vars.size());
55 for (int i = 0; i < vars.size(); ++i) {
56 result[i] = NegationOf(vars[i]);
57 }
58 return result;
59}
60
61std::string ValueLiteralPair::DebugString() const {
62 return absl::StrCat("(literal = ", literal.DebugString(),
63 ", value = ", value.value(), ")");
64}
65
66std::ostream& operator<<(std::ostream& os, const ValueLiteralPair& p) {
67 os << p.DebugString();
68 return os;
69}
70
71// TODO(user): Reserve vector index by literals? It is trickier, as we might not
72// know beforehand how many we will need. Consider alternatives to not waste
73// space like using dequeue.
75 encoding_by_var_.reserve(num_vars);
76 equality_to_associated_literal_.reserve(num_vars);
77 equality_by_var_.reserve(num_vars);
78}
79
80void IntegerEncoder::FullyEncodeVariable(IntegerVariable var) {
81 if (VariableIsFullyEncoded(var)) return;
82
83 CHECK_EQ(0, sat_solver_->CurrentDecisionLevel());
84 var = PositiveVariable(var);
85 const PositiveOnlyIndex index = GetPositiveOnlyIndex(var);
86 CHECK(!domains_[index].IsEmpty()); // UNSAT. We don't deal with that here.
87 CHECK_LT(domains_[index].Size(), 100000)
88 << "Domain too large for full encoding.";
89
90 // TODO(user): Maybe we can optimize the literal creation order and their
91 // polarity as our default SAT heuristics initially depends on this.
92 //
93 // TODO(user): Currently, in some corner cases,
94 // GetOrCreateLiteralAssociatedToEquality() might trigger some propagation
95 // that update the domain of var, so we need to cache the values to not read
96 // garbage. Note that it is okay to call the function on values no longer
97 // reachable, as this will just do nothing.
98 tmp_values_.clear();
99 for (const int64_t v : domains_[index].Values()) {
100 tmp_values_.push_back(IntegerValue(v));
101 }
102 for (const IntegerValue v : tmp_values_) {
104 }
105
106 // Mark var and Negation(var) as fully encoded.
107 DCHECK_LT(GetPositiveOnlyIndex(var), is_fully_encoded_.size());
108 is_fully_encoded_[GetPositiveOnlyIndex(var)] = true;
109}
110
111bool IntegerEncoder::VariableIsFullyEncoded(IntegerVariable var) const {
112 const PositiveOnlyIndex index = GetPositiveOnlyIndex(var);
113 if (index >= is_fully_encoded_.size()) return false;
114
115 // Once fully encoded, the status never changes.
116 if (is_fully_encoded_[index]) return true;
117 var = PositiveVariable(var);
118
119 // TODO(user): Cache result as long as equality_by_var_[index] is unchanged?
120 // It might not be needed since if the variable is not fully encoded, then
121 // PartialDomainEncoding() will filter unreachable values, and so the size
122 // check will be false until further value have been encoded.
123 const int64_t initial_domain_size = domains_[index].Size();
124 if (equality_by_var_[index].size() < initial_domain_size) return false;
125
126 // This cleans equality_by_var_[index] as a side effect and in particular,
127 // sorts it by values.
129
130 // TODO(user): Comparing the size might be enough, but we want to be always
131 // valid even if either (*domains_[var]) or PartialDomainEncoding(var) are
132 // not properly synced because the propagation is not finished.
133 const auto& ref = equality_by_var_[index];
134 int i = 0;
135 for (const int64_t v : domains_[index].Values()) {
136 if (i < ref.size() && v == ref[i].value) {
137 i++;
138 }
139 }
140 if (i == ref.size()) {
141 is_fully_encoded_[index] = true;
142 }
143 return is_fully_encoded_[index];
144}
145
146const std::vector<ValueLiteralPair>& IntegerEncoder::FullDomainEncoding(
147 IntegerVariable var) const {
148 CHECK(VariableIsFullyEncoded(var));
149 return PartialDomainEncoding(var);
150}
151
152const std::vector<ValueLiteralPair>& IntegerEncoder::PartialDomainEncoding(
153 IntegerVariable var) const {
154 const PositiveOnlyIndex index = GetPositiveOnlyIndex(var);
155 if (index >= equality_by_var_.size()) {
156 partial_encoding_.clear();
157 return partial_encoding_;
158 }
159
160 int new_size = 0;
161 partial_encoding_.assign(equality_by_var_[index].begin(),
162 equality_by_var_[index].end());
163 for (int i = 0; i < partial_encoding_.size(); ++i) {
164 const ValueLiteralPair pair = partial_encoding_[i];
165 if (sat_solver_->Assignment().LiteralIsFalse(pair.literal)) continue;
166 if (sat_solver_->Assignment().LiteralIsTrue(pair.literal)) {
167 partial_encoding_.clear();
168 partial_encoding_.push_back(pair);
169 new_size = 1;
170 break;
171 }
172 partial_encoding_[new_size++] = pair;
173 }
174 partial_encoding_.resize(new_size);
175 std::sort(partial_encoding_.begin(), partial_encoding_.end(),
177
178 if (trail_->CurrentDecisionLevel() == 0) {
179 // We can cleanup the current encoding in this case.
180 equality_by_var_[index].assign(partial_encoding_.begin(),
181 partial_encoding_.end());
182 }
183
184 if (!VariableIsPositive(var)) {
185 std::reverse(partial_encoding_.begin(), partial_encoding_.end());
186 for (ValueLiteralPair& ref : partial_encoding_) ref.value = -ref.value;
187 }
188 return partial_encoding_;
189}
190
191// Note that by not inserting the literal in "order" we can in the worst case
192// use twice as much implication (2 by literals) instead of only one between
193// consecutive literals.
194void IntegerEncoder::AddImplications(
195 const absl::btree_map<IntegerValue, Literal>& map,
196 absl::btree_map<IntegerValue, Literal>::const_iterator it,
197 Literal associated_lit) {
198 if (!add_implications_) return;
199 DCHECK_EQ(it->second, associated_lit);
200
201 // Tricky: We compute the literal first because AddClauseDuringSearch() might
202 // propagate at level zero and mess up the map.
203 LiteralIndex before_index = kNoLiteralIndex;
204 if (it != map.begin()) {
205 auto before_it = it;
206 --before_it;
207 before_index = before_it->second.Index();
208 }
209 LiteralIndex after_index = kNoLiteralIndex;
210 {
211 auto after_it = it;
212 ++after_it;
213 if (after_it != map.end()) after_index = after_it->second.Index();
214 }
215
216 // Then we add the two implications.
217 if (after_index != kNoLiteralIndex) {
218 sat_solver_->AddClauseDuringSearch(
219 {Literal(after_index).Negated(), associated_lit});
220 }
221 if (before_index != kNoLiteralIndex) {
222 sat_solver_->AddClauseDuringSearch(
223 {associated_lit.Negated(), Literal(before_index)});
224 }
225}
226
228 CHECK_EQ(0, sat_solver_->CurrentDecisionLevel());
229 add_implications_ = true;
230
231 // This is tricky: AddBinaryClause() might trigger propagation that causes the
232 // encoding to be filtered. So we make a copy...
233 const int num_vars = encoding_by_var_.size();
234 for (PositiveOnlyIndex index(0); index < num_vars; ++index) {
235 LiteralIndex previous = kNoLiteralIndex;
236 const IntegerVariable var(2 * index.value());
237 for (const auto [unused, literal] : PartialGreaterThanEncoding(var)) {
238 if (previous != kNoLiteralIndex) {
239 // literal => previous.
240 sat_solver_->AddBinaryClause(literal.Negated(), Literal(previous));
241 }
242 previous = literal.Index();
243 }
244 }
245}
246
247std::pair<IntegerLiteral, IntegerLiteral> IntegerEncoder::Canonicalize(
248 IntegerLiteral i_lit) const {
249 const bool positive = VariableIsPositive(i_lit.var);
250 if (!positive) i_lit = i_lit.Negated();
251
252 const IntegerVariable var(i_lit.var);
253 const PositiveOnlyIndex index = GetPositiveOnlyIndex(var);
254 IntegerValue after(i_lit.bound);
255 IntegerValue before(i_lit.bound - 1);
256 DCHECK_GE(before, domains_[index].Min());
257 DCHECK_LE(after, domains_[index].Max());
258 int64_t previous = std::numeric_limits<int64_t>::min();
259 for (const ClosedInterval& interval : domains_[index]) {
260 if (before > previous && before < interval.start) before = previous;
261 if (after > previous && after < interval.start) after = interval.start;
262 if (after <= interval.end) break;
263 previous = interval.end;
264 }
265 if (positive) {
266 return {IntegerLiteral::GreaterOrEqual(var, after),
267 IntegerLiteral::LowerOrEqual(var, before)};
268 } else {
269 return {IntegerLiteral::LowerOrEqual(var, before),
271 }
272}
273
275 // Remove trivial literal.
276 {
277 const PositiveOnlyIndex index = GetPositiveOnlyIndex(i_lit.var);
278 if (VariableIsPositive(i_lit.var)) {
279 if (i_lit.bound <= domains_[index].Min()) return GetTrueLiteral();
280 if (i_lit.bound > domains_[index].Max()) return GetFalseLiteral();
281 } else {
282 const IntegerValue bound = -i_lit.bound;
283 if (bound >= domains_[index].Max()) return GetTrueLiteral();
284 if (bound < domains_[index].Min()) return GetFalseLiteral();
285 }
286 }
287
288 // Canonicalize and see if we have an equivalent literal already.
289 const auto canonical_lit = Canonicalize(i_lit);
290 if (VariableIsPositive(i_lit.var)) {
291 const LiteralIndex index = GetAssociatedLiteral(canonical_lit.first);
292 if (index != kNoLiteralIndex) return Literal(index);
293 } else {
294 const LiteralIndex index = GetAssociatedLiteral(canonical_lit.second);
295 if (index != kNoLiteralIndex) return Literal(index).Negated();
296 }
297
298 ++num_created_variables_;
299 const Literal literal(sat_solver_->NewBooleanVariable(), true);
300 AssociateToIntegerLiteral(literal, canonical_lit.first);
301
302 // TODO(user): on some problem this happens. We should probably make sure that
303 // we don't create extra fixed Boolean variable for no reason.
304 if (sat_solver_->Assignment().LiteralIsAssigned(literal)) {
305 VLOG(1) << "Created a fixed literal for no reason!";
306 }
307 return literal;
308}
309
310namespace {
311std::pair<PositiveOnlyIndex, IntegerValue> PositiveVarKey(IntegerVariable var,
312 IntegerValue value) {
313 return std::make_pair(GetPositiveOnlyIndex(var),
314 VariableIsPositive(var) ? value : -value);
315}
316} // namespace
317
319 IntegerVariable var, IntegerValue value) const {
320 const auto it =
321 equality_to_associated_literal_.find(PositiveVarKey(var, value));
322 if (it != equality_to_associated_literal_.end()) {
323 return it->second.Index();
324 }
325 return kNoLiteralIndex;
326}
327
329 IntegerVariable var, IntegerValue value) {
330 {
331 const auto it =
332 equality_to_associated_literal_.find(PositiveVarKey(var, value));
333 if (it != equality_to_associated_literal_.end()) {
334 return it->second;
335 }
336 }
337
338 // Check for trivial true/false literal to avoid creating variable for no
339 // reasons.
340 const Domain& domain = domains_[GetPositiveOnlyIndex(var)];
341 if (!domain.Contains(VariableIsPositive(var) ? value.value()
342 : -value.value())) {
343 return GetFalseLiteral();
344 }
345 if (domain.IsFixed()) {
347 return GetTrueLiteral();
348 }
349
350 ++num_created_variables_;
351 const Literal literal(sat_solver_->NewBooleanVariable(), true);
352 AssociateToIntegerEqualValue(literal, var, value);
353
354 // TODO(user): this happens on some problem. We should probably
355 // make sure that we don't create extra fixed Boolean variable for no reason.
356 // Note that here we could detect the case before creating the literal. The
357 // initial domain didn't contain it, but maybe the one of (>= value) or (<=
358 // value) is false?
359 if (sat_solver_->Assignment().LiteralIsAssigned(literal)) {
360 VLOG(1) << "Created a fixed literal for no reason!";
361 }
362 return literal;
363}
364
366 IntegerLiteral i_lit) {
367 // Always transform to positive variable.
368 if (!VariableIsPositive(i_lit.var)) {
369 i_lit = i_lit.Negated();
370 literal = literal.Negated();
371 }
372
373 const PositiveOnlyIndex index = GetPositiveOnlyIndex(i_lit.var);
374 const Domain& domain = domains_[index];
375 const IntegerValue min(domain.Min());
376 const IntegerValue max(domain.Max());
377 if (i_lit.bound <= min) {
378 return (void)sat_solver_->AddUnitClause(literal);
379 }
380 if (i_lit.bound > max) {
381 return (void)sat_solver_->AddUnitClause(literal.Negated());
382 }
383
384 if (index >= encoding_by_var_.size()) {
385 encoding_by_var_.resize(index.value() + 1);
386 }
387 auto& var_encoding = encoding_by_var_[index];
388
389 // We just insert the part corresponding to the literal with positive
390 // variable.
391 const auto canonical_pair = Canonicalize(i_lit);
392 const auto [it, inserted] =
393 var_encoding.insert({canonical_pair.first.bound, literal});
394 if (!inserted) {
395 const Literal associated(it->second);
396 if (associated != literal) {
397 DCHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
398 sat_solver_->AddClauseDuringSearch({literal, associated.Negated()});
399 sat_solver_->AddClauseDuringSearch({literal.Negated(), associated});
400 }
401 return;
402 }
403 AddImplications(var_encoding, it, literal);
404
405 // Corner case if adding implication cause this to be fixed.
406 if (sat_solver_->CurrentDecisionLevel() == 0) {
407 if (sat_solver_->Assignment().LiteralIsTrue(literal)) {
408 delayed_to_fix_->integer_literal_to_fix.push_back(canonical_pair.first);
409 }
410 if (sat_solver_->Assignment().LiteralIsFalse(literal)) {
411 delayed_to_fix_->integer_literal_to_fix.push_back(canonical_pair.second);
412 }
413 }
414
415 // Resize reverse encoding.
416 const int new_size =
417 1 + std::max(literal.Index().value(), literal.NegatedIndex().value());
418 if (new_size > reverse_encoding_.size()) {
419 reverse_encoding_.resize(new_size);
420 }
421 reverse_encoding_[literal].push_back(canonical_pair.first);
422 reverse_encoding_[literal.NegatedIndex()].push_back(canonical_pair.second);
423
424 // Detect the case >= max or <= min and properly register them. Note that
425 // both cases will happen at the same time if there is just two possible
426 // value in the domain.
427 if (canonical_pair.first.bound == max) {
428 AssociateToIntegerEqualValue(literal, i_lit.var, max);
429 }
430 if (-canonical_pair.second.bound == min) {
431 AssociateToIntegerEqualValue(literal.Negated(), i_lit.var, min);
432 }
433}
434
436 IntegerVariable var,
437 IntegerValue value) {
438 // The function is symmetric and we only deal with positive variable.
439 if (!VariableIsPositive(var)) {
440 var = NegationOf(var);
441 value = -value;
442 }
443
444 // Detect literal view. Note that the same literal can be associated to more
445 // than one variable, and thus already have a view. We don't change it in
446 // this case.
447 const PositiveOnlyIndex index = GetPositiveOnlyIndex(var);
448 const Domain& domain = domains_[index];
449 if (value == 1 && domain.Min() >= 0 && domain.Max() <= 1) {
450 if (literal.Index() >= literal_view_.size()) {
451 literal_view_.resize(literal.Index().value() + 1, kNoIntegerVariable);
452 literal_view_[literal] = var;
453 } else if (literal_view_[literal] == kNoIntegerVariable) {
454 literal_view_[literal] = var;
455 }
456 }
457
458 // We use the "do not insert if present" behavior of .insert() to do just one
459 // lookup.
460 const auto insert_result = equality_to_associated_literal_.insert(
461 {PositiveVarKey(var, value), literal});
462 if (!insert_result.second) {
463 // If this key is already associated, make the two literals equal.
464 const Literal representative = insert_result.first->second;
465 if (representative != literal) {
466 sat_solver_->AddClauseDuringSearch({literal, representative.Negated()});
467 sat_solver_->AddClauseDuringSearch({literal.Negated(), representative});
468 }
469 return;
470 }
471
472 // Fix literal for value outside the domain.
473 if (!domain.Contains(value.value())) {
474 return (void)sat_solver_->AddUnitClause(literal.Negated());
475 }
476
477 // Update equality_by_var. Note that due to the
478 // equality_to_associated_literal_ hash table, there should never be any
479 // duplicate values for a given variable.
480 if (index >= equality_by_var_.size()) {
481 equality_by_var_.resize(index.value() + 1);
482 is_fully_encoded_.resize(index.value() + 1);
483 }
484 equality_by_var_[index].push_back({value, literal});
485
486 // Fix literal for constant domain.
487 if (domain.IsFixed()) {
488 return (void)sat_solver_->AddUnitClause(literal);
489 }
490
491 const IntegerLiteral ge = IntegerLiteral::GreaterOrEqual(var, value);
492 const IntegerLiteral le = IntegerLiteral::LowerOrEqual(var, value);
493
494 // Special case for the first and last value.
495 if (value == domain.Min()) {
496 // Note that this will recursively call AssociateToIntegerEqualValue() but
497 // since equality_to_associated_literal_[] is now set, the recursion will
498 // stop there. When a domain has just 2 values, this allows to call just
499 // once AssociateToIntegerEqualValue() and also associate the other value to
500 // the negation of the given literal.
501 AssociateToIntegerLiteral(literal, le);
502 return;
503 }
504 if (value == domain.Max()) {
505 AssociateToIntegerLiteral(literal, ge);
506 return;
507 }
508
509 // (var == value) <=> (var >= value) and (var <= value).
512 sat_solver_->AddClauseDuringSearch({a, literal.Negated()});
513 sat_solver_->AddClauseDuringSearch({b, literal.Negated()});
514 sat_solver_->AddClauseDuringSearch({a.Negated(), b.Negated(), literal});
515
516 // Update reverse encoding.
517 const int new_size = 1 + literal.Index().value();
518 if (new_size > reverse_equality_encoding_.size()) {
519 reverse_equality_encoding_.resize(new_size);
520 }
521 reverse_equality_encoding_[literal].push_back({var, value});
522}
523
525 if (!VariableIsPositive(i_lit.var)) i_lit = i_lit.Negated();
526 const PositiveOnlyIndex index = GetPositiveOnlyIndex(i_lit.var);
527 if (i_lit.bound <= domains_[index].Min()) return true;
528 if (i_lit.bound > domains_[index].Max()) return true;
529 return GetAssociatedLiteral(i_lit) != kNoLiteralIndex;
530}
531
532// TODO(user): Canonicalization might be slow.
534 IntegerValue bound;
535 const auto canonical_pair = Canonicalize(i_lit);
536 const LiteralIndex result =
537 SearchForLiteralAtOrBefore(canonical_pair.first, &bound);
538 if (result != kNoLiteralIndex && bound >= i_lit.bound) {
539 return result;
540 }
541 return kNoLiteralIndex;
542}
543
544// Note that we assume the input literal is canonicalized and do not fall into
545// a hole. Otherwise, this work but will likely return a literal before and
546// not one equivalent to it (which can be after!).
548 IntegerLiteral i_lit, IntegerValue* bound) const {
549 const PositiveOnlyIndex index = GetPositiveOnlyIndex(i_lit.var);
550 if (index >= encoding_by_var_.size()) return kNoLiteralIndex;
551 const auto& encoding = encoding_by_var_[index];
552 if (VariableIsPositive(i_lit.var)) {
553 // We need the entry at or before.
554 // We take the element before the upper_bound() which is either the encoding
555 // of i if it already exists, or the encoding just before it.
556 auto after_it = encoding.upper_bound(i_lit.bound);
557 if (after_it == encoding.begin()) return kNoLiteralIndex;
558 --after_it;
559 *bound = after_it->first;
560 return after_it->second.Index();
561 } else {
562 // We ask for who is implied by -var >= -bound, so we look for
563 // the var >= value with value > bound and take its negation.
564 auto after_it = encoding.upper_bound(-i_lit.bound);
565 if (after_it == encoding.end()) return kNoLiteralIndex;
566
567 // Compute tight bound if there are holes, we have X <= candidate.
568 const Domain& domain = domains_[index];
569 if (after_it->first <= domain.Min()) return kNoLiteralIndex;
570 *bound = -domain.ValueAtOrBefore(after_it->first.value() - 1);
571 return after_it->second.NegatedIndex();
572 }
573}
574
576 IntegerLiteral i_lit, IntegerValue* bound) const {
577 const PositiveOnlyIndex index = GetPositiveOnlyIndex(i_lit.var);
578 if (index >= encoding_by_var_.size()) return kNoLiteralIndex;
579 const auto& encoding = encoding_by_var_[index];
580 if (VariableIsPositive(i_lit.var)) {
581 const auto it = encoding.lower_bound(i_lit.bound);
582 if (it == encoding.end()) return kNoLiteralIndex;
583 *bound = it->first;
584 return it->second.Index();
585 } else {
586 // We ask for the first at or after by -var >= bound,
587 // so we look for the first before var < -bound and takes its negation
588 auto after_it = encoding.upper_bound(-i_lit.bound);
589 if (after_it == encoding.begin()) return kNoLiteralIndex;
590 --after_it;
591
592 // Compute tight bound if there are holes, we have X <= candidate.
593 const Domain& domain = domains_[index];
594 if (after_it->first <= domain.Min()) return kNoLiteralIndex;
595 *bound = -domain.ValueAtOrBefore(after_it->first.value() - 1);
596 return after_it->second.NegatedIndex();
597 }
598}
599
601 Literal lit, IntegerVariable* view, bool* view_is_direct) const {
602 const IntegerVariable direct_var = GetLiteralView(lit);
603 const IntegerVariable opposite_var = GetLiteralView(lit.Negated());
604 // If a literal has both views, we want to always keep the same
605 // representative: the smallest IntegerVariable.
606 if (direct_var != kNoIntegerVariable &&
607 (opposite_var == kNoIntegerVariable || direct_var <= opposite_var)) {
608 if (view != nullptr) *view = direct_var;
609 if (view_is_direct != nullptr) *view_is_direct = true;
610 return true;
611 }
612 if (opposite_var != kNoIntegerVariable) {
613 if (view != nullptr) *view = opposite_var;
614 if (view_is_direct != nullptr) *view_is_direct = false;
615 return true;
616 }
617 return false;
618}
619
620std::vector<ValueLiteralPair> IntegerEncoder::PartialGreaterThanEncoding(
621 IntegerVariable var) const {
622 std::vector<ValueLiteralPair> result;
623 const PositiveOnlyIndex index = GetPositiveOnlyIndex(var);
624 if (index >= encoding_by_var_.size()) return result;
625 if (VariableIsPositive(var)) {
626 for (const auto [value, literal] : encoding_by_var_[index]) {
627 result.push_back({value, literal});
628 }
629 return result;
630 }
631
632 // Tricky: we need to account for holes.
633 const Domain& domain = domains_[index];
634 if (domain.IsEmpty()) return result;
635 int i = 0;
636 int64_t previous;
637 const int num_intervals = domain.NumIntervals();
638 for (const auto [value, literal] : encoding_by_var_[index]) {
639 while (value > domain[i].end) {
640 previous = domain[i].end;
641 ++i;
642 if (i == num_intervals) break;
643 }
644 if (i == num_intervals) break;
645 if (value <= domain[i].start) {
646 if (i == 0) continue;
647 result.push_back({-previous, literal.Negated()});
648 } else {
649 result.push_back({-value + 1, literal.Negated()});
650 }
651 }
652 std::reverse(result.begin(), result.end());
653 return result;
654}
655
657 Domain domain) {
658 DCHECK(VariableIsPositive(var));
659 const PositiveOnlyIndex index = GetPositiveOnlyIndex(var);
660 if (index >= encoding_by_var_.size()) return true;
661
662 // Fix >= literal that can be fixed.
663 // We filter and canonicalize the encoding.
664 int i = 0;
665 int num_fixed = 0;
666 tmp_encoding_.clear();
667 for (const auto [value, literal] : encoding_by_var_[index]) {
668 while (i < domain.NumIntervals() && value > domain[i].end) ++i;
669 if (i == domain.NumIntervals()) {
670 // We are past the end, so always false.
671 if (trail_->Assignment().LiteralIsTrue(literal)) return false;
672 if (trail_->Assignment().LiteralIsFalse(literal)) continue;
673 ++num_fixed;
674 trail_->EnqueueWithUnitReason(literal.Negated());
675 continue;
676 }
677 if (i == 0 && value <= domain[0].start) {
678 // We are at or before the beginning, so always true.
679 if (trail_->Assignment().LiteralIsTrue(literal)) continue;
680 if (trail_->Assignment().LiteralIsFalse(literal)) return false;
681 ++num_fixed;
682 trail_->EnqueueWithUnitReason(literal);
683 continue;
684 }
685
686 // Note that we canonicalize the literal if it fall into a hole.
687 tmp_encoding_.push_back(
688 {std::max<IntegerValue>(value, domain[i].start), literal});
689 }
690 encoding_by_var_[index].clear();
691 for (const auto [value, literal] : tmp_encoding_) {
692 encoding_by_var_[index].insert({value, literal});
693 }
694
695 // Same for equality encoding.
696 // This will be lazily cleaned on the next PartialDomainEncoding() call.
697 i = 0;
698 for (const ValueLiteralPair pair : PartialDomainEncoding(var)) {
699 while (i < domain.NumIntervals() && pair.value > domain[i].end) ++i;
700 if (i == domain.NumIntervals() || pair.value < domain[i].start) {
701 if (trail_->Assignment().LiteralIsTrue(pair.literal)) return false;
702 if (trail_->Assignment().LiteralIsFalse(pair.literal)) continue;
703 ++num_fixed;
704 trail_->EnqueueWithUnitReason(pair.literal.Negated());
705 }
706 }
707
708 if (num_fixed > 0) {
709 VLOG(1) << "Domain intersection fixed " << num_fixed
710 << " encoding literals";
711 }
712
713 return true;
714}
715
717 if (parameters_.log_search_progress() && num_decisions_to_break_loop_ > 0) {
718 VLOG(1) << "Num decisions to break propagation loop: "
719 << num_decisions_to_break_loop_;
720 }
721}
722
724 const int level = trail->CurrentDecisionLevel();
725 for (ReversibleInterface* rev : reversible_classes_) rev->SetLevel(level);
726
727 // Make sure that our internal "integer_search_levels_" size matches the
728 // sat decision levels. At the level zero, integer_search_levels_ should
729 // be empty.
730 if (level > integer_search_levels_.size()) {
731 integer_search_levels_.push_back(integer_trail_.size());
732 lazy_reason_decision_levels_.push_back(lazy_reasons_.size());
733 reason_decision_levels_.push_back(
734 ReasonIndex(literals_reason_starts_.size()));
735 CHECK_EQ(level, integer_search_levels_.size());
736 }
737
738 // This is required because when loading a model it is possible that we add
739 // (literal <-> integer literal) associations for literals that have already
740 // been propagated here. This often happens when the presolve is off
741 // and many variables are fixed.
742 //
743 // TODO(user): refactor the interaction IntegerTrail <-> IntegerEncoder so
744 // that we can just push right away such literal. Unfortunately, this is is
745 // a big chunk of work.
746 if (level == 0) {
747 for (const IntegerLiteral i_lit : delayed_to_fix_->integer_literal_to_fix) {
748 // Note that we do not call Enqueue here but directly the update domain
749 // function so that we do not abort even if the level zero bounds were
750 // up to date.
751 const IntegerValue lb =
752 std::max(LevelZeroLowerBound(i_lit.var), i_lit.bound);
753 const IntegerValue ub = LevelZeroUpperBound(i_lit.var);
754 if (!UpdateInitialDomain(i_lit.var, Domain(lb.value(), ub.value()))) {
755 sat_solver_->NotifyThatModelIsUnsat();
756 return false;
757 }
758 }
759 delayed_to_fix_->integer_literal_to_fix.clear();
760
761 for (const Literal lit : delayed_to_fix_->literal_to_fix) {
762 if (trail_->Assignment().LiteralIsFalse(lit)) {
763 sat_solver_->NotifyThatModelIsUnsat();
764 return false;
765 }
766 if (trail_->Assignment().LiteralIsTrue(lit)) continue;
767 trail_->EnqueueWithUnitReason(lit);
768 }
769 delayed_to_fix_->literal_to_fix.clear();
770 }
771
772 // Process all the "associated" literals and Enqueue() the corresponding
773 // bounds.
775 const Literal literal = (*trail)[propagation_trail_index_++];
776 for (const IntegerLiteral i_lit : encoder_->GetIntegerLiterals(literal)) {
777 // The reason is simply the associated literal.
778 if (!EnqueueAssociatedIntegerLiteral(i_lit, literal)) {
779 return false;
780 }
781 }
782 }
783
784 return true;
785}
786
787void IntegerTrail::Untrail(const Trail& trail, int literal_trail_index) {
788 ++num_untrails_;
789 conditional_lbs_.clear();
790 const int level = trail.CurrentDecisionLevel();
792 std::min(propagation_trail_index_, literal_trail_index);
793
794 if (level < first_level_without_full_propagation_) {
795 first_level_without_full_propagation_ = -1;
796 }
797
798 // Note that if a conflict was detected before Propagate() of this class was
799 // even called, it is possible that there is nothing to backtrack.
800 if (level >= integer_search_levels_.size()) return;
801 const int target = integer_search_levels_[level];
802 integer_search_levels_.resize(level);
803 CHECK_GE(target, var_lbs_.size());
804 CHECK_LE(target, integer_trail_.size());
805
806 for (int index = integer_trail_.size() - 1; index >= target; --index) {
807 const TrailEntry& entry = integer_trail_[index];
808 if (entry.var < 0) continue; // entry used by EnqueueLiteral().
809 var_trail_index_[entry.var] = entry.prev_trail_index;
810 var_lbs_[entry.var] = integer_trail_[entry.prev_trail_index].bound;
811 }
812 integer_trail_.resize(target);
813
814 // Resize lazy reason.
815 lazy_reasons_.resize(lazy_reason_decision_levels_[level]);
816 lazy_reason_decision_levels_.resize(level);
817
818 // Clear reason.
819 const ReasonIndex old_size = reason_decision_levels_[level];
820 reason_decision_levels_.resize(level);
821 if (old_size < literals_reason_starts_.size()) {
822 literals_reason_buffer_.resize(literals_reason_starts_[old_size]);
823
824 const int bound_start = bounds_reason_starts_[old_size];
825 bounds_reason_buffer_.resize(bound_start);
826 if (bound_start < trail_index_reason_buffer_.size()) {
827 trail_index_reason_buffer_.resize(bound_start);
828 }
829
830 literals_reason_starts_.resize(old_size);
831 bounds_reason_starts_.resize(old_size);
832 cached_sizes_.resize(old_size);
833 }
834
835 // We notify the new level once all variables have been restored to their
836 // old value.
837 for (ReversibleInterface* rev : reversible_classes_) rev->SetLevel(level);
838}
839
841 // We only store the domain for the positive variable.
842 domains_->reserve(num_vars);
843 encoder_->ReserveSpaceForNumVariables(num_vars);
844
845 // Because we always create both a variable and its negation.
846 const int size = 2 * num_vars;
847 var_lbs_.reserve(size);
848 var_trail_index_.reserve(size);
849 integer_trail_.reserve(size);
850 var_trail_index_cache_.reserve(size);
851 tmp_var_to_trail_index_in_queue_.reserve(size);
852
853 var_to_trail_index_at_lower_level_.reserve(size);
854}
855
856IntegerVariable IntegerTrail::AddIntegerVariable(IntegerValue lower_bound,
857 IntegerValue upper_bound) {
858 DCHECK_GE(lower_bound, kMinIntegerValue);
859 DCHECK_LE(lower_bound, upper_bound);
860 DCHECK_LE(upper_bound, kMaxIntegerValue);
861 DCHECK(lower_bound >= 0 ||
862 lower_bound + std::numeric_limits<int64_t>::max() >= upper_bound);
863 DCHECK(integer_search_levels_.empty());
864 DCHECK_EQ(var_lbs_.size(), integer_trail_.size());
865
866 const IntegerVariable i(var_lbs_.size());
867 var_lbs_.push_back(lower_bound);
868 var_trail_index_.push_back(integer_trail_.size());
869 integer_trail_.push_back({lower_bound, i});
870 domains_->push_back(Domain(lower_bound.value(), upper_bound.value()));
871
872 CHECK_EQ(NegationOf(i).value(), var_lbs_.size());
873 var_lbs_.push_back(-upper_bound);
874 var_trail_index_.push_back(integer_trail_.size());
875 integer_trail_.push_back({-upper_bound, NegationOf(i)});
876
877 var_trail_index_cache_.resize(var_lbs_.size(), integer_trail_.size());
878 tmp_var_to_trail_index_in_queue_.resize(var_lbs_.size(), 0);
879 var_to_trail_index_at_lower_level_.resize(var_lbs_.size(), 0);
880
881 for (SparseBitset<IntegerVariable>* w : watchers_) {
882 w->Resize(NumIntegerVariables());
883 }
884 return i;
885}
886
887IntegerVariable IntegerTrail::AddIntegerVariable(const Domain& domain) {
888 CHECK(!domain.IsEmpty());
889 const IntegerVariable var = AddIntegerVariable(IntegerValue(domain.Min()),
890 IntegerValue(domain.Max()));
891 CHECK(UpdateInitialDomain(var, domain));
892 return var;
893}
894
895const Domain& IntegerTrail::InitialVariableDomain(IntegerVariable var) const {
896 const PositiveOnlyIndex index = GetPositiveOnlyIndex(var);
897 if (VariableIsPositive(var)) return (*domains_)[index];
898 temp_domain_ = (*domains_)[index].Negation();
899 return temp_domain_;
900}
901
902// Note that we don't support optional variable here. Or at least if you set
903// the domain of an optional variable to zero, the problem will be declared
904// unsat.
905bool IntegerTrail::UpdateInitialDomain(IntegerVariable var, Domain domain) {
906 CHECK_EQ(trail_->CurrentDecisionLevel(), 0);
907 if (!VariableIsPositive(var)) {
908 var = NegationOf(var);
909 domain = domain.Negation();
910 }
911
912 const PositiveOnlyIndex index = GetPositiveOnlyIndex(var);
913 const Domain& old_domain = (*domains_)[index];
914 domain = domain.IntersectionWith(old_domain);
915 if (old_domain == domain) return true;
916
917 if (domain.IsEmpty()) return false;
918 const bool lb_changed = domain.Min() > old_domain.Min();
919 const bool ub_changed = domain.Max() < old_domain.Max();
920 (*domains_)[index] = domain;
921
922 // Update directly the level zero bounds.
923 DCHECK(
924 ReasonIsValid(IntegerLiteral::LowerOrEqual(var, domain.Max()), {}, {}));
925 DCHECK(
926 ReasonIsValid(IntegerLiteral::GreaterOrEqual(var, domain.Min()), {}, {}));
927 DCHECK_GE(domain.Min(), LowerBound(var));
928 DCHECK_LE(domain.Max(), UpperBound(var));
929 var_lbs_[var] = domain.Min();
930 integer_trail_[var.value()].bound = domain.Min();
931 var_lbs_[NegationOf(var)] = -domain.Max();
932 integer_trail_[NegationOf(var).value()].bound = -domain.Max();
933
934 // Do not forget to update the watchers.
935 for (SparseBitset<IntegerVariable>* bitset : watchers_) {
936 if (lb_changed) bitset->Set(var);
937 if (ub_changed) bitset->Set(NegationOf(var));
938 }
939
940 // Update the encoding.
941 return encoder_->UpdateEncodingOnInitialDomainChange(var, domain);
942}
943
945 IntegerValue value) {
946 auto insert = constant_map_.insert(std::make_pair(value, kNoIntegerVariable));
947 if (insert.second) { // new element.
948 const IntegerVariable new_var = AddIntegerVariable(value, value);
949 insert.first->second = new_var;
950 if (value != 0) {
951 // Note that this might invalidate insert.first->second.
952 CHECK(constant_map_.emplace(-value, NegationOf(new_var)).second);
953 }
954 return new_var;
955 }
956 return insert.first->second;
957}
958
960 // The +1 if for the special key zero (the only case when we have an odd
961 // number of entries).
962 return (constant_map_.size() + 1) / 2;
963}
964
966 int threshold) const {
967 // Optimization. We assume this is only called when computing a reason, so we
968 // can ignore this trail_index if we already need a more restrictive reason
969 // for this var.
970 //
971 // Hacky: We know this is only called with threshold == trail_index of the
972 // trail entry we are trying to explain. So this test can only trigger when a
973 // variable was shown to be already implied by the current conflict.
974 const int index_in_queue = tmp_var_to_trail_index_in_queue_[var];
975 if (threshold <= index_in_queue) {
976 // Disable the other optim if we might expand this literal during
977 // 1-UIP resolution.
978 const int last_decision_index =
979 integer_search_levels_.empty() ? 0 : integer_search_levels_.back();
980 if (index_in_queue >= last_decision_index) {
981 info_is_valid_on_subsequent_last_level_expansion_ = false;
982 }
983 return -1;
984 }
985
986 DCHECK_GE(threshold, var_lbs_.size());
987 int trail_index = var_trail_index_[var];
988
989 // Check the validity of the cached index and use it if possible.
990 if (trail_index > threshold) {
991 const int cached_index = var_trail_index_cache_[var];
992 if (cached_index >= threshold && cached_index < trail_index &&
993 integer_trail_[cached_index].var == var) {
994 trail_index = cached_index;
995 }
996 }
997
998 while (trail_index >= threshold) {
999 trail_index = integer_trail_[trail_index].prev_trail_index;
1000 if (trail_index >= var_trail_index_cache_threshold_) {
1001 var_trail_index_cache_[var] = trail_index;
1002 }
1003 }
1004
1005 const int num_vars = var_lbs_.size();
1006 return trail_index < num_vars ? -1 : trail_index;
1007}
1008
1009int IntegerTrail::FindLowestTrailIndexThatExplainBound(
1010 IntegerLiteral i_lit) const {
1011 DCHECK_LE(i_lit.bound, var_lbs_[i_lit.var]);
1012 DCHECK(!IsTrueAtLevelZero(i_lit));
1013
1014 int trail_index = var_trail_index_[i_lit.var];
1015
1016 // Check the validity of the cached index and use it if possible. This caching
1017 // mechanism is important in case of long chain of propagation on the same
1018 // variable. Because during conflict resolution, we call
1019 // FindLowestTrailIndexThatExplainBound() with lowest and lowest bound, this
1020 // cache can transform a quadratic complexity into a linear one.
1021 {
1022 const int cached_index = var_trail_index_cache_[i_lit.var];
1023 if (cached_index < trail_index) {
1024 const TrailEntry& entry = integer_trail_[cached_index];
1025 if (entry.var == i_lit.var && entry.bound >= i_lit.bound) {
1026 trail_index = cached_index;
1027 }
1028 }
1029 }
1030
1031 int prev_trail_index = trail_index;
1032 while (true) {
1033 ++work_done_in_find_indices_;
1034 if (trail_index >= var_trail_index_cache_threshold_) {
1035 var_trail_index_cache_[i_lit.var] = trail_index;
1036 }
1037 const TrailEntry& entry = integer_trail_[trail_index];
1038 if (entry.bound == i_lit.bound) return trail_index;
1039 if (entry.bound < i_lit.bound) return prev_trail_index;
1040 prev_trail_index = trail_index;
1041 trail_index = entry.prev_trail_index;
1042 }
1043}
1044
1045// TODO(user): Get rid of this function and only keep the trail index one?
1047 IntegerValue slack, absl::Span<const IntegerValue> coeffs,
1048 std::vector<IntegerLiteral>* reason) const {
1049 CHECK_GE(slack, 0);
1050 if (slack == 0) return;
1051 const int size = reason->size();
1052 tmp_indices_.resize(size);
1053 for (int i = 0; i < size; ++i) {
1054 CHECK_EQ((*reason)[i].bound, LowerBound((*reason)[i].var));
1055 CHECK_GE(coeffs[i], 0);
1056 tmp_indices_[i] = var_trail_index_[(*reason)[i].var];
1057 }
1058
1059 RelaxLinearReason(slack, coeffs, &tmp_indices_);
1060
1061 reason->clear();
1062 for (const int i : tmp_indices_) {
1063 reason->push_back(IntegerLiteral::GreaterOrEqual(integer_trail_[i].var,
1064 integer_trail_[i].bound));
1065 }
1066}
1067
1069 IntegerValue slack, absl::Span<const IntegerValue> coeffs,
1070 absl::Span<const IntegerVariable> vars,
1071 std::vector<IntegerLiteral>* reason) const {
1072 tmp_indices_.clear();
1073 for (const IntegerVariable var : vars) {
1074 tmp_indices_.push_back(var_trail_index_[var]);
1075 }
1076 if (slack > 0) RelaxLinearReason(slack, coeffs, &tmp_indices_);
1077 for (const int i : tmp_indices_) {
1078 reason->push_back(IntegerLiteral::GreaterOrEqual(integer_trail_[i].var,
1079 integer_trail_[i].bound));
1080 }
1081}
1082
1083void IntegerTrail::RelaxLinearReason(IntegerValue slack,
1084 absl::Span<const IntegerValue> coeffs,
1085 std::vector<int>* trail_indices) const {
1086 DCHECK_GT(slack, 0);
1087 DCHECK(relax_heap_.empty());
1088
1089 // We start by filtering *trail_indices:
1090 // - remove all level zero entries.
1091 // - keep the one that cannot be relaxed.
1092 // - move the other one to the relax_heap_ (and creating the heap).
1093 int new_size = 0;
1094 const int size = coeffs.size();
1095 const int num_vars = var_lbs_.size();
1096 for (int i = 0; i < size; ++i) {
1097 const int index = (*trail_indices)[i];
1098
1099 // We ignore level zero entries.
1100 if (index < num_vars) continue;
1101
1102 // If the coeff is too large, we cannot relax this entry.
1103 const IntegerValue coeff = coeffs[i];
1104 if (coeff > slack) {
1105 (*trail_indices)[new_size++] = index;
1106 continue;
1107 }
1108
1109 // This is a bit hacky, but when it is used from MergeReasonIntoInternal(),
1110 // we never relax a reason that will not be expanded because it is already
1111 // part of the current conflict.
1112 const TrailEntry& entry = integer_trail_[index];
1113 if (entry.var != kNoIntegerVariable &&
1114 index <= tmp_var_to_trail_index_in_queue_[entry.var]) {
1115 (*trail_indices)[new_size++] = index;
1116 continue;
1117 }
1118
1119 // Note that both terms of the product are positive.
1120 const TrailEntry& previous_entry = integer_trail_[entry.prev_trail_index];
1121 const int64_t diff =
1122 CapProd(coeff.value(), (entry.bound - previous_entry.bound).value());
1123 if (diff > slack) {
1124 (*trail_indices)[new_size++] = index;
1125 continue;
1126 }
1127
1128 relax_heap_.push_back({index, coeff, diff});
1129 }
1130 trail_indices->resize(new_size);
1131 std::make_heap(relax_heap_.begin(), relax_heap_.end());
1132
1133 while (slack > 0 && !relax_heap_.empty()) {
1134 const RelaxHeapEntry heap_entry = relax_heap_.front();
1135 std::pop_heap(relax_heap_.begin(), relax_heap_.end());
1136 relax_heap_.pop_back();
1137
1138 // The slack might have changed since the entry was added.
1139 if (heap_entry.diff > slack) {
1140 trail_indices->push_back(heap_entry.index);
1141 continue;
1142 }
1143
1144 // Relax, and decide what to do with the new value of index.
1145 slack -= heap_entry.diff;
1146 const int index = integer_trail_[heap_entry.index].prev_trail_index;
1147
1148 // Same code as in the first block.
1149 if (index < num_vars) continue;
1150 if (heap_entry.coeff > slack) {
1151 trail_indices->push_back(index);
1152 continue;
1153 }
1154 const TrailEntry& entry = integer_trail_[index];
1155 if (entry.var != kNoIntegerVariable &&
1156 index <= tmp_var_to_trail_index_in_queue_[entry.var]) {
1157 trail_indices->push_back(index);
1158 continue;
1159 }
1160
1161 const TrailEntry& previous_entry = integer_trail_[entry.prev_trail_index];
1162 const int64_t diff = CapProd(heap_entry.coeff.value(),
1163 (entry.bound - previous_entry.bound).value());
1164 if (diff > slack) {
1165 trail_indices->push_back(index);
1166 continue;
1167 }
1168 relax_heap_.push_back({index, heap_entry.coeff, diff});
1169 std::push_heap(relax_heap_.begin(), relax_heap_.end());
1170 }
1171
1172 // If we aborted early because of the slack, we need to push all remaining
1173 // indices back into the reason.
1174 for (const RelaxHeapEntry& entry : relax_heap_) {
1175 trail_indices->push_back(entry.index);
1176 }
1177 relax_heap_.clear();
1178}
1179
1181 std::vector<IntegerLiteral>* reason) const {
1182 int new_size = 0;
1183 for (const IntegerLiteral literal : *reason) {
1184 if (literal.bound <= LevelZeroLowerBound(literal.var)) continue;
1185 (*reason)[new_size++] = literal;
1186 }
1187 reason->resize(new_size);
1188}
1189
1190std::vector<Literal>* IntegerTrail::InitializeConflict(
1191 IntegerLiteral integer_literal, bool use_lazy_reason,
1192 absl::Span<const Literal> literals_reason,
1193 absl::Span<const IntegerLiteral> bounds_reason) {
1194 DCHECK(tmp_queue_.empty());
1195 std::vector<Literal>* conflict = trail_->MutableConflict();
1196 if (use_lazy_reason) {
1197 // We use the current trail index here.
1198 conflict->clear();
1199 ComputeLazyReasonIfNeeded(-ReasonIndex(lazy_reasons_.size()));
1200 *conflict = lazy_reason_literals_;
1201 tmp_queue_ = lazy_reason_trail_indices_;
1202 } else {
1203 conflict->assign(literals_reason.begin(), literals_reason.end());
1204 for (const IntegerLiteral& literal : bounds_reason) {
1205 if (IsTrueAtLevelZero(literal)) continue;
1206 tmp_queue_.push_back(FindLowestTrailIndexThatExplainBound(literal));
1207 }
1208 }
1209
1210 if (new_conflict_resolution_) {
1211 tmp_reason_.clear();
1212 global_id_conflict_timestamp_ = trail_->conflict_timestamp();
1213 if (use_lazy_reason) {
1214 // We make a "deep copy" of tmp_lazy_reason_.
1215 //
1216 // TODO(user): This should no longer be necessary when we just return
1217 // right away.
1218 tmp_boolean_literals_.assign(
1219 tmp_lazy_reason_.boolean_literals_at_false.begin(),
1220 tmp_lazy_reason_.boolean_literals_at_false.end());
1221 for (const Literal lit : tmp_lazy_reason_.boolean_literals_at_true) {
1222 tmp_boolean_literals_.push_back(lit.Negated());
1223 }
1224 tmp_integer_literals_.assign(tmp_lazy_reason_.integer_literals.begin(),
1225 tmp_lazy_reason_.integer_literals.end());
1226
1227 // WARNING: We assume the spans here are still valid.
1228 tmp_reason_.index_at_propagation = tmp_lazy_reason_.index_at_propagation;
1229 tmp_reason_.slack = tmp_lazy_reason_.slack;
1230 tmp_reason_.propagated_i_lit = tmp_lazy_reason_.propagated_i_lit;
1231 tmp_reason_.vars = tmp_lazy_reason_.vars;
1232 tmp_reason_.coeffs = tmp_lazy_reason_.coeffs;
1233 } else {
1234 tmp_boolean_literals_.assign(literals_reason.begin(),
1235 literals_reason.end());
1236 tmp_integer_literals_.assign(bounds_reason.begin(), bounds_reason.end());
1237 }
1238
1239 tmp_reason_.boolean_literals_at_false = tmp_boolean_literals_;
1240 tmp_reason_.integer_literals = tmp_integer_literals_;
1241 }
1242
1243 return conflict;
1244}
1245
1246namespace {
1247
1248std::string ReasonDebugString(absl::Span<const Literal> literal_reason,
1249 absl::Span<const IntegerLiteral> integer_reason) {
1250 std::string result = "literals:{";
1251 for (const Literal l : literal_reason) {
1252 if (result.back() != '{') result += ",";
1253 result += l.DebugString();
1254 }
1255 result += "} bounds:{";
1256 for (const IntegerLiteral l : integer_reason) {
1257 if (result.back() != '{') result += ",";
1258 result += l.DebugString();
1259 }
1260 result += "}";
1261 return result;
1262}
1263
1264} // namespace
1265
1266std::string IntegerTrail::DebugString() {
1267 std::string result = "trail:{";
1268 const int num_vars = var_lbs_.size();
1269 const int limit =
1270 std::min(num_vars + 30, static_cast<int>(integer_trail_.size()));
1271 for (int i = num_vars; i < limit; ++i) {
1272 if (result.back() != '{') result += ",";
1273 result +=
1274 IntegerLiteral::GreaterOrEqual(IntegerVariable(integer_trail_[i].var),
1275 integer_trail_[i].bound)
1276 .DebugString();
1277 }
1278 if (limit < integer_trail_.size()) {
1279 result += ", ...";
1280 }
1281 result += "}";
1282 return result;
1283}
1284
1286 DCHECK(ReasonIsValid(i_lit, {}, {}));
1287 if (i_lit.bound <= LevelZeroLowerBound(i_lit.var)) return true;
1288 if (i_lit.bound > LevelZeroUpperBound(i_lit.var)) {
1289 sat_solver_->NotifyThatModelIsUnsat();
1290 return false;
1291 }
1292 if (trail_->CurrentDecisionLevel() == 0) {
1293 if (!Enqueue(i_lit, {}, {})) {
1294 sat_solver_->NotifyThatModelIsUnsat();
1295 return false;
1296 }
1297 return true;
1298 }
1299
1300 // If the new level zero bounds is greater than or equal to our best bound, we
1301 // "clear" all entries associated to this variables and only keep the level
1302 // zero entry.
1303 //
1304 // TODO(user): We could still "clear" just a subset of the entries event
1305 // if the recent ones are still needed.
1306 if (i_lit.bound >= var_lbs_[i_lit.var]) {
1307 int index = var_trail_index_[i_lit.var];
1308 const int num_vars = var_lbs_.size();
1309 while (index >= num_vars) {
1310 integer_trail_[index].var = kNoIntegerVariable;
1311 index = integer_trail_[index].prev_trail_index;
1312 }
1313 DCHECK_EQ(index, i_lit.var.value());
1314
1315 // Point to the level zero entry.
1316 DCHECK_GE(i_lit.bound, var_lbs_[i_lit.var]);
1317 var_lbs_[i_lit.var] = i_lit.bound;
1318 var_trail_index_[i_lit.var] = index;
1319
1320 // TODO(user): we might update this twice, but it is important to at least
1321 // increase this as this counter can be used for "timestamping" and here
1322 // we did change a bound.
1323 ++num_enqueues_;
1324 }
1325
1326 // Update the level-zero bound in any case.
1327 integer_trail_[i_lit.var.value()].bound = i_lit.bound;
1328
1329 // Make sure we will update InitialVariableDomain() when we are back
1330 // at level zero.
1331 delayed_to_fix_->integer_literal_to_fix.push_back(i_lit);
1332
1333 return true;
1334}
1335
1337 IntegerLiteral i_lit, absl::Span<const IntegerLiteral> integer_reason) {
1338 return SafeEnqueue(i_lit, {}, integer_reason);
1339}
1340
1342 IntegerLiteral i_lit, absl::Span<const Literal> literal_reason,
1343 absl::Span<const IntegerLiteral> integer_reason) {
1344 // Note that ReportConflict() deal correctly with constant literals.
1345 if (i_lit.IsAlwaysTrue()) return true;
1346 if (i_lit.IsAlwaysFalse()) {
1347 return ReportConflict(literal_reason, integer_reason);
1348 }
1349
1350 // Most of our propagation code do not use "constant" literal, so to not
1351 // have to test for them in Enqueue(), we clear them beforehand.
1352 tmp_cleaned_reason_.clear();
1353 for (const IntegerLiteral lit : integer_reason) {
1354 DCHECK(!lit.IsAlwaysFalse());
1355 if (lit.IsAlwaysTrue()) continue;
1356 tmp_cleaned_reason_.push_back(lit);
1357 }
1358 return Enqueue(i_lit, literal_reason, tmp_cleaned_reason_);
1359}
1360
1362 Literal lit, IntegerLiteral i_lit, std::vector<Literal>* literal_reason,
1363 std::vector<IntegerLiteral>* integer_reason) {
1364 const VariablesAssignment& assignment = trail_->Assignment();
1365 if (assignment.LiteralIsFalse(lit)) return true;
1366
1367 if (assignment.LiteralIsTrue(lit)) {
1368 literal_reason->push_back(lit.Negated());
1369 return Enqueue(i_lit, *literal_reason, *integer_reason);
1370 }
1371
1372 if (IntegerLiteralIsFalse(i_lit)) {
1373 integer_reason->push_back(
1374 IntegerLiteral::LowerOrEqual(i_lit.var, i_lit.bound - 1));
1375 return EnqueueLiteral(lit.Negated(), *literal_reason, *integer_reason);
1376 }
1377
1378 // We can't push anything in this case.
1379 //
1380 // We record it for this propagation phase (until the next untrail) as this
1381 // is relatively fast and heuristics can exploit this.
1382 //
1383 // Note that currently we only use ConditionalEnqueue() in scheduling
1384 // propagator, and these propagator are quite slow so this is not visible.
1385 //
1386 // TODO(user): We could even keep the reason and maybe do some reasoning using
1387 // at_least_one constraint on a set of the Boolean used here.
1388 const auto [it, inserted] =
1389 conditional_lbs_.insert({{lit.Index(), i_lit.var}, i_lit.bound});
1390 if (!inserted) {
1391 it->second = std::max(it->second, i_lit.bound);
1392 }
1393
1394 return true;
1395}
1396
1397bool IntegerTrail::ReasonIsValid(
1398 absl::Span<const Literal> literal_reason,
1399 absl::Span<const IntegerLiteral> integer_reason) {
1400 const VariablesAssignment& assignment = trail_->Assignment();
1401 for (const Literal lit : literal_reason) {
1402 if (!assignment.LiteralIsFalse(lit)) return false;
1403 }
1404 for (const IntegerLiteral i_lit : integer_reason) {
1405 if (i_lit.IsAlwaysTrue()) continue;
1406 if (i_lit.IsAlwaysFalse()) {
1407 LOG(INFO) << "Reason has a constant false literal!";
1408 return false;
1409 }
1410 if (i_lit.bound > var_lbs_[i_lit.var]) {
1411 LOG(INFO) << "Reason " << i_lit << " is not true!"
1412 << " non-optional variable:" << i_lit.var
1413 << " current_lb:" << var_lbs_[i_lit.var];
1414 return false;
1415 }
1416 }
1417
1418 // This may not indicate an incorectness, but just some propagators that
1419 // didn't reach a fixed-point at level zero.
1420 if (!integer_search_levels_.empty()) {
1421 int num_literal_assigned_after_root_node = 0;
1422 for (const Literal lit : literal_reason) {
1423 if (trail_->Info(lit.Variable()).level > 0) {
1424 num_literal_assigned_after_root_node++;
1425 }
1426 }
1427 for (const IntegerLiteral i_lit : integer_reason) {
1428 if (i_lit.IsAlwaysTrue()) continue;
1429 if (LevelZeroLowerBound(i_lit.var) < i_lit.bound) {
1430 num_literal_assigned_after_root_node++;
1431 }
1432 }
1433 if (num_literal_assigned_after_root_node == 0) {
1434 VLOG(2) << "Propagating a literal with no reason at a positive level!\n"
1435 << "level:" << integer_search_levels_.size() << " "
1436 << ReasonDebugString(literal_reason, integer_reason) << "\n"
1437 << DebugString();
1438 }
1439 }
1440
1441 return true;
1442}
1443
1444bool IntegerTrail::ReasonIsValid(
1445 IntegerLiteral i_lit, absl::Span<const Literal> literal_reason,
1446 absl::Span<const IntegerLiteral> integer_reason) {
1447 if (!ReasonIsValid(literal_reason, integer_reason)) return false;
1448 if (debug_checker_ == nullptr) return true;
1449
1450 std::vector<Literal> clause;
1451 clause.assign(literal_reason.begin(), literal_reason.end());
1452 std::vector<IntegerLiteral> lits = {integer_reason.begin(),
1453 integer_reason.end()};
1454
1455 const IntegerLiteral negated_i_lit =
1456 i_lit.IsAlwaysFalse() ? IntegerLiteral::TrueLiteral() : i_lit.Negated();
1457 lits.push_back(negated_i_lit);
1458 if (!debug_checker_(clause, lits)) {
1459 LOG(INFO) << "Invalid reason for loaded solution: " << i_lit << " "
1460 << literal_reason << " " << integer_reason;
1461 return false;
1462 }
1463 lits.pop_back();
1464
1465 MergeReasonInto(lits, &clause);
1466 if (!debug_checker_(clause, {negated_i_lit})) {
1467 LOG(INFO) << "Invalid reason for loaded solution after merging: " << i_lit
1468 << " " << literal_reason << " " << integer_reason;
1469 return false;
1470 }
1471 return true;
1472}
1473
1474bool IntegerTrail::ReasonIsValid(
1475 Literal lit, absl::Span<const Literal> literal_reason,
1476 absl::Span<const IntegerLiteral> integer_reason) {
1477 if (!ReasonIsValid(literal_reason, integer_reason)) return false;
1478 if (debug_checker_ == nullptr) return true;
1479
1480 std::vector<Literal> clause;
1481 clause.assign(literal_reason.begin(), literal_reason.end());
1482 clause.push_back(lit);
1483 if (!debug_checker_(clause, integer_reason)) {
1484 LOG(INFO) << "Invalid reason for loaded solution: " << lit << " "
1485 << literal_reason << " " << integer_reason;
1486 return false;
1487 }
1488
1489 MergeReasonInto(integer_reason, &clause);
1490 if (!debug_checker_(clause, {})) {
1491 LOG(INFO) << "Invalid reason for loaded solution after merging: " << lit
1492 << " " << literal_reason << " " << integer_reason;
1493 return false;
1494 }
1495 return true;
1496}
1497
1499 Literal literal, absl::Span<const Literal> literal_reason,
1500 absl::Span<const IntegerLiteral> integer_reason) {
1501 return EnqueueLiteralInternal(literal, false, literal_reason, integer_reason);
1502}
1503
1505 Literal literal, absl::Span<const Literal> literal_reason,
1506 absl::Span<const IntegerLiteral> integer_reason) {
1507 if (trail_->Assignment().LiteralIsTrue(literal)) {
1508 return true;
1509 } else if (trail_->Assignment().LiteralIsFalse(literal)) {
1510 return ReportConflict(literal_reason, integer_reason);
1511 }
1512
1513 // Most of our propagation code do not use "constant" literal, so to not
1514 // have to test for them in Enqueue(), we clear them beforehand.
1515 tmp_cleaned_reason_.clear();
1516 for (const IntegerLiteral lit : integer_reason) {
1517 DCHECK(!lit.IsAlwaysFalse());
1518 if (lit.IsAlwaysTrue()) continue;
1519 tmp_cleaned_reason_.push_back(lit);
1520 }
1521 return EnqueueLiteralInternal(literal, false, literal_reason,
1522 tmp_cleaned_reason_);
1523}
1524
1525bool IntegerTrail::EnqueueLiteralInternal(
1526 Literal literal, bool use_lazy_reason,
1527 absl::Span<const Literal> literal_reason,
1528 absl::Span<const IntegerLiteral> integer_reason) {
1529 DCHECK(!trail_->Assignment().LiteralIsAssigned(literal));
1530 DCHECK(use_lazy_reason ||
1531 ReasonIsValid(literal, literal_reason, integer_reason));
1532 if (integer_search_levels_.empty()) {
1533 // Level zero. We don't keep any reason.
1534 trail_->EnqueueWithUnitReason(literal);
1535 } else {
1536 // If we are fixing something at a positive level, remember it.
1537 if (!integer_search_levels_.empty() && integer_reason.empty() &&
1538 literal_reason.empty() && !use_lazy_reason) {
1539 delayed_to_fix_->literal_to_fix.push_back(literal);
1540 }
1541
1542 const int trail_index = trail_->Index();
1543 if (trail_index >= boolean_trail_index_to_reason_index_.size()) {
1544 boolean_trail_index_to_reason_index_.resize(trail_index + 1);
1545 }
1546
1547 const ReasonIndex reason_index =
1548 use_lazy_reason
1549 ? -ReasonIndex(lazy_reasons_.size())
1550 : AppendReasonToInternalBuffers(literal_reason, integer_reason);
1551 boolean_trail_index_to_reason_index_[trail_index] = reason_index;
1552
1553 trail_->Enqueue(literal, propagator_id_);
1554 }
1555
1556 // Also push all the associated literals right away ?
1557 // It should be better rather than waiting for Propagate() to be called.
1558 //
1559 // TODO(user): This is currently failing. Fix. The likely reason is that
1560 // the new linear propagator do not assume that bounds can change if a literal
1561 // is pushed...
1562 if (/*DISABLES_CODE*/ (false)) {
1563 for (const IntegerLiteral i_lit : encoder_->GetIntegerLiterals(literal)) {
1564 // The reason is simply the associated literal.
1565 if (!EnqueueAssociatedIntegerLiteral(i_lit, literal)) {
1566 return false;
1567 }
1568 }
1569 }
1570
1571 return true;
1572}
1573
1574// We count the number of propagation at the current level, and returns true
1575// if it seems really large. Note that we disable this if we are in fixed
1576// search.
1578 if (parameters_.propagation_loop_detection_factor() == 0.0) return false;
1579 return (
1580 !integer_search_levels_.empty() &&
1581 integer_trail_.size() - integer_search_levels_.back() >
1582 std::max(10000.0, parameters_.propagation_loop_detection_factor() *
1583 static_cast<double>(var_lbs_.size())) &&
1584 parameters_.search_branching() != SatParameters::FIXED_SEARCH);
1585}
1586
1588 if (first_level_without_full_propagation_ == -1) {
1589 first_level_without_full_propagation_ = trail_->CurrentDecisionLevel();
1590 }
1591}
1592
1593// We try to select a variable with a large domain that was propagated a lot
1594// already.
1596 CHECK(InPropagationLoop());
1597 ++num_decisions_to_break_loop_;
1598 std::vector<IntegerVariable> vars;
1599 for (int i = integer_search_levels_.back(); i < integer_trail_.size(); ++i) {
1600 const IntegerVariable var = integer_trail_[i].var;
1601 if (var == kNoIntegerVariable) continue;
1602 if (UpperBound(var) - LowerBound(var) <= 100) continue;
1603 vars.push_back(var);
1604 }
1605 if (vars.empty()) return kNoIntegerVariable;
1606 std::sort(vars.begin(), vars.end());
1607 IntegerVariable best_var = vars[0];
1608 int best_count = 1;
1609 int count = 1;
1610 for (int i = 1; i < vars.size(); ++i) {
1611 if (vars[i] != vars[i - 1]) {
1612 count = 1;
1613 } else {
1614 ++count;
1615 if (count > best_count) {
1616 best_count = count;
1617 best_var = vars[i];
1618 }
1619 }
1620 }
1621 return best_var;
1622}
1623
1625 return first_level_without_full_propagation_ != -1;
1626}
1627
1629 for (IntegerVariable var(0); var < var_lbs_.size(); var += 2) {
1630 if (!IsFixed(var)) return var;
1631 }
1632 return kNoIntegerVariable;
1633}
1634
1635void IntegerTrail::CanonicalizeLiteralIfNeeded(IntegerLiteral* i_lit) {
1636 const PositiveOnlyIndex index = GetPositiveOnlyIndex(i_lit->var);
1637 const Domain& domain = (*domains_)[index];
1638 if (domain.NumIntervals() <= 1) return;
1639 if (VariableIsPositive(i_lit->var)) {
1640 i_lit->bound = domain.ValueAtOrAfter(i_lit->bound.value());
1641 } else {
1642 i_lit->bound = -domain.ValueAtOrBefore(-i_lit->bound.value());
1643 }
1644}
1645
1647 absl::Span<const Literal> literal_reason,
1648 absl::Span<const IntegerLiteral> integer_reason) {
1649 const ReasonIndex reason_index(literals_reason_starts_.size());
1650 DCHECK_EQ(reason_index, bounds_reason_starts_.size());
1651 DCHECK_EQ(reason_index, cached_sizes_.size());
1652
1653 literals_reason_starts_.push_back(literals_reason_buffer_.size());
1654 if (!literal_reason.empty()) {
1655 literals_reason_buffer_.insert(literals_reason_buffer_.end(),
1656 literal_reason.begin(),
1657 literal_reason.end());
1658 }
1659
1660 cached_sizes_.push_back(-1);
1661 bounds_reason_starts_.push_back(bounds_reason_buffer_.size());
1662 if (!integer_reason.empty()) {
1663 bounds_reason_buffer_.insert(bounds_reason_buffer_.end(),
1664 integer_reason.begin(), integer_reason.end());
1665 }
1666
1667 return reason_index;
1668}
1669
1670bool IntegerTrail::EnqueueInternal(
1671 IntegerLiteral i_lit, bool use_lazy_reason,
1672 absl::Span<const Literal> literal_reason,
1673 absl::Span<const IntegerLiteral> integer_reason,
1674 int trail_index_with_same_reason) {
1675 DCHECK(use_lazy_reason ||
1676 ReasonIsValid(i_lit, literal_reason, integer_reason));
1677 const IntegerVariable var(i_lit.var);
1678
1679 // Nothing to do if the bound is not better than the current one.
1680 // TODO(user): Change this to a CHECK? propagator shouldn't try to push such
1681 // bound and waste time explaining it.
1682 if (i_lit.bound <= var_lbs_[var]) return true;
1683 ++num_enqueues_;
1684
1685 // If the domain of var is not a single intervals and i_lit.bound fall into a
1686 // "hole", we increase it to the next possible value. This ensure that we
1687 // never Enqueue() non-canonical literals. See also Canonicalize().
1688 //
1689 // Note: The literals in the reason are not necessarily canonical, but then
1690 // we always map these to enqueued literals during conflict resolution.
1691 CanonicalizeLiteralIfNeeded(&i_lit);
1692 DCHECK_EQ(var, i_lit.var);
1693
1694 // Check if the integer variable has an empty domain.
1695 if (i_lit.bound > UpperBound(var)) {
1696 // We relax the upper bound as much as possible to still have a conflict.
1697 const auto ub_reason = IntegerLiteral::LowerOrEqual(var, i_lit.bound - 1);
1698
1699 // Note that we want only one call to MergeReasonIntoInternal() for
1700 // efficiency and a potential smaller reason.
1701 auto* conflict = InitializeConflict(i_lit, use_lazy_reason, literal_reason,
1702 integer_reason);
1703 if (!IsTrueAtLevelZero(ub_reason)) {
1704 tmp_queue_.push_back(FindLowestTrailIndexThatExplainBound(ub_reason));
1705 }
1706
1707 if (new_conflict_resolution_) {
1708 if (!IsTrueAtLevelZero(ub_reason)) {
1709 // Update tmp_reason_.
1710 tmp_integer_literals_.push_back(ub_reason);
1711 tmp_reason_.integer_literals = tmp_integer_literals_;
1712 }
1713
1714 // No need for MergeReasonIntoInternal() with the new resolution.
1715 conflict->clear();
1716 tmp_queue_.clear();
1717 return false;
1718 }
1719
1720 // TODO(user): fix or remove the conflict_id optimization.
1721 MergeReasonIntoInternal(conflict, -1);
1722 return false;
1723 }
1724
1725 // Stop propagating if we detect a propagation loop. The search heuristic will
1726 // then take an appropriate next decision. Note that we do that after checking
1727 // for a potential conflict if the two bounds of a variable cross. This is
1728 // important, so that in the corner case where all variables are actually
1729 // fixed, we still make sure no propagator detect a conflict.
1730 //
1731 // Tricky: We still propagates bounds in {-1, 0, 1}, this is because it seems
1732 // nice to propagate Boolean views. Also some propagator like the product one
1733 // pushes X >= 0 or X <= 0 and assume this is now true in the rest of the
1734 // code. Note that these push are safe since they cannot be more than
1735 // O(num_variables) of them.
1736 //
1737 // TODO(user): Some propagation code have CHECKS in place and not like when
1738 // something they just pushed is not reflected right away. They must be aware
1739 // of that, which is a bit tricky.
1740 if (InPropagationLoop() && IntTypeAbs(i_lit.bound) > 1) {
1741 // Note that we still propagate "big" push as it seems better to do that
1742 // now rather than to delay to the next decision.
1743 const IntegerValue lb = LowerBound(var);
1744 const IntegerValue ub = UpperBound(var);
1745 if (i_lit.bound - lb < (ub - lb) / 2) {
1746 if (first_level_without_full_propagation_ == -1) {
1747 first_level_without_full_propagation_ = trail_->CurrentDecisionLevel();
1748 }
1749 return true;
1750 }
1751 }
1752
1753 // Notify the watchers.
1754 for (SparseBitset<IntegerVariable>* bitset : watchers_) {
1755 bitset->Set(var);
1756 }
1757
1758 // Enqueue the strongest associated Boolean literal implied by this one.
1759 // Because we linked all such literal with implications, all the one before
1760 // will be propagated by the SAT solver.
1761 //
1762 // Important: It is possible that such literal or even stronger ones are
1763 // already true! This is because we might push stuff while Propagate() haven't
1764 // been called yet. Maybe we should call it?
1765 //
1766 // TODO(user): It might be simply better and more efficient to simply enqueue
1767 // all of them here. We have also more liberty to choose the explanation we
1768 // want. A drawback might be that the implications might not be used in the
1769 // binary conflict minimization algo.
1770 IntegerValue to_enqueue_bound;
1771 const LiteralIndex literal_index =
1772 encoder_->SearchForLiteralAtOrBefore(i_lit, &to_enqueue_bound);
1773 if (literal_index != kNoLiteralIndex) {
1774 CHECK_LE(to_enqueue_bound, i_lit.bound);
1775
1776 const Literal to_enqueue = Literal(literal_index);
1777 if (trail_->Assignment().LiteralIsFalse(to_enqueue)) {
1778 auto* conflict = InitializeConflict(i_lit, use_lazy_reason,
1779 literal_reason, integer_reason);
1780 conflict->push_back(to_enqueue);
1781
1782 if (new_conflict_resolution_) {
1783 // Update tmp_reason_.
1784 tmp_boolean_literals_.push_back(to_enqueue);
1785 tmp_reason_.boolean_literals_at_false = tmp_boolean_literals_;
1786
1787 // No need for MergeReasonIntoInternal() with the new resolution.
1788 conflict->clear();
1789 tmp_queue_.clear();
1790 return false;
1791 }
1792
1793 // TODO(user): fix or remove the conflict_id optimization.
1794 MergeReasonIntoInternal(conflict, -1);
1795 return false;
1796 }
1797
1798 // If not already true, we will always enqueue that associated literal.
1799 if (!trail_->Assignment().LiteralIsTrue(to_enqueue)) {
1800 if (!EnqueueLiteralInternal(to_enqueue, use_lazy_reason, literal_reason,
1801 integer_reason)) {
1802 return false;
1803 }
1804 }
1805
1806 // We then always push the corresponding bound if it is better than our
1807 // current one.
1808 //
1809 // This is better for explanation, since when we relax reason, we want to
1810 // see this associated literal. For example: if we have x>=3 and we push
1811 // x>=9 but we have a b <=> x>=7 we want on the "global trail": (x>=3) (b)
1812 // (x>=7) (x>=9). We introduced (x>=7) so that it can be used by explanation
1813 // instead of (x>=9).
1814 //
1815 // TODO(user): this would not be needed if EnqueueLiteralInternal() just
1816 // pushed all associated bounds right away.
1817 if (new_conflict_resolution_) {
1818 // We pick the best associated literal during conflict resolution,
1819 // so better not to enqueue extra bounds on the trail.
1820 if (to_enqueue_bound == i_lit.bound) {
1821 return EnqueueAssociatedIntegerLiteral(
1822 IntegerLiteral::GreaterOrEqual(var, to_enqueue_bound), to_enqueue);
1823 }
1824 } else {
1825 if (to_enqueue_bound > var_lbs_[var]) {
1826 if (!EnqueueAssociatedIntegerLiteral(
1827 IntegerLiteral::GreaterOrEqual(var, to_enqueue_bound),
1828 to_enqueue)) {
1829 return false;
1830 }
1831 }
1832
1833 // If the associated literal exactly correspond to i_lit or if there was
1834 // holes. Then we are done.
1835 if (var_lbs_[var] >= i_lit.bound) {
1836 return true;
1837 }
1838 }
1839 }
1840
1841 // Special case for level zero.
1842 if (integer_search_levels_.empty()) {
1843 ++num_level_zero_enqueues_;
1844 var_lbs_[var] = i_lit.bound;
1845 integer_trail_[var.value()].bound = i_lit.bound;
1846
1847 // We also update the initial domain. If this fail, since we are at level
1848 // zero, we don't care about the reason.
1849 trail_->MutableConflict()->clear();
1850 return UpdateInitialDomain(
1851 var, Domain(LowerBound(var).value(), UpperBound(var).value()));
1852 }
1853 DCHECK_GT(trail_->CurrentDecisionLevel(), 0);
1854
1855 // If we are not at level zero but there is not reason, we have a root level
1856 // deduction. Remember it so that we don't forget on the next restart.
1857 if (!integer_search_levels_.empty() && integer_reason.empty() &&
1858 literal_reason.empty() && !use_lazy_reason) {
1859 if (!RootLevelEnqueue(i_lit)) return false;
1860 }
1861
1862 ReasonIndex reason_index;
1863 if (use_lazy_reason) {
1864 reason_index = -static_cast<int>(lazy_reasons_.size());
1865 } else if (trail_index_with_same_reason >= integer_trail_.size()) {
1866 reason_index =
1867 AppendReasonToInternalBuffers(literal_reason, integer_reason);
1868 } else {
1869 reason_index = integer_trail_[trail_index_with_same_reason].reason_index;
1870 }
1871
1872 const int prev_trail_index = var_trail_index_[var];
1873 var_lbs_[var] = i_lit.bound;
1874 var_trail_index_[var] = integer_trail_.size();
1875 PushOnTrail(i_lit, prev_trail_index, trail_->Index(), reason_index,
1876 trail_->CurrentDecisionLevel());
1877 return true;
1878}
1879
1880void IntegerTrail::PushOnTrail(IntegerLiteral i_lit, int prev_trail_index,
1881 int bool_trail_index, ReasonIndex reason_index,
1882 int assignment_level) {
1883 const int i = integer_trail_.size();
1884 integer_trail_.push_back({/*bound=*/i_lit.bound,
1885 /*var=*/i_lit.var,
1886 /*prev_trail_index=*/prev_trail_index,
1887 /*reason_index=*/reason_index});
1888
1889 if (!new_conflict_resolution_) return;
1890 if (extra_trail_info_.size() < integer_trail_.size()) {
1891 extra_trail_info_.resize(integer_trail_.size());
1892 }
1893
1894 extra_trail_info_[i] = {/*assignment_level=*/assignment_level,
1895 /*bool_trail_index=*/bool_trail_index};
1896}
1897
1898bool IntegerTrail::EnqueueAssociatedIntegerLiteral(IntegerLiteral i_lit,
1899 Literal literal_reason) {
1900 DCHECK(ReasonIsValid(i_lit, {literal_reason.Negated()}, {}));
1901
1902 // Nothing to do if the bound is not better than the current one.
1903 if (i_lit.bound <= var_lbs_[i_lit.var]) return true;
1904 ++num_enqueues_;
1905
1906 // Make sure we do not fall into a hole.
1907 CanonicalizeLiteralIfNeeded(&i_lit);
1908
1909 // Check if the integer variable has an empty domain. Note that this should
1910 // happen really rarely since in most situation, pushing the upper bound would
1911 // have resulted in this literal beeing false. Because of this we revert to
1912 // the "generic" Enqueue() to avoid some code duplication.
1913 if (i_lit.bound > UpperBound(i_lit.var)) {
1914 return Enqueue(i_lit, {literal_reason.Negated()}, {});
1915 }
1916
1917 // Notify the watchers.
1918 for (SparseBitset<IntegerVariable>* bitset : watchers_) {
1919 bitset->Set(i_lit.var);
1920 }
1921
1922 // Special case for level zero.
1923 //
1924 // TODO(user): Refactor to just do everything in RootLevelEnqueue() and
1925 // avoid bad recursion if one call RootLevelEnqueue() at level zero from here.
1926 if (integer_search_levels_.empty()) {
1927 var_lbs_[i_lit.var] = i_lit.bound;
1928 integer_trail_[i_lit.var.value()].bound = i_lit.bound;
1929
1930 // We also update the initial domain. If this fail, since we are at level
1931 // zero, we don't care about the reason.
1932 trail_->MutableConflict()->clear();
1933 return UpdateInitialDomain(
1934 i_lit.var,
1935 Domain(LowerBound(i_lit.var).value(), UpperBound(i_lit.var).value()));
1936 }
1937 if (trail_->AssignmentLevel(literal_reason) == 0) {
1938 return RootLevelEnqueue(i_lit);
1939 }
1940 DCHECK_GT(trail_->CurrentDecisionLevel(), 0);
1941
1942 const ReasonIndex reason_index =
1943 AppendReasonToInternalBuffers({literal_reason.Negated()}, {});
1944 const int prev_trail_index = var_trail_index_[i_lit.var];
1945 var_lbs_[i_lit.var] = i_lit.bound;
1946 var_trail_index_[i_lit.var] = integer_trail_.size();
1947
1948 // We use as a boolean trail_index the next one after the one of
1949 // literal_reason.
1950 PushOnTrail(i_lit, prev_trail_index,
1951 trail_->Info(literal_reason.Variable()).trail_index + 1,
1952 reason_index, trail_->AssignmentLevel(literal_reason));
1953 return true;
1954}
1955
1956void IntegerTrail::ComputeLazyReasonIfNeeded(ReasonIndex index) const {
1957 tmp_lazy_reason_.clear();
1958 if (index >= 0) return; // non-lazy.
1959 lazy_reasons_[-index - 1].Explain(&tmp_lazy_reason_);
1960
1961 constexpr int64_t check_period = 1e6;
1962 int64_t limit_check = work_done_in_find_indices_ + check_period;
1963
1964 lazy_reason_literals_.assign(
1965 tmp_lazy_reason_.boolean_literals_at_false.begin(),
1966 tmp_lazy_reason_.boolean_literals_at_false.end());
1967 for (const Literal lit : tmp_lazy_reason_.boolean_literals_at_true) {
1968 lazy_reason_literals_.push_back(lit.Negated());
1969 }
1970
1971 lazy_reason_trail_indices_.clear();
1972 for (const IntegerLiteral i_lit : tmp_lazy_reason_.integer_literals) {
1973 DCHECK(!i_lit.IsAlwaysFalse());
1974 if (i_lit.IsAlwaysTrue()) continue;
1975 if (IsTrueAtLevelZero(i_lit)) continue;
1976
1977 // Skip if we already have an explanation for expr >= target_min. Note
1978 // that we already do that while processing the returned indices, so this
1979 // mainly save a FindLowestTrailIndexThatExplainBound() call per skipped
1980 // indices, which can still be costly.
1981 const int index = tmp_var_to_trail_index_in_queue_[i_lit.var];
1982 if (index == std::numeric_limits<int>::max()) continue;
1983 if (index > 0 && integer_trail_[index].bound >= i_lit.bound) {
1984 has_dependency_ = true;
1985 continue;
1986 }
1987
1988 // We need to find the index that explain the bound.
1989 lazy_reason_trail_indices_.push_back(
1990 FindLowestTrailIndexThatExplainBound(i_lit));
1991
1992 // On large routing problems, we can spend a lot of time in this loop.
1993 if (work_done_in_find_indices_ > limit_check) {
1994 limit_check = work_done_in_find_indices_ + check_period;
1995 if (time_limit_->LimitReached()) return;
1996 }
1997 }
1998
1999 // Deal with the linear-reason.
2000 if (!tmp_lazy_reason_.vars.empty()) {
2001 tmp_indices_.clear();
2002 tmp_coeffs_.clear();
2003
2004 const IntegerVariable to_ignore =
2005 PositiveVariable(tmp_lazy_reason_.propagated_i_lit.var);
2006 for (int i = 0; i < tmp_lazy_reason_.vars.size(); ++i) {
2007 const IntegerVariable var = tmp_lazy_reason_.vars[i];
2008 if (PositiveVariable(var) == to_ignore) continue;
2009
2010 const int index =
2011 FindTrailIndexOfVarBefore(var, tmp_lazy_reason_.index_at_propagation);
2012 if (index >= 0) {
2013 tmp_indices_.push_back(index);
2014 if (tmp_lazy_reason_.slack > 0) {
2015 tmp_coeffs_.push_back(tmp_lazy_reason_.coeffs[i]);
2016 }
2017 }
2018 }
2019 if (tmp_lazy_reason_.slack > 0) {
2020 DCHECK_EQ(tmp_lazy_reason_.vars.size(), tmp_lazy_reason_.coeffs.size());
2021 RelaxLinearReason(tmp_lazy_reason_.slack, tmp_coeffs_, &tmp_indices_);
2022 }
2023 lazy_reason_trail_indices_.insert(lazy_reason_trail_indices_.end(),
2024 tmp_indices_.begin(), tmp_indices_.end());
2025 }
2026}
2027
2028absl::Span<const IntegerLiteral> IntegerTrail::IntegerReasonAsSpan(
2029 ReasonIndex index) const {
2030 CHECK_GE(index, 0);
2031 const int start = bounds_reason_starts_[index];
2032 const int end = index + 1 < bounds_reason_starts_.size()
2033 ? bounds_reason_starts_[index + 1]
2034 : bounds_reason_buffer_.size();
2035 return absl::MakeSpan(bounds_reason_buffer_.data() + start, end - start);
2036}
2037
2038absl::Span<const int> IntegerTrail::Dependencies(ReasonIndex index) const {
2039 if (index < 0) {
2040 return absl::Span<const int>(lazy_reason_trail_indices_);
2041 }
2042
2043 const int cached_size = cached_sizes_[index];
2044 if (cached_size == 0) return {};
2045
2046 const int start = bounds_reason_starts_[index];
2047 if (cached_size > 0) {
2048 return absl::MakeSpan(&trail_index_reason_buffer_[start], cached_size);
2049 }
2050
2051 // Else we cache.
2052 DCHECK_EQ(cached_size, -1);
2053 const int end = index + 1 < bounds_reason_starts_.size()
2054 ? bounds_reason_starts_[index + 1]
2055 : bounds_reason_buffer_.size();
2056 if (end > trail_index_reason_buffer_.size()) {
2057 trail_index_reason_buffer_.resize(end);
2058 }
2059
2060 int new_size = 0;
2061 int* data = trail_index_reason_buffer_.data() + start;
2062 for (int i = start; i < end; ++i) {
2063 const IntegerLiteral to_explain = bounds_reason_buffer_[i];
2064 if (!IsTrueAtLevelZero(to_explain)) {
2065 data[new_size++] = FindLowestTrailIndexThatExplainBound(to_explain);
2066 }
2067 }
2068 cached_sizes_[index] = new_size;
2069 if (new_size == 0) return {};
2070 return absl::MakeSpan(data, new_size);
2071}
2072
2073absl::Span<const Literal> IntegerTrail::LiteralReasonAsSpan(
2074 ReasonIndex index) const {
2075 if (index < 0) {
2076 return lazy_reason_literals_;
2077 }
2078
2079 const int start = literals_reason_starts_[index];
2080 const int end = index + 1 < literals_reason_starts_.size()
2081 ? literals_reason_starts_[index + 1]
2082 : literals_reason_buffer_.size();
2083 return absl::MakeSpan(literals_reason_buffer_.data() + start, end - start);
2084}
2085
2086void IntegerTrail::AppendLiteralsReason(ReasonIndex index,
2087 std::vector<Literal>* output) const {
2088 for (const Literal lit : LiteralReasonAsSpan(index)) {
2089 if (!added_variables_[lit.Variable()]) {
2090 added_variables_.Set(lit.Variable());
2091 output->push_back(lit);
2092 }
2093 }
2094}
2095
2096std::vector<Literal> IntegerTrail::ReasonFor(IntegerLiteral literal) const {
2097 std::vector<Literal> reason;
2098 MergeReasonInto({literal}, &reason);
2099 return reason;
2100}
2101
2102void IntegerTrail::MergeReasonInto(absl::Span<const IntegerLiteral> literals,
2103 std::vector<Literal>* output) const {
2104 DCHECK(tmp_queue_.empty());
2105 for (const IntegerLiteral& literal : literals) {
2106 if (literal.IsAlwaysTrue()) continue;
2107 if (IsTrueAtLevelZero(literal)) continue;
2108 tmp_queue_.push_back(FindLowestTrailIndexThatExplainBound(literal));
2109 }
2110 return MergeReasonIntoInternal(output, -1);
2111}
2112
2113// This will expand the reason of the IntegerLiteral already in tmp_queue_ until
2114// everything is explained in term of Literal.
2115void IntegerTrail::MergeReasonIntoInternal(std::vector<Literal>* output,
2116 int64_t conflict_id) const {
2117 // All relevant trail indices will be >= var_lbs_.size(), so we can safely use
2118 // zero to means that no literal referring to this variable is in the queue.
2119 DCHECK(std::all_of(tmp_var_to_trail_index_in_queue_.begin(),
2120 tmp_var_to_trail_index_in_queue_.end(),
2121 [](int v) { return v == 0; }));
2122 DCHECK(tmp_to_clear_.empty());
2123
2124 info_is_valid_on_subsequent_last_level_expansion_ = true;
2125 if (conflict_id == -1 || last_conflict_id_ != conflict_id) {
2126 // New conflict or a reason was asked outside first UIP resolution.
2127 // We just clear everything.
2128 last_conflict_id_ = conflict_id;
2129 for (const IntegerVariable var : to_clear_for_lower_level_) {
2130 var_to_trail_index_at_lower_level_[var] = 0;
2131 }
2132 to_clear_for_lower_level_.clear();
2133 }
2134
2135 const int last_decision_index =
2136 integer_search_levels_.empty() || conflict_id == -1
2137 ? 0
2138 : integer_search_levels_.back();
2139
2140 added_variables_.ClearAndResize(BooleanVariable(trail_->NumVariables()));
2141 for (const Literal l : *output) {
2142 added_variables_.Set(l.Variable());
2143 }
2144
2145 // During the algorithm execution, all the queue entries that do not match the
2146 // content of tmp_var_to_trail_index_in_queue_[] will be ignored.
2147 for (const int trail_index : tmp_queue_) {
2148 DCHECK_GE(trail_index, var_lbs_.size());
2149 DCHECK_LT(trail_index, integer_trail_.size());
2150 const TrailEntry& entry = integer_trail_[trail_index];
2151 DCHECK_NE(entry.var, kNoIntegerVariable);
2152 tmp_var_to_trail_index_in_queue_[entry.var] =
2153 std::max(tmp_var_to_trail_index_in_queue_[entry.var], trail_index);
2154 }
2155
2156 // We manage our heap by hand so that we can range iterate over it above, and
2157 // this initial heapify is faster.
2158 std::make_heap(tmp_queue_.begin(), tmp_queue_.end());
2159
2160 // We process the entries by highest trail_index first. The content of the
2161 // queue will always be a valid reason for the literals we already added to
2162 // the output.
2163 int64_t work_done = 0;
2164 while (!tmp_queue_.empty()) {
2165 ++work_done;
2166 const int trail_index = tmp_queue_.front();
2167 const TrailEntry& entry = integer_trail_[trail_index];
2168 std::pop_heap(tmp_queue_.begin(), tmp_queue_.end());
2169 tmp_queue_.pop_back();
2170
2171 // Skip any stale queue entry. Amongst all the entry referring to a given
2172 // variable, only the latest added to the queue is valid and we detect it
2173 // using its trail index.
2174 DCHECK_NE(entry.var, kNoIntegerVariable);
2175 if (tmp_var_to_trail_index_in_queue_[entry.var] != trail_index) {
2176 continue;
2177 }
2178
2179 // Process this entry. Note that if any of the next expansion include the
2180 // variable entry.var in their reason, we must process it again because we
2181 // cannot easily detect if it was needed to infer the current entry.
2182 //
2183 // Important: the queue might already contains entries referring to the same
2184 // variable. The code act like if we deleted all of them at this point, we
2185 // just do that lazily. tmp_var_to_trail_index_in_queue_[var] will
2186 // only refer to newly added entries.
2187 //
2188 // TODO(user): We can and should reset that to the initial value from
2189 // var_to_trail_index_at_lower_level_ instead of zero.
2190 tmp_var_to_trail_index_in_queue_[entry.var] = 0;
2191 has_dependency_ = false;
2192
2193 // Skip entries that we known are already explained by the part of the
2194 // conflict not involving the last level.
2195 if (var_to_trail_index_at_lower_level_[entry.var] >= trail_index) {
2196 continue;
2197 }
2198
2199 // If this literal is not at the highest level, it will always be
2200 // propagated by the current conflict (even after some 1-UIP resolution
2201 // step). We save this fact so that future MergeReasonIntoInternal() on
2202 // the same conflict can just avoid to expand integer literal that are
2203 // already known to be implied.
2204 if (trail_index < last_decision_index) {
2205 tmp_seen_.push_back(trail_index);
2206 }
2207
2208 // Set the cache threshold. Since we process trail indices in decreasing
2209 // order and we only have single linked list, we only want to advance the
2210 // "cache" up to this threshold.
2211 var_trail_index_cache_threshold_ = trail_index;
2212
2213 // If this entry has an associated literal, then it should always be the
2214 // one we used for the reason. This code DCHECK that.
2215 if (DEBUG_MODE) {
2216 const LiteralIndex associated_lit =
2217 encoder_->GetAssociatedLiteral(IntegerLiteral::GreaterOrEqual(
2218 IntegerVariable(entry.var), entry.bound));
2219 if (associated_lit != kNoLiteralIndex) {
2220 // We check that the reason is the same!
2221 const ReasonIndex reason_index =
2222 integer_trail_[trail_index].reason_index;
2223 CHECK_GE(reason_index, 0);
2224 {
2225 const int start = literals_reason_starts_[reason_index];
2226 const int end = reason_index + 1 < literals_reason_starts_.size()
2227 ? literals_reason_starts_[reason_index + 1]
2228 : literals_reason_buffer_.size();
2229 CHECK_EQ(start + 1, end);
2230
2231 // Because we can update initial domains, an associated literal might
2232 // fall in a domain hole and can be different when canonicalized.
2233 //
2234 // TODO(user): Make the contract clearer, it is messy right now.
2235 if (/*DISABLES_CODE*/ (false)) {
2236 CHECK_EQ(literals_reason_buffer_[start],
2237 Literal(associated_lit).Negated());
2238 }
2239 }
2240 {
2241 const int start = bounds_reason_starts_[reason_index];
2242 const int end = reason_index + 1 < bounds_reason_starts_.size()
2243 ? bounds_reason_starts_[reason_index + 1]
2244 : bounds_reason_buffer_.size();
2245 CHECK_EQ(start, end);
2246 }
2247 }
2248 }
2249
2250 ComputeLazyReasonIfNeeded(entry.reason_index);
2251 AppendLiteralsReason(entry.reason_index, output);
2252 const auto dependencies = Dependencies(entry.reason_index);
2253 work_done += dependencies.size();
2254 for (const int next_trail_index : dependencies) {
2255 DCHECK_LT(next_trail_index, trail_index);
2256 const TrailEntry& next_entry = integer_trail_[next_trail_index];
2257
2258 // Tricky: we cache trail_index in Dependencies() but it is possible
2259 // via RootLevelEnqueue() that some of the trail index listed here are
2260 // "stale", so we skip any entry with a kNoIntegerVariable.
2261 if (next_entry.var == kNoIntegerVariable) continue;
2262
2263 // Only add literals that are not "implied" by the ones already present.
2264 // For instance, do not add (x >= 4) if we already have (x >= 7). This
2265 // translate into only adding a trail index if it is larger than the one
2266 // in the queue referring to the same variable.
2267 const int index_in_queue =
2268 tmp_var_to_trail_index_in_queue_[next_entry.var];
2269
2270 // This means the integer literal had no dependency and is already
2271 // explained by the literal we added.
2272 if (index_in_queue >= trail_index) {
2273 // Disable the other optim if we might expand this literal during
2274 // 1-UIP resolution.
2275 if (index_in_queue >= last_decision_index) {
2276 info_is_valid_on_subsequent_last_level_expansion_ = false;
2277 }
2278 continue;
2279 }
2280
2281 if (next_trail_index <=
2282 var_to_trail_index_at_lower_level_[next_entry.var]) {
2283 continue;
2284 }
2285
2286 has_dependency_ = true;
2287 if (next_trail_index > index_in_queue) {
2288 tmp_var_to_trail_index_in_queue_[next_entry.var] = next_trail_index;
2289 tmp_queue_.push_back(next_trail_index);
2290 std::push_heap(tmp_queue_.begin(), tmp_queue_.end());
2291 }
2292 }
2293
2294 // Special case for a "leaf", we will never need this variable again in the
2295 // current explanation.
2296 if (!has_dependency_) {
2297 tmp_to_clear_.push_back(entry.var);
2298 tmp_var_to_trail_index_in_queue_[entry.var] = trail_index;
2299 }
2300 }
2301
2302 // Update var_to_trail_index_at_lower_level_.
2303 if (info_is_valid_on_subsequent_last_level_expansion_) {
2304 for (const int trail_index : tmp_seen_) {
2305 if (trail_index == 0) continue;
2306 const TrailEntry& entry = integer_trail_[trail_index];
2307 const int old = var_to_trail_index_at_lower_level_[entry.var];
2308 if (old == 0) {
2309 to_clear_for_lower_level_.push_back(entry.var);
2310 }
2311 var_to_trail_index_at_lower_level_[entry.var] =
2312 std::max(old, trail_index);
2313 }
2314 }
2315 tmp_seen_.clear();
2316
2317 // clean-up.
2318 for (const IntegerVariable var : tmp_to_clear_) {
2319 tmp_var_to_trail_index_in_queue_[var] = 0;
2320 }
2321 tmp_to_clear_.clear();
2322
2323 time_limit_->AdvanceDeterministicTime(work_done * 5e-9);
2324}
2325
2326// TODO(user): If this is called many time on the same variables, it could be
2327// made faster by using some caching mechanism.
2328absl::Span<const Literal> IntegerTrail::Reason(
2329 const Trail& trail, int trail_index, int64_t /* conflict_id */) const {
2330 std::vector<Literal>* reason = trail.GetEmptyVectorToStoreReason(trail_index);
2331 added_variables_.ClearAndResize(BooleanVariable(trail_->NumVariables()));
2332
2333 const ReasonIndex reason_index =
2334 boolean_trail_index_to_reason_index_[trail_index];
2335 ComputeLazyReasonIfNeeded(reason_index);
2336 AppendLiteralsReason(reason_index, reason);
2337 DCHECK(tmp_queue_.empty());
2338 for (const int prev_trail_index : Dependencies(reason_index)) {
2339 DCHECK_GE(prev_trail_index, var_lbs_.size());
2340
2341 // Tricky: we cache trail_index in Dependencies() but it is possible
2342 // via RootLevelEnqueue() that some of the trail indices listed here are
2343 // "stale", so we skip any entry with a kNoIntegerVariable.
2344 const TrailEntry& next_entry = integer_trail_[prev_trail_index];
2345 if (next_entry.var == kNoIntegerVariable) continue;
2346
2347 tmp_queue_.push_back(prev_trail_index);
2348 }
2349
2350 // TODO(user): fix or remove the conflict_id optimization.
2351 // It is probably superseded by the new integer resolution in any case,
2352 // so I fill we could just remove it.
2353 MergeReasonIntoInternal(reason, -1);
2354
2355 if (DEBUG_MODE && debug_checker_ != nullptr) {
2356 reason->push_back(trail[trail_index]);
2357 CHECK(debug_checker_(*reason, {}))
2358 << (reason_index >= 0 ? ""
2359 : absl::StrCat("lazy reason for propagator ",
2360 lazy_reasons_[-reason_index - 1]
2361 .explainer->LazyReasonName()));
2362 reason->pop_back();
2363 }
2364 return *reason;
2365}
2366
2368 GlobalTrailIndex index, std::optional<IntegerValue> needed_bound) {
2369 tmp_reason_.clear();
2370
2371 ReasonIndex reason_index;
2372 if (index.IsBoolean()) {
2373 // We have a boolean, check if it was propagated by us.
2374 //
2375 // IMPORTANT: we need to use AssignmentType() not info.type to bypass the
2376 // cache and have the proper integer reason!
2377 if (trail_->AssignmentType((*trail_)[index.bool_index].Variable()) ==
2379 reason_index = boolean_trail_index_to_reason_index_[index.bool_index];
2380 } else {
2381 // This is a normal reason, just convert it.
2382 const BooleanVariable b = (*trail_)[index.bool_index].Variable();
2383 tmp_reason_.boolean_literals_at_false = trail_->Reason(b);
2384 return tmp_reason_;
2385 }
2386 } else {
2387 reason_index = integer_trail_[index.integer_index].reason_index;
2388 }
2389
2390 if (reason_index < 0) {
2391 // If we have a lazy reason, execute it.
2392 if (needed_bound != std::nullopt) {
2393 lazy_reasons_[-reason_index - 1].Explain(*needed_bound, &tmp_reason_);
2394 } else {
2395 lazy_reasons_[-reason_index - 1].Explain(&tmp_reason_);
2396 }
2397 } else {
2398 // Reason is not lazy, just return a reference to our two spans.
2399 tmp_reason_.boolean_literals_at_false = LiteralReasonAsSpan(reason_index);
2400 tmp_reason_.integer_literals = IntegerReasonAsSpan(reason_index);
2401 }
2402
2403 return tmp_reason_;
2404}
2405
2406void IntegerTrail::AppendNewBounds(std::vector<IntegerLiteral>* output) const {
2407 return AppendNewBoundsFrom(var_lbs_.size(), output);
2408}
2409
2410// TODO(user): Implement a dense version if there is more trail entries
2411// than variables!
2413 int base_index, std::vector<IntegerLiteral>* output) const {
2414 tmp_marked_.ClearAndResize(IntegerVariable(var_lbs_.size()));
2415
2416 // In order to push the best bound for each variable, we loop backward.
2417 CHECK_GE(base_index, var_lbs_.size());
2418 for (int i = integer_trail_.size(); --i >= base_index;) {
2419 const TrailEntry& entry = integer_trail_[i];
2420 if (entry.var == kNoIntegerVariable) continue;
2421 if (tmp_marked_[entry.var]) continue;
2422
2423 tmp_marked_.Set(entry.var);
2424 output->push_back(IntegerLiteral::GreaterOrEqual(entry.var, entry.bound));
2425 }
2426}
2427
2429 : SatPropagator("GenericLiteralWatcher"),
2430 time_limit_(model->GetOrCreate<TimeLimit>()),
2431 integer_trail_(model->GetOrCreate<IntegerTrail>()),
2432 rev_int_repository_(model->GetOrCreate<RevIntRepository>()) {
2433 // TODO(user): This propagator currently needs to be last because it is the
2434 // only one enforcing that a fix-point is reached on the integer variables.
2435 // Figure out a better interaction between the sat propagation loop and
2436 // this one.
2437 model->GetOrCreate<SatSolver>()->AddLastPropagator(this);
2438
2439 integer_trail_->RegisterReversibleClass(
2440 &id_to_greatest_common_level_since_last_call_);
2441 integer_trail_->RegisterWatcher(&modified_vars_);
2442 queue_by_priority_.resize(2); // Because default priority is 1.
2443}
2444
2446 var_to_watcher_.reserve(2 * num_vars);
2447}
2448
2450 if (in_queue_[id]) return;
2451 in_queue_[id] = true;
2452 queue_by_priority_[id_to_priority_[id]].push_back(id);
2453}
2454
2455void GenericLiteralWatcher::UpdateCallingNeeds(Trail* trail) {
2456 // Process any new Literal on the trail.
2457 const int literal_limit = literal_to_watcher_.size();
2458 while (propagation_trail_index_ < trail->Index()) {
2459 const Literal literal = (*trail)[propagation_trail_index_++];
2460 if (literal.Index() >= literal_limit) continue;
2461 for (const auto entry : literal_to_watcher_[literal]) {
2462 CallOnNextPropagate(entry.id);
2463 if (entry.watch_index >= 0) {
2464 id_to_watch_indices_[entry.id].push_back(entry.watch_index);
2465 }
2466 }
2467 }
2468
2469 // Process the newly changed variables lower bounds.
2470 const int var_limit = var_to_watcher_.size();
2471 for (const IntegerVariable var : modified_vars_.PositionsSetAtLeastOnce()) {
2472 if (var.value() >= var_limit) continue;
2473 for (const auto entry : var_to_watcher_[var]) {
2474 CallOnNextPropagate(entry.id);
2475 if (entry.watch_index >= 0) {
2476 id_to_watch_indices_[entry.id].push_back(entry.watch_index);
2477 }
2478 }
2479 }
2480
2481 if (trail->CurrentDecisionLevel() == 0 &&
2482 !level_zero_modified_variable_callback_.empty()) {
2483 modified_vars_for_callback_.Resize(modified_vars_.size());
2484 for (const IntegerVariable var : modified_vars_.PositionsSetAtLeastOnce()) {
2485 modified_vars_for_callback_.Set(var);
2486 }
2487 }
2488
2489 modified_vars_.ClearAndResize(integer_trail_->NumIntegerVariables());
2490}
2491
2493 // Only once per call to Propagate(), if we are at level zero, we might want
2494 // to call propagators even if the bounds didn't change.
2495 const int level = trail->CurrentDecisionLevel();
2496 if (level == 0) {
2497 for (const int id : propagator_ids_to_call_at_level_zero_) {
2499 }
2500 }
2501
2502 UpdateCallingNeeds(trail);
2503
2504 // Increase the deterministic time depending on some basic fact about our
2505 // propagation.
2506 int64_t num_propagate_calls = 0;
2507 const int64_t old_enqueue = integer_trail_->num_enqueues();
2508 auto cleanup =
2509 ::absl::MakeCleanup([&num_propagate_calls, old_enqueue, this]() {
2510 const int64_t diff = integer_trail_->num_enqueues() - old_enqueue;
2511 time_limit_->AdvanceDeterministicTime(1e-8 * num_propagate_calls +
2512 1e-7 * diff);
2513 });
2514
2515 // Note that the priority may be set to -1 inside the loop in order to restart
2516 // at zero.
2517 int test_limit = 0;
2518 for (int priority = 0; priority < queue_by_priority_.size(); ++priority) {
2519 // We test the time limit from time to time. This is in order to return in
2520 // case of slow propagation.
2521 //
2522 // TODO(user): The queue will not be emptied, but I am not sure the solver
2523 // will be left in an usable state. Fix if it become needed to resume
2524 // the solve from the last time it was interrupted. In particular, we might
2525 // want to call UpdateCallingNeeds()?
2526 if (test_limit > 100) {
2527 test_limit = 0;
2528 if (time_limit_->LimitReached()) break;
2529 }
2530 if (stop_propagation_callback_ != nullptr && stop_propagation_callback_()) {
2531 integer_trail_->NotifyThatPropagationWasAborted();
2532 break;
2533 }
2534
2535 std::deque<int>& queue = queue_by_priority_[priority];
2536 while (!queue.empty()) {
2537 const int id = queue.front();
2538 queue.pop_front();
2539
2540 // Before we propagate, make sure any reversible structure are up to date.
2541 // Note that we never do anything expensive more than once per level.
2542 if (id_need_reversible_support_[id]) {
2543 const int low =
2544 id_to_greatest_common_level_since_last_call_[IdType(id)];
2545 const int high = id_to_level_at_last_call_[id];
2546 if (low < high || level > low) { // Equivalent to not all equal.
2547 id_to_level_at_last_call_[id] = level;
2548 id_to_greatest_common_level_since_last_call_.MutableRef(IdType(id)) =
2549 level;
2550 for (ReversibleInterface* rev : id_to_reversible_classes_[id]) {
2551 if (low < high) rev->SetLevel(low);
2552 if (level > low) rev->SetLevel(level);
2553 }
2554 for (int* rev_int : id_to_reversible_ints_[id]) {
2555 rev_int_repository_->SaveState(rev_int);
2556 }
2557 }
2558 }
2559
2560 // This is needed to detect if the propagator propagated anything or not.
2561 const int64_t old_integer_timestamp = integer_trail_->num_enqueues();
2562 const int64_t old_boolean_timestamp = trail->Index();
2563
2564 // Set fields that might be accessed from within Propagate().
2565 current_id_ = id;
2566 call_again_ = false;
2567
2568 // TODO(user): Maybe just provide one function Propagate(watch_indices) ?
2569 ++num_propagate_calls;
2570 const bool result =
2571 id_to_watch_indices_[id].empty()
2572 ? watchers_[id]->Propagate()
2573 : watchers_[id]->IncrementalPropagate(id_to_watch_indices_[id]);
2574 if (!result) {
2575 id_to_watch_indices_[id].clear();
2576 in_queue_[id] = false;
2577 return false;
2578 }
2579
2580 // Update the propagation queue. At this point, the propagator has been
2581 // removed from the queue but in_queue_ is still true.
2582 if (id_to_idempotence_[id]) {
2583 // If the propagator is assumed to be idempotent, then we set
2584 // in_queue_ to false after UpdateCallingNeeds() so this later
2585 // function will never add it back.
2586 UpdateCallingNeeds(trail);
2587 id_to_watch_indices_[id].clear();
2588 in_queue_[id] = false;
2589 } else {
2590 // Otherwise, we set in_queue_ to false first so that
2591 // UpdateCallingNeeds() may add it back if the propagator modified any
2592 // of its watched variables.
2593 id_to_watch_indices_[id].clear();
2594 in_queue_[id] = false;
2595 UpdateCallingNeeds(trail);
2596 }
2597
2598 if (call_again_) {
2599 CallOnNextPropagate(current_id_);
2600 }
2601
2602 // If the propagator pushed a literal, we exit in order to rerun all SAT
2603 // only propagators first. Note that since a literal was pushed we are
2604 // guaranteed to be called again, and we will resume from priority 0.
2605 if (trail->Index() > old_boolean_timestamp) {
2606 // Important: for now we need to re-run the clauses propagator each time
2607 // we push a new literal because some propagator like the arc consistent
2608 // all diff relies on this.
2609 //
2610 // TODO(user): However, on some problem, it seems to work better to not
2611 // do that. One possible reason is that the reason of a "natural"
2612 // propagation might be better than one we learned.
2613 return true;
2614 }
2615
2616 // If the propagator pushed an integer bound, we revert to priority = 0.
2617 if (integer_trail_->num_enqueues() > old_integer_timestamp) {
2618 ++test_limit;
2619 priority = -1; // Because of the ++priority in the for loop.
2620 break;
2621 }
2622 }
2623 }
2624
2625 // We wait until we reach the fix point before calling the callback.
2626 if (trail->CurrentDecisionLevel() == 0) {
2627 const std::vector<IntegerVariable>& modified_vars =
2628 modified_vars_for_callback_.PositionsSetAtLeastOnce();
2629 for (const auto& callback : level_zero_modified_variable_callback_) {
2630 callback(modified_vars);
2631 }
2632 modified_vars_for_callback_.ClearAndResize(
2633 integer_trail_->NumIntegerVariables());
2634 }
2635
2636 return true;
2637}
2638
2639void GenericLiteralWatcher::Untrail(const Trail& trail, int trail_index) {
2640 if (propagation_trail_index_ <= trail_index) {
2641 // Nothing to do since we found a conflict before Propagate() was called.
2642 if (DEBUG_MODE) {
2643 // The assumption is not always true if we are currently aborting.
2644 if (time_limit_->LimitReached()) return;
2645 CHECK_EQ(propagation_trail_index_, trail_index)
2646 << " level " << trail.CurrentDecisionLevel();
2647 }
2648 return;
2649 }
2650
2651 // Note that we can do that after the test above: If none of the propagator
2652 // where called, there are still technically "in dive" if we didn't backtrack
2653 // past their last Propagate() call.
2654 for (bool* to_reset : bool_to_reset_on_backtrack_) *to_reset = false;
2655 bool_to_reset_on_backtrack_.clear();
2656
2657 // We need to clear the watch indices on untrail.
2658 for (std::deque<int>& queue : queue_by_priority_) {
2659 for (const int id : queue) {
2660 id_to_watch_indices_[id].clear();
2661 }
2662 queue.clear();
2663 }
2664
2665 // This means that we already propagated all there is to propagate
2666 // at the level trail_index, so we can safely clear modified_vars_ in case
2667 // it wasn't already done.
2668 propagation_trail_index_ = trail_index;
2669 modified_vars_.ClearAndResize(integer_trail_->NumIntegerVariables());
2670 in_queue_.assign(watchers_.size(), false);
2671}
2672
2673// Registers a propagator and returns its unique ids.
2675 const int id = watchers_.size();
2676 watchers_.push_back(propagator);
2677
2678 id_need_reversible_support_.push_back(false);
2679 id_to_level_at_last_call_.push_back(0);
2680 id_to_greatest_common_level_since_last_call_.GrowByOne();
2681 id_to_reversible_classes_.push_back(std::vector<ReversibleInterface*>());
2682 id_to_reversible_ints_.push_back(std::vector<int*>());
2683
2684 id_to_watch_indices_.push_back(std::vector<int>());
2685 id_to_priority_.push_back(1);
2686 id_to_idempotence_.push_back(true);
2687
2688 // Call this propagator at least once the next time Propagate() is called.
2689 //
2690 // TODO(user): This initial propagation does not respect any later priority
2691 // settings. Fix this. Maybe we should force users to pass the priority at
2692 // registration. For now I didn't want to change the interface because there
2693 // are plans to implement a kind of "dynamic" priority, and if it works we may
2694 // want to get rid of this altogether.
2695 in_queue_.push_back(true);
2696 queue_by_priority_[1].push_back(id);
2697 return id;
2698}
2699
2701 id_to_priority_[id] = priority;
2702 if (priority >= queue_by_priority_.size()) {
2703 queue_by_priority_.resize(priority + 1);
2704 }
2705}
2706
2708 int id) {
2709 id_to_idempotence_[id] = false;
2710}
2711
2713 propagator_ids_to_call_at_level_zero_.push_back(id);
2714}
2715
2717 ReversibleInterface* rev) {
2718 id_need_reversible_support_[id] = true;
2719 id_to_reversible_classes_[id].push_back(rev);
2720}
2721
2723 id_need_reversible_support_[id] = true;
2724 id_to_reversible_ints_[id].push_back(rev);
2725}
2726
2727} // namespace sat
2728} // namespace operations_research
int64_t ValueAtOrAfter(int64_t input) const
Domain IntersectionWith(const Domain &domain) const
bool Contains(int64_t value) const
absl::InlinedVector< ClosedInterval, 1 >::const_iterator end() const
int64_t ValueAtOrBefore(int64_t input) const
void ClearAndResize(IntegerType size)
Definition bitset.h:852
void Set(IntegerType index)
Definition bitset.h:878
void Untrail(const Trail &trail, int literal_trail_index) final
Definition integer.cc:2639
void SetPropagatorPriority(int id, int priority)
Definition integer.cc:2700
void RegisterReversibleClass(int id, ReversibleInterface *rev)
Definition integer.cc:2716
int Register(PropagatorInterface *propagator)
Definition integer.cc:2674
const std::vector< ValueLiteralPair > & FullDomainEncoding(IntegerVariable var) const
Definition integer.cc:146
std::pair< IntegerLiteral, IntegerLiteral > Canonicalize(IntegerLiteral i_lit) const
Definition integer.cc:247
void AssociateToIntegerLiteral(Literal literal, IntegerLiteral i_lit)
Definition integer.cc:365
IntegerVariable GetLiteralView(Literal lit) const
Definition integer.h:265
void FullyEncodeVariable(IntegerVariable var)
Definition integer.cc:80
bool VariableIsFullyEncoded(IntegerVariable var) const
Definition integer.cc:111
bool IsFixedOrHasAssociatedLiteral(IntegerLiteral i_lit) const
Definition integer.cc:524
const std::vector< ValueLiteralPair > & PartialDomainEncoding(IntegerVariable var) const
Definition integer.cc:152
LiteralIndex GetAssociatedEqualityLiteral(IntegerVariable var, IntegerValue value) const
Definition integer.cc:318
LiteralIndex SearchForLiteralAtOrBefore(IntegerLiteral i_lit, IntegerValue *bound) const
Definition integer.cc:547
void ReserveSpaceForNumVariables(int num_vars)
Definition integer.cc:74
Literal GetOrCreateAssociatedLiteral(IntegerLiteral i_lit)
Definition integer.cc:274
LiteralIndex SearchForLiteralAtOrAfter(IntegerLiteral i_lit, IntegerValue *bound) const
Definition integer.cc:575
void AssociateToIntegerEqualValue(Literal literal, IntegerVariable var, IntegerValue value)
Definition integer.cc:435
ABSL_MUST_USE_RESULT bool LiteralOrNegationHasView(Literal lit, IntegerVariable *view=nullptr, bool *view_is_direct=nullptr) const
Definition integer.cc:600
std::vector< ValueLiteralPair > PartialGreaterThanEncoding(IntegerVariable var) const
Definition integer.cc:620
LiteralIndex GetAssociatedLiteral(IntegerLiteral i_lit) const
Definition integer.cc:533
bool UpdateEncodingOnInitialDomainChange(IntegerVariable var, Domain domain)
Definition integer.cc:656
Literal GetOrCreateLiteralAssociatedToEquality(IntegerVariable var, IntegerValue value)
Definition integer.cc:328
ABSL_MUST_USE_RESULT bool ConditionalEnqueue(Literal lit, IntegerLiteral i_lit, std::vector< Literal > *literal_reason, std::vector< IntegerLiteral > *integer_reason)
Definition integer.cc:1361
absl::Span< const Literal > Reason(const Trail &trail, int trail_index, int64_t conflict_id) const final
Definition integer.cc:2328
IntegerValue LowerBound(IntegerVariable i) const
Definition integer.h:1537
absl::Span< const IntegerLiteral > IntegerReasonAsSpan(ReasonIndex index) const
Definition integer.cc:2028
void RemoveLevelZeroBounds(std::vector< IntegerLiteral > *reason) const
Definition integer.cc:1180
int FindTrailIndexOfVarBefore(IntegerVariable var, int threshold) const
Definition integer.cc:965
absl::Span< const Literal > LiteralReasonAsSpan(ReasonIndex index) const
Definition integer.cc:2073
ABSL_MUST_USE_RESULT bool SafeEnqueue(IntegerLiteral i_lit, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.cc:1336
void RelaxLinearReason(IntegerValue slack, absl::Span< const IntegerValue > coeffs, std::vector< IntegerLiteral > *reason) const
Definition integer.cc:1046
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1541
void AppendNewBoundsFrom(int base_index, std::vector< IntegerLiteral > *output) const
Definition integer.cc:2412
bool IntegerLiteralIsFalse(IntegerLiteral l) const
Definition integer.h:1640
bool ReportConflict(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.h:905
const IntegerReason & GetIntegerReason(GlobalTrailIndex index, std::optional< IntegerValue > needed_bound)
Definition integer.cc:2367
void AppendRelaxedLinearReason(IntegerValue slack, absl::Span< const IntegerValue > coeffs, absl::Span< const IntegerVariable > vars, std::vector< IntegerLiteral > *reason) const
Definition integer.cc:1068
bool IsTrueAtLevelZero(IntegerLiteral l) const
Definition integer.h:1644
void AppendNewBounds(std::vector< IntegerLiteral > *output) const
Definition integer.cc:2406
bool UpdateInitialDomain(IntegerVariable var, Domain domain)
Definition integer.cc:905
bool IsFixed(IntegerVariable i) const
Definition integer.h:1545
IntegerVariable NumIntegerVariables() const
Definition integer.h:573
IntegerVariable NextVariableToBranchOnInPropagationLoop() const
Definition integer.cc:1595
ABSL_MUST_USE_RESULT bool SafeEnqueueLiteral(Literal literal, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.cc:1504
ABSL_MUST_USE_RESULT bool RootLevelEnqueue(IntegerLiteral i_lit)
Definition integer.cc:1285
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition integer.h:1657
void MergeReasonInto(absl::Span< const IntegerLiteral > literals, std::vector< Literal > *output) const
Definition integer.cc:2102
ABSL_MUST_USE_RESULT bool EnqueueLiteral(Literal literal, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.cc:1498
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition integer.cc:895
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition integer.h:1650
void Untrail(const Trail &trail, int literal_trail_index) final
Definition integer.cc:787
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit)
Definition integer.h:745
IntegerVariable FirstUnassignedVariable() const
Definition integer.cc:1628
void ReserveSpaceForNumVariables(int num_vars)
Definition integer.cc:840
bool Propagate(Trail *trail) final
Definition integer.cc:723
IntegerVariable GetOrCreateConstantIntegerVariable(IntegerValue value)
Definition integer.cc:944
std::vector< Literal > ReasonFor(IntegerLiteral literal) const
Definition integer.cc:2096
ReasonIndex AppendReasonToInternalBuffers(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.cc:1646
LiteralIndex NegatedIndex() const
Definition sat_base.h:93
LiteralIndex Index() const
Definition sat_base.h:92
static constexpr SearchBranching FIXED_SEARCH
SatPropagator(const std::string &name)
Definition sat_base.h:786
bool AddClauseDuringSearch(absl::Span< const Literal > literals)
void AddLastPropagator(SatPropagator *propagator)
std::vector< Literal > * MutableConflict()
Definition sat_base.h:613
void EnqueueWithUnitReason(Literal true_literal)
Definition sat_base.h:456
std::vector< Literal > * GetEmptyVectorToStoreReason(int trail_index) const
Definition sat_base.h:560
const VariablesAssignment & Assignment() const
Definition sat_base.h:654
bool LiteralIsAssigned(Literal literal) const
Definition sat_base.h:209
bool LiteralIsFalse(Literal literal) const
Definition sat_base.h:203
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:206
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntType IntTypeAbs(IntType t)
const LiteralIndex kNoLiteralIndex(-1)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Definition integer.cc:52
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
IntegerVariable PositiveVariable(IntegerVariable i)
std::ostream & operator<<(std::ostream &os, const BoolVar &var)
Definition cp_model.cc:89
std::function< int64_t(const Model &)> UpperBound(IntegerVariable v)
Definition integer.h:1825
PositiveOnlyIndex GetPositiveOnlyIndex(IntegerVariable var)
bool VariableIsPositive(IntegerVariable i)
OR-Tools root namespace.
ClosedInterval::Iterator end(ClosedInterval interval)
const bool DEBUG_MODE
Definition radix_sort.h:266
int64_t CapProd(int64_t x, int64_t y)
ClosedInterval::Iterator begin(ClosedInterval interval)
trees with all degrees equal w the current value of w
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
absl::Span< const IntegerLiteral > integer_literals
Definition integer.h:442