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