Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
lrat_proof_handler.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 <bitset>
18#include <cstdint>
19#include <cstdlib>
20#include <fstream>
21#include <ios>
22#include <memory>
23#include <string>
24#include <string_view>
25#include <vector>
26
27#include "absl/container/flat_hash_map.h"
28#include "absl/flags/flag.h"
29#include "absl/log/check.h"
30#include "absl/log/log.h"
31#include "absl/strings/str_cat.h"
32#include "absl/strings/str_join.h"
33#include "absl/types/span.h"
34#include "ortools/base/file.h"
36#include "ortools/base/timer.h"
39#include "ortools/sat/lrat.pb.h"
41#include "ortools/sat/model.h"
45#include "ortools/sat/util.h"
46
47#if defined(_MSC_VER)
48ABSL_FLAG(std::string, cp_model_drat_output, ".\\drat.txt",
49 "File name for the generated DRAT proof, if DRAT output is enabled.");
50ABSL_FLAG(std::string, cp_model_lrat_output_prefix, ".\\lrat",
51 "File name prefix for the generated LRAT proof files, if LRAT output "
52 "is enabled. One file is created for each worker.");
53#else
54ABSL_FLAG(std::string, cp_model_drat_output, "/tmp/drat.txt",
55 "File name for the generated DRAT proof, if DRAT output is enabled.");
56ABSL_FLAG(std::string, cp_model_lrat_output_prefix, "/tmp/lrat",
57 "File name prefix for the generated LRAT proof files, if LRAT output "
58 "is enabled. One file is created for each worker.");
59#endif
60
61namespace operations_research {
62namespace sat {
63
65 : filename_(filename),
66 ofstream_(filename_, std::ios::binary),
67 writer_(&ofstream_) {
68 if (!ofstream_.good()) {
69 LOG(FATAL) << "Failed to open LRAT output file: " << filename;
70 }
71}
72
74 WriteDeletedClauseIds();
75 writer_.Close();
76}
77
79 absl::Span<const Literal> clause) {
80 WriteDeletedClauseIds();
81 LratProofStep step;
82 LratImportedClause* imported_clause = step.mutable_imported_clause();
83 imported_clause->set_clause_id(id.value());
84 for (const Literal literal : clause) {
85 imported_clause->add_literals(literal.Index().value());
86 }
87 CHECK(writer_.WriteRecord(step));
88}
89
91 absl::Span<const Literal> clause,
92 absl::Span<const ClauseId> unit_ids,
93 absl::Span<const LratChecker::RatIds> rat,
94 bool exported) {
95 WriteDeletedClauseIds();
96 LratProofStep step;
97 LratInferredClause* inferred_clause = step.mutable_inferred_clause();
98 inferred_clause->set_clause_id(id.value());
99 for (const Literal literal : clause) {
100 inferred_clause->add_literals(literal.Index().value());
101 }
102 for (const ClauseId unit_id : unit_ids) {
103 inferred_clause->add_unit_ids(unit_id.value());
104 }
105 for (const LratChecker::RatIds& rat_ids : rat) {
106 LratInferredClause::RatInfo* rat_info = inferred_clause->add_rat_infos();
107 rat_info->set_resolvant_id(rat_ids.resolvant_id.value());
108 for (const ClauseId unit_id : rat_ids.unit_ids) {
109 rat_info->add_unit_ids(unit_id.value());
110 }
111 }
112 inferred_clause->set_exported(exported);
113 CHECK(writer_.WriteRecord(step));
114}
115
116void LratWriter::ExportClause(ClauseId id, absl::Span<const Literal> clause) {
117 WriteDeletedClauseIds();
118 LratProofStep step;
119 LratExportedClause* exported_clause = step.mutable_exported_clause();
120 exported_clause->set_clause_id(id.value());
121 for (const Literal literal : clause) {
122 exported_clause->add_literals(literal.Index().value());
123 }
124 CHECK(writer_.WriteRecord(step));
125}
126
127void LratWriter::DeleteClause(ClauseId id) {
128 deleted_clause_ids_.push_back(id);
129}
130
131void LratWriter::WriteDeletedClauseIds() {
132 if (deleted_clause_ids_.empty()) return;
133 LratProofStep step;
135 deleted_clause_ids_.begin(), deleted_clause_ids_.end());
136 CHECK(writer_.WriteRecord(step));
137 deleted_clause_ids_.clear();
138}
139
140namespace {
141void IndicesToLiterals(absl::Span<const int> literal_indices,
142 std::vector<Literal>* literals) {
143 literals->clear();
144 literals->reserve(literal_indices.size());
145 for (const int lit : literal_indices) {
146 literals->push_back(Literal(LiteralIndex(lit)));
147 }
148}
149} // namespace
150
152 : id_(model->GetOrCreate<SharedLratProofStatus>()->NewSubSolverId()),
153 proof_status_(model->GetOrCreate<SharedLratProofStatus>()) {
154 const SatParameters& params = *model->GetOrCreate<SatParameters>();
155 if (params.check_merged_lrat_proof()) {
156 lrat_checker_ = std::make_unique<LratChecker>(model);
157 }
158 debug_crash_on_error_ = params.debug_crash_if_lrat_check_fails();
159}
160
163 if (lrat_checker_ != nullptr) {
164 status = lrat_checker_->Check() ? DratChecker::Status::VALID
166 if (status == DratChecker::Status::INVALID && debug_crash_on_error_) {
167 LOG(FATAL) << "LRAT error: " << lrat_checker_->error_message();
168 }
169 lrat_checker_->AddStats();
170 }
171 proof_status_->NewSubsolverProofStatus(status, lrat_checker_ != nullptr,
172 /*drat_check_enabled=*/false,
173 /*num_assumed_clauses=*/0, 0.0);
174}
175
176bool LratMerger::Merge(absl::Span<const std::string> proof_filenames) {
177 if (proof_filenames.empty()) return true;
178 merged_proof_filename_ =
179 absl::StrCat(absl::GetFlag(FLAGS_cp_model_lrat_output_prefix), ".txt");
180 merged_proof_file_.open(merged_proof_filename_);
181 if (!merged_proof_file_.good()) {
182 return Error(absl::StrCat("failed to open LRAT output file: ",
183 merged_proof_filename_));
184 }
185 if (!ReadPresolveProof(proof_filenames[0])) return false;
186
187 const int num_workers = proof_filenames.size() - 1;
188 std::vector<std::ifstream> inputs(num_workers);
189 std::vector<std::unique_ptr<RecordReader>> readers(num_workers);
190 last_read_steps_.resize(num_workers);
191 local_to_global_ids_.resize(num_workers);
192 exported_local_ids_.resize(num_workers);
193 for (int i = 0; i < num_workers; ++i) {
194 const std::string& filename = proof_filenames[i + 1];
195 inputs[i].open(filename, std::ios::binary);
196 if (!inputs[i].good()) {
197 return Error(absl::StrCat("failed to open LRAT input file: ", filename));
198 }
199 readers[i] = std::make_unique<RecordReader>(&inputs[i]);
200 if (!readers[i]->ReadRecord(&last_read_steps_[i])) {
201 last_read_steps_[i].Clear();
202 }
203 }
204
205 std::vector<Literal> clause;
206 while (true) {
207 bool at_least_one_step_read = false;
208 int worker_with_missing_import = -1;
209 for (int i = 0; i < num_workers; ++i) {
210 const std::string& filename = proof_filenames[i + 1];
211 // An empty step means that the reader is at the end of the file.
212 bool missing_import = false;
213 while (last_read_steps_[i].step_case() != LratProofStep::STEP_NOT_SET &&
214 !missing_import) {
215 LratProofStep& step = last_read_steps_[i];
216 switch (step.step_case()) {
218 ClauseId local_id(step.imported_clause().clause_id());
219 IndicesToLiterals(step.imported_clause().literals(), &clause);
220 std::sort(clause.begin(), clause.end());
221 auto it = shared_clause_ids_.find(clause);
222 if (it != shared_clause_ids_.end()) {
223 local_to_global_ids_[i][local_id] = it->second;
224 } else {
225 missing_import = true;
226 }
227 break;
228 }
230 if (!RemapInferredClause(i, filename,
231 *step.mutable_inferred_clause())) {
232 return false;
233 }
234 if (!WriteInferredClause(step.inferred_clause())) return false;
235 // We found the empty clause, we don't need anymore steps.
236 if (step.inferred_clause().literals().empty()) return true;
237 if (step.inferred_clause().exported() ||
238 step.inferred_clause().literals_size() <= 2) {
239 clause.clear();
240 IndicesToLiterals(step.inferred_clause().literals(), &clause);
241 SortAndAddSharedClause(
242 GlobalId(step.inferred_clause().clause_id()), clause);
243 exported_local_ids_[i].insert(
244 ClauseId(step.inferred_clause().clause_id()));
245 }
246 break;
248 const ClauseId local_id(step.exported_clause().clause_id());
249 auto it = local_to_global_ids_[i].find(local_id);
250 if (it == local_to_global_ids_[i].end()) {
251 return Error(absl::StrCat("unknown exported clause ID ", local_id,
252 " in ", filename));
253 }
254 const GlobalId global_id = it->second;
255 IndicesToLiterals(step.exported_clause().literals(), &clause);
256 SortAndAddSharedClause(global_id, clause);
257 exported_local_ids_[i].insert(local_id);
258 break;
259 }
261 for (const int64_t clause_id :
262 step.deleted_clauses().clause_ids()) {
263 const ClauseId local_id(clause_id);
264 if (exported_local_ids_[i].contains(local_id)) {
265 // TODO(user): implement this case. We should delete the
266 // clause from `shared_clause_ids_`, but only after we are sure
267 // that no other worker will ever import it.
268 } else {
269 local_to_global_ids_[i].erase(local_id);
270 }
271 }
272 break;
273 default:
274 return Error(absl::StrCat("unknown step type ", step.step_case(),
275 " in ", filename));
276 }
277 if (missing_import) {
278 worker_with_missing_import = i;
279 } else {
280 if (!readers[i]->ReadRecord(&last_read_steps_[i])) {
281 last_read_steps_[i].Clear();
282 }
283 at_least_one_step_read = true;
284 }
285 }
286 }
287 if (!at_least_one_step_read) {
288 if (worker_with_missing_import >= 0) {
289 const LratImportedClause& missing_import =
290 last_read_steps_[worker_with_missing_import].imported_clause();
291 clause.clear();
292 IndicesToLiterals(missing_import.literals(), &clause);
293 return Error(
294 absl::StrCat("imported clause not found in ",
295 proof_filenames[worker_with_missing_import + 1],
296 ": id=", missing_import.clause_id(),
297 ", literals=", absl::StrJoin(clause, ",")));
298 } else {
299 return true;
300 }
301 }
302 }
303}
304
305bool LratMerger::ReadPresolveProof(const std::string& filename) {
306 std::ifstream input(filename, std::ios::binary);
307 if (!input.good()) {
308 return Error(absl::StrCat("failed to open LRAT input file: ", filename));
309 }
310 RecordReader reader(&input);
311 LratProofStep step;
312 std::vector<Literal> clause;
313 absl::flat_hash_map<GlobalId, std::vector<Literal>> shared_clauses;
314 GlobalId max_global_id(0);
315 while (reader.ReadRecord(&step)) {
316 switch (step.step_case()) {
318 GlobalId global_id(step.imported_clause().clause_id());
319 max_global_id = std::max(max_global_id, global_id);
320 IndicesToLiterals(step.imported_clause().literals(), &clause);
321 std::sort(clause.begin(), clause.end());
322 shared_clauses[global_id] = clause;
323 if (lrat_checker_ != nullptr &&
324 !lrat_checker_->AddProblemClause(ClauseId(global_id.value()),
325 clause)) {
326 return LratError();
327 }
328 break;
329 }
331 GlobalId global_id(step.inferred_clause().clause_id());
332 max_global_id = std::max(max_global_id, global_id);
333 IndicesToLiterals(step.inferred_clause().literals(), &clause);
334 std::sort(clause.begin(), clause.end());
335 shared_clauses[global_id] = clause;
336 if (!WriteInferredClause(step.inferred_clause())) return false;
337 break;
338 }
340 // Nothing to do, since we export all clauses in the presolve proof.
341 break;
342 }
344 for (const int64_t clause_id : step.deleted_clauses().clause_ids()) {
345 const GlobalId global_id(clause_id);
346 auto it = shared_clauses.find(global_id);
347 if (it != shared_clauses.end()) {
348 shared_clauses.erase(it);
349 }
350 }
351 break;
352 default:
353 return Error(absl::StrCat("unknown proof step type ", step.step_case(),
354 " in ", filename));
355 }
356 }
357 for (const auto& [global_id, clause] : shared_clauses) {
358 shared_clause_ids_.insert({clause, global_id});
359 }
360 next_global_id_ = ++max_global_id;
361 return true;
362}
363
364void LratMerger::SortAndAddSharedClause(GlobalId id,
365 std::vector<Literal>& literals) {
366 std::sort(literals.begin(), literals.end());
367 shared_clause_ids_.insert({literals, id});
368}
369
370bool LratMerger::RemapInferredClause(int worker_index,
371 const std::string& filename,
372 LratInferredClause& inferred_clause) {
373 const GlobalId global_id = NextGlobalId();
374 ClauseId local_id = ClauseId(inferred_clause.clause_id());
375 local_to_global_ids_[worker_index][local_id] = global_id;
376
377 inferred_clause.set_clause_id(global_id.value());
378 if (!RemapClauseIds(worker_index, filename,
379 inferred_clause.mutable_unit_ids())) {
380 return false;
381 }
382 for (LratInferredClause::RatInfo& rat_info :
383 *inferred_clause.mutable_rat_infos()) {
384 local_id = ClauseId(rat_info.resolvant_id());
385 auto it = local_to_global_ids_[worker_index].find(local_id);
386 if (it == local_to_global_ids_[worker_index].end()) {
387 return Error(
388 absl::StrCat("unknown clause ID ", local_id, " in ", filename));
389 }
390 rat_info.set_resolvant_id(it->second.value());
391 if (!RemapClauseIds(worker_index, filename, rat_info.mutable_unit_ids())) {
392 return false;
393 }
394 }
395 return true;
396}
397
398bool LratMerger::RemapClauseIds(
399 int worker_index, const std::string& filename,
400 google::protobuf::RepeatedField<int64_t>* clause_ids) {
401 for (int i = 0; i < clause_ids->size(); ++i) {
402 const ClauseId local_id = ClauseId(clause_ids->Get(i));
403 auto it = local_to_global_ids_[worker_index].find(local_id);
404 if (it == local_to_global_ids_[worker_index].end()) {
405 return Error(
406 absl::StrCat("unknown clause ID ", local_id, " in ", filename));
407 }
408 clause_ids->Set(i, it->second.value());
409 }
410 return true;
411}
412
413bool LratMerger::WriteInferredClause(
414 const LratInferredClause& inferred_clause) {
415 if (lrat_checker_ != nullptr) {
416 // TODO(user): can we optimize away this format conversion?
417 IndicesToLiterals(inferred_clause.literals(), &tmp_clause_);
418 tmp_unit_ids_.clear();
419 tmp_unit_ids_.reserve(inferred_clause.unit_ids_size());
420 for (const int64_t id : inferred_clause.unit_ids()) {
421 tmp_unit_ids_.push_back(ClauseId(id));
422 }
423 tmp_rat_ids_.clear();
424 tmp_rat_ids_.reserve(inferred_clause.rat_infos_size());
425 for (const LratInferredClause::RatInfo& rat_info :
426 inferred_clause.rat_infos()) {
427 tmp_rat_ids_.push_back(
428 {ClauseId(rat_info.resolvant_id()),
429 std::vector<ClauseId>(rat_info.unit_ids().begin(),
430 rat_info.unit_ids().end())});
431 }
432 if (!lrat_checker_->AddInferredClause(ClauseId(inferred_clause.clause_id()),
433 tmp_clause_, tmp_unit_ids_,
434 tmp_rat_ids_)) {
435 return LratError();
436 }
437 }
438 merged_proof_file_ << inferred_clause.clause_id();
439 for (const int lit : inferred_clause.literals()) {
440 merged_proof_file_ << " " << Literal(LiteralIndex(lit)).SignedValue();
441 }
442 merged_proof_file_ << " 0";
443 for (const int unit_id : inferred_clause.unit_ids()) {
444 merged_proof_file_ << " " << unit_id;
445 }
446 for (const LratInferredClause::RatInfo& rat_info :
447 inferred_clause.rat_infos()) {
448 merged_proof_file_ << " " << -rat_info.resolvant_id();
449 for (const int unit_id : rat_info.unit_ids()) {
450 merged_proof_file_ << " " << unit_id;
451 }
452 }
453 merged_proof_file_ << " 0\n";
454 return true;
455}
456
457bool LratMerger::Error(std::string_view message) const {
458 if (debug_crash_on_error_) {
459 LOG(FATAL) << "LRAT merge error: " << message;
460 } else {
461 LOG(ERROR) << "LRAT merge error: " << message;
462 }
463 return false;
464}
465
466bool LratMerger::LratError() const {
467 if (debug_crash_on_error_) {
468 LOG(FATAL) << "LRAT error: " << lrat_checker_->error_message();
469 }
470 return false;
471}
472
473namespace {
474std::vector<Literal> SortClauseForDrat(absl::Span<const Literal> clause) {
475 // The sorting is such that new variables appear first. This is important for
476 // BVA since DRAT-trim only check the RAT property with respect to the first
477 // variable of the clause.
478 std::vector<Literal> sorted_clause(clause.begin(), clause.end());
479 std::sort(sorted_clause.begin(), sorted_clause.end(),
480 [](Literal a, Literal b) {
481 return std::abs(a.SignedValue()) > std::abs(b.SignedValue());
482 });
483 return sorted_clause;
484}
485} // namespace
486
487std::unique_ptr<LratProofHandler> LratProofHandler::MaybeCreate(Model* model) {
488 return MaybeCreate(*model->GetOrCreate<SatParameters>(),
491 model->GetOrCreate<SharedStatistics>());
492}
493
494std::unique_ptr<LratProofHandler> LratProofHandler::MaybeCreate(
495 const SatParameters& params, ClauseIdGenerator* id_generator,
496 SharedLratProofStatus* proof_status, SharedStatistics* stats) {
497 if (!params.check_lrat_proof() && !params.output_lrat_proof() &&
498 !params.check_drat_proof() && !params.output_drat_proof()) {
499 return nullptr;
500 }
501 return std::unique_ptr<LratProofHandler>(
502 new LratProofHandler(params, id_generator, proof_status, stats));
503}
504
505LratProofHandler::LratProofHandler(
506 const SatParameters& params, ClauseIdGenerator* id_generator,
507 SharedLratProofStatus* shared_lrat_proof_status, SharedStatistics* stats)
508 : id_(shared_lrat_proof_status->NewSubSolverId()),
509 id_generator_(id_generator),
510 proof_status_(shared_lrat_proof_status) {
511 if (params.check_lrat_proof()) {
512 lrat_checker_ = std::make_unique<LratChecker>(stats);
513 }
514 if (params.output_lrat_proof()) {
515 lrat_writer_ = std::make_unique<LratWriter>(absl::StrCat(
516 absl::GetFlag(FLAGS_cp_model_lrat_output_prefix), id_, ".bin"));
517 }
518 if (params.check_drat_proof()) {
519 drat_checker_ = std::make_unique<DratChecker>();
520 }
521 if (params.output_drat_proof()) {
522 File* drat_output = nullptr;
523 CHECK_OK(file::Open(absl::GetFlag(FLAGS_cp_model_drat_output), "w",
524 &drat_output, file::Defaults()));
525 drat_writer_ = std::make_unique<DratWriter>(
526 /*in_binary_drat_format=*/false, drat_output);
527 }
528 max_drat_time_in_seconds_ = params.max_drat_time_in_seconds();
529 debug_crash_on_error_ = params.debug_crash_if_lrat_check_fails();
530}
531
533 absl::Span<const Literal> clause) {
534 VLOG(2) << "AddProblemClause: id=" << id
535 << " literals=" << absl::StrJoin(clause, ",");
536 if (all_problem_clauses_loaded_ && debug_crash_on_error_) {
537 LOG(FATAL) << "LRAT error: problem clauses must not be added after "
538 "EndProblemClauses()";
539 }
540 if (lrat_checker_ != nullptr &&
541 !lrat_checker_->AddProblemClause(id, clause)) {
542 return LratError("In AddProblemClause.");
543 }
544 if (drat_checker_ != nullptr) {
545 drat_checker_->AddProblemClause(SortClauseForDrat(clause));
546 }
547 if (lrat_writer_ != nullptr) {
548 lrat_writer_->AddImportedClause(id, clause);
549 }
550 return true;
551}
552
554 all_problem_clauses_loaded_ = true;
555 if (drat_checker_ != nullptr) {
556 for (const auto& clause : clauses_inferred_during_problem_loading_) {
557 drat_checker_->AddInferredClause(clause);
558 }
559 clauses_inferred_during_problem_loading_.clear();
560 }
561}
562
564 ClauseId id, absl::Span<const Literal> clause,
565 absl::Span<const ClauseId> unit_ids,
566 absl::Span<const LratChecker::RatIds> rat, bool exported) {
567 VLOG(2) << "AddInferredClause: id=" << id
568 << " literals=" << absl::StrJoin(clause, ",")
569 << " unit_ids=" << absl::StrJoin(unit_ids, ",") << " rat={"
570 << absl::StrJoin(rat, " ") << "}";
571 if (lrat_checker_ != nullptr &&
572 !lrat_checker_->AddInferredClause(id, clause, unit_ids, rat)) {
573 return LratError(absl::StrCat("AddInferredClause: id=", id,
574 "\nliterals=", absl::StrJoin(clause, ","),
575 "\nunit_ids=", absl::StrJoin(unit_ids, ","),
576 "\nrat={", absl::StrJoin(rat, " "), "}"));
577 }
578 if (drat_checker_ != nullptr) {
579 if (all_problem_clauses_loaded_) {
580 drat_checker_->AddInferredClause(SortClauseForDrat(clause));
581 } else {
582 clauses_inferred_during_problem_loading_.push_back(
583 SortClauseForDrat(clause));
584 }
585 }
586 if (lrat_writer_ != nullptr) {
587 lrat_writer_->AddInferredClause(id, clause, unit_ids, rat, exported);
588 }
589 if (drat_writer_ != nullptr) {
590 drat_writer_->AddClause(clause);
591 }
592 return true;
593}
594
596 absl::Span<const Literal> clause) {
597 VLOG(2) << "AddImportedClause: id=" << id
598 << " literals=" << absl::StrJoin(clause, ",");
599 if (lrat_checker_ != nullptr &&
600 !lrat_checker_->AddProblemClause(id, clause)) {
601 return LratError("In AddImportedClause");
602 }
603 if (drat_checker_ != nullptr) {
604 LOG(ERROR) << "Imported clauses are not supported by the DRAT checker.";
605 return false;
606 }
607 if (lrat_writer_ != nullptr) {
608 lrat_writer_->AddImportedClause(id, clause);
609 }
610 return true;
611}
612
614 absl::Span<const Literal> clause) {
615 VLOG(2) << "AddAssumedClause: id=" << id
616 << " literals=" << absl::StrJoin(clause, ",");
617 if (debug_crash_on_error_) {
618 LOG(FATAL) << "LRAT error: assumed clauses are not supposed to happen";
619 }
620 ++num_assumed_clauses_;
621 if (lrat_checker_ != nullptr &&
622 !lrat_checker_->AddProblemClause(id, clause)) {
623 return LratError("In AddAssumedClause");
624 }
625 if (drat_checker_ != nullptr) {
626 // The DRAT checker requires all problem clauses first, followed by inferred
627 // clauses only.
628 LOG(ERROR) << "Assumed clauses are not supported by the DRAT checker.";
629 return false;
630 }
631 return true;
632}
633
635 absl::Span<const Literal> clause) {
636 VLOG(2) << "ExportClause: id=" << id
637 << " literals=" << absl::StrJoin(clause, ",");
638 if (lrat_writer_ != nullptr) {
639 lrat_writer_->ExportClause(id, clause);
640 }
641 return true;
642}
643
645 absl::Span<const Literal> clause) {
646 DCHECK_NE(id, kNoClauseId);
647 DCHECK_EQ(pinned_clause_id_, kNoClauseId);
648 pinned_clause_id_ = id;
649 if (drat_checker_ != nullptr || drat_writer_ != nullptr) {
650 pinned_clause_.assign(clause.begin(), clause.end());
651 }
652 delete_pinned_clause_ = false;
653}
654
656 DCHECK_NE(id, kNoClauseId);
657 DCHECK_EQ(pinned_clause_id_, id);
658 pinned_clause_id_ = kNoClauseId;
659 if (delete_pinned_clause_) {
660 DeleteClause(id, pinned_clause_);
661 }
662}
663
665 absl::Span<const Literal> clause) {
666 if (pinned_clause_id_ == id) {
667 delete_pinned_clause_ = true;
668 return;
669 }
670 VLOG(2) << "DeleteClause: id=" << id
671 << " literals=" << absl::StrJoin(clause, ",");
672 if (lrat_checker_ != nullptr) {
673 lrat_checker_->DeleteClauses({id});
674 }
675 if (drat_checker_ != nullptr) {
676 drat_checker_->DeleteClause(clause);
677 }
678 if (lrat_writer_ != nullptr) {
679 lrat_writer_->DeleteClause(id);
680 }
681 if (drat_writer_ != nullptr) {
682 drat_writer_->DeleteClause(clause);
683 }
684}
685
687 if (lrat_checker_ != nullptr) {
688 if (lrat_checker_->Valid()) {
690 }
692 }
694}
695
698 if (lrat_checker_ != nullptr) {
699 status = lrat_checker_->Check() ? DratChecker::Status::VALID
701 if (status == DratChecker::Status::INVALID && debug_crash_on_error_) {
702 LOG(FATAL) << "LRAT error: " << lrat_checker_->error_message();
703 }
704 }
705 if (status != DratChecker::Status::INVALID && drat_checker_ != nullptr) {
706 drat_checker_->Check(max_drat_time_in_seconds_);
707 if (status == DratChecker::Status::INVALID && debug_crash_on_error_) {
708 LOG(FATAL) << "DRAT check failed";
709 }
710 }
711 return status;
712}
713
714bool LratProofHandler::LratError(absl::string_view message) const {
715 if (debug_crash_on_error_) {
716 LOG(FATAL) << "LRAT error: " << message
717 << "\nChecker_error:" << lrat_checker_->error_message();
718 }
719 return false;
720}
721
722void LratProofHandler::Close(bool model_is_unsat) {
723 WallTimer timer;
724 timer.Start();
725 const bool valid = model_is_unsat ? Check() : Valid();
726 proof_status_->NewSubsolverProofStatus(
729 timer.Get());
730 if (lrat_checker_ != nullptr) {
731 lrat_checker_->AddStats();
732 }
733 if (lrat_writer_ != nullptr) {
734 proof_status_->NewProofFile(lrat_writer_->filename());
735 }
736}
737
739 absl::Span<const Literal> new_clause,
740 absl::Span<const ClauseId> ids_for_proof,
741 const CompactVectorVector<int, Literal>& clauses_for_proof) {
742 CHECK_EQ(ids_for_proof.size(), clauses_for_proof.size());
743 CHECK(!clauses_for_proof.empty());
744
745 // helper function to report some info on proof failure.
746 const auto error = [&, this](absl::string_view message) {
747 if (debug_crash_on_error_) {
748 LOG(INFO) << "Proving " << new_clause;
749 for (int i = 0; i < ids_for_proof.size(); ++i) {
750 LOG(INFO) << "input id= " << ids_for_proof[i]
751 << " clause=" << clauses_for_proof[i];
752 }
753 LOG(FATAL) << message;
754 } else {
755 VLOG(2) << "Proving " << new_clause;
756 for (int i = 0; i < ids_for_proof.size(); ++i) {
757 VLOG(2) << "input id= " << ids_for_proof[i]
758 << " clause=" << clauses_for_proof[i];
759 }
760 VLOG(2) << message;
761 }
762 return kNoClauseId;
763 };
764
765 // First we count the number of variables appearing and have a separate dense
766 // index for them. The first new_clause.size() dense index are exactly the
767 // literal of the new_clause.
768 absl::flat_hash_map<BooleanVariable, int> to_dense_index;
769 std::vector<Literal> dense_index_to_literal;
770 dense_index_to_literal.assign(new_clause.begin(), new_clause.end());
771 for (const Literal lit : new_clause) {
772 const auto [it, inserted] =
773 to_dense_index.insert({lit.Variable(), to_dense_index.size()});
774 if (!inserted) {
775 return error("Duplicate variable in new clause");
776 }
777 }
778
779 // Then any new BooleanVariable appearing get the next dense index.
780 std::vector<Literal> relevant_literals;
781 for (int i = 0; i < clauses_for_proof.size(); ++i) {
782 for (const Literal lit : clauses_for_proof[i]) {
783 const auto [it, inserted] =
784 to_dense_index.insert({lit.Variable(), to_dense_index.size()});
785 if (inserted) {
786 relevant_literals.push_back(lit);
787 }
788 }
789 }
790
791 // Too many variables.
792 //
793 // TODO(user): The limit can be increased a bit if needed.
794 if (to_dense_index.size() > 6) {
795 return error("Too many variables");
796 }
797
798 // For the proof we will need all clauses of the form
799 // {new_clause, l0, ..., lk} for all k in [0, n) and
800 // li = relevant_literals[i] OR relevant_literals[i].Negated().
801 //
802 // That give us 2^(n + 1) intermediate clauses.
803 // Their ids will be stored in (1 << k) + binary_encoding_of_the_li.
804 const int n = to_dense_index.size() - new_clause.size();
805 CHECK_EQ(n, relevant_literals.size());
806 const int num_intermediates = 1 << (n + 1);
807 std::vector<ClauseId> ids(num_intermediates, kNoClauseId);
808
809 VLOG(2) << "Starting proof n= " << n << " " << num_intermediates;
810
811 // Any initial clause can be used to prove all the intermediates that contains
812 // it. Note that this code supports duplicate literals in the clauses.
813 for (int i = 0; i < clauses_for_proof.size(); ++i) {
814 bool skip = false;
815 int base_index = 0;
816 int mask = 0;
817 int k = 0;
818 for (const Literal lit : clauses_for_proof[i]) {
819 const int dense_index = to_dense_index[lit.Variable()];
820 if (dense_index < new_clause.size()) {
821 // Check that the literal is the same as in the new_clause, if
822 // not, this clause will not be needed for the proof.
823 if (lit != new_clause[dense_index]) {
824 skip = true;
825 break;
826 }
827 } else {
828 k = std::max(k, dense_index);
829 mask |= 1 << dense_index;
830 if (lit == relevant_literals[dense_index - new_clause.size()]) {
831 base_index |= 1 << dense_index;
832 }
833 }
834 }
835 if (skip) continue;
836 if (k == 0) {
837 // The clause is the same as the one we try to prove! or smaller.
838 if (clauses_for_proof[i].size() == new_clause.size()) {
839 return ids_for_proof[i];
840 } else {
841 // TODO(user): Likely we could have simplified what we are trying to
842 // prove. Like I saw this happen when we prove an equivalence but we
843 // can actually prove that the variables are fixed.
844 const ClauseId new_id = id_generator_->GetNextId();
845 if (!AddInferredClause(new_id, new_clause, {ids_for_proof[i]})) {
846 return error("failed trivial inclusion proof");
847 }
848 return new_id;
849 }
850 }
851
852 mask >>= new_clause.size();
853 base_index >>= new_clause.size();
854 k = k + 1 - new_clause.size();
855
856 VLOG(2) << k << " " << std::bitset<8>(mask) << " "
857 << std::bitset<8>(base_index);
858
859 // TODO(user): we could be faster here if it become needed.
860 for (int m = 0; m < (1 << n); ++m) {
861 if ((m & mask) != base_index) continue; // not included.
862 const int index = m | base_index;
863 for (int j = k; j <= n; ++j) {
864 if (index >> j == 0) {
865 VLOG(2) << "Included in " << j << " "
866 << std::bitset<8>((1 << j) | index);
867 ids[(1 << j) | index] = ids_for_proof[i];
868 }
869 }
870 }
871 }
872
873 // We can prove the others by decreasing k.
874 std::vector<Literal> tmp_clause;
875 tmp_clause.assign(new_clause.begin(), new_clause.end());
876 std::vector<bool> id_need_deletion(num_intermediates, false);
877 for (int k = n; --k >= 0;) {
878 for (int m = 0; m < (1 << k); ++m) {
879 const int index = (1 << k) | m;
880 if (ids[index] != kNoClauseId) continue; // Already proven.
881
882 // Generate the tmp_clause.
883 tmp_clause.resize(new_clause.size());
884 for (int i = 0; i < k; ++i) {
885 tmp_clause.push_back(relevant_literals[i]);
886 if (((index >> i) & 1) == 0) {
887 tmp_clause.back() = tmp_clause.back().Negated();
888 }
889 }
890
891 // Prove it from the two clauses at k + 1.
892 const int higher1 = index ^ ((0b11) << k);
893 const int higher2 = index ^ ((0b10) << k);
894 const ClauseId id1 = ids[higher1];
895 const ClauseId id2 = ids[higher2];
896 if (id1 == kNoClauseId || id2 == kNoClauseId) {
897 return error(
898 absl::StrCat("missing higher level clauses in the resolution.",
899 " index: ", std::bitset<8>(index).to_string(),
900 " higher1: ", std::bitset<8>(higher1).to_string(),
901 " higher2: ", std::bitset<8>(higher2).to_string()));
902 }
903
904 ids[index] = id_generator_->GetNextId();
905 if (k != 0) {
906 VLOG(2) << "temporary !! " << ids[index] << " " << tmp_clause;
907 id_need_deletion[index] = true; // temporary.
908 }
909 if (!AddInferredClause(ids[index], tmp_clause, {id1, id2})) {
910 return error("Failed resolution step");
911 }
912
913 if (k == 0) {
914 DCHECK_EQ(new_clause, tmp_clause);
915 VLOG(2) << "Proven " << new_clause << "!";
916 }
917
918 // Lets delete the ids if they were temporary.
919 if (id_need_deletion[higher1]) {
920 tmp_clause.push_back(relevant_literals[k].Negated());
921 VLOG(2) << "deleting: " << id1 << " " << tmp_clause;
922 DeleteClause(id1, tmp_clause);
923 tmp_clause.pop_back();
924 }
925 if (id_need_deletion[higher2]) {
926 tmp_clause.push_back(relevant_literals[k]);
927 VLOG(2) << "deleting: " << id2 << " " << tmp_clause;
928 DeleteClause(id2, tmp_clause);
929 tmp_clause.pop_back();
930 }
931 }
932 }
933
934 return ids[1];
935}
936
937} // namespace sat
938} // namespace operations_research
double Get() const
Definition timer.h:44
void Start()
Definition timer.h:30
::int64_t clause_ids(int index) const
Definition lrat.pb.h:1987
::google::protobuf::RepeatedField<::int64_t > *PROTOBUF_NONNULL mutable_clause_ids()
Definition lrat.pb.h:2006
::int32_t literals(int index) const
Definition lrat.pb.h:1932
::int32_t literals(int index) const
Definition lrat.pb.h:1545
::operations_research::sat::LratInferredClause_RatInfo *PROTOBUF_NONNULL add_rat_infos()
Definition lrat.pb.h:1830
::int32_t literals(int index) const
Definition lrat.pb.h:1713
LratInferredClause_RatInfo RatInfo
Definition lrat.pb.h:1076
bool Merge(absl::Span< const std::string > proof_filenames)
void DeleteClause(ClauseId id, absl::Span< const Literal > clause)
bool ExportClause(ClauseId id, absl::Span< const Literal > clause)
bool AddProblemClause(ClauseId id, absl::Span< const Literal > clause)
ClauseId AddAndProveInferredClauseByEnumeration(absl::Span< const Literal > new_clause, absl::Span< const ClauseId > ids_for_proof, const CompactVectorVector< int, Literal > &clauses_for_proof)
static std::unique_ptr< LratProofHandler > MaybeCreate(Model *model)
bool AddInferredClause(ClauseId id, absl::Span< const Literal > clause, absl::Span< const ClauseId > unit_ids, bool exported=false)
bool AddImportedClause(ClauseId id, absl::Span< const Literal > clause)
void PinClause(ClauseId id, absl::Span< const Literal > clause)
bool AddAssumedClause(ClauseId id, absl::Span< const Literal > clause)
const ::operations_research::sat::LratDeletedClauses & deleted_clauses() const
Definition lrat.pb.h:2314
const ::operations_research::sat::LratImportedClause & imported_clause() const
Definition lrat.pb.h:2068
const ::operations_research::sat::LratInferredClause & inferred_clause() const
Definition lrat.pb.h:2150
::operations_research::sat::LratImportedClause *PROTOBUF_NONNULL mutable_imported_clause()
Definition lrat.pb.h:2104
::operations_research::sat::LratDeletedClauses *PROTOBUF_NONNULL mutable_deleted_clauses()
Definition lrat.pb.h:2350
::operations_research::sat::LratExportedClause *PROTOBUF_NONNULL mutable_exported_clause()
Definition lrat.pb.h:2268
const ::operations_research::sat::LratExportedClause & exported_clause() const
Definition lrat.pb.h:2232
::operations_research::sat::LratInferredClause *PROTOBUF_NONNULL mutable_inferred_clause()
Definition lrat.pb.h:2186
LratWriter(std::string_view filename)
void AddInferredClause(ClauseId id, absl::Span< const Literal > clause, absl::Span< const ClauseId > unit_ids, absl::Span< const LratChecker::RatIds > rat, bool exported=false)
void ExportClause(ClauseId id, absl::Span< const Literal > clause)
void AddImportedClause(ClauseId id, absl::Span< const Literal > clause)
bool WriteRecord(const google::protobuf::MessageLite &record)
Definition recordio.cc:42
ABSL_FLAG(std::string, cp_model_drat_output, "/tmp/drat.txt", "File name for the generated DRAT proof, if DRAT output is enabled.")
constexpr ClauseId kNoClauseId(0)
OR-Tools root namespace.
ClosedInterval::Iterator end(ClosedInterval interval)
STL namespace.
if(!yyg->yy_init)
Definition parser.yy.cc:966
static int input(yyscan_t yyscanner)