Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
all_different.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 <algorithm>
17#include <functional>
18#include <utility>
19#include <vector>
20
21#include "absl/algorithm/container.h"
22#include "absl/container/btree_map.h"
23#include "absl/container/flat_hash_map.h"
24#include "absl/log/check.h"
25#include "absl/types/span.h"
29#include "ortools/sat/integer.h"
31#include "ortools/sat/model.h"
34#include "ortools/util/sort.h"
36
37namespace operations_research {
38namespace sat {
39
40std::function<void(Model*)> AllDifferentBinary(
41 absl::Span<const IntegerVariable> vars) {
42 return [=, vars = std::vector<IntegerVariable>(vars.begin(), vars.end())](
43 Model* model) {
44 // Fully encode all the given variables and construct a mapping value ->
45 // List of literal each indicating that a given variable takes this value.
46 //
47 // Note that we use a map to always add the constraints in the same order.
48 absl::btree_map<IntegerValue, std::vector<Literal>> value_to_literals;
49 IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
50 for (const IntegerVariable var : vars) {
51 model->Add(FullyEncodeVariable(var));
52 for (const auto& entry : encoder->FullDomainEncoding(var)) {
53 value_to_literals[entry.value].push_back(entry.literal);
54 }
55 }
56
57 // Add an at most one constraint for each value.
58 for (const auto& entry : value_to_literals) {
59 if (entry.second.size() > 1) {
60 model->Add(AtMostOneConstraint(entry.second));
61 }
62 }
63
64 // If the number of values is equal to the number of variables, we have
65 // a permutation. We can add a bool_or for each literals attached to a
66 // value.
67 if (value_to_literals.size() == vars.size()) {
68 for (const auto& entry : value_to_literals) {
69 model->Add(ClauseConstraint(entry.second));
70 }
71 }
72 };
73}
74
75std::function<void(Model*)> AllDifferentOnBounds(
76 absl::Span<const Literal> enforcement_literals,
77 absl::Span<const AffineExpression> expressions) {
78 return [=, expressions = std::vector<AffineExpression>(
79 expressions.begin(), expressions.end())](Model* model) {
80 if (expressions.empty()) return;
81 model->TakeOwnership(new AllDifferentBoundsPropagator(enforcement_literals,
82 expressions, model));
83 };
84}
85
86std::function<void(Model*)> AllDifferentOnBounds(
87 absl::Span<const IntegerVariable> vars) {
88 return [=, vars = std::vector<IntegerVariable>(vars.begin(), vars.end())](
89 Model* model) {
90 if (vars.empty()) return;
91 std::vector<AffineExpression> expressions;
92 expressions.reserve(vars.size());
93 for (const IntegerVariable var : vars) {
94 expressions.push_back(AffineExpression(var));
95 }
96 model->TakeOwnership(new AllDifferentBoundsPropagator(
97 /*enforcement_literals=*/{}, expressions, model));
98 };
99}
100
101std::function<void(Model*)> AllDifferentAC(
102 absl::Span<const IntegerVariable> variables) {
103 return [variables](Model* model) {
104 if (variables.size() < 3) return;
105
106 AllDifferentConstraint* constraint =
107 new AllDifferentConstraint(variables, model);
108 constraint->RegisterWith(model->GetOrCreate<GenericLiteralWatcher>());
109 model->TakeOwnership(constraint);
110 };
111}
112
114 absl::Span<const IntegerVariable> variables, Model* model)
115 : num_variables_(variables.size()),
116 trail_(model->GetOrCreate<Trail>()),
117 integer_trail_(model->GetOrCreate<IntegerTrail>()) {
118 // Initialize literals cache.
119 // Note that remap all values appearing here with a dense_index.
120 num_values_ = 0;
121 absl::flat_hash_map<IntegerValue, int> dense_indexing;
122 variable_to_possible_values_.resize(num_variables_);
123 auto* encoder = model->GetOrCreate<IntegerEncoder>();
124 for (int x = 0; x < num_variables_; x++) {
125 const IntegerValue lb = integer_trail_->LowerBound(variables[x]);
126 const IntegerValue ub = integer_trail_->UpperBound(variables[x]);
127
128 // FullyEncode does not like 1-value domains, handle this case first.
129 // TODO(user): Prune now, ignore these variables during solving.
130 if (lb == ub) {
131 const auto [it, inserted] = dense_indexing.insert({lb, num_values_});
132 if (inserted) ++num_values_;
133
134 variable_to_possible_values_[x].push_back(
135 {it->second, encoder->GetTrueLiteral()});
136 continue;
137 }
138
139 // Force full encoding if not already done.
140 if (!encoder->VariableIsFullyEncoded(variables[x])) {
141 encoder->FullyEncodeVariable(variables[x]);
142 }
143
144 // Fill cache with literals, default value is kFalseLiteralIndex.
145 for (const auto [value, lit] : encoder->FullDomainEncoding(variables[x])) {
146 const auto [it, inserted] = dense_indexing.insert({value, num_values_});
147 if (inserted) ++num_values_;
148
149 variable_to_possible_values_[x].push_back({it->second, lit});
150 }
151
152 // Not sure it is needed, but lets sort.
153 absl::c_sort(
154 variable_to_possible_values_[x],
155 [](const std::pair<int, Literal>& a, const std::pair<int, Literal>& b) {
156 return a.first < b.first;
157 });
158 }
159
160 variable_to_value_.assign(num_variables_, -1);
161 visiting_.resize(num_variables_);
162 variable_visited_from_.resize(num_variables_);
163 component_number_.resize(num_variables_ + num_values_ + 1);
164}
165
167 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
168 absl::Span<const Literal> literals, Model* model)
169 : num_variables_(num_nodes),
170 trail_(model->GetOrCreate<Trail>()),
171 integer_trail_(model->GetOrCreate<IntegerTrail>()) {
172 num_values_ = num_nodes;
173
174 // We assume everything is already dense.
175 const int num_arcs = tails.size();
176 variable_to_possible_values_.resize(num_variables_);
177 for (int a = 0; a < num_arcs; ++a) {
178 variable_to_possible_values_[tails[a]].push_back({heads[a], literals[a]});
179 }
180
181 variable_to_value_.assign(num_variables_, -1);
182 visiting_.resize(num_variables_);
183 variable_visited_from_.resize(num_variables_);
184 component_number_.resize(num_variables_ + num_values_ + 1);
185}
186
188 const int id = watcher->Register(this);
189 watcher->SetPropagatorPriority(id, 2);
190 for (int x = 0; x < num_variables_; x++) {
191 for (const auto [_, lit] : variable_to_possible_values_[x]) {
192 // Watch only unbound literals.
193 if (!trail_->Assignment().LiteralIsAssigned(lit)) {
194 watcher->WatchLiteral(lit, id);
195 watcher->WatchLiteral(lit.Negated(), id);
196 }
197 }
198 }
199}
200
201bool AllDifferentConstraint::MakeAugmentingPath(int start) {
202 // Do a BFS and use visiting_ as a queue, with num_visited pointing
203 // at its begin() and num_to_visit its end().
204 // To switch to the augmenting path once a nonmatched value was found,
205 // we remember the BFS tree in variable_visited_from_.
206 int num_to_visit = 0;
207 int num_visited = 0;
208 // Enqueue start.
209 visiting_[num_to_visit++] = start;
210 variable_visited_[start] = true;
211 variable_visited_from_[start] = -1;
212
213 while (num_visited < num_to_visit) {
214 // Dequeue node to visit.
215 const int node = visiting_[num_visited++];
216
217 for (const int value : successor_[node]) {
218 if (value_visited_[value]) continue;
219 value_visited_[value] = true;
220 if (value_to_variable_[value] == -1) {
221 // value is not matched: change path from node to start, and return.
222 int path_node = node;
223 int path_value = value;
224 while (path_node != -1) {
225 int old_value = variable_to_value_[path_node];
226 variable_to_value_[path_node] = path_value;
227 value_to_variable_[path_value] = path_node;
228 path_node = variable_visited_from_[path_node];
229 path_value = old_value;
230 }
231 return true;
232 } else {
233 // Enqueue node matched to value.
234 const int next_node = value_to_variable_[value];
235 variable_visited_[next_node] = true;
236 visiting_[num_to_visit++] = next_node;
237 variable_visited_from_[next_node] = node;
238 }
239 }
240 }
241 return false;
242}
243
244// The algorithm copies the solver state to successor_, which is used to compute
245// a matching. If all variables can be matched, it generates the residual graph
246// in separate vectors, computes its SCCs, and filters variable -> value if
247// variable is not in the same SCC as value.
248// Explanations for failure and filtering are fine-grained:
249// failure is explained by a Hall set, i.e. dom(variables) \subseteq {values},
250// with |variables| < |values|; filtering is explained by the Hall set that
251// would happen if the variable was assigned to the value.
252//
253// TODO(user): If needed, there are several ways performance could be
254// improved.
255// If copying the variable state is too costly, it could be maintained instead.
256// If the propagator has too many fruitless calls (without failing/pruning),
257// we can remember the O(n) arcs used in the matching and the SCC decomposition,
258// and guard calls to Propagate() if these arcs are still valid.
260 // Copy variable state to graph state.
261 prev_matching_ = variable_to_value_;
262 value_to_variable_.assign(num_values_, -1);
263 variable_to_value_.assign(num_variables_, -1);
264 successor_.clear();
265 const auto assignment = AssignmentView(trail_->Assignment());
266 for (int x = 0; x < num_variables_; x++) {
267 successor_.Add({});
268 for (const auto [value, lit] : variable_to_possible_values_[x]) {
269 if (assignment.LiteralIsFalse(lit)) continue;
270
271 // Forward-checking should propagate x != value.
272 successor_.AppendToLastVector(value);
273
274 // Seed with previous matching.
275 if (prev_matching_[x] == value && value_to_variable_[value] == -1) {
276 variable_to_value_[x] = prev_matching_[x];
277 value_to_variable_[prev_matching_[x]] = x;
278 }
279 }
280 if (successor_[x].size() == 1) {
281 const int value = successor_[x][0];
282 if (value_to_variable_[value] == -1) {
283 value_to_variable_[value] = x;
284 variable_to_value_[x] = value;
285 }
286 }
287 }
288
289 // Compute max matching.
290 int x = 0;
291 for (; x < num_variables_; x++) {
292 if (variable_to_value_[x] == -1) {
293 value_visited_.assign(num_values_, false);
294 variable_visited_.assign(num_variables_, false);
295 MakeAugmentingPath(x);
296 }
297 if (variable_to_value_[x] == -1) break; // No augmenting path exists.
298 }
299
300 // Fail if covering variables impossible.
301 // Explain with the forbidden parts of the graph that prevent
302 // MakeAugmentingPath from increasing the matching size.
303 if (x < num_variables_) {
304 // For now explain all forbidden arcs.
305 std::vector<Literal>* conflict = trail_->MutableConflict();
306 conflict->clear();
307 for (int y = 0; y < num_variables_; y++) {
308 if (!variable_visited_[y]) continue;
309 for (const auto [value, lit] : variable_to_possible_values_[y]) {
310 if (!value_visited_[value]) {
311 DCHECK(assignment.LiteralIsFalse(lit));
312 conflict->push_back(lit);
313 }
314 }
315 }
316 return false;
317 }
318
319 // The current matching is a valid solution, now try to filter values.
320 // Build residual graph, compute its SCCs.
321 residual_graph_successors_.clear();
322 for (int x = 0; x < num_variables_; x++) {
323 residual_graph_successors_.Add({});
324 for (const int succ : successor_[x]) {
325 if (succ != variable_to_value_[x]) {
326 residual_graph_successors_.AppendToLastVector(num_variables_ + succ);
327 }
328 }
329 }
330
331 const int dummy_node = num_variables_ + num_values_;
332 const bool need_dummy = num_variables_ < num_values_;
333 for (int value = 0; value < num_values_; value++) {
334 residual_graph_successors_.Add({});
335 if (value_to_variable_[value] != -1) {
336 residual_graph_successors_.AppendToLastVector(value_to_variable_[value]);
337 } else if (need_dummy) {
338 residual_graph_successors_.AppendToLastVector(dummy_node);
339 }
340 }
341 if (need_dummy) {
342 DCHECK_EQ(residual_graph_successors_.size(), dummy_node);
343 residual_graph_successors_.Add({});
344 for (int x = 0; x < num_variables_; x++) {
345 residual_graph_successors_.AppendToLastVector(x);
346 }
347 }
348
349 // Compute SCCs, make node -> component map.
350 struct SccOutput {
351 explicit SccOutput(std::vector<int>* c) : components(c) {}
352 void emplace_back(int const* b, int const* e) {
353 for (int const* it = b; it < e; ++it) {
354 (*components)[*it] = num_components;
355 }
356 ++num_components;
357 }
358 int num_components = 0;
359 std::vector<int>* components;
360 };
361 SccOutput scc_output(&component_number_);
363 static_cast<int>(residual_graph_successors_.size()),
364 residual_graph_successors_, &scc_output);
365
366 // Remove arcs var -> val where SCC(var) -/->* SCC(val).
367 for (int x = 0; x < num_variables_; x++) {
368 if (successor_[x].size() == 1) continue;
369 for (const auto [value, x_lit] : variable_to_possible_values_[x]) {
370 if (assignment.LiteralIsFalse(x_lit)) continue;
371
372 const int value_node = value + num_variables_;
373 DCHECK_LT(value_node, component_number_.size());
374 if (variable_to_value_[x] != value &&
375 component_number_[x] != component_number_[value_node]) {
376 // We can deduce that x != value. To explain, force x == value,
377 // then find another assignment for the variable matched to
378 // value. It will fail: explaining why is the same as
379 // explaining failure as above, and it is an explanation of x != value.
380 value_visited_.assign(num_values_, false);
381 variable_visited_.assign(num_variables_, false);
382 // Undo x -> old_value and old_variable -> value.
383 const int old_variable = value_to_variable_[value];
384 DCHECK_GE(old_variable, 0);
385 DCHECK_LT(old_variable, num_variables_);
386 variable_to_value_[old_variable] = -1;
387 const int old_value = variable_to_value_[x];
388 value_to_variable_[old_value] = -1;
389 variable_to_value_[x] = value;
390 value_to_variable_[value] = x;
391
392 value_visited_[value] = true;
393 MakeAugmentingPath(old_variable);
394 DCHECK_EQ(variable_to_value_[old_variable], -1); // No reassignment.
395
396 std::vector<Literal>* reason = trail_->GetEmptyVectorToStoreReason();
397 for (int y = 0; y < num_variables_; y++) {
398 if (!variable_visited_[y]) continue;
399 for (const auto [value, y_lit] : variable_to_possible_values_[y]) {
400 if (!value_visited_[value]) {
401 DCHECK(assignment.LiteralIsFalse(y_lit));
402 reason->push_back(y_lit);
403 }
404 }
405 }
406
407 return trail_->EnqueueWithStoredReason(kNoClauseId, x_lit.Negated());
408 }
409 }
410 }
411
412 return true;
413}
414
416 absl::Span<const Literal> enforcement_literals,
417 absl::Span<const AffineExpression> expressions, Model* model)
418 : integer_trail_(*model->GetOrCreate<IntegerTrail>()),
419 enforcement_helper_(*model->GetOrCreate<EnforcementHelper>()) {
420 CHECK(!expressions.empty());
421
422 // We need +2 for sentinels.
423 const int capacity = expressions.size() + 2;
424 index_to_start_index_.resize(capacity);
425 index_to_end_index_.resize(capacity);
426 index_is_present_.Resize(capacity);
427 index_to_expr_.resize(capacity, kNoIntegerVariable);
428
429 for (int i = 0; i < expressions.size(); ++i) {
430 bounds_.push_back({expressions[i]});
431 negated_bounds_.push_back({expressions[i].Negated()});
432 }
433
435 enforcement_id_ = enforcement_helper_.Register(enforcement_literals, watcher,
436 RegisterWith(watcher));
437}
438
440 const EnforcementStatus status = enforcement_helper_.Status(enforcement_id_);
441 if (status != EnforcementStatus::IS_ENFORCED &&
443 return true;
444 }
445 if (!PropagateLowerBounds()) return false;
446
447 // Note that it is not required to swap back bounds_ and negated_bounds_.
448 // TODO(user): investigate the impact.
449 std::swap(bounds_, negated_bounds_);
450 const bool result = PropagateLowerBounds();
451 std::swap(bounds_, negated_bounds_);
452 return result;
453}
454
455void AllDifferentBoundsPropagator::FillHallReason(IntegerValue hall_lb,
456 IntegerValue hall_ub) {
457 integer_reason_.clear();
458 const int limit = GetIndex(hall_ub);
459 for (int i = GetIndex(hall_lb); i <= limit; ++i) {
460 const AffineExpression expr = index_to_expr_[i];
461 integer_reason_.push_back(expr.GreaterOrEqual(hall_lb));
462 integer_reason_.push_back(expr.LowerOrEqual(hall_ub));
463 }
464}
465
466int AllDifferentBoundsPropagator::FindStartIndexAndCompressPath(int index) {
467 // First, walk the pointer until we find one pointing to itself.
468 int start_index = index;
469 while (true) {
470 const int next = index_to_start_index_[start_index];
471 if (start_index == next) break;
472 start_index = next;
473 }
474
475 // Second, redo the same thing and make everyone point to the representative.
476 while (true) {
477 const int next = index_to_start_index_[index];
478 if (start_index == next) break;
479 index_to_start_index_[index] = start_index;
480 index = next;
481 }
482 return start_index;
483}
484
485bool AllDifferentBoundsPropagator::PropagateLowerBounds() {
486 // Start by filling the cached bounds and sorting by increasing lb.
487 for (CachedBounds& entry : bounds_) {
488 entry.lb = integer_trail_.LowerBound(entry.expr);
489 entry.ub = integer_trail_.UpperBound(entry.expr);
490 }
491 IncrementalSort(bounds_.begin(), bounds_.end(),
492 [](CachedBounds a, CachedBounds b) { return a.lb < b.lb; });
493
494 // We will split the affine epressions in vars sorted by lb in contiguous
495 // subset with index of the form [start, start + num_in_window).
496 int start = 0;
497 int num_in_window = 1;
498
499 // Minimum lower bound in the current window.
500 IntegerValue min_lb = bounds_.front().lb;
501
502 const int size = bounds_.size();
503 for (int i = 1; i < size; ++i) {
504 const IntegerValue lb = bounds_[i].lb;
505
506 // If the lower bounds of all the other variables is greater, then it can
507 // never fall into a potential hall interval formed by the variable in the
508 // current window, so we can split the problem into independent parts.
509 if (lb <= min_lb + IntegerValue(num_in_window - 1)) {
510 ++num_in_window;
511 continue;
512 }
513
514 // Process the current window.
515 if (num_in_window > 1) {
516 absl::Span<CachedBounds> window(&bounds_[start], num_in_window);
517 if (!PropagateLowerBoundsInternal(min_lb, window)) {
518 return false;
519 }
520 }
521
522 // Start of the next window.
523 start = i;
524 num_in_window = 1;
525 min_lb = lb;
526 }
527
528 // Take care of the last window.
529 if (num_in_window > 1) {
530 absl::Span<CachedBounds> window(&bounds_[start], num_in_window);
531 return PropagateLowerBoundsInternal(min_lb, window);
532 }
533
534 return true;
535}
536
537bool AllDifferentBoundsPropagator::PropagateLowerBoundsInternal(
538 IntegerValue min_lb, absl::Span<CachedBounds> bounds) {
539 hall_starts_.clear();
540 hall_ends_.clear();
541
542 // All cached lb in bounds will be in [min_lb, min_lb + bounds_.size()).
543 // Make sure we change our base_ so that GetIndex() fit in our buffers.
544 base_ = min_lb - IntegerValue(1);
545
546 index_is_present_.ResetAllToFalse();
547
548 // Sort bounds by increasing ub.
549 std::sort(bounds.begin(), bounds.end(),
550 [](CachedBounds a, CachedBounds b) { return a.ub < b.ub; });
551 for (const CachedBounds entry : bounds) {
552 const AffineExpression expr = entry.expr;
553
554 // Note that it is important to use the cache to make sure GetIndex() is
555 // not out of bound in case integer_trail_->LowerBound() changed when we
556 // pushed something.
557 const IntegerValue lb = entry.lb;
558 const int lb_index = GetIndex(lb);
559 const bool value_is_covered = index_is_present_[lb_index];
560
561 // Check if lb is in an Hall interval, and push it if this is the case.
562 if (value_is_covered) {
563 const int hall_index =
564 std::lower_bound(hall_ends_.begin(), hall_ends_.end(), lb) -
565 hall_ends_.begin();
566 if (hall_index < hall_ends_.size() && hall_starts_[hall_index] <= lb) {
567 const IntegerValue hs = hall_starts_[hall_index];
568 const IntegerValue he = hall_ends_[hall_index];
569 FillHallReason(hs, he);
570 integer_reason_.push_back(expr.GreaterOrEqual(hs));
571 if (enforcement_helper_.Status(enforcement_id_) ==
573 if (!enforcement_helper_.SafeEnqueue(enforcement_id_,
574 expr.GreaterOrEqual(he + 1),
575 integer_reason_)) {
576 return false;
577 }
578 } else if (he >= entry.ub) {
579 integer_reason_.push_back(expr.LowerOrEqual(entry.ub));
580 return enforcement_helper_.PropagateWhenFalse(
581 enforcement_id_, /*literal_reason=*/{}, integer_reason_);
582 }
583 }
584 }
585
586 // Update our internal representation of the non-consecutive intervals.
587 //
588 // If lb is not used, we add a node there, otherwise we add it to the
589 // right of the interval that contains lb. In both cases, if there is an
590 // interval to the left (resp. right) we merge them.
591 int new_index = lb_index;
592 int start_index = lb_index;
593 int end_index = lb_index;
594 if (value_is_covered) {
595 start_index = FindStartIndexAndCompressPath(new_index);
596 new_index = index_to_end_index_[start_index] + 1;
597 end_index = new_index;
598 } else {
599 if (index_is_present_[new_index - 1]) {
600 start_index = FindStartIndexAndCompressPath(new_index - 1);
601 }
602 }
603 if (index_is_present_[new_index + 1]) {
604 end_index = index_to_end_index_[new_index + 1];
605 index_to_start_index_[new_index + 1] = start_index;
606 }
607
608 // Update the end of the representative.
609 index_to_end_index_[start_index] = end_index;
610
611 // This is the only place where we "add" a new node.
612 {
613 index_to_start_index_[new_index] = start_index;
614 index_to_expr_[new_index] = expr;
615 index_is_present_.Set(new_index);
616 }
617
618 // In most situation, we cannot have a conflict now, because it should have
619 // been detected before by pushing an interval lower bound past its upper
620 // bound. However, it is possible that when we push one bound, other bounds
621 // change. So if the upper bound is smaller than the current interval end,
622 // we abort so that the conflict reason will be better on the next call to
623 // the propagator.
624 const IntegerValue end = GetValue(end_index);
625 if (end > integer_trail_.UpperBound(expr)) return true;
626
627 // If we have a new Hall interval, add it to the set. Note that it will
628 // always be last, and if it overlaps some previous Hall intervals, it
629 // always overlaps them fully.
630 //
631 // Note: It is okay to not use entry.ub here if we want to fetch the last
632 // value, but in practice it shouldn't really change when we push a
633 // lower_bound and it is faster to use the cached entry.
634 if (end == entry.ub) {
635 const IntegerValue start = GetValue(start_index);
636 while (!hall_starts_.empty() && start <= hall_starts_.back()) {
637 hall_starts_.pop_back();
638 hall_ends_.pop_back();
639 }
640 DCHECK(hall_ends_.empty() || hall_ends_.back() < start);
641 hall_starts_.push_back(start);
642 hall_ends_.push_back(end);
643 }
644 }
645 return true;
646}
647
648int AllDifferentBoundsPropagator::RegisterWith(GenericLiteralWatcher* watcher) {
649 const int id = watcher->Register(this);
650 for (const CachedBounds& entry : bounds_) {
651 watcher->WatchAffineExpression(entry.expr, id);
652 }
653 watcher->NotifyThatPropagatorMayNotReachFixedPointInOnePass(id);
654 return id;
655}
656
657} // namespace sat
658} // namespace operations_research
AllDifferentBoundsPropagator(absl::Span< const Literal > enforcement_literals, absl::Span< const AffineExpression > expressions, Model *model)
AllDifferentConstraint(absl::Span< const IntegerVariable > variables, Model *model)
void RegisterWith(GenericLiteralWatcher *watcher)
void WatchLiteral(Literal l, int id, int watch_index=-1)
Definition integer.h:1708
void SetPropagatorPriority(int id, int priority)
Definition integer.cc:2700
int Register(PropagatorInterface *propagator)
Definition integer.cc:2674
const std::vector< ValueLiteralPair > & FullDomainEncoding(IntegerVariable var) const
Definition integer.cc:146
std::function< std::vector< ValueLiteralPair >(Model *)> FullyEncodeVariable(IntegerVariable var)
Definition integer.h:1939
std::function< void(Model *)> ClauseConstraint(absl::Span< const Literal > literals)
Definition sat_solver.h:994
const IntegerVariable kNoIntegerVariable(-1)
std::function< void(Model *)> AllDifferentBinary(absl::Span< const IntegerVariable > vars)
constexpr ClauseId kNoClauseId(0)
std::function< void(Model *)> AtMostOneConstraint(absl::Span< const Literal > literals)
Definition sat_solver.h:979
std::function< void(Model *)> AllDifferentOnBounds(absl::Span< const Literal > enforcement_literals, absl::Span< const AffineExpression > expressions)
std::function< void(Model *)> AllDifferentAC(absl::Span< const IntegerVariable > variables)
OR-Tools root namespace.
ClosedInterval::Iterator end(ClosedInterval interval)
void IncrementalSort(int max_comparisons, Iterator begin, Iterator end, Compare comp=Compare{}, bool is_stable=false)
Definition sort.h:46
void FindStronglyConnectedComponents(NodeIndex num_nodes, const Graph &graph, SccOutput *components)
IntegerLiteral GreaterOrEqual(IntegerValue bound) const
IntegerLiteral LowerOrEqual(IntegerValue bound) const