forked from google/or-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
simplification.h
449 lines (383 loc) · 18.8 KB
/
simplification.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
// Copyright 2010-2021 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Implementation of a pure SAT presolver. This roughly follows the paper:
//
// "Effective Preprocessing in SAT through Variable and Clause Elimination",
// Niklas Een and Armin Biere, published in the SAT 2005 proceedings.
#ifndef OR_TOOLS_SAT_SIMPLIFICATION_H_
#define OR_TOOLS_SAT_SIMPLIFICATION_H_
#include <cstdint>
#include <deque>
#include <memory>
#include <set>
#include <vector>
#include "absl/types/span.h"
#include "ortools/base/adjustable_priority_queue.h"
#include "ortools/base/int_type.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/macros.h"
#include "ortools/base/strong_vector.h"
#include "ortools/sat/drat_proof_handler.h"
#include "ortools/sat/sat_base.h"
#include "ortools/sat/sat_parameters.pb.h"
#include "ortools/sat/sat_solver.h"
#include "ortools/util/logging.h"
#include "ortools/util/time_limit.h"
namespace operations_research {
namespace sat {
// A simple sat postsolver.
//
// The idea is that any presolve algorithm can just update this class, and at
// the end, this class will recover a solution of the initial problem from a
// solution of the presolved problem.
class SatPostsolver {
public:
explicit SatPostsolver(int num_variables);
// The postsolver will process the Add() calls in reverse order. If the given
// clause has all its literals at false, it simply sets the literal x to true.
// Note that x must be a literal of the given clause.
void Add(Literal x, const absl::Span<const Literal> clause);
// Tells the postsolver that the given literal must be true in any solution.
// We currently check that the variable is not already fixed.
//
// TODO(user): this as almost the same effect as adding an unit clause, and we
// should probably remove this to simplify the code.
void FixVariable(Literal x);
// This assumes that the given variable mapping has been applied to the
// problem. All the subsequent Add() and FixVariable() will refer to the new
// problem. During postsolve, the initial solution must also correspond to
// this new problem. Note that if mapping[v] == -1, then the literal v is
// assumed to be deleted.
//
// This can be called more than once. But each call must refer to the current
// variables set (after all the previous mapping have been applied).
void ApplyMapping(
const absl::StrongVector<BooleanVariable, BooleanVariable>& mapping);
// Extracts the current assignment of the given solver and postsolve it.
//
// Node(fdid): This can currently be called only once (but this is easy to
// change since only some CHECK will fail).
std::vector<bool> ExtractAndPostsolveSolution(const SatSolver& solver);
std::vector<bool> PostsolveSolution(const std::vector<bool>& solution);
// Getters to the clauses managed by this class.
// Important: This will always put the associated literal first.
int NumClauses() const { return clauses_start_.size(); }
std::vector<Literal> Clause(int i) const {
// TODO(user): we could avoid the copy here, but because clauses_literals_
// is a deque, we do need a special return class and cannot juste use
// absl::Span<Literal> for instance.
const int begin = clauses_start_[i];
const int end = i + 1 < clauses_start_.size() ? clauses_start_[i + 1]
: clauses_literals_.size();
std::vector<Literal> result(clauses_literals_.begin() + begin,
clauses_literals_.begin() + end);
for (int j = 0; j < result.size(); ++j) {
if (result[j] == associated_literal_[i]) {
std::swap(result[0], result[j]);
break;
}
}
return result;
}
private:
Literal ApplyReverseMapping(Literal l);
void Postsolve(VariablesAssignment* assignment) const;
// The presolve can add new variables, so we need to store the number of
// original variables in order to return a solution with the correct number
// of variables.
const int initial_num_variables_;
int num_variables_;
// Stores the arguments of the Add() calls: clauses_start_[i] is the index of
// the first literal of the clause #i in the clauses_literals_ deque.
std::vector<int> clauses_start_;
std::deque<Literal> clauses_literals_;
std::vector<Literal> associated_literal_;
// All the added clauses will be mapped back to the initial variables using
// this reverse mapping. This way, clauses_ and associated_literal_ are only
// in term of the initial problem.
absl::StrongVector<BooleanVariable, BooleanVariable> reverse_mapping_;
// This will stores the fixed variables value and later the postsolved
// assignment.
VariablesAssignment assignment_;
DISALLOW_COPY_AND_ASSIGN(SatPostsolver);
};
// This class holds a SAT problem (i.e. a set of clauses) and the logic to
// presolve it by a series of subsumption, self-subsuming resolution, and
// variable elimination by clause distribution.
//
// Note that this does propagate unit-clauses, but probably much
// less efficiently than the propagation code in the SAT solver. So it is better
// to use a SAT solver to fix variables before using this class.
//
// TODO(user): Interact more with a SAT solver to reuse its propagation logic.
//
// TODO(user): Forbid the removal of some variables. This way we can presolve
// only the clause part of a general Boolean problem by not removing variables
// appearing in pseudo-Boolean constraints.
class SatPresolver {
public:
// TODO(user): use IntType!
typedef int32_t ClauseIndex;
explicit SatPresolver(SatPostsolver* postsolver, SolverLogger* logger)
: postsolver_(postsolver),
num_trivial_clauses_(0),
drat_proof_handler_(nullptr),
logger_(logger) {}
void SetParameters(const SatParameters& params) { parameters_ = params; }
void SetTimeLimit(TimeLimit* time_limit) { time_limit_ = time_limit; }
// Registers a mapping to encode equivalent literals.
// See ProbeAndFindEquivalentLiteral().
void SetEquivalentLiteralMapping(
const absl::StrongVector<LiteralIndex, LiteralIndex>& mapping) {
equiv_mapping_ = mapping;
}
// Adds new clause to the SatPresolver.
void SetNumVariables(int num_variables);
void AddBinaryClause(Literal a, Literal b);
void AddClause(absl::Span<const Literal> clause);
// Presolves the problem currently loaded. Returns false if the model is
// proven to be UNSAT during the presolving.
//
// TODO(user): Add support for a time limit and some kind of iterations limit
// so that this can never take too much time.
bool Presolve();
// Same as Presolve() but only allow to remove BooleanVariable whose index
// is set to true in the given vector.
bool Presolve(const std::vector<bool>& var_that_can_be_removed);
// All the clauses managed by this class.
// Note that deleted clauses keep their indices (they are just empty).
int NumClauses() const { return clauses_.size(); }
const std::vector<Literal>& Clause(ClauseIndex ci) const {
return clauses_[ci];
}
// The number of variables. This is computed automatically from the clauses
// added to the SatPresolver.
int NumVariables() const { return literal_to_clause_sizes_.size() / 2; }
// After presolving, Some variables in [0, NumVariables()) have no longer any
// clause pointing to them. This return a mapping that maps this interval to
// [0, new_size) such that now all variables are used. The unused variable
// will be mapped to BooleanVariable(-1).
absl::StrongVector<BooleanVariable, BooleanVariable> VariableMapping() const;
// Loads the current presolved problem in to the given sat solver.
// Note that the variables will be re-indexed according to the mapping given
// by GetMapping() so that they form a dense set.
//
// IMPORTANT: This is not const because it deletes the presolver clauses as
// they are added to the SatSolver in order to save memory. After this is
// called, only VariableMapping() will still works.
void LoadProblemIntoSatSolver(SatSolver* solver);
// Visible for Testing. Takes a given clause index and looks for clause that
// can be subsumed or strengthened using this clause. Returns false if the
// model is proven to be unsat.
bool ProcessClauseToSimplifyOthers(ClauseIndex clause_index);
// Visible for testing. Tries to eliminate x by clause distribution.
// This is also known as bounded variable elimination.
//
// It is always possible to remove x by resolving each clause containing x
// with all the clauses containing not(x). Hence the cross-product name. Note
// that this function only do that if the number of clauses is reduced.
bool CrossProduct(Literal x);
// Visible for testing. Just applies the BVA step of the presolve.
void PresolveWithBva();
void SetDratProofHandler(DratProofHandler* drat_proof_handler) {
drat_proof_handler_ = drat_proof_handler;
}
private:
// Internal function used by ProcessClauseToSimplifyOthers().
bool ProcessClauseToSimplifyOthersUsingLiteral(ClauseIndex clause_index,
Literal lit);
// Internal function to add clauses generated during the presolve. The clause
// must already be sorted with the default Literal order and will be cleared
// after this call.
void AddClauseInternal(std::vector<Literal>* clause);
// Clause removal function.
void Remove(ClauseIndex ci);
void RemoveAndRegisterForPostsolve(ClauseIndex ci, Literal x);
void RemoveAndRegisterForPostsolveAllClauseContaining(Literal x);
// Call ProcessClauseToSimplifyOthers() on all the clauses in
// clause_to_process_ and empty the list afterwards. Note that while some
// clauses are processed, new ones may be added to the list. Returns false if
// the problem is shown to be UNSAT.
bool ProcessAllClauses();
// Finds the literal from the clause that occur the less in the clause
// database.
Literal FindLiteralWithShortestOccurrenceList(
const std::vector<Literal>& clause);
LiteralIndex FindLiteralWithShortestOccurrenceListExcluding(
const std::vector<Literal>& clause, Literal to_exclude);
// Tests and maybe perform a Simple Bounded Variable addition starting from
// the given literal as described in the paper: "Automated Reencoding of
// Boolean Formulas", Norbert Manthey, Marijn J. H. Heule, and Armin Biere,
// Volume 7857 of the series Lecture Notes in Computer Science pp 102-117,
// 2013.
// https://www.research.ibm.com/haifa/conferences/hvc2012/papers/paper16.pdf
//
// This seems to have a mostly positive effect, except on the crafted problem
// familly mugrauer_balint--GI.crafted_nxx_d6_cx_numxx where the reduction
// is big, but apparently the problem is harder to prove UNSAT for the solver.
void SimpleBva(LiteralIndex l);
// Display some statistics on the current clause database.
void DisplayStats(double elapsed_seconds);
// Returns a hash of the given clause variables (not literal) in such a way
// that hash1 & not(hash2) == 0 iff the set of variable of clause 1 is a
// subset of the one of clause2.
uint64_t ComputeSignatureOfClauseVariables(ClauseIndex ci);
// The "active" variables on which we want to call CrossProduct() are kept
// in a priority queue so that we process first the ones that occur the least
// often in the clause database.
void InitializePriorityQueue();
void UpdatePriorityQueue(BooleanVariable var);
struct PQElement {
PQElement() : heap_index(-1), variable(-1), weight(0.0) {}
// Interface for the AdjustablePriorityQueue.
void SetHeapIndex(int h) { heap_index = h; }
int GetHeapIndex() const { return heap_index; }
// Priority order. The AdjustablePriorityQueue returns the largest element
// first, but our weight goes this other way around (smaller is better).
bool operator<(const PQElement& other) const {
return weight > other.weight;
}
int heap_index;
BooleanVariable variable;
double weight;
};
absl::StrongVector<BooleanVariable, PQElement> var_pq_elements_;
AdjustablePriorityQueue<PQElement> var_pq_;
// Literal priority queue for BVA. The literals are ordered by descending
// number of occurrences in clauses.
void InitializeBvaPriorityQueue();
void UpdateBvaPriorityQueue(LiteralIndex lit);
void AddToBvaPriorityQueue(LiteralIndex lit);
struct BvaPqElement {
BvaPqElement() : heap_index(-1), literal(-1), weight(0.0) {}
// Interface for the AdjustablePriorityQueue.
void SetHeapIndex(int h) { heap_index = h; }
int GetHeapIndex() const { return heap_index; }
// Priority order.
// The AdjustablePriorityQueue returns the largest element first.
bool operator<(const BvaPqElement& other) const {
return weight < other.weight;
}
int heap_index;
LiteralIndex literal;
double weight;
};
std::deque<BvaPqElement> bva_pq_elements_; // deque because we add variables.
AdjustablePriorityQueue<BvaPqElement> bva_pq_;
// Temporary data for SimpleBva().
std::set<LiteralIndex> m_lit_;
std::vector<ClauseIndex> m_cls_;
absl::StrongVector<LiteralIndex, int> literal_to_p_size_;
std::vector<std::pair<LiteralIndex, ClauseIndex>> flattened_p_;
std::vector<Literal> tmp_new_clause_;
// List of clauses on which we need to call ProcessClauseToSimplifyOthers().
// See ProcessAllClauses().
std::vector<bool> in_clause_to_process_;
std::deque<ClauseIndex> clause_to_process_;
// The set of all clauses.
// An empty clause means that it has been removed.
std::vector<std::vector<Literal>> clauses_; // Indexed by ClauseIndex
// The cached value of ComputeSignatureOfClauseVariables() for each clause.
std::vector<uint64_t> signatures_; // Indexed by ClauseIndex
int64_t num_inspected_signatures_ = 0;
int64_t num_inspected_literals_ = 0;
// Occurrence list. For each literal, contains the ClauseIndex of the clause
// that contains it (ordered by clause index).
absl::StrongVector<LiteralIndex, std::vector<ClauseIndex>>
literal_to_clauses_;
// Because we only lazily clean the occurrence list after clause deletions,
// we keep the size of the occurrence list (without the deleted clause) here.
absl::StrongVector<LiteralIndex, int> literal_to_clause_sizes_;
// Used for postsolve.
SatPostsolver* postsolver_;
// Equivalent literal mapping.
absl::StrongVector<LiteralIndex, LiteralIndex> equiv_mapping_;
int num_trivial_clauses_;
SatParameters parameters_;
DratProofHandler* drat_proof_handler_;
TimeLimit* time_limit_ = nullptr;
SolverLogger* logger_;
DISALLOW_COPY_AND_ASSIGN(SatPresolver);
};
// Visible for testing. Returns true iff:
// - a subsume b (subsumption): the clause a is a subset of b, in which case
// opposite_literal is set to -1.
// - b is strengthened by self-subsumption using a (self-subsuming resolution):
// the clause a with one of its literal negated is a subset of b, in which
// case opposite_literal is set to this negated literal index. Moreover, this
// opposite_literal is then removed from b.
//
// If num_inspected_literals_ is not nullptr, the "complexity" of this function
// will be added to it in order to track the amount of work done.
//
// TODO(user): when a.size() << b.size(), we should use binary search instead
// of scanning b linearly.
bool SimplifyClause(const std::vector<Literal>& a, std::vector<Literal>* b,
LiteralIndex* opposite_literal,
int64_t* num_inspected_literals = nullptr);
// Visible for testing. Returns kNoLiteralIndex except if:
// - a and b differ in only one literal.
// - For a it is the given literal l.
// In which case, returns the LiteralIndex of the literal in b that is not in a.
LiteralIndex DifferAtGivenLiteral(const std::vector<Literal>& a,
const std::vector<Literal>& b, Literal l);
// Visible for testing. Computes the resolvant of 'a' and 'b' obtained by
// performing the resolution on 'x'. If the resolvant is trivially true this
// returns false, otherwise it returns true and fill 'out' with the resolvant.
//
// Note that the resolvant is just 'a' union 'b' with the literals 'x' and
// not(x) removed. The two clauses are assumed to be sorted, and the computed
// resolvant will also be sorted.
//
// This is the basic operation when a variable is eliminated by clause
// distribution.
bool ComputeResolvant(Literal x, const std::vector<Literal>& a,
const std::vector<Literal>& b, std::vector<Literal>* out);
// Same as ComputeResolvant() but just returns the resolvant size.
// Returns -1 when ComputeResolvant() returns false.
int ComputeResolvantSize(Literal x, const std::vector<Literal>& a,
const std::vector<Literal>& b);
// Presolver that does literals probing and finds equivalent literals by
// computing the strongly connected components of the graph:
// literal l -> literals propagated by l.
//
// Clears the mapping if there are no equivalent literals. Otherwise, mapping[l]
// is the representative of the equivalent class of l. Note that mapping[l] may
// be equal to l.
//
// The postsolver will be updated so it can recover a solution of the mapped
// problem. Note that this works on any problem the SatSolver can handle, not
// only pure SAT problem, but the returned mapping do need to be applied to all
// constraints.
void ProbeAndFindEquivalentLiteral(
SatSolver* solver, SatPostsolver* postsolver,
DratProofHandler* drat_proof_handler,
absl::StrongVector<LiteralIndex, LiteralIndex>* mapping);
// Given a 'solver' with a problem already loaded, this will try to simplify the
// problem (i.e. presolve it) before calling solver->Solve(). In the process,
// because of the way the presolve is implemented, the underlying SatSolver may
// change (it is why we use this unique_ptr interface). In particular, the final
// variables and 'solver' state may have nothing to do with the problem
// originaly present in the solver. That said, if the problem is shown to be
// SAT, then the returned solution will be in term of the original variables.
//
// Note that the full presolve is only executed if the problem is a pure SAT
// problem with only clauses.
SatSolver::Status SolveWithPresolve(
std::unique_ptr<SatSolver>* solver, TimeLimit* time_limit,
std::vector<bool>* solution /* only filled if SAT */,
DratProofHandler* drat_proof_handler /* can be nullptr */,
SolverLogger* logger);
} // namespace sat
} // namespace operations_research
#endif // OR_TOOLS_SAT_SIMPLIFICATION_H_