OR-Tools  9.6
implied_bounds.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <array>
20 #include <bitset>
21 #include <limits>
22 #include <optional>
23 #include <string>
24 #include <utility>
25 #include <vector>
26 
27 #include "absl/container/flat_hash_map.h"
28 #include "absl/strings/str_cat.h"
29 #include "ortools/base/logging.h"
31 #include "ortools/sat/integer.h"
33 #include "ortools/sat/model.h"
34 #include "ortools/sat/sat_base.h"
35 #include "ortools/sat/sat_parameters.pb.h"
36 #include "ortools/sat/sat_solver.h"
37 #include "ortools/util/bitset.h"
40 
41 namespace operations_research {
42 namespace sat {
43 
44 // Just display some global statistics on destruction.
46  if (!VLOG_IS_ON(1)) return;
47  if (shared_stats_ == nullptr) return;
48  std::vector<std::pair<std::string, int64_t>> stats;
49  stats.push_back({"implied_bound/num_deductions", num_deductions_});
50  stats.push_back({"implied_bound/num_stored", bounds_.size()});
51  stats.push_back(
52  {"implied_bound/num_stored_with_view", num_enqueued_in_var_to_bounds_});
53  shared_stats_->AddStats(stats);
54 }
55 
57  if (!parameters_.use_implied_bounds()) return true;
58  const IntegerVariable var = integer_literal.var;
59 
60  // Ignore any Add() with a bound worse than the level zero one.
61  // TODO(user): Check that this never happen? it shouldn't.
62  const IntegerValue root_lb = integer_trail_->LevelZeroLowerBound(var);
63  if (integer_literal.bound <= root_lb) return true;
64 
65  // We skip any IntegerLiteral referring to a variable with only two
66  // consecutive possible values. This is because, once shifted this will
67  // already be a variable in [0, 1] so we shouldn't gain much by substituing
68  // it.
69  if (root_lb + 1 >= integer_trail_->LevelZeroUpperBound(var)) return true;
70 
71  // Add or update the current bound.
72  const auto key = std::make_pair(literal.Index(), var);
73  auto insert_result = bounds_.insert({key, integer_literal.bound});
74  if (!insert_result.second) {
75  if (insert_result.first->second < integer_literal.bound) {
76  insert_result.first->second = integer_literal.bound;
77  } else {
78  // No new info.
79  return true;
80  }
81  }
82 
83  // Checks if the variable is now fixed.
84  if (integer_trail_->LevelZeroUpperBound(var) == integer_literal.bound) {
85  AddLiteralImpliesVarEqValue(literal, var, integer_literal.bound);
86  } else {
87  const auto it =
88  bounds_.find(std::make_pair(literal.Index(), NegationOf(var)));
89  if (it != bounds_.end() && it->second == -integer_literal.bound) {
90  AddLiteralImpliesVarEqValue(literal, var, integer_literal.bound);
91  }
92  }
93 
94  // Check if we have any deduction. Since at least one of (literal,
95  // literal.Negated()) must be true, we can take the min bound as valid at
96  // level zero.
97  //
98  // TODO(user): Like in probing, we can also create hole in the domain if there
99  // is some implied bounds for (literal.NegatedIndex, NegagtionOf(var)) that
100  // crosses integer_literal.bound.
101  const auto it = bounds_.find(std::make_pair(literal.NegatedIndex(), var));
102  if (it != bounds_.end()) {
103  if (it->second <= root_lb) {
104  // The other bounds is worse than the new level-zero bound which can
105  // happen because of lazy update, so here we just remove it.
106  bounds_.erase(it);
107  } else {
108  const IntegerValue deduction =
109  std::min(integer_literal.bound, it->second);
110  DCHECK_GT(deduction, root_lb);
111 
112  ++num_deductions_;
113  if (!integer_trail_->RootLevelEnqueue(
114  IntegerLiteral::GreaterOrEqual(var, deduction))) {
115  return false;
116  }
117 
118  VLOG(2) << "Deduction old: "
120  var, integer_trail_->LevelZeroLowerBound(var))
121  << " new: " << IntegerLiteral::GreaterOrEqual(var, deduction);
122 
123  // The entries that are equal to the min no longer need to be stored once
124  // the level zero bound is enqueued.
125  if (it->second == deduction) {
126  bounds_.erase(it);
127  }
128  if (integer_literal.bound == deduction) {
129  bounds_.erase(std::make_pair(literal.Index(), var));
130 
131  // No need to update var_to_bounds_ in this case.
132  return true;
133  }
134  }
135  }
136 
137  // While the code above deal correctly with optionality, we cannot just
138  // register a literal => bound for an optional variable, because the equation
139  // might end up in the LP which do not handle them correctly.
140  //
141  // TODO(user): Maybe we can handle this case somehow, as long as every
142  // constraint using this bound is protected by the variable optional literal.
143  // Alternativelly we could disable optional variable when we are at
144  // linearization level 2.
145  if (integer_trail_->IsOptional(var)) return true;
146 
147  // The information below is currently only used for cuts.
148  // So no need to store it if we aren't going to use it.
149  if (parameters_.linearization_level() == 0) return true;
150  if (parameters_.cut_level() == 0) return true;
151 
152  // If we have a new implied bound and the literal has a view, add it to
153  // var_to_bounds_. Note that we might add more than one entry with the same
154  // literal_view, and we will later need to lazily clean the vector up.
155  if (integer_encoder_->GetLiteralView(literal) != kNoIntegerVariable) {
156  if (var_to_bounds_.size() <= var) {
157  var_to_bounds_.resize(var.value() + 1);
158  has_implied_bounds_.Resize(var + 1);
159  }
160  ++num_enqueued_in_var_to_bounds_;
161  has_implied_bounds_.Set(var);
162  var_to_bounds_[var].push_back({integer_encoder_->GetLiteralView(literal),
163  integer_literal.bound, true});
164  } else if (integer_encoder_->GetLiteralView(literal.Negated()) !=
166  if (var_to_bounds_.size() <= var) {
167  var_to_bounds_.resize(var.value() + 1);
168  has_implied_bounds_.Resize(var + 1);
169  }
170  ++num_enqueued_in_var_to_bounds_;
171  has_implied_bounds_.Set(var);
172  var_to_bounds_[var].push_back(
173  {integer_encoder_->GetLiteralView(literal.Negated()),
174  integer_literal.bound, false});
175  }
176  return true;
177 }
178 
179 const std::vector<ImpliedBoundEntry>& ImpliedBounds::GetImpliedBounds(
180  IntegerVariable var) {
181  if (var >= var_to_bounds_.size()) return empty_implied_bounds_;
182 
183  // Lazily remove obsolete entries from the vector.
184  //
185  // TODO(user): Check no duplicate and remove old entry if the enforcement
186  // is tighter.
187  int new_size = 0;
188  std::vector<ImpliedBoundEntry>& ref = var_to_bounds_[var];
189  const IntegerValue root_lb = integer_trail_->LevelZeroLowerBound(var);
190  for (const ImpliedBoundEntry& entry : ref) {
191  if (entry.lower_bound <= root_lb) continue;
192  ref[new_size++] = entry;
193  }
194  ref.resize(new_size);
195 
196  return ref;
197 }
198 
200  IntegerVariable var,
201  IntegerValue value) {
202  if (!VariableIsPositive(var)) {
203  var = NegationOf(var);
204  value = -value;
205  }
206  literal_to_var_to_value_[literal.Index()][var] = value;
207 }
208 
210  if (!parameters_.use_implied_bounds()) return true;
211 
212  CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 1);
213  tmp_integer_literals_.clear();
214  integer_trail_->AppendNewBounds(&tmp_integer_literals_);
215  for (const IntegerLiteral lit : tmp_integer_literals_) {
216  if (!Add(first_decision, lit)) return false;
217  }
218  return true;
219 }
220 
222  IntegerVariable var, const std::vector<ValueLiteralPair>& encoding,
223  int exactly_one_index) {
224  var_to_index_to_element_encodings_[var][exactly_one_index] = encoding;
225 }
226 
227 const absl::flat_hash_map<int, std::vector<ValueLiteralPair>>&
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 
237 const std::vector<IntegerVariable>& ImpliedBounds::GetElementEncodedVariables()
238  const {
239  return element_encoded_variables_;
240 }
241 
242 std::string EncodingStr(const std::vector<ValueLiteralPair>& enc) {
243  std::string result;
244  for (const ValueLiteralPair& term : enc) {
245  absl::StrAppend(&result, term.literal.DebugString(), ":",
246  term.value.value(), " ");
247  }
248  return result;
249 }
250 
251 // If a variable has a size of 2, it is most likely reduced to an affine
252 // expression pointing to a variable with domain [0,1] or [-1,0].
253 // If the original variable has been removed from the model, then there are no
254 // implied values from any exactly_one constraint to its domain.
255 // If we are lucky, one of the literal of the exactly_one constraints, and its
256 // negation are used to encode the Boolean variable of the affine.
257 //
258 // This may fail if exactly_one(l0, l1, l2, l3); l0 and l1 imply x = 0,
259 // l2 and l3 imply x = 1. In that case, one must look at the binary
260 // implications to find the missing link.
261 //
262 // TODO(user): Consider removing this once we are more complete in our implied
263 // bounds repository. Because if we can reconcile an encoding, then any of the
264 // literal in the at most one should imply a value on the boolean view use in
265 // the size2 affine.
266 std::vector<LiteralValueValue> TryToReconcileEncodings(
267  const AffineExpression& size2_affine, const AffineExpression& affine,
268  const std::vector<ValueLiteralPair>& affine_var_encoding,
269  bool put_affine_left_in_result, Model* model) {
270  IntegerEncoder* integer_encoder = model->GetOrCreate<IntegerEncoder>();
271  IntegerVariable binary = size2_affine.var;
272  std::vector<LiteralValueValue> terms;
273  if (!integer_encoder->VariableIsFullyEncoded(binary)) return terms;
274  const std::vector<ValueLiteralPair>& size2_enc =
275  integer_encoder->FullDomainEncoding(binary);
276 
277  // TODO(user): I am not sure how this can happen since size2_affine is
278  // supposed to be non-fixed. Maybe we miss some propag. Investigate.
279  if (size2_enc.size() != 2) return terms;
280 
281  Literal lit0 = size2_enc[0].literal;
282  IntegerValue value0 = size2_affine.ValueAt(size2_enc[0].value);
283  Literal lit1 = size2_enc[1].literal;
284  IntegerValue value1 = size2_affine.ValueAt(size2_enc[1].value);
285 
286  for (const auto& [unused, candidate_literal] : affine_var_encoding) {
287  if (candidate_literal == lit1) {
288  std::swap(lit0, lit1);
289  std::swap(value0, value1);
290  }
291  if (candidate_literal != lit0) continue;
292 
293  // Build the decomposition.
294  for (const auto& [value, literal] : affine_var_encoding) {
295  const IntegerValue size_2_value = literal == lit0 ? value0 : value1;
296  const IntegerValue affine_value = affine.ValueAt(value);
297  if (put_affine_left_in_result) {
298  terms.push_back({literal, affine_value, size_2_value});
299  } else {
300  terms.push_back({literal, size_2_value, affine_value});
301  }
302  }
303  break;
304  }
305 
306  return terms;
307 }
308 
309 // Specialized case of encoding reconciliation when both variables have a domain
310 // of size of 2.
311 std::vector<LiteralValueValue> TryToReconcileSize2Encodings(
312  const AffineExpression& left, const AffineExpression& right, Model* model) {
313  IntegerEncoder* integer_encoder = model->GetOrCreate<IntegerEncoder>();
314  std::vector<LiteralValueValue> terms;
315  if (!integer_encoder->VariableIsFullyEncoded(left.var) ||
316  !integer_encoder->VariableIsFullyEncoded(right.var)) {
317  return terms;
318  }
319  const std::vector<ValueLiteralPair>& left_enc =
320  integer_encoder->FullDomainEncoding(left.var);
321  const std::vector<ValueLiteralPair>& right_enc =
322  integer_encoder->FullDomainEncoding(right.var);
323  if (left_enc.size() != 2 || right_enc.size() != 2) {
324  VLOG(2) << "encodings are not fully propagated";
325  return terms;
326  }
327 
328  const Literal left_lit0 = left_enc[0].literal;
329  const IntegerValue left_value0 = left.ValueAt(left_enc[0].value);
330  const Literal left_lit1 = left_enc[1].literal;
331  const IntegerValue left_value1 = left.ValueAt(left_enc[1].value);
332 
333  const Literal right_lit0 = right_enc[0].literal;
334  const IntegerValue right_value0 = right.ValueAt(right_enc[0].value);
335  const Literal right_lit1 = right_enc[1].literal;
336  const IntegerValue right_value1 = right.ValueAt(right_enc[1].value);
337 
338  if (left_lit0 == right_lit0 || left_lit0 == right_lit1.Negated()) {
339  terms.push_back({left_lit0, left_value0, right_value0});
340  terms.push_back({left_lit0.Negated(), left_value1, right_value1});
341  } else if (left_lit0 == right_lit1 || left_lit0 == right_lit0.Negated()) {
342  terms.push_back({left_lit0, left_value0, right_value1});
343  terms.push_back({left_lit0.Negated(), left_value1, right_value0});
344  } else if (left_lit1 == right_lit1 || left_lit1 == right_lit0.Negated()) {
345  terms.push_back({left_lit1.Negated(), left_value0, right_value0});
346  terms.push_back({left_lit1, left_value1, right_value1});
347  } else if (left_lit1 == right_lit0 || left_lit1 == right_lit1.Negated()) {
348  terms.push_back({left_lit1.Negated(), left_value0, right_value1});
349  terms.push_back({left_lit1, left_value1, right_value0});
350  } else {
351  VLOG(3) << "Complex size 2 encoding case, need to scan exactly_ones";
352  }
353 
354  return terms;
355 }
356 
357 std::vector<LiteralValueValue> TryToDecomposeProduct(
358  const AffineExpression& left, const AffineExpression& right, Model* model) {
359  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
360  if (integer_trail->IsFixed(left) || integer_trail->IsFixed(right)) return {};
361 
362  // Fill in the encodings for the left variable.
363  ImpliedBounds* implied_bounds = model->GetOrCreate<ImpliedBounds>();
364  const absl::flat_hash_map<int, std::vector<ValueLiteralPair>>&
365  left_encodings = implied_bounds->GetElementEncodings(left.var);
366 
367  // Fill in the encodings for the right variable.
368  const absl::flat_hash_map<int, std::vector<ValueLiteralPair>>&
369  right_encodings = implied_bounds->GetElementEncodings(right.var);
370 
371  std::vector<int> compatible_keys;
372  for (const auto& [index, encoding] : left_encodings) {
373  if (right_encodings.contains(index)) {
374  compatible_keys.push_back(index);
375  }
376  }
377 
378  if (compatible_keys.empty()) {
379  if (integer_trail->InitialVariableDomain(left.var).Size() == 2) {
380  for (const auto& [index, right_encoding] : right_encodings) {
381  const std::vector<LiteralValueValue> result =
382  TryToReconcileEncodings(left, right, right_encoding,
383  /*put_affine_left_in_result=*/false, model);
384  if (!result.empty()) {
385  return result;
386  }
387  }
388  }
389  if (integer_trail->InitialVariableDomain(right.var).Size() == 2) {
390  for (const auto& [index, left_encoding] : left_encodings) {
391  const std::vector<LiteralValueValue> result =
392  TryToReconcileEncodings(right, left, left_encoding,
393  /*put_affine_left_in_result=*/true, model);
394  if (!result.empty()) {
395  return result;
396  }
397  }
398  }
399  if (integer_trail->InitialVariableDomain(left.var).Size() == 2 &&
400  integer_trail->InitialVariableDomain(right.var).Size() == 2) {
401  const std::vector<LiteralValueValue> result =
402  TryToReconcileSize2Encodings(left, right, model);
403  if (!result.empty()) {
404  return result;
405  }
406  }
407  return {};
408  }
409 
410  if (compatible_keys.size() > 1) {
411  VLOG(3) << "More than one exactly_one involved in the encoding of the two "
412  "variables";
413  }
414 
415  // Select the compatible encoding with the minimum index.
416  const int min_index =
417  *std::min_element(compatible_keys.begin(), compatible_keys.end());
418  // By construction, encodings follow the order of literals in the exactly_one
419  // constraint.
420  const std::vector<ValueLiteralPair>& left_encoding =
421  left_encodings.at(min_index);
422  const std::vector<ValueLiteralPair>& right_encoding =
423  right_encodings.at(min_index);
424  DCHECK_EQ(left_encoding.size(), right_encoding.size());
425 
426  // Build decomposition of the product.
427  std::vector<LiteralValueValue> terms;
428  for (int i = 0; i < left_encoding.size(); ++i) {
429  const Literal literal = left_encoding[i].literal;
430  DCHECK_EQ(literal, right_encoding[i].literal);
431  terms.push_back({literal, left.ValueAt(left_encoding[i].value),
432  right.ValueAt(right_encoding[i].value)});
433  }
434 
435  return terms;
436 }
437 
438 // TODO(user): Experiment with x * x where constants = 0, x is
439 // fully encoded, and the domain is small.
441  const AffineExpression& right, Model* model,
442  LinearConstraintBuilder* builder) {
443  DCHECK(builder != nullptr);
444  builder->Clear();
445 
446  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
447  if (integer_trail->IsFixed(left)) {
448  if (integer_trail->IsFixed(right)) {
449  builder->AddConstant(integer_trail->FixedValue(left) *
450  integer_trail->FixedValue(right));
451  return true;
452  }
453  builder->AddTerm(right, integer_trail->FixedValue(left));
454  return true;
455  }
456 
457  if (integer_trail->IsFixed(right)) {
458  builder->AddTerm(left, integer_trail->FixedValue(right));
459  return true;
460  }
461 
462  // Linearization is possible if both left and right have the same Boolean
463  // variable.
464  if (PositiveVariable(left.var) == PositiveVariable(right.var) &&
465  integer_trail->LowerBound(PositiveVariable(left.var)) == 0 &&
466  integer_trail->UpperBound(PositiveVariable(left.var)) == 1) {
467  const IntegerValue left_coeff =
468  VariableIsPositive(left.var) ? left.coeff : -left.coeff;
469  const IntegerValue right_coeff =
470  VariableIsPositive(right.var) ? right.coeff : -right.coeff;
471  builder->AddTerm(PositiveVariable(left.var),
472  left_coeff * right_coeff + left.constant * right_coeff +
473  left_coeff * right.constant);
474  builder->AddConstant(left.constant * right.constant);
475  return true;
476  }
477 
478  const std::vector<LiteralValueValue> product =
479  TryToDecomposeProduct(left, right, model);
480  if (product.empty()) return false;
481 
482  IntegerValue min_coefficient = kMaxIntegerValue;
483  for (const LiteralValueValue& term : product) {
484  min_coefficient =
485  std::min(min_coefficient, term.left_value * term.right_value);
486  }
487 
488  for (const LiteralValueValue& term : product) {
489  const IntegerValue coefficient =
490  term.left_value * term.right_value - min_coefficient;
491  if (coefficient == 0) continue;
492  if (!builder->AddLiteralTerm(term.literal, coefficient)) {
493  return false;
494  }
495  }
496  builder->AddConstant(min_coefficient);
497  return true;
498 }
499 
501  : enabled_(model->GetOrCreate<SatParameters>()->linearization_level() > 1),
502  sat_solver_(model->GetOrCreate<SatSolver>()),
503  trail_(model->GetOrCreate<Trail>()),
504  integer_trail_(model->GetOrCreate<IntegerTrail>()),
505  integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
506  shared_stats_(model->GetOrCreate<SharedStatistics>()) {}
507 
509  if (!VLOG_IS_ON(1)) return;
510  if (shared_stats_ == nullptr) return;
511  std::vector<std::pair<std::string, int64_t>> stats;
512  stats.push_back(
513  {"product_detector/num_processed_binary", num_processed_binary_});
514  stats.push_back(
515  {"product_detector/num_processed_exactly_one", num_processed_exo_});
516  stats.push_back(
517  {"product_detector/num_processed_ternary", num_processed_ternary_});
518  stats.push_back({"product_detector/num_trail_updates", num_trail_updates_});
519  stats.push_back({"product_detector/num_products", num_products_});
520  stats.push_back({"product_detector/num_conditional_equalities",
521  num_conditional_equalities_});
522  stats.push_back(
523  {"product_detector/num_conditional_zeros", num_conditional_zeros_});
524  stats.push_back({"product_detector/num_int_products", num_int_products_});
525  shared_stats_->AddStats(stats);
526 }
527 
529  absl::Span<const Literal> ternary_clause) {
530  if (!enabled_) return;
531  if (ternary_clause.size() != 3) return;
532  ++num_processed_ternary_;
533  candidates_[GetKey(ternary_clause[0].Index(), ternary_clause[1].Index())]
534  .push_back(ternary_clause[2].Index());
535  candidates_[GetKey(ternary_clause[0].Index(), ternary_clause[2].Index())]
536  .push_back(ternary_clause[1].Index());
537  candidates_[GetKey(ternary_clause[1].Index(), ternary_clause[2].Index())]
538  .push_back(ternary_clause[0].Index());
539 
540  // We mark the literal of the ternary clause as seen.
541  // Only a => b with a seen need to be looked at.
542  for (const Literal l : ternary_clause) {
543  if (l.Index() >= seen_.size()) seen_.resize(l.Index() + 1);
544  seen_[l.Index()] = true;
545  }
546 }
547 
549  absl::Span<const Literal> ternary_exo) {
550  if (!enabled_) return;
551  if (ternary_exo.size() != 3) return;
552  ++num_processed_exo_;
553  ProcessNewProduct(ternary_exo[0].Index(), ternary_exo[1].NegatedIndex(),
554  ternary_exo[2].NegatedIndex());
555  ProcessNewProduct(ternary_exo[1].Index(), ternary_exo[0].NegatedIndex(),
556  ternary_exo[2].NegatedIndex());
557  ProcessNewProduct(ternary_exo[2].Index(), ternary_exo[0].NegatedIndex(),
558  ternary_exo[1].NegatedIndex());
559 }
560 
561 // TODO(user): As product are discovered, we could remove entries from our
562 // hash maps!
564  absl::Span<const Literal> binary_clause) {
565  if (!enabled_) return;
566  if (binary_clause.size() != 2) return;
567  ++num_processed_binary_;
568  const std::array<LiteralIndex, 2> key =
569  GetKey(binary_clause[0].NegatedIndex(), binary_clause[1].NegatedIndex());
570  std::array<LiteralIndex, 3> ternary;
571  for (const LiteralIndex l : candidates_[key]) {
572  ternary[0] = key[0];
573  ternary[1] = key[1];
574  ternary[2] = l;
575  std::sort(ternary.begin(), ternary.end());
576  const int l_index = ternary[0] == l ? 0 : ternary[1] == l ? 1 : 2;
577  std::bitset<3>& bs = detector_[ternary];
578  if (bs[l_index]) continue;
579  bs[l_index] = true;
580  if (bs[0] && bs[1] && l_index != 2) {
581  ProcessNewProduct(ternary[2], Literal(ternary[0]).NegatedIndex(),
582  Literal(ternary[1]).NegatedIndex());
583  }
584  if (bs[0] && bs[2] && l_index != 1) {
585  ProcessNewProduct(ternary[1], Literal(ternary[0]).NegatedIndex(),
586  Literal(ternary[2]).NegatedIndex());
587  }
588  if (bs[1] && bs[2] && l_index != 0) {
589  ProcessNewProduct(ternary[0], Literal(ternary[1]).NegatedIndex(),
590  Literal(ternary[2]).NegatedIndex());
591  }
592  }
593 }
594 
596  if (!enabled_) return;
597  for (LiteralIndex a(0); a < seen_.size(); ++a) {
598  if (!seen_[a]) continue;
599  if (trail_->Assignment().LiteralIsAssigned(Literal(a))) continue;
600  const Literal not_a = Literal(a).Negated();
601  for (const Literal b : graph->DirectImplications(Literal(a))) {
602  ProcessBinaryClause({not_a, b}); // a => b;
603  }
604  }
605 }
606 
608  if (!enabled_) return;
609  if (trail_->CurrentDecisionLevel() != 1) return;
610  ++num_trail_updates_;
611 
612  const SatSolver::Decision decision = sat_solver_->Decisions()[0];
613  if (decision.literal.Index() >= seen_.size() ||
614  !seen_[decision.literal.Index()]) {
615  return;
616  }
617  const Literal not_a = decision.literal.Negated();
618  const int current_index = trail_->Index();
619  for (int i = decision.trail_index + 1; i < current_index; ++i) {
620  const Literal b = (*trail_)[i];
621  ProcessBinaryClause({not_a, b});
622  }
623 }
624 
626  const auto it = products_.find(GetKey(a.Index(), b.Index()));
627  if (it == products_.end()) return kNoLiteralIndex;
628  return it->second;
629 }
630 
631 std::array<LiteralIndex, 2> ProductDetector::GetKey(LiteralIndex a,
632  LiteralIndex b) const {
633  std::array<LiteralIndex, 2> key{a, b};
634  if (key[0] > key[1]) std::swap(key[0], key[1]);
635  return key;
636 }
637 
638 void ProductDetector::ProcessNewProduct(LiteralIndex p, LiteralIndex a,
639  LiteralIndex b) {
640  // If many literal correspond to the same product, we just keep one.
641  ++num_products_;
642  products_[GetKey(a, b)] = p;
643 
644  // This is used by ProductIsLinearizable().
645  has_product_.insert(
646  GetKey(Literal(a).IsPositive() ? a : Literal(a).NegatedIndex(),
647  Literal(b).IsPositive() ? b : Literal(b).NegatedIndex()));
648 }
649 
651  IntegerVariable b) const {
652  if (a == b) return true;
653  if (a == NegationOf(b)) return true;
654 
655  // Otherwise, we need both a and b to be expressible as linear expression
656  // involving Booleans whose product is also expressible.
657  if (integer_trail_->InitialVariableDomain(a).Size() != 2) return false;
658  if (integer_trail_->InitialVariableDomain(b).Size() != 2) return false;
659 
660  const LiteralIndex la =
662  a, integer_trail_->LevelZeroUpperBound(a)));
663  if (la == kNoLiteralIndex) return false;
664 
665  const LiteralIndex lb =
667  b, integer_trail_->LevelZeroUpperBound(b)));
668  if (lb == kNoLiteralIndex) return false;
669 
670  // Any product involving la/not(la) * lb/not(lb) can be used.
671  return has_product_.contains(
672  GetKey(Literal(la).IsPositive() ? la : Literal(la).NegatedIndex(),
673  Literal(lb).IsPositive() ? lb : Literal(lb).NegatedIndex()));
674 }
675 
677  IntegerVariable b) const {
678  const auto it = int_products_.find({a.Index(), PositiveVariable(b)});
679  if (it == int_products_.end()) return kNoIntegerVariable;
680  return VariableIsPositive(b) ? it->second : NegationOf(it->second);
681 }
682 
683 void ProductDetector::ProcessNewProduct(IntegerVariable p, Literal l,
684  IntegerVariable x) {
685  if (!VariableIsPositive(x)) {
686  x = NegationOf(x);
687  p = NegationOf(p);
688  }
689 
690  // We only store one product if there are many.
691  ++num_int_products_;
692  int_products_[{l.Index(), x}] = p;
693 }
694 
696  IntegerVariable y) {
697  ++num_conditional_equalities_;
698  if (x == y) return;
699 
700  // We process both possibilities (product = x or product = y).
701  for (int i = 0; i < 2; ++i) {
702  if (!VariableIsPositive(x)) {
703  x = NegationOf(x);
704  y = NegationOf(y);
705  }
706  bool seen = false;
707 
708  // TODO(user): Linear scan can be bad if b => X = many other variables.
709  // Hopefully this will not be common.
710  std::vector<IntegerVariable>& others =
711  conditional_equalities_[{l.Index(), x}];
712  for (const IntegerVariable o : others) {
713  if (o == y) {
714  seen = true;
715  break;
716  }
717  }
718 
719  if (!seen) {
720  others.push_back(y);
721  if (conditional_zeros_.contains({l.NegatedIndex(), x})) {
722  ProcessNewProduct(/*p=*/x, l, y);
723  }
724  }
725  std::swap(x, y);
726  }
727 }
728 
730  ++num_conditional_zeros_;
731  p = PositiveVariable(p);
732  auto [_, inserted] = conditional_zeros_.insert({l.Index(), p});
733  if (inserted) {
734  const auto it = conditional_equalities_.find({l.NegatedIndex(), p});
735  if (it != conditional_equalities_.end()) {
736  for (const IntegerVariable x : it->second) {
737  ProcessNewProduct(p, l.Negated(), x);
738  }
739  }
740  }
741 }
742 
743 } // namespace sat
744 } // namespace operations_research
int64_t min
Definition: alldiff_cst.cc:139
void resize(size_type new_size)
size_type size() const
int64_t Size() const
Returns the number of elements in the domain.
void Set(IntegerType index)
Definition: bitset.h:792
void Resize(IntegerType size)
Definition: bitset.h:778
const std::vector< Literal > & DirectImplications(Literal literal)
Definition: clause.cc:1949
const std::vector< ImpliedBoundEntry > & GetImpliedBounds(IntegerVariable var)
void AddLiteralImpliesVarEqValue(Literal literal, IntegerVariable var, IntegerValue value)
bool Add(Literal literal, IntegerLiteral integer_literal)
const absl::flat_hash_map< int, std::vector< ValueLiteralPair > > & GetElementEncodings(IntegerVariable var)
void AddElementEncoding(IntegerVariable var, const std::vector< ValueLiteralPair > &encoding, int exactly_one_index)
const std::vector< IntegerVariable > & GetElementEncodedVariables() const
bool ProcessIntegerTrail(Literal first_decision)
LiteralIndex GetAssociatedLiteral(IntegerLiteral i_lit) const
Definition: integer.cc:517
const IntegerVariable GetLiteralView(Literal lit) const
Definition: integer.h:558
std::vector< ValueLiteralPair > FullDomainEncoding(IntegerVariable var) const
Definition: integer.cc:140
bool VariableIsFullyEncoded(IntegerVariable var) const
Definition: integer.cc:105
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1565
ABSL_MUST_USE_RESULT bool RootLevelEnqueue(IntegerLiteral i_lit)
Definition: integer.cc:1188
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1561
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1646
IntegerValue FixedValue(IntegerVariable i) const
Definition: integer.h:1569
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1641
void AppendNewBounds(std::vector< IntegerLiteral > *output) const
Definition: integer.cc:2049
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1557
bool IsOptional(IntegerVariable i) const
Definition: integer.h:772
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition: integer.cc:852
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff=IntegerValue(1))
void AddTerm(IntegerVariable var, IntegerValue coeff)
LiteralIndex NegatedIndex() const
Definition: sat_base.h:91
LiteralIndex Index() const
Definition: sat_base.h:90
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
void ProcessTernaryExactlyOne(absl::Span< const Literal > ternary_exo)
void ProcessConditionalZero(Literal l, IntegerVariable p)
LiteralIndex GetProduct(Literal a, Literal b) const
void ProcessBinaryClause(absl::Span< const Literal > binary_clause)
bool ProductIsLinearizable(IntegerVariable a, IntegerVariable b) const
void ProcessImplicationGraph(BinaryImplicationGraph *graph)
void ProcessTernaryClause(absl::Span< const Literal > ternary_clause)
void ProcessConditionalEquality(Literal l, IntegerVariable x, IntegerVariable y)
const std::vector< Decision > & Decisions() const
Definition: sat_solver.h:385
void AddStats(absl::Span< const std::pair< std::string, int64_t >> stats)
const VariablesAssignment & Assignment() const
Definition: sat_base.h:402
bool LiteralIsAssigned(Literal literal) const
Definition: sat_base.h:167
int64_t b
int64_t a
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
int index
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
const LiteralIndex kNoLiteralIndex(-1)
std::string EncodingStr(const std::vector< ValueLiteralPair > &enc)
const IntegerVariable kNoIntegerVariable(-1)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:149
bool DetectLinearEncodingOfProducts(const AffineExpression &left, const AffineExpression &right, Model *model, LinearConstraintBuilder *builder)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:46
std::vector< LiteralValueValue > TryToDecomposeProduct(const AffineExpression &left, const AffineExpression &right, Model *model)
std::vector< LiteralValueValue > TryToReconcileEncodings(const AffineExpression &size2_affine, const AffineExpression &affine, const std::vector< ValueLiteralPair > &affine_var_encoding, bool put_affine_left_in_result, Model *model)
std::vector< LiteralValueValue > TryToReconcileSize2Encodings(const AffineExpression &left, const AffineExpression &right, Model *model)
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:145
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:88
int64_t coefficient
IntegerValue ValueAt(IntegerValue var_value) const
Definition: integer.h:291
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1499
#define VLOG(verboselevel)
Definition: vlog.h:39
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:47