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