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