Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
simplification.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 <cstdint>
18#include <deque>
19#include <limits>
20#include <utility>
21#include <vector>
22
23#include "absl/container/btree_set.h"
24#include "absl/log/check.h"
25#include "absl/types/span.h"
32#include "ortools/base/timer.h"
36#include "ortools/sat/sat_parameters.pb.h"
41
42namespace operations_research {
43namespace sat {
44
46 : initial_num_variables_(num_variables), num_variables_(num_variables) {
47 reverse_mapping_.resize(num_variables);
48 for (BooleanVariable var(0); var < num_variables; ++var) {
49 reverse_mapping_[var] = var;
50 }
51 assignment_.Resize(num_variables);
52}
53
54void SatPostsolver::Add(Literal x, absl::Span<const Literal> clause) {
55 DCHECK(!clause.empty());
56 DCHECK(std::find(clause.begin(), clause.end(), x) != clause.end());
57 associated_literal_.push_back(ApplyReverseMapping(x));
58 clauses_start_.push_back(clauses_literals_.size());
59 for (const Literal& l : clause) {
60 clauses_literals_.push_back(ApplyReverseMapping(l));
61 }
62}
63
65 const Literal l = ApplyReverseMapping(x);
66 assignment_.AssignFromTrueLiteral(l);
67}
68
71 mapping) {
73 if (reverse_mapping_.size() < mapping.size()) {
74 // We have new variables.
75 while (reverse_mapping_.size() < mapping.size()) {
76 reverse_mapping_.push_back(BooleanVariable(num_variables_++));
77 }
78 assignment_.Resize(num_variables_);
79 }
80 for (BooleanVariable v(0); v < mapping.size(); ++v) {
81 const BooleanVariable image = mapping[v];
82 if (image != kNoBooleanVariable) {
83 if (image >= new_mapping.size()) {
84 new_mapping.resize(image.value() + 1, kNoBooleanVariable);
85 }
86 new_mapping[image] = reverse_mapping_[v];
87 DCHECK_NE(new_mapping[image], kNoBooleanVariable);
88 }
89 }
90 std::swap(new_mapping, reverse_mapping_);
91}
92
93Literal SatPostsolver::ApplyReverseMapping(Literal l) {
94 if (l.Variable() >= reverse_mapping_.size()) {
95 // We have new variables.
96 while (l.Variable() >= reverse_mapping_.size()) {
97 reverse_mapping_.push_back(BooleanVariable(num_variables_++));
98 }
99 assignment_.Resize(num_variables_);
100 }
101 DCHECK_NE(reverse_mapping_[l.Variable()], kNoBooleanVariable);
102 const Literal result(reverse_mapping_[l.Variable()], l.IsPositive());
103 DCHECK(!assignment_.LiteralIsAssigned(result));
104 return result;
105}
106
107void SatPostsolver::Postsolve(VariablesAssignment* assignment) const {
108 // First, we set all unassigned variable to true.
109 // This will be a valid assignment of the presolved problem.
110 for (BooleanVariable var(0); var < assignment->NumberOfVariables(); ++var) {
111 if (!assignment->VariableIsAssigned(var)) {
112 assignment->AssignFromTrueLiteral(Literal(var, true));
113 }
114 }
115
116 int previous_start = clauses_literals_.size();
117 for (int i = static_cast<int>(clauses_start_.size()) - 1; i >= 0; --i) {
118 bool set_associated_var = true;
119 const int new_start = clauses_start_[i];
120 for (int j = new_start; j < previous_start; ++j) {
121 if (assignment->LiteralIsTrue(clauses_literals_[j])) {
122 set_associated_var = false;
123 break;
124 }
125 }
126 previous_start = new_start;
127 if (set_associated_var) {
128 assignment->UnassignLiteral(associated_literal_[i].Negated());
129 assignment->AssignFromTrueLiteral(associated_literal_[i]);
130 }
131 }
132
133 // Ignore the value of any variables added by the presolve.
134 assignment->Resize(initial_num_variables_);
135}
136
138 const SatSolver& solver) {
139 std::vector<bool> solution(solver.NumVariables());
140 for (BooleanVariable var(0); var < solver.NumVariables(); ++var) {
141 DCHECK(solver.Assignment().VariableIsAssigned(var));
142 solution[var.value()] =
143 solver.Assignment().LiteralIsTrue(Literal(var, true));
144 }
146}
147
149 const std::vector<bool>& solution) {
150 for (BooleanVariable var(0); var < solution.size(); ++var) {
151 DCHECK_LT(var, reverse_mapping_.size());
152 DCHECK_NE(reverse_mapping_[var], kNoBooleanVariable);
153 DCHECK(!assignment_.VariableIsAssigned(reverse_mapping_[var]));
154 assignment_.AssignFromTrueLiteral(
155 Literal(reverse_mapping_[var], solution[var.value()]));
156 }
157 Postsolve(&assignment_);
158 std::vector<bool> postsolved_solution;
159 postsolved_solution.reserve(initial_num_variables_);
160 for (int i = 0; i < initial_num_variables_; ++i) {
161 postsolved_solution.push_back(
162 assignment_.LiteralIsTrue(Literal(BooleanVariable(i), true)));
163 }
164 return postsolved_solution;
165}
166
168
169void SatPresolver::AddClause(absl::Span<const Literal> clause) {
170 DCHECK_GT(clause.size(), 0) << "Added an empty clause to the presolver";
171 const ClauseIndex ci(clauses_.size());
172 clauses_.push_back(std::vector<Literal>(clause.begin(), clause.end()));
173 in_clause_to_process_.push_back(true);
174 clause_to_process_.push_back(ci);
175
176 bool changed = false;
177 std::vector<Literal>& clause_ref = clauses_.back();
178 if (!equiv_mapping_.empty()) {
179 for (int i = 0; i < clause_ref.size(); ++i) {
180 const Literal old_literal = clause_ref[i];
181 clause_ref[i] = Literal(equiv_mapping_[clause_ref[i]]);
182 if (old_literal != clause_ref[i]) changed = true;
183 }
184 }
185 std::sort(clause_ref.begin(), clause_ref.end());
186 clause_ref.erase(std::unique(clause_ref.begin(), clause_ref.end()),
187 clause_ref.end());
188
189 // Check for trivial clauses:
190 for (int i = 1; i < clause_ref.size(); ++i) {
191 if (clause_ref[i] == clause_ref[i - 1].Negated()) {
192 // The clause is trivial!
193 ++num_trivial_clauses_;
194 clause_to_process_.pop_back();
195 clauses_.pop_back();
196 in_clause_to_process_.pop_back();
197 return;
198 }
199 }
200
201 // This needs to be done after the basic canonicalization above.
202 signatures_.push_back(ComputeSignatureOfClauseVariables(ci));
203 DCHECK_EQ(signatures_.size(), clauses_.size());
204
205 if (drat_proof_handler_ != nullptr && changed) {
206 drat_proof_handler_->AddClause(clause_ref);
207 drat_proof_handler_->DeleteClause(clause);
208 }
209
210 const Literal max_literal = clause_ref.back();
211 const int required_size = std::max(max_literal.Index().value(),
212 max_literal.NegatedIndex().value()) +
213 1;
214 if (required_size > literal_to_clauses_.size()) {
215 literal_to_clauses_.resize(required_size);
216 literal_to_clause_sizes_.resize(required_size);
217 }
218 for (Literal e : clause_ref) {
219 literal_to_clauses_[e].push_back(ci);
220 literal_to_clause_sizes_[e]++;
221 }
222}
223
224void SatPresolver::SetNumVariables(int num_variables) {
225 const int required_size = 2 * num_variables;
226 if (required_size > literal_to_clauses_.size()) {
227 literal_to_clauses_.resize(required_size);
228 literal_to_clause_sizes_.resize(required_size);
229 }
230}
231
232void SatPresolver::AddClauseInternal(std::vector<Literal>* clause) {
233 if (drat_proof_handler_ != nullptr) drat_proof_handler_->AddClause(*clause);
234
235 DCHECK(std::is_sorted(clause->begin(), clause->end()));
236 DCHECK_GT(clause->size(), 0) << "TODO(user): Unsat during presolve?";
237 const ClauseIndex ci(clauses_.size());
238 clauses_.push_back(std::vector<Literal>());
239 clauses_.back().swap(*clause);
240 in_clause_to_process_.push_back(true);
241 clause_to_process_.push_back(ci);
242 for (const Literal e : clauses_.back()) {
243 literal_to_clauses_[e].push_back(ci);
244 literal_to_clause_sizes_[e]++;
245 UpdatePriorityQueue(e.Variable());
246 UpdateBvaPriorityQueue(e.Index());
247 }
248
249 signatures_.push_back(ComputeSignatureOfClauseVariables(ci));
250 DCHECK_EQ(signatures_.size(), clauses_.size());
251}
252
256 BooleanVariable new_var(0);
257 for (BooleanVariable var(0); var < NumVariables(); ++var) {
258 if (literal_to_clause_sizes_[Literal(var, true)] > 0 ||
259 literal_to_clause_sizes_[Literal(var, false)] > 0) {
260 result.push_back(new_var);
261 ++new_var;
262 } else {
264 }
265 }
266 return result;
267}
268
270 // Cleanup some memory that is not needed anymore. Note that we do need
271 // literal_to_clause_sizes_ for VariableMapping() to work.
272 var_pq_.Clear();
273 var_pq_elements_.clear();
274 in_clause_to_process_.clear();
275 clause_to_process_.clear();
276 literal_to_clauses_.clear();
277 signatures_.clear();
278
281 int new_size = 0;
282 for (BooleanVariable index : mapping) {
283 if (index != kNoBooleanVariable) ++new_size;
284 }
285
286 std::vector<Literal> temp;
287 solver->SetNumVariables(new_size);
288 for (std::vector<Literal>& clause_ref : clauses_) {
289 temp.clear();
290 for (Literal l : clause_ref) {
291 DCHECK_NE(mapping[l.Variable()], kNoBooleanVariable);
292 temp.push_back(Literal(mapping[l.Variable()], l.IsPositive()));
293 }
294 if (!temp.empty()) solver->AddProblemClause(temp);
295 gtl::STLClearObject(&clause_ref);
296 }
297}
298
299bool SatPresolver::ProcessAllClauses() {
300 int num_skipped_checks = 0;
301 const int kCheckFrequency = 1000;
302
303 // Because on large problem we don't have a budget to process all clauses,
304 // lets start by the smallest ones first.
305 std::stable_sort(clause_to_process_.begin(), clause_to_process_.end(),
306 [this](ClauseIndex c1, ClauseIndex c2) {
307 return clauses_[c1].size() < clauses_[c2].size();
308 });
309 while (!clause_to_process_.empty()) {
310 const ClauseIndex ci = clause_to_process_.front();
311 in_clause_to_process_[ci] = false;
312 clause_to_process_.pop_front();
313 if (!ProcessClauseToSimplifyOthers(ci)) return false;
314 if (++num_skipped_checks >= kCheckFrequency) {
315 if (num_inspected_signatures_ + num_inspected_literals_ > 1e9) {
316 VLOG(1) << "Aborting ProcessAllClauses() because work limit has been "
317 "reached";
318 return true;
319 }
320 if (time_limit_ != nullptr && time_limit_->LimitReached()) return true;
321 num_skipped_checks = 0;
322 }
323 }
324 return true;
325}
326
328 // This is slighlty inefficient, but the presolve algorithm is
329 // a lot more costly anyway.
330 std::vector<bool> can_be_removed(NumVariables(), true);
331 return Presolve(can_be_removed);
332}
333
334bool SatPresolver::Presolve(const std::vector<bool>& can_be_removed) {
335 WallTimer timer;
336 timer.Start();
337
338 if (logger_->LoggingIsEnabled()) {
339 int64_t num_removable = 0;
340 for (const bool b : can_be_removed) {
341 if (b) ++num_removable;
342 }
343 SOLVER_LOG(logger_,
344 "[SAT presolve] num removable Booleans: ", num_removable, " / ",
345 can_be_removed.size());
346 SOLVER_LOG(logger_,
347 "[SAT presolve] num trivial clauses: ", num_trivial_clauses_);
348 DisplayStats(0);
349 }
350
351 // TODO(user): When a clause is strengthened, add it to a queue so it can
352 // be processed again?
353 if (!ProcessAllClauses()) return false;
354 if (logger_->LoggingIsEnabled()) DisplayStats(timer.Get());
355
356 if (time_limit_ != nullptr && time_limit_->LimitReached()) return true;
357 if (num_inspected_signatures_ + num_inspected_literals_ > 1e9) return true;
358
359 InitializePriorityQueue();
360 while (var_pq_.Size() > 0) {
361 const BooleanVariable var = var_pq_.Top()->variable;
362 var_pq_.Pop();
363 if (!can_be_removed[var.value()]) continue;
364 if (CrossProduct(Literal(var, true))) {
365 if (!ProcessAllClauses()) return false;
366 }
367 if (time_limit_ != nullptr && time_limit_->LimitReached()) return true;
368 if (num_inspected_signatures_ + num_inspected_literals_ > 1e9) return true;
369 }
370 if (logger_->LoggingIsEnabled()) DisplayStats(timer.Get());
371
372 // We apply BVA after a pass of the other algorithms.
373 if (parameters_.presolve_use_bva()) {
375 if (logger_->LoggingIsEnabled()) DisplayStats(timer.Get());
376 }
377
378 return true;
379}
380
381// TODO(user): Put work limit in place !
383 var_pq_elements_.clear(); // so we don't update it.
384 InitializeBvaPriorityQueue();
385 while (bva_pq_.Size() > 0) {
386 const LiteralIndex lit = bva_pq_.Top()->literal;
387 bva_pq_.Pop();
388 SimpleBva(lit);
389 if (time_limit_ != nullptr && time_limit_->LimitReached()) break;
390 }
391}
392
393// We use the same notation as in the article mentioned in the .h
394void SatPresolver::SimpleBva(LiteralIndex l) {
395 literal_to_p_size_.resize(literal_to_clauses_.size(), 0);
396 DCHECK(std::all_of(literal_to_p_size_.begin(), literal_to_p_size_.end(),
397 [](int v) { return v == 0; }));
398
399 // We will try to add a literal to m_lit_ and take a subset of m_cls_ such
400 // that |m_lit_| * |m_cls_| - |m_lit_| - |m_cls_| is maximized.
401 m_lit_ = {l};
402 m_cls_ = literal_to_clauses_[l];
403
404 int reduction = 0;
405 for (int loop = 0; loop < 100; ++loop) {
406 LiteralIndex lmax = kNoLiteralIndex;
407 int max_size = 0;
408
409 flattened_p_.clear();
410 for (const ClauseIndex c : m_cls_) {
411 const std::vector<Literal>& clause = clauses_[c];
412 if (clause.empty()) continue; // It has been deleted.
413
414 // Find a literal different from l that occur in the less number of
415 // clauses.
416 const LiteralIndex l_min =
417 FindLiteralWithShortestOccurrenceListExcluding(clause, Literal(l));
418 if (l_min == kNoLiteralIndex) continue;
419
420 // Find all the clauses of the form "clause \ {l} + {l'}", for a literal
421 // l' that is not in the clause.
422 for (const ClauseIndex d : literal_to_clauses_[l_min]) {
423 if (clause.size() != clauses_[d].size()) continue;
424 const LiteralIndex l_diff =
425 DifferAtGivenLiteral(clause, clauses_[d], Literal(l));
426 if (l_diff == kNoLiteralIndex || m_lit_.count(l_diff) > 0) continue;
427 if (l_diff == Literal(l).NegatedIndex()) {
428 // Self-subsumbtion!
429 //
430 // TODO(user): Not sure this can happen after the presolve we did
431 // before calling SimpleBva().
432 VLOG(1) << "self-subsumbtion";
433 }
434
435 flattened_p_.push_back({l_diff, c});
436 const int new_size = ++literal_to_p_size_[l_diff];
437 if (new_size > max_size) {
438 lmax = l_diff;
439 max_size = new_size;
440 }
441 }
442 }
443
444 if (lmax == kNoLiteralIndex) break;
445 const int new_m_lit_size = m_lit_.size() + 1;
446 const int new_m_cls_size = max_size;
447 const int new_reduction =
448 new_m_lit_size * new_m_cls_size - new_m_cls_size - new_m_lit_size;
449
450 if (new_reduction <= reduction) break;
451 DCHECK_NE(1, new_m_lit_size);
452 DCHECK_NE(1, new_m_cls_size);
453
454 // TODO(user): Instead of looping and recomputing p_ again, we can instead
455 // simply intersect the clause indices in p_. This should be a lot faster.
456 // That said, we loop again only when we have a reduction, so this happens
457 // not that often compared to the initial computation of p.
458 reduction = new_reduction;
459 m_lit_.insert(lmax);
460
461 // Set m_cls_ to p_[lmax].
462 m_cls_.clear();
463 for (const auto& entry : flattened_p_) {
464 literal_to_p_size_[entry.first] = 0;
465 if (entry.first == lmax) m_cls_.push_back(entry.second);
466 }
467 flattened_p_.clear();
468 }
469
470 // Make sure literal_to_p_size_ is all zero.
471 for (const auto& entry : flattened_p_) literal_to_p_size_[entry.first] = 0;
472 flattened_p_.clear();
473
474 // A strictly positive reduction means that applying the BVA transform will
475 // reduce the overall number of clauses by that much. Here we can control
476 // what kind of reduction we want to apply.
477 if (reduction <= parameters_.presolve_bva_threshold()) return;
478 DCHECK_GT(m_lit_.size(), 1);
479
480 // Create a new variable.
481 const int old_size = literal_to_clauses_.size();
482 const LiteralIndex x_true = LiteralIndex(old_size);
483 const LiteralIndex x_false = LiteralIndex(old_size + 1);
484 literal_to_clauses_.resize(old_size + 2);
485 literal_to_clause_sizes_.resize(old_size + 2);
486 bva_pq_elements_.resize(old_size + 2);
487 bva_pq_elements_[x_true.value()].literal = x_true;
488 bva_pq_elements_[x_false.value()].literal = x_false;
489
490 // Add the new clauses.
491 if (drat_proof_handler_ != nullptr) drat_proof_handler_->AddOneVariable();
492 for (const LiteralIndex lit : m_lit_) {
493 tmp_new_clause_ = {Literal(lit), Literal(x_true)};
494 AddClauseInternal(&tmp_new_clause_);
495 }
496 for (const ClauseIndex ci : m_cls_) {
497 tmp_new_clause_ = clauses_[ci];
498 DCHECK(!tmp_new_clause_.empty());
499 for (Literal& ref : tmp_new_clause_) {
500 if (ref.Index() == l) {
501 ref = Literal(x_false);
502 break;
503 }
504 }
505
506 // TODO(user): we can be more efficient here since the clause used to
507 // derive this one is already sorted. We just need to insert x_false in
508 // the correct place and remove l.
509 std::sort(tmp_new_clause_.begin(), tmp_new_clause_.end());
510 AddClauseInternal(&tmp_new_clause_);
511 }
512
513 // Delete the old clauses.
514 //
515 // TODO(user): do that more efficiently? we can simply store the clause d
516 // instead of finding it again. That said, this is executed only when a
517 // reduction occur, whereas the start of this function occur all the time, so
518 // we want it to be as fast as possible.
519 for (const ClauseIndex c : m_cls_) {
520 const std::vector<Literal>& clause = clauses_[c];
521 DCHECK(!clause.empty());
522 const LiteralIndex l_min =
523 FindLiteralWithShortestOccurrenceListExcluding(clause, Literal(l));
524 for (const LiteralIndex lit : m_lit_) {
525 if (lit == l) continue;
526 for (const ClauseIndex d : literal_to_clauses_[l_min]) {
527 if (clause.size() != clauses_[d].size()) continue;
528 const LiteralIndex l_diff =
529 DifferAtGivenLiteral(clause, clauses_[d], Literal(l));
530 if (l_diff == lit) {
531 Remove(d);
532 break;
533 }
534 }
535 }
536 Remove(c);
537 }
538
539 // Add these elements to the priority queue.
540 //
541 // TODO(user): It seems some of the element already processed could benefit
542 // from being processed again by SimpleBva(). It is unclear if it is worth the
543 // extra time though.
544 AddToBvaPriorityQueue(x_true);
545 AddToBvaPriorityQueue(x_false);
546 AddToBvaPriorityQueue(l);
547}
548
549uint64_t SatPresolver::ComputeSignatureOfClauseVariables(ClauseIndex ci) {
550 uint64_t signature = 0;
551 for (const Literal l : clauses_[ci]) {
552 signature |= (uint64_t{1} << (l.Variable().value() % 64));
553 }
554 DCHECK_EQ(signature == 0, clauses_[ci].empty());
555 return signature;
556}
557
558// We are looking for clause that contains lit and contains a superset of the
559// literals in clauses_[clauses_index] or a superset with just one literal of
560// clauses_[clause_index] negated.
561bool SatPresolver::ProcessClauseToSimplifyOthersUsingLiteral(
562 ClauseIndex clause_index, Literal lit) {
563 const std::vector<Literal>& clause = clauses_[clause_index];
564 const uint64_t clause_signature = signatures_[clause_index];
565 LiteralIndex opposite_literal;
566
567 // Try to simplify the clauses containing 'lit'. We take advantage of this
568 // loop to also detect if there is any empty clause, in which case we will
569 // trigger a "cleaning" below.
570 bool need_cleaning = false;
571 num_inspected_signatures_ += literal_to_clauses_[lit].size();
572 for (const ClauseIndex ci : literal_to_clauses_[lit]) {
573 const uint64_t ci_signature = signatures_[ci];
574
575 // This allows to check for empty clause without fetching the memory at
576 // clause_[ci]. It can have a huge time impact on large problems.
577 DCHECK_EQ(ci_signature, ComputeSignatureOfClauseVariables(ci));
578 if (ci_signature == 0) {
579 need_cleaning = true;
580 continue;
581 }
582
583 // Note that SimplifyClause() can return true only if the variables in
584 // 'a' are a subset of the one in 'b'. We use the signatures to abort
585 // early as a speed optimization.
586 if (ci != clause_index && (clause_signature & ~ci_signature) == 0 &&
587 SimplifyClause(clause, &clauses_[ci], &opposite_literal,
588 &num_inspected_literals_)) {
589 if (opposite_literal == kNoLiteralIndex) {
590 need_cleaning = true;
591 Remove(ci);
592 continue;
593 } else {
594 DCHECK_NE(opposite_literal, lit.Index());
595 if (clauses_[ci].empty()) return false; // UNSAT.
596 if (drat_proof_handler_ != nullptr) {
597 // TODO(user): remove the old clauses_[ci] afterwards.
598 drat_proof_handler_->AddClause(clauses_[ci]);
599 }
600
601 // Recompute signature.
602 signatures_[ci] = ComputeSignatureOfClauseVariables(ci);
603
604 // Remove ci from the occurrence list. Note that opposite_literal
605 // cannot be literal or literal.Negated().
606 gtl::STLEraseAllFromSequence(&literal_to_clauses_[opposite_literal],
607 ci);
608 --literal_to_clause_sizes_[opposite_literal];
609 UpdatePriorityQueue(Literal(opposite_literal).Variable());
610
611 if (!in_clause_to_process_[ci]) {
612 in_clause_to_process_[ci] = true;
613 clause_to_process_.push_back(ci);
614 }
615 }
616 }
617 }
618
619 if (need_cleaning) {
620 int new_index = 0;
621 auto& occurrence_list_ref = literal_to_clauses_[lit];
622 for (const ClauseIndex ci : occurrence_list_ref) {
623 if (signatures_[ci] != 0) occurrence_list_ref[new_index++] = ci;
624 }
625 occurrence_list_ref.resize(new_index);
626 DCHECK_EQ(literal_to_clause_sizes_[lit], new_index);
627 }
628
629 return true;
630}
631
632// TODO(user): Binary clauses are really common, and we can probably do this
633// more efficiently for them. For instance, we could just take the intersection
634// of two sorted lists to get the simplified clauses.
636 const std::vector<Literal>& clause = clauses_[clause_index];
637 if (clause.empty()) return true;
638 DCHECK(std::is_sorted(clause.begin(), clause.end()));
639
640 LiteralIndex opposite_literal;
641 const Literal lit = FindLiteralWithShortestOccurrenceList(clause);
642
643 if (!ProcessClauseToSimplifyOthersUsingLiteral(clause_index, lit)) {
644 return false;
645 }
646
647 // If there is another "short" occurrence list, use it. Otherwise use the
648 // one corresponding to the negation of lit.
649 const LiteralIndex other_lit_index =
650 FindLiteralWithShortestOccurrenceListExcluding(clause, lit);
651 if (other_lit_index != kNoLiteralIndex &&
652 literal_to_clause_sizes_[other_lit_index] <
653 literal_to_clause_sizes_[lit.NegatedIndex()]) {
654 return ProcessClauseToSimplifyOthersUsingLiteral(clause_index,
655 Literal(other_lit_index));
656 } else {
657 // Treat the clauses containing lit.Negated().
658 int new_index = 0;
659 bool something_removed = false;
660 auto& occurrence_list_ref = literal_to_clauses_[lit.NegatedIndex()];
661 const uint64_t clause_signature = signatures_[clause_index];
662 for (const ClauseIndex ci : occurrence_list_ref) {
663 const uint64_t ci_signature = signatures_[ci];
664 DCHECK_EQ(ci_signature, ComputeSignatureOfClauseVariables(ci));
665 if (ci_signature == 0) continue;
666
667 // TODO(user): not super optimal since we could abort earlier if
668 // opposite_literal is not the negation of shortest_list. Note that this
669 // applies to the second call to
670 // ProcessClauseToSimplifyOthersUsingLiteral() above too.
671 if ((clause_signature & ~ci_signature) == 0 &&
672 SimplifyClause(clause, &clauses_[ci], &opposite_literal,
673 &num_inspected_literals_)) {
674 DCHECK_EQ(opposite_literal, lit.NegatedIndex());
675 if (clauses_[ci].empty()) return false; // UNSAT.
676 if (drat_proof_handler_ != nullptr) {
677 // TODO(user): remove the old clauses_[ci] afterwards.
678 drat_proof_handler_->AddClause(clauses_[ci]);
679 }
680
681 // Recompute signature.
682 signatures_[ci] = ComputeSignatureOfClauseVariables(ci);
683
684 if (!in_clause_to_process_[ci]) {
685 in_clause_to_process_[ci] = true;
686 clause_to_process_.push_back(ci);
687 }
688 something_removed = true;
689 continue;
690 }
691 occurrence_list_ref[new_index] = ci;
692 ++new_index;
693 }
694 occurrence_list_ref.resize(new_index);
695 literal_to_clause_sizes_[lit.NegatedIndex()] = new_index;
696 if (something_removed) {
697 UpdatePriorityQueue(Literal(lit.NegatedIndex()).Variable());
698 }
699 }
700 return true;
701}
702
703void SatPresolver::RemoveAndRegisterForPostsolveAllClauseContaining(Literal x) {
704 for (ClauseIndex i : literal_to_clauses_[x]) {
705 if (!clauses_[i].empty()) RemoveAndRegisterForPostsolve(i, x);
706 }
707 gtl::STLClearObject(&literal_to_clauses_[x]);
708 literal_to_clause_sizes_[x] = 0;
709}
710
712 const int s1 = literal_to_clause_sizes_[x];
713 const int s2 = literal_to_clause_sizes_[x.NegatedIndex()];
714
715 // Note that if s1 or s2 is equal to 0, this function will implicitly just
716 // fix the variable x.
717 if (s1 == 0 && s2 == 0) return false;
718
719 // Heuristic. Abort if the work required to decide if x should be removed
720 // seems to big.
721 if (s1 > 1 && s2 > 1 && s1 * s2 > parameters_.presolve_bve_threshold()) {
722 return false;
723 }
724
725 // Compute the threshold under which we don't remove x.Variable().
726 int threshold = 0;
727 const int clause_weight = parameters_.presolve_bve_clause_weight();
728 for (ClauseIndex i : literal_to_clauses_[x]) {
729 if (!clauses_[i].empty()) {
730 threshold += clause_weight + clauses_[i].size();
731 }
732 }
733 for (ClauseIndex i : literal_to_clauses_[x.NegatedIndex()]) {
734 if (!clauses_[i].empty()) {
735 threshold += clause_weight + clauses_[i].size();
736 }
737 }
738
739 // For the BCE, we prefer s2 to be small.
740 if (s1 < s2) x = x.Negated();
741
742 // Test whether we should remove the x.Variable().
743 int size = 0;
744 for (ClauseIndex i : literal_to_clauses_[x]) {
745 if (clauses_[i].empty()) continue;
746 bool no_resolvant = true;
747 for (ClauseIndex j : literal_to_clauses_[x.NegatedIndex()]) {
748 if (clauses_[j].empty()) continue;
749 const int rs = ComputeResolvantSize(x, clauses_[i], clauses_[j]);
750 if (rs >= 0) {
751 no_resolvant = false;
752 size += clause_weight + rs;
753
754 // Abort early if the "size" become too big.
755 if (size > threshold) return false;
756 }
757 }
758 if (no_resolvant && parameters_.presolve_blocked_clause()) {
759 // This is an incomplete heuristic for blocked clause detection. Here,
760 // the clause i is "blocked", so we can remove it. Note that the code
761 // below already do that if we decide to eliminate x.
762 //
763 // For more details, see the paper "Blocked clause elimination", Matti
764 // Jarvisalo, Armin Biere, Marijn Heule. TACAS, volume 6015 of Lecture
765 // Notes in Computer Science, pages 129–144. Springer, 2010.
766 //
767 // TODO(user): Choose if we use x or x.Negated() depending on the list
768 // sizes? The function achieve the same if x = x.Negated(), however the
769 // loops are not done in the same order which may change this incomplete
770 // "blocked" clause detection.
771 RemoveAndRegisterForPostsolve(i, x);
772 }
773 }
774
775 // Add all the resolvant clauses.
776 // Note that the variable priority queue will only be updated during the
777 // deletion.
778 std::vector<Literal> temp;
779 for (ClauseIndex i : literal_to_clauses_[x]) {
780 if (clauses_[i].empty()) continue;
781 for (ClauseIndex j : literal_to_clauses_[x.NegatedIndex()]) {
782 if (clauses_[j].empty()) continue;
783 if (ComputeResolvant(x, clauses_[i], clauses_[j], &temp)) {
784 AddClauseInternal(&temp);
785 }
786 }
787 }
788
789 // Deletes the old clauses.
790 //
791 // TODO(user): We could only update the priority queue once for each variable
792 // instead of doing it many times.
793 RemoveAndRegisterForPostsolveAllClauseContaining(x);
794 RemoveAndRegisterForPostsolveAllClauseContaining(x.Negated());
795
796 // TODO(user): At this point x.Variable() is added back to the priority queue.
797 // Avoid doing that.
798 return true;
799}
800
801void SatPresolver::Remove(ClauseIndex ci) {
802 signatures_[ci] = 0;
803 for (Literal e : clauses_[ci]) {
804 literal_to_clause_sizes_[e]--;
805 UpdatePriorityQueue(e.Variable());
806 UpdateBvaPriorityQueue(Literal(e.Variable(), true).Index());
807 UpdateBvaPriorityQueue(Literal(e.Variable(), false).Index());
808 }
809 if (drat_proof_handler_ != nullptr) {
810 drat_proof_handler_->DeleteClause(clauses_[ci]);
811 }
812 gtl::STLClearObject(&clauses_[ci]);
813}
814
815void SatPresolver::RemoveAndRegisterForPostsolve(ClauseIndex ci, Literal x) {
816 postsolver_->Add(x, clauses_[ci]);
817 Remove(ci);
818}
819
820Literal SatPresolver::FindLiteralWithShortestOccurrenceList(
821 const std::vector<Literal>& clause) {
822 DCHECK(!clause.empty());
823 Literal result = clause.front();
824 int best_size = literal_to_clause_sizes_[result];
825 for (const Literal l : clause) {
826 const int size = literal_to_clause_sizes_[l];
827 if (size < best_size) {
828 result = l;
829 best_size = size;
830 }
831 }
832 return result;
833}
834
835LiteralIndex SatPresolver::FindLiteralWithShortestOccurrenceListExcluding(
836 const std::vector<Literal>& clause, Literal to_exclude) {
837 DCHECK(!clause.empty());
838 LiteralIndex result = kNoLiteralIndex;
839 int num_occurrences = std::numeric_limits<int>::max();
840 for (const Literal l : clause) {
841 if (l == to_exclude) continue;
842 if (literal_to_clause_sizes_[l] < num_occurrences) {
843 result = l.Index();
844 num_occurrences = literal_to_clause_sizes_[l];
845 }
846 }
847 return result;
848}
849
850void SatPresolver::UpdatePriorityQueue(BooleanVariable var) {
851 if (var_pq_elements_.empty()) return; // not initialized.
852 PQElement* element = &var_pq_elements_[var];
853 element->weight = literal_to_clause_sizes_[Literal(var, true)] +
854 literal_to_clause_sizes_[Literal(var, false)];
855 if (var_pq_.Contains(element)) {
856 var_pq_.NoteChangedPriority(element);
857 } else {
858 var_pq_.Add(element);
859 }
860}
861
862void SatPresolver::InitializePriorityQueue() {
863 const int num_vars = NumVariables();
864 var_pq_elements_.resize(num_vars);
865 for (BooleanVariable var(0); var < num_vars; ++var) {
866 PQElement* element = &var_pq_elements_[var];
867 element->variable = var;
868 element->weight = literal_to_clause_sizes_[Literal(var, true)] +
869 literal_to_clause_sizes_[Literal(var, false)];
870 var_pq_.Add(element);
871 }
872}
873
874void SatPresolver::UpdateBvaPriorityQueue(LiteralIndex lit) {
875 if (bva_pq_elements_.empty()) return; // not initialized.
876 DCHECK_LT(lit, bva_pq_elements_.size());
877 BvaPqElement* element = &bva_pq_elements_[lit.value()];
878 element->weight = literal_to_clause_sizes_[lit];
879 if (bva_pq_.Contains(element)) {
880 bva_pq_.NoteChangedPriority(element);
881 }
882}
883
884void SatPresolver::AddToBvaPriorityQueue(LiteralIndex lit) {
885 if (bva_pq_elements_.empty()) return; // not initialized.
886 DCHECK_LT(lit, bva_pq_elements_.size());
887 BvaPqElement* element = &bva_pq_elements_[lit.value()];
888 element->weight = literal_to_clause_sizes_[lit];
889 DCHECK(!bva_pq_.Contains(element));
890 if (element->weight > 2) bva_pq_.Add(element);
891}
892
893void SatPresolver::InitializeBvaPriorityQueue() {
894 const int num_literals = 2 * NumVariables();
895 bva_pq_.Clear();
896 bva_pq_elements_.assign(num_literals, BvaPqElement());
897 for (LiteralIndex lit(0); lit < num_literals; ++lit) {
898 BvaPqElement* element = &bva_pq_elements_[lit.value()];
899 element->literal = lit;
900 element->weight = literal_to_clause_sizes_[lit];
901
902 // If a literal occur only in two clauses, then there is no point calling
903 // SimpleBva() on it.
904 if (element->weight > 2) bva_pq_.Add(element);
905 }
906}
907
908void SatPresolver::DisplayStats(double elapsed_seconds) {
909 int num_literals = 0;
910 int num_clauses = 0;
911 int num_singleton_clauses = 0;
912 for (const std::vector<Literal>& c : clauses_) {
913 if (!c.empty()) {
914 if (c.size() == 1) ++num_singleton_clauses;
915 ++num_clauses;
916 num_literals += c.size();
917 }
918 }
919 int num_one_side = 0;
920 int num_simple_definition = 0;
921 int num_vars = 0;
922 for (BooleanVariable var(0); var < NumVariables(); ++var) {
923 const int s1 = literal_to_clause_sizes_[Literal(var, true)];
924 const int s2 = literal_to_clause_sizes_[Literal(var, false)];
925 if (s1 == 0 && s2 == 0) continue;
926
927 ++num_vars;
928 if (s1 == 0 || s2 == 0) {
929 num_one_side++;
930 } else if (s1 == 1 || s2 == 1) {
931 num_simple_definition++;
932 }
933 }
934 SOLVER_LOG(logger_, "[SAT presolve] [", elapsed_seconds, "s]",
935 " clauses:", num_clauses, " literals:", num_literals,
936 " vars:", num_vars, " one_side_vars:", num_one_side,
937 " simple_definition:", num_simple_definition,
938 " singleton_clauses:", num_singleton_clauses);
939}
940
941bool SimplifyClause(const std::vector<Literal>& a, std::vector<Literal>* b,
942 LiteralIndex* opposite_literal,
943 int64_t* num_inspected_literals) {
944 if (b->size() < a.size()) return false;
945 DCHECK(std::is_sorted(a.begin(), a.end()));
946 DCHECK(std::is_sorted(b->begin(), b->end()));
947 if (num_inspected_literals != nullptr) {
948 *num_inspected_literals += a.size();
949 *num_inspected_literals += b->size();
950 }
951
952 *opposite_literal = LiteralIndex(-1);
953
954 int num_diff = 0;
955 std::vector<Literal>::const_iterator ia = a.begin();
956 std::vector<Literal>::const_iterator ib = b->begin();
957 std::vector<Literal>::const_iterator to_remove;
958
959 // Because we abort early when size_diff becomes negative, the second test
960 // in the while loop is not needed.
961 int size_diff = b->size() - a.size();
962 while (ia != a.end() /* && ib != b->end() */) {
963 if (*ia == *ib) { // Same literal.
964 ++ia;
965 ++ib;
966 } else if (*ia == ib->Negated()) { // Opposite literal.
967 ++num_diff;
968 if (num_diff > 1) return false; // Too much difference.
969 to_remove = ib;
970 ++ia;
971 ++ib;
972 } else if (*ia < *ib) {
973 return false; // A literal of a is not in b.
974 } else { // *ia > *ib
975 ++ib;
976
977 // A literal of b is not in a, we can abort early by comparing the sizes
978 // left.
979 if (--size_diff < 0) return false;
980 }
981 }
982 if (num_diff == 1) {
983 *opposite_literal = to_remove->Index();
984 b->erase(to_remove);
985 }
986 return true;
987}
988
989LiteralIndex DifferAtGivenLiteral(const std::vector<Literal>& a,
990 const std::vector<Literal>& b, Literal l) {
991 DCHECK_EQ(b.size(), a.size());
992 DCHECK(std::is_sorted(a.begin(), a.end()));
993 DCHECK(std::is_sorted(b.begin(), b.end()));
994 LiteralIndex result = kNoLiteralIndex;
995 std::vector<Literal>::const_iterator ia = a.begin();
996 std::vector<Literal>::const_iterator ib = b.begin();
997 while (ia != a.end() && ib != b.end()) {
998 if (*ia == *ib) { // Same literal.
999 ++ia;
1000 ++ib;
1001 } else if (*ia < *ib) {
1002 // A literal of a is not in b, it must be l.
1003 // Note that this can only happen once.
1004 if (*ia != l) return kNoLiteralIndex;
1005 ++ia;
1006 } else {
1007 // A literal of b is not in a, save it.
1008 // We abort if this happen twice.
1009 if (result != kNoLiteralIndex) return kNoLiteralIndex;
1010 result = (*ib).Index();
1011 ++ib;
1012 }
1013 }
1014 // Check the corner case of the difference at the end.
1015 if (ia != a.end() && *ia != l) return kNoLiteralIndex;
1016 if (ib != b.end()) {
1017 if (result != kNoLiteralIndex) return kNoLiteralIndex;
1018 result = (*ib).Index();
1019 }
1020 return result;
1021}
1022
1023bool ComputeResolvant(Literal x, const std::vector<Literal>& a,
1024 const std::vector<Literal>& b,
1025 std::vector<Literal>* out) {
1026 DCHECK(std::is_sorted(a.begin(), a.end()));
1027 DCHECK(std::is_sorted(b.begin(), b.end()));
1028
1029 out->clear();
1030 std::vector<Literal>::const_iterator ia = a.begin();
1031 std::vector<Literal>::const_iterator ib = b.begin();
1032 while ((ia != a.end()) && (ib != b.end())) {
1033 if (*ia == *ib) {
1034 out->push_back(*ia);
1035 ++ia;
1036 ++ib;
1037 } else if (*ia == ib->Negated()) {
1038 if (*ia != x) return false; // Trivially true.
1039 DCHECK_EQ(*ib, x.Negated());
1040 ++ia;
1041 ++ib;
1042 } else if (*ia < *ib) {
1043 out->push_back(*ia);
1044 ++ia;
1045 } else { // *ia > *ib
1046 out->push_back(*ib);
1047 ++ib;
1048 }
1049 }
1050
1051 // Copy remaining literals.
1052 out->insert(out->end(), ia, a.end());
1053 out->insert(out->end(), ib, b.end());
1054 return true;
1055}
1056
1057// Note that this function takes a big chunk of the presolve running time.
1058int ComputeResolvantSize(Literal x, const std::vector<Literal>& a,
1059 const std::vector<Literal>& b) {
1060 DCHECK(std::is_sorted(a.begin(), a.end()));
1061 DCHECK(std::is_sorted(b.begin(), b.end()));
1062
1063 int size = static_cast<int>(a.size() + b.size()) - 2;
1064 std::vector<Literal>::const_iterator ia = a.begin();
1065 std::vector<Literal>::const_iterator ib = b.begin();
1066 while ((ia != a.end()) && (ib != b.end())) {
1067 if (*ia == *ib) {
1068 --size;
1069 ++ia;
1070 ++ib;
1071 } else if (*ia == ib->Negated()) {
1072 if (*ia != x) return -1; // Trivially true.
1073 DCHECK_EQ(*ib, x.Negated());
1074 ++ia;
1075 ++ib;
1076 } else if (*ia < *ib) {
1077 ++ia;
1078 } else { // *ia > *ib
1079 ++ib;
1080 }
1081 }
1082 DCHECK_GE(size, 0);
1083 return size;
1084}
1085
1086// A simple graph where the nodes are the literals and the nodes adjacent to a
1087// literal l are the propagated literal when l is assigned in the underlying
1088// sat solver.
1089//
1090// This can be used to do a strong component analysis while probing all the
1091// literals of a solver. Note that this can be expensive, hence the support
1092// for a deterministic time limit.
1094 public:
1095 PropagationGraph(double deterministic_time_limit, SatSolver* solver)
1096 : solver_(solver),
1097 deterministic_time_limit(solver->deterministic_time() +
1098 deterministic_time_limit) {}
1099
1100 // This type is neither copyable nor movable.
1103
1104 // Returns the set of node adjacent to the given one.
1105 // Interface needed by FindStronglyConnectedComponents(), note that it needs
1106 // to be const.
1107 const std::vector<int32_t>& operator[](int32_t index) const {
1108 scratchpad_.clear();
1109 solver_->Backtrack(0);
1110
1111 // Note that when the time limit is reached, we just keep returning empty
1112 // adjacency list. This way, the SCC algorithm will terminate quickly and
1113 // the equivalent literals detection will be incomplete but correct. Note
1114 // also that thanks to the SCC algorithm, we will explore the connected
1115 // components first.
1116 if (solver_->deterministic_time() > deterministic_time_limit) {
1117 return scratchpad_;
1118 }
1119
1120 const Literal l = Literal(LiteralIndex(index));
1121 if (!solver_->Assignment().LiteralIsAssigned(l)) {
1122 const int trail_index = solver_->LiteralTrail().Index();
1123 solver_->EnqueueDecisionAndBackjumpOnConflict(l);
1124 if (solver_->CurrentDecisionLevel() > 0) {
1125 // Note that the +1 is to avoid adding a => a.
1126 for (int i = trail_index + 1; i < solver_->LiteralTrail().Index();
1127 ++i) {
1128 scratchpad_.push_back(solver_->LiteralTrail()[i].Index().value());
1129 }
1130 }
1131 }
1132 return scratchpad_;
1133 }
1134
1135 private:
1136 mutable std::vector<int32_t> scratchpad_;
1137 SatSolver* const solver_;
1138 const double deterministic_time_limit;
1139};
1140
1142 SatSolver* solver, SatPostsolver* postsolver,
1143 DratProofHandler* drat_proof_handler,
1145 SolverLogger* logger) {
1146 WallTimer timer;
1147 timer.Start();
1148
1149 solver->Backtrack(0);
1150 mapping->clear();
1151 const int num_already_fixed_vars = solver->LiteralTrail().Index();
1152
1153 PropagationGraph graph(
1154 solver->parameters().presolve_probing_deterministic_time_limit(), solver);
1155 const int32_t size = solver->NumVariables() * 2;
1156 std::vector<std::vector<int32_t>> scc;
1157 FindStronglyConnectedComponents(size, graph, &scc);
1158
1159 // We have no guarantee that the cycle of x and not(x) touch the same
1160 // variables. This is because we may have more info for the literal probed
1161 // later or the propagation may go only in one direction. For instance if we
1162 // have two clauses (not(x1) v x2) and (not(x1) v not(x2) v x3) then x1
1163 // implies x2 and x3 but not(x3) doesn't imply anything by unit propagation.
1164 //
1165 // TODO(user): Add some constraint so that it does?
1166 //
1167 // Because of this, we "merge" the cycles.
1168 MergingPartition partition(size);
1169 for (const std::vector<int32_t>& component : scc) {
1170 if (component.size() > 1) {
1171 if (mapping->empty()) mapping->resize(size, LiteralIndex(-1));
1172 const Literal representative((LiteralIndex(component[0])));
1173 for (int i = 1; i < component.size(); ++i) {
1174 const Literal l((LiteralIndex(component[i])));
1175 // TODO(user): check compatibility? if x ~ not(x) => unsat.
1176 // but probably, the solver would have found this too? not sure...
1177 partition.MergePartsOf(representative.Index().value(),
1178 l.Index().value());
1179 partition.MergePartsOf(representative.NegatedIndex().value(),
1180 l.NegatedIndex().value());
1181 }
1182
1183 // We rely on the fact that the representative of a literal x and the one
1184 // of its negation are the same variable.
1185 DCHECK_EQ(Literal(LiteralIndex(partition.GetRootAndCompressPath(
1186 representative.Index().value()))),
1187 Literal(LiteralIndex(partition.GetRootAndCompressPath(
1188 representative.NegatedIndex().value())))
1189 .Negated());
1190 }
1191 }
1192
1193 solver->Backtrack(0);
1194 int num_equiv = 0;
1195 if (!mapping->empty()) {
1196 // If a variable in a cycle is fixed. We want to fix all of them.
1197 //
1198 // We first fix all representative if one variable of the cycle is fixed. In
1199 // a second pass we fix all the variable of a cycle whose representative is
1200 // fixed.
1201 //
1202 // TODO(user): Fixing a variable might fix more of them by propagation, so
1203 // we might not fix everything possible with these loops.
1204 const VariablesAssignment& assignment = solver->Assignment();
1205 for (LiteralIndex i(0); i < size; ++i) {
1206 const LiteralIndex rep(partition.GetRootAndCompressPath(i.value()));
1207 if (assignment.LiteralIsAssigned(Literal(i)) &&
1208 !assignment.LiteralIsAssigned(Literal(rep))) {
1209 const Literal true_lit = assignment.LiteralIsTrue(Literal(i))
1210 ? Literal(rep)
1211 : Literal(rep).Negated();
1212 if (!solver->AddUnitClause(true_lit)) return;
1213 if (drat_proof_handler != nullptr) {
1214 drat_proof_handler->AddClause({true_lit});
1215 }
1216 }
1217 }
1218 for (LiteralIndex i(0); i < size; ++i) {
1219 const LiteralIndex rep(partition.GetRootAndCompressPath(i.value()));
1220 (*mapping)[i] = rep;
1221 if (assignment.LiteralIsAssigned(Literal(rep))) {
1222 if (!assignment.LiteralIsAssigned(Literal(i))) {
1223 const Literal true_lit = assignment.LiteralIsTrue(Literal(rep))
1224 ? Literal(i)
1225 : Literal(i).Negated();
1226 if (!solver->AddUnitClause(true_lit)) return;
1227 if (drat_proof_handler != nullptr) {
1228 drat_proof_handler->AddClause({true_lit});
1229 }
1230 }
1231 } else if (assignment.LiteralIsAssigned(Literal(i))) {
1232 if (!assignment.LiteralIsAssigned(Literal(rep))) {
1233 const Literal true_lit = assignment.LiteralIsTrue(Literal(i))
1234 ? Literal(rep)
1235 : Literal(rep).Negated();
1236 if (!solver->AddUnitClause(true_lit)) return;
1237 if (drat_proof_handler != nullptr) {
1238 drat_proof_handler->AddClause({true_lit});
1239 }
1240 }
1241 } else if (rep != i) {
1242 ++num_equiv;
1243 postsolver->Add(Literal(i), {Literal(i), Literal(rep).Negated()});
1244 if (drat_proof_handler != nullptr) {
1245 drat_proof_handler->AddClause({Literal(i), Literal(rep).Negated()});
1246 }
1247 }
1248 }
1249 }
1250
1251 if (logger != nullptr) {
1252 SOLVER_LOG(logger, "[Pure SAT probing] fixed ", num_already_fixed_vars,
1253 " + ", solver->LiteralTrail().Index() - num_already_fixed_vars,
1254 " equiv ", num_equiv / 2, " total ", solver->NumVariables(),
1255 " wtime: ", timer.Get());
1256 } else {
1257 const bool log_info =
1258 solver->parameters().log_search_progress() || VLOG_IS_ON(1);
1259 LOG_IF(INFO, log_info) << "Probing. fixed " << num_already_fixed_vars
1260 << " + "
1261 << solver->LiteralTrail().Index() -
1262 num_already_fixed_vars
1263 << " equiv " << num_equiv / 2 << " total "
1264 << solver->NumVariables()
1265 << " wtime: " << timer.Get();
1266 }
1267}
1268
1269} // namespace sat
1270} // namespace operations_research
double Get() const
Definition timer.h:46
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:32
void AddClause(absl::Span< const Literal > clause)
void DeleteClause(absl::Span< const Literal > clause)
LiteralIndex NegatedIndex() const
Definition sat_base.h:92
LiteralIndex Index() const
Definition sat_base.h:91
BooleanVariable Variable() const
Definition sat_base.h:87
PropagationGraph & operator=(const PropagationGraph &)=delete
const std::vector< int32_t > & operator[](int32_t index) const
PropagationGraph(const PropagationGraph &)=delete
This type is neither copyable nor movable.
PropagationGraph(double deterministic_time_limit, SatSolver *solver)
const VariablesAssignment & assignment()
std::vector< bool > PostsolveSolution(const std::vector< bool > &solution)
void ApplyMapping(const util_intops::StrongVector< BooleanVariable, BooleanVariable > &mapping)
void Add(Literal x, absl::Span< const Literal > clause)
std::vector< bool > ExtractAndPostsolveSolution(const SatSolver &solver)
void AddBinaryClause(Literal a, Literal b)
void SetNumVariables(int num_variables)
Adds new clause to the SatPresolver.
bool ProcessClauseToSimplifyOthers(ClauseIndex clause_index)
util_intops::StrongVector< BooleanVariable, BooleanVariable > VariableMapping() const
void AddClause(absl::Span< const Literal > clause)
void LoadProblemIntoSatSolver(SatSolver *solver)
void PresolveWithBva()
Visible for testing. Just applies the BVA step of the presolve.
bool AddProblemClause(absl::Span< const Literal > literals)
void Backtrack(int target_level)
const SatParameters & parameters() const
ABSL_MUST_USE_RESULT bool AddUnitClause(Literal true_literal)
const VariablesAssignment & Assignment() const
Definition sat_solver.h:393
const Trail & LiteralTrail() const
Definition sat_solver.h:392
void SetNumVariables(int num_variables)
Definition sat_solver.cc:84
bool VariableIsAssigned(BooleanVariable var) const
Returns true iff the given variable is assigned.
Definition sat_base.h:196
bool LiteralIsAssigned(Literal literal) const
Definition sat_base.h:191
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:188
void push_back(const value_type &val)
void resize(size_type new_size)
void STLEraseAllFromSequence(T *v, const E &e)
Definition stl_util.h:93
void STLClearObject(T *obj)
Definition stl_util.h:123
const LiteralIndex kNoLiteralIndex(-1)
LiteralIndex DifferAtGivenLiteral(const std::vector< Literal > &a, const std::vector< Literal > &b, Literal l)
void ProbeAndFindEquivalentLiteral(SatSolver *solver, SatPostsolver *postsolver, DratProofHandler *drat_proof_handler, util_intops::StrongVector< LiteralIndex, LiteralIndex > *mapping, SolverLogger *logger)
int ComputeResolvantSize(Literal x, const std::vector< Literal > &a, const std::vector< Literal > &b)
bool SimplifyClause(const std::vector< Literal > &a, std::vector< Literal > *b, LiteralIndex *opposite_literal, int64_t *num_inspected_literals)
bool ComputeResolvant(Literal x, const std::vector< Literal > &a, const std::vector< Literal > &b, std::vector< Literal > *out)
const BooleanVariable kNoBooleanVariable(-1)
In SWIG mode, we don't want anything besides these top-level includes.
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
void FindStronglyConnectedComponents(NodeIndex num_nodes, const Graph &graph, SccOutput *components)
Simple wrapper function for most usage.
#define SOLVER_LOG(logger,...)
Definition logging.h:109