Google OR-Tools v9.14
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 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/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 // Adds to the integer trail all the new level-zero deduction made here.
136 // This can only be called at decision level zero. Returns false iff the model
137 // is infeasible.
139
140 private:
141 const SatParameters& parameters_;
142 SatSolver* sat_solver_;
143 IntegerTrail* integer_trail_;
144 IntegerEncoder* integer_encoder_;
145 SharedStatistics* shared_stats_;
146
147 // TODO(user): Remove the need for this.
148 std::vector<IntegerLiteral> tmp_integer_literals_;
149
150 // For each (Literal, IntegerVariable) the best lower bound implied by this
151 // literal. Note that there is no need to store any entries that do not
152 // improve on the level zero lower bound.
153 //
154 // TODO(user): we could lazily remove old entries to save a bit of space if
155 // many deduction where made at level zero.
156 absl::flat_hash_map<std::pair<LiteralIndex, IntegerVariable>, IntegerValue>
157 bounds_;
158
159 // Note(user): This is currently only used during cut generation, so only the
160 // Literal with an IntegerView that can be used in the LP relaxation need to
161 // be kept here.
162 //
163 // TODO(user): Use inlined vectors. Even better, we actually only process
164 // all variables at once, so no need to organize it by IntegerVariable even
165 // if that might be more friendly cache-wise.
166 std::vector<ImpliedBoundEntry> empty_implied_bounds_;
168 var_to_bounds_;
169 SparseBitset<IntegerVariable> has_implied_bounds_;
170
171 // Stores implied values per variable.
172 absl::flat_hash_map<LiteralIndex,
173 absl::flat_hash_map<IntegerVariable, IntegerValue>>
174 literal_to_var_to_value_;
175 const absl::flat_hash_map<IntegerVariable, IntegerValue> empty_var_to_value_;
176
177 // Stats.
178 int64_t num_deductions_ = 0;
179 int64_t num_enqueued_in_var_to_bounds_ = 0;
180};
181
183 public:
184 ElementEncodings() = default;
185
186 // Register the fact that var = sum literal * value with sum literal == 1.
187 // Note that we call this an "element" encoding because a value can appear
188 // more than once.
189 void Add(IntegerVariable var, const std::vector<ValueLiteralPair>& encoding,
190 int exactly_one_index);
191
192 // Returns an empty map if there is no such encoding.
193 const absl::btree_map<int, std::vector<ValueLiteralPair>>& Get(
194 IntegerVariable var);
195
196 // Get an unsorted set of variables appearing in element encodings.
197 const std::vector<IntegerVariable>& GetElementEncodedVariables() const;
198
199 private:
200 absl::btree_map<IntegerVariable,
201 absl::btree_map<int, std::vector<ValueLiteralPair>>>
202 var_to_index_to_element_encodings_;
203 const absl::btree_map<int, std::vector<ValueLiteralPair>>
204 empty_element_encoding_;
205 std::vector<IntegerVariable> element_encoded_variables_;
206};
207
208// Helper class to express a product as a linear constraint.
210 public:
211 explicit ProductDecomposer(Model* model)
212 : integer_trail_(model->GetOrCreate<IntegerTrail>()),
213 element_encodings_(model->GetOrCreate<ElementEncodings>()),
214 integer_encoder_(model->GetOrCreate<IntegerEncoder>()) {}
215
216 // Tries to decompose a product left * right in a list of constant alternative
217 // left_value * right_value controlled by literals in an exactly one
218 // relationship. We construct this by using literals from the full encoding or
219 // element encodings of the variables of the two affine expressions.
220 // If it fails, it returns an empty vector.
221 std::vector<LiteralValueValue> TryToDecompose(const AffineExpression& left,
222 const AffineExpression& right);
223
224 // Looks at value encodings and detects if the product of two variables can be
225 // linearized.
226 //
227 // On true, the builder will be cleared to contain the linearization. On
228 // false, it might be in an undefined state.
229 //
230 // In the returned encoding, note that all the literals will be unique and in
231 // exactly one relation, and that the values can be duplicated. This is what
232 // we call an "element" encoding. The expressions will also be canonical.
233 bool TryToLinearize(const AffineExpression& left,
234 const AffineExpression& right,
235 LinearConstraintBuilder* builder);
236
237 private:
238 IntegerTrail* integer_trail_;
239 ElementEncodings* element_encodings_;
240 IntegerEncoder* integer_encoder_;
241};
242
243// Class used to detect and hold all the information about a variable beeing the
244// product of two others. This class is meant to be used by LP relaxation and
245// cuts.
247 public:
248 explicit ProductDetector(Model* model);
250
251 // Internally, a Boolean product is encoded in a linear fashion:
252 // p = a * b become:
253 // 1/ a and b => p, i.e. a clause (not(a), not(b), p).
254 // 2/ p => a and p => b, which is a clause (not(p), a) and (not(p), b).
255 //
256 // In particular if we have a+b+c==1 then we have a=b*c, b=a*c, and c=a*b !!
257 //
258 // For the detection to work, we must load all ternary clause first, then the
259 // implication.
260 void ProcessTernaryClause(absl::Span<const Literal> ternary_clause);
261 void ProcessTernaryExactlyOne(absl::Span<const Literal> ternary_exo);
262 void ProcessBinaryClause(absl::Span<const Literal> binary_clause);
263
264 // Utility function to process a bunch of implication all at once.
267
268 // We also detect product of Boolean with IntegerVariable.
269 // After presolve, a product P = l * X should be encoded with:
270 // l => P = X
271 // not(l) => P = 0
272 //
273 // TODO(user): Generalize to a * X + b = l * (Y + c) since these are also
274 // easy to linearize if we see l * Y.
275 void ProcessConditionalEquality(Literal l, IntegerVariable x,
276 IntegerVariable y);
277 void ProcessConditionalZero(Literal l, IntegerVariable p);
278
279 // Query function mainly used for testing.
280 LiteralIndex GetProduct(Literal a, Literal b) const;
281 IntegerVariable GetProduct(Literal a, IntegerVariable b) const;
282
283 // Query Functions. LinearizeProduct() should only be called if
284 // ProductIsLinearizable() is true.
285 bool ProductIsLinearizable(IntegerVariable a, IntegerVariable b) const;
286
287 // TODO(user): Implement!
288 LinearExpression LinearizeProduct(IntegerVariable a, IntegerVariable b);
289
290 // Returns an expression that is always lower or equal to the product a * b.
291 // This use the exact LinearizeProduct() if ProductIsLinearizable() otherwise
292 // it uses the simple McCormick lower bound.
293 //
294 // TODO(user): Implement!
295 LinearExpression ProductLowerBound(IntegerVariable a, IntegerVariable b);
296
297 // Experimental. Find violated inequality of the form l1 * l2 <= l3.
298 // And set-up data structure to query this efficiently.
300 absl::Span<const IntegerVariable> lp_vars,
302
303 // BoolRLTCandidates()[var] contains the list of factor for which we have
304 // a violated upper bound on lit(var) * lit(factor).
305 const absl::flat_hash_map<IntegerVariable, std::vector<IntegerVariable>>&
307 return bool_rlt_candidates_;
308 }
309
310 // Returns if it exists an integer variable u such that lit(a) * lit(b) <=
311 // lit(u). All integer variable must be boolean, a positive variable means
312 // positive literal, and a negative variable means negative literal. Returns
313 // kNoIntegerVariable if there are none.
314 IntegerVariable LiteralProductUpperBound(IntegerVariable a,
315 IntegerVariable b) const {
316 if (b < a) std::swap(a, b);
317 const auto it = bool_rlt_ubs_.find({a, b});
318 if (it == bool_rlt_ubs_.end()) return kNoIntegerVariable;
319 return it->second;
320 }
321
322 private:
323 std::array<LiteralIndex, 2> GetKey(LiteralIndex a, LiteralIndex b) const;
324 void ProcessNewProduct(LiteralIndex p, LiteralIndex a, LiteralIndex b);
325 void ProcessNewProduct(IntegerVariable p, Literal l, IntegerVariable x);
326 void ProcessTernaryClauseForRLT(absl::Span<const Literal> clause);
327
328 // Process a relation lit(var1) * lit(var2) <= lit(bound_var).
329 void UpdateRLTMaps(
331 IntegerVariable var1, double lp1, IntegerVariable var2, double lp2,
332 IntegerVariable bound_var, double bound_lp);
333
334 // Fixed at creation time.
335 const bool enabled_;
336 const bool rlt_enabled_;
337 SatSolver* sat_solver_;
338 Trail* trail_;
339 IntegerTrail* integer_trail_;
340 IntegerEncoder* integer_encoder_;
341 SharedStatistics* shared_stats_;
342
343 // No need to process implication a => b if a was never seen.
345
346 // For each clause of size 3 (l0, l1, l2) and a permutation of index (i, j, k)
347 // we bitset[i] to true if lj => not(lk) and lk => not(lj).
348 //
349 // The key is sorted.
350 absl::flat_hash_map<std::array<LiteralIndex, 3>, std::bitset<3>> detector_;
351
352 // For each (l0, l1) we list all the l2 such that (l0, l1, l2) is a 3 clause.
353 absl::flat_hash_map<std::array<LiteralIndex, 2>, std::vector<LiteralIndex>>
354 candidates_;
355
356 // Products (a, b) -> p such that p == a * b. They key is sorted.
357 absl::flat_hash_map<std::array<LiteralIndex, 2>, LiteralIndex> products_;
358
359 // Same keys has in products_ but canonicalized so we capture all 4 products
360 // a * b, (1 - a) * b, a * (1 - b) and (1 - a) * (1 - b) with one query.
361 absl::flat_hash_set<std::array<LiteralIndex, 2>> has_product_;
362
363 // For bool * int detection. Note that we only use positive IntegerVariable
364 // in the key part.
365 absl::flat_hash_set<std::pair<LiteralIndex, IntegerVariable>>
366 conditional_zeros_;
367 absl::flat_hash_map<std::pair<LiteralIndex, IntegerVariable>,
368 std::vector<IntegerVariable>>
369 conditional_equalities_;
370
371 // Stores l * X = P.
372 absl::flat_hash_map<std::pair<LiteralIndex, IntegerVariable>, IntegerVariable>
373 int_products_;
374
375 // For RLT cuts.
376 absl::flat_hash_map<IntegerVariable, std::vector<IntegerVariable>>
377 bool_rlt_candidates_;
378 absl::flat_hash_map<std::pair<IntegerVariable, IntegerVariable>,
379 IntegerVariable>
380 bool_rlt_ubs_;
381
382 // Store ternary clause which have an IntegerVariable view.
383 // We only consider BooleanVariable == IntegerVariable, and store not(literal)
384 // as NegatedVariable(). This is a flat vector of size multiple of 3.
385 std::vector<IntegerVariable> ternary_clauses_with_view_;
386
387 Bitset64<IntegerVariable> is_in_lp_vars_;
388
389 // Stats.
390 int64_t num_products_ = 0;
391 int64_t num_int_products_ = 0;
392 int64_t num_trail_updates_ = 0;
393 int64_t num_processed_binary_ = 0;
394 int64_t num_processed_ternary_ = 0;
395 int64_t num_processed_exo_ = 0;
396 int64_t num_conditional_zeros_ = 0;
397 int64_t num_conditional_equalities_ = 0;
398};
399
400} // namespace sat
401} // namespace operations_research
402
403#endif // OR_TOOLS_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)
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)
LiteralIndex Index() const
Definition sat_base.h:91
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)
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 ProcessBinaryClause(absl::Span< const Literal > binary_clause)
Simple class to add statistics by name and print them at the end.
const IntegerVariable kNoIntegerVariable(-1)
In SWIG mode, we don't want anything besides these top-level includes.
ImpliedBoundEntry(IntegerVariable lit, IntegerValue lb)
These constructors are needed for OR-Tools.