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