Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
implied_bounds.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <stddef.h>
17#include <stdint.h>
18
19#include <algorithm>
20#include <array>
21#include <bitset>
22#include <string>
23#include <utility>
24#include <vector>
25
26#include "absl/container/btree_map.h"
27#include "absl/container/flat_hash_map.h"
28#include "absl/container/flat_hash_set.h"
29#include "absl/log/check.h"
30#include "absl/meta/type_traits.h"
31#include "absl/strings/str_cat.h"
32#include "absl/types/span.h"
36#include "ortools/sat/clause.h"
37#include "ortools/sat/integer.h"
39#include "ortools/sat/model.h"
41#include "ortools/sat/sat_parameters.pb.h"
44#include "ortools/util/bitset.h"
47
48namespace operations_research {
49namespace sat {
50
51// Just display some global statistics on destruction.
53 if (!VLOG_IS_ON(1)) return;
54 if (shared_stats_ == nullptr) return;
55 std::vector<std::pair<std::string, int64_t>> stats;
56 stats.push_back({"implied_bound/num_deductions", num_deductions_});
57 stats.push_back({"implied_bound/num_stored", bounds_.size()});
58 stats.push_back(
59 {"implied_bound/num_stored_with_view", num_enqueued_in_var_to_bounds_});
60 shared_stats_->AddStats(stats);
61}
62
64 if (!parameters_.use_implied_bounds()) return true;
65 const IntegerVariable var = integer_literal.var;
66
67 // Ignore any Add() with a bound worse than the level zero one.
68 // TODO(user): Check that this never happen? it shouldn't.
69 const IntegerValue root_lb = integer_trail_->LevelZeroLowerBound(var);
70 if (integer_literal.bound <= root_lb) return true;
71
72 // We skip any IntegerLiteral referring to a variable with only two
73 // consecutive possible values. This is because, once shifted this will
74 // already be a variable in [0, 1] so we shouldn't gain much by substituing
75 // it.
76 if (root_lb + 1 >= integer_trail_->LevelZeroUpperBound(var)) return true;
77
78 // Add or update the current bound.
79 const auto key = std::make_pair(literal.Index(), var);
80 auto insert_result = bounds_.insert({key, integer_literal.bound});
81 if (!insert_result.second) {
82 if (insert_result.first->second < integer_literal.bound) {
83 insert_result.first->second = integer_literal.bound;
84 } else {
85 // No new info.
86 return true;
87 }
88 }
89
90 // Checks if the variable is now fixed.
91 if (integer_trail_->LevelZeroUpperBound(var) == integer_literal.bound) {
93 } else {
94 const auto it =
95 bounds_.find(std::make_pair(literal.Index(), NegationOf(var)));
96 if (it != bounds_.end() && it->second == -integer_literal.bound) {
98 }
99 }
100
101 // Check if we have any deduction. Since at least one of (literal,
102 // literal.Negated()) must be true, we can take the min bound as valid at
103 // level zero.
104 //
105 // TODO(user): Like in probing, we can also create hole in the domain if there
106 // is some implied bounds for (literal.NegatedIndex, NegagtionOf(var)) that
107 // crosses integer_literal.bound.
108 const auto it = bounds_.find(std::make_pair(literal.NegatedIndex(), var));
109 if (it != bounds_.end()) {
110 if (it->second <= root_lb) {
111 // The other bounds is worse than the new level-zero bound which can
112 // happen because of lazy update, so here we just remove it.
113 bounds_.erase(it);
114 } else {
115 const IntegerValue deduction =
116 std::min(integer_literal.bound, it->second);
117 DCHECK_GT(deduction, root_lb);
118
119 ++num_deductions_;
120 if (!integer_trail_->RootLevelEnqueue(
121 IntegerLiteral::GreaterOrEqual(var, deduction))) {
122 return false;
123 }
124
125 VLOG(2) << "Deduction old: "
127 var, integer_trail_->LevelZeroLowerBound(var))
128 << " new: " << IntegerLiteral::GreaterOrEqual(var, deduction);
129
130 // The entries that are equal to the min no longer need to be stored once
131 // the level zero bound is enqueued.
132 if (it->second == deduction) {
133 bounds_.erase(it);
134 }
135 if (integer_literal.bound == deduction) {
136 bounds_.erase(std::make_pair(literal.Index(), var));
137
138 // No need to update var_to_bounds_ in this case.
139 return true;
140 }
141 }
142 }
143
144 // The information below is currently only used for cuts.
145 // So no need to store it if we aren't going to use it.
146 if (parameters_.linearization_level() == 0) return true;
147 if (parameters_.cut_level() == 0) return true;
148
149 // If we have a new implied bound and the literal has a view, add it to
150 // var_to_bounds_. Note that we might add more than one entry with the same
151 // literal_view, and we will later need to lazily clean the vector up.
152 if (integer_encoder_->GetLiteralView(literal) != kNoIntegerVariable) {
153 if (var_to_bounds_.size() <= var) {
154 var_to_bounds_.resize(var.value() + 1);
155 has_implied_bounds_.Resize(var + 1);
156 }
157 ++num_enqueued_in_var_to_bounds_;
158 has_implied_bounds_.Set(var);
159 var_to_bounds_[var].push_back({integer_encoder_->GetLiteralView(literal),
160 integer_literal.bound, true});
161 } else if (integer_encoder_->GetLiteralView(literal.Negated()) !=
163 if (var_to_bounds_.size() <= var) {
164 var_to_bounds_.resize(var.value() + 1);
165 has_implied_bounds_.Resize(var + 1);
166 }
167 ++num_enqueued_in_var_to_bounds_;
168 has_implied_bounds_.Set(var);
169 var_to_bounds_[var].push_back(
170 {integer_encoder_->GetLiteralView(literal.Negated()),
171 integer_literal.bound, false});
172 }
173 return true;
174}
175
176const std::vector<ImpliedBoundEntry>& ImpliedBounds::GetImpliedBounds(
177 IntegerVariable var) {
178 if (var >= var_to_bounds_.size()) return empty_implied_bounds_;
179
180 // Lazily remove obsolete entries from the vector.
181 //
182 // TODO(user): Check no duplicate and remove old entry if the enforcement
183 // is tighter.
184 int new_size = 0;
185 std::vector<ImpliedBoundEntry>& ref = var_to_bounds_[var];
186 const IntegerValue root_lb = integer_trail_->LevelZeroLowerBound(var);
187 for (const ImpliedBoundEntry& entry : ref) {
188 if (entry.lower_bound <= root_lb) continue;
189 ref[new_size++] = entry;
190 }
191 ref.resize(new_size);
192
193 return ref;
194}
195
197 IntegerVariable var,
198 IntegerValue value) {
199 if (!VariableIsPositive(var)) {
200 var = NegationOf(var);
201 value = -value;
202 }
203 literal_to_var_to_value_[literal.Index()][var] = value;
204}
205
207 if (!parameters_.use_implied_bounds()) return true;
208
209 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 1);
210 tmp_integer_literals_.clear();
211 integer_trail_->AppendNewBounds(&tmp_integer_literals_);
212 for (const IntegerLiteral lit : tmp_integer_literals_) {
213 if (!Add(first_decision, lit)) return false;
214 }
215 return true;
216}
217
218void ElementEncodings::Add(IntegerVariable var,
219 const std::vector<ValueLiteralPair>& encoding,
220 int exactly_one_index) {
221 if (!var_to_index_to_element_encodings_.contains(var)) {
222 element_encoded_variables_.push_back(var);
223 }
224 var_to_index_to_element_encodings_[var][exactly_one_index] = encoding;
225}
226
227const absl::btree_map<int, std::vector<ValueLiteralPair>>&
228ElementEncodings::Get(IntegerVariable var) {
229 const auto& it = var_to_index_to_element_encodings_.find(var);
230 if (it == var_to_index_to_element_encodings_.end()) {
231 return empty_element_encoding_;
232 } else {
233 return it->second;
234 }
235}
236
237const std::vector<IntegerVariable>&
239 return element_encoded_variables_;
240}
241
242// If a variable has a size of 2, it is most likely reduced to an affine
243// expression pointing to a variable with domain [0,1] or [-1,0].
244// If the original variable has been removed from the model, then there are no
245// implied values from any exactly_one constraint to its domain.
246// If we are lucky, one of the literal of the exactly_one constraints, and its
247// negation are used to encode the Boolean variable of the affine.
248//
249// This may fail if exactly_one(l0, l1, l2, l3); l0 and l1 imply x = 0,
250// l2 and l3 imply x = 1. In that case, one must look at the binary
251// implications to find the missing link.
252//
253// TODO(user): Consider removing this once we are more complete in our implied
254// bounds repository. Because if we can reconcile an encoding, then any of the
255// literal in the at most one should imply a value on the boolean view use in
256// the size2 affine.
257std::vector<LiteralValueValue> TryToReconcileEncodings(
258 const AffineExpression& size2_affine, const AffineExpression& affine,
259 absl::Span<const ValueLiteralPair> affine_var_encoding,
260 bool put_affine_left_in_result, IntegerEncoder* integer_encoder) {
261 IntegerVariable binary = size2_affine.var;
262 std::vector<LiteralValueValue> terms;
263 if (!integer_encoder->VariableIsFullyEncoded(binary)) return terms;
264 const std::vector<ValueLiteralPair>& size2_enc =
265 integer_encoder->FullDomainEncoding(binary);
266
267 // TODO(user): I am not sure how this can happen since size2_affine is
268 // supposed to be non-fixed. Maybe we miss some propag. Investigate.
269 if (size2_enc.size() != 2) return terms;
270
271 Literal lit0 = size2_enc[0].literal;
272 IntegerValue value0 = size2_affine.ValueAt(size2_enc[0].value);
273 Literal lit1 = size2_enc[1].literal;
274 IntegerValue value1 = size2_affine.ValueAt(size2_enc[1].value);
275
276 for (const auto& [unused, candidate_literal] : affine_var_encoding) {
277 if (candidate_literal == lit1) {
278 std::swap(lit0, lit1);
279 std::swap(value0, value1);
280 }
281 if (candidate_literal != lit0) continue;
282
283 // Build the decomposition.
284 for (const auto& [value, literal] : affine_var_encoding) {
285 const IntegerValue size_2_value = literal == lit0 ? value0 : value1;
286 const IntegerValue affine_value = affine.ValueAt(value);
287 if (put_affine_left_in_result) {
288 terms.push_back({literal, affine_value, size_2_value});
289 } else {
290 terms.push_back({literal, size_2_value, affine_value});
291 }
292 }
293 break;
294 }
295
296 return terms;
297}
298
299// Specialized case of encoding reconciliation when both variables have a domain
300// of size of 2.
301std::vector<LiteralValueValue> TryToReconcileSize2Encodings(
302 const AffineExpression& left, const AffineExpression& right,
303 IntegerEncoder* integer_encoder) {
304 std::vector<LiteralValueValue> terms;
305 if (!integer_encoder->VariableIsFullyEncoded(left.var) ||
306 !integer_encoder->VariableIsFullyEncoded(right.var)) {
307 return terms;
308 }
309 const std::vector<ValueLiteralPair> left_enc =
310 integer_encoder->FullDomainEncoding(left.var);
311 const std::vector<ValueLiteralPair> right_enc =
312 integer_encoder->FullDomainEncoding(right.var);
313 if (left_enc.size() != 2 || right_enc.size() != 2) {
314 VLOG(2) << "encodings are not fully propagated";
315 return terms;
316 }
317
318 const Literal left_lit0 = left_enc[0].literal;
319 const IntegerValue left_value0 = left.ValueAt(left_enc[0].value);
320 const Literal left_lit1 = left_enc[1].literal;
321 const IntegerValue left_value1 = left.ValueAt(left_enc[1].value);
322
323 const Literal right_lit0 = right_enc[0].literal;
324 const IntegerValue right_value0 = right.ValueAt(right_enc[0].value);
325 const Literal right_lit1 = right_enc[1].literal;
326 const IntegerValue right_value1 = right.ValueAt(right_enc[1].value);
327
328 if (left_lit0 == right_lit0 || left_lit0 == right_lit1.Negated()) {
329 terms.push_back({left_lit0, left_value0, right_value0});
330 terms.push_back({left_lit0.Negated(), left_value1, right_value1});
331 } else if (left_lit0 == right_lit1 || left_lit0 == right_lit0.Negated()) {
332 terms.push_back({left_lit0, left_value0, right_value1});
333 terms.push_back({left_lit0.Negated(), left_value1, right_value0});
334 } else if (left_lit1 == right_lit1 || left_lit1 == right_lit0.Negated()) {
335 terms.push_back({left_lit1.Negated(), left_value0, right_value0});
336 terms.push_back({left_lit1, left_value1, right_value1});
337 } else if (left_lit1 == right_lit0 || left_lit1 == right_lit1.Negated()) {
338 terms.push_back({left_lit1.Negated(), left_value0, right_value1});
339 terms.push_back({left_lit1, left_value1, right_value0});
340 } else {
341 VLOG(3) << "Complex size 2 encoding case, need to scan exactly_ones";
342 }
343
344 return terms;
345}
346
347std::vector<LiteralValueValue> ProductDecomposer::TryToDecompose(
348 const AffineExpression& left, const AffineExpression& right) {
349 if (integer_trail_->IsFixed(left) || integer_trail_->IsFixed(right)) {
350 return {};
351 }
352
353 // Fill in the encodings for the left variable.
354 const absl::btree_map<int, std::vector<ValueLiteralPair>>& left_encodings =
355 element_encodings_->Get(left.var);
356
357 // Fill in the encodings for the right variable.
358 const absl::btree_map<int, std::vector<ValueLiteralPair>>& right_encodings =
359 element_encodings_->Get(right.var);
360
361 std::vector<int> compatible_keys;
362 for (const auto& [index, encoding] : left_encodings) {
363 if (right_encodings.contains(index)) {
364 compatible_keys.push_back(index);
365 }
366 }
367
368 if (compatible_keys.empty()) {
369 if (integer_trail_->InitialVariableDomain(left.var).Size() == 2) {
370 for (const auto& [index, right_encoding] : right_encodings) {
371 const std::vector<LiteralValueValue> result = TryToReconcileEncodings(
372 left, right, right_encoding,
373 /*put_affine_left_in_result=*/false, integer_encoder_);
374 if (!result.empty()) {
375 return result;
376 }
377 }
378 }
379 if (integer_trail_->InitialVariableDomain(right.var).Size() == 2) {
380 for (const auto& [index, left_encoding] : left_encodings) {
381 const std::vector<LiteralValueValue> result = TryToReconcileEncodings(
382 right, left, left_encoding,
383 /*put_affine_left_in_result=*/true, integer_encoder_);
384 if (!result.empty()) {
385 return result;
386 }
387 }
388 }
389 if (integer_trail_->InitialVariableDomain(left.var).Size() == 2 &&
390 integer_trail_->InitialVariableDomain(right.var).Size() == 2) {
391 const std::vector<LiteralValueValue> result =
392 TryToReconcileSize2Encodings(left, right, integer_encoder_);
393 if (!result.empty()) {
394 return result;
395 }
396 }
397 return {};
398 }
399
400 if (compatible_keys.size() > 1) {
401 VLOG(3) << "More than one exactly_one involved in the encoding of the two "
402 "variables";
403 }
404
405 // Select the compatible encoding with the minimum index.
406 const int min_index =
407 *std::min_element(compatible_keys.begin(), compatible_keys.end());
408 // By construction, encodings follow the order of literals in the exactly_one
409 // constraint.
410 const std::vector<ValueLiteralPair>& left_encoding =
411 left_encodings.at(min_index);
412 const std::vector<ValueLiteralPair>& right_encoding =
413 right_encodings.at(min_index);
414 DCHECK_EQ(left_encoding.size(), right_encoding.size());
415
416 // Build decomposition of the product.
417 std::vector<LiteralValueValue> terms;
418 for (int i = 0; i < left_encoding.size(); ++i) {
419 const Literal literal = left_encoding[i].literal;
420 DCHECK_EQ(literal, right_encoding[i].literal);
421 terms.push_back({literal, left.ValueAt(left_encoding[i].value),
422 right.ValueAt(right_encoding[i].value)});
423 }
424
425 return terms;
426}
427
428// TODO(user): Experiment with x * x where constants = 0, x is
429// fully encoded, and the domain is small.
431 const AffineExpression& right,
432 LinearConstraintBuilder* builder) {
433 DCHECK(builder != nullptr);
434 builder->Clear();
435
436 if (integer_trail_->IsFixed(left)) {
437 if (integer_trail_->IsFixed(right)) {
438 builder->AddConstant(integer_trail_->FixedValue(left) *
439 integer_trail_->FixedValue(right));
440 return true;
441 }
442 builder->AddTerm(right, integer_trail_->FixedValue(left));
443 return true;
444 }
445
446 if (integer_trail_->IsFixed(right)) {
447 builder->AddTerm(left, integer_trail_->FixedValue(right));
448 return true;
449 }
450
451 // Linearization is possible if both left and right have the same Boolean
452 // variable.
453 if (PositiveVariable(left.var) == PositiveVariable(right.var) &&
454 integer_trail_->LowerBound(PositiveVariable(left.var)) == 0 &&
455 integer_trail_->UpperBound(PositiveVariable(left.var)) == 1) {
456 const IntegerValue left_coeff =
457 VariableIsPositive(left.var) ? left.coeff : -left.coeff;
458 const IntegerValue right_coeff =
459 VariableIsPositive(right.var) ? right.coeff : -right.coeff;
460 builder->AddTerm(PositiveVariable(left.var),
461 left_coeff * right_coeff + left.constant * right_coeff +
462 left_coeff * right.constant);
463 builder->AddConstant(left.constant * right.constant);
464 return true;
465 }
466
467 const std::vector<LiteralValueValue> decomposition =
468 TryToDecompose(left, right);
469 if (decomposition.empty()) return false;
470
471 IntegerValue min_coefficient = kMaxIntegerValue;
472 for (const LiteralValueValue& term : decomposition) {
473 min_coefficient =
474 std::min(min_coefficient, term.left_value * term.right_value);
475 }
476 for (const LiteralValueValue& term : decomposition) {
477 const IntegerValue coefficient =
478 term.left_value * term.right_value - min_coefficient;
479 if (coefficient == 0) continue;
480 if (!builder->AddLiteralTerm(term.literal, coefficient)) {
481 return false;
482 }
483 }
484 builder->AddConstant(min_coefficient);
485 return true;
486}
487
489 : enabled_(
490 model->GetOrCreate<SatParameters>()->detect_linearized_product() &&
491 model->GetOrCreate<SatParameters>()->linearization_level() > 1),
492 rlt_enabled_(model->GetOrCreate<SatParameters>()->add_rlt_cuts() &&
493 model->GetOrCreate<SatParameters>()->linearization_level() >
494 1),
495 sat_solver_(model->GetOrCreate<SatSolver>()),
496 trail_(model->GetOrCreate<Trail>()),
497 integer_trail_(model->GetOrCreate<IntegerTrail>()),
498 integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
499 shared_stats_(model->GetOrCreate<SharedStatistics>()) {}
500
502 if (!VLOG_IS_ON(1)) return;
503 if (shared_stats_ == nullptr) return;
504 std::vector<std::pair<std::string, int64_t>> stats;
505 stats.push_back(
506 {"product_detector/num_processed_binary", num_processed_binary_});
507 stats.push_back(
508 {"product_detector/num_processed_exactly_one", num_processed_exo_});
509 stats.push_back(
510 {"product_detector/num_processed_ternary", num_processed_ternary_});
511 stats.push_back({"product_detector/num_trail_updates", num_trail_updates_});
512 stats.push_back({"product_detector/num_products", num_products_});
513 stats.push_back({"product_detector/num_conditional_equalities",
514 num_conditional_equalities_});
515 stats.push_back(
516 {"product_detector/num_conditional_zeros", num_conditional_zeros_});
517 stats.push_back({"product_detector/num_int_products", num_int_products_});
518 shared_stats_->AddStats(stats);
519}
520
522 absl::Span<const Literal> ternary_clause) {
523 if (ternary_clause.size() != 3) return;
524 ++num_processed_ternary_;
525
526 if (rlt_enabled_) ProcessTernaryClauseForRLT(ternary_clause);
527 if (!enabled_) return;
528
529 candidates_[GetKey(ternary_clause[0].Index(), ternary_clause[1].Index())]
530 .push_back(ternary_clause[2].Index());
531 candidates_[GetKey(ternary_clause[0].Index(), ternary_clause[2].Index())]
532 .push_back(ternary_clause[1].Index());
533 candidates_[GetKey(ternary_clause[1].Index(), ternary_clause[2].Index())]
534 .push_back(ternary_clause[0].Index());
535
536 // We mark the literal of the ternary clause as seen.
537 // Only a => b with a seen need to be looked at.
538 for (const Literal l : ternary_clause) {
539 if (l.Index() >= seen_.size()) seen_.resize(l.Index() + 1);
540 seen_[l.Index()] = true;
541 }
542}
543
544// If all have view, add to flat representation.
545void ProductDetector::ProcessTernaryClauseForRLT(
546 absl::Span<const Literal> ternary_clause) {
547 const int old_size = ternary_clauses_with_view_.size();
548 for (const Literal l : ternary_clause) {
549 const IntegerVariable var =
550 integer_encoder_->GetLiteralView(Literal(l.Variable(), true));
552 ternary_clauses_with_view_.resize(old_size);
553 return;
554 }
555 ternary_clauses_with_view_.push_back(l.IsPositive() ? var
556 : NegationOf(var));
557 }
558}
559
561 absl::Span<const Literal> ternary_exo) {
562 if (ternary_exo.size() != 3) return;
563 ++num_processed_exo_;
564
565 if (rlt_enabled_) ProcessTernaryClauseForRLT(ternary_exo);
566 if (!enabled_) return;
567
568 ProcessNewProduct(ternary_exo[0].Index(), ternary_exo[1].NegatedIndex(),
569 ternary_exo[2].NegatedIndex());
570 ProcessNewProduct(ternary_exo[1].Index(), ternary_exo[0].NegatedIndex(),
571 ternary_exo[2].NegatedIndex());
572 ProcessNewProduct(ternary_exo[2].Index(), ternary_exo[0].NegatedIndex(),
573 ternary_exo[1].NegatedIndex());
574}
575
576// TODO(user): As product are discovered, we could remove entries from our
577// hash maps!
579 absl::Span<const Literal> binary_clause) {
580 if (!enabled_) return;
581 if (binary_clause.size() != 2) return;
582 ++num_processed_binary_;
583 const std::array<LiteralIndex, 2> key =
584 GetKey(binary_clause[0].NegatedIndex(), binary_clause[1].NegatedIndex());
585 std::array<LiteralIndex, 3> ternary;
586 for (const LiteralIndex l : candidates_[key]) {
587 ternary[0] = key[0];
588 ternary[1] = key[1];
589 ternary[2] = l;
590 std::sort(ternary.begin(), ternary.end());
591 const int l_index = ternary[0] == l ? 0 : ternary[1] == l ? 1 : 2;
592 std::bitset<3>& bs = detector_[ternary];
593 if (bs[l_index]) continue;
594 bs[l_index] = true;
595 if (bs[0] && bs[1] && l_index != 2) {
596 ProcessNewProduct(ternary[2], Literal(ternary[0]).NegatedIndex(),
597 Literal(ternary[1]).NegatedIndex());
598 }
599 if (bs[0] && bs[2] && l_index != 1) {
600 ProcessNewProduct(ternary[1], Literal(ternary[0]).NegatedIndex(),
601 Literal(ternary[2]).NegatedIndex());
602 }
603 if (bs[1] && bs[2] && l_index != 0) {
604 ProcessNewProduct(ternary[0], Literal(ternary[1]).NegatedIndex(),
605 Literal(ternary[2]).NegatedIndex());
606 }
607 }
608}
609
611 if (!enabled_) return;
612 for (LiteralIndex a(0); a < seen_.size(); ++a) {
613 if (!seen_[a]) continue;
614 if (trail_->Assignment().LiteralIsAssigned(Literal(a))) continue;
615 const Literal not_a = Literal(a).Negated();
616 for (const Literal b : graph->DirectImplications(Literal(a))) {
617 ProcessBinaryClause({not_a, b}); // a => b;
618 }
619 }
620}
621
623 if (!enabled_) return;
624 if (trail_->CurrentDecisionLevel() != 1) return;
625 ++num_trail_updates_;
626
627 const SatSolver::Decision decision = sat_solver_->Decisions()[0];
628 if (decision.literal.Index() >= seen_.size() ||
629 !seen_[decision.literal.Index()]) {
630 return;
631 }
632 const Literal not_a = decision.literal.Negated();
633 const int current_index = trail_->Index();
634 for (int i = decision.trail_index + 1; i < current_index; ++i) {
635 const Literal b = (*trail_)[i];
636 ProcessBinaryClause({not_a, b});
637 }
638}
639
641 const auto it = products_.find(GetKey(a.Index(), b.Index()));
642 if (it == products_.end()) return kNoLiteralIndex;
643 return it->second;
644}
645
646std::array<LiteralIndex, 2> ProductDetector::GetKey(LiteralIndex a,
647 LiteralIndex b) const {
648 std::array<LiteralIndex, 2> key{a, b};
649 if (key[0] > key[1]) std::swap(key[0], key[1]);
650 return key;
651}
652
653void ProductDetector::ProcessNewProduct(LiteralIndex p, LiteralIndex a,
654 LiteralIndex b) {
655 // If many literal correspond to the same product, we just keep one.
656 ++num_products_;
657 products_[GetKey(a, b)] = p;
658
659 // This is used by ProductIsLinearizable().
660 has_product_.insert(
661 GetKey(Literal(a).IsPositive() ? a : Literal(a).NegatedIndex(),
662 Literal(b).IsPositive() ? b : Literal(b).NegatedIndex()));
663}
664
666 IntegerVariable b) const {
667 if (a == b) return true;
668 if (a == NegationOf(b)) return true;
669
670 // Otherwise, we need both a and b to be expressible as linear expression
671 // involving Booleans whose product is also expressible.
672 if (integer_trail_->InitialVariableDomain(a).Size() != 2) return false;
673 if (integer_trail_->InitialVariableDomain(b).Size() != 2) return false;
674
675 const LiteralIndex la =
677 a, integer_trail_->LevelZeroUpperBound(a)));
678 if (la == kNoLiteralIndex) return false;
679
680 const LiteralIndex lb =
682 b, integer_trail_->LevelZeroUpperBound(b)));
683 if (lb == kNoLiteralIndex) return false;
684
685 // Any product involving la/not(la) * lb/not(lb) can be used.
686 return has_product_.contains(
687 GetKey(Literal(la).IsPositive() ? la : Literal(la).NegatedIndex(),
688 Literal(lb).IsPositive() ? lb : Literal(lb).NegatedIndex()));
689}
690
692 IntegerVariable b) const {
693 const auto it = int_products_.find({a.Index(), PositiveVariable(b)});
694 if (it == int_products_.end()) return kNoIntegerVariable;
695 return VariableIsPositive(b) ? it->second : NegationOf(it->second);
696}
697
698void ProductDetector::ProcessNewProduct(IntegerVariable p, Literal l,
699 IntegerVariable x) {
700 if (!VariableIsPositive(x)) {
701 x = NegationOf(x);
702 p = NegationOf(p);
703 }
704
705 // We only store one product if there are many.
706 ++num_int_products_;
707 int_products_[{l.Index(), x}] = p;
708}
709
711 IntegerVariable y) {
712 ++num_conditional_equalities_;
713 if (x == y) return;
714
715 // We process both possibilities (product = x or product = y).
716 for (int i = 0; i < 2; ++i) {
717 if (!VariableIsPositive(x)) {
718 x = NegationOf(x);
719 y = NegationOf(y);
720 }
721 bool seen = false;
722
723 // TODO(user): Linear scan can be bad if b => X = many other variables.
724 // Hopefully this will not be common.
725 std::vector<IntegerVariable>& others =
726 conditional_equalities_[{l.Index(), x}];
727 for (const IntegerVariable o : others) {
728 if (o == y) {
729 seen = true;
730 break;
731 }
732 }
733
734 if (!seen) {
735 others.push_back(y);
736 if (conditional_zeros_.contains({l.NegatedIndex(), x})) {
737 ProcessNewProduct(/*p=*/x, l, y);
738 }
739 }
740 std::swap(x, y);
741 }
742}
743
745 ++num_conditional_zeros_;
746 p = PositiveVariable(p);
747 auto [_, inserted] = conditional_zeros_.insert({l.Index(), p});
748 if (inserted) {
749 const auto it = conditional_equalities_.find({l.NegatedIndex(), p});
750 if (it != conditional_equalities_.end()) {
751 for (const IntegerVariable x : it->second) {
752 ProcessNewProduct(p, l.Negated(), x);
753 }
754 }
755 }
756}
757
758namespace {
759
760std::pair<IntegerVariable, IntegerVariable> Canonicalize(IntegerVariable a,
761 IntegerVariable b) {
762 if (a < b) return {a, b};
763 return {b, a};
764}
765
766double GetLiteralLpValue(
767 IntegerVariable var,
769 return VariableIsPositive(var) ? lp_values[var]
770 : 1.0 - lp_values[PositiveVariable(var)];
771}
772
773} // namespace
774
775void ProductDetector::UpdateRLTMaps(
777 IntegerVariable var1, double lp1, IntegerVariable var2, double lp2,
778 IntegerVariable bound_var, double bound_lp) {
779 // we have var1 * var2 <= bound_var, and this is only useful if it is better
780 // than the trivial bound <= var1 or <= var2.
781 if (bound_lp > lp1 && bound_lp > lp2) return;
782
783 const auto [it, inserted] =
784 bool_rlt_ubs_.insert({Canonicalize(var1, var2), bound_var});
785
786 // Replace if better.
787 if (!inserted && bound_lp < GetLiteralLpValue(it->second, lp_values)) {
788 it->second = bound_var;
789 }
790
791 // This will increase a RLT cut violation and is a good candidate.
792 if (lp1 * lp2 > bound_lp + 1e-4) {
793 bool_rlt_candidates_[var1].push_back(var2);
794 bool_rlt_candidates_[var2].push_back(var1);
795 }
796}
797
798// TODO(user): limit work if too many ternary.
800 const absl::flat_hash_map<IntegerVariable, glop::ColIndex>& lp_vars,
802 // TODO(user): Maybe we shouldn't reconstruct this every time, but it is hard
803 // in case of multiple lps to make sure we don't use variables not in the lp
804 // otherwise.
805 bool_rlt_ubs_.clear();
806
807 // If we transform a linear constraint to sum positive_coeff * bool <= rhs.
808 // We will list all interesting multiplicative candidate for each variable.
809 bool_rlt_candidates_.clear();
810 const int size = ternary_clauses_with_view_.size();
811 for (int i = 0; i < size; i += 3) {
812 const IntegerVariable var1 = ternary_clauses_with_view_[i];
813 const IntegerVariable var2 = ternary_clauses_with_view_[i + 1];
814 const IntegerVariable var3 = ternary_clauses_with_view_[i + 2];
815
816 if (!lp_vars.contains(PositiveVariable(var1))) continue;
817 if (!lp_vars.contains(PositiveVariable(var2))) continue;
818 if (!lp_vars.contains(PositiveVariable(var3))) continue;
819
820 // If we have l1 + l2 + l3 >= 1, then for all (i, j) pair we have
821 // !li * !lj <= lk. We are looking for violation like this.
822 const double lp1 = GetLiteralLpValue(var1, lp_values);
823 const double lp2 = GetLiteralLpValue(var2, lp_values);
824 const double lp3 = GetLiteralLpValue(var3, lp_values);
825
826 UpdateRLTMaps(lp_values, NegationOf(var1), 1.0 - lp1, NegationOf(var2),
827 1.0 - lp2, var3, lp3);
828 UpdateRLTMaps(lp_values, NegationOf(var1), 1.0 - lp1, NegationOf(var3),
829 1.0 - lp3, var2, lp2);
830 UpdateRLTMaps(lp_values, NegationOf(var2), 1.0 - lp2, NegationOf(var3),
831 1.0 - lp3, var1, lp1);
832 }
833}
834
835} // namespace sat
836} // namespace operations_research
IntegerValue y
IntegerValue size
void Set(IntegerType index)
Definition bitset.h:864
void Resize(IntegerType size)
Definition bitset.h:850
void Add(IntegerVariable var, const std::vector< ValueLiteralPair > &encoding, int exactly_one_index)
const absl::btree_map< int, std::vector< ValueLiteralPair > > & Get(IntegerVariable var)
Returns an empty map if there is no such encoding.
const std::vector< IntegerVariable > & GetElementEncodedVariables() const
Get an unsorted set of variables appearing in element encodings.
bool ProcessIntegerTrail(Literal first_decision)
~ImpliedBounds()
Just display some global statistics on destruction.
void AddLiteralImpliesVarEqValue(Literal literal, IntegerVariable var, IntegerValue value)
Adds literal => var == value.
bool Add(Literal literal, IntegerLiteral integer_literal)
const std::vector< ImpliedBoundEntry > & GetImpliedBounds(IntegerVariable var)
const std::vector< ValueLiteralPair > & FullDomainEncoding(IntegerVariable var) const
Definition integer.cc:145
IntegerVariable GetLiteralView(Literal lit) const
Definition integer.h:618
bool VariableIsFullyEncoded(IntegerVariable var) const
Definition integer.cc:110
LiteralIndex GetAssociatedLiteral(IntegerLiteral i_lit) const
Definition integer.cc:540
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
Definition integer.h:1717
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1721
IntegerValue FixedValue(IntegerVariable i) const
Checks that the variable is fixed and returns its value.
Definition integer.h:1729
void AppendNewBounds(std::vector< IntegerLiteral > *output) const
Definition integer.cc:2065
bool IsFixed(IntegerVariable i) const
Checks if the variable is fixed.
Definition integer.h:1725
ABSL_MUST_USE_RESULT bool RootLevelEnqueue(IntegerLiteral i_lit)
Definition integer.cc:1229
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition integer.h:1817
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition integer.cc:876
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Returns globally valid lower/upper bound on the given integer variable.
Definition integer.h:1812
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff=IntegerValue(1))
void AddTerm(IntegerVariable var, IntegerValue coeff)
void AddConstant(IntegerValue value)
Adds the corresponding term to the current linear expression.
void Clear()
Clears all added terms and constants. Keeps the original bounds.
LiteralIndex NegatedIndex() const
Definition sat_base.h:92
LiteralIndex Index() const
Definition sat_base.h:91
bool TryToLinearize(const AffineExpression &left, const AffineExpression &right, LinearConstraintBuilder *builder)
std::vector< LiteralValueValue > TryToDecompose(const AffineExpression &left, const AffineExpression &right)
void ProcessTernaryClause(absl::Span< const Literal > ternary_clause)
void ProcessTernaryExactlyOne(absl::Span< const Literal > ternary_exo)
void ProcessImplicationGraph(BinaryImplicationGraph *graph)
Utility function to process a bunch of implication all at once.
LiteralIndex GetProduct(Literal a, Literal b) const
Query function mainly used for testing.
bool ProductIsLinearizable(IntegerVariable a, IntegerVariable b) const
void ProcessConditionalEquality(Literal l, IntegerVariable x, IntegerVariable y)
void ProcessConditionalZero(Literal l, IntegerVariable p)
void InitializeBooleanRLTCuts(const absl::flat_hash_map< IntegerVariable, glop::ColIndex > &lp_vars, const util_intops::StrongVector< IntegerVariable, double > &lp_values)
void ProcessBinaryClause(absl::Span< const Literal > binary_clause)
const std::vector< Decision > & Decisions() const
Definition sat_solver.h:385
Simple class to add statistics by name and print them at the end.
void AddStats(absl::Span< const std::pair< std::string, int64_t > > stats)
Adds a bunch of stats, adding count for the same key together.
const VariablesAssignment & Assignment() const
Definition sat_base.h:462
bool LiteralIsAssigned(Literal literal) const
Definition sat_base.h:191
void resize(size_type new_size)
int64_t a
Definition table.cc:44
GraphType graph
int64_t value
IntVar * var
GRBmodel * model
int lit
int literal
int index
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
std::vector< LiteralValueValue > TryToReconcileSize2Encodings(const AffineExpression &left, const AffineExpression &right, IntegerEncoder *integer_encoder)
const LiteralIndex kNoLiteralIndex(-1)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Returns the vector of the negated variables.
Definition integer.cc:51
const IntegerVariable kNoIntegerVariable(-1)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition integer.h:193
std::vector< LiteralValueValue > TryToReconcileEncodings(const AffineExpression &size2_affine, const AffineExpression &affine, absl::Span< const ValueLiteralPair > affine_var_encoding, bool put_affine_left_in_result, IntegerEncoder *integer_encoder)
bool VariableIsPositive(IntegerVariable i)
Definition integer.h:189
In SWIG mode, we don't want anything besides these top-level includes.
const Variable x
Definition qp_tests.cc:127
int64_t coefficient
IntegerValue ValueAt(IntegerValue var_value) const
Returns the value of this affine expression given its variable value.
Definition integer.h:330
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition integer.h:1667