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