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