Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
drat_checker.h
Go to the documentation of this file.
1// Copyright 2010-2025 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_DRAT_CHECKER_H_
15#define OR_TOOLS_SAT_DRAT_CHECKER_H_
16
17#include <stddef.h>
18
19#include <cstddef>
20#include <cstdint>
21#include <limits>
22#include <string>
23#include <vector>
24
25#include "absl/container/flat_hash_set.h"
26#include "absl/types/span.h"
30
31namespace operations_research {
32namespace sat {
33
34// Index of a clause (>= 0).
36const ClauseIndex kNoClauseIndex(-1);
37
38// DRAT is a SAT proof format that allows a simple program to check that a
39// problem is really UNSAT. The description of the format and a checker are
40// available at http://www.cs.utexas.edu/~marijn/drat-trim/. This class checks
41// that a DRAT proof is valid.
42//
43// Note that DRAT proofs are often huge (can be GB), and can take about as much
44// time to check as it takes to find the proof in the first place!
46 public:
48 ~DratChecker() = default;
49
50 // Returns the number of Boolean variables used in the problem and infered
51 // clauses.
52 int num_variables() const { return num_variables_; }
53
54 // Adds a clause of the problem that must be checked. The problem clauses must
55 // be added first, before any infered clause. The given clause must not
56 // contain a literal and its negation. Must not be called after Check().
57 void AddProblemClause(absl::Span<const Literal> clause);
58
59 // Adds a clause which is infered from the problem clauses and the previously
60 // infered clauses (that are have not been deleted). Infered clauses must be
61 // added after the problem clauses. Clauses with the Reverse Asymmetric
62 // Tautology (RAT) property for literal l must start with this literal. The
63 // given clause must not contain a literal and its negation. Must not be
64 // called after Check().
65 void AddInferedClause(absl::Span<const Literal> clause);
66
67 // Deletes a problem or infered clause. The order of the literals does not
68 // matter. In particular, it can be different from the order that was used
69 // when the clause was added. Must not be called after Check().
70 void DeleteClause(absl::Span<const Literal> clause);
71
72 // Checks that the infered clauses form a DRAT proof that the problem clauses
73 // are UNSAT. For this the last added infered clause must be the empty clause
74 // and each infered clause must have either the Reverse Unit Propagation (RUP)
75 // or the Reverse Asymmetric Tautology (RAT) property with respect to the
76 // problem clauses and the previously infered clauses which are not deleted.
77 // Returns VALID if the proof is valid, INVALID if it is not, and UNKNOWN if
78 // the check timed out.
79 // WARNING: no new clause must be added or deleted after this method has been
80 // called.
86 Status Check(double max_time_in_seconds);
87
88 // Returns a subproblem of the original problem that is already UNSAT. The
89 // result is undefined if Check() was not previously called, or did not return
90 // true.
91 std::vector<std::vector<Literal>> GetUnsatSubProblem() const;
92
93 // Returns a DRAT proof that GetUnsatSubProblem() is UNSAT. The result is
94 // undefined if Check() was not previously called, or did not return true.
95 std::vector<std::vector<Literal>> GetOptimizedProof() const;
96
97 private:
98 // A problem or infered clause. The literals are specified as a subrange of
99 // 'literals_' (namely the subrange from 'first_literal_index' to
100 // 'first_literal_index' + 'num_literals' - 1), and are sorted in increasing
101 // order *before Check() is called*.
102 struct Clause {
103 // The index of the first literal of this clause in 'literals_'.
104 int first_literal_index;
105 // The number of literals of this clause.
106 int num_literals;
107
108 // The clause literal to use to check the RAT property, or kNoLiteralIndex
109 // for problem clauses and empty infered clauses.
110 LiteralIndex rat_literal_index = kNoLiteralIndex;
111
112 // The *current* number of copies of this clause. This number is incremented
113 // each time a copy of the clause is added, and decremented each time a copy
114 // is deleted. When this number reaches 0, the clause is actually marked as
115 // deleted (see 'deleted_index'). If other copies are added after this
116 // number reached 0, a new clause is added (because a Clause lifetime is a
117 // single interval of ClauseIndex values; therefore, in order to represent a
118 // lifetime made of several intervals, several Clause are used).
119 int num_copies = 1;
120
121 // The index in 'clauses_' from which this clause is deleted (inclusive).
122 // For instance, with AddProblemClause(c0), AddProblemClause(c1),
123 // DeleteClause(c0), AddProblemClause(c2), ... if c0's index is 0, then its
124 // deleted_index is 2. Meaning that when checking a clause whose index is
125 // larger than or equal to 2 (e.g. c2), c0 can be ignored.
126 ClauseIndex deleted_index = ClauseIndex(std::numeric_limits<int>::max());
127
128 // The indices of the clauses (with at least two literals) which are deleted
129 // just after this clause.
130 std::vector<ClauseIndex> deleted_clauses;
131
132 // Whether this clause is actually needed to check the DRAT proof.
133 bool is_needed_for_proof = false;
134 // Whether this clause is actually needed to check the current step (i.e. an
135 // infered clause) of the DRAT proof. This bool is always false, except in
136 // MarkAsNeededForProof() that uses it temporarily.
137 bool tmp_is_needed_for_proof_step = false;
138
139 Clause(int first_literal_index, int num_literals);
140
141 // Returns true if this clause is deleted before the given clause.
142 bool IsDeleted(ClauseIndex clause_index) const;
143 };
144
145 // A literal to assign to true during boolean constraint propagation. When a
146 // literal is assigned, new literals can be found that also need to be
147 // assigned to true (via unit clauses). In this case they are pushed on a
148 // stack of LiteralToAssign values, to be processed later on (the use of this
149 // stack avoids recursive calls to the boolean constraint propagation method
150 // AssignAndPropagate()).
151 struct LiteralToAssign {
152 // The literal that must be assigned to true.
153 Literal literal;
154 // The index of the clause from which this assignment was deduced. This is
155 // kNoClauseIndex for the clause we are currently checking (whose literals
156 // are all falsified to check if a conflict can be derived). Otherwise this
157 // is the index of a unit clause with unit literal 'literal' that was found
158 // during boolean constraint propagation.
159 ClauseIndex source_clause_index;
160 };
161
162 // Hash function for clauses.
163 struct ClauseHash {
164 DratChecker* checker;
165 explicit ClauseHash(DratChecker* checker) : checker(checker) {}
166 std::size_t operator()(ClauseIndex clause_index) const;
167 };
168
169 // Equality function for clauses.
170 struct ClauseEquiv {
171 DratChecker* checker;
172 explicit ClauseEquiv(DratChecker* checker) : checker(checker) {}
173 bool operator()(ClauseIndex clause_index1, ClauseIndex clause_index2) const;
174 };
175
176 // Adds a clause and returns its index.
177 ClauseIndex AddClause(absl::Span<const Literal> clause);
178
179 // Removes the last clause added to 'clauses_'.
180 void RemoveLastClause();
181
182 // Returns the literals of the given clause in increasing order.
183 absl::Span<const Literal> Literals(const Clause& clause) const;
184
185 // Initializes the data structures used to check the DRAT proof.
186 void Init();
187
188 // Adds 2 watch literals for the given clause.
189 void WatchClause(ClauseIndex clause_index);
190
191 // Returns true if, by assigning all the literals of 'clause' to false, a
192 // conflict can be found with boolean constraint propagation, using the non
193 // deleted clauses whose index is strictly less than 'num_clauses'. If so,
194 // marks the clauses actually used in this process as needed to check to DRAT
195 // proof.
196 bool HasRupProperty(ClauseIndex num_clauses,
197 absl::Span<const Literal> clause);
198
199 // Assigns 'literal' to true in 'assignment_' (and pushes it to 'assigned_'),
200 // records its source clause 'source_clause_index' in 'assignment_source_',
201 // and uses the watched literals to find all the clauses (whose index is less
202 // than 'num_clauses') that become unit due to this assignment. For each unit
203 // clause found, pushes its unit literal on top of
204 // 'high_priority_literals_to_assign_' or 'low_priority_literals_to_assign_'.
205 // Returns kNoClauseIndex if no falsified clause is found, or the index of the
206 // first found falsified clause otherwise.
207 ClauseIndex AssignAndPropagate(ClauseIndex num_clauses, Literal literal,
208 ClauseIndex source_clause_index);
209
210 // Marks the given clause as needed to check the DRAT proof, as well as the
211 // other clauses which were used to check this clause (these are found from
212 // 'unit_stack_', containing the clauses that became unit in
213 // AssignAndPropagate, and from 'assignment_source_', containing for each
214 // variable the clause that caused its assignment).
215 void MarkAsNeededForProof(Clause* clause);
216
217 // Returns the clauses whose index is in [begin,end) which are needed for the
218 // proof. The result is undefined if Check() was not previously called, or did
219 // not return true.
220 std::vector<std::vector<Literal>> GetClausesNeededForProof(
221 ClauseIndex begin, ClauseIndex end) const;
222
223 void LogStatistics(int64_t duration_nanos) const;
224
225 // The index of the first infered clause in 'clauses_', or kNoClauseIndex if
226 // there is no infered clause.
227 ClauseIndex first_infered_clause_index_;
228
229 // The problem clauses, followed by the infered clauses.
230 util_intops::StrongVector<ClauseIndex, Clause> clauses_;
231
232 // A content addressable set of the non-deleted clauses in clauses_. After
233 // adding a clause to clauses_, this set can be used to find if the same
234 // clause was previously added (i.e if a find using the new clause index
235 // returns a previous index) and not yet deleted.
236 absl::flat_hash_set<ClauseIndex, ClauseHash, ClauseEquiv> clause_set_;
237
238 // All the literals used in 'clauses_'.
239 std::vector<Literal> literals_;
240
241 // The number of Boolean variables used in the clauses.
242 int num_variables_;
243
244 // ---------------------------------------------------------------------------
245 // Data initialized in Init() and used in Check() to check the DRAT proof.
246
247 // The literals that have been assigned so far (this is used to unassign them
248 // after a clause has been checked, before checking the next one).
249 std::vector<Literal> assigned_;
250
251 // The current assignment values of literals_.
252 VariablesAssignment assignment_;
253
254 // For each variable, the index of the unit clause that caused its assignment,
255 // or kNoClauseIndex if the variable is not assigned, or was assigned to
256 // falsify the clause that is currently being checked.
257 util_intops::StrongVector<BooleanVariable, ClauseIndex> assignment_source_;
258
259 // The stack of literals that remain to be assigned to true during boolean
260 // constraint propagation, with high priority (unit clauses which are already
261 // marked as needed for the proof are given higher priority than the others
262 // during boolean constraint propagation. According to 'Trimming while
263 // Checking Clausal Proofs', this heuristics reduces the final number of
264 // clauses that are marked as needed for the proof, and therefore the
265 // verification time, in a majority of cases -- but not all).
266 std::vector<LiteralToAssign> high_priority_literals_to_assign_;
267
268 // The stack of literals that remain to be assigned to true during boolean
269 // constraint propagation, with low priority (see above).
270 std::vector<LiteralToAssign> low_priority_literals_to_assign_;
271
272 // For each literal, the list of clauses in which this literal is watched.
273 // Invariant 1: the literals with indices first_watched_literal_index and
274 // second_watched_literal_index of each clause with at least two literals are
275 // watched.
276 // Invariant 2: watched literals are non-falsified if the clause is not
277 // satisfied (in more details: if a clause c is contained in
278 // 'watched_literals_[l]' for literal l, then either c is satisfied with
279 // 'assignment_', or l is unassigned or assigned to true).
280 util_intops::StrongVector<LiteralIndex, std::vector<ClauseIndex>>
281 watched_literals_;
282
283 // The list of clauses with only one literal. This is needed for boolean
284 // constraint propagation, in addition to watched literals, because watched
285 // literals can only be used with clauses having at least two literals.
286 std::vector<ClauseIndex> single_literal_clauses_;
287
288 // The stack of clauses that have become unit during boolean constraint
289 // propagation, in HasRupProperty().
290 std::vector<ClauseIndex> unit_stack_;
291
292 // A temporary assignment, always fully unassigned except in Resolve().
293 VariablesAssignment tmp_assignment_;
294
295 // ---------------------------------------------------------------------------
296 // Statistics
297
298 // The number of infered clauses having the RAT property (but not the RUP
299 // property).
300 int num_rat_checks_;
301};
302
303// Returns true if the given clause contains the given literal. This works in
304// O(clause.size()).
305bool ContainsLiteral(absl::Span<const Literal> clause, Literal literal);
306
307// Returns true if 'complementary_literal' is the unique complementary literal
308// in the two given clauses. If so the resolvent of these clauses (i.e. their
309// union with 'complementary_literal' and its negation removed) is set in
310// 'resolvent'. 'clause' must contain 'complementary_literal', while
311// 'other_clause' must contain its negation. 'assignment' must have at least as
312// many variables as each clause, and they must all be unassigned. They are
313// still unassigned upon return.
314bool Resolve(absl::Span<const Literal> clause,
315 absl::Span<const Literal> other_clause,
316 Literal complementary_literal, VariablesAssignment* assignment,
317 std::vector<Literal>* resolvent);
318
319// Adds to the given drat checker the problem clauses from the file at the given
320// path, which must be in DIMACS format. Returns true iff the file was
321// successfully parsed.
322bool AddProblemClauses(const std::string& file_path, DratChecker* drat_checker);
323
324// Adds to the given drat checker the infered and deleted clauses from the file
325// at the given path, which must be in DRAT format. Returns true iff the file
326// was successfully parsed.
327bool AddInferedAndDeletedClauses(const std::string& file_path,
328 DratChecker* drat_checker);
329
330// The file formats that can be used to save a list of clauses.
331enum SatFormat {
332 DIMACS,
336// Prints the given clauses in the file at the given path, using the given file
337// format. Returns true iff the file was successfully written.
338bool PrintClauses(const std::string& file_path, SatFormat format,
339 absl::Span<const std::vector<Literal>> clauses,
340 int num_variables);
341
342} // namespace sat
343} // namespace operations_research
344
345#endif // OR_TOOLS_SAT_DRAT_CHECKER_H_
void AddProblemClause(absl::Span< const Literal > clause)
std::vector< std::vector< Literal > > GetUnsatSubProblem() const
Status Check(double max_time_in_seconds)
See Algorithm of Fig. 8 in 'Trimming while Checking Clausal Proofs'.
std::vector< std::vector< Literal > > GetOptimizedProof() const
void AddInferedClause(absl::Span< const Literal > clause)
void DeleteClause(absl::Span< const Literal > clause)
bool AddInferedAndDeletedClauses(const std::string &file_path, DratChecker *drat_checker)
bool AddProblemClauses(const std::string &file_path, DratChecker *drat_checker)
const LiteralIndex kNoLiteralIndex(-1)
bool Resolve(absl::Span< const Literal > clause, absl::Span< const Literal > other_clause, Literal complementary_literal, VariablesAssignment *assignment, std::vector< Literal > *resolvent)
SatFormat
The file formats that can be used to save a list of clauses.
bool PrintClauses(const std::string &file_path, SatFormat format, absl::Span< const std::vector< Literal > > clauses, int num_variables)
bool ContainsLiteral(absl::Span< const Literal > clause, Literal literal)
const ClauseIndex kNoClauseIndex(-1)
In SWIG mode, we don't want anything besides these top-level includes.
#define DEFINE_STRONG_INDEX_TYPE(index_type_name)