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