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