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