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