Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
lrat_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 ORTOOLS_SAT_LRAT_CHECKER_H_
15#define ORTOOLS_SAT_LRAT_CHECKER_H_
16
17#include <stddef.h>
18
19#include <cstdint>
20#include <string>
21#include <string_view>
22
23#include "absl/container/flat_hash_map.h"
24#include "absl/container/flat_hash_set.h"
25#include "absl/strings/str_join.h"
26#include "absl/types/span.h"
28#include "ortools/sat/model.h"
31#include "ortools/sat/util.h"
32#include "ortools/util/bitset.h"
33
34namespace operations_research {
35namespace sat {
36
37// An incremental checker for LRAT proofs (https://arxiv.org/abs/1612.02353).
39 public:
40 explicit LratChecker(Model* model)
41 : LratChecker(model->GetOrCreate<SharedStatistics>()) {}
42 explicit LratChecker(SharedStatistics* stats) : stats_(stats) {}
43
44 // The clause IDs used in a proof that a clause has a Resolution Asymmetric
45 // Tautology (RAT) property. See AddInferredClause() for more details.
46 struct RatIds {
47 ClauseId resolvant_id;
48 absl::Span<const ClauseId> unit_ids;
49 };
50
51 // Adds a clause of the problem. Returns true if successful, i.e., if the
52 // given ID is not already used by another clause (it is OK to reuse the ID of
53 // a deleted clause). Does nothing if a previous step failed or if the proof
54 // is already complete, or if the clause contains a literal and its negation
55 // (since it is always true, it should not be needed to infer anything).
56 //
57 // Problem clauses can be added after inferred clauses which do not reference
58 // them. This can be used to add learned clauses proved by another worker, or
59 // "axioms" that we admit without proof.
60 bool AddProblemClause(ClauseId id, absl::Span<const Literal> clause);
61
62 // Adds a clause which is inferred from the problem clauses and/or the
63 // previously inferred clauses (that have not been deleted; they are called
64 // the 'current' clauses). Does nothing if a previous step failed or if the
65 // proof is already complete, or if the clause contains a literal and its
66 // negation (since it is always true, it should not be needed to infer
67 // anything). Otherwise, returns true if the given inference proof is valid,
68 // i.e., if the following conditions hold (checked sequentially):
69 //
70 // 1) The clause ID is not already used by another clause (it is OK to reuse
71 // the ID of a deleted clause).
72 // 2) The `unit_ids` clauses exist and are or become unit and eventually empty
73 // if all the literals of `clause` are assumed to be false (verification
74 // stops at the first empty clause). This list must be in unit propagation
75 // order: if a clause c becomes unit (or empty) because clauses c_1, ...,
76 // c_k are unit, then c must appear after c_1, ..., c_k in the list. Let
77 // RUP be all the literals which are found to be false by unit propagation.
78 // 3) If `rat` is empty, the last `unit_ids` clause must become empty after
79 // unit propagation.
80 // 4) Otherwise `clause` must not be empty, and `rat_clauses` must contain
81 // all the current clauses which contain the negation of the first
82 // literal of `clause` (called the pivot 'p') -- and no other clauses.
83 // Moreover, for each r in `rat`:
84 // * Either `clause` and the `r.resolvant_id` clause have two pairs of
85 // complementary literals.
86 // * Or all the `r.unit_ids` clauses must become unit and eventually empty
87 // if all the literals of `clause` and of the `r.resolvant_id` clause
88 // (minus ~p), as well as those in RUP (from condition 2), are assumed to
89 // be false (this list must be in unit propagation order, as explained
90 // above; verification stops at the first empty clause).
91 bool AddInferredClause(ClauseId id, absl::Span<const Literal> clause,
92 absl::Span<const ClauseId> unit_ids,
93 absl::Span<const RatIds> rat = {});
94
95 // Deletes problem or inferred clauses. It is OK to delete a clause which has
96 // already been deleted or has never been added.
97 void DeleteClauses(absl::Span<const ClauseId> clause_ids);
98
99 // Returns true if all the operations made so far were valid.
100 bool Valid() const { return valid_; }
101
102 // Returns true if the unsatisfiability proof is valid and complete, i.e.
103 // whether the empty clause has been successfully inferred.
104 bool Check() {
105 if (valid_ && !complete_) {
106 error_message_ = "empty clause not inferred";
107 }
108 return complete_;
109 }
110
111 void AddStats() const;
112
113 // Returns the reason of the first failed operation, or an empty string if all
114 // operations were successful.
115 std::string_view error_message() const { return error_message_; }
116
117 // This can help debugging wrong proof.
118 absl::Span<const Literal> GetClauseForDebug(ClauseId id) const {
119 auto it = clauses_.find(id);
120 if (it == clauses_.end()) return {};
121 return it->second;
122 }
123
124 private:
125 bool AddClauseInternal(ClauseId id, absl::Span<const Literal> clause,
126 bool is_problem_clause,
127 absl::Span<const ClauseId> unit_ids,
128 absl::Span<const RatIds> rat);
129
130 void InitializeOccurrences();
131
132 bool Error(ClauseId id, std::string_view error);
133
134 int num_variables_ = 0;
135
136 // The problem and inferred clauses which have not been deleted. The clause
137 // literals are sorted and without duplicates.
138 // TODO(user): investigate more cache friendly data structures (could be
139 // more efficient but their correctness could be harder to trust).
140 absl::flat_hash_map<ClauseId, FixedCapacityVector<Literal>> clauses_;
141
142 // The number of clauses in `clauses_` which contain each literal. This is
143 // initialized only if needed, i.e., when the first RAT proof is needed.
145 bool occurrences_needed_ = false;
146
147 // Whether all the operations made so far were valid.
148 bool valid_ = true;
149 std::string error_message_;
150
151 // Statistics.
152 int64_t num_problem_clauses_ = 0;
153 int64_t num_inferred_clauses_ = 0;
154 int64_t num_processed_rup_literals_ = 0;
155 int64_t num_processed_rup_clauses_ = 0;
156 int64_t num_unneeded_rup_literals_ = 0;
157 int64_t num_unneeded_rup_clauses_ = 0;
158 int64_t num_processed_rat_literals_ = 0;
159 int64_t num_processed_rat_clauses_ = 0;
160 int64_t num_unneeded_rat_literals_ = 0;
161 int64_t num_unneeded_rat_clauses_ = 0;
162 int64_t num_deleted_clauses_ = 0;
163 int64_t num_deleted_clauses_not_found_ = 0;
164
165 // Whether the proof is complete, i.e., whether the empty clause has been
166 // successfully inferred.
167 bool complete_ = false;
168
169 // Temporary sets used to check unit propagation proofs.
170 SparseBitset<LiteralIndex> tmp_false_literals_set_;
171 SparseBitset<LiteralIndex> tmp_rat_false_literals_set_;
172
173 // Temporary set used to check the RAT property of an inferred clause.
174 absl::flat_hash_set<ClauseId> tmp_clause_ids_;
175
176 SharedStatistics* stats_;
177};
178
179template <typename Sink, typename... T>
180void AbslStringify(Sink& sink, LratChecker::RatIds arg) {
181 absl::Format(&sink, "resolvant_id=%d unit_ids=[%s]", arg.resolvant_id.value(),
182 absl::StrJoin(arg.unit_ids, " "));
183}
184
185} // namespace sat
186} // namespace operations_research
187
188#endif // ORTOOLS_SAT_LRAT_CHECKER_H_
std::string_view error_message() const
absl::Span< const Literal > GetClauseForDebug(ClauseId id) const
bool AddProblemClause(ClauseId id, absl::Span< const Literal > clause)
*Either clause and the r resolvant_id clause have two pairs of *Or all the r unit_ids clauses must become unit and eventually empty bool AddInferredClause(ClauseId id, absl::Span< const Literal > clause, absl::Span< const ClauseId > unit_ids, absl::Span< const RatIds > rat={})
LratChecker(SharedStatistics *stats)
void DeleteClauses(absl::Span< const ClauseId > clause_ids)
void AbslStringify(Sink &sink, EdgePosition e)
OR-Tools root namespace.
absl::Span< const ClauseId > unit_ids