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