OR-Tools  9.2
implied_bounds.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
15 
16 #include <algorithm>
17 #include <limits>
18 
19 #include "ortools/sat/integer.h"
21 
22 namespace operations_research {
23 namespace sat {
24 
25 // Just display some global statistics on destruction.
27  VLOG(1) << num_deductions_ << " enqueued deductions.";
28  VLOG(1) << bounds_.size() << " implied bounds stored.";
29  VLOG(1) << num_enqueued_in_var_to_bounds_
30  << " implied bounds with view stored.";
31 }
32 
34  if (!parameters_.use_implied_bounds()) return;
35  const IntegerVariable var = integer_literal.var;
36 
37  // Update our local level-zero bound.
38  if (var >= level_zero_lower_bounds_.size()) {
39  level_zero_lower_bounds_.resize(var.value() + 1, kMinIntegerValue);
40  new_level_zero_bounds_.Resize(var + 1);
41  }
42  level_zero_lower_bounds_[var] = std::max(
43  level_zero_lower_bounds_[var], integer_trail_->LevelZeroLowerBound(var));
44 
45  // Ignore any Add() with a bound worse than the level zero one.
46  // TODO(user): Check that this never happen? it shouldn't.
47  if (integer_literal.bound <= level_zero_lower_bounds_[var]) {
48  return;
49  }
50 
51  // We skip any IntegerLiteral referring to a variable with only two
52  // consecutive possible values. This is because, once shifted this will
53  // already be a variable in [0, 1] so we shouldn't gain much by substituing
54  // it.
55  if (integer_trail_->LevelZeroLowerBound(var) + 1 >=
56  integer_trail_->LevelZeroUpperBound(var)) {
57  return;
58  }
59 
60  // Add or update the current bound.
61  const auto key = std::make_pair(literal.Index(), var);
62  auto insert_result = bounds_.insert({key, integer_literal.bound});
63  if (!insert_result.second) {
64  if (insert_result.first->second < integer_literal.bound) {
65  insert_result.first->second = integer_literal.bound;
66  } else {
67  // No new info.
68  return;
69  }
70  }
71 
72  // Checks if the variable is now fixed.
73  if (integer_trail_->LevelZeroUpperBound(var) == integer_literal.bound) {
74  AddLiteralImpliesVarEqValue(literal, var, integer_literal.bound);
75  } else {
76  const auto it =
77  bounds_.find(std::make_pair(literal.Index(), NegationOf(var)));
78  if (it != bounds_.end() && it->second == -integer_literal.bound) {
79  AddLiteralImpliesVarEqValue(literal, var, integer_literal.bound);
80  }
81  }
82 
83  // Check if we have any deduction. Since at least one of (literal,
84  // literal.Negated()) must be true, we can take the min bound as valid at
85  // level zero.
86  //
87  // TODO(user): Like in probing, we can also create hole in the domain if there
88  // is some implied bounds for (literal.NegatedIndex, NegagtionOf(var)) that
89  // crosses integer_literal.bound.
90  const auto it = bounds_.find(std::make_pair(literal.NegatedIndex(), var));
91  if (it != bounds_.end()) {
92  if (it->second <= level_zero_lower_bounds_[var]) {
93  // The other bounds is worse than the new level-zero bound which can
94  // happen because of lazy update, so here we just remove it.
95  bounds_.erase(it);
96  } else {
97  const IntegerValue deduction =
98  std::min(integer_literal.bound, it->second);
99  DCHECK_GT(deduction, level_zero_lower_bounds_[var]);
100  DCHECK_GT(deduction, integer_trail_->LevelZeroLowerBound(var));
101 
102  // TODO(user): support Enqueueing level zero fact at a positive level.
103  // That is, do not loose the info on backtrack. This should be doable. It
104  // is also why we return a bool in case of conflict when pushing
105  // deduction.
106  ++num_deductions_;
107  level_zero_lower_bounds_[var] = deduction;
108  new_level_zero_bounds_.Set(var);
109 
110  VLOG(1) << "Deduction old: "
112  var, integer_trail_->LevelZeroLowerBound(var))
113  << " new: " << IntegerLiteral::GreaterOrEqual(var, deduction);
114 
115  // The entries that are equal to the min no longer need to be stored once
116  // the level zero bound is enqueued.
117  if (it->second == deduction) {
118  bounds_.erase(it);
119  }
120  if (integer_literal.bound == deduction) {
121  bounds_.erase(std::make_pair(literal.Index(), var));
122 
123  // No need to update var_to_bounds_ in this case.
124  return;
125  }
126  }
127  }
128 
129  // While the code above deal correctly with optionality, we cannot just
130  // register a literal => bound for an optional variable, because the equation
131  // might end up in the LP which do not handle them correctly.
132  //
133  // TODO(user): Maybe we can handle this case somehow, as long as every
134  // constraint using this bound is protected by the variable optional literal.
135  // Alternativelly we could disable optional variable when we are at
136  // linearization level 2.
137  if (integer_trail_->IsOptional(var)) return;
138 
139  // If we have a new implied bound and the literal has a view, add it to
140  // var_to_bounds_. Note that we might add more than one entry with the same
141  // literal_view, and we will later need to lazily clean the vector up.
142  if (integer_encoder_->GetLiteralView(literal) != kNoIntegerVariable) {
143  if (var_to_bounds_.size() <= var) {
144  var_to_bounds_.resize(var.value() + 1);
145  has_implied_bounds_.Resize(var + 1);
146  }
147  ++num_enqueued_in_var_to_bounds_;
148  has_implied_bounds_.Set(var);
149  var_to_bounds_[var].push_back({integer_encoder_->GetLiteralView(literal),
150  integer_literal.bound, true});
151  } else if (integer_encoder_->GetLiteralView(literal.Negated()) !=
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(
160  {integer_encoder_->GetLiteralView(literal.Negated()),
161  integer_literal.bound, false});
162  }
163 }
164 
165 const std::vector<ImpliedBoundEntry>& ImpliedBounds::GetImpliedBounds(
166  IntegerVariable var) {
167  if (var >= var_to_bounds_.size()) return empty_implied_bounds_;
168 
169  // Lazily remove obsolete entries from the vector.
170  //
171  // TODO(user): Check no duplicate and remove old entry if the enforcement
172  // is tighter.
173  int new_size = 0;
174  std::vector<ImpliedBoundEntry>& ref = var_to_bounds_[var];
175  const IntegerValue level_zero_lb = std::max(
176  level_zero_lower_bounds_[var], integer_trail_->LevelZeroLowerBound(var));
177  level_zero_lower_bounds_[var] = level_zero_lb;
178  for (const ImpliedBoundEntry& entry : ref) {
179  if (entry.lower_bound <= level_zero_lb) continue;
180  ref[new_size++] = entry;
181  }
182  ref.resize(new_size);
183 
184  return ref;
185 }
186 
188  IntegerVariable var,
189  IntegerValue value) {
190  if (!VariableIsPositive(var)) {
191  var = NegationOf(var);
192  value = -value;
193  }
194  literal_to_var_to_value_[literal.Index()][var] = value;
195 }
196 
198  if (!parameters_.use_implied_bounds()) return;
199 
200  CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 1);
201  tmp_integer_literals_.clear();
202  integer_trail_->AppendNewBounds(&tmp_integer_literals_);
203  for (const IntegerLiteral lit : tmp_integer_literals_) {
204  Add(first_decision, lit);
205  }
206 }
207 
209  IntegerVariable var, const std::vector<ValueLiteralPair>& encoding,
210  int exactly_one_index) {
211  var_to_index_to_element_encodings_[var][exactly_one_index] = encoding;
212 }
213 
214 const absl::flat_hash_map<int, std::vector<ValueLiteralPair>>&
216  const auto& it = var_to_index_to_element_encodings_.find(var);
217  if (it == var_to_index_to_element_encodings_.end()) {
218  return empty_element_encoding_;
219  } else {
220  return it->second;
221  }
222 }
223 
224 const std::vector<IntegerVariable>& ImpliedBounds::GetElementEncodedVariables()
225  const {
226  return element_encoded_variables_;
227 }
228 
230  CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
231  for (const IntegerVariable var :
232  new_level_zero_bounds_.PositionsSetAtLeastOnce()) {
233  if (!integer_trail_->Enqueue(
234  IntegerLiteral::GreaterOrEqual(var, level_zero_lower_bounds_[var]),
235  {}, {})) {
236  return false;
237  }
238  }
239  new_level_zero_bounds_.SparseClearAll();
240  return sat_solver_->FinishPropagation();
241 }
242 
243 std::string EncodingStr(const std::vector<ValueLiteralPair>& enc) {
244  std::string result;
245  for (const ValueLiteralPair& term : enc) {
246  absl::StrAppend(&result, term.literal.DebugString(), ":",
247  term.value.value(), " ");
248  }
249  return result;
250 }
251 
252 // If a variable has a size of 2, it is most likely reduced to an affine
253 // expression pointing to a variable with domain [0,1] or [-1,0].
254 // If the original variable has been removed from the model, then there are no
255 // implied values from any exactly_one constraint to its domain.
256 // If we are lucky, one of the literal of the exactly_one constraints, and its
257 // negation are used to encode the Boolean variable of the affine.
258 //
259 // This may fail if exactly_one(l0, l1, l2, l3); l0 and l1 imply x = 0,
260 // l2 and l3 imply x = 1. In that case, one must look at the binary
261 // implications to find the missing link.
262 //
263 // TODO(user): Consider removing this once we are more complete in our implied
264 // bounds repository. Because if we can reconcile an encoding, then any of the
265 // literal in the at most one should imply a value on the boolean view use in
266 // the size2 affine.
268  const AffineExpression& size2_affine, const AffineExpression& affine,
269  const std::vector<ValueLiteralPair>& affine_var_encoding, Model* model,
270  LinearConstraintBuilder* builder) {
271  IntegerEncoder* integer_encoder = model->GetOrCreate<IntegerEncoder>();
272  IntegerVariable binary = size2_affine.var;
273  if (!integer_encoder->VariableIsFullyEncoded(binary)) return false;
274  const std::vector<ValueLiteralPair>& size2_enc =
275  integer_encoder->FullDomainEncoding(binary);
276  CHECK_EQ(2, size2_enc.size());
277  Literal lit0 = size2_enc[0].literal;
278  IntegerValue value0 =
279  size2_enc[0].value * size2_affine.coeff + size2_affine.constant;
280  Literal lit1 = size2_enc[1].literal;
281  IntegerValue value1 =
282  size2_enc[1].value * size2_affine.coeff + size2_affine.constant;
283  for (const auto& [unused, candidate_literal] : affine_var_encoding) {
284  if (candidate_literal == lit1) {
285  std::swap(lit0, lit1);
286  std::swap(value0, value1);
287  }
288  if (candidate_literal != lit0) continue;
289 
290  // Compute the minimum energy.
291  IntegerValue min_energy = kMaxIntegerValue;
292  for (const auto& [value, literal] : affine_var_encoding) {
293  const IntegerValue energy = literal == lit0
294  ? value0 * affine.ValueAt(value)
295  : value1 * affine.ValueAt(value);
296  min_energy = std::min(energy, min_energy);
297  }
298 
299  // Build the energy expression.
300  builder->Clear();
301  builder->AddConstant(min_energy);
302  for (const auto& [value, literal] : affine_var_encoding) {
303  const IntegerValue energy = literal == lit0
304  ? value0 * affine.ValueAt(value)
305  : value1 * affine.ValueAt(value);
306  if (energy > min_energy) {
307  if (!builder->AddLiteralTerm(literal, energy - min_energy)) {
308  return false;
309  }
310  }
311  }
312  return true;
313  }
314 
315  return false;
316 }
317 
318 // TODO(user): Experiment with x * x where constants = 0, x is
319 // fully encoded, and the domain is small.
321  const AffineExpression& right, Model* model,
322  LinearConstraintBuilder* builder) {
323  CHECK(builder != nullptr);
324  builder->Clear();
325 
326  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
327  ImpliedBounds* implied_bounds = model->GetOrCreate<ImpliedBounds>();
328 
329  if (integer_trail->IsFixed(left)) {
330  const IntegerValue value = integer_trail->FixedValue(left);
331  builder->AddTerm(right, value);
332  return true;
333  }
334 
335  if (integer_trail->IsFixed(right)) {
336  const IntegerValue value = integer_trail->FixedValue(right);
337  builder->AddTerm(left, value);
338  return true;
339  }
340 
341  // Linearization is possible if both left and right have the same Boolean
342  // variable.
343  if (PositiveVariable(left.var) == PositiveVariable(right.var) &&
344  integer_trail->LowerBound(PositiveVariable(left.var)) == 0 &&
345  integer_trail->UpperBound(PositiveVariable(left.var)) == 1) {
346  const IntegerValue left_coeff =
347  VariableIsPositive(left.var) ? left.coeff : -left.coeff;
348  const IntegerValue right_coeff =
349  VariableIsPositive(right.var) ? right.coeff : -right.coeff;
350  builder->AddTerm(PositiveVariable(left.var),
351  left_coeff * right_coeff + left.constant * right_coeff +
352  left_coeff * right.constant);
353  builder->AddConstant(left.constant * right.constant);
354  return true;
355  }
356 
357  // Fill in the encodings for the left variable.
358  const absl::flat_hash_map<int, std::vector<ValueLiteralPair>>&
359  left_encodings = implied_bounds->GetElementEncodings(left.var);
360 
361  // Fill in the encodings for the right variable.
362  const absl::flat_hash_map<int, std::vector<ValueLiteralPair>>&
363  right_encodings = implied_bounds->GetElementEncodings(right.var);
364 
365  std::vector<int> compatible_keys;
366  for (const auto& [index, encoding] : left_encodings) {
367  if (right_encodings.contains(index)) {
368  compatible_keys.push_back(index);
369  }
370  }
371 
372  if (compatible_keys.empty()) {
373  if (integer_trail->InitialVariableDomain(left.var).Size() == 2) {
374  for (const auto& [index, right_encoding] : right_encodings) {
375  if (TryToReconcileEncodings(left, right, right_encoding, model,
376  builder)) {
377  return true;
378  }
379  }
380  }
381  if (integer_trail->InitialVariableDomain(right.var).Size() == 2) {
382  for (const auto& [index, left_encoding] : left_encodings) {
383  if (TryToReconcileEncodings(right, left, left_encoding, model,
384  builder)) {
385  return true;
386  }
387  }
388  }
389  return false;
390  }
391 
392  if (compatible_keys.size() > 1) {
393  VLOG(1) << "More than one exactly_one involved in the encoding of the two "
394  "variables";
395  }
396 
397  // Select the compatible encoding with the minimum index.
398  const int min_index =
399  *std::min_element(compatible_keys.begin(), compatible_keys.end());
400  // By construction, encodings follow the order of literals in the exactly_one
401  // constraint.
402  const std::vector<ValueLiteralPair>& left_encoding =
403  left_encodings.at(min_index);
404  const std::vector<ValueLiteralPair>& right_encoding =
405  right_encodings.at(min_index);
406  DCHECK_EQ(left_encoding.size(), right_encoding.size());
407 
408  // Compute the min energy.
409  IntegerValue min_energy = kMaxIntegerValue;
410  for (int i = 0; i < left_encoding.size(); ++i) {
411  const IntegerValue energy = left.ValueAt(left_encoding[i].value) *
412  right.ValueAt(right_encoding[i].value);
413  min_energy = std::min(min_energy, energy);
414  }
415 
416  // Build the linear formulation of the energy.
417  for (int i = 0; i < left_encoding.size(); ++i) {
418  const IntegerValue energy = left.ValueAt(left_encoding[i].value) *
419  right.ValueAt(right_encoding[i].value);
420  if (energy == min_energy) continue;
421  DCHECK_GT(energy, min_energy);
422  const Literal lit = left_encoding[i].literal;
423  DCHECK_EQ(lit, right_encoding[i].literal);
424 
425  if (!builder->AddLiteralTerm(lit, energy - min_energy)) {
426  return false;
427  }
428  }
429  builder->AddConstant(min_energy);
430  return true;
431 }
432 
434  LinearExpression result;
436  return result;
437 }
438 
440  return !expr.vars.empty() ||
442 }
443 
444 void LinearizeInnerProduct(const std::vector<AffineExpression>& left,
445  const std::vector<AffineExpression>& right,
446  Model* model,
447  std::vector<LinearExpression>* energies) {
448  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
449  for (int i = 0; i < left.size(); ++i) {
451  if (DetectLinearEncodingOfProducts(left[i], right[i], model, &builder)) {
452  VLOG(3) << "linearized energy: "
453  << builder.BuildExpression().DebugString();
454  energies->push_back(builder.BuildExpression());
455  } else {
456  VLOG(2) << "Product is not linearizable: demands "
457  << left[i].DebugString() << " with var domain "
458  << integer_trail->InitialVariableDomain(left[i].var)
459  << ", size = " << right[i].DebugString() << " with var domain "
460  << integer_trail->InitialVariableDomain(right[i].var);
461  energies->push_back(NotLinearizedEnergy());
462  }
463  }
464 }
465 
466 } // namespace sat
467 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:495
IntegerValue ValueAt(IntegerValue var_value) const
Definition: integer.h:267
int64_t min
Definition: alldiff_cst.cc:139
void Set(IntegerType index)
Definition: bitset.h:804
bool TryToReconcileEncodings(const AffineExpression &size2_affine, const AffineExpression &affine, const std::vector< ValueLiteralPair > &affine_var_encoding, Model *model, LinearConstraintBuilder *builder)
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
bool VariableIsFullyEncoded(IntegerVariable var) const
Definition: integer.cc:79
const absl::flat_hash_map< int, std::vector< ValueLiteralPair > > & GetElementEncodings(IntegerVariable var)
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition: integer.cc:664
#define VLOG(verboselevel)
Definition: base/logging.h:983
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1435
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
void AddElementEncoding(IntegerVariable var, const std::vector< ValueLiteralPair > &encoding, int exactly_one_index)
void Resize(IntegerType size)
Definition: bitset.h:790
GRBmodel * model
void AddTerm(IntegerVariable var, IntegerValue coeff)
LinearExpression NotLinearizedEnergy()
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff)
void Add(Literal literal, IntegerLiteral integer_literal)
bool DetectLinearEncodingOfProducts(const AffineExpression &left, const AffineExpression &right, Model *model, LinearConstraintBuilder *builder)
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:895
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1027
const IntegerVariable GetLiteralView(Literal lit) const
Definition: integer.h:493
void AppendNewBounds(std::vector< IntegerLiteral > *output) const
Definition: integer.cc:1784
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:143
int64_t max
Definition: alldiff_cst.cc:140
void resize(size_type new_size)
std::string EncodingStr(const std::vector< ValueLiteralPair > &enc)
void AddLiteralImpliesVarEqValue(Literal literal, IntegerVariable var, IntegerValue value)
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1524
void ProcessIntegerTrail(Literal first_decision)
int64_t energy
Definition: resource.cc:354
std::vector< ValueLiteralPair > FullDomainEncoding(IntegerVariable var) const
Definition: integer.cc:114
int index
Definition: pack.cc:509
int64_t Size() const
Returns the number of elements in the domain.
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:139
bool IsOptional(IntegerVariable i) const
Definition: integer.h:695
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
size_type size() const
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:30
const std::vector< ImpliedBoundEntry > & GetImpliedBounds(IntegerVariable var)
void LinearizeInnerProduct(const std::vector< AffineExpression > &left, const std::vector< AffineExpression > &right, Model *model, std::vector< LinearExpression > *energies)
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:890
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1439
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition: bitset.h:814
Collection of objects used to extend the Constraint Solver library.
const IntegerVariable kNoIntegerVariable(-1)
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1377
IntegerValue FixedValue(IntegerVariable i) const
Definition: integer.h:1447
IntVar * var
Definition: expr_array.cc:1874
const std::vector< IntegerVariable > & GetElementEncodedVariables() const
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1519
bool ProductIsLinearized(const LinearExpression &expr)
int64_t value
Literal literal
Definition: optimization.cc:85
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1443