Google OR-Tools v9.9
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
linear_propagation.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <stdint.h>
17
18#include <algorithm>
19#include <functional>
20#include <limits>
21#include <ostream>
22#include <string>
23#include <utility>
24#include <vector>
25
26#include "absl/container/flat_hash_map.h"
27#include "absl/container/inlined_vector.h"
28#include "absl/log/check.h"
29#include "absl/numeric/int128.h"
30#include "absl/strings/str_cat.h"
31#include "absl/types/span.h"
35#include "ortools/sat/integer.h"
36#include "ortools/sat/model.h"
40#include "ortools/util/bitset.h"
43
44namespace operations_research {
45namespace sat {
46
48 CHECK_GE(n, pos_.size());
49 pos_.resize(n, -1);
50 tmp_positions_.resize(n, 0);
51
52 // We need 1 more space since we can add at most n element.
53 if (n + 1 < queue_.size()) return;
54 const int old_size = queue_.size();
55 queue_.resize(n + 1, 0);
56 queue_.resize(queue_.capacity(), 0);
57
58 // We need to reconstruct the queue in this case.
59 if (left_ > right_) {
60 const int diff = queue_.size() - old_size;
61 for (int i = old_size - 1; i >= left_; --i) {
62 pos_[queue_[i]] = i + diff;
63 queue_[i + diff] = queue_[i];
64 }
65 left_ += diff;
66 }
67}
68
70 DCHECK(!empty());
71 const int id = queue_[left_];
72 pos_[id] = -1;
73 ++left_;
74 if (left_ == queue_.size()) left_ = 0;
75 return id;
76}
77
79 DCHECK_GE(id, 0);
80 DCHECK_LE(id, pos_.size());
81 DCHECK(!Contains(id));
82 pos_[id] = right_;
83 queue_[right_] = id;
84 ++right_;
85 if (right_ == queue_.size()) right_ = 0;
86}
87
88void CustomFifoQueue::Reorder(absl::Span<const int> order) {
89 if (order.size() <= 1) return;
90
91 const int capacity = queue_.size();
92 const int size = left_ < right_ ? right_ - left_ : left_ + capacity - right_;
93 if (order.size() > size / 8) {
94 return ReorderDense(order);
95 }
96
97 int index = 0;
98 for (const int id : order) {
99 const int p = pos_[id];
100 DCHECK_GE(p, 0);
101 tmp_positions_[index++] = p >= left_ ? p : p + capacity;
102 }
103 std::sort(&tmp_positions_[0], &tmp_positions_[index]);
104 DCHECK(std::unique(&tmp_positions_[0], &tmp_positions_[index]) ==
105 &tmp_positions_[index]);
106
107 index = 0;
108 for (const int id : order) {
109 int p = tmp_positions_[index++];
110 if (p >= capacity) p -= capacity;
111 pos_[id] = p;
112 queue_[p] = id;
113 }
114}
115
116void CustomFifoQueue::ReorderDense(absl::Span<const int> order) {
117 for (const int id : order) {
118 DCHECK_GE(pos_[id], 0);
119 queue_[pos_[id]] = -1;
120 }
121 int order_index = 0;
122 if (left_ <= right_) {
123 for (int i = left_; i < right_; ++i) {
124 if (queue_[i] == -1) {
125 queue_[i] = order[order_index++];
126 pos_[queue_[i]] = i;
127 }
128 }
129 } else {
130 const int size = queue_.size();
131 for (int i = left_; i < size; ++i) {
132 if (queue_[i] == -1) {
133 queue_[i] = order[order_index++];
134 pos_[queue_[i]] = i;
135 }
136 }
137 for (int i = 0; i < left_; ++i) {
138 if (queue_[i] == -1) {
139 queue_[i] = order[order_index++];
140 pos_[queue_[i]] = i;
141 }
142 }
143 }
144 DCHECK_EQ(order_index, order.size());
145}
146
147void CustomFifoQueue::SortByPos(absl::Span<int> elements) {
148 std::sort(elements.begin(), elements.end(),
149 [this](const int id1, const int id2) {
150 const int p1 = pos_[id1];
151 const int p2 = pos_[id2];
152 if (p1 >= left_) {
153 if (p2 >= left_) return p1 < p2;
154 return true;
155 } else {
156 // p1 < left_.
157 if (p2 < left_) return p1 < p2;
158 return false;
159 }
160 });
161}
162
163std::ostream& operator<<(std::ostream& os, const EnforcementStatus& e) {
164 switch (e) {
165 case EnforcementStatus::IS_FALSE:
166 os << "IS_FALSE";
167 break;
168 case EnforcementStatus::CANNOT_PROPAGATE:
169 os << "CANNOT_PROPAGATE";
170 break;
171 case EnforcementStatus::CAN_PROPAGATE:
172 os << "CAN_PROPAGATE";
173 break;
174 case EnforcementStatus::IS_ENFORCED:
175 os << "IS_ENFORCED";
176 break;
177 }
178 return os;
179}
180
181EnforcementPropagator::EnforcementPropagator(Model* model)
182 : SatPropagator("EnforcementPropagator"),
183 trail_(*model->GetOrCreate<Trail>()),
184 assignment_(trail_.Assignment()),
185 integer_trail_(model->GetOrCreate<IntegerTrail>()),
186 rev_int_repository_(model->GetOrCreate<RevIntRepository>()) {
187 // Note that this will be after the integer trail since rev_int_repository_
188 // depends on IntegerTrail.
189 model->GetOrCreate<SatSolver>()->AddPropagator(this);
190
191 // Sentinel - also start of next Register().
192 starts_.push_back(0);
193}
194
196 rev_int_repository_->SaveStateWithStamp(&rev_stack_size_, &rev_stamp_);
197 while (propagation_trail_index_ < trail_.Index()) {
198 const Literal literal = trail_[propagation_trail_index_++];
199 if (literal.Index() >= static_cast<int>(watcher_.size())) continue;
200
201 int new_size = 0;
202 auto& watch_list = watcher_[literal.Index()];
203 for (const EnforcementId id : watch_list) {
204 const LiteralIndex index = ProcessIdOnTrue(literal, id);
205 if (index == kNoLiteralIndex) {
206 // We keep the same watcher.
207 watch_list[new_size++] = id;
208 } else {
209 // Change the watcher.
210 CHECK_NE(index, literal.Index());
211 watcher_[index].push_back(id);
212 }
213 }
214 watch_list.resize(new_size);
215
216 // We also mark some constraint false.
217 for (const EnforcementId id : watcher_[literal.NegatedIndex()]) {
218 ChangeStatus(id, EnforcementStatus::IS_FALSE);
219 }
220 }
221 rev_stack_size_ = static_cast<int>(untrail_stack_.size());
222 return true;
223}
224
225void EnforcementPropagator::Untrail(const Trail& /*trail*/, int trail_index) {
226 // Simply revert the status change.
227 const int size = static_cast<int>(untrail_stack_.size());
228 for (int i = size - 1; i >= rev_stack_size_; --i) {
229 const auto [id, status] = untrail_stack_[i];
230 statuses_[id] = status;
231 if (callbacks_[id] != nullptr) callbacks_[id](status);
232 }
233 untrail_stack_.resize(rev_stack_size_);
234 propagation_trail_index_ = trail_index;
235}
236
237// Adds a new constraint to the class and returns the constraint id.
238//
239// Note that we accept empty enforcement list so that client code can be used
240// regardless of the presence of enforcement or not. A negative id means the
241// constraint is never enforced, and should be ignored.
243 absl::Span<const Literal> enforcement,
244 std::function<void(EnforcementStatus)> callback) {
245 int num_true = 0;
246 int num_false = 0;
247 bool is_always_false = false;
248 temp_literals_.clear();
249 const int level = trail_.CurrentDecisionLevel();
250 for (const Literal l : enforcement) {
251 // Make sure we always have enough room for the literal and its negation.
252 const int size = std::max(l.Index().value(), l.NegatedIndex().value()) + 1;
253 if (size > static_cast<int>(watcher_.size())) {
254 watcher_.resize(size);
255 }
256 if (assignment_.LiteralIsTrue(l)) {
257 if (level == 0 || trail_.Info(l.Variable()).level == 0) continue;
258 ++num_true;
259 } else if (assignment_.LiteralIsFalse(l)) {
260 if (level == 0 || trail_.Info(l.Variable()).level == 0) {
261 is_always_false = true;
262 break;
263 }
264 ++num_false;
265 }
266 temp_literals_.push_back(l);
267 }
268 gtl::STLSortAndRemoveDuplicates(&temp_literals_);
269
270 // Return special indices if never/always enforced.
271 if (is_always_false) {
273 return EnforcementId(-1);
274 }
275 if (temp_literals_.empty()) {
277 return EnforcementId(-1);
278 }
279
280 const EnforcementId id(static_cast<int>(callbacks_.size()));
281 callbacks_.push_back(std::move(callback));
282
283 CHECK(!temp_literals_.empty());
284 buffer_.insert(buffer_.end(), temp_literals_.begin(), temp_literals_.end());
285 starts_.push_back(buffer_.size()); // Sentinel/next-start.
286
287 // The default status at level zero.
288 statuses_.push_back(temp_literals_.size() == 1
291
292 if (temp_literals_.size() == 1) {
293 watcher_[temp_literals_[0].Index()].push_back(id);
294 } else {
295 // Make sure we watch correct literals.
296 const auto span = GetSpan(id);
297 int num_not_true = 0;
298 for (int i = 0; i < span.size(); ++i) {
299 if (assignment_.LiteralIsTrue(span[i])) continue;
300 std::swap(span[num_not_true], span[i]);
301 ++num_not_true;
302 if (num_not_true == 2) break;
303 }
304
305 // We need to watch one of the literals at highest level.
306 if (num_not_true == 1) {
307 int max_level = trail_.Info(span[1].Variable()).level;
308 for (int i = 2; i < span.size(); ++i) {
309 const int level = trail_.Info(span[i].Variable()).level;
310 if (level > max_level) {
311 max_level = level;
312 std::swap(span[1], span[i]);
313 }
314 }
315 }
316
317 watcher_[span[0].Index()].push_back(id);
318 watcher_[span[1].Index()].push_back(id);
319 }
320
321 // Change status, call callback and set up untrail if the status is different
322 // from EnforcementStatus::CANNOT_PROPAGATE.
323 if (num_false > 0) {
324 ChangeStatus(id, EnforcementStatus::IS_FALSE);
325 } else if (num_true == temp_literals_.size()) {
326 ChangeStatus(id, EnforcementStatus::IS_ENFORCED);
327 } else if (num_true + 1 == temp_literals_.size()) {
328 ChangeStatus(id, EnforcementStatus::CAN_PROPAGATE);
329 // Because this is the default status, we still need to call the callback.
330 if (temp_literals_.size() == 1) {
331 if (callbacks_[id] != nullptr) {
332 callbacks_[id](EnforcementStatus::CAN_PROPAGATE);
333 }
334 }
335 }
336 return id;
337}
338
339// Add the enforcement reason to the given vector.
341 EnforcementId id, std::vector<Literal>* reason) const {
342 for (const Literal l : GetSpan(id)) {
343 reason->push_back(l.Negated());
344 }
345}
346
347// Try to propagate when the enforced constraint is not satisfiable.
348// This is currently in O(enforcement_size);
350 EnforcementId id, absl::Span<const Literal> literal_reason,
351 absl::Span<const IntegerLiteral> integer_reason) {
352 temp_reason_.clear();
353 LiteralIndex unique_unassigned = kNoLiteralIndex;
354 for (const Literal l : GetSpan(id)) {
355 if (assignment_.LiteralIsFalse(l)) return true;
356 if (assignment_.LiteralIsTrue(l)) {
357 temp_reason_.push_back(l.Negated());
358 continue;
359 }
360 if (unique_unassigned != kNoLiteralIndex) return true;
361 unique_unassigned = l.Index();
362 }
363
364 temp_reason_.insert(temp_reason_.end(), literal_reason.begin(),
365 literal_reason.end());
366 if (unique_unassigned == kNoLiteralIndex) {
367 return integer_trail_->ReportConflict(temp_reason_, integer_reason);
368 }
369
370 integer_trail_->EnqueueLiteral(Literal(unique_unassigned).Negated(),
371 temp_reason_, integer_reason);
372 return true;
373}
374
375absl::Span<Literal> EnforcementPropagator::GetSpan(EnforcementId id) {
376 if (id < 0) return {};
377 DCHECK_LE(id + 1, starts_.size());
378 const int size = starts_[id + 1] - starts_[id];
379 DCHECK_NE(size, 0);
380 return absl::MakeSpan(&buffer_[starts_[id]], size);
381}
382
383absl::Span<const Literal> EnforcementPropagator::GetSpan(
384 EnforcementId id) const {
385 if (id < 0) return {};
386 DCHECK_LE(id + 1, starts_.size());
387 const int size = starts_[id + 1] - starts_[id];
388 DCHECK_NE(size, 0);
389 return absl::MakeSpan(&buffer_[starts_[id]], size);
390}
391
392LiteralIndex EnforcementPropagator::ProcessIdOnTrue(Literal watched,
393 EnforcementId id) {
394 const EnforcementStatus status = statuses_[id];
396
397 const auto span = GetSpan(id);
398 if (span.size() == 1) {
400 ChangeStatus(id, EnforcementStatus::IS_ENFORCED);
401 return kNoLiteralIndex;
402 }
403
404 const int watched_pos = (span[0] == watched) ? 0 : 1;
405 CHECK_EQ(span[watched_pos], watched);
406 if (assignment_.LiteralIsFalse(span[watched_pos ^ 1])) {
407 ChangeStatus(id, EnforcementStatus::IS_FALSE);
408 return kNoLiteralIndex;
409 }
410
411 for (int i = 2; i < span.size(); ++i) {
412 const Literal l = span[i];
413 if (assignment_.LiteralIsFalse(l)) {
414 ChangeStatus(id, EnforcementStatus::IS_FALSE);
415 return kNoLiteralIndex;
416 }
417 if (!assignment_.LiteralIsAssigned(l)) {
418 // Replace the watched literal. Note that if the other watched literal is
419 // true, it should be processed afterwards. We do not change the status
420 std::swap(span[watched_pos], span[i]);
421 return span[watched_pos].Index();
422 }
423 }
424
425 // All literal with index > 1 are true. Two case.
426 if (assignment_.LiteralIsTrue(span[watched_pos ^ 1])) {
427 // All literals are true.
428 ChangeStatus(id, EnforcementStatus::IS_ENFORCED);
429 return kNoLiteralIndex;
430 } else {
431 // The other watched literal is the last unassigned
433 ChangeStatus(id, EnforcementStatus::CAN_PROPAGATE);
434 return kNoLiteralIndex;
435 }
436}
437
438void EnforcementPropagator::ChangeStatus(EnforcementId id,
439 EnforcementStatus new_status) {
440 const EnforcementStatus old_status = statuses_[id];
441 if (old_status == new_status) return;
442 if (trail_.CurrentDecisionLevel() != 0) {
443 untrail_stack_.push_back({id, old_status});
444 }
445 statuses_[id] = new_status;
446 if (callbacks_[id] != nullptr) callbacks_[id](new_status);
447}
448
450 if (id < 0) return EnforcementStatus::IS_ENFORCED;
451
452 int num_true = 0;
453 for (const Literal l : GetSpan(id)) {
454 if (assignment_.LiteralIsFalse(l)) {
456 }
457 if (assignment_.LiteralIsTrue(l)) ++num_true;
458 }
459 const int size = GetSpan(id).size();
460 if (num_true == size) return EnforcementStatus::IS_ENFORCED;
461 if (num_true + 1 == size) return EnforcementStatus::CAN_PROPAGATE;
463}
464
466 : trail_(model->GetOrCreate<Trail>()),
467 integer_trail_(model->GetOrCreate<IntegerTrail>()),
468 enforcement_propagator_(model->GetOrCreate<EnforcementPropagator>()),
469 watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
470 time_limit_(model->GetOrCreate<TimeLimit>()),
471 rev_int_repository_(model->GetOrCreate<RevIntRepository>()),
472 rev_integer_value_repository_(
473 model->GetOrCreate<RevIntegerValueRepository>()),
474 shared_stats_(model->GetOrCreate<SharedStatistics>()),
475 watcher_id_(watcher_->Register(this)) {
476 // Note that we need this class always in sync.
477 integer_trail_->RegisterWatcher(&modified_vars_);
478 integer_trail_->RegisterReversibleClass(this);
479
480 // TODO(user): When we start to push too much (Cycle?) we should see what
481 // other propagator says before repropagating this one, system for call
482 // later?
483 watcher_->SetPropagatorPriority(watcher_id_, 0);
484}
485
487 if (!VLOG_IS_ON(1)) return;
488 if (shared_stats_ == nullptr) return;
489 std::vector<std::pair<std::string, int64_t>> stats;
490 stats.push_back({"linear_propag/num_pushes", num_pushes_});
491 stats.push_back(
492 {"linear_propag/num_enforcement_pushes", num_enforcement_pushes_});
493 stats.push_back({"linear_propag/num_simple_cycles", num_simple_cycles_});
494 stats.push_back({"linear_propag/num_complex_cycles", num_complex_cycles_});
495 stats.push_back({"linear_propag/num_scanned", num_scanned_});
496 stats.push_back({"linear_propag/num_extra_scan", num_extra_scans_});
497 stats.push_back({"linear_propag/num_explored_in_disassemble",
498 num_explored_in_disassemble_});
499 stats.push_back({"linear_propag/num_bool_aborts", num_bool_aborts_});
500 stats.push_back({"linear_propag/num_ignored", num_ignored_});
501 stats.push_back({"linear_propag/num_reordered", num_reordered_});
502 shared_stats_->AddStats(stats);
503}
504
506 if (level < previous_level_) {
507 // If the solver backtracked at any point, we invalidate all our queue
508 // and propagated_by information.
509 ClearPropagatedBy();
510 while (!propagation_queue_.empty()) {
511 in_queue_[propagation_queue_.Pop()] = false;
512 }
513 for (int i = rev_unenforced_size_; i < unenforced_constraints_.size();
514 ++i) {
515 in_queue_[unenforced_constraints_[i]] = false;
516 }
517 unenforced_constraints_.resize(rev_unenforced_size_);
518 } else if (level > previous_level_) {
519 rev_unenforced_size_ = unenforced_constraints_.size();
520 rev_int_repository_->SaveState(&rev_unenforced_size_);
521 }
522 previous_level_ = level;
523
524 // Tricky: if we aborted the current propagation because we pushed a Boolean,
525 // by default, the GenericLiteralWatcher will only call Propagate() again if
526 // one of the watched variable changed. With this, it is guaranteed to call
527 // it again if it wasn't in the queue already.
528 if (!propagation_queue_.empty()) {
529 watcher_->CallOnNextPropagate(watcher_id_);
530 }
531}
532
534 id_scanned_at_least_once_.ClearAndResize(in_queue_.size());
535
536 // Initial addition.
537 // We will clear modified_vars_ on exit.
538 for (const IntegerVariable var : modified_vars_.PositionsSetAtLeastOnce()) {
539 if (var >= var_to_constraint_ids_.size()) continue;
540 SetPropagatedBy(var, -1);
541 AddWatchedToQueue(var);
542 }
543
544 // We abort this propagator as soon as a Boolean is propagated, so that we
545 // always finish the Boolean propagation first. This can happen when we push a
546 // bound that has associated Booleans or push enforcement to false. The idea
547 // is to resume from our current state when we are called again. Note however
548 // that we have to clear the propagated_by_ info (as done above) has other
549 // propagator might have pushed the same variable further.
550 //
551 // Empty FIFO queue.
552 const int saved_index = trail_->Index();
553 while (!propagation_queue_.empty()) {
554 const int id = propagation_queue_.Pop();
555 in_queue_[id] = false;
556 if (!PropagateOneConstraint(id)) {
557 modified_vars_.ClearAndResize(integer_trail_->NumIntegerVariables());
558 return false;
559 }
560
561 if (trail_->Index() > saved_index) {
562 ++num_bool_aborts_;
563 break;
564 }
565 }
566
567 // Clean-up modified_vars_ to do as little as possible on the next call.
568 modified_vars_.ClearAndResize(integer_trail_->NumIntegerVariables());
569 return true;
570}
571
572// Adds a new constraint to the propagator.
574 absl::Span<const Literal> enforcement_literals,
575 absl::Span<const IntegerVariable> vars,
576 absl::Span<const IntegerValue> coeffs, IntegerValue upper_bound) {
577 if (vars.empty()) return true;
578 if (trail_->CurrentDecisionLevel() == 0) {
579 for (const Literal l : enforcement_literals) {
580 if (trail_->Assignment().LiteralIsFalse(l)) return true;
581 }
582 }
583
584 // Make sure max_variations_ is of correct size.
585 // Note that we also have a hard limit of 1 << 29 on the size.
586 CHECK_LT(vars.size(), 1 << 29);
587 if (vars.size() > max_variations_.size()) {
588 max_variations_.resize(vars.size(), 0);
589 buffer_of_ones_.resize(vars.size(), IntegerValue(1));
590 }
591
592 // Initialize constraint data.
593 CHECK_EQ(vars.size(), coeffs.size());
594 const int id = infos_.size();
595 {
596 ConstraintInfo info;
597 info.all_coeffs_are_one = false;
598 info.start = variables_buffer_.size();
599 info.initial_size = vars.size();
600 info.rev_rhs = upper_bound;
601 info.rev_size = vars.size();
602 infos_.push_back(std::move(info));
603 }
604
605 id_to_propagation_count_.push_back(0);
606 variables_buffer_.insert(variables_buffer_.end(), vars.begin(), vars.end());
607 coeffs_buffer_.insert(coeffs_buffer_.end(), coeffs.begin(), coeffs.end());
608 CanonicalizeConstraint(id);
609
610 bool all_at_one = true;
611 for (const IntegerValue coeff : GetCoeffs(infos_.back())) {
612 if (coeff != 1) {
613 all_at_one = false;
614 break;
615 }
616 }
617 if (all_at_one) {
618 // TODO(user): we still waste the space in coeffs_buffer_ so that the
619 // start are aligned with the variables_buffer_.
620 infos_.back().all_coeffs_are_one = true;
621 }
622
623 // Initialize watchers.
624 // Initialy we want everything to be propagated at least once.
625 in_queue_.push_back(false);
626 propagation_queue_.IncreaseSize(in_queue_.size());
627
628 if (!enforcement_literals.empty()) {
629 infos_.back().enf_status =
630 static_cast<int>(EnforcementStatus::CANNOT_PROPAGATE);
631 infos_.back().enf_id = enforcement_propagator_->Register(
632 enforcement_literals, [this, id](EnforcementStatus status) {
633 infos_[id].enf_status = static_cast<int>(status);
634 // TODO(user): With some care, when we cannot propagate or the
635 // constraint is not enforced, we could leave in_queue_[] at true but
636 // not put the constraint in the queue.
639 AddToQueueIfNeeded(id);
640 watcher_->CallOnNextPropagate(watcher_id_);
641 }
642 });
643 } else {
644 AddToQueueIfNeeded(id);
645 infos_.back().enf_id = -1;
646 infos_.back().enf_status = static_cast<int>(EnforcementStatus::IS_ENFORCED);
647 }
648
649 for (const IntegerVariable var : GetVariables(infos_[id])) {
650 // Transposed graph to know which constraint to wake up.
651 if (var >= var_to_constraint_ids_.size()) {
652 // We need both the var entry and its negation to be allocated.
653 const int size = std::max(var, NegationOf(var)).value() + 1;
654 var_to_constraint_ids_.resize(size);
655 propagated_by_.resize(size, -1);
656 propagated_by_was_set_.Resize(IntegerVariable(size));
657 is_watched_.resize(size, false);
658 }
659
660 // TODO(user): Shall we decide on some ordering here? maybe big coeff first
661 // so that we get the largest change in slack? the idea being to propagate
662 // large change first in case of cycles.
663 var_to_constraint_ids_[var].push_back(id);
664
665 // We need to be registered to the watcher so Propagate() is called at
666 // the proper priority. But then we rely on modified_vars_.
667 if (!is_watched_[var]) {
668 is_watched_[var] = true;
669 watcher_->WatchLowerBound(var, watcher_id_);
670 }
671 }
672
673 // Propagate this new constraint.
674 return PropagateOneConstraint(id);
675}
676
677absl::Span<IntegerValue> LinearPropagator::GetCoeffs(
678 const ConstraintInfo& info) {
679 if (info.all_coeffs_are_one) {
680 return absl::MakeSpan(&buffer_of_ones_[0], info.initial_size);
681 }
682 return absl::MakeSpan(&coeffs_buffer_[info.start], info.initial_size);
683}
684
685absl::Span<IntegerVariable> LinearPropagator::GetVariables(
686 const ConstraintInfo& info) {
687 return absl::MakeSpan(&variables_buffer_[info.start], info.initial_size);
688}
689
690void LinearPropagator::CanonicalizeConstraint(int id) {
691 const ConstraintInfo& info = infos_[id];
692 auto coeffs = GetCoeffs(info);
693 auto vars = GetVariables(info);
694 for (int i = 0; i < vars.size(); ++i) {
695 if (coeffs[i] < 0) {
696 coeffs[i] = -coeffs[i];
697 vars[i] = NegationOf(vars[i]);
698 }
699 }
700}
701
702// TODO(user): template everything for the case info.all_coeffs_are_one ?
703bool LinearPropagator::PropagateOneConstraint(int id) {
704 // This is here for development purpose, it is a bit too slow to check by
705 // default though, even VLOG_IS_ON(1) so we disable it.
706 if (/* DISABLES CODE */ (false)) {
707 ++num_scanned_;
708 if (id_scanned_at_least_once_[id]) {
709 ++num_extra_scans_;
710 } else {
711 id_scanned_at_least_once_.Set(id);
712 }
713 }
714
715 // Skip constraint not enforced or that cannot propagate if false.
716 ConstraintInfo& info = infos_[id];
717 const EnforcementStatus enf_status = EnforcementStatus(info.enf_status);
718 DCHECK_EQ(enf_status, enforcement_propagator_->DebugStatus(info.enf_id));
719 if (enf_status == EnforcementStatus::IS_FALSE ||
721 DCHECK(!in_queue_[id]);
722 if (enf_status == EnforcementStatus::IS_FALSE) {
723 // We mark this constraint as in the queue but will never inspect it
724 // again until we backtrack over this time.
725 in_queue_[id] = true;
726 unenforced_constraints_.push_back(id);
727 }
728 ++num_ignored_;
729 return true;
730 }
731
732 // Compute the slack and max_variations_ of each variables.
733 // We also filter out fixed variables in a reversible way.
734 IntegerValue implied_lb(0);
735 auto vars = GetVariables(info);
736 auto coeffs = GetCoeffs(info);
737 IntegerValue max_variation(0);
738 bool first_change = true;
739 time_limit_->AdvanceDeterministicTime(static_cast<double>(info.rev_size) *
740 1e-9);
741 for (int i = 0; i < info.rev_size;) {
742 const IntegerVariable var = vars[i];
743 const IntegerValue coeff = coeffs[i];
744 const IntegerValue lb = integer_trail_->LowerBound(var);
745 const IntegerValue ub = integer_trail_->UpperBound(var);
746 if (lb == ub) {
747 if (first_change) {
748 // Note that we can save at most one state per fixed var. Also at
749 // level zero we don't save anything.
750 rev_int_repository_->SaveState(&info.rev_size);
751 rev_integer_value_repository_->SaveState(&info.rev_rhs);
752 first_change = false;
753 }
754 info.rev_size--;
755 std::swap(vars[i], vars[info.rev_size]);
756 std::swap(coeffs[i], coeffs[info.rev_size]);
757 info.rev_rhs -= coeff * lb;
758 } else {
759 implied_lb += coeff * lb;
760 max_variations_[i] = (ub - lb) * coeff;
761 max_variation = std::max(max_variation, max_variations_[i]);
762 ++i;
763 }
764 }
765 const IntegerValue slack = info.rev_rhs - implied_lb;
766
767 // Negative slack means the constraint is false.
768 if (max_variation <= slack) return true;
769 if (slack < 0) {
770 // Fill integer reason.
771 integer_reason_.clear();
772 reason_coeffs_.clear();
773 for (int i = 0; i < info.initial_size; ++i) {
774 const IntegerVariable var = vars[i];
775 if (!integer_trail_->VariableLowerBoundIsFromLevelZero(var)) {
776 integer_reason_.push_back(integer_trail_->LowerBoundAsLiteral(var));
777 reason_coeffs_.push_back(coeffs[i]);
778 }
779 }
780
781 // Relax it.
782 integer_trail_->RelaxLinearReason(-slack - 1, reason_coeffs_,
783 &integer_reason_);
784 ++num_enforcement_pushes_;
785 return enforcement_propagator_->PropagateWhenFalse(info.enf_id, {},
786 integer_reason_);
787 }
788
789 // We can only propagate more if all the enforcement literals are true.
790 if (info.enf_status != static_cast<int>(EnforcementStatus::IS_ENFORCED)) {
791 return true;
792 }
793
794 // The lower bound of all the variables except one can be used to update the
795 // upper bound of the last one.
796 int num_pushed = 0;
797 for (int i = 0; i < info.rev_size; ++i) {
798 if (max_variations_[i] <= slack) continue;
799
800 // TODO(user): If the new ub fall into an hole of the variable, we can
801 // actually relax the reason more by computing a better slack.
802 ++num_pushes_;
803 const IntegerVariable var = vars[i];
804 const IntegerValue coeff = coeffs[i];
805 const IntegerValue div = slack / coeff;
806 const IntegerValue new_ub = integer_trail_->LowerBound(var) + div;
807 const IntegerValue propagation_slack = (div + 1) * coeff - slack - 1;
808 if (!integer_trail_->Enqueue(
810 /*lazy_reason=*/[this, info, propagation_slack](
811 IntegerLiteral i_lit, int trail_index,
812 std::vector<Literal>* literal_reason,
813 std::vector<int>* trail_indices_reason) {
814 literal_reason->clear();
815 trail_indices_reason->clear();
816 enforcement_propagator_->AddEnforcementReason(info.enf_id,
817 literal_reason);
818 reason_coeffs_.clear();
819
820 auto coeffs = GetCoeffs(info);
821 auto vars = GetVariables(info);
822 for (int i = 0; i < info.initial_size; ++i) {
823 const IntegerVariable var = vars[i];
824 if (PositiveVariable(var) == PositiveVariable(i_lit.var)) {
825 continue;
826 }
827 const int index =
828 integer_trail_->FindTrailIndexOfVarBefore(var, trail_index);
829 if (index >= 0) {
830 trail_indices_reason->push_back(index);
831 if (propagation_slack > 0) {
832 reason_coeffs_.push_back(coeffs[i]);
833 }
834 }
835 }
836 if (propagation_slack > 0) {
837 integer_trail_->RelaxLinearReason(
838 propagation_slack, reason_coeffs_, trail_indices_reason);
839 }
840 })) {
841 return false;
842 }
843
844 // Add to the queue all touched constraint.
845 const IntegerValue actual_ub = integer_trail_->UpperBound(var);
846 const IntegerVariable next_var = NegationOf(var);
847 if (actual_ub < new_ub) {
848 // Was pushed further due to hole. We clear it.
849 SetPropagatedBy(next_var, -1);
850 AddWatchedToQueue(next_var);
851 } else if (actual_ub == new_ub) {
852 SetPropagatedBy(next_var, id);
853 AddWatchedToQueue(next_var);
854
855 // We reorder them first.
856 std::swap(vars[i], vars[num_pushed]);
857 std::swap(coeffs[i], coeffs[num_pushed]);
858 ++num_pushed;
859 }
860
861 // Explore the subtree and detect cycles greedily.
862 // Also postpone some propagation.
863 if (num_pushed > 0) {
864 if (!DisassembleSubtree(id, num_pushed)) {
865 return false;
866 }
867 }
868 }
869
870 return true;
871}
872
873std::string LinearPropagator::ConstraintDebugString(int id) {
874 std::string result;
875 const ConstraintInfo& info = infos_[id];
876 auto coeffs = GetCoeffs(info);
877 auto vars = GetVariables(info);
878 IntegerValue implied_lb(0);
879 IntegerValue rhs_correction(0);
880 for (int i = 0; i < info.initial_size; ++i) {
881 const IntegerValue term = coeffs[i] * integer_trail_->LowerBound(vars[i]);
882 if (i >= info.rev_size) {
883 rhs_correction += term;
884 }
885 implied_lb += term;
886 absl::StrAppend(&result, " +", coeffs[i].value(), "*X", vars[i].value());
887 }
888 const IntegerValue original_rhs = info.rev_rhs + rhs_correction;
889 absl::StrAppend(&result, " <= ", original_rhs.value(),
890 " slack=", original_rhs.value() - implied_lb.value());
891 absl::StrAppend(&result, " enf=", info.enf_status);
892 return result;
893}
894
895bool LinearPropagator::ReportConflictingCycle() {
896 // Often, all coefficients of the variable involved in the cycle are the same
897 // and if we sum all constraint, we get an infeasible one. If this is the
898 // case, we simplify the reason.
899 //
900 // TODO(user): We could relax if the coefficient of the sum do not overflow.
901 // TODO(user): Sum constraints with eventual factor in more cases.
902 {
903 literal_reason_.clear();
904 integer_reason_.clear();
905 absl::int128 rhs_sum = 0;
906 absl::flat_hash_map<IntegerVariable, absl::int128> map_sum;
907 for (const auto [id, next_var] : disassemble_branch_) {
908 const ConstraintInfo& info = infos_[id];
909 enforcement_propagator_->AddEnforcementReason(info.enf_id,
910 &literal_reason_);
911 auto coeffs = GetCoeffs(info);
912 auto vars = GetVariables(info);
913 IntegerValue rhs_correction(0);
914 for (int i = 0; i < info.initial_size; ++i) {
915 if (i >= info.rev_size) {
916 rhs_correction += coeffs[i] * integer_trail_->LowerBound(vars[i]);
917 }
918 if (VariableIsPositive(vars[i])) {
919 map_sum[vars[i]] += coeffs[i].value();
920 } else {
921 map_sum[PositiveVariable(vars[i])] -= coeffs[i].value();
922 }
923 }
924 rhs_sum += (info.rev_rhs + rhs_correction).value();
925 }
926
927 // We shouldn't have overflow since each component do not overflow an
928 // int64_t and we sum a small amount of them.
929 absl::int128 implied_lb = 0;
930 for (const auto [var, coeff] : map_sum) {
931 if (coeff > 0) {
932 if (!integer_trail_->VariableLowerBoundIsFromLevelZero(var)) {
933 integer_reason_.push_back(integer_trail_->LowerBoundAsLiteral(var));
934 }
935 implied_lb +=
936 coeff * absl::int128{integer_trail_->LowerBound(var).value()};
937 } else if (coeff < 0) {
938 if (!integer_trail_->VariableLowerBoundIsFromLevelZero(
939 NegationOf(var))) {
940 integer_reason_.push_back(integer_trail_->UpperBoundAsLiteral(var));
941 }
942 implied_lb +=
943 coeff * absl::int128{integer_trail_->UpperBound(var).value()};
944 }
945 }
946 if (implied_lb > rhs_sum) {
947 // We sort for determinism.
948 std::sort(integer_reason_.begin(), integer_reason_.end(),
949 [](const IntegerLiteral& a, const IntegerLiteral& b) {
950 return a.var < b.var;
951 });
952
953 // Relax the linear reason if everything fit on an int64_t.
954 const absl::int128 limit{std::numeric_limits<int64_t>::max()};
955 const absl::int128 slack = implied_lb - rhs_sum;
956 if (slack > 1) {
957 reason_coeffs_.clear();
958 bool abort = false;
959 for (const IntegerLiteral i_lit : integer_reason_) {
960 absl::int128 c = map_sum.at(PositiveVariable(i_lit.var));
961 if (c < 0) c = -c; // No std::abs() for int128.
962 if (c >= limit) {
963 abort = true;
964 break;
965 }
966 reason_coeffs_.push_back(static_cast<int64_t>(c));
967 }
968 if (!abort) {
969 const IntegerValue slack64(
970 static_cast<int64_t>(std::min(limit, slack)));
971 integer_trail_->RelaxLinearReason(slack64 - 1, reason_coeffs_,
972 &integer_reason_);
973 }
974 }
975
976 ++num_simple_cycles_;
977 VLOG(2) << "Simplified " << integer_reason_.size() << " slack "
978 << implied_lb - rhs_sum;
979 return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
980 }
981 }
982
983 // For the complex reason, we just use the bound of every variable.
984 // We do some basic simplification for the variable involved in the cycle.
985 //
986 // TODO(user): Can we simplify more?
987 VLOG(2) << "Cycle";
988 literal_reason_.clear();
989 integer_reason_.clear();
990 IntegerVariable previous_var = kNoIntegerVariable;
991 for (const auto [id, next_var] : disassemble_branch_) {
992 const ConstraintInfo& info = infos_[id];
993 enforcement_propagator_->AddEnforcementReason(info.enf_id,
994 &literal_reason_);
995 for (const IntegerVariable var : GetVariables(infos_[id])) {
996 // The lower bound of this variable is implied by the previous constraint,
997 // so we do not need to include it.
998 if (var == previous_var) continue;
999
1000 // We do not need the lower bound of var to propagate its upper bound.
1001 if (var == NegationOf(next_var)) continue;
1002
1003 if (!integer_trail_->VariableLowerBoundIsFromLevelZero(var)) {
1004 integer_reason_.push_back(integer_trail_->LowerBoundAsLiteral(var));
1005 }
1006 }
1007 previous_var = next_var;
1008
1009 VLOG(2) << next_var << " [" << integer_trail_->LowerBound(next_var) << ","
1010 << integer_trail_->UpperBound(next_var)
1011 << "] : " << ConstraintDebugString(id);
1012 }
1013 ++num_complex_cycles_;
1014 return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
1015}
1016
1017// Note that if there is a loop in the propagated_by_ graph, it must be
1018// from root_id -> root_var, because each time we add an edge, we do
1019// disassemble.
1020//
1021// TODO(user): If one of the var coeff is > previous slack we push an id again,
1022// we can stop early with a conflict by propagating the ids in sequence.
1023bool LinearPropagator::DisassembleSubtree(int root_id, int num_pushed) {
1024 disassemble_to_reorder_.ClearAndResize(in_queue_.size());
1025 disassemble_reverse_topo_order_.clear();
1026
1027 // The variable was just pushed, we explore the set of variable that will
1028 // be pushed further due to this push. Basically, if a constraint propagated
1029 // before and its slack will reduce due to the push, then any previously
1030 // propagated variable with a coefficient NOT GREATER than the one of the
1031 // variable reducing the slack will be pushed further.
1032 disassemble_queue_.clear();
1033 disassemble_branch_.clear();
1034 {
1035 const ConstraintInfo& info = infos_[root_id];
1036 auto vars = GetVariables(info);
1037 for (int i = 0; i < num_pushed; ++i) {
1038 disassemble_queue_.push_back({root_id, NegationOf(vars[i])});
1039 }
1040 }
1041
1042 // Note that all var should be unique since there is only one propagated_by_
1043 // for each one. And each time we explore an id, we disassemble the tree.
1044 while (!disassemble_queue_.empty()) {
1045 const auto [prev_id, var] = disassemble_queue_.back();
1046 if (!disassemble_branch_.empty() &&
1047 disassemble_branch_.back().first == prev_id &&
1048 disassemble_branch_.back().second == var) {
1049 disassemble_branch_.pop_back();
1050 disassemble_reverse_topo_order_.push_back(prev_id);
1051 disassemble_queue_.pop_back();
1052 continue;
1053 }
1054
1055 disassemble_branch_.push_back({prev_id, var});
1056 time_limit_->AdvanceDeterministicTime(
1057 static_cast<double>(var_to_constraint_ids_[var].size()) * 1e-9);
1058 for (const int id : var_to_constraint_ids_[var]) {
1059 if (prev_id == root_id) {
1060 // Root id was just propagated, so there is no need to reorder what
1061 // it pushes.
1062 DCHECK_NE(id, root_id);
1063 if (disassemble_to_reorder_[id]) continue;
1064 disassemble_to_reorder_.Set(id);
1065 } else if (id == root_id) {
1066 // TODO(user): Check previous slack vs var coeff?
1067 // TODO(user): Make sure there are none or detect cycle not going back
1068 // to the root.
1069 CHECK(!disassemble_branch_.empty());
1070
1071 // This is a corner case in which there is actually no cycle.
1072 const IntegerVariable root_var = disassemble_branch_[0].second;
1073 CHECK_EQ(disassemble_branch_[0].first, root_id);
1074 CHECK_NE(var, root_var);
1075 if (var == NegationOf(root_var)) continue;
1076
1077 // Tricky: We have a cycle here only if coeff of var >= root_coeff.
1078 // If there is no cycle, we will just finish the branch here.
1079 //
1080 // TODO(user): Can we be more precise? if one coeff is big, the
1081 // variation in slack might be big enough to push a variable twice and
1082 // thus push a lower coeff.
1083 const ConstraintInfo& info = infos_[id];
1084 auto coeffs = GetCoeffs(info);
1085 auto vars = GetVariables(info);
1086 IntegerValue root_coeff(0);
1087 IntegerValue var_coeff(0);
1088 for (int i = 0; i < info.initial_size; ++i) {
1089 if (vars[i] == var) var_coeff = coeffs[i];
1090 if (vars[i] == NegationOf(root_var)) root_coeff = coeffs[i];
1091 }
1092 CHECK_NE(root_coeff, 0);
1093 CHECK_NE(var_coeff, 0);
1094 if (var_coeff >= root_coeff) {
1095 return ReportConflictingCycle();
1096 } else {
1097 // We don't want to continue the search from root_id.
1098 continue;
1099 }
1100 }
1101
1102 if (id_to_propagation_count_[id] == 0) continue; // Didn't push.
1103 disassemble_to_reorder_.Set(id);
1104
1105 // The constraint pushed some variable. Identify which ones will be pushed
1106 // further. Disassemble the whole info since we are about to propagate
1107 // this constraint again. Any pushed variable must be before the rev_size.
1108 const ConstraintInfo& info = infos_[id];
1109 auto coeffs = GetCoeffs(info);
1110 auto vars = GetVariables(info);
1111 IntegerValue var_coeff(0);
1112 disassemble_candidates_.clear();
1113 ++num_explored_in_disassemble_;
1114 time_limit_->AdvanceDeterministicTime(static_cast<double>(info.rev_size) *
1115 1e-9);
1116 for (int i = 0; i < info.rev_size; ++i) {
1117 if (vars[i] == var) {
1118 var_coeff = coeffs[i];
1119 continue;
1120 }
1121 const IntegerVariable next_var = NegationOf(vars[i]);
1122 if (propagated_by_[next_var] == id) {
1123 disassemble_candidates_.push_back({next_var, coeffs[i]});
1124
1125 // We will propagate var again later, so clear all this for now.
1126 propagated_by_[next_var] = -1;
1127 id_to_propagation_count_[id]--;
1128 }
1129 }
1130 for (const auto [next_var, coeff] : disassemble_candidates_) {
1131 if (coeff <= var_coeff) {
1132 // We are guaranteed to push next_var only if var_coeff will move
1133 // the slack enough.
1134 //
1135 // TODO(user): Keep current delta in term of the DFS so we detect
1136 // cycle and depedendences in more cases.
1137 disassemble_queue_.push_back({id, next_var});
1138 }
1139 }
1140 }
1141 }
1142
1143 CHECK(!disassemble_to_reorder_[root_id]);
1144 tmp_to_reorder_.clear();
1145 std::reverse(disassemble_reverse_topo_order_.begin(),
1146 disassemble_reverse_topo_order_.end()); // !! not unique
1147 for (const int id : disassemble_reverse_topo_order_) {
1148 if (!disassemble_to_reorder_[id]) continue;
1149 disassemble_to_reorder_.Clear(id);
1150 AddToQueueIfNeeded(id);
1151 if (!propagation_queue_.Contains(id)) continue;
1152 tmp_to_reorder_.push_back(id);
1153 }
1154
1155 // TODO(user): Reordering can be sloe since require sort and can touch many
1156 // entries. Investigate alternatives. We could probably optimize this a bit
1157 // more.
1158 if (tmp_to_reorder_.empty()) return true;
1159 const int important_size = static_cast<int>(tmp_to_reorder_.size());
1160
1161 for (const int id : disassemble_to_reorder_.PositionsSetAtLeastOnce()) {
1162 if (!disassemble_to_reorder_[id]) continue;
1163 disassemble_to_reorder_.Clear(id);
1164 if (!propagation_queue_.Contains(id)) continue;
1165 tmp_to_reorder_.push_back(id);
1166 }
1167 disassemble_to_reorder_.NotifyAllClear();
1168
1169 // We try to keep the same order as before for the elements not in the
1170 // topological order.
1171 if (important_size < tmp_to_reorder_.size()) {
1172 propagation_queue_.SortByPos(
1173 absl::MakeSpan(&tmp_to_reorder_[important_size],
1174 tmp_to_reorder_.size() - important_size));
1175 }
1176
1177 num_reordered_ += tmp_to_reorder_.size();
1178 propagation_queue_.Reorder(tmp_to_reorder_);
1179 return true;
1180}
1181
1182void LinearPropagator::AddToQueueIfNeeded(int id) {
1183 DCHECK_LT(id, in_queue_.size());
1184 DCHECK_LT(id, infos_.size());
1185
1186 if (in_queue_[id]) return;
1187 in_queue_[id] = true;
1188 propagation_queue_.Push(id);
1189}
1190
1191void LinearPropagator::AddWatchedToQueue(IntegerVariable var) {
1192 if (var >= static_cast<int>(var_to_constraint_ids_.size())) return;
1193 time_limit_->AdvanceDeterministicTime(
1194 static_cast<double>(var_to_constraint_ids_[var].size()) * 1e-9);
1195 for (const int id : var_to_constraint_ids_[var]) {
1196 AddToQueueIfNeeded(id);
1197 }
1198}
1199
1200void LinearPropagator::SetPropagatedBy(IntegerVariable var, int id) {
1201 int& ref_id = propagated_by_[var];
1202 if (ref_id == id) return;
1203
1204 propagated_by_was_set_.Set(var);
1205
1206 DCHECK_GE(var, 0);
1207 DCHECK_LT(var, propagated_by_.size());
1208 if (ref_id != -1) {
1209 DCHECK_GE(ref_id, 0);
1210 DCHECK_LT(ref_id, id_to_propagation_count_.size());
1211 id_to_propagation_count_[ref_id]--;
1212 }
1213 ref_id = id;
1214 if (id != -1) id_to_propagation_count_[id]++;
1215}
1216
1217void LinearPropagator::ClearPropagatedBy() {
1218 // To be sparse, we use the fact that each node with a parent must be in
1219 // modified_vars_.
1220 for (const IntegerVariable var :
1221 propagated_by_was_set_.PositionsSetAtLeastOnce()) {
1222 int& id = propagated_by_[var];
1223 if (id != -1) --id_to_propagation_count_[id];
1224 propagated_by_[var] = -1;
1225 }
1226 propagated_by_was_set_.ClearAndResize(propagated_by_was_set_.size());
1227 DCHECK(std::all_of(propagated_by_.begin(), propagated_by_.end(),
1228 [](int id) { return id == -1; }));
1229 DCHECK(std::all_of(id_to_propagation_count_.begin(),
1230 id_to_propagation_count_.end(),
1231 [](int count) { return count == 0; }));
1232}
1233
1234} // namespace sat
1235} // namespace operations_research
void push_back(const value_type &x)
void resize(size_type new_size)
size_type size() const
void SaveStateWithStamp(T *object, int64_t *stamp)
Definition rev.h:70
void SaveState(T *object)
Definition rev.h:60
void ClearAndResize(IntegerType size)
Definition bitset.h:820
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition bitset.h:859
void Set(IntegerType index)
Definition bitset.h:845
void Resize(IntegerType size)
Definition bitset.h:831
void AdvanceDeterministicTime(double deterministic_duration)
Definition time_limit.h:178
void SortByPos(absl::Span< int > elements)
void ReorderDense(absl::Span< const int > order)
void Reorder(absl::Span< const int > order)
This is meant as an helper to deal with enforcement for any constraint.
bool Propagate(Trail *trail) final
SatPropagator interface.
void AddEnforcementReason(EnforcementId id, std::vector< Literal > *reason) const
Add the enforcement reason to the given vector.
ABSL_MUST_USE_RESULT bool PropagateWhenFalse(EnforcementId id, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
void Untrail(const Trail &trail, int trail_index) final
EnforcementStatus DebugStatus(EnforcementId id)
EnforcementId Register(absl::Span< const Literal > enforcement, std::function< void(EnforcementStatus)> callback=nullptr)
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition integer.h:1752
void SetPropagatorPriority(int id, int priority)
Definition integer.cc:2288
void CallOnNextPropagate(int id)
Add the given propagator to its queue.
Definition integer.cc:2045
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
Definition integer.h:1617
bool VariableLowerBoundIsFromLevelZero(IntegerVariable var) const
Returns true if the variable lower bound is still the one from level zero.
Definition integer.h:1063
void RegisterWatcher(SparseBitset< IntegerVariable > *p)
Definition integer.h:1039
void RelaxLinearReason(IntegerValue slack, absl::Span< const IntegerValue > coeffs, std::vector< IntegerLiteral > *reason) const
Definition integer.cc:996
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1621
IntegerLiteral LowerBoundAsLiteral(IntegerVariable i) const
Definition integer.h:1660
void RegisterReversibleClass(ReversibleInterface *rev)
Definition integer.h:1069
bool ReportConflict(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.h:1046
IntegerVariable NumIntegerVariables() const
Definition integer.h:782
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.cc:1240
void EnqueueLiteral(Literal literal, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.cc:1383
bool AddConstraint(absl::Span< const Literal > enforcement_literals, absl::Span< const IntegerVariable > vars, absl::Span< const IntegerValue > coeffs, IntegerValue upper_bound)
Adds a new constraint to the propagator.
Base class for all the SAT constraints.
Definition sat_base.h:511
Simple class to add statistics by name and print them at the end.
void AddStats(absl::Span< const std::pair< std::string, int64_t > > stats)
Adds a bunch of stats, adding count for the same key together.
const AssignmentInfo & Info(BooleanVariable var) const
Definition sat_base.h:441
const VariablesAssignment & Assignment() const
Definition sat_base.h:440
bool LiteralIsAssigned(Literal literal) const
Definition sat_base.h:179
bool LiteralIsFalse(Literal literal) const
Definition sat_base.h:173
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:176
int64_t b
Definition table.cc:45
int64_t a
Definition table.cc:44
SharedStatistics *const stats
int64_t value
IntVar * var
absl::Status status
Definition g_gurobi.cc:44
double upper_bound
GRBmodel * model
MPCallback * callback
int literal
int index
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
const LiteralIndex kNoLiteralIndex(-1)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Returns the vector of the negated variables.
Definition integer.cc:51
const IntegerVariable kNoIntegerVariable(-1)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition integer.h:193
@ IS_ENFORCED
All enforcement literals are true.
@ CANNOT_PROPAGATE
More than two literals are unassigned.
@ IS_FALSE
One enforcement literal is false.
@ CAN_PROPAGATE
All enforcement literals are true but one.
bool VariableIsPositive(IntegerVariable i)
Definition integer.h:189
In SWIG mode, we don't want anything besides these top-level includes.
std::ostream & operator<<(std::ostream &out, const Assignment &assignment)
if(!yyg->yy_init)
Definition parser.yy.cc:965
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition integer.h:1573