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