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