Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
sat_inprocessing.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cmath>
18#include <cstdint>
19#include <deque>
20#include <limits>
21#include <utility>
22#include <vector>
23
24#include "absl/algorithm/container.h"
25#include "absl/cleanup/cleanup.h"
26#include "absl/container/inlined_vector.h"
27#include "absl/log/check.h"
28#include "absl/log/log.h"
29#include "absl/log/vlog_is_on.h"
30#include "absl/types/span.h"
34#include "ortools/base/timer.h"
35#include "ortools/sat/clause.h"
38#include "ortools/sat/probing.h"
43#include "ortools/util/bitset.h"
48
49namespace operations_research {
50namespace sat {
51
53 Literal literal, absl::Span<const Literal> clause) {
54 bool found = false;
55 clauses.emplace_back(clause.begin(), clause.end());
56 for (int i = 0; i < clause.size(); ++i) {
57 if (clause[i] == literal) {
58 found = true;
59 std::swap(clauses.back()[0], clauses.back()[i]);
60 break;
61 }
62 }
63 CHECK(found);
64}
65
66#define RETURN_IF_FALSE(f) \
67 if (!(f)) return false;
68
70 WallTimer wall_timer;
71 wall_timer.Start();
72
73 // Mainly useful for development.
74 double probing_time = 0.0;
75 const bool log_round_info = VLOG_IS_ON(1);
76
77 // We currently do the transformations in a given order and restart each time
78 // we did something to make sure that the earlier step cannot strengthen more.
79 // This might not be the best, but it is really good during development phase
80 // to make sure each individual functions is as incremental and as fast as
81 // possible.
82 const double start_dtime = time_limit_->GetElapsedDeterministicTime();
83 const double stop_dtime = start_dtime + options.deterministic_time_limit;
84 while (!time_limit_->LimitReached() &&
85 time_limit_->GetElapsedDeterministicTime() <= stop_dtime) {
86 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
87 if (!LevelZeroPropagate()) return false;
88
89 // This one is fast since only newly fixed variables are considered.
90 implication_graph_->RemoveFixedVariables();
91 implication_graph_->RemoveDuplicates();
92
93 // This also prepare the stamping below so that we do that on a DAG and do
94 // not consider potential new implications added by
95 // RemoveFixedAndEquivalentVariables().
97 log_round_info));
98
99 // TODO(user): This should/could be integrated with the stamping since it
100 // seems better to do just one loop instead of two over all clauses. Because
101 // of memory access. it isn't that clear though.
103
104 // IMPORTANT: Since we only run this on pure sat problem, we can just
105 // get rid of equivalent variable right away and do not need to keep them
106 // in the implication_graph_ for propagation.
107 //
108 // This is needed for the correctness of the bounded variable elimination.
109 implication_graph_->RemoveAllRedundantVariables(&postsolve_->clauses);
110
111 RETURN_IF_FALSE(stamping_simplifier_->DoOneRound(log_round_info));
112
113 // We wait for the fix-point to be reached before doing the other
114 // simplifications below.
116 !implication_graph_->IsDag()) {
117 continue;
118 }
119
122 !implication_graph_->IsDag()) {
123 continue;
124 }
125
126 // TODO(user): Combine the two? this way we don't create a full literal <->
127 // clause graph twice. It might make sense to reach the BCE fix point which
128 // is unique before each variable elimination.
129 if (!params_.fill_tightened_domains_in_response()) {
130 blocked_clause_simplifier_->DoOneRound(log_round_info);
131 }
132
133 // TODO(user): this break some binary graph invariant. Fix!
135 RETURN_IF_FALSE(bounded_variable_elimination_->DoOneRound(log_round_info));
137
138 // Probing.
139 const double saved_wtime = wall_timer.Get();
140 const double time_left =
141 stop_dtime - time_limit_->GetElapsedDeterministicTime();
142 if (time_left <= 0) break;
143 ProbingOptions probing_options;
144 probing_options.log_info = log_round_info;
145 probing_options.deterministic_limit = time_left;
146 probing_options.extract_binary_clauses =
148 RETURN_IF_FALSE(FailedLiteralProbingRound(probing_options, model_));
149 probing_time += wall_timer.Get() - saved_wtime;
150
152 !implication_graph_->IsDag()) {
153 continue;
154 }
155
156 break;
157 }
158
159 // Tricky: It is important to clean-up any potential equivalence left in
160 // case we aborted early due to the limit.
162 if (!LevelZeroPropagate()) return false;
163
164 // TODO(user): Maintain the total number of literals in the watched clauses.
166 logger_, "[Pure SAT presolve]", " num_fixed: ", trail_->Index(),
167 " num_redundant: ", implication_graph_->num_redundant_literals() / 2, "/",
168 sat_solver_->NumVariables(),
169 " num_implications: ", implication_graph_->ComputeNumImplicationsForLog(),
170 " num_watched_clauses: ", clause_manager_->num_watched_clauses(),
171 " dtime: ", time_limit_->GetElapsedDeterministicTime() - start_dtime, "/",
172 options.deterministic_time_limit, " wtime: ", wall_timer.Get(),
173 " non-probing time: ", (wall_timer.Get() - probing_time));
174 return true;
175}
176
178 DCHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
179 if (sat_solver_->ModelIsUnsat()) return false;
180 WallTimer wall_timer;
181 wall_timer.Start();
182
183 const bool log_info = VLOG_IS_ON(1);
184 const bool log_round_info = VLOG_IS_ON(2);
185 const double start_dtime = time_limit_->GetElapsedDeterministicTime();
186
187 // Mainly useful for development.
188 double probing_time = 0.0;
189
190 // Store the dtime of the first call (first restart) and wait for the next
191 // restart.
192 if (first_inprocessing_call_) {
193 reference_dtime_ = start_dtime;
194 first_inprocessing_call_ = false;
195 return true;
196 }
197
198 // Try to spend a given ratio of time in the inprocessing.
199 //
200 // TODO(user): Tune the heuristic, in particular, with the current code we
201 // start some inprocessing before the first search.
202 const double diff = start_dtime - reference_dtime_;
203 if (total_dtime_ > params_.inprocessing_dtime_ratio() * diff) {
204 return true;
205 }
206
207 // LP Propagation during inprocessing can be really slow, so we temporarily
208 // disable it.
209 //
210 // TODO(user): The LP and incremental structure will still be called though,
211 // which can take some time, try to disable it more cleanly.
212 std::vector<std::pair<LinearProgrammingConstraint*, bool>> saved_state;
213 for (LinearProgrammingConstraint* lp : *all_lp_constraints_) {
214 saved_state.push_back({lp, lp->PropagationIsEnabled()});
215 lp->EnablePropagation(false);
216 }
217 auto cleanup = absl::MakeCleanup([&saved_state]() {
218 for (const auto [lp, old_value] : saved_state) {
219 lp->EnablePropagation(old_value);
220 }
221 });
222
223 // We make sure we do not "pollute" the current saved polarities. We will
224 // restore them at the end.
225 //
226 // TODO(user): We should probably also disable the variable/clauses activity
227 // updates.
228 decision_policy_->MaybeEnablePhaseSaving(/*save_phase=*/false);
229
230 implication_graph_->RemoveDuplicates();
231 RETURN_IF_FALSE(DetectEquivalencesAndStamp(true, log_round_info));
234
235 // Probing.
236 if (params_.inprocessing_probing_dtime() > 0.0) {
237 const double saved_wtime = wall_timer.Get();
238 ProbingOptions probing_options;
239 probing_options.log_info = log_round_info;
240 probing_options.deterministic_limit = params_.inprocessing_probing_dtime();
241 probing_options.extract_binary_clauses = true;
242 RETURN_IF_FALSE(FailedLiteralProbingRound(probing_options, model_));
243 probing_time += wall_timer.Get() - saved_wtime;
244 }
245
246 RETURN_IF_FALSE(DetectEquivalencesAndStamp(true, log_round_info));
249
250 RETURN_IF_FALSE(stamping_simplifier_->DoOneRound(log_round_info));
253
254 // TODO(user): Add a small wrapper function to time this.
255 const auto old_counter = sat_solver_->counters();
256 if (params_.inprocessing_minimization_dtime() > 0.0) {
257 RETURN_IF_FALSE(sat_solver_->MinimizeByPropagation(
258 params_.inprocessing_minimization_dtime()));
259 }
260 const int64_t mini_num_clause =
261 sat_solver_->counters().minimization_num_clauses -
262 old_counter.minimization_num_clauses;
263 const int64_t mini_num_removed =
264 sat_solver_->counters().minimization_num_removed_literals -
265 old_counter.minimization_num_removed_literals;
266
270
271 // TODO(user): try to enable these? The problem is that we can only remove
272 // variables not used the non-pure SAT part of a model.
273 if (/*DISABLES_CODE*/ (false)) {
274 blocked_clause_simplifier_->DoOneRound(log_round_info);
275 RETURN_IF_FALSE(bounded_variable_elimination_->DoOneRound(log_round_info));
276 }
278
279 sat_solver_->AdvanceDeterministicTime(time_limit_);
280 total_dtime_ += time_limit_->GetElapsedDeterministicTime() - start_dtime;
281 if (log_info) {
283 logger_, "Inprocessing.", " fixed:", trail_->Index(),
284 " equiv:", implication_graph_->num_redundant_literals() / 2,
285 " bools:", sat_solver_->NumVariables(),
286 " implications:", implication_graph_->ComputeNumImplicationsForLog(),
287 " watched:", clause_manager_->num_watched_clauses(),
288 " minimization:", mini_num_clause, "|", mini_num_removed,
289 " dtime:", time_limit_->GetElapsedDeterministicTime() - start_dtime,
290 " wtime:", wall_timer.Get(),
291 " np_wtime:", (wall_timer.Get() - probing_time));
292 }
293
294 DCHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
295 decision_policy_->MaybeEnablePhaseSaving(/*save_phase=*/true);
296 return true;
297}
298
299#undef RETURN_IF_FALSE
300
302 const int64_t new_num_fixed_variables = trail_->Index();
303 return last_num_fixed_variables_ < new_num_fixed_variables;
304}
305
307 const int64_t new_num_redundant_literals =
308 implication_graph_->num_redundant_literals();
309 return last_num_redundant_literals_ < new_num_redundant_literals;
310}
311
313 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
314 clause_manager_->AttachAllClauses();
315 return sat_solver_->Propagate();
316}
317
318// It make sense to do the pre-stamping right after the equivalence detection
319// since it needs a DAG and can detect extra failed literal.
320bool Inprocessing::DetectEquivalencesAndStamp(bool use_transitive_reduction,
321 bool log_info) {
322 if (!LevelZeroPropagate()) return false;
323 implication_graph_->RemoveFixedVariables();
324 if (!implication_graph_->IsDag()) {
325 // TODO(user): consider doing the transitive reduction after each SCC.
326 // It might be slow but we could try to make it more incremental to
327 // compensate and it should allow further reduction.
328 if (!implication_graph_->DetectEquivalences(log_info)) return false;
329 if (!LevelZeroPropagate()) return false;
330 if (use_transitive_reduction) {
331 if (!implication_graph_->ComputeTransitiveReduction(log_info)) {
332 return false;
333 }
334 if (!LevelZeroPropagate()) return false;
335 }
336 }
337
338 if (!stamping_simplifier_->ComputeStampsForNextRound(log_info)) return false;
339 return LevelZeroPropagate();
340}
341
343 // Preconditions.
344 //
345 // TODO(user): The level zero is required because we remove fixed variables
346 // but if we split this into two functions, we could rewrite clause at any
347 // level.
348 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
349 if (!LevelZeroPropagate()) return false;
350
351 // Test if some work is needed.
352 //
353 // TODO(user): If only new fixed variables are there, we can use a faster
354 // function. We should also merge the code with the deletion code in
355 // sat_solver_.cc, but that require some refactoring of the dependence between
356 // files.
357 const int64_t new_num_redundant_literals =
358 implication_graph_->num_redundant_literals();
359 const int64_t new_num_fixed_variables = trail_->Index();
360 if (last_num_redundant_literals_ == new_num_redundant_literals &&
361 last_num_fixed_variables_ == new_num_fixed_variables) {
362 return true;
363 }
364 last_num_fixed_variables_ = new_num_fixed_variables;
365 last_num_redundant_literals_ = new_num_redundant_literals;
366
367 // Start the round.
368 WallTimer wall_timer;
369 wall_timer.Start();
370
371 int64_t num_removed_literals = 0;
372 int64_t num_inspected_literals = 0;
373
374 // We need this temporary vector for the DRAT proof settings, otherwise
375 // we could just have done an in-place transformation.
376 std::vector<Literal> new_clause;
377
378 // Used to mark clause literals.
379 const int num_literals(sat_solver_->NumVariables() * 2);
380 util_intops::StrongVector<LiteralIndex, bool> marked(num_literals, false);
381
382 clause_manager_->DeleteRemovedClauses();
383 clause_manager_->DetachAllClauses();
384 for (SatClause* clause : clause_manager_->AllClausesInCreationOrder()) {
385 bool removed = false;
386 bool need_rewrite = false;
387
388 // We first do a loop to see if there is anything to do.
389 for (const Literal l : clause->AsSpan()) {
390 if (assignment_.LiteralIsTrue(l)) {
391 // TODO(user): we should output literal to the proof right away,
392 // currently if we remove clauses before fixing literal the proof is
393 // wrong.
394 if (!clause_manager_->InprocessingFixLiteral(l)) return false;
395 clause_manager_->InprocessingRemoveClause(clause);
396 num_removed_literals += clause->size();
397 removed = true;
398 break;
399 }
400 if (assignment_.LiteralIsFalse(l) || implication_graph_->IsRedundant(l)) {
401 need_rewrite = true;
402 break;
403 }
404 }
405
406 num_inspected_literals += clause->size();
407 if (removed || !need_rewrite) continue;
408 num_inspected_literals += clause->size();
409
410 // Rewrite the clause.
411 new_clause.clear();
412 for (const Literal l : clause->AsSpan()) {
413 const Literal r = implication_graph_->RepresentativeOf(l);
414 if (marked[r] || assignment_.LiteralIsFalse(r)) {
415 continue;
416 }
417 if (marked[r.NegatedIndex()] || assignment_.LiteralIsTrue(r)) {
418 clause_manager_->InprocessingRemoveClause(clause);
419 num_removed_literals += clause->size();
420 removed = true;
421 break;
422 }
423 marked[r] = true;
424 new_clause.push_back(r);
425 }
426
427 // Restore marked.
428 for (const Literal l : new_clause) marked[l] = false;
429 if (removed) continue;
430
431 num_removed_literals += clause->size() - new_clause.size();
432 if (!clause_manager_->InprocessingRewriteClause(clause, new_clause)) {
433 return false;
434 }
435 }
436
437 // TODO(user): find a way to auto-tune that after a run on borg...
438 const double dtime = static_cast<double>(num_inspected_literals) * 1e-8;
439 time_limit_->AdvanceDeterministicTime(dtime);
440 LOG_IF(INFO, log_info) << "Cleanup. num_removed_literals: "
441 << num_removed_literals << " dtime: " << dtime
442 << " wtime: " << wall_timer.Get();
443 return true;
444}
445
446// TODO(user): Use better work limits, see SAT09.CRAFTED.ramseycube.Q3inK12
447//
448// TODO(user): Be more incremental, each time a clause is added/reduced track
449// which literal are impacted? Also try to do orthogonal reductions from one
450// round to the next.
452 WallTimer wall_timer;
453 wall_timer.Start();
454
455 int64_t num_subsumed_clauses = 0;
456 int64_t num_removed_literals = 0;
457 int64_t num_inspected_signatures = 0;
458 int64_t num_inspected_literals = 0;
459
460 // We need this temporary vector for the DRAT proof settings, otherwise
461 // we could just have done an in-place transformation.
462 std::vector<Literal> new_clause;
463
464 // This function needs the watcher to be detached as we might remove some
465 // of the watched literals.
466 //
467 // TODO(user): We could do that only if we do some reduction, but this is
468 // quite fast though.
469 clause_manager_->DeleteRemovedClauses();
470 clause_manager_->DetachAllClauses();
471
472 // Process clause by increasing sizes.
473 // TODO(user): probably faster without the size indirection.
474 std::vector<SatClause*> clauses =
475 clause_manager_->AllClausesInCreationOrder();
476 std::stable_sort(
477 clauses.begin(), clauses.end(),
478 [](SatClause* a, SatClause* b) { return a->size() < b->size(); });
479
480 // Used to mark clause literals.
481 const LiteralIndex num_literals(sat_solver_->NumVariables() * 2);
482 SparseBitset<LiteralIndex> marked(num_literals);
483
484 // Clause index in clauses.
485 // TODO(user): Storing signatures here might be faster?
487 one_watcher(num_literals.value());
488
489 // Clause signatures in the same order as clauses.
490 std::vector<uint64_t> signatures(clauses.size());
491
492 std::vector<Literal> candidates_for_removal;
493 for (int clause_index = 0; clause_index < clauses.size(); ++clause_index) {
494 SatClause* clause = clauses[clause_index];
495
496 // TODO(user): Better abort limit. We could also limit the watcher sizes and
497 // never look at really long clauses. Note that for an easier
498 // incrementality, it is better to reach some kind of completion so we know
499 // what new stuff need to be done.
500 if (num_inspected_literals + num_inspected_signatures > 1e9) {
501 break;
502 }
503
504 // Check for subsumption, note that this currently ignore all clauses in the
505 // binary implication graphs. Stamping is doing some of that (and some also
506 // happen during probing), but we could consider only direct implications
507 // here and be a bit more exhaustive than what stamping do with them (at
508 // least for node with many incoming and outgoing implications).
509 //
510 // TODO(user): Do some reduction using binary clauses. Note that only clause
511 // that never propagated since last round need to be checked for binary
512 // subsumption.
513
514 // Compute hash and mark literals.
515 uint64_t signature = 0;
516 marked.ResetAllToFalse();
517 for (const Literal l : clause->AsSpan()) {
518 marked.Set(l.Index());
519 signature |= (uint64_t{1} << (l.Variable().value() % 64));
520 }
521
522 // Look for clause that subsumes this one. Note that because we inspect
523 // all one watcher lists for the literals of this clause, if a clause is
524 // included inside this one, it must appear in one of these lists.
525 bool removed = false;
526 candidates_for_removal.clear();
527 const uint64_t mask = ~signature;
528 for (const Literal l : clause->AsSpan()) {
529 num_inspected_signatures += one_watcher[l].size();
530 for (const int i : one_watcher[l]) {
531 if ((mask & signatures[i]) != 0) continue;
532
533 bool subsumed = true;
534 bool stengthen = true;
535 LiteralIndex to_remove = kNoLiteralIndex;
536 num_inspected_literals += clauses[i]->size();
537 for (const Literal o : clauses[i]->AsSpan()) {
538 if (!marked[o]) {
539 subsumed = false;
540 if (to_remove == kNoLiteralIndex && marked[o.NegatedIndex()]) {
541 to_remove = o.NegatedIndex();
542 } else {
543 stengthen = false;
544 break;
545 }
546 }
547 }
548 if (subsumed) {
549 ++num_subsumed_clauses;
550 num_removed_literals += clause->size();
551 clause_manager_->InprocessingRemoveClause(clause);
552 removed = true;
553 break;
554 }
555 if (stengthen) {
556 CHECK_NE(kNoLiteralIndex, to_remove);
557 candidates_for_removal.push_back(Literal(to_remove));
558 }
559 }
560 if (removed) break;
561 }
562 if (removed) continue;
563
564 // For strengthenning we also need to check the negative watcher lists.
565 for (const Literal l : clause->AsSpan()) {
566 num_inspected_signatures += one_watcher[l.NegatedIndex()].size();
567 for (const int i : one_watcher[l.NegatedIndex()]) {
568 if ((mask & signatures[i]) != 0) continue;
569
570 bool stengthen = true;
571 num_inspected_literals += clauses[i]->size();
572 for (const Literal o : clauses[i]->AsSpan()) {
573 if (o == l.Negated()) continue;
574 if (!marked[o]) {
575 stengthen = false;
576 break;
577 }
578 }
579 if (stengthen) {
580 candidates_for_removal.push_back(l);
581 }
582 }
583 }
584
585 // Any literal here can be removed, but afterwards the other might not. For
586 // now we just remove the first one.
587 //
588 // TODO(user): remove first and see if other still removable. Alternatively
589 // use a "removed" marker and redo a check for each clause that simplifies
590 // this one? Or just remove the first one, and wait for next round.
591 if (!candidates_for_removal.empty()) {
592 new_clause.clear();
593 for (const Literal l : clause->AsSpan()) {
594 if (l == candidates_for_removal[0]) continue;
595 new_clause.push_back(l);
596 }
597 CHECK_EQ(new_clause.size() + 1, clause->size());
598
599 num_removed_literals += clause->size() - new_clause.size();
600 if (!clause_manager_->InprocessingRewriteClause(clause, new_clause)) {
601 return false;
602 }
603 if (clause->size() == 0) continue;
604
605 // Recompute signature.
606 signature = 0;
607 for (const Literal l : clause->AsSpan()) {
608 signature |= (uint64_t{1} << (l.Variable().value() % 64));
609 }
610 }
611
612 // Register one literal to watch. Any literal works, but we choose the
613 // smallest list.
614 //
615 // TODO(user): No need to add this clause if we know it cannot subsume
616 // any new clause since last round. i.e. unchanged clause that do not
617 // contains any literals of newly added clause do not need to be added
618 // here. We can track two bitset in LiteralWatchers via a register
619 // mechanism:
620 // - literal of newly watched clauses since last clear.
621 // - literal of reduced clauses since last clear.
622 //
623 // Important: we can only use this clause to subsume/strenghten others if
624 // it cannot be deleted later.
625 if (!clause_manager_->IsRemovable(clause)) {
626 int min_size = std::numeric_limits<int32_t>::max();
627 LiteralIndex min_literal = kNoLiteralIndex;
628 for (const Literal l : clause->AsSpan()) {
629 if (one_watcher[l].size() < min_size) {
630 min_size = one_watcher[l].size();
631 min_literal = l.Index();
632 }
633 }
634
635 // TODO(user): We could/should sort the literal in this clause by
636 // using literals that appear in a small number of clauses first so that
637 // we maximize the chance of early abort in the critical loops above.
638 //
639 // TODO(user): We could also move the watched literal first so we always
640 // skip it.
641 signatures[clause_index] = signature;
642 one_watcher[min_literal].push_back(clause_index);
643 }
644 }
645
646 // We might have fixed variables, finish the propagation.
647 if (!LevelZeroPropagate()) return false;
648
649 // TODO(user): tune the deterministic time.
650 const double dtime = static_cast<double>(num_inspected_signatures) * 1e-8 +
651 static_cast<double>(num_inspected_literals) * 5e-9;
652 time_limit_->AdvanceDeterministicTime(dtime);
653 LOG_IF(INFO, log_info) << "Subsume. num_removed_literals: "
654 << num_removed_literals
655 << " num_subsumed: " << num_subsumed_clauses
656 << " dtime: " << dtime
657 << " wtime: " << wall_timer.Get();
658 return true;
659}
660
662 WallTimer wall_timer;
663 wall_timer.Start();
664
665 dtime_ = 0.0;
666 num_subsumed_clauses_ = 0;
667 num_removed_literals_ = 0;
668 num_fixed_ = 0;
669
670 if (implication_graph_->IsEmpty()) return true;
671
672 if (!stamps_are_already_computed_) {
673 // We need a DAG so that we don't have cycle while we sample the tree.
674 // TODO(user): We could probably deal with it if needed so that we don't
675 // need to do equivalence detection each time we want to run this.
676 implication_graph_->RemoveFixedVariables();
677 if (!implication_graph_->DetectEquivalences(log_info)) return true;
679 if (!ComputeStamps()) return false;
680 }
681 stamps_are_already_computed_ = false;
682 if (!ProcessClauses()) return false;
683
684 // Note that num_removed_literals_ do not count the literals of the subsumed
685 // clauses.
686 time_limit_->AdvanceDeterministicTime(dtime_);
687 LOG_IF(INFO, log_info) << "Stamping. num_removed_literals: "
688 << num_removed_literals_
689 << " num_subsumed: " << num_subsumed_clauses_
690 << " num_fixed: " << num_fixed_ << " dtime: " << dtime_
691 << " wtime: " << wall_timer.Get();
692 return true;
693}
694
696 WallTimer wall_timer;
697 wall_timer.Start();
698 dtime_ = 0.0;
699 num_fixed_ = 0;
700
701 if (implication_graph_->IsEmpty()) return true;
702
703 implication_graph_->RemoveFixedVariables();
704 if (!implication_graph_->DetectEquivalences(log_info)) return true;
706 if (!ComputeStamps()) return false;
707 stamps_are_already_computed_ = true;
708
709 // TODO(user): compute some dtime, it is always zero currently.
710 time_limit_->AdvanceDeterministicTime(dtime_);
711 LOG_IF(INFO, log_info) << "Prestamping."
712 << " num_fixed: " << num_fixed_ << " dtime: " << dtime_
713 << " wtime: " << wall_timer.Get();
714 return true;
715}
716
718 const int size = implication_graph_->literal_size();
719 CHECK(implication_graph_->IsDag()); // so we don't have cycle.
720 parents_.resize(size);
721 for (LiteralIndex i(0); i < size; ++i) {
722 parents_[i] = i; // default.
723 if (implication_graph_->IsRedundant(Literal(i))) continue;
724 if (assignment_.LiteralIsAssigned(Literal(i))) continue;
725
726 // TODO(user): Better algo to not select redundant parent.
727 //
728 // TODO(user): if parents_[x] = y, try not to have parents_[not(y)] = not(x)
729 // because this is not as useful for the simplification power.
730 //
731 // TODO(user): More generally, we could sample a parent while probing so
732 // that we consider all hyper binary implications (in the case we don't add
733 // them to the implication graph already).
734 for (int num_tries = 0; num_tries < 10; ++num_tries) {
735 // We look for a random lit that implies i. For that we take a random
736 // literal in the direct implications of not(i) and reverse it.
737 const LiteralIndex index =
738 implication_graph_->RandomImpliedLiteral(Literal(i).Negated());
739 if (index == kNoLiteralIndex) break;
740
741 const Literal candidate = Literal(index).Negated();
742 if (implication_graph_->IsRedundant(candidate)) continue;
743 if (i == candidate.Index()) continue;
744
745 // We found an interesting parent.
746 parents_[i] = candidate.Index();
747 break;
748 }
749 }
750}
751
753 const int size = implication_graph_->literal_size();
754
755 // Compute sizes.
756 sizes_.assign(size, 0);
757 for (LiteralIndex i(0); i < size; ++i) {
758 if (parents_[i] == i) continue; // leaf.
759 sizes_[parents_[i]]++;
760 }
761
762 // Compute starts in the children_ vector for each node.
763 starts_.resize(size + 1); // We use a sentinel.
764 starts_[LiteralIndex(0)] = 0;
765 for (LiteralIndex i(1); i <= size; ++i) {
766 starts_[i] = starts_[i - 1] + sizes_[i - 1];
767 }
768
769 // Fill children. This messes up starts_.
770 children_.resize(size);
771 for (LiteralIndex i(0); i < size; ++i) {
772 if (parents_[i] == i) continue; // leaf.
773 children_[starts_[parents_[i]]++] = i;
774 }
775
776 // Reset starts to correct value.
777 for (LiteralIndex i(0); i < size; ++i) {
778 starts_[i] -= sizes_[i];
779 }
780
781 if (DEBUG_MODE) {
782 CHECK_EQ(starts_[LiteralIndex(0)], 0);
783 for (LiteralIndex i(1); i <= size; ++i) {
784 CHECK_EQ(starts_[i], starts_[i - 1] + sizes_[i - 1]);
785 }
786 }
787
788 // Perform a DFS from each root to compute the stamps.
789 int64_t stamp = 0;
790 first_stamps_.resize(size);
791 last_stamps_.resize(size);
792 marked_.assign(size, false);
793 for (LiteralIndex i(0); i < size; ++i) {
794 if (parents_[i] != i) continue; // Not a root.
795 DCHECK(!marked_[i]);
796 const LiteralIndex tree_root = i;
797 dfs_stack_.push_back(i);
798 while (!dfs_stack_.empty()) {
799 const LiteralIndex top = dfs_stack_.back();
800 if (marked_[top]) {
801 dfs_stack_.pop_back();
802 last_stamps_[top] = stamp++;
803 continue;
804 }
805 first_stamps_[top] = stamp++;
806 marked_[top] = true;
807
808 // Failed literal detection. If the negation of top is in the same
809 // tree, we can fix the LCA of top and its negation to false.
810 if (marked_[Literal(top).NegatedIndex()] &&
811 first_stamps_[Literal(top).NegatedIndex()] >=
812 first_stamps_[tree_root]) {
813 // Find the LCA.
814 const int first_stamp = first_stamps_[Literal(top).NegatedIndex()];
815 LiteralIndex lca = top;
816 while (first_stamps_[lca] > first_stamp) {
817 lca = parents_[lca];
818 }
819 ++num_fixed_;
820 if (!clause_manager_->InprocessingFixLiteral(Literal(lca).Negated())) {
821 return false;
822 }
823 }
824
825 const int end = starts_[top + 1]; // Ok with sentinel.
826 for (int j = starts_[top]; j < end; ++j) {
827 DCHECK_NE(top, children_[j]); // We removed leaf self-loop.
828 DCHECK(!marked_[children_[j]]); // This is a tree.
829 dfs_stack_.push_back(children_[j]);
830 }
831 }
832 }
833 DCHECK_EQ(stamp, 2 * size);
834 return true;
835}
836
838 struct Entry {
839 int i; // Index in the clause.
840 bool is_negated; // Correspond to clause[i] or clause[i].Negated();
841 int start; // Note that all start stamps are different.
842 int end;
843 bool operator<(const Entry& o) const { return start < o.start; }
844 };
845 std::vector<int> to_remove;
846 std::vector<Literal> new_clause;
847 std::vector<Entry> entries;
848 clause_manager_->DeleteRemovedClauses();
849 clause_manager_->DetachAllClauses();
850 for (SatClause* clause : clause_manager_->AllClausesInCreationOrder()) {
851 const auto span = clause->AsSpan();
852 if (span.empty()) continue;
853
854 // Note that we might fix literal as we perform the loop here, so we do
855 // need to deal with them.
856 //
857 // For a and b in the clause, if not(a) => b is present, then the clause is
858 // subsumed. If a => b, then a can be removed, and if not(a) => not(b) then
859 // b can be removed. Nothing can be done if a => not(b).
860 entries.clear();
861 for (int i = 0; i < span.size(); ++i) {
862 if (assignment_.LiteralIsTrue(span[i])) {
863 clause_manager_->InprocessingRemoveClause(clause);
864 break;
865 }
866 if (assignment_.LiteralIsFalse(span[i])) continue;
867 entries.push_back(
868 {i, false, first_stamps_[span[i]], last_stamps_[span[i]]});
869 entries.push_back({i, true, first_stamps_[span[i].NegatedIndex()],
870 last_stamps_[span[i].NegatedIndex()]});
871 }
872 if (clause->IsRemoved()) continue;
873
874 // The sort should be dominant.
875 if (!entries.empty()) {
876 const double n = static_cast<double>(entries.size());
877 dtime_ += 1.5e-8 * n * std::log(n);
878 std::sort(entries.begin(), entries.end());
879 }
880
881 Entry top_entry;
882 top_entry.end = -1; // Sentinel.
883 to_remove.clear();
884 for (const Entry& e : entries) {
885 if (e.end < top_entry.end) {
886 // We found an implication: top_entry => this entry.
887 const Literal lhs = top_entry.is_negated ? span[top_entry.i].Negated()
888 : span[top_entry.i];
889 const Literal rhs = e.is_negated ? span[e.i].Negated() : span[e.i];
890 DCHECK(ImplicationIsInTree(lhs, rhs));
891
892 if (top_entry.is_negated != e.is_negated) {
893 // Failed literal?
894 if (top_entry.i == e.i) {
895 ++num_fixed_;
896 if (top_entry.is_negated) {
897 // not(span[i]) => span[i] so span[i] true.
898 // And the clause is satisfied (so we count as as subsumed).
899 if (!clause_manager_->InprocessingFixLiteral(span[top_entry.i])) {
900 return false;
901 }
902 } else {
903 // span[i] => not(span[i]) so span[i] false.
904 if (!clause_manager_->InprocessingFixLiteral(
905 span[top_entry.i].Negated())) {
906 return false;
907 }
908 to_remove.push_back(top_entry.i);
909 continue;
910 }
911 }
912
913 // not(a) => b : subsumption.
914 // a => not(b), we cannot deduce anything, but it might make sense
915 // to see if not(b) implies anything instead of just keeping
916 // top_entry. See TODO below.
917 if (top_entry.is_negated) {
918 num_subsumed_clauses_++;
919 clause_manager_->InprocessingRemoveClause(clause);
920 break;
921 }
922 } else {
923 CHECK_NE(top_entry.i, e.i);
924 if (top_entry.is_negated) {
925 // not(a) => not(b), we can remove b.
926 to_remove.push_back(e.i);
927 } else {
928 // a => b, we can remove a.
929 //
930 // TODO(user): Note that it is okay to still use top_entry, but we
931 // might miss the removal of b if b => c. Also the paper do things
932 // differently. Make sure we don't miss any simplification
933 // opportunites by not changing top_entry. Same in the other
934 // branches.
935 to_remove.push_back(top_entry.i);
936 }
937 }
938 } else {
939 top_entry = e;
940 }
941 }
942
943 if (clause->IsRemoved()) continue;
944
945 // Strengthen the clause.
946 if (!to_remove.empty() || entries.size() < span.size()) {
947 new_clause.clear();
949 int to_remove_index = 0;
950 for (int i = 0; i < span.size(); ++i) {
951 if (to_remove_index < to_remove.size() &&
952 i == to_remove[to_remove_index]) {
953 ++to_remove_index;
954 continue;
955 }
956 if (assignment_.LiteralIsTrue(span[i])) {
957 clause_manager_->InprocessingRemoveClause(clause);
958 continue;
959 }
960 if (assignment_.LiteralIsFalse(span[i])) continue;
961 new_clause.push_back(span[i]);
962 }
963 num_removed_literals_ += span.size() - new_clause.size();
964 if (!clause_manager_->InprocessingRewriteClause(clause, new_clause)) {
965 return false;
966 }
967 }
968 }
969 return true;
970}
971
973 WallTimer wall_timer;
974 wall_timer.Start();
975
976 dtime_ = 0.0;
977 num_blocked_clauses_ = 0;
978 num_inspected_literals_ = 0;
979
980 InitializeForNewRound();
981
982 while (!time_limit_->LimitReached() && !queue_.empty()) {
983 const Literal l = queue_.front();
984 in_queue_[l] = false;
985 queue_.pop_front();
986
987 // Avoid doing too much work here on large problem.
988 // Note that we still what to empty the queue.
989 if (num_inspected_literals_ <= 1e9) ProcessLiteral(l);
990 }
991
992 // Release some memory.
993 literal_to_clauses_.clear();
994
995 dtime_ += 1e-8 * num_inspected_literals_;
996 time_limit_->AdvanceDeterministicTime(dtime_);
997 log_info |= VLOG_IS_ON(1);
998 LOG_IF(INFO, log_info) << "Blocked clause. num_blocked_clauses: "
999 << num_blocked_clauses_ << " dtime: " << dtime_
1000 << " wtime: " << wall_timer.Get();
1001}
1002
1003void BlockedClauseSimplifier::InitializeForNewRound() {
1004 clauses_.clear();
1005 clause_manager_->DeleteRemovedClauses();
1006 clause_manager_->DetachAllClauses();
1007 for (SatClause* c : clause_manager_->AllClausesInCreationOrder()) {
1008 // We ignore redundant clause. This shouldn't cause any validity issue.
1009 if (clause_manager_->IsRemovable(c)) continue;
1010
1011 clauses_.push_back(c);
1012 }
1013 const int num_literals = clause_manager_->literal_size();
1014
1015 // TODO(user): process in order of increasing number of clause that contains
1016 // not(l)?
1017 in_queue_.assign(num_literals, true);
1018 for (LiteralIndex l(0); l < num_literals; ++l) {
1019 queue_.push_back(Literal(l));
1020 }
1021
1022 marked_.resize(num_literals);
1023 DCHECK(
1024 std::all_of(marked_.begin(), marked_.end(), [](bool b) { return !b; }));
1025
1026 // TODO(user): because we don't create new clause here we can use a flat
1027 // vector for literal_to_clauses_.
1028 literal_to_clauses_.clear();
1029 literal_to_clauses_.resize(num_literals);
1030 for (ClauseIndex i(0); i < clauses_.size(); ++i) {
1031 for (const Literal l : clauses_[i]->AsSpan()) {
1032 literal_to_clauses_[l].push_back(i);
1033 }
1034 num_inspected_literals_ += clauses_[i]->size();
1035 }
1036}
1037
1038void BlockedClauseSimplifier::ProcessLiteral(Literal current_literal) {
1039 if (assignment_.LiteralIsAssigned(current_literal)) return;
1040 if (implication_graph_->IsRemoved(current_literal)) return;
1041
1042 // We want to check first that this clause will resolve to trivial clause with
1043 // all binary containing not(current_literal). So mark all literal l so that
1044 // current_literal => l.
1045 //
1046 // TODO(user): We do not need to redo that each time we reprocess
1047 // current_literal.
1048 //
1049 // TODO(user): Ignore redundant literals. That might require pushing
1050 // equivalence to the postsolve stack though. Better to simply remove
1051 // these equivalence if we are allowed to and update the postsolve then.
1052 //
1053 // TODO(user): Make this work in the presence of at most ones.
1054 int num_binary = 0;
1055 const std::vector<Literal>& implications =
1056 implication_graph_->DirectImplications(current_literal);
1057 for (const Literal l : implications) {
1058 if (l == current_literal) continue;
1059 ++num_binary;
1060 marked_[l] = true;
1061 }
1062
1063 // TODO(user): We could also mark a small clause containing
1064 // current_literal.Negated(), and make sure we only include in
1065 // clauses_to_process clauses that resolve trivially with that clause.
1066 std::vector<ClauseIndex> clauses_to_process;
1067 for (const ClauseIndex i : literal_to_clauses_[current_literal]) {
1068 if (clauses_[i]->IsRemoved()) continue;
1069
1070 // Blocked with respect to binary clause only? all marked binary should have
1071 // their negation in clause.
1072 //
1073 // TODO(user): Abort if size left is too small.
1074 if (num_binary > 0) {
1075 if (clauses_[i]->size() <= num_binary) continue;
1076 int num_with_negation_marked = 0;
1077 for (const Literal l : clauses_[i]->AsSpan()) {
1078 if (l == current_literal) continue;
1079 if (marked_[l.NegatedIndex()]) {
1080 ++num_with_negation_marked;
1081 }
1082 }
1083 num_inspected_literals_ += clauses_[i]->size();
1084 if (num_with_negation_marked < num_binary) continue;
1085 }
1086 clauses_to_process.push_back(i);
1087 }
1088
1089 // Clear marked.
1090 for (const Literal l : implications) {
1091 marked_[l] = false;
1092 }
1093
1094 // TODO(user): There is a possible optimization: If we mark all literals of
1095 // all the clause to process, we can check that each clause containing
1096 // current_literal.Negated() contains at least one of these literal negated
1097 // other than current_literal. Otherwise none of the clause are blocked.
1098 //
1099 // TODO(user): If a clause cannot be blocked because of another clause, then
1100 // when we call ProcessLiteral(current_literal.Negated()) we can skip some
1101 // inspection.
1102 for (const ClauseIndex i : clauses_to_process) {
1103 const auto c = clauses_[i]->AsSpan();
1104 if (ClauseIsBlocked(current_literal, c)) {
1105 // Reprocess all clauses that have a negated literal in this one as
1106 // some might be blocked now.
1107 //
1108 // TODO(user): Maybe we can remember for which (literal, clause) pair this
1109 // was used as a certificate for "not-blocked" and just reprocess those,
1110 // but it might be memory intensive.
1111 for (const Literal l : c) {
1112 if (!in_queue_[l.NegatedIndex()]) {
1113 in_queue_[l.NegatedIndex()] = true;
1114 queue_.push_back(l.Negated());
1115 }
1116 }
1117
1118 // Add the clause to the postsolving set.
1119 postsolve_->AddClauseWithSpecialLiteral(current_literal, c);
1120
1121 // We can remove a blocked clause.
1122 ++num_blocked_clauses_;
1123 clause_manager_->InprocessingRemoveClause(clauses_[i]);
1124 }
1125 }
1126}
1127
1128// Note that this assume that the binary clauses have already been checked.
1129bool BlockedClauseSimplifier::ClauseIsBlocked(
1130 Literal current_literal, absl::Span<const Literal> clause) {
1131 bool is_blocked = true;
1132 for (const Literal l : clause) marked_[l] = true;
1133
1134 // TODO(user): For faster reprocessing of the same literal, we should move
1135 // all clauses that are used in a non-blocked certificate first in the list.
1136 for (const ClauseIndex i :
1137 literal_to_clauses_[current_literal.NegatedIndex()]) {
1138 if (clauses_[i]->IsRemoved()) continue;
1139 bool some_marked = false;
1140 for (const Literal l : clauses_[i]->AsSpan()) {
1141 // TODO(user): we can be faster here by only updating it at the end?
1142 ++num_inspected_literals_;
1143
1144 if (l == current_literal.Negated()) continue;
1145 if (marked_[l.NegatedIndex()]) {
1146 some_marked = true;
1147 break;
1148 }
1149 }
1150 if (!some_marked) {
1151 is_blocked = false;
1152 break;
1153 }
1154 }
1155
1156 for (const Literal l : clause) marked_[l] = false;
1157 return is_blocked;
1158}
1159
1161 WallTimer wall_timer;
1162 wall_timer.Start();
1163
1164 dtime_ = 0.0;
1165 num_inspected_literals_ = 0;
1166 num_eliminated_variables_ = 0;
1167 num_literals_diff_ = 0;
1168 num_clauses_diff_ = 0;
1169 num_simplifications_ = 0;
1170 num_blocked_clauses_ = 0;
1171
1172 clauses_.clear();
1173 clause_manager_->DeleteRemovedClauses();
1174 clause_manager_->DetachAllClauses();
1175 for (SatClause* c : clause_manager_->AllClausesInCreationOrder()) {
1176 // We ignore redundant clause. This shouldn't cause any validity issue.
1177 // TODO(user): but we shouldn't keep clauses containing removed literals.
1178 // It is still valid to do so, but it should be less efficient.
1179 if (clause_manager_->IsRemovable(c)) continue;
1180
1181 clauses_.push_back(c);
1182 }
1183 const int num_literals = clause_manager_->literal_size();
1184 const int num_variables = num_literals / 2;
1185
1186 literal_to_clauses_.clear();
1187 literal_to_clauses_.resize(num_literals);
1188 literal_to_num_clauses_.assign(num_literals, 0);
1189 for (ClauseIndex i(0); i < clauses_.size(); ++i) {
1190 for (const Literal l : clauses_[i]->AsSpan()) {
1191 literal_to_clauses_[l].push_back(i);
1192 literal_to_num_clauses_[l]++;
1193 }
1194 num_inspected_literals_ += clauses_[i]->size();
1195 }
1196
1197 const int saved_trail_index = trail_->Index();
1198 propagation_index_ = trail_->Index();
1199
1200 need_to_be_updated_.clear();
1201 in_need_to_be_updated_.resize(num_variables);
1202 DCHECK(absl::c_find(in_need_to_be_updated_, true) ==
1203 in_need_to_be_updated_.end());
1204 queue_.Reserve(num_variables);
1205 for (BooleanVariable v(0); v < num_variables; ++v) {
1206 if (assignment_.VariableIsAssigned(v)) continue;
1207 UpdatePriorityQueue(v);
1208 }
1209
1210 marked_.resize(num_literals);
1211 DCHECK(
1212 std::all_of(marked_.begin(), marked_.end(), [](bool b) { return !b; }));
1213
1214 // TODO(user): add a local dtime limit for the corner case where this take too
1215 // much time. We can adapt the limit depending on how much we want to spend on
1216 // inprocessing.
1217 while (!time_limit_->LimitReached() && !queue_.IsEmpty()) {
1218 const BooleanVariable top = queue_.Top().var;
1219 queue_.Pop();
1220
1221 // Make sure we fix variables first if needed. Note that because new binary
1222 // clause might appear when we fix variables, we need a loop here.
1223 //
1224 // TODO(user): we might also find new equivalent variable l => var => l
1225 // here, but for now we ignore those.
1226 bool is_unsat = false;
1227 if (!Propagate()) return false;
1228 while (implication_graph_->FindFailedLiteralAroundVar(top, &is_unsat)) {
1229 if (!Propagate()) return false;
1230 }
1231 if (is_unsat) return false;
1232
1233 if (!CrossProduct(top)) return false;
1234
1235 for (const BooleanVariable v : need_to_be_updated_) {
1236 in_need_to_be_updated_[v] = false;
1237
1238 // Currently we never re-add top if we just processed it.
1239 if (v != top) UpdatePriorityQueue(v);
1240 }
1241 need_to_be_updated_.clear();
1242 }
1243
1244 if (!Propagate()) return false;
1245 implication_graph_->CleanupAllRemovedAndFixedVariables();
1246
1247 // Remove all redundant clause containing a removed literal. This avoid to
1248 // re-introduce a removed literal via conflict learning.
1249 for (SatClause* c : clause_manager_->AllClausesInCreationOrder()) {
1250 bool remove = false;
1251 for (const Literal l : c->AsSpan()) {
1252 if (implication_graph_->IsRemoved(l)) {
1253 remove = true;
1254 break;
1255 }
1256 }
1257 if (remove) clause_manager_->InprocessingRemoveClause(c);
1258 }
1259
1260 // Release some memory.
1261 literal_to_clauses_.clear();
1262 literal_to_num_clauses_.clear();
1263
1264 dtime_ += 1e-8 * num_inspected_literals_;
1265 time_limit_->AdvanceDeterministicTime(dtime_);
1266 log_info |= VLOG_IS_ON(1);
1267 LOG_IF(INFO, log_info) << "BVE."
1268 << " num_fixed: "
1269 << trail_->Index() - saved_trail_index
1270 << " num_simplified_literals: " << num_simplifications_
1271 << " num_blocked_clauses_: " << num_blocked_clauses_
1272 << " num_eliminations: " << num_eliminated_variables_
1273 << " num_literals_diff: " << num_literals_diff_
1274 << " num_clause_diff: " << num_clauses_diff_
1275 << " dtime: " << dtime_
1276 << " wtime: " << wall_timer.Get();
1277 return true;
1278}
1279
1280bool BoundedVariableElimination::RemoveLiteralFromClause(
1281 Literal lit, SatClause* sat_clause) {
1282 num_literals_diff_ -= sat_clause->size();
1283 resolvant_.clear();
1284 for (const Literal l : sat_clause->AsSpan()) {
1285 if (l == lit || assignment_.LiteralIsFalse(l)) {
1286 literal_to_num_clauses_[l]--;
1287 continue;
1288 }
1289 if (assignment_.LiteralIsTrue(l)) {
1290 num_clauses_diff_--;
1291 clause_manager_->InprocessingRemoveClause(sat_clause);
1292 return true;
1293 }
1294 resolvant_.push_back(l);
1295 }
1296 if (!clause_manager_->InprocessingRewriteClause(sat_clause, resolvant_)) {
1297 return false;
1298 }
1299 if (sat_clause->IsRemoved()) {
1300 --num_clauses_diff_;
1301 for (const Literal l : resolvant_) literal_to_num_clauses_[l]--;
1302 } else {
1303 num_literals_diff_ += sat_clause->size();
1304 }
1305 return true;
1306}
1307
1308bool BoundedVariableElimination::Propagate() {
1309 for (; propagation_index_ < trail_->Index(); ++propagation_index_) {
1310 // Make sure we always propagate the binary clauses first.
1311 if (!implication_graph_->Propagate(trail_)) return false;
1312
1313 const Literal l = (*trail_)[propagation_index_];
1314 for (const ClauseIndex index : literal_to_clauses_[l]) {
1315 if (clauses_[index]->IsRemoved()) continue;
1316 num_clauses_diff_--;
1317 num_literals_diff_ -= clauses_[index]->size();
1318 clause_manager_->InprocessingRemoveClause(clauses_[index]);
1319 }
1320 literal_to_clauses_[l].clear();
1321 for (const ClauseIndex index : literal_to_clauses_[l.NegatedIndex()]) {
1322 if (clauses_[index]->IsRemoved()) continue;
1323 if (!RemoveLiteralFromClause(l.Negated(), clauses_[index])) return false;
1324 }
1325 literal_to_clauses_[l.NegatedIndex()].clear();
1326 }
1327 return true;
1328}
1329
1330// Note that we use the estimated size here to make it fast. It is okay if the
1331// order of elimination is not perfect... We can improve on this later.
1332int BoundedVariableElimination::NumClausesContaining(Literal l) {
1333 return literal_to_num_clauses_[l] +
1334 implication_graph_->DirectImplicationsEstimatedSize(l.Negated());
1335}
1336
1337// TODO(user): Only enqueue variable that can be removed.
1338void BoundedVariableElimination::UpdatePriorityQueue(BooleanVariable var) {
1339 if (assignment_.VariableIsAssigned(var)) return;
1340 if (implication_graph_->IsRemoved(Literal(var, true))) return;
1341 if (implication_graph_->IsRedundant(Literal(var, true))) return;
1342 const int priority = -NumClausesContaining(Literal(var, true)) -
1343 NumClausesContaining(Literal(var, false));
1344 if (queue_.Contains(var.value())) {
1345 queue_.ChangePriority({var, priority});
1346 } else {
1347 queue_.Add({var, priority});
1348 }
1349}
1350
1351void BoundedVariableElimination::DeleteClause(SatClause* sat_clause) {
1352 const auto clause = sat_clause->AsSpan();
1353
1354 num_clauses_diff_--;
1355 num_literals_diff_ -= clause.size();
1356
1357 // Update literal <-> clause graph.
1358 for (const Literal l : clause) {
1359 literal_to_num_clauses_[l]--;
1360 if (!in_need_to_be_updated_[l.Variable()]) {
1361 in_need_to_be_updated_[l.Variable()] = true;
1362 need_to_be_updated_.push_back(l.Variable());
1363 }
1364 }
1365
1366 // Lazy deletion of the clause.
1367 clause_manager_->InprocessingRemoveClause(sat_clause);
1368}
1369
1370void BoundedVariableElimination::DeleteAllClausesContaining(Literal literal) {
1371 for (const ClauseIndex i : literal_to_clauses_[literal]) {
1372 const auto clause = clauses_[i]->AsSpan();
1373 if (clause.empty()) continue;
1374 postsolve_->AddClauseWithSpecialLiteral(literal, clause);
1375 DeleteClause(clauses_[i]);
1376 }
1377 literal_to_clauses_[literal].clear();
1378}
1379
1380void BoundedVariableElimination::AddClause(absl::Span<const Literal> clause) {
1381 SatClause* pt = clause_manager_->InprocessingAddClause(clause);
1382 if (pt == nullptr) return;
1383
1384 num_clauses_diff_++;
1385 num_literals_diff_ += clause.size();
1386
1387 const ClauseIndex index(clauses_.size());
1388 clauses_.push_back(pt);
1389 for (const Literal l : clause) {
1390 literal_to_num_clauses_[l]++;
1391 literal_to_clauses_[l].push_back(index);
1392 if (!in_need_to_be_updated_[l.Variable()]) {
1393 in_need_to_be_updated_[l.Variable()] = true;
1394 need_to_be_updated_.push_back(l.Variable());
1395 }
1396 }
1397}
1398
1399template <bool score_only, bool with_binary_only>
1400bool BoundedVariableElimination::ResolveAllClauseContaining(Literal lit) {
1401 const int clause_weight = parameters_.presolve_bve_clause_weight();
1402
1403 const std::vector<Literal>& implications =
1404 implication_graph_->DirectImplications(lit);
1405 auto& clause_containing_lit = literal_to_clauses_[lit];
1406 for (int i = 0; i < clause_containing_lit.size(); ++i) {
1407 const ClauseIndex clause_index = clause_containing_lit[i];
1408 const auto clause = clauses_[clause_index]->AsSpan();
1409 if (clause.empty()) continue;
1410
1411 if (!score_only) resolvant_.clear();
1412 for (const Literal l : clause) {
1413 if (!score_only && l != lit) resolvant_.push_back(l);
1414 marked_[l] = true;
1415 }
1416 DCHECK(marked_[lit]);
1417 num_inspected_literals_ += clause.size() + implications.size();
1418
1419 // If this is true, then "clause" is subsumed by one of its resolvant and we
1420 // can just remove lit from it. Then it doesn't need to be acounted at all.
1421 bool clause_can_be_simplified = false;
1422 const int64_t saved_score = new_score_;
1423
1424 // Resolution with binary clauses.
1425 for (const Literal l : implications) {
1426 CHECK_NE(l, lit);
1427 if (marked_[l.NegatedIndex()]) continue; // trivial.
1428 if (marked_[l]) {
1429 clause_can_be_simplified = true;
1430 break;
1431 } else {
1432 if (score_only) {
1433 new_score_ += clause_weight + clause.size();
1434 } else {
1435 resolvant_.push_back(l);
1436 AddClause(resolvant_);
1437 resolvant_.pop_back();
1438 }
1439 }
1440 }
1441
1442 // Resolution with non-binary clauses.
1443 if (!with_binary_only && !clause_can_be_simplified) {
1444 auto& clause_containing_not_lit = literal_to_clauses_[lit.NegatedIndex()];
1445 for (int j = 0; j < clause_containing_not_lit.size(); ++j) {
1446 if (score_only && new_score_ > score_threshold_) break;
1447 const ClauseIndex other_index = clause_containing_not_lit[j];
1448 const auto other = clauses_[other_index]->AsSpan();
1449 if (other.empty()) continue;
1450 bool trivial = false;
1451 int extra_size = 0;
1452 for (const Literal l : other) {
1453 // TODO(user): we can optimize this by updating it outside the loop.
1454 ++num_inspected_literals_;
1455 if (l == lit.Negated()) continue;
1456 if (marked_[l.NegatedIndex()]) {
1457 trivial = true;
1458 break;
1459 }
1460 if (!marked_[l]) {
1461 ++extra_size;
1462 if (!score_only) resolvant_.push_back(l);
1463 }
1464 }
1465 if (trivial) {
1466 if (!score_only) resolvant_.resize(resolvant_.size() - extra_size);
1467 continue;
1468 }
1469
1470 // If this is the case, the other clause is subsumed by the resolvant.
1471 // We can just remove not_lit from it and ignore it.
1472 if (score_only && clause.size() + extra_size <= other.size()) {
1473 // TODO(user): We should have an exact equality here, except if
1474 // presolve is off before the clause are added to the sat solver and
1475 // we have duplicate literals. The code should still work but it
1476 // wasn't written with that in mind nor tested like this, so we should
1477 // just enforce the invariant.
1478 if (false) DCHECK_EQ(clause.size() + extra_size, other.size());
1479 ++num_simplifications_;
1480
1481 // Note that we update the threshold since this clause was counted in
1482 // it.
1483 score_threshold_ -= clause_weight + other.size();
1484
1485 if (extra_size == 0) {
1486 // We have a double self-subsumption. We can just remove this
1487 // clause since it will be subsumed by the clause created in the
1488 // "clause_can_be_simplified" case below.
1489 DeleteClause(clauses_[other_index]);
1490 } else {
1491 if (!RemoveLiteralFromClause(lit.Negated(),
1492 clauses_[other_index])) {
1493 return false;
1494 }
1495 std::swap(clause_containing_not_lit[j],
1496 clause_containing_not_lit.back());
1497 clause_containing_not_lit.pop_back();
1498 --j; // Reprocess the new position.
1499 continue;
1500 }
1501 }
1502
1503 if (extra_size == 0) {
1504 clause_can_be_simplified = true;
1505 break;
1506 } else {
1507 if (score_only) {
1508 // Hack. We do not want to create long clauses during BVE.
1509 if (clause.size() - 1 + extra_size > 100) {
1510 new_score_ = score_threshold_ + 1;
1511 break;
1512 }
1513
1514 new_score_ += clause_weight + clause.size() - 1 + extra_size;
1515 } else {
1516 AddClause(resolvant_);
1517 resolvant_.resize(resolvant_.size() - extra_size);
1518 }
1519 }
1520 }
1521 }
1522
1523 // Note that we need to clear marked before aborting.
1524 for (const Literal l : clause) marked_[l] = false;
1525
1526 // In this case, we simplify and remove the clause from here.
1527 if (clause_can_be_simplified) {
1528 ++num_simplifications_;
1529
1530 // Note that we update the threshold as if this was simplified before.
1531 new_score_ = saved_score;
1532 score_threshold_ -= clause_weight + clause.size();
1533
1534 if (!RemoveLiteralFromClause(lit, clauses_[clause_index])) return false;
1535 std::swap(clause_containing_lit[i], clause_containing_lit.back());
1536 clause_containing_lit.pop_back();
1537 --i; // Reprocess the new position.
1538 }
1539
1540 if (score_only && new_score_ > score_threshold_) return true;
1541
1542 // When this happen, then the clause is blocked (i.e. all its resolvant are
1543 // trivial). So even if we do not actually perform the variable elimination,
1544 // we can still remove this clause. Note that we treat the score as if the
1545 // clause was removed before.
1546 //
1547 // Tricky: The detection only work if we didn't abort the computation above,
1548 // so we do that after the score_threshold_ check.
1549 //
1550 // TODO(user): Also detect blocked clause for not(lit)? It is not as cheap
1551 // though and require more code.
1552 if (score_only && !with_binary_only && !clause_can_be_simplified &&
1553 new_score_ == saved_score) {
1554 ++num_blocked_clauses_;
1555 score_threshold_ -= clause_weight + clause.size();
1556 postsolve_->AddClauseWithSpecialLiteral(lit, clause);
1557 DeleteClause(clauses_[clause_index]);
1558 }
1559 }
1560 return true;
1561}
1562
1563bool BoundedVariableElimination::CrossProduct(BooleanVariable var) {
1564 if (assignment_.VariableIsAssigned(var)) return true;
1565
1566 const Literal lit(var, true);
1567 const Literal not_lit(var, false);
1568 DCHECK(!implication_graph_->IsRedundant(lit));
1569 {
1570 const int s1 = NumClausesContaining(lit);
1571 const int s2 = NumClausesContaining(not_lit);
1572 if (s1 == 0 && s2 == 0) return true;
1573 if (s1 > 0 && s2 == 0) {
1574 num_eliminated_variables_++;
1575 if (!clause_manager_->InprocessingFixLiteral(lit)) return false;
1576 DeleteAllClausesContaining(lit);
1577 return true;
1578 }
1579 if (s1 == 0 && s2 > 0) {
1580 num_eliminated_variables_++;
1581 if (!clause_manager_->InprocessingFixLiteral(not_lit)) return false;
1582 DeleteAllClausesContaining(not_lit);
1583 return true;
1584 }
1585
1586 // Heuristic. Abort if the work required to decide if var should be removed
1587 // seems to big.
1588 if (s1 > 1 && s2 > 1 && s1 * s2 > parameters_.presolve_bve_threshold()) {
1589 return true;
1590 }
1591 }
1592
1593 // TODO(user): swap lit and not_lit for speed? it is unclear if we prefer
1594 // to minimize the number of clause containing lit or not_lit though. Also,
1595 // we might want to alternate since we also detect blocked clause containing
1596 // lit, but don't do it for not_lit.
1597
1598 // Compute the current score.
1599 // TODO(user): cleanup the list lazily at the same time?
1600 int64_t score = 0;
1601 const int clause_weight = parameters_.presolve_bve_clause_weight();
1602 score +=
1603 implication_graph_->DirectImplications(lit).size() * (clause_weight + 2);
1604 score += implication_graph_->DirectImplications(not_lit).size() *
1605 (clause_weight + 2);
1606 for (const ClauseIndex i : literal_to_clauses_[lit]) {
1607 const auto c = clauses_[i]->AsSpan();
1608 if (!c.empty()) score += clause_weight + c.size();
1609 }
1610 for (const ClauseIndex i : literal_to_clauses_[not_lit]) {
1611 const auto c = clauses_[i]->AsSpan();
1612 if (!c.empty()) score += clause_weight + c.size();
1613 }
1614
1615 // Compute the new score after BVE.
1616 // Abort as soon as it crosses the threshold.
1617 //
1618 // TODO(user): Experiment with leaving the implications graph as is. This will
1619 // not remove the variable completely, but it seems interesting since after
1620 // equivalent variable removal and failed literal probing, the cross product
1621 // of the implication always add a quadratic number of implication, except if
1622 // the in (or out) degree is zero or one.
1623 score_threshold_ = score;
1624 new_score_ = implication_graph_->NumImplicationOnVariableRemoval(var) *
1625 (clause_weight + 2);
1626 if (new_score_ > score_threshold_) return true;
1627 if (!ResolveAllClauseContaining</*score_only=*/true,
1628 /*with_binary_only=*/true>(not_lit)) {
1629 return false;
1630 }
1631 if (new_score_ > score_threshold_) return true;
1632 if (!ResolveAllClauseContaining</*score_only=*/true,
1633 /*with_binary_only=*/false>(lit)) {
1634 return false;
1635 }
1636 if (new_score_ > score_threshold_) return true;
1637
1638 // Perform BVE.
1639 //
1640 // TODO(user): If filter_sat_postsolve_clauses is true, only one of the two
1641 // sets need to be kept for postsolve.
1642 if (new_score_ > 0) {
1643 if (!ResolveAllClauseContaining</*score_only=*/false,
1644 /*with_binary_only=*/false>(lit)) {
1645 return false;
1646 }
1647 if (!ResolveAllClauseContaining</*score_only=*/false,
1648 /*with_binary_only=*/true>(not_lit)) {
1649 return false;
1650 }
1651 }
1652
1653 ++num_eliminated_variables_;
1654 implication_graph_->RemoveBooleanVariable(var, &postsolve_->clauses);
1655 DeleteAllClausesContaining(lit);
1656 DeleteAllClausesContaining(not_lit);
1657 return true;
1658}
1659
1660} // namespace sat
1661} // namespace operations_research
double Get() const
Definition timer.h:44
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:30
void Set(IntegerType index)
Definition bitset.h:878
void InprocessingRemoveClause(SatClause *clause)
These must only be called between [Detach/Attach]AllClauses() calls.
Definition clause.cc:382
bool IsRemovable(SatClause *const clause) const
Definition clause.h:225
int64_t literal_size() const
The number of different literals (always twice the number of variables).
Definition clause.h:240
const std::vector< SatClause * > & AllClausesInCreationOrder() const
Definition clause.h:217
bool DetectEquivalencesAndStamp(bool use_transitive_reduction, bool log_info)
bool RemoveFixedAndEquivalentVariables(bool log_info)
bool PresolveLoop(SatPresolveOptions options)
LiteralIndex NegatedIndex() const
Definition sat_base.h:92
LiteralIndex Index() const
Definition sat_base.h:91
absl::Span< const Literal > AsSpan() const
Returns a Span<> representation of the clause.
Definition clause.h:99
int size() const
Number of literals in the clause.
Definition clause.h:69
bool ImplicationIsInTree(Literal a, Literal b) const
bool LiteralIsFalse(Literal literal) const
Definition sat_base.h:185
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:188
void assign(size_type n, const value_type &val)
void push_back(const value_type &val)
void resize(size_type new_size)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:55
const LiteralIndex kNoLiteralIndex(-1)
bool FailedLiteralProbingRound(ProbingOptions options, Model *model)
Definition probing.cc:502
In SWIG mode, we don't want anything besides these top-level includes.
ClosedInterval::Iterator end(ClosedInterval interval)
const bool DEBUG_MODE
Definition radix_sort.h:266
#define RETURN_IF_FALSE(f)
Definition diffn.cc:333
std::deque< std::vector< Literal > > clauses
void AddClauseWithSpecialLiteral(Literal literal, absl::Span< const Literal > clause)
bool log_info
We assume this is also true if –v 1 is activated.
Definition probing.h:228
double deterministic_time_limit
The time budget to spend.
bool extract_binary_clauses_in_probing
See ProbingOptions in probing.h.
#define SOLVER_LOG(logger,...)
Definition logging.h:110