Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
implied_bounds.h
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
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"
25#include "absl/container/flat_hash_set.h"
26#include "absl/types/span.h"
29#include "ortools/base/types.h"
31#include "ortools/sat/clause.h"
32#include "ortools/sat/integer.h"
34#include "ortools/sat/model.h"
36#include "ortools/sat/sat_parameters.pb.h"
39#include "ortools/util/bitset.h"
41
42namespace operations_research {
43namespace sat {
44
45// For each IntegerVariable, the ImpliedBound class allows to list all such
46// entries.
47//
48// This is meant to be used in the cut generation code when it make sense: if we
49// have BoolVar => X >= bound, we can always lower bound the variable X by
50// (bound - X_lb) * BoolVar + X_lb, and that can lead to stronger cuts.
52 // An integer variable in [0, 1]. When at 1, then the IntegerVariable
53 // corresponding to this entry must be greater or equal to the given lower
54 // bound.
56 IntegerValue lower_bound = IntegerValue(0);
57
58 // If false, it is when the literal_view is zero that the lower bound is
59 // valid.
60 bool is_positive = true;
61
62 // These constructors are needed for OR-Tools.
63 ImpliedBoundEntry(IntegerVariable lit, IntegerValue lb, bool positive)
64 : literal_view(lit), lower_bound(lb), is_positive(positive) {}
65
68};
69
70// Maintains all the implications of the form Literal => IntegerLiteral. We
71// collect these implication at model loading, during probing and during search.
72//
73// TODO(user): This can quickly use up too much memory. Add some limit in place.
74// In particular, each time we have literal => integer_literal we should avoid
75// storing the same integer_literal for all other_literal for which
76// other_literal => literal. For this we need to interact with the
77// BinaryImplicationGraph.
78//
79// TODO(user): This is a bit of a duplicate with the Literal <=> IntegerLiteral
80// stored in the IntegerEncoder class. However we only need one side here.
81//
82// TODO(user): Do like in the DomainDeductions class and allow to process
83// clauses (or store them) to perform more level zero deductions. Note that this
84// is again a slight duplicate with what we do there (except that we work at the
85// Domain level in that presolve class).
86//
87// TODO(user): Add an implied bound cut generator to add these simple
88// constraints to the LP when needed.
90 public:
92 : parameters_(*model->GetOrCreate<SatParameters>()),
93 sat_solver_(model->GetOrCreate<SatSolver>()),
94 integer_trail_(model->GetOrCreate<IntegerTrail>()),
95 integer_encoder_(model->GetOrCreate<IntegerEncoder>()),
96 shared_stats_(model->GetOrCreate<SharedStatistics>()) {}
98
99 // Adds literal => integer_literal to the repository.
100 //
101 // Not that it checks right aways if there is another bound on the same
102 // variable involving literal.Negated(), in which case we can improve the
103 // level zero lower bound of the variable.
104 bool Add(Literal literal, IntegerLiteral integer_literal);
105
106 // Adds literal => var == value.
107 void AddLiteralImpliesVarEqValue(Literal literal, IntegerVariable var,
108 IntegerValue value);
109
110 // This must be called after first_decision has been enqueued and propagated.
111 // It will inspect the trail and add all new implied bounds.
112 //
113 // Preconditions: The decision level must be one (CHECKed). And the decision
114 // must be equal to first decision (we currently do not CHECK that).
115 bool ProcessIntegerTrail(Literal first_decision);
116
117 // Returns all the implied bounds stored for the given variable.
118 // Note that only literal with an IntegerView are considered here.
119 const std::vector<ImpliedBoundEntry>& GetImpliedBounds(IntegerVariable var);
120
121 // Returns all the variables for which GetImpliedBounds(var) is not empty. Or
122 // at least that was not empty at some point, because we lazily remove bounds
123 // that become trivial as the search progress.
124 const std::vector<IntegerVariable>& VariablesWithImpliedBounds() const {
125 return has_implied_bounds_.PositionsSetAtLeastOnce();
126 }
127
128 // Returns all the implied values stored for a given literal.
129 const absl::flat_hash_map<IntegerVariable, IntegerValue>& GetImpliedValues(
130 Literal literal) const {
131 const auto it = literal_to_var_to_value_.find(literal.Index());
132 return it != literal_to_var_to_value_.end() ? it->second
133 : empty_var_to_value_;
134 }
135
136 // Adds to the integer trail all the new level-zero deduction made here.
137 // This can only be called at decision level zero. Returns false iff the model
138 // is infeasible.
140
141 private:
142 const SatParameters& parameters_;
143 SatSolver* sat_solver_;
144 IntegerTrail* integer_trail_;
145 IntegerEncoder* integer_encoder_;
146 SharedStatistics* shared_stats_;
147
148 // TODO(user): Remove the need for this.
149 std::vector<IntegerLiteral> tmp_integer_literals_;
150
151 // For each (Literal, IntegerVariable) the best lower bound implied by this
152 // literal. Note that there is no need to store any entries that do not
153 // improve on the level zero lower bound.
154 //
155 // TODO(user): we could lazily remove old entries to save a bit of space if
156 // many deduction where made at level zero.
157 absl::flat_hash_map<std::pair<LiteralIndex, IntegerVariable>, IntegerValue>
158 bounds_;
159
160 // Note(user): This is currently only used during cut generation, so only the
161 // Literal with an IntegerView that can be used in the LP relaxation need to
162 // be kept here.
163 //
164 // TODO(user): Use inlined vectors. Even better, we actually only process
165 // all variables at once, so no need to organize it by IntegerVariable even
166 // if that might be more friendly cache-wise.
167 std::vector<ImpliedBoundEntry> empty_implied_bounds_;
169 var_to_bounds_;
170 SparseBitset<IntegerVariable> has_implied_bounds_;
171
172 // Stores implied values per variable.
173 absl::flat_hash_map<LiteralIndex,
174 absl::flat_hash_map<IntegerVariable, IntegerValue>>
175 literal_to_var_to_value_;
176 const absl::flat_hash_map<IntegerVariable, IntegerValue> empty_var_to_value_;
177
178 // Stats.
179 int64_t num_deductions_ = 0;
180 int64_t num_enqueued_in_var_to_bounds_ = 0;
181};
182
184 public:
185 ElementEncodings() = default;
186
187 // Register the fact that var = sum literal * value with sum literal == 1.
188 // Note that we call this an "element" encoding because a value can appear
189 // more than once.
190 void Add(IntegerVariable var, const std::vector<ValueLiteralPair>& encoding,
191 int exactly_one_index);
192
193 // Returns an empty map if there is no such encoding.
194 const absl::btree_map<int, std::vector<ValueLiteralPair>>& Get(
195 IntegerVariable var);
196
197 // Get an unsorted set of variables appearing in element encodings.
198 const std::vector<IntegerVariable>& GetElementEncodedVariables() const;
199
200 private:
201 absl::btree_map<IntegerVariable,
202 absl::btree_map<int, std::vector<ValueLiteralPair>>>
203 var_to_index_to_element_encodings_;
204 const absl::btree_map<int, std::vector<ValueLiteralPair>>
205 empty_element_encoding_;
206 std::vector<IntegerVariable> element_encoded_variables_;
207};
208
209// Helper class to express a product as a linear constraint.
211 public:
213 : integer_trail_(model->GetOrCreate<IntegerTrail>()),
214 element_encodings_(model->GetOrCreate<ElementEncodings>()),
215 integer_encoder_(model->GetOrCreate<IntegerEncoder>()) {}
216
217 // Tries to decompose a product left * right in a list of constant alternative
218 // left_value * right_value controlled by literals in an exactly one
219 // relationship. We construct this by using literals from the full encoding or
220 // element encodings of the variables of the two affine expressions.
221 // If it fails, it returns an empty vector.
222 std::vector<LiteralValueValue> TryToDecompose(const AffineExpression& left,
223 const AffineExpression& right);
224
225 // Looks at value encodings and detects if the product of two variables can be
226 // linearized.
227 //
228 // On true, the builder will be cleared to contain the linearization. On
229 // false, it might be in an undefined state.
230 //
231 // In the returned encoding, note that all the literals will be unique and in
232 // exactly one relation, and that the values can be duplicated. This is what
233 // we call an "element" encoding. The expressions will also be canonical.
234 bool TryToLinearize(const AffineExpression& left,
235 const AffineExpression& right,
236 LinearConstraintBuilder* builder);
237
238 private:
239 IntegerTrail* integer_trail_;
240 ElementEncodings* element_encodings_;
241 IntegerEncoder* integer_encoder_;
242};
243
244// Class used to detect and hold all the information about a variable beeing the
245// product of two others. This class is meant to be used by LP relaxation and
246// cuts.
248 public:
249 explicit ProductDetector(Model* model);
251
252 // Internally, a Boolean product is encoded in a linear fashion:
253 // p = a * b become:
254 // 1/ a and b => p, i.e. a clause (not(a), not(b), p).
255 // 2/ p => a and p => b, which is a clause (not(p), a) and (not(p), b).
256 //
257 // In particular if we have a+b+c==1 then we have a=b*c, b=a*c, and c=a*b !!
258 //
259 // For the detection to work, we must load all ternary clause first, then the
260 // implication.
261 void ProcessTernaryClause(absl::Span<const Literal> ternary_clause);
262 void ProcessTernaryExactlyOne(absl::Span<const Literal> ternary_exo);
263 void ProcessBinaryClause(absl::Span<const Literal> binary_clause);
264
265 // Utility function to process a bunch of implication all at once.
268
269 // We also detect product of Boolean with IntegerVariable.
270 // After presolve, a product P = l * X should be encoded with:
271 // l => P = X
272 // not(l) => P = 0
273 //
274 // TODO(user): Generalize to a * X + b = l * (Y + c) since these are also
275 // easy to linearize if we see l * Y.
276 void ProcessConditionalEquality(Literal l, IntegerVariable x,
277 IntegerVariable y);
278 void ProcessConditionalZero(Literal l, IntegerVariable p);
279
280 // Query function mainly used for testing.
281 LiteralIndex GetProduct(Literal a, Literal b) const;
282 IntegerVariable GetProduct(Literal a, IntegerVariable b) const;
283
284 // Query Functions. LinearizeProduct() should only be called if
285 // ProductIsLinearizable() is true.
286 bool ProductIsLinearizable(IntegerVariable a, IntegerVariable b) const;
287
288 // TODO(user): Implement!
289 LinearExpression LinearizeProduct(IntegerVariable a, IntegerVariable b);
290
291 // Returns an expression that is always lower or equal to the product a * b.
292 // This use the exact LinearizeProduct() if ProductIsLinearizable() otherwise
293 // it uses the simple McCormick lower bound.
294 //
295 // TODO(user): Implement!
296 LinearExpression ProductLowerBound(IntegerVariable a, IntegerVariable b);
297
298 // Experimental. Find violated inequality of the form l1 * l2 <= l3.
299 // And set-up data structure to query this efficiently.
301 const absl::flat_hash_map<IntegerVariable, glop::ColIndex>& lp_vars,
303
304 // BoolRLTCandidates()[var] contains the list of factor for which we have
305 // a violated upper bound on lit(var) * lit(factor).
306 const absl::flat_hash_map<IntegerVariable, std::vector<IntegerVariable>>&
308 return bool_rlt_candidates_;
309 }
310
311 // Returns if it exists an integer variable u such that lit(a) * lit(b) <=
312 // lit(u). All integer variable must be boolean, a positive variable means
313 // positive literal, and a negative variable means negative literal. Returns
314 // kNoIntegerVariable if there are none.
315 IntegerVariable LiteralProductUpperBound(IntegerVariable a,
316 IntegerVariable b) const {
317 if (b < a) std::swap(a, b);
318 const auto it = bool_rlt_ubs_.find({a, b});
319 if (it == bool_rlt_ubs_.end()) return kNoIntegerVariable;
320 return it->second;
321 }
322
323 private:
324 std::array<LiteralIndex, 2> GetKey(LiteralIndex a, LiteralIndex b) const;
325 void ProcessNewProduct(LiteralIndex p, LiteralIndex a, LiteralIndex b);
326 void ProcessNewProduct(IntegerVariable p, Literal l, IntegerVariable x);
327 void ProcessTernaryClauseForRLT(absl::Span<const Literal> clause);
328
329 // Process a relation lit(var1) * lit(var2) <= lit(bound_var).
330 void UpdateRLTMaps(
332 IntegerVariable var1, double lp1, IntegerVariable var2, double lp2,
333 IntegerVariable bound_var, double bound_lp);
334
335 // Fixed at creation time.
336 const bool enabled_;
337 const bool rlt_enabled_;
338 SatSolver* sat_solver_;
339 Trail* trail_;
340 IntegerTrail* integer_trail_;
341 IntegerEncoder* integer_encoder_;
342 SharedStatistics* shared_stats_;
343
344 // No need to process implication a => b if a was never seen.
346
347 // For each clause of size 3 (l0, l1, l2) and a permutation of index (i, j, k)
348 // we bitset[i] to true if lj => not(lk) and lk => not(lj).
349 //
350 // The key is sorted.
351 absl::flat_hash_map<std::array<LiteralIndex, 3>, std::bitset<3>> detector_;
352
353 // For each (l0, l1) we list all the l2 such that (l0, l1, l2) is a 3 clause.
354 absl::flat_hash_map<std::array<LiteralIndex, 2>, std::vector<LiteralIndex>>
355 candidates_;
356
357 // Products (a, b) -> p such that p == a * b. They key is sorted.
358 absl::flat_hash_map<std::array<LiteralIndex, 2>, LiteralIndex> products_;
359
360 // Same keys has in products_ but canonicalized so we capture all 4 products
361 // a * b, (1 - a) * b, a * (1 - b) and (1 - a) * (1 - b) with one query.
362 absl::flat_hash_set<std::array<LiteralIndex, 2>> has_product_;
363
364 // For bool * int detection. Note that we only use positive IntegerVariable
365 // in the key part.
366 absl::flat_hash_set<std::pair<LiteralIndex, IntegerVariable>>
367 conditional_zeros_;
368 absl::flat_hash_map<std::pair<LiteralIndex, IntegerVariable>,
369 std::vector<IntegerVariable>>
370 conditional_equalities_;
371
372 // Stores l * X = P.
373 absl::flat_hash_map<std::pair<LiteralIndex, IntegerVariable>, IntegerVariable>
374 int_products_;
375
376 // For RLT cuts.
377 absl::flat_hash_map<IntegerVariable, std::vector<IntegerVariable>>
378 bool_rlt_candidates_;
379 absl::flat_hash_map<std::pair<IntegerVariable, IntegerVariable>,
380 IntegerVariable>
381 bool_rlt_ubs_;
382
383 // Store ternary clause which have an IntegerVariable view.
384 // We only consider BooleanVariable == IntegerVariable, and store not(literal)
385 // as NegatedVariable(). This is a flat vector of size multiple of 3.
386 std::vector<IntegerVariable> ternary_clauses_with_view_;
387
388 // Stats.
389 int64_t num_products_ = 0;
390 int64_t num_int_products_ = 0;
391 int64_t num_trail_updates_ = 0;
392 int64_t num_processed_binary_ = 0;
393 int64_t num_processed_ternary_ = 0;
394 int64_t num_processed_exo_ = 0;
395 int64_t num_conditional_zeros_ = 0;
396 int64_t num_conditional_equalities_ = 0;
397};
398
399} // namespace sat
400} // namespace operations_research
401
402#endif // OR_TOOLS_SAT_IMPLIED_BOUNDS_H_
IntegerValue y
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition bitset.h:878
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)
const absl::flat_hash_map< IntegerVariable, IntegerValue > & GetImpliedValues(Literal literal) const
Returns all the implied values stored for a given literal.
~ImpliedBounds()
Just display some global statistics on destruction.
const std::vector< IntegerVariable > & VariablesWithImpliedBounds() const
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)
Helper class to express a product as a linear constraint.
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.
LinearExpression ProductLowerBound(IntegerVariable a, IntegerVariable b)
LiteralIndex GetProduct(Literal a, Literal b) const
Query function mainly used for testing.
LinearExpression LinearizeProduct(IntegerVariable a, IntegerVariable b)
bool ProductIsLinearizable(IntegerVariable a, IntegerVariable b) const
void ProcessConditionalEquality(Literal l, IntegerVariable x, IntegerVariable y)
void ProcessConditionalZero(Literal l, IntegerVariable p)
IntegerVariable LiteralProductUpperBound(IntegerVariable a, IntegerVariable b) const
const absl::flat_hash_map< IntegerVariable, std::vector< IntegerVariable > > & BoolRLTCandidates() const
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)
Simple class to add statistics by name and print them at the end.
int64_t a
Definition table.cc:44
GraphType graph
int64_t value
IntVar * var
GRBmodel * model
int lit
int literal
const IntegerVariable kNoIntegerVariable(-1)
In SWIG mode, we don't want anything besides these top-level includes.
const Variable x
Definition qp_tests.cc:127
ImpliedBoundEntry(IntegerVariable lit, IntegerValue lb, bool positive)
These constructors are needed for OR-Tools.