OR-Tools  9.6
implied_bounds.h
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 
14 #ifndef OR_TOOLS_SAT_IMPLIED_BOUNDS_H_
15 #define OR_TOOLS_SAT_IMPLIED_BOUNDS_H_
16 
17 #include <algorithm>
18 #include <array>
19 #include <bitset>
20 #include <cstdint>
21 #include <utility>
22 #include <vector>
23 
24 #include "absl/container/flat_hash_map.h"
26 #include "ortools/base/logging.h"
28 #include "ortools/sat/integer.h"
30 #include "ortools/sat/model.h"
31 #include "ortools/sat/sat_base.h"
32 #include "ortools/sat/sat_parameters.pb.h"
33 #include "ortools/sat/sat_solver.h"
35 #include "ortools/util/bitset.h"
37 
38 namespace operations_research {
39 namespace sat {
40 
41 // For each IntegerVariable, the ImpliedBound class allows to list all such
42 // entries.
43 //
44 // This is meant to be used in the cut generation code when it make sense: if we
45 // have BoolVar => X >= bound, we can always lower bound the variable X by
46 // (bound - X_lb) * BoolVar + X_lb, and that can lead to stronger cuts.
48  // An integer variable in [0, 1]. When at 1, then the IntegerVariable
49  // corresponding to this entry must be greater or equal to the given lower
50  // bound.
51  IntegerVariable literal_view = kNoIntegerVariable;
52  IntegerValue lower_bound = IntegerValue(0);
53 
54  // If false, it is when the literal_view is zero that the lower bound is
55  // valid.
56  bool is_positive = true;
57 
58  // These constructors are needed for OR-Tools.
59  ImpliedBoundEntry(IntegerVariable lit, IntegerValue lb, bool positive)
60  : literal_view(lit), lower_bound(lb), is_positive(positive) {}
61 
64 };
65 
66 // Maintains all the implications of the form Literal => IntegerLiteral. We
67 // collect these implication at model loading, during probing and during search.
68 //
69 // TODO(user): This can quickly use up too much memory. Add some limit in place.
70 // In particular, each time we have literal => integer_literal we should avoid
71 // storing the same integer_literal for all other_literal for which
72 // other_literal => literal. For this we need to interact with the
73 // BinaryImplicationGraph.
74 //
75 // TODO(user): This is a bit of a duplicate with the Literal <=> IntegerLiteral
76 // stored in the IntegerEncoder class. However we only need one side here.
77 //
78 // TODO(user): Do like in the DomainDeductions class and allow to process
79 // clauses (or store them) to perform more level zero deductions. Note that this
80 // is again a slight duplicate with what we do there (except that we work at the
81 // Domain level in that presolve class).
82 //
83 // TODO(user): Add an implied bound cut generator to add these simple
84 // constraints to the LP when needed.
86  public:
88  : parameters_(*model->GetOrCreate<SatParameters>()),
89  sat_solver_(model->GetOrCreate<SatSolver>()),
90  integer_trail_(model->GetOrCreate<IntegerTrail>()),
91  integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
92  shared_stats_(model->GetOrCreate<SharedStatistics>()) {}
94 
95  // Adds literal => integer_literal to the repository.
96  //
97  // Not that it checks right aways if there is another bound on the same
98  // variable involving literal.Negated(), in which case we can improve the
99  // level zero lower bound of the variable.
100  bool Add(Literal literal, IntegerLiteral integer_literal);
101 
102  // Adds literal => var == value.
103  void AddLiteralImpliesVarEqValue(Literal literal, IntegerVariable var,
104  IntegerValue value);
105 
106  // This must be called after first_decision has been enqueued and propagated.
107  // It will inspect the trail and add all new implied bounds.
108  //
109  // Preconditions: The decision level must be one (CHECKed). And the decision
110  // must be equal to first decision (we currently do not CHECK that).
111  bool ProcessIntegerTrail(Literal first_decision);
112 
113  // Returns all the implied bounds stored for the given variable.
114  // Note that only literal with an IntegerView are considered here.
115  const std::vector<ImpliedBoundEntry>& GetImpliedBounds(IntegerVariable var);
116 
117  // Returns all the variables for which GetImpliedBounds(var) is not empty. Or
118  // at least that was not empty at some point, because we lazily remove bounds
119  // that become trivial as the search progress.
120  const std::vector<IntegerVariable>& VariablesWithImpliedBounds() const {
121  return has_implied_bounds_.PositionsSetAtLeastOnce();
122  }
123 
124  // Returns all the implied values stored for a given literal.
125  const absl::flat_hash_map<IntegerVariable, IntegerValue>& GetImpliedValues(
126  Literal literal) const {
127  const auto it = literal_to_var_to_value_.find(literal.Index());
128  return it != literal_to_var_to_value_.end() ? it->second
129  : empty_var_to_value_;
130  }
131 
132  // Register the fact that var = sum literal * value with sum literal == 1.
133  // Note that we call this an "element" encoding because a value can appear
134  // more than once.
135  void AddElementEncoding(IntegerVariable var,
136  const std::vector<ValueLiteralPair>& encoding,
137  int exactly_one_index);
138 
139  // Returns an empty map if there is no such encoding.
140  const absl::flat_hash_map<int, std::vector<ValueLiteralPair>>&
141  GetElementEncodings(IntegerVariable var);
142 
143  // Get an unsorted set of variables appearing in element encodings.
144  const std::vector<IntegerVariable>& GetElementEncodedVariables() const;
145 
146  // Adds to the integer trail all the new level-zero deduction made here.
147  // This can only be called at decision level zero. Returns false iff the model
148  // is infeasible.
150 
151  private:
152  const SatParameters& parameters_;
153  SatSolver* sat_solver_;
154  IntegerTrail* integer_trail_;
155  IntegerEncoder* integer_encoder_;
156  SharedStatistics* shared_stats_;
157 
158  // TODO(user): Remove the need for this.
159  std::vector<IntegerLiteral> tmp_integer_literals_;
160 
161  // For each (Literal, IntegerVariable) the best lower bound implied by this
162  // literal. Note that there is no need to store any entries that do not
163  // improve on the level zero lower bound.
164  //
165  // TODO(user): we could lazily remove old entries to save a bit of space if
166  // many deduction where made at level zero.
167  absl::flat_hash_map<std::pair<LiteralIndex, IntegerVariable>, IntegerValue>
168  bounds_;
169 
170  // Note(user): This is currently only used during cut generation, so only the
171  // Literal with an IntegerView that can be used in the LP relaxation need to
172  // be kept here.
173  //
174  // TODO(user): Use inlined vectors. Even better, we actually only process
175  // all variables at once, so no need to organize it by IntegerVariable even
176  // if that might be more friendly cache-wise.
177  std::vector<ImpliedBoundEntry> empty_implied_bounds_;
179  var_to_bounds_;
180  SparseBitset<IntegerVariable> has_implied_bounds_;
181 
182  // Stores implied values per variable.
183  absl::flat_hash_map<LiteralIndex,
184  absl::flat_hash_map<IntegerVariable, IntegerValue>>
185  literal_to_var_to_value_;
186  const absl::flat_hash_map<IntegerVariable, IntegerValue> empty_var_to_value_;
187 
188  absl::flat_hash_map<IntegerVariable,
189  absl::flat_hash_map<int, std::vector<ValueLiteralPair>>>
190  var_to_index_to_element_encodings_;
191  const absl::flat_hash_map<int, std::vector<ValueLiteralPair>>
192  empty_element_encoding_;
193  std::vector<IntegerVariable> element_encoded_variables_;
194 
195  // Stats.
196  int64_t num_deductions_ = 0;
197  int64_t num_enqueued_in_var_to_bounds_ = 0;
198 };
199 
200 // Tries to decompose a product left * right in a list of constant alternative
201 // left_value * right_value controlled by literals in an exactly one
202 // relationship. We construct this by using literals from the full encoding or
203 // element encodings of the variables of the two affine expressions.
204 // If it fails, it returns an empty vector.
205 std::vector<LiteralValueValue> TryToDecomposeProduct(
206  const AffineExpression& left, const AffineExpression& right, Model* model);
207 
208 // Looks at value encodings and detects if the product of two variables can be
209 // linearized.
210 //
211 // In the returned encoding, note that all the literals will be unique and in
212 // exactly one relation, and that the values can be duplicated. This is what we
213 // call an "element" encoding.
214 //
215 // The expressions will also be canonical.
217  const AffineExpression& right, Model* model,
218  LinearConstraintBuilder* builder);
219 
220 // Class used to detect and hold all the information about a variable beeing the
221 // product of two others. This class is meant to be used by LP relaxation and
222 // cuts.
224  public:
225  explicit ProductDetector(Model* model);
227 
228  // Internally, a Boolean product is encoded in a linear fashion:
229  // p = a * b become:
230  // 1/ a and b => p, i.e. a clause (not(a), not(b), p).
231  // 2/ p => a and p => b, which is a clause (not(p), a) and (not(p), b).
232  //
233  // In particular if we have a+b+c==1 then we have a=b*c, b=a*c, and c=a*b !!
234  //
235  // For the detection to work, we must load all ternary clause first, then the
236  // implication.
237  void ProcessTernaryClause(absl::Span<const Literal> ternary_clause);
238  void ProcessTernaryExactlyOne(absl::Span<const Literal> ternary_exo);
239  void ProcessBinaryClause(absl::Span<const Literal> binary_clause);
240 
241  // Utility function to process a bunch of implication all at once.
243  void ProcessTrailAtLevelOne();
244 
245  // We also detect product of Boolean with IntegerVariable.
246  // After presolve, a product P = l * X should be encoded with:
247  // l => P = X
248  // not(l) => P = 0
249  //
250  // TODO(user): Generalize to a * X + b = l * (Y + c) since these are also
251  // easy to linearize if we see l * Y.
252  void ProcessConditionalEquality(Literal l, IntegerVariable x,
253  IntegerVariable y);
254  void ProcessConditionalZero(Literal l, IntegerVariable p);
255 
256  // Query function mainly used for testing.
257  LiteralIndex GetProduct(Literal a, Literal b) const;
258  IntegerVariable GetProduct(Literal a, IntegerVariable b) const;
259 
260  // Query Functions. LinearizeProduct() should only be called if
261  // ProductIsLinearizable() is true.
262  bool ProductIsLinearizable(IntegerVariable a, IntegerVariable b) const;
263 
264  // TODO(user): Implement!
265  LinearExpression LinearizeProduct(IntegerVariable a, IntegerVariable b);
266 
267  // Returns an expression that is always lower or equal to the product a * b.
268  // This use the exact LinearizeProduct() if ProductIsLinearizable() otherwise
269  // it uses the simple McCormick lower bound.
270  //
271  // TODO(user): Implement!
272  LinearExpression ProductLowerBound(IntegerVariable a, IntegerVariable b);
273 
274  private:
275  std::array<LiteralIndex, 2> GetKey(LiteralIndex a, LiteralIndex b) const;
276  void ProcessNewProduct(LiteralIndex p, LiteralIndex a, LiteralIndex b);
277  void ProcessNewProduct(IntegerVariable p, Literal l, IntegerVariable x);
278 
279  // Fixed at creation time.
280  bool enabled_;
281  SatSolver* sat_solver_;
282  Trail* trail_;
283  IntegerTrail* integer_trail_;
284  IntegerEncoder* integer_encoder_;
285  SharedStatistics* shared_stats_;
286 
287  // No need to process implication a => b if a was never seen.
289 
290  // For each clause of size 3 (l0, l1, l2) and a permutation of index (i, j, k)
291  // we bitset[i] to true if lj => not(lk) and lk => not(lj).
292  //
293  // The key is sorted.
294  absl::flat_hash_map<std::array<LiteralIndex, 3>, std::bitset<3>> detector_;
295 
296  // For each (l0, l1) we list all the l2 such that (l0, l1, l2) is a 3 clause.
297  absl::flat_hash_map<std::array<LiteralIndex, 2>, std::vector<LiteralIndex>>
298  candidates_;
299 
300  // Products (a, b) -> p such that p == a * b. They key is sorted.
301  absl::flat_hash_map<std::array<LiteralIndex, 2>, LiteralIndex> products_;
302 
303  // Same keys has in products_ but canonicalized so we capture all 4 products
304  // a * b, (1 - a) * b, a * (1 - b) and (1 - a) * (1 - b) with one query.
305  absl::flat_hash_set<std::array<LiteralIndex, 2>> has_product_;
306 
307  // For bool * int detection. Note that we only use positive IntegerVariable
308  // in the key part.
309  absl::flat_hash_set<std::pair<LiteralIndex, IntegerVariable>>
310  conditional_zeros_;
311  absl::flat_hash_map<std::pair<LiteralIndex, IntegerVariable>,
312  std::vector<IntegerVariable>>
313  conditional_equalities_;
314 
315  // Stores l * X = P.
316  absl::flat_hash_map<std::pair<LiteralIndex, IntegerVariable>, IntegerVariable>
317  int_products_;
318 
319  // Stats.
320  int64_t num_products_ = 0;
321  int64_t num_int_products_ = 0;
322  int64_t num_trail_updates_ = 0;
323  int64_t num_processed_binary_ = 0;
324  int64_t num_processed_ternary_ = 0;
325  int64_t num_processed_exo_ = 0;
326  int64_t num_conditional_zeros_ = 0;
327  int64_t num_conditional_equalities_ = 0;
328 };
329 
330 } // namespace sat
331 } // namespace operations_research
332 
333 #endif // OR_TOOLS_SAT_IMPLIED_BOUNDS_H_
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition: bitset.h:806
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 absl::flat_hash_map< IntegerVariable, IntegerValue > & GetImpliedValues(Literal literal) const
const std::vector< IntegerVariable > & GetElementEncodedVariables() const
const std::vector< IntegerVariable > & VariablesWithImpliedBounds() const
bool ProcessIntegerTrail(Literal first_decision)
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
void ProcessTernaryExactlyOne(absl::Span< const Literal > ternary_exo)
LinearExpression ProductLowerBound(IntegerVariable a, IntegerVariable b)
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
LinearExpression LinearizeProduct(IntegerVariable a, IntegerVariable b)
void ProcessImplicationGraph(BinaryImplicationGraph *graph)
void ProcessTernaryClause(absl::Span< const Literal > ternary_clause)
void ProcessConditionalEquality(Literal l, IntegerVariable x, IntegerVariable y)
int64_t b
int64_t a
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
const IntegerVariable kNoIntegerVariable(-1)
bool DetectLinearEncodingOfProducts(const AffineExpression &left, const AffineExpression &right, Model *model, LinearConstraintBuilder *builder)
std::vector< LiteralValueValue > TryToDecomposeProduct(const AffineExpression &left, const AffineExpression &right, Model *model)
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:88
ImpliedBoundEntry()
bool is_positive
ImpliedBoundEntry(IntegerVariable lit, IntegerValue lb, bool positive)
IntegerValue lower_bound
IntegerVariable literal_view