Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
presolve_util.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_PRESOLVE_UTIL_H_
15#define OR_TOOLS_SAT_PRESOLVE_UTIL_H_
16
17#include <array>
18#include <cstdint>
19#include <string>
20#include <utility>
21#include <vector>
22
23#include "absl/container/flat_hash_map.h"
24#include "absl/container/flat_hash_set.h"
25#include "absl/random/bit_gen_ref.h"
26#include "absl/types/span.h"
28#include "ortools/base/timer.h"
29#include "ortools/sat/cp_model.pb.h"
31#include "ortools/sat/util.h"
32#include "ortools/util/bitset.h"
37
38namespace operations_research {
39namespace sat {
40
41// Simple helper class to:
42// - log in an uniform way a "time-consuming" presolve operation.
43// - track a deterministic work limit.
44// - update the deterministic time on finish.
46 public:
47 PresolveTimer(std::string name, SolverLogger* logger, TimeLimit* time_limit)
48 : name_(std::move(name)), logger_(logger), time_limit_(time_limit) {
49 timer_.Start();
50 }
51
52 // Track the work done (which is also the deterministic time).
53 // By default we want a limit of around 1 deterministic seconds.
54 void AddToWork(double dtime) { work_ += dtime; }
55 void TrackSimpleLoop(int size) { work_ += 5e-9 * size; }
56 bool WorkLimitIsReached() const { return work_ >= 1.0; }
57
58 // Extra stats=value to display at the end.
59 // We filter value of zero to have less clutter.
60 void AddCounter(std::string name, int64_t count) {
61 if (count == 0) return;
62 counters_.emplace_back(std::move(name), count);
63 }
64
65 // Extra info at the end of the log line.
66 void AddMessage(std::string name) { extra_infos_.push_back(std::move(name)); }
67
68 // Update dtime and log operation summary.
70
71 private:
72 const std::string name_;
73
74 WallTimer timer_;
75 SolverLogger* logger_;
76 TimeLimit* time_limit_;
77
78 double work_ = 0;
79 std::vector<std::pair<std::string, int64_t>> counters_;
80 std::vector<std::string> extra_infos_;
81};
82
83// If for each literal of a clause, we can infer a domain on an integer
84// variable, then we know that this variable domain is included in the union of
85// such inferred domains.
86//
87// This allows to propagate "element" like constraints encoded as enforced
88// linear relations, and other more general reasoning.
89//
90// TODO(user): Also use these "deductions" in the solver directly. This is done
91// in good MIP solvers, and we should exploit them more.
92//
93// TODO(user): Also propagate implicit clauses (lit, not(lit)). Maybe merge
94// that with probing code? it might be costly to store all deduction done by
95// probing though, but I think this is what MIP solver do.
97 public:
98 // Adds the fact that enforcement => var \in domain.
99 //
100 // Important: No need to store any deductions where the domain is a superset
101 // of the current variable domain.
102 void AddDeduction(int literal_ref, int var, Domain domain);
103
104 // Returns the domain of var when literal_ref is true.
105 // If there is no information, returns Domain::AllValues().
106 Domain ImpliedDomain(int literal_ref, int var) const;
107
108 // Returns list of (var, domain) that were deduced because:
109 // 1/ We have a domain deduction for var and all literal from the clause
110 // 2/ So we can take the union of all the deduced domains.
111 //
112 // TODO(user): We could probably be even more efficient. We could also
113 // compute exactly what clauses need to be "waked up" as new deductions are
114 // added.
115 std::vector<std::pair<int, Domain>> ProcessClause(
116 absl::Span<const int> clause);
117
118 // Optimization. Any following ProcessClause() will be fast if no more
119 // deduction touching that clause are added.
121 something_changed_.ClearAndResize(something_changed_.size());
122 }
123
124 // Returns the total number of "deductions" stored by this class.
125 int NumDeductions() const { return deductions_.size(); }
126
127 private:
129 Index IndexFromLiteral(int ref) const {
130 return Index(ref >= 0 ? 2 * ref : -2 * ref - 1);
131 }
132
133 std::vector<int> tmp_num_occurrences_;
134
135 SparseBitset<Index> something_changed_;
137 absl::flat_hash_map<std::pair<Index, int>, Domain> deductions_;
138};
139
140// Does "to_modify += factor * to_add". Both constraint must be linear.
141// Returns false and does not change anything in case of overflow.
142//
143// Note that the enforcement literals (if any) are ignored and left untouched.
144bool AddLinearConstraintMultiple(int64_t factor, const ConstraintProto& to_add,
145 ConstraintProto* to_modify);
146
147// Replaces the variable var in ct using the definition constraint.
148// Currently the coefficient in the definition must be 1 or -1.
149//
150// This might return false and NOT modify ConstraintProto in case of overflow
151// or other issue with the substitution.
152bool SubstituteVariable(int var, int64_t var_coeff_in_definition,
153 const ConstraintProto& definition, ConstraintProto* ct);
154
155// Same as a vector<T> or hash_map<int, T> where the index are in [0, size),
156// but optimized for the case where only a few entries are touched before the
157// vector need to be reset to zero and used again.
158//
159// TODO(user): Maybe a SparseBitset + sparse clear is better. But this is a
160// worth alternative to test IMO.
161template <typename T>
163 public:
164 // Taking a view allow to cache the never changing addresses.
165 class View {
166 public:
167 View(int* i, int* pi, T* pv)
168 : index_to_position_(i),
169 position_to_index_(pi),
170 position_to_value_(pv) {}
171
172 T& operator[](int index) {
173 const int p = index_to_position_[index];
174 if (p < size_ && index == position_to_index_[p]) {
175 // [index] was already called.
176 return position_to_value_[p];
177 }
178
179 // First call.
180 index_to_position_[index] = size_;
181 position_to_index_[size_] = index;
182 position_to_value_[size_] = 0;
183 return position_to_value_[size_++];
184 }
185
186 private:
187 int size_ = 0;
188 int* const index_to_position_;
189 int* const position_to_index_;
190 T* const position_to_value_;
191 };
192
193 // This reserve the size for using indices in [0, size).
194 View ClearedView(int size) {
195 index_to_position_.resize(size);
196 position_to_index_.resize(size);
197 position_to_value_.resize(size);
198 return View(index_to_position_.data(), position_to_index_.data(),
199 position_to_value_.data());
200 }
201
202 private:
203 // We never need to clear this. We can detect stale positions if
204 // position_to_index_[index_to_position_[index]] is inconsistent.
205 std::vector<int> index_to_position_;
206
207 // Only the beginning [0, num touched indices) is used here.
208 std::vector<int> position_to_index_;
209 std::vector<T> position_to_value_;
210};
211
212// Try to get more precise min/max activity of a linear constraints using
213// at most ones from the model. This is heuristic based but should be relatively
214// fast.
215//
216// TODO(user): Use better algorithm. The problem is the same as finding upper
217// bound to the classic problem: maximum-independent set in a graph. We also
218// only use at most ones, but we could use the more general binary implication
219// graph.
221 public:
223
224 // The at most one constraint must be added before linear constraint are
225 // processed. The functions below will still works, but do nothing more than
226 // compute trivial bounds.
227 void ClearAtMostOnes();
228 void AddAtMostOne(absl::Span<const int> amo);
229 void AddAllAtMostOnes(const CpModelProto& proto);
230
231 // Computes the max/min activity of a linear expression involving only
232 // Booleans.
233 //
234 // Accepts a list of (literal, coefficient). Note that all literal will be
235 // interpreted as referring to [0, 1] variables. We use the CpModelProto
236 // convention for negated literal index.
237 //
238 // If conditional is not nullptr, then conditional[i][0/1] will give the
239 // max activity if the literal at position i is false/true. This can be used
240 // to fix variables or extract enforcement literal.
241 //
242 // Important: We shouldn't have duplicates or a lit and NegatedRef(lit)
243 // appearing both.
244 //
245 // Note: the result of this function is not exact (it uses an heuristic to
246 // detect AMOs), but it does not depend on the order of the input terms, so
247 // passing an input in non-deterministic order is fine.
248 //
249 // TODO(user): Indicate when the bounds are trivial (i.e. not intersection
250 // with any amo) so that we don't waste more time processing the result?
252 absl::Span<const std::pair<int, int64_t>> terms,
253 std::vector<std::array<int64_t, 2>>* conditional = nullptr) {
254 return ComputeActivity(/*compute_min=*/false, terms, conditional);
255 }
257 absl::Span<const std::pair<int, int64_t>> terms,
258 std::vector<std::array<int64_t, 2>>* conditional = nullptr) {
259 return ComputeActivity(/*compute_min=*/true, terms, conditional);
260 }
261
262 // Computes relevant info to presolve a constraint with enforcement using
263 // at most one information.
264 //
265 // This returns false iff the enforcement list cannot be satisfied.
266 // It filters the enforcement list if some are consequences of other.
267 // It fills the given set with the literal that must be true due to the
268 // enforcement. Note that only literals or negated literal appearing in ref
269 // are filled.
270 bool PresolveEnforcement(absl::Span<const int> refs, ConstraintProto* ct,
271 absl::flat_hash_set<int>* literals_at_true);
272
273 // For each enforcement literal enf, if not(enf) implies that the constraint
274 // is trivial, then we can just remove not(enf) from the list.
275 //
276 // Actually, we could even "lift" such enforcement so that if it is negative
277 // the constraint is still trivial but tighter.
279 absl::Span<const std::pair<int, int64_t>> boolean_terms,
280 const Domain& other_terms, const Domain& rhs, ConstraintProto* ct);
281
282 // Partition the list of literals into disjoint at most ones. The returned
283 // spans are only valid until another function from this class is used.
284 std::vector<absl::Span<const int>> PartitionLiteralsIntoAmo(
285 absl::Span<const int> literals);
286
287 // Returns true iff the given literal are in at most one relationship.
288 bool IsAmo(absl::Span<const int> literals);
289
290 // Returns in how many amo var or Not(var) are part of.
291 int NumAmoForVariable(int var) const {
292 const Index i = IndexFromLiteral(var);
293 const Index j = IndexFromLiteral(NegatedRef(var));
294 return (i < amo_indices_.size() ? amo_indices_[i].size() : 0) +
295 (j < amo_indices_.size() ? amo_indices_[j].size() : 0);
296 }
297
298 private:
300 Index IndexFromLiteral(int ref) const {
301 return Index(ref >= 0 ? 2 * ref : -2 * ref - 1);
302 }
303
304 bool AppearInTriggeredAmo(int literal);
305
306 int64_t ComputeActivity(
307 bool compute_min, absl::Span<const std::pair<int, int64_t>> terms,
308 std::vector<std::array<int64_t, 2>>* conditional = nullptr);
309
310 void PartitionIntoAmo(absl::Span<const std::pair<int, int64_t>> terms);
311
312 // All coeff must be >= 0 here. Note that in practice, we shouldn't have
313 // zero coeff, but we still support it.
314 int64_t ComputeMaxActivityInternal(
315 absl::Span<const std::pair<int, int64_t>> terms,
316 std::vector<std::array<int64_t, 2>>* conditional = nullptr);
317
318 // We use an unique index by at most one, and just stores for each literal
319 // the at most one to which it belong.
320 int num_at_most_ones_ = 0;
322
323 std::vector<std::pair<int, int64_t>> tmp_terms_for_compute_activity_;
324
325 struct TermWithIndex {
326 int64_t coeff;
327 Index index;
328 int span_index;
329 };
330 std::vector<TermWithIndex> to_sort_;
331
332 // We partition the set of term into disjoint at most one.
333 VectorWithSparseUsage<int64_t> amo_sums_;
334 std::vector<int> partition_;
335 std::vector<int64_t> max_by_partition_;
336 std::vector<int64_t> second_max_by_partition_;
337
338 // Used by PartitionLiteralsIntoAmo().
339 CompactVectorVector<int, int> part_to_literals_;
340
341 absl::flat_hash_set<int> triggered_amo_;
342 absl::flat_hash_set<int> tmp_set_;
343};
344
345// Class to help detects clauses that differ on a single literal.
347 public:
348 explicit ClauseWithOneMissingHasher(absl::BitGenRef random)
349 : random_(random) {}
350
351 // We use the proto encoding of literals here.
352 void RegisterClause(int c, absl::Span<const int> clause);
353
354 // Returns a hash of the clause with index c and literal ref removed.
355 // This assumes that ref was part of the clause. Work in O(1).
356 uint64_t HashWithout(int c, int ref) const {
357 return clause_to_hash_[c] ^ literal_to_hash_[IndexFromLiteral(ref)];
358 }
359
360 // Returns a hash of the negation of all the given literals.
361 uint64_t HashOfNegatedLiterals(absl::Span<const int> literals);
362
363 private:
365 Index IndexFromLiteral(int ref) const {
366 return Index(ref >= 0 ? 2 * ref : -2 * ref - 1);
367 }
368
369 absl::BitGenRef random_;
371 std::vector<uint64_t> clause_to_hash_;
372};
373
374// Specific function. Returns true if the negation of all literals in clause
375// except literal is exactly equal to the literal of enforcement.
376//
377// We assumes that enforcement and negated(clause) are sorted lexicographically
378// Or negated(enforcement) and clause. Both option works. If not, we will only
379// return false more often. When we return true, the property is enforced.
380//
381// TODO(user): For the same complexity, we do not need to specify literal and
382// can recover it.
383inline bool ClauseIsEnforcementImpliesLiteral(absl::Span<const int> clause,
384 absl::Span<const int> enforcement,
385 int literal) {
386 if (clause.size() != enforcement.size() + 1) return false;
387 int j = 0;
388 for (int i = 0; i < clause.size(); ++i) {
389 if (clause[i] == literal) continue;
390 if (clause[i] != NegatedRef(enforcement[j])) return false;
391 ++j;
392 }
393 return true;
394}
395
396// Same as LinearsDifferAtOneTerm() below but also fills the differing terms.
397bool FindSingleLinearDifference(const LinearConstraintProto& lin1,
398 const LinearConstraintProto& lin2, int* var1,
399 int64_t* coeff1, int* var2, int64_t* coeff2);
400
401// Returns true iff the two linear constraint only differ at a single term.
402//
403// Preconditions: Constraint should be sorted by variable and of same size.
404inline bool LinearsDifferAtOneTerm(const LinearConstraintProto& lin1,
405 const LinearConstraintProto& lin2) {
406 int var1, var2;
407 int64_t coeff1, coeff2;
408 return FindSingleLinearDifference(lin1, lin2, &var1, &coeff1, &var2, &coeff2);
409}
410
411} // namespace sat
412} // namespace operations_research
413
414#endif // OR_TOOLS_SAT_PRESOLVE_UTIL_H_
void AddAllAtMostOnes(const CpModelProto &proto)
int64_t ComputeMaxActivity(absl::Span< const std::pair< int, int64_t > > terms, std::vector< std::array< int64_t, 2 > > *conditional=nullptr)
void AddAtMostOne(absl::Span< const int > amo)
int RemoveEnforcementThatMakesConstraintTrivial(absl::Span< const std::pair< int, int64_t > > boolean_terms, const Domain &other_terms, const Domain &rhs, ConstraintProto *ct)
int64_t ComputeMinActivity(absl::Span< const std::pair< int, int64_t > > terms, std::vector< std::array< int64_t, 2 > > *conditional=nullptr)
std::vector< absl::Span< const int > > PartitionLiteralsIntoAmo(absl::Span< const int > literals)
Similar algo as above for this simpler case.
bool PresolveEnforcement(absl::Span< const int > refs, ConstraintProto *ct, absl::flat_hash_set< int > *literals_at_true)
bool IsAmo(absl::Span< const int > literals)
Returns true iff the given literal are in at most one relationship.
int NumAmoForVariable(int var) const
Returns in how many amo var or Not(var) are part of.
void RegisterClause(int c, absl::Span< const int > clause)
We use the proto encoding of literals here.
uint64_t HashOfNegatedLiterals(absl::Span< const int > literals)
Returns a hash of the negation of all the given literals.
int NumDeductions() const
Returns the total number of "deductions" stored by this class.
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
void AddDeduction(int literal_ref, int var, Domain domain)
Domain ImpliedDomain(int literal_ref, int var) const
void AddMessage(std::string name)
Extra info at the end of the log line.
PresolveTimer(std::string name, SolverLogger *logger, TimeLimit *time_limit)
~PresolveTimer()
Update dtime and log operation summary.
void AddCounter(std::string name, int64_t count)
Taking a view allow to cache the never changing addresses.
View ClearedView(int size)
This reserve the size for using indices in [0, size).
time_limit
Definition solve.cc:22
bool FindSingleLinearDifference(const LinearConstraintProto &lin1, const LinearConstraintProto &lin2, int *var1, int64_t *coeff1, int *var2, int64_t *coeff2)
Same as LinearsDifferAtOneTerm() below but also fills the differing terms.
bool ClauseIsEnforcementImpliesLiteral(absl::Span< const int > clause, absl::Span< const int > enforcement, int literal)
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
bool AddLinearConstraintMultiple(int64_t factor, const ConstraintProto &to_add, ConstraintProto *to_modify)
bool LinearsDifferAtOneTerm(const LinearConstraintProto &lin1, const LinearConstraintProto &lin2)
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
In SWIG mode, we don't want anything besides these top-level includes.
STL namespace.
#define DEFINE_STRONG_INDEX_TYPE(index_type_name)