Google OR-Tools v9.11
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/base/log_severity.h"
27#include "absl/cleanup/cleanup.h"
28#include "absl/container/flat_hash_map.h"
29#include "absl/container/flat_hash_set.h"
30#include "absl/container/inlined_vector.h"
31#include "absl/log/check.h"
32#include "absl/numeric/int128.h"
33#include "absl/strings/str_cat.h"
34#include "absl/types/span.h"
38#include "ortools/sat/integer.h"
39#include "ortools/sat/model.h"
44#include "ortools/sat/util.h"
45#include "ortools/util/bitset.h"
48
49namespace operations_research {
50namespace sat {
51
52std::ostream& operator<<(std::ostream& os, const EnforcementStatus& e) {
53 switch (e) {
55 os << "IS_FALSE";
56 break;
58 os << "CANNOT_PROPAGATE";
59 break;
61 os << "CAN_PROPAGATE";
62 break;
64 os << "IS_ENFORCED";
65 break;
66 }
67 return os;
68}
69
71 : SatPropagator("EnforcementPropagator"),
72 trail_(*model->GetOrCreate<Trail>()),
73 assignment_(trail_.Assignment()),
74 integer_trail_(model->GetOrCreate<IntegerTrail>()),
75 rev_int_repository_(model->GetOrCreate<RevIntRepository>()) {
76 // Note that this will be after the integer trail since rev_int_repository_
77 // depends on IntegerTrail.
78 model->GetOrCreate<SatSolver>()->AddPropagator(this);
79
80 // Sentinel - also start of next Register().
81 starts_.push_back(0);
82}
83
85 rev_int_repository_->SaveStateWithStamp(&rev_stack_size_, &rev_stamp_);
86 while (propagation_trail_index_ < trail_.Index()) {
88 if (literal.Index() >= static_cast<int>(watcher_.size())) continue;
89
90 int new_size = 0;
91 auto& watch_list = watcher_[literal.Index()];
92 for (const EnforcementId id : watch_list) {
93 const LiteralIndex index = ProcessIdOnTrue(literal, id);
94 if (index == kNoLiteralIndex) {
95 // We keep the same watcher.
96 watch_list[new_size++] = id;
97 } else {
98 // Change the watcher.
99 CHECK_NE(index, literal.Index());
100 watcher_[index].push_back(id);
101 }
102 }
103 watch_list.resize(new_size);
104
105 // We also mark some constraint false.
106 for (const EnforcementId id : watcher_[literal.NegatedIndex()]) {
107 ChangeStatus(id, EnforcementStatus::IS_FALSE);
108 }
109 }
110 rev_stack_size_ = static_cast<int>(untrail_stack_.size());
111 return true;
112}
113
114void EnforcementPropagator::Untrail(const Trail& /*trail*/, int trail_index) {
115 // Simply revert the status change.
116 const int size = static_cast<int>(untrail_stack_.size());
117 for (int i = size - 1; i >= rev_stack_size_; --i) {
118 const auto [id, status] = untrail_stack_[i];
119 statuses_[id] = status;
120 if (callbacks_[id] != nullptr) callbacks_[id](id, status);
121 }
122 untrail_stack_.resize(rev_stack_size_);
123 propagation_trail_index_ = trail_index;
124}
125
126// Adds a new constraint to the class and returns the constraint id.
127//
128// Note that we accept empty enforcement list so that client code can be used
129// regardless of the presence of enforcement or not. A negative id means the
130// constraint is never enforced, and should be ignored.
132 absl::Span<const Literal> enforcement,
133 std::function<void(EnforcementId, EnforcementStatus)> callback) {
134 int num_true = 0;
135 int num_false = 0;
136 bool is_always_false = false;
137 temp_literals_.clear();
138 const int level = trail_.CurrentDecisionLevel();
139 for (const Literal l : enforcement) {
140 // Make sure we always have enough room for the literal and its negation.
141 const int size = std::max(l.Index().value(), l.NegatedIndex().value()) + 1;
142 if (size > static_cast<int>(watcher_.size())) {
143 watcher_.resize(size);
144 }
145 if (assignment_.LiteralIsTrue(l)) {
146 if (level == 0 || trail_.Info(l.Variable()).level == 0) continue;
147 ++num_true;
148 } else if (assignment_.LiteralIsFalse(l)) {
149 if (level == 0 || trail_.Info(l.Variable()).level == 0) {
150 is_always_false = true;
151 break;
152 }
153 ++num_false;
154 }
155 temp_literals_.push_back(l);
156 }
157 gtl::STLSortAndRemoveDuplicates(&temp_literals_);
158
159 // Return special indices if never/always enforced.
160 if (is_always_false) {
161 if (callback != nullptr)
162 callback(EnforcementId(-1), EnforcementStatus::IS_FALSE);
163 return EnforcementId(-1);
164 }
165 if (temp_literals_.empty()) {
166 if (callback != nullptr)
167 callback(EnforcementId(-1), EnforcementStatus::IS_ENFORCED);
168 return EnforcementId(-1);
169 }
170
171 const EnforcementId id(static_cast<int>(callbacks_.size()));
172 callbacks_.push_back(std::move(callback));
173
174 CHECK(!temp_literals_.empty());
175 buffer_.insert(buffer_.end(), temp_literals_.begin(), temp_literals_.end());
176 starts_.push_back(buffer_.size()); // Sentinel/next-start.
177
178 // The default status at level zero.
179 statuses_.push_back(temp_literals_.size() == 1
182
183 if (temp_literals_.size() == 1) {
184 watcher_[temp_literals_[0].Index()].push_back(id);
185 } else {
186 // Make sure we watch correct literals.
187 const auto span = GetSpan(id);
188 int num_not_true = 0;
189 for (int i = 0; i < span.size(); ++i) {
190 if (assignment_.LiteralIsTrue(span[i])) continue;
191 std::swap(span[num_not_true], span[i]);
192 ++num_not_true;
193 if (num_not_true == 2) break;
194 }
195
196 // We need to watch one of the literals at highest level.
197 if (num_not_true == 1) {
198 int max_level = trail_.Info(span[1].Variable()).level;
199 for (int i = 2; i < span.size(); ++i) {
200 const int level = trail_.Info(span[i].Variable()).level;
201 if (level > max_level) {
202 max_level = level;
203 std::swap(span[1], span[i]);
204 }
205 }
206 }
207
208 watcher_[span[0].Index()].push_back(id);
209 watcher_[span[1].Index()].push_back(id);
210 }
211
212 // Change status, call callback and set up untrail if the status is different
213 // from EnforcementStatus::CANNOT_PROPAGATE.
214 if (num_false > 0) {
215 ChangeStatus(id, EnforcementStatus::IS_FALSE);
216 } else if (num_true == temp_literals_.size()) {
217 ChangeStatus(id, EnforcementStatus::IS_ENFORCED);
218 } else if (num_true + 1 == temp_literals_.size()) {
219 ChangeStatus(id, EnforcementStatus::CAN_PROPAGATE);
220 // Because this is the default status, we still need to call the callback.
221 if (temp_literals_.size() == 1) {
222 if (callbacks_[id] != nullptr) {
223 callbacks_[id](id, EnforcementStatus::CAN_PROPAGATE);
224 }
225 }
226 }
227 return id;
228}
229
230// Add the enforcement reason to the given vector.
232 EnforcementId id, std::vector<Literal>* reason) const {
233 for (const Literal l : GetSpan(id)) {
234 reason->push_back(l.Negated());
235 }
236}
237
238// Try to propagate when the enforced constraint is not satisfiable.
239// This is currently in O(enforcement_size);
241 EnforcementId id, absl::Span<const Literal> literal_reason,
242 absl::Span<const IntegerLiteral> integer_reason) {
243 temp_reason_.clear();
244 LiteralIndex unique_unassigned = kNoLiteralIndex;
245 for (const Literal l : GetSpan(id)) {
246 if (assignment_.LiteralIsFalse(l)) return true;
247 if (assignment_.LiteralIsTrue(l)) {
248 temp_reason_.push_back(l.Negated());
249 continue;
250 }
251 if (unique_unassigned != kNoLiteralIndex) return true;
252 unique_unassigned = l.Index();
253 }
254
255 temp_reason_.insert(temp_reason_.end(), literal_reason.begin(),
256 literal_reason.end());
257 if (unique_unassigned == kNoLiteralIndex) {
258 return integer_trail_->ReportConflict(temp_reason_, integer_reason);
259 }
260
261 // We also change the status right away.
262 ChangeStatus(id, EnforcementStatus::IS_FALSE);
263 integer_trail_->EnqueueLiteral(Literal(unique_unassigned).Negated(),
264 temp_reason_, integer_reason);
265 return true;
266}
267
268absl::Span<Literal> EnforcementPropagator::GetSpan(EnforcementId id) {
269 if (id < 0) return {};
270 DCHECK_LE(id + 1, starts_.size());
271 const int size = starts_[id + 1] - starts_[id];
272 DCHECK_NE(size, 0);
273 return absl::MakeSpan(&buffer_[starts_[id]], size);
274}
275
276absl::Span<const Literal> EnforcementPropagator::GetSpan(
277 EnforcementId id) const {
278 if (id < 0) return {};
279 DCHECK_LE(id + 1, starts_.size());
280 const int size = starts_[id + 1] - starts_[id];
281 DCHECK_NE(size, 0);
282 return absl::MakeSpan(&buffer_[starts_[id]], size);
283}
284
285LiteralIndex EnforcementPropagator::ProcessIdOnTrue(Literal watched,
286 EnforcementId id) {
287 const EnforcementStatus status = statuses_[id];
289
290 const auto span = GetSpan(id);
291 if (span.size() == 1) {
293 ChangeStatus(id, EnforcementStatus::IS_ENFORCED);
294 return kNoLiteralIndex;
295 }
296
297 const int watched_pos = (span[0] == watched) ? 0 : 1;
298 CHECK_EQ(span[watched_pos], watched);
299 if (assignment_.LiteralIsFalse(span[watched_pos ^ 1])) {
300 ChangeStatus(id, EnforcementStatus::IS_FALSE);
301 return kNoLiteralIndex;
302 }
303
304 for (int i = 2; i < span.size(); ++i) {
305 const Literal l = span[i];
306 if (assignment_.LiteralIsFalse(l)) {
307 ChangeStatus(id, EnforcementStatus::IS_FALSE);
308 return kNoLiteralIndex;
309 }
310 if (!assignment_.LiteralIsAssigned(l)) {
311 // Replace the watched literal. Note that if the other watched literal is
312 // true, it should be processed afterwards. We do not change the status
313 std::swap(span[watched_pos], span[i]);
314 return span[watched_pos].Index();
315 }
316 }
317
318 // All literal with index > 1 are true. Two case.
319 if (assignment_.LiteralIsTrue(span[watched_pos ^ 1])) {
320 // All literals are true.
321 ChangeStatus(id, EnforcementStatus::IS_ENFORCED);
322 return kNoLiteralIndex;
323 } else {
324 // The other watched literal is the last unassigned
326 ChangeStatus(id, EnforcementStatus::CAN_PROPAGATE);
327 return kNoLiteralIndex;
328 }
329}
330
331void EnforcementPropagator::ChangeStatus(EnforcementId id,
332 EnforcementStatus new_status) {
333 const EnforcementStatus old_status = statuses_[id];
334 if (old_status == new_status) return;
335 if (trail_.CurrentDecisionLevel() != 0) {
336 untrail_stack_.push_back({id, old_status});
337 }
338 statuses_[id] = new_status;
339 if (callbacks_[id] != nullptr) callbacks_[id](id, new_status);
340}
341
343 if (id < 0) return EnforcementStatus::IS_ENFORCED;
344
345 int num_true = 0;
346 for (const Literal l : GetSpan(id)) {
347 if (assignment_.LiteralIsFalse(l)) {
349 }
350 if (assignment_.LiteralIsTrue(l)) ++num_true;
351 }
352 const int size = GetSpan(id).size();
353 if (num_true == size) return EnforcementStatus::IS_ENFORCED;
354 if (num_true + 1 == size) return EnforcementStatus::CAN_PROPAGATE;
356}
357
359 : trail_(model->GetOrCreate<Trail>()),
360 integer_trail_(model->GetOrCreate<IntegerTrail>()),
361 enforcement_propagator_(model->GetOrCreate<EnforcementPropagator>()),
362 watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
363 time_limit_(model->GetOrCreate<TimeLimit>()),
364 rev_int_repository_(model->GetOrCreate<RevIntRepository>()),
365 rev_integer_value_repository_(
366 model->GetOrCreate<RevIntegerValueRepository>()),
367 precedences_(model->GetOrCreate<PrecedenceRelations>()),
368 random_(model->GetOrCreate<ModelRandomGenerator>()),
369 shared_stats_(model->GetOrCreate<SharedStatistics>()),
370 watcher_id_(watcher_->Register(this)),
371 order_(random_, [this](int id) { return GetVariables(infos_[id]); }) {
372 // Note that we need this class always in sync.
373 integer_trail_->RegisterWatcher(&modified_vars_);
374 integer_trail_->RegisterReversibleClass(this);
375
376 // TODO(user): When we start to push too much (Cycle?) we should see what
377 // other propagator says before repropagating this one, system for call
378 // later?
379 watcher_->SetPropagatorPriority(watcher_id_, 0);
380}
381
383 if (!VLOG_IS_ON(1)) return;
384 if (shared_stats_ == nullptr) return;
385 std::vector<std::pair<std::string, int64_t>> stats;
386 stats.push_back({"linear_propag/num_pushes", num_pushes_});
387 stats.push_back(
388 {"linear_propag/num_enforcement_pushes", num_enforcement_pushes_});
389
390 stats.push_back({"linear_propag/num_cycles", num_cycles_});
391 stats.push_back({"linear_propag/num_failed_cycles", num_failed_cycles_});
392
393 stats.push_back({"linear_propag/num_short_reasons_", num_short_reasons_});
394 stats.push_back({"linear_propag/num_long_reasons_", num_long_reasons_});
395
396 stats.push_back({"linear_propag/num_scanned", num_scanned_});
397 stats.push_back({"linear_propag/num_explored_in_disassemble",
398 num_explored_in_disassemble_});
399 stats.push_back({"linear_propag/num_bool_aborts", num_bool_aborts_});
400 stats.push_back({"linear_propag/num_loop_aborts", num_loop_aborts_});
401 stats.push_back({"linear_propag/num_ignored", num_ignored_});
402 stats.push_back({"linear_propag/num_delayed", num_delayed_});
403 shared_stats_->AddStats(stats);
404}
405
407 if (level < previous_level_) {
408 order_.Clear();
409 modified_vars_.ClearAll();
410
411 // If the solver backtracked at any point, we invalidate all our queue
412 // and propagated_by information.
413 ClearPropagatedBy();
414 for (const int id : propagation_queue_) in_queue_.Clear(id);
415 propagation_queue_.clear();
416 for (int i = rev_unenforced_size_; i < unenforced_constraints_.size();
417 ++i) {
418 in_queue_.Clear(unenforced_constraints_[i]);
419 }
420 unenforced_constraints_.resize(rev_unenforced_size_);
421 } else if (level > previous_level_) {
422 rev_unenforced_size_ = unenforced_constraints_.size();
423 rev_int_repository_->SaveState(&rev_unenforced_size_);
424 }
425
426 // Tricky: if we aborted the current propagation because we pushed a Boolean,
427 // by default, the GenericLiteralWatcher will only call Propagate() again if
428 // one of the watched variable changed. With this, it is guaranteed to call
429 // it again if it wasn't in the queue already.
430 if (!propagation_queue_.empty() ||
431 !modified_vars_.PositionsSetAtLeastOnce().empty() || !order_.IsEmpty()) {
432 watcher_->CallOnNextPropagate(watcher_id_);
433 }
434
435 previous_level_ = level;
436}
437
438void LinearPropagator::SetPropagatedBy(IntegerVariable var, int id) {
439 int& ref_id = propagated_by_[var];
440 if (ref_id == id) return;
441
442 propagated_by_was_set_.Set(var);
443
444 DCHECK_GE(var, 0);
445 DCHECK_LT(var, propagated_by_.size());
446 if (ref_id != -1) {
447 DCHECK_GE(ref_id, 0);
448 DCHECK_LT(ref_id, id_to_propagation_count_.size());
449 id_to_propagation_count_[ref_id]--;
450 }
451 ref_id = id;
452 if (id != -1) id_to_propagation_count_[id]++;
453}
454
455void LinearPropagator::OnVariableChange(IntegerVariable var, IntegerValue lb,
456 int id) {
457 // If no constraint use this var, we just ignore it.
458 const int size = var_to_constraint_ids_[var].size();
459 if (size == 0) return;
460
461 SetPropagatedBy(var, id);
462 order_.UpdateBound(var, lb);
463 Bitset64<int>::View in_queue = in_queue_.view();
464 time_limit_->AdvanceDeterministicTime(static_cast<double>(size) * 1e-9);
465 for (const int id : var_to_constraint_ids_[var]) {
466 if (in_queue[id]) continue;
467 in_queue.Set(id);
468 propagation_queue_.push_back(id);
469 }
470}
471
473 // Initial addition.
474 //
475 // We will clear modified_vars_ on exit since everything we propagate here
476 // is handled by PropagateOneConstraint().
477 for (const IntegerVariable var : modified_vars_.PositionsSetAtLeastOnce()) {
478 if (var >= var_to_constraint_ids_.size()) continue;
479 OnVariableChange(var, integer_trail_->LowerBound(var), -1);
480 }
481
482 // Cleanup.
483 num_terms_for_dtime_update_ = 0;
484 const auto cleanup = ::absl::MakeCleanup([this]() {
485 time_limit_->AdvanceDeterministicTime(
486 static_cast<double>(num_terms_for_dtime_update_) * 1e-9);
487 modified_vars_.ClearAndResize(integer_trail_->NumIntegerVariables());
488 });
489
490 // We abort this propagator as soon as a Boolean is propagated, so that we
491 // always finish the Boolean propagation first. This can happen when we push a
492 // bound that has associated Booleans or push enforcement to false. The idea
493 // is to resume from our current state when we are called again. Note however
494 // that we have to clear the propagated_by_ info has other propagator might
495 // have pushed the same variable further.
496 //
497 // TODO(user): More than the propagation speed, I think it is important to
498 // have proper explanation, so if A pushes B, but later on the queue we have C
499 // that push A that push B again, that might be bad? We can try to avoid this
500 // even further, by organizing the queue in passes:
501 // - Scan all relevant constraints, remember who pushes but DO NOT push yet!
502 // - If no cycle, do not pushes constraint whose slack will changes due to
503 // other pushes.
504 // - consider the new constraint that need to be scanned and repeat.
505 // I think it is okay to scan twice the constraints that push something in
506 // order to get better explanation. We tend to diverge from the class shortest
507 // path algo in this regard.
508 //
509 // TODO(user): If we push the idea further, can we first compute the fix point
510 // without pushing anything, then compute a good order of constraints for the
511 // explanations? what is tricky is that we might need to "scan" more than once
512 // a constraint I think. ex: Y, Z, T >=0
513 // - 2 * Y + Z + T <= 11 ==> Y <= 5, Z <= 11, T <= 11 (1)
514 // - Z + Y >= 6 ==> Z >= 1
515 // - (1) again to push T <= 10 and reach the propagation fixed point.
516 Bitset64<int>::View in_queue = in_queue_.view();
517 while (true) {
518 // We always process the whole queue in FIFO order.
519 // Note that the order really only matter for infeasible constraint so it
520 // shouldn't have a big impact.
521 const int saved_index = trail_->Index();
522 while (!propagation_queue_.empty()) {
523 const int id = propagation_queue_.front();
524 propagation_queue_.pop_front();
525 in_queue.Clear(id);
526 const auto [slack, num_to_push] = AnalyzeConstraint(id);
527 if (slack < 0) {
528 // This is either a conflict or an enforcement propagation.
529 // We do it right away.
530 if (!PropagateInfeasibleConstraint(id, slack)) return false;
531
532 // We abort after the first pushed boolean. We could abort later too,
533 // it is unclear what works best.
534 //
535 // TODO(user): Maybe we should "update" explanation if we have a shorter
536 // one to be less reliant on the propagation order.
537 if (trail_->Index() > saved_index) {
538 ++num_bool_aborts_;
539 return true;
540 }
541 } else if (num_to_push > 0) {
542 // Note that the last constraint added in to_propagate_ should never
543 // be "skipped" and have any variable marked as var_will_change_.
544 const auto vars = GetVariables(infos_[id]);
545 const auto coeffs = GetCoeffs(infos_[id]);
546 for (int i = 0; i < num_to_push; ++i) {
547 const IntegerVariable var = vars[i];
548 const IntegerValue coeff = coeffs[i];
549 const IntegerValue div = slack / coeff;
550 const IntegerValue new_ub = integer_trail_->LowerBound(var) + div;
551 order_.Register(id, NegationOf(var), -new_ub);
552 }
553 }
554 }
555
556 const int next_id = order_.NextId();
557 if (next_id == -1) break;
558
559 // We can probably save the AnalyzeConstraint() cost, but then we only do
560 // that when the constraint propagate, and the context might have change
561 // since we computed it above.
562 if (!PropagateOneConstraint(next_id)) return false;
563
564 // TODO(user): This do not seems always good, especially since we pushed
565 // Boolean with a really small explanation, maybe we want to push more of
566 // these rather than go back to pure-binary propagation.
567 if (trail_->Index() > saved_index) {
568 ++num_bool_aborts_;
569 return true;
570 }
571 }
572 return true;
573}
574
575// Adds a new constraint to the propagator.
577 absl::Span<const Literal> enforcement_literals,
578 absl::Span<const IntegerVariable> vars,
579 absl::Span<const IntegerValue> coeffs, IntegerValue upper_bound) {
580 if (vars.empty()) return true;
581 if (trail_->CurrentDecisionLevel() == 0) {
582 for (const Literal l : enforcement_literals) {
583 if (trail_->Assignment().LiteralIsFalse(l)) return true;
584 }
585 }
586
587 // Make sure max_variations_ is of correct size.
588 // Note that we also have a hard limit of 1 << 29 on the size.
589 CHECK_LT(vars.size(), 1 << 29);
590 if (vars.size() > max_variations_.size()) {
591 max_variations_.resize(vars.size(), 0);
592 buffer_of_ones_.resize(vars.size(), IntegerValue(1));
593 }
594
595 // Initialize constraint data.
596 CHECK_EQ(vars.size(), coeffs.size());
597 const int id = infos_.size();
598 {
599 ConstraintInfo info;
600 info.all_coeffs_are_one = false;
601 info.start = variables_buffer_.size();
602 info.initial_size = vars.size();
603 info.rev_rhs = upper_bound;
604 info.rev_size = vars.size();
605 infos_.push_back(std::move(info));
606 initial_rhs_.push_back(upper_bound);
607 }
608
609 id_to_propagation_count_.push_back(0);
610 variables_buffer_.insert(variables_buffer_.end(), vars.begin(), vars.end());
611 coeffs_buffer_.insert(coeffs_buffer_.end(), coeffs.begin(), coeffs.end());
612 CanonicalizeConstraint(id);
613
614 bool all_at_one = true;
615 for (const IntegerValue coeff : GetCoeffs(infos_.back())) {
616 if (coeff != 1) {
617 all_at_one = false;
618 break;
619 }
620 }
621 if (all_at_one) {
622 // TODO(user): we still waste the space in coeffs_buffer_ so that the
623 // start are aligned with the variables_buffer_.
624 infos_.back().all_coeffs_are_one = true;
625 }
626
627 // Initialize watchers.
628 // Initialy we want everything to be propagated at least once.
629 in_queue_.resize(in_queue_.size() + 1);
630
631 if (!enforcement_literals.empty()) {
632 infos_.back().enf_status =
633 static_cast<int>(EnforcementStatus::CANNOT_PROPAGATE);
634 infos_.back().enf_id = enforcement_propagator_->Register(
635 enforcement_literals,
636 [this, id](EnforcementId enf_id, EnforcementStatus status) {
637 infos_[id].enf_status = static_cast<int>(status);
638 // TODO(user): With some care, when we cannot propagate or the
639 // constraint is not enforced, we could leave in_queue_[] at true but
640 // not put the constraint in the queue.
643 AddToQueueIfNeeded(id);
644 watcher_->CallOnNextPropagate(watcher_id_);
645 }
646
647 // When a conditional precedence becomes enforced, add it.
648 // Note that we only look at relation that were a "precedence" from
649 // the start, note the one currently of size 2 if we ignore fixed
650 // variables.
652 const auto info = infos_[id];
653 if (info.initial_size == 2 && info.all_coeffs_are_one) {
654 const auto vars = GetVariables(info);
655 precedences_->PushConditionalRelation(
656 enforcement_propagator_->GetEnforcementLiterals(enf_id),
657 vars[0], vars[1], initial_rhs_[id]);
658 }
659 }
660 });
661 } else {
662 // TODO(user): Shall we register root level precedence from here rather than
663 // separately?
664 AddToQueueIfNeeded(id);
665 infos_.back().enf_id = -1;
666 infos_.back().enf_status = static_cast<int>(EnforcementStatus::IS_ENFORCED);
667 }
668
669 order_.Resize(var_to_constraint_ids_.size(), in_queue_.size());
670 for (const IntegerVariable var : GetVariables(infos_[id])) {
671 // Transposed graph to know which constraint to wake up.
672 if (var >= var_to_constraint_ids_.size()) {
673 // We need both the var entry and its negation to be allocated.
674 const int size = std::max(var, NegationOf(var)).value() + 1;
675 var_to_constraint_ids_.resize(size);
676 propagated_by_.resize(size, -1);
677 propagated_by_was_set_.Resize(IntegerVariable(size));
678 is_watched_.resize(size, false);
679
680 order_.Resize(size, in_queue_.size());
681 }
682
683 // TODO(user): Shall we decide on some ordering here? maybe big coeff first
684 // so that we get the largest change in slack? the idea being to propagate
685 // large change first in case of cycles.
686 var_to_constraint_ids_[var].push_back(id);
687
688 // We need to be registered to the watcher so Propagate() is called at
689 // the proper priority. But then we rely on modified_vars_.
690 if (!is_watched_[var]) {
691 is_watched_[var] = true;
692 watcher_->WatchLowerBound(var, watcher_id_);
693 }
694 }
695
696 // Propagate this new constraint.
697 // TODO(user): Do we want to do that?
698 num_terms_for_dtime_update_ = 0;
699 const auto cleanup = ::absl::MakeCleanup([this]() {
700 time_limit_->AdvanceDeterministicTime(
701 static_cast<double>(num_terms_for_dtime_update_) * 1e-9);
702 });
703 if (!PropagateOneConstraint(id)) return false;
704 return true;
705}
706
707absl::Span<IntegerValue> LinearPropagator::GetCoeffs(
708 const ConstraintInfo& info) {
709 if (info.all_coeffs_are_one) {
710 return absl::MakeSpan(&buffer_of_ones_[0], info.initial_size);
711 }
712 return absl::MakeSpan(&coeffs_buffer_[info.start], info.initial_size);
713}
714
715absl::Span<IntegerVariable> LinearPropagator::GetVariables(
716 const ConstraintInfo& info) {
717 return absl::MakeSpan(&variables_buffer_[info.start], info.initial_size);
718}
719
720void LinearPropagator::CanonicalizeConstraint(int id) {
721 const ConstraintInfo& info = infos_[id];
722 const auto coeffs = GetCoeffs(info);
723 const auto vars = GetVariables(info);
724 for (int i = 0; i < vars.size(); ++i) {
725 if (coeffs[i] < 0) {
726 coeffs[i] = -coeffs[i];
727 vars[i] = NegationOf(vars[i]);
728 }
729 }
730
731 // Note that we DO NOT support having both var and NegationOf(var) in a
732 // constraint, that would break the algo.
733 if (DEBUG_MODE) {
734 absl::flat_hash_set<IntegerVariable> no_dup;
735 for (const IntegerVariable var : GetVariables(info)) {
736 auto [_, inserted] = no_dup.insert(PositiveVariable(var));
737 CHECK(inserted);
738 }
739 }
740}
741
742// TODO(user): template everything for the case info.all_coeffs_are_one ?
743std::pair<IntegerValue, int> LinearPropagator::AnalyzeConstraint(int id) {
744 ++num_scanned_;
745
746 // Skip constraint not enforced or that cannot propagate if false.
747 ConstraintInfo& info = infos_[id];
748 const EnforcementStatus enf_status = EnforcementStatus(info.enf_status);
749 if (DEBUG_MODE && enforcement_propagator_->PropagationIsDone(*trail_)) {
750 const EnforcementStatus debug_status =
751 enforcement_propagator_->DebugStatus(info.enf_id);
752 if (enf_status != debug_status) {
753 if (enf_status == EnforcementStatus::CANNOT_PROPAGATE &&
754 debug_status == EnforcementStatus::IS_FALSE) {
755 // This case might happen because in our two watched literals scheme,
756 // we might watch two unassigned literal without knowing another one is
757 // already false.
758 } else {
759 LOG(FATAL) << "Enforcement status not up to date: " << enf_status
760 << " vs debug: " << debug_status;
761 }
762 }
763 }
764
765 if (enf_status == EnforcementStatus::IS_FALSE ||
767 DCHECK(!in_queue_[id]);
768 if (enf_status == EnforcementStatus::IS_FALSE) {
769 // We mark this constraint as in the queue but will never inspect it
770 // again until we backtrack over this time.
771 in_queue_.Set(id);
772 unenforced_constraints_.push_back(id);
773 }
774 ++num_ignored_;
775 return {0, 0};
776 }
777
778 // Compute the slack and max_variations_ of each variables.
779 // We also filter out fixed variables in a reversible way.
780 IntegerValue implied_lb(0);
781 const auto vars = GetVariables(info);
782 IntegerValue max_variation(0);
783 bool first_change = true;
784 num_terms_for_dtime_update_ += info.rev_size;
785 IntegerValue* max_variations = max_variations_.data();
786 const IntegerValue* lower_bounds = integer_trail_->LowerBoundsData();
787 if (info.all_coeffs_are_one) {
788 // TODO(user): Avoid duplication?
789 for (int i = 0; i < info.rev_size;) {
790 const IntegerVariable var = vars[i];
791 const IntegerValue lb = lower_bounds[var.value()];
792 const IntegerValue diff = -lower_bounds[NegationOf(var).value()] - lb;
793 if (diff == 0) {
794 if (first_change) {
795 // Note that we can save at most one state per fixed var. Also at
796 // level zero we don't save anything.
797 rev_int_repository_->SaveState(&info.rev_size);
798 rev_integer_value_repository_->SaveState(&info.rev_rhs);
799 first_change = false;
800 }
801 info.rev_size--;
802 std::swap(vars[i], vars[info.rev_size]);
803 info.rev_rhs -= lb;
804 } else {
805 implied_lb += lb;
806 max_variations[i] = diff;
807 max_variation = std::max(max_variation, diff);
808 ++i;
809 }
810 }
811 } else {
812 const auto coeffs = GetCoeffs(info);
813 for (int i = 0; i < info.rev_size;) {
814 const IntegerVariable var = vars[i];
815 const IntegerValue coeff = coeffs[i];
816 const IntegerValue lb = lower_bounds[var.value()];
817 const IntegerValue diff = -lower_bounds[NegationOf(var).value()] - lb;
818 if (diff == 0) {
819 if (first_change) {
820 // Note that we can save at most one state per fixed var. Also at
821 // level zero we don't save anything.
822 rev_int_repository_->SaveState(&info.rev_size);
823 rev_integer_value_repository_->SaveState(&info.rev_rhs);
824 first_change = false;
825 }
826 info.rev_size--;
827 std::swap(vars[i], vars[info.rev_size]);
828 std::swap(coeffs[i], coeffs[info.rev_size]);
829 info.rev_rhs -= coeff * lb;
830 } else {
831 implied_lb += coeff * lb;
832 max_variations[i] = diff * coeff;
833 max_variation = std::max(max_variation, max_variations[i]);
834 ++i;
835 }
836 }
837 }
838
839 // What we call slack here is the "room" between the implied_lb and the rhs.
840 // Note that we use slack in other context in this file too.
841 const IntegerValue slack = info.rev_rhs - implied_lb;
842
843 // Negative slack means the constraint is false.
844 // Note that if max_variation > slack, we are sure to propagate something
845 // except if the constraint is enforced and the slack is non-negative.
846 if (slack < 0 || max_variation <= slack) return {slack, 0};
847 if (enf_status == EnforcementStatus::IS_ENFORCED) {
848 // Swap the variable(s) that will be pushed at the beginning.
849 int num_to_push = 0;
850 const auto coeffs = GetCoeffs(info);
851 for (int i = 0; i < info.rev_size; ++i) {
852 if (max_variations[i] <= slack) continue;
853 std::swap(vars[i], vars[num_to_push]);
854 std::swap(coeffs[i], coeffs[num_to_push]);
855 ++num_to_push;
856 }
857 return {slack, num_to_push};
858 }
859 return {slack, 0};
860}
861
862bool LinearPropagator::PropagateInfeasibleConstraint(int id,
863 IntegerValue slack) {
864 DCHECK_LT(slack, 0);
865 const ConstraintInfo& info = infos_[id];
866 const auto vars = GetVariables(info);
867 const auto coeffs = GetCoeffs(info);
868
869 // Fill integer reason.
870 integer_reason_.clear();
871 reason_coeffs_.clear();
872 for (int i = 0; i < info.initial_size; ++i) {
873 const IntegerVariable var = vars[i];
874 if (!integer_trail_->VariableLowerBoundIsFromLevelZero(var)) {
875 integer_reason_.push_back(integer_trail_->LowerBoundAsLiteral(var));
876 reason_coeffs_.push_back(coeffs[i]);
877 }
878 }
879
880 // Relax it.
881 integer_trail_->RelaxLinearReason(-slack - 1, reason_coeffs_,
882 &integer_reason_);
883 ++num_enforcement_pushes_;
884 return enforcement_propagator_->PropagateWhenFalse(info.enf_id, {},
885 integer_reason_);
886}
887
888void LinearPropagator::Explain(int id, IntegerValue propagation_slack,
889 IntegerVariable var_to_explain, int trail_index,
890 std::vector<Literal>* literals_reason,
891 std::vector<int>* trail_indices_reason) {
892 literals_reason->clear();
893 trail_indices_reason->clear();
894 const ConstraintInfo& info = infos_[id];
895 enforcement_propagator_->AddEnforcementReason(info.enf_id, literals_reason);
896 reason_coeffs_.clear();
897
898 const auto coeffs = GetCoeffs(info);
899 const auto vars = GetVariables(info);
900 for (int i = 0; i < info.initial_size; ++i) {
901 const IntegerVariable var = vars[i];
902 if (PositiveVariable(var) == PositiveVariable(var_to_explain)) {
903 continue;
904 }
905 const int index =
906 integer_trail_->FindTrailIndexOfVarBefore(var, trail_index);
907 if (index >= 0) {
908 trail_indices_reason->push_back(index);
909 if (propagation_slack > 0) {
910 reason_coeffs_.push_back(coeffs[i]);
911 }
912 }
913 }
914 if (propagation_slack > 0) {
915 integer_trail_->RelaxLinearReason(propagation_slack, reason_coeffs_,
916 trail_indices_reason);
917 }
918}
919
920bool LinearPropagator::PropagateOneConstraint(int id) {
921 const auto [slack, num_to_push] = AnalyzeConstraint(id);
922 if (slack < 0) return PropagateInfeasibleConstraint(id, slack);
923 if (num_to_push == 0) return true;
924
925 DCHECK_GT(num_to_push, 0);
926 DCHECK_GE(slack, 0);
927 const ConstraintInfo& info = infos_[id];
928 const auto vars = GetVariables(info);
929 const auto coeffs = GetCoeffs(info);
930
931 // We can only propagate more if all the enforcement literals are true.
932 // But this should have been checked by SkipConstraint().
933 CHECK_EQ(info.enf_status, static_cast<int>(EnforcementStatus::IS_ENFORCED));
934
935 // We can look for disasemble before the actual push.
936 // This should lead to slighly better reason.
937 // Explore the subtree and detect cycles greedily.
938 // Also postpone some propagation.
939 if (!DisassembleSubtree(id, num_to_push)) {
940 return false;
941 }
942
943 // The lower bound of all the variables except one can be used to update the
944 // upper bound of the last one.
945 int num_pushed = 0;
946 for (int i = 0; i < num_to_push; ++i) {
947 if (!order_.VarShouldBePushedById(NegationOf(vars[i]), id)) {
948 ++num_delayed_;
949 continue;
950 }
951
952 // TODO(user): If the new ub fall into an hole of the variable, we can
953 // actually relax the reason more by computing a better slack.
954 ++num_pushes_;
955 const IntegerVariable var = vars[i];
956 const IntegerValue coeff = coeffs[i];
957 const IntegerValue div = slack / coeff;
958 const IntegerValue new_ub = integer_trail_->LowerBound(var) + div;
959 const IntegerValue propagation_slack = (div + 1) * coeff - slack - 1;
960 if (!integer_trail_->EnqueueWithLazyReason(
961 IntegerLiteral::LowerOrEqual(var, new_ub), id, propagation_slack,
962 this)) {
963 return false;
964 }
965
966 // Add to the queue all touched constraint.
967 const IntegerValue actual_ub = integer_trail_->UpperBound(var);
968 const IntegerVariable next_var = NegationOf(var);
969 if (actual_ub < new_ub) {
970 // Was pushed further due to hole. We clear it.
971 OnVariableChange(next_var, -actual_ub, -1);
972 } else if (actual_ub == new_ub) {
973 OnVariableChange(next_var, -actual_ub, id);
974
975 // We reorder them first.
976 std::swap(vars[i], vars[num_pushed]);
977 std::swap(coeffs[i], coeffs[num_pushed]);
978 ++num_pushed;
979 } else {
980 // The bound was not pushed because we think we are in a propagation loop.
981 ++num_loop_aborts_;
982 }
983 }
984
985 return true;
986}
987
988std::string LinearPropagator::ConstraintDebugString(int id) {
989 std::string result;
990 const ConstraintInfo& info = infos_[id];
991 const auto coeffs = GetCoeffs(info);
992 const auto vars = GetVariables(info);
993 IntegerValue implied_lb(0);
994 IntegerValue rhs_correction(0);
995 for (int i = 0; i < info.initial_size; ++i) {
996 const IntegerValue term = coeffs[i] * integer_trail_->LowerBound(vars[i]);
997 if (i >= info.rev_size) {
998 rhs_correction += term;
999 }
1000 implied_lb += term;
1001 absl::StrAppend(&result, " +", coeffs[i].value(), "*X", vars[i].value());
1002 }
1003 const IntegerValue original_rhs = info.rev_rhs + rhs_correction;
1004 absl::StrAppend(&result, " <= ", original_rhs.value(),
1005 " slack=", original_rhs.value() - implied_lb.value());
1006 absl::StrAppend(&result, " enf=", info.enf_status);
1007 return result;
1008}
1009
1010bool LinearPropagator::ReportConflictingCycle() {
1011 // Often, all coefficients of the variable involved in the cycle are the same
1012 // and if we sum all constraint, we get an infeasible one. If this is the
1013 // case, we simplify the reason.
1014 //
1015 // TODO(user): We could relax if the coefficient of the sum do not overflow.
1016 // TODO(user): Sum constraints with eventual factor in more cases.
1017 {
1018 literal_reason_.clear();
1019 integer_reason_.clear();
1020 absl::int128 rhs_sum = 0;
1021 absl::flat_hash_map<IntegerVariable, absl::int128> map_sum;
1022 for (const auto [id, next_var, increase] : disassemble_branch_) {
1023 const ConstraintInfo& info = infos_[id];
1024 enforcement_propagator_->AddEnforcementReason(info.enf_id,
1025 &literal_reason_);
1026 const auto coeffs = GetCoeffs(info);
1027 const auto vars = GetVariables(info);
1028 IntegerValue rhs_correction(0);
1029 for (int i = 0; i < info.initial_size; ++i) {
1030 if (i >= info.rev_size) {
1031 rhs_correction += coeffs[i] * integer_trail_->LowerBound(vars[i]);
1032 }
1033 if (VariableIsPositive(vars[i])) {
1034 map_sum[vars[i]] += coeffs[i].value();
1035 } else {
1036 map_sum[PositiveVariable(vars[i])] -= coeffs[i].value();
1037 }
1038 }
1039 rhs_sum += (info.rev_rhs + rhs_correction).value();
1040 }
1041
1042 // We shouldn't have overflow since each component do not overflow an
1043 // int64_t and we sum a small amount of them.
1044 absl::int128 implied_lb = 0;
1045 for (const auto [var, coeff] : map_sum) {
1046 if (coeff > 0) {
1047 if (!integer_trail_->VariableLowerBoundIsFromLevelZero(var)) {
1048 integer_reason_.push_back(integer_trail_->LowerBoundAsLiteral(var));
1049 }
1050 implied_lb +=
1051 coeff * absl::int128{integer_trail_->LowerBound(var).value()};
1052 } else if (coeff < 0) {
1053 if (!integer_trail_->VariableLowerBoundIsFromLevelZero(
1054 NegationOf(var))) {
1055 integer_reason_.push_back(integer_trail_->UpperBoundAsLiteral(var));
1056 }
1057 implied_lb +=
1058 coeff * absl::int128{integer_trail_->UpperBound(var).value()};
1059 }
1060 }
1061 if (implied_lb > rhs_sum) {
1062 // We sort for determinism.
1063 std::sort(integer_reason_.begin(), integer_reason_.end(),
1064 [](const IntegerLiteral& a, const IntegerLiteral& b) {
1065 return a.var < b.var;
1066 });
1067
1068 // Relax the linear reason if everything fit on an int64_t.
1069 const absl::int128 limit{std::numeric_limits<int64_t>::max()};
1070 const absl::int128 slack = implied_lb - rhs_sum;
1071 if (slack > 1) {
1072 reason_coeffs_.clear();
1073 bool abort = false;
1074 for (const IntegerLiteral i_lit : integer_reason_) {
1075 absl::int128 c = map_sum.at(PositiveVariable(i_lit.var));
1076 if (c < 0) c = -c; // No std::abs() for int128.
1077 if (c >= limit) {
1078 abort = true;
1079 break;
1080 }
1081 reason_coeffs_.push_back(static_cast<int64_t>(c));
1082 }
1083 if (!abort) {
1084 const IntegerValue slack64(
1085 static_cast<int64_t>(std::min(limit, slack)));
1086 integer_trail_->RelaxLinearReason(slack64 - 1, reason_coeffs_,
1087 &integer_reason_);
1088 }
1089 }
1090
1091 ++num_short_reasons_;
1092 VLOG(2) << "Simplified " << integer_reason_.size() << " slack "
1093 << implied_lb - rhs_sum;
1094 return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
1095 }
1096 }
1097
1098 // For the complex reason, we just use the bound of every variable.
1099 // We do some basic simplification for the variable involved in the cycle.
1100 //
1101 // TODO(user): Can we simplify more?
1102 VLOG(2) << "Cycle";
1103 literal_reason_.clear();
1104 integer_reason_.clear();
1105 IntegerVariable previous_var = kNoIntegerVariable;
1106 for (const auto [id, next_var, increase] : disassemble_branch_) {
1107 const ConstraintInfo& info = infos_[id];
1108 enforcement_propagator_->AddEnforcementReason(info.enf_id,
1109 &literal_reason_);
1110 for (const IntegerVariable var : GetVariables(infos_[id])) {
1111 // The lower bound of this variable is implied by the previous constraint,
1112 // so we do not need to include it.
1113 if (var == previous_var) continue;
1114
1115 // We do not need the lower bound of var to propagate its upper bound.
1116 if (var == NegationOf(next_var)) continue;
1117
1118 if (!integer_trail_->VariableLowerBoundIsFromLevelZero(var)) {
1119 integer_reason_.push_back(integer_trail_->LowerBoundAsLiteral(var));
1120 }
1121 }
1122 previous_var = next_var;
1123
1124 VLOG(2) << next_var << " [" << integer_trail_->LowerBound(next_var) << ","
1125 << integer_trail_->UpperBound(next_var)
1126 << "] : " << ConstraintDebugString(id);
1127 }
1128 ++num_long_reasons_;
1129 return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
1130}
1131
1132std::pair<IntegerValue, IntegerValue> LinearPropagator::GetCycleCoefficients(
1133 int id, IntegerVariable var, IntegerVariable next_var) {
1134 const ConstraintInfo& info = infos_[id];
1135 const auto coeffs = GetCoeffs(info);
1136 const auto vars = GetVariables(info);
1137 IntegerValue var_coeff(0);
1138 IntegerValue next_coeff(0);
1139 for (int i = 0; i < info.initial_size; ++i) {
1140 if (vars[i] == var) var_coeff = coeffs[i];
1141 if (vars[i] == NegationOf(next_var)) next_coeff = coeffs[i];
1142 }
1143 DCHECK_NE(var_coeff, 0);
1144 DCHECK_NE(next_coeff, 0);
1145 return {var_coeff, next_coeff};
1146}
1147
1148// Note that if there is a loop in the propagated_by_ graph, it must be
1149// from root_id -> root_var, because each time we add an edge, we do
1150// disassemble.
1151//
1152// TODO(user): If one of the var coeff is > previous slack we push an id again,
1153// we can stop early with a conflict by propagating the ids in sequence.
1154//
1155// TODO(user): Revisit the algo, no point exploring twice the same var, also
1156// the queue reordering heuristic might not be the best.
1157bool LinearPropagator::DisassembleSubtree(int root_id, int num_tight) {
1158 // The variable was just pushed, we explore the set of variable that will
1159 // be pushed further due to this push. Basically, if a constraint propagated
1160 // before and its slack will reduce due to the push, then any previously
1161 // propagated variable with a coefficient NOT GREATER than the one of the
1162 // variable reducing the slack will be pushed further.
1163 disassemble_queue_.clear();
1164 disassemble_branch_.clear();
1165 {
1166 const ConstraintInfo& info = infos_[root_id];
1167 const auto vars = GetVariables(info);
1168 for (int i = 0; i < num_tight; ++i) {
1169 disassemble_queue_.push_back({root_id, NegationOf(vars[i]), 1});
1170 }
1171 }
1172
1173 // Note that all var should be unique since there is only one propagated_by_
1174 // for each one. And each time we explore an id, we disassemble the tree.
1175 absl::Span<int> id_to_count = absl::MakeSpan(id_to_propagation_count_);
1176 while (!disassemble_queue_.empty()) {
1177 const auto [prev_id, var, increase] = disassemble_queue_.back();
1178 if (!disassemble_branch_.empty() &&
1179 disassemble_branch_.back().id == prev_id &&
1180 disassemble_branch_.back().var == var) {
1181 disassemble_branch_.pop_back();
1182 disassemble_queue_.pop_back();
1183 continue;
1184 }
1185
1186 disassemble_branch_.push_back({prev_id, var, increase});
1187
1188 time_limit_->AdvanceDeterministicTime(
1189 static_cast<double>(var_to_constraint_ids_[var].size()) * 1e-9);
1190 for (const int id : var_to_constraint_ids_[var]) {
1191 if (id == root_id) {
1192 // TODO(user): Check previous slack vs var coeff?
1193 // TODO(user): Make sure there are none or detect cycle not going back
1194 // to the root.
1195 CHECK(!disassemble_branch_.empty());
1196
1197 // This is a corner case in which there is actually no cycle.
1198 const auto [test_id, root_var, var_increase] = disassemble_branch_[0];
1199 CHECK_EQ(test_id, root_id);
1200 CHECK_NE(var, root_var);
1201 if (var == NegationOf(root_var)) continue;
1202
1203 // Simple case, we have a cycle var -> root_var -> ... -> var where
1204 // all coefficient are non-increasing.
1205 const auto [var_coeff, root_coeff] =
1206 GetCycleCoefficients(id, var, root_var);
1207 if (CapProdI(var_increase, var_coeff) >= root_coeff) {
1208 ++num_cycles_;
1209 return ReportConflictingCycle();
1210 }
1211
1212 // We don't want to continue the search from root_id.
1213 // TODO(user): We could still try the simple reason, it might be a
1214 // conflict.
1215 ++num_failed_cycles_;
1216 continue;
1217 }
1218
1219 if (id_to_count[id] == 0) continue; // Didn't push or was desassembled.
1220
1221 // The constraint pushed some variable. Identify which ones will be pushed
1222 // further. Disassemble the whole info since we are about to propagate
1223 // this constraint again. Any pushed variable must be before the rev_size.
1224 const ConstraintInfo& info = infos_[id];
1225 const auto coeffs = GetCoeffs(info);
1226 const auto vars = GetVariables(info);
1227 IntegerValue var_coeff(0);
1228 disassemble_candidates_.clear();
1229 ++num_explored_in_disassemble_;
1230 time_limit_->AdvanceDeterministicTime(static_cast<double>(info.rev_size) *
1231 1e-9);
1232 for (int i = 0; i < info.rev_size; ++i) {
1233 if (vars[i] == var) {
1234 var_coeff = coeffs[i];
1235 continue;
1236 }
1237 const IntegerVariable next_var = NegationOf(vars[i]);
1238 if (propagated_by_[next_var] == id) {
1239 disassemble_candidates_.push_back({next_var, coeffs[i]});
1240
1241 // We will propagate var again later, so clear all this for now.
1242 propagated_by_[next_var] = -1;
1243 id_to_count[id]--;
1244 }
1245 }
1246
1247 for (const auto [next_var, next_var_coeff] : disassemble_candidates_) {
1248 // If var was pushed by increase, next_var is pushed by
1249 // (var_coeff * increase) / next_var_coeff.
1250 //
1251 // Note that it is okay to underevalute the increase in case of
1252 // overflow.
1253 const IntegerValue next_increase =
1254 FloorRatio(CapProdI(var_coeff, increase), next_var_coeff);
1255 if (next_increase > 0) {
1256 disassemble_queue_.push_back({id, next_var, next_increase});
1257
1258 // We know this will push later, so we register hit with a sentinel
1259 // value so that it do not block any earlier propagation. Hopefully,
1260 // adding this "dependency" should help find a better propagation
1261 // order.
1262 order_.Register(id, next_var, kMinIntegerValue);
1263 }
1264 }
1265 }
1266 }
1267
1268 return true;
1269}
1270
1271void LinearPropagator::AddToQueueIfNeeded(int id) {
1272 DCHECK_LT(id, in_queue_.size());
1273 DCHECK_LT(id, infos_.size());
1274
1275 if (in_queue_[id]) return;
1276 in_queue_.Set(id);
1277 propagation_queue_.push_back(id);
1278}
1279
1280void LinearPropagator::ClearPropagatedBy() {
1281 // To be sparse, we use the fact that each node with a parent must be in
1282 // modified_vars_.
1283 for (const IntegerVariable var :
1284 propagated_by_was_set_.PositionsSetAtLeastOnce()) {
1285 int& id = propagated_by_[var];
1286 if (id != -1) --id_to_propagation_count_[id];
1287 propagated_by_[var] = -1;
1288 }
1289 propagated_by_was_set_.ClearAndResize(propagated_by_was_set_.size());
1290 DCHECK(std::all_of(propagated_by_.begin(), propagated_by_.end(),
1291 [](int id) { return id == -1; }));
1292 DCHECK(std::all_of(id_to_propagation_count_.begin(),
1293 id_to_propagation_count_.end(),
1294 [](int count) { return count == 0; }));
1295}
1296
1297} // namespace sat
1298} // namespace operations_research
IntegerValue size
IndexType size() const
Returns how many bits this Bitset64 can hold.
Definition bitset.h:468
void Clear(IndexType i)
Sets the bit at position i to 0.
Definition bitset.h:510
void resize(int size)
Resizes the Bitset64 to the given number of bits. New bits are sets to 0.
Definition bitset.h:478
void Set(IndexType i)
Sets the bit at position i to 1.
Definition bitset.h:548
void SaveStateWithStamp(T *object, int64_t *stamp)
Definition rev.h:72
void SaveState(T *object)
Definition rev.h:62
void ClearAndResize(IntegerType size)
Definition bitset.h:839
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition bitset.h:878
void Set(IntegerType index)
Definition bitset.h:864
IntegerType size() const
Definition bitset.h:830
void Resize(IntegerType size)
Definition bitset.h:850
void AdvanceDeterministicTime(double deterministic_duration)
Definition time_limit.h:183
void UpdateBound(IntegerVariable var, IntegerValue lb)
bool VarShouldBePushedById(IntegerVariable var, int id)
void Register(int id, IntegerVariable var, IntegerValue lb)
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
EnforcementId Register(absl::Span< const Literal > enforcement, std::function< void(EnforcementId, EnforcementStatus)> callback=nullptr)
absl::Span< const Literal > GetEnforcementLiterals(EnforcementId id) const
Returns the enforcement literals of the given id.
EnforcementStatus DebugStatus(EnforcementId id)
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition integer.h:1852
void CallOnNextPropagate(int id)
Add the given propagator to its queue.
Definition integer.cc:2108
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
Definition integer.h:1717
int FindTrailIndexOfVarBefore(IntegerVariable var, int threshold) const
Definition integer.cc:946
bool VariableLowerBoundIsFromLevelZero(IntegerVariable var) const
Returns true if the variable lower bound is still the one from level zero.
Definition integer.h:1093
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
Definition integer.h:1765
void RelaxLinearReason(IntegerValue slack, absl::Span< const IntegerValue > coeffs, std::vector< IntegerLiteral > *reason) const
Definition integer.cc:1025
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1721
IntegerLiteral LowerBoundAsLiteral(IntegerVariable i) const
Definition integer.h:1760
bool ReportConflict(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.h:1076
ABSL_MUST_USE_RESULT bool EnqueueWithLazyReason(IntegerLiteral i_lit, int id, IntegerValue propagation_slack, LazyReasonInterface *explainer)
Lazy reason API.
Definition integer.h:1020
IntegerVariable NumIntegerVariables() const
Definition integer.h:806
const IntegerValue * LowerBoundsData() const
Definition integer.h:864
void EnqueueLiteral(Literal literal, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition integer.cc:1392
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.
void Explain(int id, IntegerValue propagation_slack, IntegerVariable var_to_explain, int trail_index, std::vector< Literal > *literals_reason, std::vector< int > *trail_indices_reason) final
For LazyReasonInterface.
void PushConditionalRelation(absl::Span< const Literal > enforcements, IntegerVariable a, IntegerVariable b, IntegerValue rhs)
Base class for all the SAT constraints.
Definition sat_base.h:533
bool PropagationIsDone(const Trail &trail) const
Returns true iff all the trail was inspected by this propagator.
Definition sat_base.h:597
void AddPropagator(SatPropagator *propagator)
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:463
const VariablesAssignment & Assignment() const
Definition sat_base.h:462
bool LiteralIsAssigned(Literal literal) const
Definition sat_base.h:191
bool LiteralIsFalse(Literal literal) const
Definition sat_base.h:185
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:188
void push_back(const value_type &val)
void resize(size_type new_size)
int64_t a
Definition table.cc:44
int64_t value
IntVar * var
absl::Status status
Definition g_gurobi.cc:44
double upper_bound
GRBmodel * model
MPCallback * callback
int literal
int index
const bool DEBUG_MODE
Definition macros.h:24
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition integer.h:94
const LiteralIndex kNoLiteralIndex(-1)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Returns the vector of the negated variables.
Definition integer.cc:51
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition integer.h:193
std::ostream & operator<<(std::ostream &os, const BoolVar &var)
Definition cp_model.cc:89
@ 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
IntegerValue CapProdI(IntegerValue a, IntegerValue b)
Overflows and saturated arithmetic.
Definition integer.h:105
In SWIG mode, we don't want anything besides these top-level includes.
std::vector< double > lower_bounds
Definition lp_utils.cc:746
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition integer.h:1673