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