Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
precedences.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 <limits>
20#include <string>
21#include <tuple>
22#include <utility>
23#include <variant>
24#include <vector>
25
26#include "absl/algorithm/container.h"
27#include "absl/container/btree_set.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/types/span.h"
39#include "ortools/sat/clause.h"
42#include "ortools/sat/integer.h"
44#include "ortools/sat/model.h"
48#include "ortools/sat/util.h"
52
53namespace operations_research {
54namespace sat {
55
56LinearExpression2Index Linear2Indices::AddOrGet(
57 LinearExpression2 original_expr) {
58 LinearExpression2 expr = original_expr;
59 DCHECK(expr.IsCanonicalized());
60 DCHECK_EQ(expr.DivideByGcd(), 1);
61 DCHECK_NE(expr.coeffs[0], 0);
62 DCHECK_NE(expr.coeffs[1], 0);
63 const bool negated = expr.NegateForCanonicalization();
64 auto [it, inserted] = expr_to_index_.insert({expr, exprs_.size()});
65 if (inserted) {
66 CHECK_LT(2 * exprs_.size() + 1,
67 std::numeric_limits<LinearExpression2Index>::max());
68 exprs_.push_back(expr);
69 }
70 const LinearExpression2Index result =
71 negated ? NegationOf(LinearExpression2Index(2 * it->second))
72 : LinearExpression2Index(2 * it->second);
73
74 if (!inserted) return result;
75
76 // Update our per-variable and per-pair lookup tables.
77 IntegerVariable var1 = PositiveVariable(expr.vars[0]);
78 IntegerVariable var2 = PositiveVariable(expr.vars[1]);
79 if (var1 > var2) std::swap(var1, var2);
80 var_pair_to_bounds_[{var1, var2}].push_back(result);
81 var_to_bounds_[var1].push_back(result);
82 var_to_bounds_[var2].push_back(result);
83
84 return result;
85}
86
88 DCHECK(expr.IsCanonicalized());
89 DCHECK_EQ(expr.DivideByGcd(), 1);
90 ++timestamp_;
91 for (const int id : propagator_ids_) {
92 watcher_->CallOnNextPropagate(id);
93 }
94 for (IntegerVariable var : expr.non_zero_vars()) {
95 var = PositiveVariable(var); // TODO(user): Be more precise?
96 if (var >= var_timestamp_.size()) {
97 var_timestamp_.resize(var + 1, 0);
98 }
99 var_timestamp_[var]++;
100 }
101}
102
103int64_t Linear2Watcher::VarTimestamp(IntegerVariable var) {
104 var = PositiveVariable(var);
105 return var < var_timestamp_.size() ? var_timestamp_[var] : 0;
106}
107
108bool RootLevelLinear2Bounds::AddUpperBound(LinearExpression2Index index,
109 IntegerValue ub) {
110 const LinearExpression2 expr = lin2_indices_->GetExpression(index);
111 const IntegerValue zero_level_ub = integer_trail_->LevelZeroUpperBound(expr);
112 if (ub >= zero_level_ub) {
113 return false;
114 }
115 if (best_upper_bounds_.size() <= index) {
116 best_upper_bounds_.resize(index.value() + 1, kMaxIntegerValue);
117 }
118 if (ub >= best_upper_bounds_[index]) {
119 return false;
120 }
121 best_upper_bounds_[index] = ub;
122
123 ++num_updates_;
124 linear2_watcher_->NotifyBoundChanged(expr);
125
126 // Simple relations.
127 //
128 // TODO(user): Remove them each time we go back to level zero and they become
129 // trivially true ?
130 if (IntTypeAbs(expr.coeffs[0]) == 1 && IntTypeAbs(expr.coeffs[1]) == 1) {
131 if (index >= in_coeff_one_lookup_.size()) {
132 in_coeff_one_lookup_.resize(index + 1, false);
133 }
134 if (!in_coeff_one_lookup_[index]) {
135 const IntegerVariable a =
136 expr.coeffs[0] > 0 ? expr.vars[0] : NegationOf(expr.vars[0]);
137 const IntegerVariable b =
138 expr.coeffs[1] > 0 ? expr.vars[1] : NegationOf(expr.vars[1]);
139
140 coeff_one_var_lookup_.resize(integer_trail_->NumIntegerVariables());
141 in_coeff_one_lookup_[index] = true;
142 coeff_one_var_lookup_[a].push_back({b, index});
143 coeff_one_var_lookup_[b].push_back({a, index});
144 }
145 }
146
147 // Share.
148 //
149 // TODO(user): It seems we could change the canonicalization to only use
150 // positive variable? that would simplify a bit the code here and not make it
151 // worse elsewhere?
152 if (shared_linear2_bounds_ != nullptr) {
153 const IntegerValue lb = -LevelZeroUpperBound(NegationOf(index));
154 const int proto_var0 =
155 cp_model_mapping_->GetProtoVariableFromIntegerVariable(
156 PositiveVariable(expr.vars[0]));
157 const int proto_var1 =
158 cp_model_mapping_->GetProtoVariableFromIntegerVariable(
159 PositiveVariable(expr.vars[1]));
160 if (proto_var0 >= 0 && proto_var1 >= 0) {
161 // This is also a relation between cp_model proto variable. Share it!
162 // Note that since expr is canonicalized, this one should too.
164 key.vars[0] = proto_var0;
165 key.coeffs[0] =
166 VariableIsPositive(expr.vars[0]) ? expr.coeffs[0] : -expr.coeffs[0];
167 key.vars[1] = proto_var1;
168 key.coeffs[1] =
169 VariableIsPositive(expr.vars[1]) ? expr.coeffs[1] : -expr.coeffs[1];
170 shared_linear2_bounds_->Add(shared_linear2_bounds_id_, key, lb, ub);
171 }
172 }
173 return true;
174}
175
176// TODO(user): If we add an indexing for "coeff * var" this is kind of
177// easy to generalize to affine relations, not just "simple one".
179 int work_limit) {
180 var = PositiveVariable(var);
181 if (var >= coeff_one_var_lookup_.size()) return 0;
182 if (NegationOf(var) >= coeff_one_var_lookup_.size()) return 0;
183
184 // Note that this never touches in_coeff_one_lookup_[var/NegationOf(var)],
185 // so it should be safe to iterate on it.
186 int work_done = 0;
187 for (const auto [a, a_index] : coeff_one_var_lookup_[var]) {
188 CHECK_NE(PositiveVariable(a), var);
189 const IntegerValue a_ub = best_upper_bounds_[a_index];
190 for (const auto [b, b_index] : coeff_one_var_lookup_[NegationOf(var)]) {
191 if (PositiveVariable(b) == PositiveVariable(a)) continue;
192 CHECK_NE(PositiveVariable(b), var);
193 if (++work_done > work_limit) return work_done;
194
195 const LinearExpression2 candidate{a, b, 1, 1};
196 AddUpperBound(candidate, a_ub + best_upper_bounds_[b_index]);
197 }
198 }
199 return work_done;
200}
201
203 if (!VLOG_IS_ON(1)) return;
204 std::vector<std::pair<std::string, int64_t>> stats;
205 stats.push_back({"RootLevelLinear2Bounds/num_updates", num_updates_});
206 shared_stats_->AddStats(stats);
207}
208
210 LinearExpression2 expr, IntegerValue lb, IntegerValue ub) const {
211 IntegerValue known_ub = integer_trail_->LevelZeroUpperBound(expr);
212 IntegerValue known_lb = integer_trail_->LevelZeroLowerBound(expr);
213
214 if (lb <= known_lb && ub >= known_ub) return RelationStatus::IS_TRUE;
215 if (lb > known_ub || ub < known_lb) return RelationStatus::IS_FALSE;
216
218 if (expr.coeffs[0] == 0) {
220 }
221 DCHECK_NE(expr.coeffs[1], 0);
222 const IntegerValue gcd = expr.DivideByGcd();
223 ub = FloorRatio(ub, gcd);
224 const LinearExpression2Index index = lin2_indices_->GetIndex(expr);
225
226 if (index == kNoLinearExpression2Index) {
228 }
229
230 known_ub = std::min(known_ub, GetUpperBoundNoTrail(index));
231 known_lb = std::max(known_lb, -GetUpperBoundNoTrail(NegationOf(index)));
232
233 if (lb <= known_lb && ub >= known_ub) return RelationStatus::IS_TRUE;
234 if (lb > known_ub || ub < known_lb) return RelationStatus::IS_FALSE;
235
237}
238
240 LinearExpression2Index index) const {
241 if (best_upper_bounds_.size() <= index) {
242 return kMaxIntegerValue;
243 }
244 return best_upper_bounds_[index];
245}
246
247std::vector<std::pair<LinearExpression2, IntegerValue>>
249 std::vector<std::pair<LinearExpression2, IntegerValue>> result;
250 for (LinearExpression2Index index = LinearExpression2Index{0};
251 index < best_upper_bounds_.size(); ++index) {
252 const IntegerValue ub = best_upper_bounds_[index];
253 if (ub == kMaxIntegerValue) continue;
254 const LinearExpression2 expr = lin2_indices_->GetExpression(index);
255 if (ub < integer_trail_->LevelZeroUpperBound(expr)) {
256 result.push_back({expr, ub});
257 }
258 }
259 std::sort(result.begin(), result.end());
260 return result;
261}
262
263std::vector<std::tuple<LinearExpression2, IntegerValue, IntegerValue>>
265 IntegerVariable var) const {
266 std::vector<std::tuple<LinearExpression2, IntegerValue, IntegerValue>> result;
267 for (const LinearExpression2Index index :
268 lin2_indices_->GetAllLinear2ContainingVariable(var)) {
269 const IntegerValue lb = -GetUpperBoundNoTrail(NegationOf(index));
270 const IntegerValue ub = GetUpperBoundNoTrail(index);
271 const LinearExpression2 expr = lin2_indices_->GetExpression(index);
272 const IntegerValue trail_lb = integer_trail_->LevelZeroLowerBound(expr);
273 const IntegerValue trail_ub = integer_trail_->LevelZeroUpperBound(expr);
274 if (lb <= trail_lb && ub >= trail_ub) continue;
275 LinearExpression2 explicit_vars_expr = expr;
276 if (explicit_vars_expr.vars[0] == NegationOf(var)) {
277 explicit_vars_expr.vars[0] = NegationOf(explicit_vars_expr.vars[0]);
278 explicit_vars_expr.coeffs[0] = -explicit_vars_expr.coeffs[0];
279 }
280 if (explicit_vars_expr.vars[1] == NegationOf(var)) {
281 explicit_vars_expr.vars[1] = NegationOf(explicit_vars_expr.vars[1]);
282 explicit_vars_expr.coeffs[1] = -explicit_vars_expr.coeffs[1];
283 }
284 if (explicit_vars_expr.vars[1] == var) {
285 std::swap(explicit_vars_expr.vars[0], explicit_vars_expr.vars[1]);
286 std::swap(explicit_vars_expr.coeffs[0], explicit_vars_expr.coeffs[1]);
287 }
288 DCHECK(explicit_vars_expr.vars[0] == var);
289 result.push_back(
290 {explicit_vars_expr, std::max(lb, trail_lb), std::min(ub, trail_ub)});
291 }
292 return result;
293}
294
295// Return a list of (lb <= expr <= ub), with expr.vars = {var1, var2}, where
296// at least one of the bounds is non-trivial and the potential other
297// non-trivial bound is tight.
298std::vector<std::tuple<LinearExpression2, IntegerValue, IntegerValue>>
300 IntegerVariable var1, IntegerVariable var2) const {
301 std::vector<std::tuple<LinearExpression2, IntegerValue, IntegerValue>> result;
302 for (const LinearExpression2Index index :
303 lin2_indices_->GetAllLinear2ContainingVariables(var1, var2)) {
304 const IntegerValue lb = -GetUpperBoundNoTrail(NegationOf(index));
305 const IntegerValue ub = GetUpperBoundNoTrail(index);
306 const LinearExpression2 expr = lin2_indices_->GetExpression(index);
307 const IntegerValue trail_lb = integer_trail_->LevelZeroLowerBound(expr);
308 const IntegerValue trail_ub = integer_trail_->LevelZeroUpperBound(expr);
309 if (lb <= trail_lb && ub >= trail_ub) continue;
310
311 LinearExpression2 explicit_vars_expr = expr;
312 if (explicit_vars_expr.vars[0] == NegationOf(var1) ||
313 explicit_vars_expr.vars[0] == NegationOf(var2)) {
314 explicit_vars_expr.vars[0] = NegationOf(explicit_vars_expr.vars[0]);
315 explicit_vars_expr.coeffs[0] = -explicit_vars_expr.coeffs[0];
316 }
317 if (explicit_vars_expr.vars[1] == NegationOf(var1) ||
318 explicit_vars_expr.vars[1] == NegationOf(var2)) {
319 explicit_vars_expr.vars[1] = NegationOf(explicit_vars_expr.vars[1]);
320 explicit_vars_expr.coeffs[1] = -explicit_vars_expr.coeffs[1];
321 }
322 if (explicit_vars_expr.vars[1] == var1) {
323 std::swap(explicit_vars_expr.vars[0], explicit_vars_expr.vars[1]);
324 std::swap(explicit_vars_expr.coeffs[0], explicit_vars_expr.coeffs[1]);
325 }
326 DCHECK(explicit_vars_expr.vars[0] == var1 &&
327 explicit_vars_expr.vars[1] == var2);
328 result.push_back(
329 {explicit_vars_expr, std::max(lb, trail_lb), std::min(ub, trail_ub)});
330 }
331 return result;
332}
333
334absl::Span<const std::pair<IntegerVariable, LinearExpression2Index>>
336 IntegerVariable var) const {
337 if (var >= coeff_one_var_lookup_.size()) return {};
338 return coeff_one_var_lookup_[var];
339}
340
342 if (!VLOG_IS_ON(1)) return;
343 std::vector<std::pair<std::string, int64_t>> stats;
344 stats.push_back({"EnforcedLinear2Bounds/num_conditional_relation_updates",
345 num_conditional_relation_updates_});
346 shared_stats_->AddStats(stats);
347}
348
350 absl::Span<const Literal> enforcements, LinearExpression2Index lin2_index,
351 IntegerValue rhs) {
352 DCHECK_EQ(trail_->CurrentDecisionLevel(), stored_level_);
353 // This must be currently true.
354 if (DEBUG_MODE) {
355 for (const Literal l : enforcements) {
356 CHECK(trail_->Assignment().LiteralIsTrue(l));
357 }
358 }
359
360 if (enforcements.empty() || trail_->CurrentDecisionLevel() == 0) {
361 root_level_bounds_->AddUpperBound(lin2_index, rhs);
362 return;
363 }
364
365 if (rhs >= root_level_bounds_->LevelZeroUpperBound(lin2_index)) return;
366 const LinearExpression2 expr = lin2_indices_->GetExpression(lin2_index);
367
368 linear2_watcher_->NotifyBoundChanged(expr);
369 ++num_conditional_relation_updates_;
370
371 const int new_index = conditional_stack_.size();
372 if (conditional_relations_.size() <= lin2_index) {
373 conditional_relations_.resize(lin2_index.value() + 1, -1);
374 }
375 if (conditional_relations_[lin2_index] == -1) {
376 conditional_relations_[lin2_index] = new_index;
377 CreateLevelEntryIfNeeded();
378 conditional_stack_.emplace_back(/*prev_entry=*/-1, rhs, lin2_index,
379 enforcements);
380
381 if (expr.coeffs[0] == 1 && expr.coeffs[1] == 1) {
382 const int new_size =
383 std::max(expr.vars[0].value(), expr.vars[1].value()) + 1;
384 if (new_size > conditional_var_lookup_.size()) {
385 conditional_var_lookup_.resize(new_size);
386 }
387 conditional_var_lookup_[expr.vars[0]].push_back(
388 {expr.vars[1], lin2_index});
389 conditional_var_lookup_[expr.vars[1]].push_back(
390 {expr.vars[0], lin2_index});
391 }
392 } else {
393 const int prev_entry = conditional_relations_[lin2_index];
394 if (rhs >= conditional_stack_[prev_entry].rhs) return;
395
396 // Update.
397 conditional_relations_[lin2_index] = new_index;
398 CreateLevelEntryIfNeeded();
399 conditional_stack_.emplace_back(prev_entry, rhs, lin2_index, enforcements);
400 }
401}
402
403void EnforcedLinear2Bounds::CreateLevelEntryIfNeeded() {
404 const int current = trail_->CurrentDecisionLevel();
405 if (!level_to_stack_size_.empty() &&
406 level_to_stack_size_.back().first == current)
407 return;
408 level_to_stack_size_.push_back({current, conditional_stack_.size()});
409}
410
411// We only pop what is needed.
413 stored_level_ = level;
414 while (!level_to_stack_size_.empty() &&
415 level_to_stack_size_.back().first > level) {
416 const int target = level_to_stack_size_.back().second;
417 DCHECK_GE(conditional_stack_.size(), target);
418 while (conditional_stack_.size() > target) {
419 const ConditionalEntry& back = conditional_stack_.back();
420 if (back.prev_entry != -1) {
421 conditional_relations_[back.key] = back.prev_entry;
422 } else {
423 conditional_relations_[back.key] = -1;
424 const LinearExpression2 expr = lin2_indices_->GetExpression(back.key);
425
426 if (expr.coeffs[0] == 1 && expr.coeffs[1] == 1) {
427 DCHECK_EQ(conditional_var_lookup_[expr.vars[0]].back().first,
428 expr.vars[1]);
429 DCHECK_EQ(conditional_var_lookup_[expr.vars[1]].back().first,
430 expr.vars[0]);
431 conditional_var_lookup_[expr.vars[0]].pop_back();
432 conditional_var_lookup_[expr.vars[1]].pop_back();
433 }
434 }
435 conditional_stack_.pop_back();
436 }
437 level_to_stack_size_.pop_back();
438 }
439}
440
442 LinearExpression2Index index, IntegerValue ub,
443 std::vector<Literal>* literal_reason,
444 std::vector<IntegerLiteral>* /*unused*/) const {
445 DCHECK_EQ(trail_->CurrentDecisionLevel(), stored_level_);
446 if (ub >= root_level_bounds_->LevelZeroUpperBound(index)) return;
447 DCHECK_LT(index, conditional_relations_.size());
448 const int entry_index = conditional_relations_[index];
449 DCHECK_NE(entry_index, -1);
450
451 const ConditionalEntry& entry = conditional_stack_[entry_index];
452 if (DEBUG_MODE) {
453 for (const Literal l : entry.enforcements) {
454 CHECK(trail_->Assignment().LiteralIsTrue(l));
455 }
456 }
457 DCHECK_LE(entry.rhs, ub);
458 for (const Literal l : entry.enforcements) {
459 literal_reason->push_back(l.Negated());
460 }
461}
462
464 LinearExpression2Index index) const {
465 DCHECK_EQ(trail_->CurrentDecisionLevel(), stored_level_);
466 if (index >= conditional_relations_.size()) {
467 return kMaxIntegerValue;
468 }
469 const int entry_index = conditional_relations_[index];
470 if (entry_index == -1) {
471 return kMaxIntegerValue;
472 } else {
473 const ConditionalEntry& entry = conditional_stack_[entry_index];
474 if (DEBUG_MODE) {
475 for (const Literal l : entry.enforcements) {
476 CHECK(trail_->Assignment().LiteralIsTrue(l));
477 }
478 }
479
480 // Note(user): We used to check
481 // entry.rhs <= root_level_bounds_->LevelZeroUpperBound(index));
482 // But that assumed level zero bounds where only added at level zero, if we
483 // add them at an higher level, some of the enforced relations here might be
484 // worse than the fixed one we have. This is not a big deal, as we will not
485 // add then again on bactrack, and we should use the level-zero reason in
486 // that case.
487 return entry.rhs;
488 }
489}
490
492 const int64_t in_timestamp = root_level_bounds_->num_updates();
493 if (in_timestamp <= build_timestamp_) return true;
494 build_timestamp_ = in_timestamp;
495
496 const std::vector<std::pair<LinearExpression2, IntegerValue>>
497 root_relations_sorted =
498 root_level_bounds_->GetSortedNonTrivialUpperBounds();
499 int max_node = 0;
500 for (const auto [expr, _] : root_relations_sorted) {
501 max_node = std::max(max_node, PositiveVariable(expr.vars[0]).value());
502 max_node = std::max(max_node, PositiveVariable(expr.vars[1]).value());
503 }
504 max_node++; // For negation.
505
506 // Is it a DAG?
507 // Get a topological order of the DAG formed by all the arcs that are present.
508 //
509 // TODO(user): This can fail if we don't have a DAG. But in the end we
510 // don't really need a topological order, just something that is close to
511 // one so that we can compute an approximated transitive closure in O(n^2) and
512 // not O(n^3). We could use an heuristic instead, like as long as there is
513 // node with an in-degree of zero, add them to the order and update the
514 // in-degree of the other (by removing outgoing arcs). If there is a cycle
515 // (i.e. no node with no incoming arc), pick one with a small in-degree
516 // randomly.
517 DenseIntStableTopologicalSorter sorter(max_node);
518 for (const auto [expr, negated_offset] : root_relations_sorted) {
519 // Coefficients should be positive.
520 DCHECK_GT(expr.coeffs[0], 0);
521 DCHECK_GT(expr.coeffs[1], 0);
522
523 // TODO(user): Support negative offset?
524 //
525 // Note that if we only have >= 0 ones, if we do have a cycle, we could
526 // make sure all variables are the same, and otherwise, we have a DAG or a
527 // conflict.
528 const IntegerValue offset = -negated_offset;
529 if (offset < 0) continue;
530
531 if (expr.coeffs[0] != 1 || expr.coeffs[1] != 1) {
532 // TODO(user): Support non-1 coefficients.
533 continue;
534 }
535
536 // We have two arcs.
537 sorter.AddEdge(expr.vars[0].value(), NegationOf(expr.vars[1]).value());
538 sorter.AddEdge(expr.vars[1].value(), NegationOf(expr.vars[0]).value());
539 }
540 int next;
541 bool graph_has_cycle = false;
542 topological_order_.clear();
543 while (sorter.GetNext(&next, &graph_has_cycle, nullptr)) {
544 topological_order_.push_back(IntegerVariable(next));
545 if (graph_has_cycle) {
546 is_dag_ = false;
547 return true;
548 }
549 }
550 is_dag_ = !graph_has_cycle;
551
552 // Lets get the transitive closure if it is cheap. This is also a way not to
553 // add too many relations per call.
554 int total_work = 0;
555 const int kWorkLimit = params_->transitive_precedences_work_limit();
556 if (kWorkLimit > 0) {
557 for (const IntegerVariable var : topological_order_) {
558 const int work = root_level_bounds_->AugmentSimpleRelations(
559 var, kWorkLimit - total_work);
560 total_work += work;
561 if (total_work >= kWorkLimit) break;
562 }
563 }
564
565 build_timestamp_ = root_level_bounds_->num_updates();
566 VLOG(2) << "Full precedences. Work=" << total_work
567 << " Relations=" << root_relations_sorted.size()
568 << " num_added=" << build_timestamp_ - in_timestamp;
569 return true;
570}
571
572// TODO(user): There is probably little need for that function. For small
573// problem, we already augment root_level_bounds_ will all the relation obtained
574// by transitive closure, so this algo only need to look at direct dependency in
575// root_level_bounds_->GetVariablesInSimpleRelation(). And for large graph, we
576// probably do not want this.
578 absl::Span<const IntegerVariable> vars,
579 std::vector<FullIntegerPrecedence>* output) {
580 output->clear();
581 Build(); // Will do nothing if we are up to date.
582 if (!is_dag_) return;
583
584 // Compute all precedences.
585 // We loop over the node in topological order, and we maintain for all
586 // variable we encounter, the list of "to_consider" variables that are before.
587 //
588 // TODO(user): use vector of fixed size.
589 absl::flat_hash_set<IntegerVariable> is_interesting;
590 absl::flat_hash_set<IntegerVariable> to_consider(vars.begin(), vars.end());
591 absl::flat_hash_map<IntegerVariable,
592 absl::flat_hash_map<IntegerVariable, IntegerValue>>
593 vars_before_with_offset;
594 absl::flat_hash_map<IntegerVariable, IntegerValue> tail_map;
595 for (const IntegerVariable tail_var : topological_order_) {
596 if (!to_consider.contains(tail_var) &&
597 !vars_before_with_offset.contains(tail_var)) {
598 continue;
599 }
600
601 // We copy the data for tail_var here, because the pointer is not stable.
602 // TODO(user): optimize when needed.
603 tail_map.clear();
604 {
605 const auto it = vars_before_with_offset.find(tail_var);
606 if (it != vars_before_with_offset.end()) {
607 tail_map = it->second;
608 }
609 }
610
611 // We look for tail_var + offset <= head_var.
612 for (const auto [neg_head_var, index] :
613 root_level_bounds_->GetVariablesInSimpleRelation(tail_var)) {
614 const IntegerVariable head_var = NegationOf(neg_head_var);
615 const IntegerValue arc_offset =
616 -root_level_bounds_->GetUpperBoundNoTrail(index);
617
618 // No need to create an empty entry in this case.
619 if (tail_map.empty() && !to_consider.contains(tail_var)) continue;
620
621 auto& to_update = vars_before_with_offset[head_var];
622 for (const auto& [var_before, offset] : tail_map) {
623 if (!to_update.contains(var_before)) {
624 to_update[var_before] = arc_offset + offset;
625 } else {
626 to_update[var_before] =
627 std::max(arc_offset + offset, to_update[var_before]);
628 }
629 }
630 if (to_consider.contains(tail_var)) {
631 if (!to_update.contains(tail_var)) {
632 to_update[tail_var] = arc_offset;
633 } else {
634 to_update[tail_var] = std::max(arc_offset, to_update[tail_var]);
635 }
636 }
637
638 // Small filtering heuristic: if we have (before) < tail, and tail < head,
639 // we really do not need to list (before, tail) < head. We only need that
640 // if the list of variable before head contains some variable that are not
641 // already before tail.
642 if (to_update.size() > tail_map.size() + 1) {
643 is_interesting.insert(head_var);
644 } else {
645 is_interesting.erase(head_var);
646 }
647 }
648
649 // Extract the output for tail_var. Because of the topological ordering, the
650 // data for tail_var is already final now.
651 //
652 // TODO(user): Release the memory right away.
653 if (!is_interesting.contains(tail_var)) continue;
654 if (tail_map.size() == 1) continue;
655
657 data.var = tail_var;
658 IntegerValue min_offset = kMaxIntegerValue;
659 for (int i = 0; i < vars.size(); ++i) {
660 const auto offset_it = tail_map.find(vars[i]);
661 if (offset_it == tail_map.end()) continue;
662 data.indices.push_back(i);
663 data.offsets.push_back(offset_it->second);
664 min_offset = std::min(data.offsets.back(), min_offset);
665 }
666 output->push_back(std::move(data));
667 }
668}
669
671 absl::Span<const IntegerVariable> vars,
672 std::vector<PrecedenceData>* output) {
673 const int needed_size = integer_trail_->NumIntegerVariables().value();
674 var_to_degree_.resize(needed_size);
675 var_to_last_index_.resize(needed_size);
676 var_with_positive_degree_.resize(needed_size);
677 tmp_precedences_.clear();
678
679 // We first compute the degree/size in order to perform the transposition.
680 // Note that we also remove duplicates.
681 int num_relevants = 0;
682 IntegerVariable* var_with_positive_degree = var_with_positive_degree_.data();
683 int* var_to_degree = var_to_degree_.data();
684 int* var_to_last_index = var_to_last_index_.data();
685 const auto process =
686 [&](int var_index,
687 absl::Span<const std::pair<IntegerVariable, LinearExpression2Index>>
688 v) {
689 for (const auto [other, lin2_index] : v) {
690 const IntegerVariable after = NegationOf(other);
691 DCHECK_LT(after, needed_size);
692 if (var_to_degree[after.value()] == 0) {
693 var_with_positive_degree[num_relevants++] = after;
694 } else {
695 // We do not want duplicates.
696 if (var_to_last_index[after.value()] == var_index) continue;
697 }
698
699 tmp_precedences_.push_back({after, var_index, lin2_index});
700 var_to_degree[after.value()]++;
701 var_to_last_index[after.value()] = var_index;
702 }
703 };
704
705 for (int var_index = 0; var_index < vars.size(); ++var_index) {
706 const IntegerVariable var = vars[var_index];
707 process(var_index, root_level_bounds_->GetVariablesInSimpleRelation(var));
708 if (var < conditional_var_lookup_.size()) {
709 process(var_index, conditional_var_lookup_[var]);
710 }
711 }
712
713 // Permute tmp_precedences_ into the output to put it in the correct order.
714 // For that we transform var_to_degree to point to the first position of
715 // each lbvar in the output vector.
716 int start = 0;
717 const absl::Span<const IntegerVariable> relevant_variables =
718 absl::MakeSpan(var_with_positive_degree, num_relevants);
719 for (const IntegerVariable var : relevant_variables) {
720 const int degree = var_to_degree[var.value()];
721 if (degree > 1) {
722 var_to_degree[var.value()] = start;
723 start += degree;
724 } else {
725 // Optimization: we remove degree one relations.
726 var_to_degree[var.value()] = -1;
727 }
728 }
729
730 output->resize(start);
731 for (const auto& precedence : tmp_precedences_) {
732 // Note that it is okay to increase the -1 pos since they appear only once.
733 const int pos = var_to_degree[precedence.var.value()]++;
734 if (pos < 0) continue;
735 (*output)[pos] = precedence;
736 }
737
738 // Cleanup var_to_degree, note that we don't need to clean
739 // var_to_last_index_.
740 for (const IntegerVariable var : relevant_variables) {
741 var_to_degree[var.value()] = 0;
742 }
743}
744
746 if (!VLOG_IS_ON(1)) return;
747 std::vector<std::pair<std::string, int64_t>> stats;
748 stats.push_back({"ConditionalLinear2Bounds/num_enforced_relations",
749 num_enforced_relations_});
750 stats.push_back({"ConditionalLinear2Bounds/num_encoded_equivalences",
751 num_encoded_equivalences_});
752 shared_stats_->AddStats(stats);
753}
754
756 IntegerValue lhs, IntegerValue rhs) {
758 if (expr.coeffs[0] == 0 || expr.coeffs[1] == 0) return;
759 expr.CanonicalizeAndUpdateBounds(lhs, rhs);
760
761 CHECK_NE(lit.Index(), kNoLiteralIndex);
762 num_enforced_relations_++;
763
764 relations_.push_back(
765 {.enforcement = lit, .expr = expr, .lhs = lhs, .rhs = rhs});
766}
767
769 IntegerVariable a,
770 IntegerVariable b) {
771 DCHECK_NE(a, kNoIntegerVariable);
772 DCHECK_NE(b, kNoIntegerVariable);
773 DCHECK_NE(a, b);
774 Add(lit, LinearExpression2(a, b, 1, 1), 0, 0);
775}
776
778 DCHECK(!is_built_);
779 is_built_ = true;
780 std::vector<std::pair<LiteralIndex, int>> literal_key_values;
781 const int num_relations = relations_.size();
782 literal_key_values.reserve(num_enforced_relations_);
783 for (int i = 0; i < num_relations; ++i) {
784 const Relation& r = relations_[i];
785 literal_key_values.emplace_back(r.enforcement.Index(), i);
786 }
787 lit_to_relations_.ResetFromPairs(literal_key_values);
788 lit_to_relations_.Add({}); // One extra unit size to make sure the negation
789 // cannot be out of bounds in lit_to_relations_.
790
791 // If we have "l => (x <= 6)" and "~l => (x >= 9)" we can push
792 // "l <=> (x <= 6)" to the repository of fully encoded linear2 bounds.
793 // More generally, if we have:
794 //
795 // l => (expr <= a)
796 // ~l => (expr >= b)
797 //
798 // And if moreover a < b, we have the following truth table:
799 //
800 // l | expr <= a | a < expr < b | expr >= b
801 // --+-----------+---------------+----------
802 // 0 | false | false | true (from "~l => (expr >= b)")
803 // 1 | true | false | false (from "l => (expr <= a)")
804 //
805 // So we can generalize the expressions to equivalences:
806 // l <=> (expr <= a)
807 // ~l <=> (expr >= b)
808 // (a < expr < b) is impossible
809 absl::flat_hash_map<LinearExpression2, IntegerValue> lin2_to_upper_bound;
810 for (LiteralIndex lit_index{0}; lit_index < lit_to_relations_.size() - 1;
811 ++lit_index) {
812 const Literal lit(lit_index);
813 lin2_to_upper_bound.clear();
814 const absl::Span<const int> relations = lit_to_relations_[lit_index];
815 const absl::Span<const int> relations_negation =
816 lit_to_relations_[lit.NegatedIndex()];
817 if (relations.empty() || relations_negation.empty()) continue;
818 for (const int relation_index : relations) {
819 const Relation& r = relations_[relation_index];
820 DCHECK(r.expr.IsCanonicalized());
821 LinearExpression2 expr = r.expr;
822 {
823 const auto [it, inserted] = lin2_to_upper_bound.insert({expr, r.rhs});
824 if (!inserted) {
825 it->second = std::min(it->second, r.rhs);
826 }
827 }
828 {
829 expr.Negate();
830 const auto [it, inserted] = lin2_to_upper_bound.insert({expr, -r.lhs});
831 if (!inserted) {
832 it->second = std::min(it->second, -r.lhs);
833 }
834 }
835 }
836 for (const int relation_index : relations_negation) {
837 const Relation& r = relations_[relation_index];
838 DCHECK(r.expr.IsCanonicalized());
839
840 // Let's work with lower bounds only.
841 const IntegerValue lower_bounds[2] = {r.lhs, -r.rhs};
842 LinearExpression2 exprs[2] = {r.expr, r.expr};
843 exprs[1].Negate();
844 for (int i = 0; i < 2; ++i) {
845 // We have here "~l => (exprs[i] >= lower_bounds[i])".
846 const auto it = lin2_to_upper_bound.find(exprs[i]);
847 if (it != lin2_to_upper_bound.end()) {
848 const IntegerValue ub = it->second;
849 // Here we have "l => expr <= ub".
850 if (ub >= lower_bounds[i]) {
851 // Don't obey the "a < b" condition
852 continue;
853 }
854 num_encoded_equivalences_++;
855
856 // Make both relationships two-way.
857 reified_linear2_bounds_->AddBoundEncodingIfNonTrivial(lit, exprs[i],
858 ub);
859 LinearExpression2 expr = exprs[i];
860 expr.Negate();
861 reified_linear2_bounds_->AddBoundEncodingIfNonTrivial(
862 lit.Negated(), expr, -lower_bounds[i]);
863 }
864 }
865 }
866 }
867}
868
870 const IntegerTrail& integer_trail,
871 const RootLevelLinear2Bounds& root_level_bounds,
872 const ConditionalLinear2Bounds& repository,
873 const ImpliedBounds& implied_bounds, Literal lit,
874 const absl::flat_hash_map<IntegerVariable, IntegerValue>& input,
875 absl::flat_hash_map<IntegerVariable, IntegerValue>* output) {
876 DCHECK_NE(lit.Index(), kNoLiteralIndex);
877
878 auto get_lower_bound = [&](IntegerVariable var) {
879 const auto it = input.find(var);
880 if (it != input.end()) return it->second;
881 return integer_trail.LevelZeroLowerBound(var);
882 };
883 auto get_upper_bound = [&](IntegerVariable var) {
884 return -get_lower_bound(NegationOf(var));
885 };
886 auto update_lower_bound_by_var = [&](IntegerVariable var, IntegerValue lb) {
887 if (lb <= integer_trail.LevelZeroLowerBound(var)) return;
888 const auto [it, inserted] = output->insert({var, lb});
889 if (!inserted) {
890 it->second = std::max(it->second, lb);
891 }
892 };
893 auto update_upper_bound_by_var = [&](IntegerVariable var, IntegerValue ub) {
894 update_lower_bound_by_var(NegationOf(var), -ub);
895 };
896 auto update_var_bounds = [&](const LinearExpression2& expr, IntegerValue lhs,
897 IntegerValue rhs) {
898 if (expr.coeffs[0] == 0) return;
899
900 // lb(b.y) <= b.y <= ub(b.y) and lhs <= a.x + b.y <= rhs imply
901 // ceil((lhs - ub(b.y)) / a) <= x <= floor((rhs - lb(b.y)) / a)
902 if (expr.coeffs[1] != 0) {
903 lhs = lhs - expr.coeffs[1] * get_upper_bound(expr.vars[1]);
904 rhs = rhs - expr.coeffs[1] * get_lower_bound(expr.vars[1]);
905 }
906 update_lower_bound_by_var(expr.vars[0],
907 MathUtil::CeilOfRatio(lhs, expr.coeffs[0]));
908 update_upper_bound_by_var(expr.vars[0],
909 MathUtil::FloorOfRatio(rhs, expr.coeffs[0]));
910 };
911 auto update_var_bounds_from_relation = [&](Relation r) {
912 DCHECK(r.expr.IsCanonicalized());
913 update_var_bounds(r.expr, r.lhs, r.rhs);
914 std::swap(r.expr.vars[0], r.expr.vars[1]);
915 std::swap(r.expr.coeffs[0], r.expr.coeffs[1]);
916 update_var_bounds(r.expr, r.lhs, r.rhs);
917 };
918 for (const int relation_index :
919 repository.IndicesOfRelationsEnforcedBy(lit)) {
920 const Relation& r = repository.relation(relation_index);
921 if (r.expr.coeffs[0] == 0) continue;
922 update_var_bounds_from_relation(r);
923 }
924 for (const auto& [var, unused] : input) {
925 for (const auto& [expr, lb, ub] :
926 root_level_bounds.GetAllBoundsContainingVariable(var)) {
927 // Note: GetAllBoundsContainingVariable() does not return canonicalized
928 // expr on purpose.
929 Relation r{Literal(kNoLiteralIndex), expr, lb, ub};
931 update_var_bounds_from_relation(r);
932 }
933 const auto [lb, ub] = implied_bounds.GetImpliedBounds(lit, var);
934 update_var_bounds_from_relation(Relation{
935 lit, LinearExpression2(kNoIntegerVariable, var, 0, 1), lb, ub});
936 }
937
938 // Check feasibility.
939 // TODO(user): we might do that earlier?
940 for (const auto [var, lb] : *output) {
941 if (lb > integer_trail.LevelZeroUpperBound(var)) return false;
942 }
943 return true;
944}
945
946void GreaterThanAtLeastOneOfDetector::AddRelationIfNonTrivial(
947 const Relation& r,
948 std::vector<VariableConditionalAffineBound>* clause_bounds) const {
949 const Literal l = r.enforcement;
950
951 auto add_if_non_trivial = [&](Literal cond, IntegerVariable var,
952 AffineExpression expr) {
953 if (integer_trail_.LevelZeroLowerBound(var) <
954 integer_trail_.LevelZeroUpperBound(expr)) {
955 clause_bounds->push_back({cond, var, expr});
956 }
957 };
958
959 DCHECK_NE(r.expr.vars[0], kNoIntegerVariable);
960 DCHECK_NE(r.expr.vars[1], kNoIntegerVariable);
961 if (r.lhs != kMinIntegerValue) {
962 for (int i = 0; i < 2; ++i) {
963 const AffineExpression affine_lb = r.expr.GetAffineLowerBound(
964 i, r.lhs, integer_trail_.LevelZeroLowerBound(r.expr.vars[1 - i]));
965 add_if_non_trivial(l, r.expr.vars[i], affine_lb);
966 }
967 }
968 if (r.rhs != kMaxIntegerValue) {
969 LinearExpression2 negated_expr(r.expr);
970 negated_expr.Negate();
971 for (int i = 0; i < 2; ++i) {
972 const AffineExpression affine_lb = negated_expr.GetAffineLowerBound(
973 i, -r.rhs,
974 integer_trail_.LevelZeroLowerBound(negated_expr.vars[1 - i]));
975 add_if_non_trivial(l, negated_expr.vars[i], affine_lb);
976 }
977 }
978}
979
980bool GreaterThanAtLeastOneOfDetector::AddRelationFromBounds(
981 IntegerVariable var, absl::Span<const Literal> clause,
982 absl::Span<const VariableConditionalAffineBound> bounds, Model* model) {
983 std::vector<AffineExpression> exprs;
984 std::vector<Literal> selectors;
985 absl::flat_hash_set<LiteralIndex> used;
986
987 for (const VariableConditionalAffineBound& bound : bounds) {
988 DCHECK_EQ(bound.var, var);
989 // Note that duplicate selector are supported.
990 selectors.push_back(bound.enforcement_literal);
991 used.insert(bound.enforcement_literal);
992 exprs.push_back(bound.bound);
993 }
994
995 // The enforcement of the new constraint are simply the literal not used
996 // above.
997 std::vector<Literal> enforcements;
998 for (const Literal l : clause) {
999 if (!used.contains(l.Index())) {
1000 enforcements.push_back(l.Negated());
1001 }
1002 }
1003
1004 // No point adding a constraint if there is not at least two different
1005 // literals in selectors.
1006 if (used.size() <= 1) return false;
1007
1008 total_num_expressions_ += exprs.size();
1009 ++num_detected_constraints_;
1010 maximum_num_expressions_ =
1011 std::max(maximum_num_expressions_, static_cast<int64_t>(exprs.size()));
1012 // Add the constraint.
1013
1014 GreaterThanAtLeastOneOfPropagator* constraint =
1015 new GreaterThanAtLeastOneOfPropagator(var, exprs, selectors, enforcements,
1016 model);
1017 constraint->RegisterWith(model->GetOrCreate<GenericLiteralWatcher>());
1018 model->TakeOwnership(constraint);
1019 return true;
1020}
1021
1022int GreaterThanAtLeastOneOfDetector::
1023 AddGreaterThanAtLeastOneOfConstraintsFromClause(
1024 const absl::Span<const Literal> clause, Model* model,
1026 implied_bounds_by_literal) {
1027 CHECK_EQ(model->GetOrCreate<Trail>()->CurrentDecisionLevel(), 0);
1028 if (clause.size() < 2) return 0;
1029
1030 std::vector<VariableConditionalAffineBound> clause_bounds;
1031
1032 // Collect all relations impacted by this clause from conditional linear2.
1033 for (const Literal l : clause) {
1034 for (const int index :
1035 repository_.IndicesOfRelationsEnforcedBy(l.Index())) {
1036 const Relation& r = repository_.relation(index);
1037 AddRelationIfNonTrivial(r, &clause_bounds);
1038 }
1039 for (const IntegerLiteral& i_lit : implied_bounds_by_literal[l.Index()]) {
1040 if (integer_trail_.LevelZeroLowerBound(i_lit.var) < i_lit.bound) {
1041 clause_bounds.push_back({l, i_lit.var, AffineExpression(i_lit.bound)});
1042 }
1043 }
1044 }
1045 if (clause_bounds.size() <= 1) return 0;
1046
1047 // Stable sort to regroup by var.
1048 absl::c_stable_sort(
1049 clause_bounds,
1050 [](const VariableConditionalAffineBound& a,
1051 const VariableConditionalAffineBound& b) { return a.var < b.var; });
1052
1053 // We process the info with same variable together.
1054 int num_added_constraints = 0;
1055 for (int i = 0; i < clause_bounds.size();) {
1056 const int start = i;
1057 const IntegerVariable var = clause_bounds[start].var;
1058
1059 for (; i < clause_bounds.size() && clause_bounds[i].var == var; ++i) {
1060 }
1061
1062 const int end = i;
1063 absl::Span<const VariableConditionalAffineBound> bounds =
1064 absl::MakeSpan(clause_bounds).subspan(start, end - start);
1065
1066 // Skip single relations, we are not interested in these.
1067 if (bounds.size() < 2) continue;
1068
1069 // Heuristic. Look for full or almost full clauses.
1070 //
1071 // TODO(user): We could add GreaterThanAtLeastOneOf() with more enforcement
1072 // literals. Experiment.
1073 if (bounds.size() + 1 < clause.size()) continue;
1074
1075 if (AddRelationFromBounds(var, clause, bounds, model)) {
1076 ++num_added_constraints;
1077 }
1078 }
1079 return num_added_constraints;
1080}
1081
1082int GreaterThanAtLeastOneOfDetector::
1083 AddGreaterThanAtLeastOneOfConstraintsWithClauseAutoDetection(Model* model) {
1084 auto* time_limit = model->GetOrCreate<TimeLimit>();
1085 auto* solver = model->GetOrCreate<SatSolver>();
1086
1087 // Fill the set of interesting relations for each variables.
1088 std::vector<VariableConditionalAffineBound> clause_bounds;
1089 for (int index = 0; index < repository_.size(); ++index) {
1090 const Relation& r = repository_.relation(index);
1091 if (r.enforcement.Index() == kNoLiteralIndex) continue;
1092 AddRelationIfNonTrivial(r, &clause_bounds);
1093 }
1094 for (const auto& [literal_var_pair, bound] :
1095 implied_bounds_.GetModelImpliedBounds()) {
1096 if (integer_trail_.LevelZeroLowerBound(literal_var_pair.second) < bound) {
1097 clause_bounds.push_back(VariableConditionalAffineBound{
1098 .enforcement_literal = Literal(literal_var_pair.first),
1099 .var = literal_var_pair.second,
1100 .bound = AffineExpression(bound)});
1101 }
1102 }
1103
1104 // Stable sort to regroup by var.
1105 // TODO(user): We should probably also sort by enforcement literal,
1106 // and regroup entry with same variable/enforcement if that happen often to
1107 // have more than one such entry.
1108 absl::c_stable_sort(
1109 clause_bounds,
1110 [](const VariableConditionalAffineBound& a,
1111 const VariableConditionalAffineBound& b) { return a.var < b.var; });
1112
1113 int num_added_constraints = 0;
1114 for (int i = 0; i < clause_bounds.size();) {
1115 const int start = i;
1116 const IntegerVariable var = clause_bounds[start].var;
1117
1118 for (; i < clause_bounds.size() && clause_bounds[i].var == var; ++i) {
1119 }
1120
1121 const int end = i;
1122 absl::Span<const VariableConditionalAffineBound> bounds =
1123 absl::MakeSpan(clause_bounds).subspan(start, end - start);
1124
1125 if (bounds.size() <= 1) continue;
1126 if (time_limit->LimitReached()) return num_added_constraints;
1127
1128 // Detect set of incoming arcs for which at least one must be present.
1129 // TODO(user): Find more than one disjoint set of incoming arcs.
1130 // TODO(user): call MinimizeCoreWithPropagation() on the clause.
1131 solver->Backtrack(0);
1132 if (solver->ModelIsUnsat()) return num_added_constraints;
1133 std::vector<Literal> clause;
1134 for (const VariableConditionalAffineBound& bound : bounds) {
1135 const Literal literal = bound.enforcement_literal;
1136 if (solver->Assignment().LiteralIsFalse(literal)) continue;
1137 const SatSolver::Status status =
1138 solver->EnqueueDecisionAndBacktrackOnConflict(literal.Negated());
1139 if (status == SatSolver::INFEASIBLE) return num_added_constraints;
1140 if (status == SatSolver::ASSUMPTIONS_UNSAT) {
1141 // We need to invert it, since a clause is not all false.
1142 clause = solver->GetLastIncompatibleDecisions();
1143 for (Literal& ref : clause) ref = ref.Negated();
1144 break;
1145 }
1146 }
1147 solver->Backtrack(0);
1148 if (clause.size() <= 1) continue;
1149
1150 // Recover the indices corresponding to this clause.
1151 const absl::btree_set<Literal> clause_set(clause.begin(), clause.end());
1152
1153 std::vector<VariableConditionalAffineBound> for_cur_clause;
1154 for (const VariableConditionalAffineBound& bound : bounds) {
1155 const Literal literal = bound.enforcement_literal;
1156 if (clause_set.contains(literal)) {
1157 for_cur_clause.push_back(bound);
1158 }
1159 }
1160
1161 if (AddRelationFromBounds(bounds[0].var, clause, for_cur_clause, model)) {
1162 ++num_added_constraints;
1163 }
1164 }
1165
1166 solver->Backtrack(0);
1167 return num_added_constraints;
1168}
1169
1171 Model* model, bool auto_detect_clauses) {
1172 auto* time_limit = model->GetOrCreate<TimeLimit>();
1173 auto* solver = model->GetOrCreate<SatSolver>();
1174 auto* clauses = model->GetOrCreate<ClauseManager>();
1175 auto* logger = model->GetOrCreate<SolverLogger>();
1176
1177 int num_added_constraints = 0;
1178 SOLVER_LOG(logger, "[Precedences] num_relations=", repository_.size(),
1179 " num_clauses=", clauses->AllClausesInCreationOrder().size());
1180
1181 CompactVectorVector<LiteralIndex, IntegerLiteral> implied_bounds_by_literal;
1182 {
1183 const auto& all_implied_bounds = implied_bounds_.GetModelImpliedBounds();
1184 std::vector<LiteralIndex> implied_bounds_conditions;
1185 std::vector<IntegerLiteral> implied_bounds_integer_lit;
1186 implied_bounds_conditions.reserve(all_implied_bounds.size());
1187 implied_bounds_integer_lit.reserve(all_implied_bounds.size());
1188 for (const auto& [literal_var_pair, bound] : all_implied_bounds) {
1189 implied_bounds_conditions.push_back(literal_var_pair.first);
1190 implied_bounds_integer_lit.push_back(
1191 IntegerLiteral::GreaterOrEqual(literal_var_pair.second, bound));
1192 }
1193 implied_bounds_by_literal.ResetFromFlatMapping(
1194 std::move(implied_bounds_conditions),
1195 std::move(implied_bounds_integer_lit), 2 * solver->NumVariables());
1196 }
1197
1198 // We have two possible approaches. For now, we prefer the first one except if
1199 // there is too many clauses in the problem.
1200 //
1201 // TODO(user): Do more extensive experiment. Remove the second approach as
1202 // it is more time consuming? or identify when it make sense. Note that the
1203 // first approach also allows to use "incomplete" at least one between arcs.
1204 if (!auto_detect_clauses &&
1205 clauses->AllClausesInCreationOrder().size() < 1e6) {
1206 // TODO(user): This does not take into account clause of size 2 since they
1207 // are stored in the BinaryImplicationGraph instead. Some ideas specific
1208 // to size 2:
1209 // - There can be a lot of such clauses, but it might be nice to consider
1210 // them. we need to experiments.
1211 // - The automatic clause detection might be a better approach and it
1212 // could be combined with probing.
1213 for (const SatClause* clause : clauses->AllClausesInCreationOrder()) {
1214 if (time_limit->LimitReached()) return num_added_constraints;
1215 if (solver->ModelIsUnsat()) return num_added_constraints;
1216 num_added_constraints += AddGreaterThanAtLeastOneOfConstraintsFromClause(
1217 clause->AsSpan(), model, implied_bounds_by_literal);
1218 }
1219
1220 // It is common that there is only two alternatives to push a variable.
1221 // In this case, our presolve most likely made sure that the two are
1222 // controlled by a single Boolean. This allows to detect this and add the
1223 // appropriate greater than at least one of.
1224 const int num_booleans = solver->NumVariables();
1225 if (num_booleans < 1e6) {
1226 for (int i = 0; i < num_booleans; ++i) {
1227 if (time_limit->LimitReached()) return num_added_constraints;
1228 if (solver->ModelIsUnsat()) return num_added_constraints;
1229 num_added_constraints +=
1230 AddGreaterThanAtLeastOneOfConstraintsFromClause(
1231 {Literal(BooleanVariable(i), true),
1232 Literal(BooleanVariable(i), false)},
1233 model, implied_bounds_by_literal);
1234 }
1235 }
1236
1237 } else {
1238 num_added_constraints +=
1239 AddGreaterThanAtLeastOneOfConstraintsWithClauseAutoDetection(model);
1240 }
1241
1242 if (num_added_constraints > 0) {
1243 SOLVER_LOG(logger, "[Precedences] Added ", num_added_constraints,
1244 " GreaterThanAtLeastOneOf() constraints.");
1245 }
1246
1247 return num_added_constraints;
1248}
1249
1251 if (!VLOG_IS_ON(1)) return;
1252 std::vector<std::pair<std::string, int64_t>> stats;
1253 stats.push_back({"GreaterThanAtLeastOneOfDetector/total_num_expressions",
1254 total_num_expressions_});
1255 stats.push_back({"GreaterThanAtLeastOneOfDetector/maximum_num_expressions",
1256 maximum_num_expressions_});
1257 stats.push_back({"GreaterThanAtLeastOneOfDetector/num_detected_constraints",
1258 num_detected_constraints_});
1259 shared_stats_->AddStats(stats);
1260}
1261
1263 : best_root_level_bounds_(model->GetOrCreate<RootLevelLinear2Bounds>()),
1264 lin2_indices_(model->GetOrCreate<Linear2Indices>()),
1265 shared_stats_(model->GetOrCreate<SharedStatistics>()) {
1266 int index = 0;
1267 model->GetOrCreate<LevelZeroCallbackHelper>()->callbacks.push_back(
1268 [index = index, trail = model->GetOrCreate<Trail>(), this]() mutable {
1269 DCHECK_EQ(trail->CurrentDecisionLevel(), 0);
1270 absl::flat_hash_set<Literal> relevant_true_literals;
1271 for (; index < trail->Index(); ++index) {
1272 const Literal l = (*trail)[index];
1273 if (variable_appearing_in_reified_relations_.contains(l.Variable())) {
1274 relevant_true_literals.insert(l);
1275 }
1276 }
1277 if (relevant_true_literals.empty()) return true;
1278
1279 // Linear scan.
1280 for (const auto [l, expr_index, ub] : all_reified_relations_) {
1281 if (relevant_true_literals.contains(l)) {
1282 ++num_relations_fixed_at_root_level_;
1283 best_root_level_bounds_->AddUpperBound(expr_index, ub);
1284 VLOG(2) << "New fixed precedence: "
1285 << lin2_indices_->GetExpression(expr_index) << " <= " << ub
1286 << " (was reified by " << l << ")";
1287 } else if (relevant_true_literals.contains(l.Negated())) {
1288 ++num_relations_fixed_at_root_level_;
1289 best_root_level_bounds_->AddLowerBound(expr_index, ub + 1);
1290 VLOG(2) << "New fixed precedence: "
1291 << lin2_indices_->GetExpression(expr_index) << " > " << ub
1292 << " (was reified by not(" << l << "))";
1293 }
1294 }
1295 return true;
1296 });
1297}
1298
1300 if (!VLOG_IS_ON(1)) return;
1301 std::vector<std::pair<std::string, int64_t>> stats;
1302 stats.push_back(
1303 {"ReifiedLinear2Bounds/num_linear3_relations", num_linear3_relations_});
1304 stats.push_back(
1305 {"ReifiedLinear2Bounds/num_literal_relations", relation_to_lit_.size()});
1306 stats.push_back({"ReifiedLinear2Bounds/num_relations_fixed_at_root_level",
1307 num_relations_fixed_at_root_level_});
1308 shared_stats_->AddStats(stats);
1309}
1310
1312 LinearExpression2 expr,
1313 IntegerValue ub) {
1314 DCHECK(expr.IsCanonicalized());
1315 DCHECK_EQ(expr.DivideByGcd(), 1);
1316 const RelationStatus status =
1317 best_root_level_bounds_->GetLevelZeroStatus(expr, kMinIntegerValue, ub);
1318 if (status != RelationStatus::IS_UNKNOWN) return;
1319
1320 if (expr.vars[0] == kNoIntegerVariable) {
1321 // For a single Affine GetEncodedBound() will return the IntegerLiteral
1322 // without needing any indexing.
1323 return;
1324 }
1325 const LinearExpression2Index expr_index = lin2_indices_->AddOrGet(expr);
1326 relation_to_lit_.insert({{expr_index, ub}, l});
1327 variable_appearing_in_reified_relations_.insert(l.Variable());
1328 all_reified_relations_.push_back({l, expr_index, ub});
1329}
1330
1331std::variant<ReifiedLinear2Bounds::ReifiedBoundType, Literal, IntegerLiteral>
1333 DCHECK(expr.IsCanonicalized());
1334 DCHECK_EQ(expr.DivideByGcd(), 1);
1335 const RelationStatus status =
1336 best_root_level_bounds_->GetLevelZeroStatus(expr, kMinIntegerValue, ub);
1337 if (status == RelationStatus::IS_TRUE) {
1339 }
1340 if (status == RelationStatus::IS_FALSE) {
1342 }
1343 if (expr.vars[0] == kNoIntegerVariable) {
1344 DCHECK_NE(expr.vars[1], kNoIntegerVariable);
1345 DCHECK_EQ(expr.coeffs[1], 1);
1346 return IntegerLiteral::LowerOrEqual(expr.vars[1], ub);
1347 }
1348
1349 const LinearExpression2Index expr_index = lin2_indices_->GetIndex(expr);
1350 if (expr_index == kNoLinearExpression2Index) {
1352 }
1353 const auto it = relation_to_lit_.find({expr_index, ub});
1354 if (it != relation_to_lit_.end()) return it->second;
1355 if (linear3_bounds_.size() <= expr_index) {
1357 }
1358 const auto [affine_expr, divisor] = linear3_bounds_[expr_index];
1359 if (divisor == 0) {
1361 }
1362 const IntegerValue affine_bound = CapProdI(ub, divisor);
1363 if (affine_bound == kMaxIntegerValue) {
1365 }
1366 return affine_expr.LowerOrEqual(affine_bound);
1367}
1368
1369void ReifiedLinear2Bounds::AddLinear3(absl::Span<const IntegerVariable> vars,
1370 absl::Span<const int64_t> coeffs,
1371 int64_t activity) {
1372 DCHECK_EQ(vars.size(), 3);
1373 DCHECK_EQ(coeffs.size(), 3);
1374 for (int i = 0; i < vars.size(); ++i) {
1375 LinearExpression2 expr(vars[i], vars[(i + 1) % 3], coeffs[i],
1376 coeffs[(i + 1) % 3]);
1377 AffineExpression affine_expr(vars[(i + 2) % 3], -coeffs[(i + 2) % 3],
1378 activity);
1380 const IntegerValue gcd = expr.DivideByGcd();
1381 const LinearExpression2Index expr_index = lin2_indices_->AddOrGet(expr);
1382 if (linear3_bounds_.size() <= expr_index) {
1383 linear3_bounds_.resize(expr_index + 1, {AffineExpression(), 0});
1384 }
1385 auto& [old_affine_expr, old_divisor] = linear3_bounds_[expr_index];
1386 if (old_divisor == 0 || old_divisor > gcd) {
1387 linear3_bounds_[expr_index] = {affine_expr, gcd};
1388 if (old_divisor == 0) ++num_linear3_relations_;
1389 }
1390 }
1391}
1392
1393std::pair<AffineExpression, IntegerValue> ReifiedLinear2Bounds::GetLinear3Bound(
1394 LinearExpression2Index lin2_index) const {
1395 DCHECK_LT(lin2_index, linear3_bounds_.size());
1396 DCHECK_GT(linear3_bounds_[lin2_index].second, 0);
1397 return linear3_bounds_[lin2_index];
1398}
1399
1401 : integer_trail_(model->GetOrCreate<IntegerTrail>()),
1402 trail_(model->GetOrCreate<Trail>()),
1403 linear2_watcher_(model->GetOrCreate<Linear2Watcher>()),
1404 watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
1405 shared_stats_(model->GetOrCreate<SharedStatistics>()),
1406 root_level_bounds_(model->GetOrCreate<RootLevelLinear2Bounds>()),
1407 lin2_indices_(model->GetOrCreate<Linear2Indices>()) {}
1408
1410 if (!VLOG_IS_ON(1)) return;
1411 std::vector<std::pair<std::string, int64_t>> stats;
1412 stats.push_back(
1413 {"Linear2BoundsFromLinear3/num_affine_updates", num_affine_updates_});
1414 shared_stats_->AddStats(stats);
1415}
1416
1417// Note that for speed we do not compare to the trivial or root level bounds.
1418//
1419// It is okay to still store it in the hash-map, since at worst we will have no
1420// more entries than 3 * number_of_linear3_in_the_problem.
1422 LinearExpression2Index lin2_index, IntegerValue lin_expr_gcd,
1423 AffineExpression affine_ub) {
1424 // At level zero, just add it to root_level_bounds_.
1425 if (trail_->CurrentDecisionLevel() == 0 || affine_ub.IsConstant()) {
1426 root_level_bounds_->AddUpperBound(
1427 lin2_index, FloorRatio(integer_trail_->LevelZeroUpperBound(affine_ub),
1428 lin_expr_gcd));
1429 return false; // Not important.
1430 }
1431
1432 // We have gcd * canonical_expr <= affine_ub,
1433 // so we do need to store a "divisor".
1434 if (lin2_index >= best_affine_ub_.size()) {
1435 best_affine_ub_.resize(lin2_index.value() + 1, {AffineExpression(), 0});
1436 }
1437 auto& [old_affine_ub, old_divisor] = best_affine_ub_[lin2_index];
1438 if (old_divisor != 0) {
1439 // We have an affine bound for this expr in the map. Can be exactly the
1440 // same, a better one or a worse one.
1441 //
1442 // Note that we expect exactly the same most of the time as it should be
1443 // rare to have many linear3 "competing" for the same linear2 bound.
1444 if (old_affine_ub == affine_ub && old_divisor == lin_expr_gcd) {
1445 linear2_watcher_->NotifyBoundChanged(
1446 lin2_indices_->GetExpression(lin2_index));
1447 return false;
1448 }
1449
1450 const IntegerValue new_ub =
1451 FloorRatioWithTest(integer_trail_->UpperBound(affine_ub), lin_expr_gcd);
1452 const IntegerValue old_ub = FloorRatioWithTest(
1453 integer_trail_->UpperBound(old_affine_ub), old_divisor);
1454 if (old_ub <= new_ub) return false; // old bound is better.
1455
1456 best_affine_ub_[lin2_index] = {affine_ub, lin_expr_gcd}; // Overwrite.
1457 } else {
1458 // Note that this should almost never happen (only once per lin2).
1459 best_affine_ub_[lin2_index] = {affine_ub, lin_expr_gcd};
1460 }
1461
1462 ++num_affine_updates_;
1463 linear2_watcher_->NotifyBoundChanged(
1464 lin2_indices_->GetExpression(lin2_index));
1465 return true;
1466}
1467
1469 LinearExpression2Index lin2_index) const {
1470 if (lin2_index >= best_affine_ub_.size()) return kMaxIntegerValue;
1471 auto [affine, divisor] = best_affine_ub_[lin2_index];
1472 if (divisor == 0) return kMaxIntegerValue;
1473 return FloorRatio(integer_trail_->UpperBound(affine), divisor);
1474}
1475
1477 LinearExpression2Index lin2_index, IntegerValue ub,
1478 std::vector<Literal>* /*literal_reason*/,
1479 std::vector<IntegerLiteral>* integer_reason) const {
1480 DCHECK_LE(GetUpperBoundFromLinear3(lin2_index), ub);
1481 DCHECK_LT(lin2_index, best_affine_ub_.size());
1482
1483 // We want the reason for "expr <= ub"
1484 // knowing that expr <= affine / divisor.
1485 const auto [affine, divisor] = best_affine_ub_[lin2_index];
1486 DCHECK_NE(divisor, 0);
1487 integer_reason->push_back(affine.LowerOrEqual(CapProdI(ub + 1, divisor) - 1));
1488}
1489
1491 if (!VLOG_IS_ON(1)) return;
1492 std::vector<std::pair<std::string, int64_t>> stats;
1493 stats.push_back({"Linear2Bounds/enqueue_trivial", enqueue_trivial_});
1494 stats.push_back({"Linear2Bounds/enqueue_degenerate", enqueue_degenerate_});
1495 stats.push_back({"Linear2Bounds/enqueue_true_at_root_level",
1496 enqueue_true_at_root_level_});
1497 stats.push_back({"Linear2Bounds/enqueue_conflict_false_at_root_level",
1498 enqueue_conflict_false_at_root_level_});
1499 stats.push_back({"Linear2Bounds/enqueue_individual_var_bounds",
1500 enqueue_individual_var_bounds_});
1501 stats.push_back(
1502 {"Linear2Bounds/enqueue_literal_encoding", enqueue_literal_encoding_});
1503 stats.push_back({"Linear2Bounds/enqueue_integer_linear3_encoding",
1504 enqueue_integer_linear3_encoding_});
1505 shared_stats_->AddStats(stats);
1506}
1507
1509 LinearExpression2Index lin2_index) const {
1510 return std::min(
1511 NonTrivialUpperBound(lin2_index),
1512 integer_trail_->UpperBound(lin2_indices_->GetExpression(lin2_index)));
1513}
1514
1517 if (expr.coeffs[0] == 0) {
1518 return integer_trail_->UpperBound(expr);
1519 }
1520 DCHECK_NE(expr.coeffs[1], 0);
1521 const IntegerValue gcd = expr.DivideByGcd();
1522 IntegerValue ub = integer_trail_->UpperBound(expr);
1523 // TODO(user): remove this when the UB in the trail in the current level is
1524 // never worse than in the root level.
1525 ub = std::min(ub, integer_trail_->LevelZeroUpperBound(expr));
1526 const LinearExpression2Index index = lin2_indices_->GetIndex(expr);
1527 if (index != kNoLinearExpression2Index) {
1528 ub = std::min(ub, root_level_bounds_->GetUpperBoundNoTrail(index));
1529 ub = std::min(ub, enforced_bounds_->GetUpperBoundFromEnforced(index));
1530 ub = std::min(ub, linear3_bounds_->GetUpperBoundFromLinear3(index));
1531 }
1532 return CapProdI(gcd, ub);
1533}
1534
1536 LinearExpression2 expr, IntegerValue ub,
1537 std::vector<Literal>* literal_reason,
1538 std::vector<IntegerLiteral>* integer_reason) const {
1539 DCHECK_LE(UpperBound(expr), ub);
1540
1541 // Explanation are by order of preference, with no reason needed first.
1542 if (integer_trail_->LevelZeroUpperBound(expr) <= ub) {
1543 return;
1544 }
1546 const IntegerValue gcd = expr.DivideByGcd();
1547 ub = FloorRatio(ub, gcd);
1548 const LinearExpression2Index index = lin2_indices_->GetIndex(expr);
1549 if (index != kNoLinearExpression2Index) {
1550 // No reason.
1551 if (root_level_bounds_->GetUpperBoundNoTrail(index) <= ub) {
1552 return;
1553 }
1554 // This one is a single literal.
1555 if (enforced_bounds_->GetUpperBoundFromEnforced(index) <= ub) {
1556 return enforced_bounds_->AddReasonForUpperBoundLowerThan(
1557 index, ub, literal_reason, integer_reason);
1558 }
1559 // This one is a single var upper bound.
1560 if (linear3_bounds_->GetUpperBoundFromLinear3(index) <= ub) {
1561 return linear3_bounds_->AddReasonForUpperBoundLowerThan(
1562 index, ub, literal_reason, integer_reason);
1563 }
1564 }
1565
1566 // Trivial linear2 bounds from its variables.
1567 const IntegerValue implied_ub = integer_trail_->UpperBound(expr);
1568 const IntegerValue slack = ub - implied_ub;
1569 DCHECK_GE(slack, 0);
1570 expr.Negate(); // AppendRelaxedLinearReason() explains a lower bound.
1571 absl::Span<const IntegerVariable> vars = expr.non_zero_vars();
1572 absl::Span<const IntegerValue> coeffs = expr.non_zero_coeffs();
1573 integer_trail_->AppendRelaxedLinearReason(slack, coeffs, vars,
1574 integer_reason);
1575}
1576
1578 IntegerValue ub) const {
1580 const IntegerValue gcd = expr.DivideByGcd();
1581 const LinearExpression2Index index = lin2_indices_->GetIndex(expr);
1582 IntegerValue known_ub;
1583 IntegerValue known_lb;
1584 if (index == kNoLinearExpression2Index) {
1585 known_ub = CapProdI(gcd, integer_trail_->UpperBound(expr));
1586 expr.Negate();
1587 known_lb = -CapProdI(gcd, integer_trail_->UpperBound(expr));
1588 } else {
1589 known_ub = CapProdI(gcd, UpperBound(index));
1590 known_lb = -CapProdI(gcd, UpperBound(NegationOf(index)));
1591 }
1592 if (lb <= known_lb && ub >= known_ub) return RelationStatus::IS_TRUE;
1593 if (lb > known_ub || ub < known_lb) return RelationStatus::IS_FALSE;
1594
1596}
1597
1599 LinearExpression2 expr, IntegerValue ub,
1600 absl::Span<const Literal> literal_reason,
1601 absl::Span<const IntegerLiteral> integer_reason) {
1602 using ReifiedBoundType = ReifiedLinear2Bounds::ReifiedBoundType;
1604 const IntegerValue gcd = expr.DivideByGcd();
1605 ub = FloorRatio(ub, gcd);
1606 // We have many different scenarios here, each one pushing something different
1607 // in the trail.
1608
1609 // Trivial.
1610 if (expr.coeffs[0] == 0 && expr.coeffs[1] == 0) {
1611 ++enqueue_trivial_;
1612 if (ub >= 0) {
1613 return true;
1614 } else {
1615 return integer_trail_->ReportConflict(literal_reason, integer_reason);
1616 }
1617 }
1618
1619 // Degenerate single variable case, just push the IntegerLiteral.
1620 if (expr.coeffs[0] == 0) {
1621 ++enqueue_degenerate_;
1622 return integer_trail_->Enqueue(
1623 IntegerLiteral::LowerOrEqual(expr.vars[1], ub), literal_reason,
1624 integer_reason);
1625 }
1626
1627 // TODO(user): also check partially-encoded bounds, e.g. (expr <= ub) => l,
1628 // which might be in ConditionalLinear2Bounds as ~l => (-expr <= - ub - 1).
1629 const auto reified_bound = reified_lin2_bounds_->GetEncodedBound(expr, ub);
1630
1631 // Already true.
1632 if (std::holds_alternative<ReifiedBoundType>(reified_bound) &&
1633 std::get<ReifiedBoundType>(reified_bound) ==
1634 ReifiedBoundType::kAlwaysTrue) {
1635 ++enqueue_true_at_root_level_;
1636 return true;
1637 }
1638
1639 // Conflict (ub < lb).
1640 if (std::holds_alternative<ReifiedBoundType>(reified_bound) &&
1641 std::get<ReifiedBoundType>(reified_bound) ==
1642 ReifiedBoundType::kAlwaysFalse) {
1643 ++enqueue_conflict_false_at_root_level_;
1644 // It's false at level zero, so we can just report a conflict.
1645 return integer_trail_->ReportConflict(literal_reason, integer_reason);
1646 }
1647
1648 // Now all the cases below are pushing a proper linear2 bound. If we are at
1649 // level zero, store this in the root_level_bounds_.
1650 if (trail_->CurrentDecisionLevel() == 0) {
1651 root_level_bounds_->AddUpperBound(expr, ub);
1652 }
1653
1654 // We don't have anything encoding this linear2 bound. Push the bounds of
1655 // its two variables.
1656 if (std::holds_alternative<ReifiedBoundType>(reified_bound) &&
1657 std::get<ReifiedBoundType>(reified_bound) ==
1658 ReifiedBoundType::kNoLiteralStored) {
1659 // TODO(user): create a Linear2 trail and enqueue this bound there.
1660 ++enqueue_individual_var_bounds_;
1661
1662 const ReasonIndex reason_index =
1663 integer_trail_->AppendReasonToInternalBuffers(literal_reason,
1664 integer_reason);
1665 if (reason_index >= saved_reasons_.size()) {
1666 saved_reasons_.resize(reason_index + 1);
1667 }
1668
1669 // We require positive coefficients here.
1670 CHECK_GT(expr.coeffs[0], 0);
1671 CHECK_GT(expr.coeffs[1], 0);
1672 saved_reasons_[reason_index] = expr;
1673
1674 // This is a simple linear2 propagation with lazy reason.
1675 // It will be explained by Explain() below.
1676 const IntegerValue min_activity =
1677 integer_trail_->LowerBound(expr.vars[0]) * expr.coeffs[0] +
1678 integer_trail_->LowerBound(expr.vars[1]) * expr.coeffs[1];
1679 const IntegerValue linear_slack = ub - min_activity;
1680 CHECK_GE(linear_slack, 0); // Conflict should already be dealt with.
1681 for (int i = 0; i < 2; ++i) {
1682 const IntegerValue div = linear_slack / expr.coeffs[i];
1683 const IntegerValue new_ub =
1684 integer_trail_->LowerBound(expr.vars[i]) + div;
1685 const IntegerValue propagation_slack =
1686 (div + 1) * expr.coeffs[i] - linear_slack - 1;
1687 if (!integer_trail_->EnqueueWithLazyReason(
1688 IntegerLiteral::LowerOrEqual(expr.vars[i], new_ub),
1689 reason_index.value(), propagation_slack, this)) {
1690 return false;
1691 }
1692 }
1693 return true;
1694 }
1695
1696 // We have a literal encoding for this linear2 bound, push it.
1697 if (std::holds_alternative<Literal>(reified_bound)) {
1698 ++enqueue_literal_encoding_;
1699 const Literal literal = std::get<Literal>(reified_bound);
1700 if (!integer_trail_->SafeEnqueueLiteral(literal, literal_reason,
1701 integer_reason)) {
1702 return false;
1703 }
1704 enforced_bounds_->PushConditionalRelation({literal}, expr, ub);
1705 return true;
1706 }
1707
1708 // We can encode this linear2 bound with an IntegerLiteral (probably coming
1709 // from a linear3). Push the IntegerLiteral.
1710 if (std::holds_alternative<IntegerLiteral>(reified_bound)) {
1711 ++enqueue_integer_linear3_encoding_;
1712 // Immediately push the affine bound to Linear2BoundsFromLinear3 so that the
1713 // new linear2 bound is visible without needing to wait for the propagation
1714 // fix point.
1715 const auto [affine, divisor] =
1716 reified_lin2_bounds_->GetLinear3Bound(lin2_indices_->GetIndex(expr));
1717 linear3_bounds_->AddAffineUpperBound(lin2_indices_->GetIndex(expr), divisor,
1718 affine);
1719
1720 const IntegerLiteral literal = std::get<IntegerLiteral>(reified_bound);
1721 return integer_trail_->Enqueue(literal, literal_reason, integer_reason);
1722 }
1723 LOG(FATAL) << "Unknown reified bound type";
1724 return false;
1725}
1726
1727void Linear2Bounds::Explain(int id, IntegerLiteral /*to_explain*/,
1728 IntegerReason* reason) {
1729 const ReasonIndex reason_index(id);
1731 integer_trail_->LiteralReasonAsSpan(reason_index);
1732 reason->integer_literals = integer_trail_->IntegerReasonAsSpan(reason_index);
1733
1734 // Recover the linear reason from our LinearExpression2 expr.
1735 // Slack should already be set.
1736 reason->vars = saved_reasons_[reason_index].non_zero_vars();
1737 reason->coeffs = saved_reasons_[reason_index].non_zero_coeffs();
1738}
1739
1740} // namespace sat
1741} // namespace operations_research
static IntegralType CeilOfRatio(IntegralType numerator, IntegralType denominator)
Definition mathutil.h:39
static IntegralType FloorOfRatio(IntegralType numerator, IntegralType denominator)
Definition mathutil.h:53
void ResetFromFlatMapping(Keys keys, Values values, int minimum_num_nodes=0)
Definition util.h:1046
void AddPartialRelation(Literal lit, IntegerVariable a, IntegerVariable b)
absl::Span< const int > IndicesOfRelationsEnforcedBy(LiteralIndex lit) const
void Add(Literal lit, LinearExpression2 expr, IntegerValue lhs, IntegerValue rhs)
const Relation & relation(int index) const
void PushConditionalRelation(absl::Span< const Literal > enforcements, LinearExpression2Index index, IntegerValue rhs)
IntegerValue GetUpperBoundFromEnforced(LinearExpression2Index index) const
void AddReasonForUpperBoundLowerThan(LinearExpression2Index index, IntegerValue ub, std::vector< Literal > *literal_reason, std::vector< IntegerLiteral > *integer_reason) const
void CollectPrecedences(absl::Span< const IntegerVariable > vars, std::vector< PrecedenceData > *output)
int AddGreaterThanAtLeastOneOfConstraints(Model *model, bool auto_detect_clauses=false)
const std::vector< ImpliedBoundEntry > & GetImpliedBounds(IntegerVariable var)
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition integer.h:1657
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition integer.h:1650
IntegerValue GetUpperBoundFromLinear3(LinearExpression2Index lin2_index) const
bool AddAffineUpperBound(LinearExpression2Index lin2_index, IntegerValue lin_expr_gcd, AffineExpression affine_ub)
void AddReasonForUpperBoundLowerThan(LinearExpression2Index lin2_index, IntegerValue ub, std::vector< Literal > *literal_reason, std::vector< IntegerLiteral > *integer_reason) const
void Explain(int id, IntegerLiteral to_explain, IntegerReason *reason) final
RelationStatus GetStatus(LinearExpression2 expr, IntegerValue lb, IntegerValue ub) const
bool EnqueueLowerOrEqual(LinearExpression2 expr, IntegerValue ub, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
void AddReasonForUpperBoundLowerThan(LinearExpression2 expr, IntegerValue ub, std::vector< Literal > *literal_reason, std::vector< IntegerLiteral > *integer_reason) const
IntegerValue NonTrivialUpperBound(LinearExpression2Index lin2_index) const
IntegerValue UpperBound(LinearExpression2 expr) const
LinearExpression2Index AddOrGet(LinearExpression2 expr)
int64_t VarTimestamp(IntegerVariable var)
void NotifyBoundChanged(LinearExpression2 expr)
LiteralIndex NegatedIndex() const
Definition sat_base.h:93
LiteralIndex Index() const
Definition sat_base.h:92
BooleanVariable Variable() const
Definition sat_base.h:88
std::pair< AffineExpression, IntegerValue > GetLinear3Bound(LinearExpression2Index lin2_index) const
void AddBoundEncodingIfNonTrivial(Literal l, LinearExpression2 expr, IntegerValue ub)
std::variant< ReifiedBoundType, Literal, IntegerLiteral > GetEncodedBound(LinearExpression2 expr, IntegerValue ub)
void AddLinear3(absl::Span< const IntegerVariable > vars, absl::Span< const int64_t > coeffs, int64_t activity)
RelationStatus GetLevelZeroStatus(LinearExpression2 expr, IntegerValue lb, IntegerValue ub) const
int AugmentSimpleRelations(IntegerVariable var, int work_limit)
absl::Span< const std::pair< IntegerVariable, LinearExpression2Index > > GetVariablesInSimpleRelation(IntegerVariable var) const
std::vector< std::tuple< LinearExpression2, IntegerValue, IntegerValue > > GetAllBoundsContainingVariable(IntegerVariable var) const
bool AddUpperBound(LinearExpression2 expr, IntegerValue ub)
IntegerValue GetUpperBoundNoTrail(LinearExpression2Index index) const
std::vector< std::pair< LinearExpression2, IntegerValue > > GetSortedNonTrivialUpperBounds() const
std::vector< std::tuple< LinearExpression2, IntegerValue, IntegerValue > > GetAllBoundsContainingVariables(IntegerVariable var1, IntegerVariable var2) const
IntegerValue LevelZeroUpperBound(LinearExpression2 expr) const
void ComputeFullPrecedences(absl::Span< const IntegerVariable > vars, std::vector< FullIntegerPrecedence > *output)
bool GetNext(int *next_node_index, bool *cyclic, std::vector< int > *output_cycle_nodes=nullptr)
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntType IntTypeAbs(IntType t)
bool PropagateLocalBounds(const IntegerTrail &integer_trail, const RootLevelLinear2Bounds &root_level_bounds, const ConditionalLinear2Bounds &repository, const ImpliedBounds &implied_bounds, Literal lit, const absl::flat_hash_map< IntegerVariable, IntegerValue > &input, absl::flat_hash_map< IntegerVariable, IntegerValue > *output)
const LiteralIndex kNoLiteralIndex(-1)
const LinearExpression2Index kNoLinearExpression2Index(-1)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Definition integer.cc:52
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
IntegerVariable PositiveVariable(IntegerVariable i)
IntegerValue FloorRatioWithTest(IntegerValue dividend, IntegerValue positive_divisor)
bool VariableIsPositive(IntegerVariable i)
IntegerValue CapProdI(IntegerValue a, IntegerValue b)
OR-Tools root namespace.
ClosedInterval::Iterator end(ClosedInterval interval)
const bool DEBUG_MODE
Definition radix_sort.h:266
if(!yyg->yy_init)
Definition parser.yy.cc:966
static int input(yyscan_t yyscanner)
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
absl::Span< const Literal > boolean_literals_at_false
Definition integer.h:441
absl::Span< const IntegerValue > coeffs
Definition integer.h:457
absl::Span< const IntegerVariable > vars
Definition integer.h:456
absl::Span< const IntegerLiteral > integer_literals
Definition integer.h:442
std::vector< std::function< bool()> > callbacks
absl::Span< const IntegerValue > non_zero_coeffs() const
absl::Span< const IntegerVariable > non_zero_vars() const
bool CanonicalizeAndUpdateBounds(IntegerValue &lb, IntegerValue &ub)
::util::DenseIntStableTopologicalSorter DenseIntStableTopologicalSorter
#define SOLVER_LOG(logger,...)
Definition logging.h:114