Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
lrat_checker.cc
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
15
16#include <algorithm>
17#include <string_view>
18#include <utility>
19
20#include "absl/algorithm/container.h"
21#include "absl/container/flat_hash_set.h"
22#include "absl/log/check.h"
23#include "absl/log/log.h"
24#include "absl/strings/str_cat.h"
25#include "absl/strings/str_join.h"
26#include "absl/types/span.h"
28#include "ortools/sat/util.h"
29#include "ortools/util/bitset.h"
30
31namespace operations_research {
32namespace sat {
33
35 if (!VLOG_IS_ON(1)) return;
36 stats_->AddStats(
37 {{"LratChecker/num_problem_clauses", num_problem_clauses_},
38 {"LratChecker/num_inferred_clauses", num_inferred_clauses_},
39 {"LratChecker/num_processed_rup_literals", num_processed_rup_literals_},
40 {"LratChecker/num_processed_rup_clauses", num_processed_rup_clauses_},
41 {"LratChecker/num_unneeded_rup_literals", num_unneeded_rup_literals_},
42 {"LratChecker/num_unneeded_rup_clauses", num_unneeded_rup_clauses_},
43 {"LratChecker/num_processed_rat_literals", num_processed_rat_literals_},
44 {"LratChecker/num_processed_rat_clauses", num_processed_rat_clauses_},
45 {"LratChecker/num_unneeded_rat_literals", num_unneeded_rat_literals_},
46 {"LratChecker/num_unneeded_rat_clauses", num_unneeded_rat_clauses_},
47 {"LratChecker/num_deleted_clauses", num_deleted_clauses_},
48 {"LratChecker/num_deleted_clauses_not_found",
49 num_deleted_clauses_not_found_}});
50}
51
53 absl::Span<const Literal> clause) {
54 ++num_problem_clauses_;
55 return AddClauseInternal(id, clause, /*is_problem_clause=*/true,
56 /*unit_ids=*/{}, /*rat=*/{});
57}
58
60 absl::Span<const Literal> clause,
61 absl::Span<const ClauseId> unit_ids,
62 absl::Span<const RatIds> rat) {
63 ++num_inferred_clauses_;
64 return AddClauseInternal(id, clause, /*is_problem_clause=*/false, unit_ids,
65 rat);
66}
67
68void LratChecker::DeleteClauses(absl::Span<const ClauseId> clause_ids) {
69 ++num_deleted_clauses_;
70 for (const ClauseId clause_id : clause_ids) {
71 const auto it = clauses_.find(clause_id);
72 if (it == clauses_.end()) {
73 ++num_deleted_clauses_not_found_;
74 continue;
75 }
76 if (occurrences_needed_) {
77 for (const Literal literal : it->second) {
78 occurrences_[literal.Index()]--;
79 }
80 }
81 clauses_.erase(it);
82 }
83}
84
85namespace {
86enum UnitPropagationStatus {
87 kUnit = 1,
88 kConflict = 2,
89 kWarning = 3,
90 kError = 4,
91};
92
93UnitPropagationStatus Propagate(
94 absl::Span<const Literal> clause,
95 SparseBitset<LiteralIndex>& false_literals_set) {
96 LiteralIndex unique_unassigned_literal = kNoLiteralIndex;
97 for (const Literal literal : clause) {
98 if (!false_literals_set[literal]) {
99 if (unique_unassigned_literal != kNoLiteralIndex) return kError;
100 unique_unassigned_literal = literal.Index();
101 }
102 }
103 if (unique_unassigned_literal == kNoLiteralIndex) return kConflict;
104 const Literal unassigned_literal = Literal(unique_unassigned_literal);
105 DCHECK(!false_literals_set[unassigned_literal]);
106 if (false_literals_set[unassigned_literal.Negated()]) {
107 // `clause` propagates `unassigned_literal` which was already propagated by
108 // a previous clause.
109 return kWarning;
110 }
111 false_literals_set.Set(unassigned_literal.Negated());
112 return kUnit;
113}
114} // namespace
115
116bool LratChecker::AddClauseInternal(ClauseId id,
117 absl::Span<const Literal> clause,
118 bool is_problem_clause,
119 absl::Span<const ClauseId> unit_ids,
120 absl::Span<const RatIds> rat) {
121 if (!valid_) return false;
122 if (complete_) return true;
123 if (clauses_.contains(id))
124 return Error(id, absl::StrCat("clause ID ", id, " already used"));
125
126 FixedCapacityVector<Literal> sorted_clause(clause);
127 std::sort(sorted_clause.begin(), sorted_clause.end());
128 sorted_clause.resize(std::unique(sorted_clause.begin(), sorted_clause.end()) -
129 sorted_clause.begin());
130 for (int i = 1; i < sorted_clause.size(); ++i) {
131 if (sorted_clause[i] == sorted_clause[i - 1].Negated()) {
132 return true;
133 }
134 }
135 if (!sorted_clause.empty()) {
136 const int last_variable = sorted_clause.back().Variable().value();
137 if (last_variable >= num_variables_) {
138 num_variables_ = last_variable + 1;
139 if (occurrences_needed_) {
140 occurrences_.resize(2 * num_variables_, 0);
141 } else if (clause.size() == 1 && unit_ids.empty() && rat.empty()) {
142 // Early return for unit clauses made of a new variable. The following
143 // code would validate this proof with the RAT property, but would also
144 // set `occurrences_needed_` to true, which is unnecessary.
145 clauses_[id] = std::move(sorted_clause);
146 return true;
147 }
148 }
149 }
150
151 if (!is_problem_clause) {
152 tmp_false_literals_set_.ClearAndResize(LiteralIndex(2 * num_variables_));
153 for (const Literal literal : sorted_clause) {
154 tmp_false_literals_set_.Set(literal);
155 }
156 UnitPropagationStatus last_propagation_status = kUnit;
157 for (int i = 0; i < unit_ids.size(); ++i) {
158 const ClauseId unit_id = unit_ids[i];
159 auto it = clauses_.find(unit_id);
160 if (it == clauses_.end()) {
161 return Error(id, absl::StrCat("unit_id ", unit_id, " not found"));
162 }
163 ++num_processed_rup_clauses_;
164 num_processed_rup_literals_ += it->second.size();
165 last_propagation_status = Propagate(it->second, tmp_false_literals_set_);
166 if (last_propagation_status == kError) {
167 return Error(
168 id, absl::StrCat("unit_id ", unit_id, " is not unit. literals=[",
169 absl::StrJoin(it->second, ","), "]"));
170 } else if (last_propagation_status == kWarning) {
171 last_propagation_status = kUnit;
172 ++num_unneeded_rup_literals_;
173 } else if (last_propagation_status == kConflict) {
174 num_unneeded_rup_clauses_ += unit_ids.size() - i - 1;
175 break;
176 }
177 }
178 if (last_propagation_status == kUnit) {
179 // Check if `clause` has the RAT property.
180 if (clause.empty()) return Error(id, "missing pivot for RAT proof");
181 const Literal pivot = clause.front();
182 if (!occurrences_needed_) {
183 occurrences_needed_ = true;
184 InitializeOccurrences();
185 }
186 if (rat.size() != occurrences_[pivot.Negated()]) {
187 return Error(id, "wrong number of resolvant IDs in RAT proof");
188 }
189 absl::flat_hash_set<ClauseId>& resolvant_ids_set = tmp_clause_ids_;
190 resolvant_ids_set.clear();
191 // Check that the unit propagation proof of each rat_id is correct.
192 for (const RatIds& rat_ids : rat) {
193 const ClauseId resolvant_id = rat_ids.resolvant_id;
194 // rat_ids must not contain duplicate resolvant clause IDs.
195 if (!resolvant_ids_set.insert(resolvant_id).second) {
196 return Error(id,
197 absl::StrCat("duplicate resolvant_id ", resolvant_id));
198 }
199 // The resolvant clause must exist and must contain pivot.Negated().
200 const auto it = clauses_.find(resolvant_id);
201 if (it == clauses_.end()) {
202 return Error(
203 id, absl::StrCat("resolvant_id ", resolvant_id, " not found"));
204 }
205 const absl::Span<const Literal> resolvant = it->second;
206 if (!absl::c_binary_search(resolvant, pivot.Negated())) {
207 return Error(id,
208 absl::StrCat("missing negated pivot in resolvant_id ",
209 resolvant_id));
210 }
211 // Its unit propagation proof must be correct, unless `clause` and
212 // `resolvant` have two complementary literals (other than the pivot --
213 // this is still valid if we use `tmp_false_literals_set_` instead of
214 // `clause`).
215 tmp_rat_false_literals_set_.CopyFrom(tmp_false_literals_set_);
216 bool has_two_complementary_literals = false;
217 for (const Literal literal : resolvant) {
218 if (literal == pivot.Negated()) continue;
219 if (tmp_false_literals_set_[literal.Negated()]) {
220 has_two_complementary_literals = true;
221 break;
222 }
223 tmp_rat_false_literals_set_.Set(literal);
224 }
225 if (has_two_complementary_literals) continue;
226 last_propagation_status = kUnit;
227 for (int j = 0; j < rat_ids.unit_ids.size(); ++j) {
228 const ClauseId unit_id = rat_ids.unit_ids[j];
229 const auto it = clauses_.find(unit_id);
230 if (it == clauses_.end()) {
231 return Error(id,
232 absl::StrCat("rat.unit_id ", unit_id, " not found"));
233 }
234 ++num_processed_rat_clauses_;
235 num_processed_rat_literals_ += it->second.size();
236 last_propagation_status =
237 Propagate(it->second, tmp_rat_false_literals_set_);
238 if (last_propagation_status == kError) {
239 return Error(id,
240 absl::StrCat("rat.unit_id ", unit_id, " is not unit"));
241 } else if (last_propagation_status == kWarning) {
242 last_propagation_status = kUnit;
243 ++num_unneeded_rat_literals_;
244 } else if (last_propagation_status == kConflict) {
245 num_unneeded_rat_clauses_ += rat_ids.unit_ids.size() - j - 1;
246 break;
247 }
248 }
249 if (last_propagation_status != kConflict) {
250 return Error(id, absl::StrCat("last unit_id for rat.resolvant_id ",
251 resolvant_id, " is not a conflict"));
252 }
253 }
254 }
255 }
256
257 if (occurrences_needed_) {
258 for (const Literal literal : sorted_clause) {
259 occurrences_[literal.Index()]++;
260 }
261 }
262 clauses_[id] = std::move(sorted_clause);
263 if (clause.empty()) {
264 complete_ = true;
265 }
266 return true;
267}
268
269void LratChecker::InitializeOccurrences() {
270 occurrences_.assign(2 * num_variables_, 0);
271 for (const auto& [id, clause] : clauses_) {
272 for (const Literal literal : clause) {
273 occurrences_[literal.Index()]++;
274 }
275 }
276}
277
278bool LratChecker::Error(ClauseId id, std::string_view error) {
279 if (valid_) {
280 error_message_ = absl::StrCat("In clause ", id, ": ", error);
281 valid_ = false;
282 }
283 return false;
284}
285
286} // namespace sat
287} // namespace operations_research
void Set(IntegerType index)
Definition bitset.h:878
LiteralIndex Index() const
Definition sat_base.h:92
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={})
void DeleteClauses(absl::Span< const ClauseId > clause_ids)
const LiteralIndex kNoLiteralIndex(-1)
OR-Tools root namespace.