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