OR-Tools  9.6
presolve_util.h
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <utility>
21 #include <vector>
22 
23 #include "absl/container/flat_hash_map.h"
24 #include "absl/random/bit_gen_ref.h"
25 #include "absl/random/random.h"
26 #include "absl/strings/string_view.h"
27 #include "absl/types/span.h"
29 #include "ortools/base/logging.h"
31 #include "ortools/sat/cp_model.pb.h"
33 #include "ortools/util/bitset.h"
36 
37 namespace operations_research {
38 namespace sat {
39 
40 // If for each literal of a clause, we can infer a domain on an integer
41 // variable, then we know that this variable domain is included in the union of
42 // such infered domains.
43 //
44 // This allows to propagate "element" like constraints encoded as enforced
45 // linear relations, and other more general reasoning.
46 //
47 // TODO(user): Also use these "deductions" in the solver directly. This is done
48 // in good MIP solvers, and we should exploit them more.
49 //
50 // TODO(user): Also propagate implicit clauses (lit, not(lit)). Maybe merge
51 // that with probing code? it might be costly to store all deduction done by
52 // probing though, but I think this is what MIP solver do.
54  public:
55  // Adds the fact that enforcement => var \in domain.
56  //
57  // Important: No need to store any deductions where the domain is a superset
58  // of the current variable domain.
59  void AddDeduction(int literal_ref, int var, Domain domain);
60 
61  // Returns the domain of var when literal_ref is true.
62  // If there is no information, returns Domain::AllValues().
63  Domain ImpliedDomain(int literal_ref, int var) const;
64 
65  // Returns list of (var, domain) that were deduced because:
66  // 1/ We have a domain deduction for var and all literal from the clause
67  // 2/ So we can take the union of all the deduced domains.
68  //
69  // TODO(user): We could probably be even more efficient. We could also
70  // compute exactly what clauses need to be "waked up" as new deductions are
71  // added.
72  std::vector<std::pair<int, Domain>> ProcessClause(
73  absl::Span<const int> clause);
74 
75  // Optimization. Any following ProcessClause() will be fast if no more
76  // deduction touching that clause are added.
78  something_changed_.ClearAndResize(something_changed_.size());
79  }
80 
81  // Returns the total number of "deductions" stored by this class.
82  int NumDeductions() const { return deductions_.size(); }
83 
84  private:
85  DEFINE_STRONG_INDEX_TYPE(Index);
86  Index IndexFromLiteral(int ref) const {
87  return Index(ref >= 0 ? 2 * ref : -2 * ref - 1);
88  }
89 
90  std::vector<int> tmp_num_occurrences_;
91 
92  SparseBitset<Index> something_changed_;
93  absl::StrongVector<Index, std::vector<int>> enforcement_to_vars_;
94  absl::flat_hash_map<std::pair<Index, int>, Domain> deductions_;
95 };
96 
97 // Replaces the variable var in ct using the definition constraint.
98 // Currently the coefficient in the definition must be 1 or -1.
99 bool SubstituteVariable(int var, int64_t var_coeff_in_definition,
100  const ConstraintProto& definition, ConstraintProto* ct);
101 
102 // Try to get more precise min/max activity of a linear constraints using
103 // at most ones from the model. This is heuristic based but should be relatively
104 // fast.
105 //
106 // TODO(user): Use better algorithm. The problem is the same as finding upper
107 // bound to the classic problem: maximum-independent set in a graph. We also
108 // only use at most ones, but we could use the more general binary implication
109 // graph.
111  public:
113 
114  // The at most one constraint must be added before linear constraint are
115  // processed. The functions below will still works, but do nothing more than
116  // compute trivial bounds.
117  void ClearAtMostOnes();
118  void AddAtMostOne(absl::Span<const int> amo);
119  void AddAllAtMostOnes(const CpModelProto& proto);
120 
121  // Computes the max/min activity of a linear expression involving only
122  // Booleans.
123  //
124  // Accepts a list of (literal, coefficient). Note that all literal will be
125  // interpreted as referring to [0, 1] variables. We use the CpModelProto
126  // convention for negated literal index.
127  //
128  // If conditional is not nullptr, then conditional[i][0/1] will give the
129  // max activity if the literal at position i is false/true. This can be used
130  // to fix variables or extract enforcement literal.
131  //
132  // Important: We shouldn't have duplicates or a lit and NegatedRef(lit)
133  // appearing both.
134  //
135  // TODO(user): Indicate when the bounds are trivial (i.e. not intersection
136  // with any amo) so that we don't waste more time processing the result?
138  absl::Span<const std::pair<int, int64_t>> terms,
139  std::vector<std::array<int64_t, 2>>* conditional = nullptr) {
140  return ComputeActivity(/*compute_min=*/false, terms, conditional);
141  }
143  absl::Span<const std::pair<int, int64_t>> terms,
144  std::vector<std::array<int64_t, 2>>* conditional = nullptr) {
145  return ComputeActivity(/*compute_min=*/true, terms, conditional);
146  }
147 
148  // Computes relevant info to presolve a constraint with enforcement using
149  // at most one information.
150  //
151  // This returns false iff the enforcement list cannot be satisfied.
152  // It filters the enforcement list if some are consequences of other.
153  // It fills the given set with the literal that must be true due to the
154  // enforcement. Note that only literals or negated literal appearing in ref
155  // are filled.
156  bool PresolveEnforcement(absl::Span<const int> refs, ConstraintProto* ct,
157  absl::flat_hash_set<int>* literals_at_true);
158 
159  // partition the list of literals into disjoint at most ones. The returned
160  // spans are only valid until another function from this class is used.
161  std::vector<absl::Span<const int>> PartitionLiteralsIntoAmo(
162  absl::Span<const int> literals);
163 
164  // Returns true iff the given literal are in at most one relationship.
165  bool IsAmo(absl::Span<const int> literals);
166 
167  private:
168  DEFINE_STRONG_INDEX_TYPE(Index);
169  Index IndexFromLiteral(int ref) const {
170  return Index(ref >= 0 ? 2 * ref : -2 * ref - 1);
171  }
172 
173  int64_t ComputeActivity(
174  bool compute_min, absl::Span<const std::pair<int, int64_t>> terms,
175  std::vector<std::array<int64_t, 2>>* conditional = nullptr);
176 
177  void PartitionIntoAmo(absl::Span<const std::pair<int, int64_t>> terms);
178 
179  // All coeff must be >= 0 here. Note that in practice, we shouldn't have
180  // zero coeff, but we still support it.
181  int64_t ComputeMaxActivityInternal(
182  absl::Span<const std::pair<int, int64_t>> terms,
183  std::vector<std::array<int64_t, 2>>* conditional = nullptr);
184 
185  // We use an unique index by at most one, and just stores for each literal
186  // the at most one to which it belong.
187  int num_at_most_ones_ = 0;
189 
190  std::vector<std::pair<int, int64_t>> tmp_terms_;
191  std::vector<std::pair<int64_t, int>> to_sort_;
192 
193  // We partition the set of term into disjoint at most one.
194  absl::flat_hash_map<int, int> used_amo_to_dense_index_;
195  absl::flat_hash_map<int, int64_t> amo_sums_;
196  std::vector<int> partition_;
197  std::vector<int64_t> max_by_partition_;
198  std::vector<int64_t> second_max_by_partition_;
199 
200  // Used by PartitionLiteralsIntoAmo().
201  std::vector<int> part_starts_;
202  std::vector<int> part_ends_;
203  std::vector<int> part_sizes_;
204  std::vector<int> reordered_literals_;
205 
206  absl::flat_hash_set<int> triggered_amo_;
207 };
208 
209 // Class to help detects clauses that differ on a single literal.
211  public:
212  explicit ClauseWithOneMissingHasher(absl::BitGenRef random)
213  : random_(random) {}
214 
215  // We use the proto encoding of literals here.
216  void RegisterClause(int c, absl::Span<const int> clause);
217 
218  // Returns a hash of the clause with index c and literal ref removed.
219  // This assumes that ref was part of the clause. Work in O(1).
220  uint64_t HashWithout(int c, int ref) const {
221  return clause_to_hash_[c] ^ literal_to_hash_[IndexFromLiteral(ref)];
222  }
223 
224  // Returns a hash of the negation of all the given literals.
225  uint64_t HashOfNegatedLiterals(absl::Span<const int> literals);
226 
227  private:
228  DEFINE_STRONG_INDEX_TYPE(Index);
229  Index IndexFromLiteral(int ref) const {
230  return Index(ref >= 0 ? 2 * ref : -2 * ref - 1);
231  }
232 
233  absl::BitGenRef random_;
234  absl::StrongVector<Index, uint64_t> literal_to_hash_;
235  std::vector<uint64_t> clause_to_hash_;
236 };
237 
238 // Specific function. Returns true if the negation of all literals in clause
239 // except literal is exactly equal to the literal of enforcement.
240 //
241 // We assumes that enforcement and negated(clause) are sorted lexicographically
242 // Or negated(enforcement) and clause. Both option works. If not, we will only
243 // return false more often. When we return true, the property is enforced.
244 //
245 // TODO(user): For the same complexity, we do not need to specify literal and
246 // can recover it.
247 inline bool ClauseIsEnforcementImpliesLiteral(absl::Span<const int> clause,
248  absl::Span<const int> enforcement,
249  int literal) {
250  if (clause.size() != enforcement.size() + 1) return false;
251  int j = 0;
252  for (int i = 0; i < clause.size(); ++i) {
253  if (clause[i] == literal) continue;
254  if (clause[i] != NegatedRef(enforcement[j])) return false;
255  ++j;
256  }
257  return true;
258 }
259 
260 } // namespace sat
261 } // namespace operations_research
262 
263 #endif // OR_TOOLS_SAT_PRESOLVE_UTIL_H_
We call domain any subset of Int64 = [kint64min, kint64max].
IntegerType size() const
Definition: bitset.h:758
void ClearAndResize(IntegerType size)
Definition: bitset.h:767
std::vector< absl::Span< const int > > PartitionLiteralsIntoAmo(absl::Span< const int > literals)
bool IsAmo(absl::Span< const int > literals)
bool PresolveEnforcement(absl::Span< const int > refs, ConstraintProto *ct, absl::flat_hash_set< int > *literals_at_true)
void AddAtMostOne(absl::Span< const int > amo)
int64_t ComputeMinActivity(absl::Span< const std::pair< int, int64_t >> terms, std::vector< std::array< int64_t, 2 >> *conditional=nullptr)
int64_t ComputeMaxActivity(absl::Span< const std::pair< int, int64_t >> terms, std::vector< std::array< int64_t, 2 >> *conditional=nullptr)
void AddAllAtMostOnes(const CpModelProto &proto)
uint64_t HashOfNegatedLiterals(absl::Span< const int > literals)
void RegisterClause(int c, absl::Span< const int > clause)
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
Domain ImpliedDomain(int literal_ref, int var) const
void AddDeduction(int literal_ref, int var, Domain domain)
CpModelProto proto
const Constraint * ct
IntVar * var
Definition: expr_array.cc:1874
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)
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:88