OR-Tools  9.1
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 
18 #include "ortools/sat/integer.h"
19 
20 namespace operations_research {
21 namespace sat {
22 
23 // Just display some global statistics on destruction.
25  VLOG(1) << num_deductions_ << " enqueued deductions.";
26  VLOG(1) << bounds_.size() << " implied bounds stored.";
27  VLOG(1) << num_enqueued_in_var_to_bounds_
28  << " implied bounds with view stored.";
29 }
30 
32  if (!parameters_.use_implied_bounds()) return;
33  const IntegerVariable var = integer_literal.var;
34 
35  // Update our local level-zero bound.
36  if (var >= level_zero_lower_bounds_.size()) {
37  level_zero_lower_bounds_.resize(var.value() + 1, kMinIntegerValue);
38  new_level_zero_bounds_.Resize(var + 1);
39  }
40  level_zero_lower_bounds_[var] = std::max(
41  level_zero_lower_bounds_[var], integer_trail_->LevelZeroLowerBound(var));
42 
43  // Ignore any Add() with a bound worse than the level zero one.
44  // TODO(user): Check that this never happen? it shouldn't.
45  if (integer_literal.bound <= level_zero_lower_bounds_[var]) {
46  return;
47  }
48 
49  // We skip any IntegerLiteral referring to a variable with only two
50  // consecutive possible values. This is because, once shifted this will
51  // already be a variable in [0, 1] so we shouldn't gain much by substituing
52  // it.
53  if (integer_trail_->LevelZeroLowerBound(var) + 1 >=
54  integer_trail_->LevelZeroUpperBound(var)) {
55  return;
56  }
57 
58  // Add or update the current bound.
59  const auto key = std::make_pair(literal.Index(), var);
60  auto insert_result = bounds_.insert({key, integer_literal.bound});
61  if (!insert_result.second) {
62  if (insert_result.first->second < integer_literal.bound) {
63  insert_result.first->second = integer_literal.bound;
64  } else {
65  // No new info.
66  return;
67  }
68  }
69 
70  // Checks if the variable is now fixed.
71  if (integer_trail_->LevelZeroUpperBound(var) == integer_literal.bound) {
72  AddLiteralImpliesVarEqValue(literal, var, integer_literal.bound);
73  } else {
74  const auto it =
75  bounds_.find(std::make_pair(literal.Index(), NegationOf(var)));
76  if (it != bounds_.end() && it->second == -integer_literal.bound) {
77  AddLiteralImpliesVarEqValue(literal, var, integer_literal.bound);
78  }
79  }
80 
81  // Check if we have any deduction. Since at least one of (literal,
82  // literal.Negated()) must be true, we can take the min bound as valid at
83  // level zero.
84  //
85  // TODO(user): Like in probing, we can also create hole in the domain if there
86  // is some implied bounds for (literal.NegatedIndex, NegagtionOf(var)) that
87  // crosses integer_literal.bound.
88  const auto it = bounds_.find(std::make_pair(literal.NegatedIndex(), var));
89  if (it != bounds_.end()) {
90  if (it->second <= level_zero_lower_bounds_[var]) {
91  // The other bounds is worse than the new level-zero bound which can
92  // happen because of lazy update, so here we just remove it.
93  bounds_.erase(it);
94  } else {
95  const IntegerValue deduction =
96  std::min(integer_literal.bound, it->second);
97  DCHECK_GT(deduction, level_zero_lower_bounds_[var]);
98  DCHECK_GT(deduction, integer_trail_->LevelZeroLowerBound(var));
99 
100  // TODO(user): support Enqueueing level zero fact at a positive level.
101  // That is, do not loose the info on backtrack. This should be doable. It
102  // is also why we return a bool in case of conflict when pushing
103  // deduction.
104  ++num_deductions_;
105  level_zero_lower_bounds_[var] = deduction;
106  new_level_zero_bounds_.Set(var);
107 
108  VLOG(1) << "Deduction old: "
110  var, integer_trail_->LevelZeroLowerBound(var))
111  << " new: " << IntegerLiteral::GreaterOrEqual(var, deduction);
112 
113  // The entries that are equal to the min no longer need to be stored once
114  // the level zero bound is enqueued.
115  if (it->second == deduction) {
116  bounds_.erase(it);
117  }
118  if (integer_literal.bound == deduction) {
119  bounds_.erase(std::make_pair(literal.Index(), var));
120 
121  // No need to update var_to_bounds_ in this case.
122  return;
123  }
124  }
125  }
126 
127  // While the code above deal correctly with optionality, we cannot just
128  // register a literal => bound for an optional variable, because the equation
129  // might end up in the LP which do not handle them correctly.
130  //
131  // TODO(user): Maybe we can handle this case somehow, as long as every
132  // constraint using this bound is protected by the variable optional literal.
133  // Alternativelly we could disable optional variable when we are at
134  // linearization level 2.
135  if (integer_trail_->IsOptional(var)) return;
136 
137  // If we have a new implied bound and the literal has a view, add it to
138  // var_to_bounds_. Note that we might add more than one entry with the same
139  // literal_view, and we will later need to lazily clean the vector up.
140  if (integer_encoder_->GetLiteralView(literal) != kNoIntegerVariable) {
141  if (var_to_bounds_.size() <= var) {
142  var_to_bounds_.resize(var.value() + 1);
143  has_implied_bounds_.Resize(var + 1);
144  }
145  ++num_enqueued_in_var_to_bounds_;
146  has_implied_bounds_.Set(var);
147  var_to_bounds_[var].push_back({integer_encoder_->GetLiteralView(literal),
148  integer_literal.bound, true});
149  } else if (integer_encoder_->GetLiteralView(literal.Negated()) !=
151  if (var_to_bounds_.size() <= var) {
152  var_to_bounds_.resize(var.value() + 1);
153  has_implied_bounds_.Resize(var + 1);
154  }
155  ++num_enqueued_in_var_to_bounds_;
156  has_implied_bounds_.Set(var);
157  var_to_bounds_[var].push_back(
158  {integer_encoder_->GetLiteralView(literal.Negated()),
159  integer_literal.bound, false});
160  }
161 }
162 
163 const std::vector<ImpliedBoundEntry>& ImpliedBounds::GetImpliedBounds(
164  IntegerVariable var) {
165  if (var >= var_to_bounds_.size()) return empty_implied_bounds_;
166 
167  // Lazily remove obsolete entries from the vector.
168  //
169  // TODO(user): Check no duplicate and remove old entry if the enforcement
170  // is tighter.
171  int new_size = 0;
172  std::vector<ImpliedBoundEntry>& ref = var_to_bounds_[var];
173  const IntegerValue level_zero_lb = std::max(
174  level_zero_lower_bounds_[var], integer_trail_->LevelZeroLowerBound(var));
175  level_zero_lower_bounds_[var] = level_zero_lb;
176  for (const ImpliedBoundEntry& entry : ref) {
177  if (entry.lower_bound <= level_zero_lb) continue;
178  ref[new_size++] = entry;
179  }
180  ref.resize(new_size);
181 
182  return ref;
183 }
184 
186  IntegerVariable var,
187  IntegerValue value) {
188  if (!VariableIsPositive(var)) {
189  var = NegationOf(var);
190  value = -value;
191  }
192  literal_to_var_to_value_[literal.Index()][var] = value;
193 }
194 
196  if (!parameters_.use_implied_bounds()) return;
197 
198  CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 1);
199  tmp_integer_literals_.clear();
200  integer_trail_->AppendNewBounds(&tmp_integer_literals_);
201  for (const IntegerLiteral lit : tmp_integer_literals_) {
202  Add(first_decision, lit);
203  }
204 }
205 
207  CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
208  for (const IntegerVariable var :
209  new_level_zero_bounds_.PositionsSetAtLeastOnce()) {
210  if (!integer_trail_->Enqueue(
211  IntegerLiteral::GreaterOrEqual(var, level_zero_lower_bounds_[var]),
212  {}, {})) {
213  return false;
214  }
215  }
216  new_level_zero_bounds_.SparseClearAll();
217  return sat_solver_->FinishPropagation();
218 }
219 
220 } // namespace sat
221 } // namespace operations_research
int64_t min
Definition: alldiff_cst.cc:139
void Set(IntegerType index)
Definition: bitset.h:804
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
#define VLOG(verboselevel)
Definition: base/logging.h:979
void Resize(IntegerType size)
Definition: bitset.h:790
void Add(Literal literal, IntegerLiteral integer_literal)
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:891
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1028
const IntegerVariable GetLiteralView(Literal lit) const
Definition: integer.h:454
void AppendNewBounds(std::vector< IntegerLiteral > *output) const
Definition: integer.cc:1785
int64_t max
Definition: alldiff_cst.cc:140
void resize(size_type new_size)
void AddLiteralImpliesVarEqValue(Literal literal, IntegerVariable var, IntegerValue value)
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition: integer.h:1412
void ProcessIntegerTrail(Literal first_decision)
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:138
bool IsOptional(IntegerVariable i) const
Definition: integer.h:656
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
size_type size() const
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:29
const std::vector< ImpliedBoundEntry > & GetImpliedBounds(IntegerVariable var)
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:1309
IntVar * var
Definition: expr_array.cc:1874
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition: integer.h:1407
int64_t value
Literal literal
Definition: optimization.cc:85