OR-Tools  9.6
sat_inprocessing.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 // This file contains the entry point for our presolve/inprocessing code.
15 //
16 // TODO(user): for now it is mainly presolve, but the idea is to call these
17 // function during the search so they should be as incremental as possible. That
18 // is avoid doing work that is not useful because nothing changed or exploring
19 // parts that were not done during the last round.
20 
21 #ifndef OR_TOOLS_SAT_SAT_INPROCESSING_H_
22 #define OR_TOOLS_SAT_SAT_INPROCESSING_H_
23 
24 #include <cstdint>
25 #include <deque>
26 #include <vector>
27 
28 #include "absl/strings/string_view.h"
29 #include "absl/types/span.h"
31 #include "ortools/sat/clause.h"
33 #include "ortools/sat/model.h"
34 #include "ortools/sat/sat_base.h"
36 #include "ortools/sat/sat_parameters.pb.h"
37 #include "ortools/sat/sat_solver.h"
38 #include "ortools/sat/util.h"
42 
43 namespace operations_research {
44 namespace sat {
45 
46 // The order is important and each clauses has a "special" literal that is
47 // put first.
48 //
49 // TODO(user): Use a flat memory structure instead.
51  // Utility function that push back clause but also make sure the given literal
52  // from clause appear first.
54  absl::Span<const Literal> clause);
55 
56  std::deque<std::vector<Literal>> clauses;
57 };
58 
61 class StampingSimplifier;
62 
64  // The time budget to spend.
65  double deterministic_time_limit = 30.0;
66 
67  // We assume this is also true if --v 1 is activated and we actually log
68  // even more in --v 1.
69  bool log_info = false;
70 
71  // See ProbingOptions in probing.h
73 
74  // Whether we perform a transitive reduction of the binary implication graph
75  // after equivalent literal detection and before each probing pass.
76  //
77  // TODO(user): Doing that before the current SAT presolve also change the
78  // possible reduction. This shouldn't matter if we use the binary implication
79  // graph and its reachability instead of just binary clause though.
81 };
82 
83 // We need to keep some information from one call to the next, so we use a
84 // class. Note that as this becomes big, we can probably split it.
85 //
86 // TODO(user): Some algorithms here use the normal SAT propagation engine.
87 // However we might want to temporarily disable activities/phase saving and so
88 // on if we want to run them as in-processing steps so that they
89 // do not "pollute" the normal SAT search.
90 //
91 // TODO(user): For the propagation, this depends on the SatSolver class, which
92 // mean we cannot really use it without some refactoring as an in-processing
93 // from the SatSolver::Solve() function. So we do need a special
94 // InprocessingSolve() that lives outside SatSolver. Alternatively, we can
95 // extract the propagation main loop and conflict analysis from SatSolver.
96 class Inprocessing {
97  public:
99  : assignment_(model->GetOrCreate<Trail>()->Assignment()),
100  implication_graph_(model->GetOrCreate<BinaryImplicationGraph>()),
101  clause_manager_(model->GetOrCreate<LiteralWatchers>()),
102  trail_(model->GetOrCreate<Trail>()),
103  decision_policy_(model->GetOrCreate<SatDecisionPolicy>()),
104  time_limit_(model->GetOrCreate<TimeLimit>()),
105  sat_solver_(model->GetOrCreate<SatSolver>()),
106  stamping_simplifier_(model->GetOrCreate<StampingSimplifier>()),
107  blocked_clause_simplifier_(
108  model->GetOrCreate<BlockedClauseSimplifier>()),
109  bounded_variable_elimination_(
110  model->GetOrCreate<BoundedVariableElimination>()),
111  model_(model) {}
112 
113  // Does some simplifications until a fix point is reached or the given
114  // deterministic time is passed.
115  bool PresolveLoop(SatPresolveOptions options);
116 
117  // When the option use_sat_inprocessing is true, then this is called at each
118  // restart. It is up to the heuristics here to decide what inprocessing we
119  // do or if we wait for the next call before doing anything.
120  bool InprocessingRound();
121 
122  // Simple wrapper that makes sure all the clauses are attached before a
123  // propagation is performed.
124  bool LevelZeroPropagate();
125 
126  // Detects equivalences in the implication graph and propagates any failed
127  // literal found during the process.
128  bool DetectEquivalencesAndStamp(bool use_transitive_reduction, bool log_info);
129 
130  // Removes fixed variables and exploit equivalence relations to cleanup the
131  // clauses. Returns false if UNSAT.
132  bool RemoveFixedAndEquivalentVariables(bool log_info);
133 
134  // Returns true if there is new fixed variables or new equivalence relations
135  // since RemoveFixedAndEquivalentVariables() was last called.
136  bool MoreFixedVariableToClean() const;
137  bool MoreRedundantVariableToClean() const;
138 
139  // Processes all clauses and see if there is any subsumption/strenghtening
140  // reductions that can be performed. Returns false if UNSAT.
141  bool SubsumeAndStrenghtenRound(bool log_info);
142 
143  private:
144  const VariablesAssignment& assignment_;
145  BinaryImplicationGraph* implication_graph_;
146  LiteralWatchers* clause_manager_;
147  Trail* trail_;
148  SatDecisionPolicy* decision_policy_;
149  TimeLimit* time_limit_;
150  SatSolver* sat_solver_;
151  StampingSimplifier* stamping_simplifier_;
152  BlockedClauseSimplifier* blocked_clause_simplifier_;
153  BoundedVariableElimination* bounded_variable_elimination_;
154 
155  double total_dtime_ = 0.0;
156 
157  // TODO(user): This is only used for calling probing. We should probably
158  // create a Probing class to wraps its data. This will also be needed to not
159  // always probe the same variables in each round if the deterministic time
160  // limit is low.
161  Model* model_;
162 
163  // Last since clause database was cleaned up.
164  int64_t last_num_redundant_literals_ = 0;
165  int64_t last_num_fixed_variables_ = 0;
166 };
167 
168 // Implements "stamping" as described in "Efficient CNF Simplification based on
169 // Binary Implication Graphs", Marijn Heule, Matti Jarvisalo and Armin Biere.
170 //
171 // This sample the implications graph with a spanning tree, and then simplify
172 // all clauses (subsumption / strengthening) using the implications encoded in
173 // this tree. So this allows to consider chain of implications instead of just
174 // direct ones, but depending on the problem, only a small fraction of the
175 // implication graph will be captured by the tree.
176 //
177 // Note that we randomize the spanning tree at each call. This can benefit by
178 // having the implication graph be transitively reduced before.
180  public:
182  : assignment_(model->GetOrCreate<Trail>()->Assignment()),
183  implication_graph_(model->GetOrCreate<BinaryImplicationGraph>()),
184  clause_manager_(model->GetOrCreate<LiteralWatchers>()),
185  random_(model->GetOrCreate<ModelRandomGenerator>()),
186  time_limit_(model->GetOrCreate<TimeLimit>()) {}
187 
188  // This is "fast" (linear scan + sort of all clauses) so we always complete
189  // the full round.
190  //
191  // TODO(user): To save one scan over all the clauses, we could do the fixed
192  // and equivalence variable cleaning here too.
193  bool DoOneRound(bool log_info);
194 
195  // When we compute stamps, we might detect fixed variable (via failed literal
196  // probing in the implication graph). So it might make sense to do that until
197  // we have dealt with all fixed literals before calling DoOneRound().
198  bool ComputeStampsForNextRound(bool log_info);
199 
200  // Visible for testing.
202 
203  // Using a DFS visiting order, we can answer reachability query in O(1) on a
204  // tree, this is well known. ComputeStamps() also detect failed literal in
205  // the tree and fix them. It can return false on UNSAT.
206  bool ComputeStamps();
208  return first_stamps_[a.Index()] < first_stamps_[b.Index()] &&
209  last_stamps_[b.Index()] < last_stamps_[a.Index()];
210  }
211 
212  bool ProcessClauses();
213 
214  private:
215  const VariablesAssignment& assignment_;
216  BinaryImplicationGraph* implication_graph_;
217  LiteralWatchers* clause_manager_;
218  ModelRandomGenerator* random_;
219  TimeLimit* time_limit_;
220 
221  // For ComputeStampsForNextRound().
222  bool stamps_are_already_computed_ = false;
223 
224  // Reset at each round.
225  double dtime_ = 0.0;
226  int64_t num_subsumed_clauses_ = 0;
227  int64_t num_removed_literals_ = 0;
228  int64_t num_fixed_ = 0;
229 
230  // Encode a spanning tree of the implication graph.
232 
233  // Adjacency list representation of the parents_ tree.
236  std::vector<LiteralIndex> children_;
237 
238  // Temporary data for the DFS.
240  std::vector<LiteralIndex> dfs_stack_;
241 
242  // First/Last visited index in a DFS of the tree above.
245 };
246 
247 // A clause c is "blocked" by a literal l if all clauses containing the
248 // negation of l resolve to trivial clause with c. Blocked clause can be
249 // simply removed from the problem. At postsolve, if a blocked clause is not
250 // satisfied, then l can simply be set to true without breaking any of the
251 // clause containing not(l).
252 //
253 // See the paper "Blocked Clause Elimination", Matti Jarvisalo, Armin Biere,
254 // and Marijn Heule.
255 //
256 // TODO(user): This requires that l only appear in clauses and not in the
257 // integer part of CP-SAT.
259  public:
261  : assignment_(model->GetOrCreate<Trail>()->Assignment()),
262  implication_graph_(model->GetOrCreate<BinaryImplicationGraph>()),
263  clause_manager_(model->GetOrCreate<LiteralWatchers>()),
264  postsolve_(model->GetOrCreate<PostsolveClauses>()),
265  time_limit_(model->GetOrCreate<TimeLimit>()) {}
266 
267  void DoOneRound(bool log_info);
268 
269  private:
270  void InitializeForNewRound();
271  void ProcessLiteral(Literal current_literal);
272  bool ClauseIsBlocked(Literal current_literal,
273  absl::Span<const Literal> clause);
274 
275  const VariablesAssignment& assignment_;
276  BinaryImplicationGraph* implication_graph_;
277  LiteralWatchers* clause_manager_;
278  PostsolveClauses* postsolve_;
279  TimeLimit* time_limit_;
280 
281  double dtime_ = 0.0;
282  int32_t num_blocked_clauses_ = 0;
283  int64_t num_inspected_literals_ = 0;
284 
285  // Temporary vector to mark literal of a clause.
287 
288  // List of literal to process.
289  // TODO(user): use priority queue?
291  std::deque<Literal> queue_;
292 
293  // We compute the occurrence graph just once at the beginning of each round
294  // and we do not shrink it as we remove blocked clauses.
295  DEFINE_STRONG_INDEX_TYPE(rat_literal_clause_index);
298  literal_to_clauses_;
299 };
300 
302  public:
304  : parameters_(*model->GetOrCreate<SatParameters>()),
305  assignment_(model->GetOrCreate<Trail>()->Assignment()),
306  implication_graph_(model->GetOrCreate<BinaryImplicationGraph>()),
307  clause_manager_(model->GetOrCreate<LiteralWatchers>()),
308  postsolve_(model->GetOrCreate<PostsolveClauses>()),
309  trail_(model->GetOrCreate<Trail>()),
310  time_limit_(model->GetOrCreate<TimeLimit>()) {}
311 
312  bool DoOneRound(bool log_info);
313 
314  private:
315  int NumClausesContaining(Literal l);
316  void UpdatePriorityQueue(BooleanVariable var);
317  bool CrossProduct(BooleanVariable var);
318  void DeleteClause(SatClause* sat_clause);
319  void DeleteAllClausesContaining(Literal literal);
320  void AddClause(absl::Span<const Literal> clause);
321  bool RemoveLiteralFromClause(Literal lit, SatClause* sat_clause);
322  bool Propagate();
323 
324  // The actual clause elimination algo. We have two versions, one just compute
325  // the "score" of what will be the final state. The other perform the
326  // resolution, remove old clauses and add the new ones.
327  //
328  // Returns false on UNSAT.
329  template <bool score_only, bool with_binary_only>
330  bool ResolveAllClauseContaining(Literal lit);
331 
332  const SatParameters& parameters_;
333  const VariablesAssignment& assignment_;
334  BinaryImplicationGraph* implication_graph_;
335  LiteralWatchers* clause_manager_;
336  PostsolveClauses* postsolve_;
337  Trail* trail_;
338  TimeLimit* time_limit_;
339 
340  int propagation_index_;
341 
342  double dtime_ = 0.0;
343  int64_t num_inspected_literals_ = 0;
344  int64_t num_simplifications_ = 0;
345  int64_t num_blocked_clauses_ = 0;
346  int64_t num_eliminated_variables_ = 0;
347  int64_t num_literals_diff_ = 0;
348  int64_t num_clauses_diff_ = 0;
349 
350  int64_t new_score_;
351  int64_t score_threshold_;
352 
353  // Temporary vector to mark literal of a clause and compute its resolvant.
355  std::vector<Literal> resolvant_;
356 
357  // Priority queue of variable to process.
358  // We will process highest priority first.
359  struct VariableWithPriority {
360  BooleanVariable var;
361  int32_t priority;
362 
363  // Interface for the IntegerPriorityQueue.
364  int Index() const { return var.value(); }
365  bool operator<(const VariableWithPriority& o) const {
366  return priority < o.priority;
367  }
368  };
369  IntegerPriorityQueue<VariableWithPriority> queue_;
370 
371  // We update the queue_ in batch.
372  absl::StrongVector<BooleanVariable, bool> in_need_to_be_updated_;
373  std::vector<BooleanVariable> need_to_be_updated_;
374 
375  // We compute the occurrence graph just once at the beginning of each round.
376  // We maintains the sizes at all time and lazily shrink the graph with deleted
377  // clauses.
378  DEFINE_STRONG_INDEX_TYPE(ClauseIndex);
381  literal_to_clauses_;
382  absl::StrongVector<LiteralIndex, int> literal_to_num_clauses_;
383 };
384 
385 } // namespace sat
386 } // namespace operations_research
387 
388 #endif // OR_TOOLS_SAT_SAT_INPROCESSING_H_
An Assignment is a variable -> domains mapping, used to report solutions to the user.
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
bool PresolveLoop(SatPresolveOptions options)
bool RemoveFixedAndEquivalentVariables(bool log_info)
bool DetectEquivalencesAndStamp(bool use_transitive_reduction, bool log_info)
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
bool ImplicationIsInTree(Literal a, Literal b) const
int64_t b
int64_t a
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:88
std::deque< std::vector< Literal > > clauses
void AddClauseWithSpecialLiteral(Literal literal, absl::Span< const Literal > clause)