Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
sat_inprocessing.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <array>
18#include <bitset>
19#include <cmath>
20#include <cstdint>
21#include <deque>
22#include <limits>
23#include <utility>
24#include <vector>
25
26#include "absl/algorithm/container.h"
27#include "absl/cleanup/cleanup.h"
28#include "absl/container/flat_hash_map.h"
29#include "absl/container/flat_hash_set.h"
30#include "absl/container/inlined_vector.h"
31#include "absl/functional/function_ref.h"
32#include "absl/log/check.h"
33#include "absl/log/log.h"
34#include "absl/log/vlog_is_on.h"
35#include "absl/types/span.h"
39#include "ortools/base/timer.h"
41#include "ortools/sat/clause.h"
46#include "ortools/sat/model.h"
47#include "ortools/sat/probing.h"
52#include "ortools/sat/util.h"
53#include "ortools/util/bitset.h"
58
59namespace operations_research {
60namespace sat {
61
62namespace {
63
64bool SomeLiteralAreAssigned(const VariablesAssignment& assignment,
65 absl::Span<const Literal> literals) {
66 return absl::c_any_of(
67 literals, [&](Literal l) { return assignment.LiteralIsAssigned(l); });
68}
69
70} // namespace
71
73 Literal literal, absl::Span<const Literal> clause) {
74 bool found = false;
75 clauses.emplace_back(clause.begin(), clause.end());
76 for (int i = 0; i < clause.size(); ++i) {
77 if (clause[i] == literal) {
78 found = true;
79 std::swap(clauses.back()[0], clauses.back()[i]);
80 break;
81 }
82 }
83 CHECK(found);
84}
85
86#define RETURN_IF_FALSE(f) \
87 if (!(f)) return false;
88
90 WallTimer wall_timer;
91 wall_timer.Start();
92
93 // Mainly useful for development.
94 double probing_time = 0.0;
95 const bool log_round_info = VLOG_IS_ON(2);
96
97 // In the presolve, we run this first as some preprocessing technique might
98 // change the problem clauses in such a way that make our heuristic gate
99 // detection miss some gates. Also, when this applies the reduction in problem
100 // size is huge, so it is just faster to run this early.
101 //
102 // TODO(user): If we remove fixed variables, on some problem like:
103 // ~/SAT24/f0426369f61595aee97055965ee7e6a3-hwmcc12miters-xits-iso-6s111.sanitized.cnf.xz
104 // we don't detect as much equivalences... Understand why. I suspect it is due
105 // to the heuristic for ITE gate that combine two clauses of size 3 to get a
106 // truth table on 4 variables. If one of them become of size 2, we might miss
107 // it. Still we should be more robust to stuff like this.
108 RETURN_IF_FALSE(congruence_closure_->DoOneRound(log_round_info));
109
110 // We currently do the transformations in a given order and restart each time
111 // we did something to make sure that the earlier step cannot strengthen more.
112 // This might not be the best, but it is really good during development phase
113 // to make sure each individual functions is as incremental and as fast as
114 // possible.
115 const double start_dtime = time_limit_->GetElapsedDeterministicTime();
116 const double stop_dtime = start_dtime + options.deterministic_time_limit;
117 while (!time_limit_->LimitReached() &&
118 time_limit_->GetElapsedDeterministicTime() <= stop_dtime) {
119 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
120 if (!LevelZeroPropagate()) return false;
121
122 // This one is fast since only new implications or new fixed variables are
123 // considered.
124 RETURN_IF_FALSE(implication_graph_->RemoveDuplicatesAndFixedVariables());
125
126 // This also prepare the stamping below so that we do that on a DAG and do
127 // not consider potential new implications added by
128 // RemoveFixedAndEquivalentVariables().
130 log_round_info));
131
132 // TODO(user): This should/could be integrated with the stamping since it
133 // seems better to do just one loop instead of two over all clauses. Because
134 // of memory access. it isn't that clear though.
136
137 // IMPORTANT: Since we only run this on pure sat problem, we can just
138 // get rid of equivalent variable right away and do not need to keep them
139 // in the implication_graph_ for propagation.
140 //
141 // This is needed for the correctness of the bounded variable elimination.
142 implication_graph_->RemoveAllRedundantVariables(&postsolve_->clauses);
143
144 RETURN_IF_FALSE(stamping_simplifier_->DoOneRound(log_round_info));
145
146 // We wait for the fix-point to be reached before doing the other
147 // simplifications below.
149 !implication_graph_->IsDag()) {
150 continue;
151 }
152
155 !implication_graph_->IsDag()) {
156 continue;
157 }
158
159 // SAT sweeping has a small dtime limit, so do it before other heuristics
160 // exhaust our budget.
161 if (options.use_equivalence_sat_sweeping &&
162 stop_dtime > time_limit_->GetElapsedDeterministicTime()) {
163 auto inner_model_inprocessing = [&](Model* inner_model) {
164 inner_model->GetOrCreate<SatParameters>()
165 ->set_inprocessing_use_sat_sweeping(false);
166 inner_model->GetOrCreate<Inprocessing>()->InprocessingRound();
167 };
170 equivalence_sat_sweeping_->DoOneRound(inner_model_inprocessing));
172 implication_graph_->RemoveAllRedundantVariables(&postsolve_->clauses);
173 }
174
175 // TODO(user): Combine the two? this way we don't create a full literal <->
176 // clause graph twice. It might make sense to reach the BCE fix point which
177 // is unique before each variable elimination.
178 if (!params_.fill_tightened_domains_in_response()) {
179 blocked_clause_simplifier_->DoOneRound(log_round_info);
180 }
181
182 // TODO(user): this break some binary graph invariant. Fix!
184 RETURN_IF_FALSE(bounded_variable_elimination_->DoOneRound(log_round_info));
186
187 // Probing.
188 const double saved_wtime = wall_timer.Get();
189 const double time_left =
190 stop_dtime - time_limit_->GetElapsedDeterministicTime();
191 if (time_left <= 0) break;
192 ProbingOptions probing_options;
193 probing_options.log_info = log_round_info;
194 probing_options.deterministic_limit = time_left;
195 probing_options.extract_binary_clauses =
197 RETURN_IF_FALSE(failed_literal_probing_->DoOneRound(probing_options));
198 probing_time += wall_timer.Get() - saved_wtime;
199
201 !implication_graph_->IsDag()) {
202 continue;
203 }
204
205 break;
206 }
207
208 // Tricky: It is important to clean-up any potential equivalence left in
209 // case we aborted early due to the limit.
211 if (!LevelZeroPropagate()) return false;
212
213 // TODO(user): Maintain the total number of literals in the watched clauses.
215 logger_, "[Pure SAT presolve]",
216 " num_fixed: ", FormatCounter(trail_->Index()), " num_equiv: ",
217 FormatCounter(implication_graph_->num_current_equivalences() / 2), "/",
218 FormatCounter(sat_solver_->NumVariables()), " num_implications: ",
219 FormatCounter(implication_graph_->ComputeNumImplicationsForLog()),
220 " num_watched_clauses: ",
221 FormatCounter(clause_manager_->num_watched_clauses()),
222 " dtime: ", time_limit_->GetElapsedDeterministicTime() - start_dtime, "/",
223 options.deterministic_time_limit, " wtime: ", wall_timer.Get(),
224 " non-probing time: ", (wall_timer.Get() - probing_time));
225 return true;
226}
227
229 DCHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
230 if (sat_solver_->ModelIsUnsat()) return false;
231 WallTimer wall_timer;
232 wall_timer.Start();
233
234 const bool log_info = VLOG_IS_ON(2);
235 const bool log_round_info = VLOG_IS_ON(3);
236 const double start_dtime = time_limit_->GetElapsedDeterministicTime();
237
238 // Mainly useful for development.
239 double probing_time = 0.0;
240
241 // Store the dtime of the first call (first restart) and wait for the next
242 // restart.
243 if (first_inprocessing_call_) {
244 reference_dtime_ = start_dtime;
245 first_inprocessing_call_ = false;
246 return true;
247 }
248
249 // Try to spend a given ratio of time in the inprocessing.
250 //
251 // TODO(user): Tune the heuristic, in particular, with the current code we
252 // start some inprocessing before the first search.
253 const double diff = start_dtime - reference_dtime_;
254 if (total_dtime_ > params_.inprocessing_dtime_ratio() * diff) {
255 return true;
256 }
257
258 // LP Propagation during inprocessing can be really slow, so we temporarily
259 // disable it.
260 //
261 // TODO(user): The LP and incremental structure will still be called though,
262 // which can take some time, try to disable it more cleanly.
263 std::vector<std::pair<LinearProgrammingConstraint*, bool>> saved_state;
264 for (LinearProgrammingConstraint* lp : *all_lp_constraints_) {
265 saved_state.push_back({lp, lp->PropagationIsEnabled()});
266 lp->EnablePropagation(false);
267 }
268 auto cleanup = absl::MakeCleanup([&saved_state]() {
269 for (const auto [lp, old_value] : saved_state) {
270 lp->EnablePropagation(old_value);
271 }
272 });
273
274 // We make sure we do not "pollute" the current saved polarities. We will
275 // restore them at the end.
276 //
277 // TODO(user): We should probably also disable the variable/clauses activity
278 // updates.
279 decision_policy_->MaybeEnablePhaseSaving(/*save_phase=*/false);
280
281 RETURN_IF_FALSE(implication_graph_->RemoveDuplicatesAndFixedVariables());
282 RETURN_IF_FALSE(DetectEquivalencesAndStamp(true, log_round_info));
285
286 // Probing.
287 //
288 // TODO(user): right now we can't run probing if the solver is configured
289 // with assumption. Fix.
290 if (params_.inprocessing_probing_dtime() > 0.0 &&
291 sat_solver_->AssumptionLevel() == 0) {
292 const double saved_wtime = wall_timer.Get();
293 ProbingOptions probing_options;
294 probing_options.log_info = log_round_info;
295 probing_options.deterministic_limit = params_.inprocessing_probing_dtime();
296 probing_options.extract_binary_clauses = true;
297 RETURN_IF_FALSE(failed_literal_probing_->DoOneRound(probing_options));
298 probing_time += wall_timer.Get() - saved_wtime;
299 }
300
301 RETURN_IF_FALSE(DetectEquivalencesAndStamp(true, log_round_info));
304
305 RETURN_IF_FALSE(stamping_simplifier_->DoOneRound(log_round_info));
308
309 if (params_.inprocessing_minimization_dtime() > 0.0) {
310 RETURN_IF_FALSE(vivifier_->MinimizeByPropagation(
311 log_round_info, params_.inprocessing_minimization_dtime()));
312 }
313
314 // TODO(user): Think about the right order in this function.
315 if (params_.inprocessing_use_congruence_closure()) {
317 RETURN_IF_FALSE(implication_graph_->RemoveDuplicatesAndFixedVariables());
318 RETURN_IF_FALSE(congruence_closure_->DoOneRound(log_round_info));
319 }
320
324
325 // TODO(user): try to enable these? The problem is that we can only remove
326 // variables not used the non-pure SAT part of a model.
327 if (/*DISABLES_CODE*/ (false)) {
328 blocked_clause_simplifier_->DoOneRound(log_round_info);
329 RETURN_IF_FALSE(bounded_variable_elimination_->DoOneRound(log_round_info));
330 }
332
333 if (params_.inprocessing_use_sat_sweeping()) {
334 auto inner_model_inprocessing = [&](Model* inner_model) {
335 inner_model->GetOrCreate<SatParameters>()
336 ->set_inprocessing_use_sat_sweeping(false);
337 inner_model->GetOrCreate<Inprocessing>()->InprocessingRound();
338 };
340 equivalence_sat_sweeping_->DoOneRound(inner_model_inprocessing));
342 }
343 sat_solver_->AdvanceDeterministicTime(time_limit_);
344 total_dtime_ += time_limit_->GetElapsedDeterministicTime() - start_dtime;
345 if (log_info) {
346 const int num_fixed = trail_->Index();
347 const int num_equiv = implication_graph_->num_current_equivalences() / 2;
348
350 logger_, "Inprocessing.", " fixed:", FormatCounter(trail_->Index()),
351 " equiv:", FormatCounter(num_equiv), " left:",
352 FormatCounter(sat_solver_->NumVariables() - num_fixed - num_equiv),
353 " binary:",
354 FormatCounter(implication_graph_->ComputeNumImplicationsForLog()),
355 " clauses:",
356 FormatCounter(clause_manager_->num_watched_clauses() -
357 clause_manager_->num_removable_clauses()),
358 "|", FormatCounter(clause_manager_->num_removable_clauses()), "|",
359 FormatCounter(sat_solver_->counters().num_deleted_clauses), "|",
360 FormatCounter(sat_solver_->num_failures()),
361 " minimization:", FormatCounter(vivifier_->last_num_vivified()), "|",
362 FormatCounter(vivifier_->last_num_literals_removed()),
363 " dtime:", time_limit_->GetElapsedDeterministicTime() - start_dtime,
364 " wtime:", wall_timer.Get(),
365 " np_wtime:", (wall_timer.Get() - probing_time));
366 }
367
368 DCHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
369 decision_policy_->MaybeEnablePhaseSaving(/*save_phase=*/true);
370 return true;
371}
372
373#undef RETURN_IF_FALSE
374
376 const int64_t new_num_fixed_variables = trail_->Index();
377 return last_num_fixed_variables_ < new_num_fixed_variables;
378}
379
381 const int64_t new_num_redundant_literals =
382 implication_graph_->num_redundant_literals();
383 return last_num_redundant_literals_ < new_num_redundant_literals;
384}
385
387 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
388 clause_manager_->AttachAllClauses();
389 if (!sat_solver_->Propagate()) {
390 // This adds the UNSAT proof to the LRAT handler, if any.
391 sat_solver_->ProcessCurrentConflict();
392 return false;
393 }
394 return true;
395}
396
397// It make sense to do the pre-stamping right after the equivalence detection
398// since it needs a DAG and can detect extra failed literal.
399bool Inprocessing::DetectEquivalencesAndStamp(bool use_transitive_reduction,
400 bool log_info) {
401 if (!LevelZeroPropagate()) return false;
402 implication_graph_->RemoveFixedVariables();
403 if (!implication_graph_->IsDag()) {
404 // TODO(user): consider doing the transitive reduction after each SCC.
405 // It might be slow but we could try to make it more incremental to
406 // compensate and it should allow further reduction.
407 if (!implication_graph_->DetectEquivalences(log_info)) return false;
408 if (!LevelZeroPropagate()) return false;
409 if (use_transitive_reduction) {
410 if (!implication_graph_->ComputeTransitiveReduction(log_info)) {
411 return false;
412 }
413 if (!LevelZeroPropagate()) return false;
414 }
415 }
416
417 if (!stamping_simplifier_->ComputeStampsForNextRound(log_info)) return false;
418 return LevelZeroPropagate();
419}
420
422 // Preconditions.
423 //
424 // TODO(user): The level zero is required because we remove fixed variables
425 // but if we split this into two functions, we could rewrite clause at any
426 // level.
427 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
428 if (!LevelZeroPropagate()) return false;
429
430 // Start the round.
431 WallTimer wall_timer;
432 wall_timer.Start();
433
434 int64_t num_removed_literals = 0;
435 int64_t num_inspected_literals = 0;
436
437 // We use a huge "limit" in case we have some bad interactions and we need to
438 // loop often to reach the fixed point.
439 while (num_inspected_literals < 1e10) {
440 // Test if some work is needed.
441 //
442 // TODO(user): If only new fixed variables are there, we can use a faster
443 // function. We should also merge the code with the deletion code in
444 // sat_solver_.cc, but that require some refactoring of the dependence
445 // between files.
446 const int64_t new_num_redundant_literals =
447 implication_graph_->num_redundant_literals();
448 const int64_t new_num_fixed_variables = trail_->Index();
449 if (last_num_redundant_literals_ == new_num_redundant_literals &&
450 last_num_fixed_variables_ == new_num_fixed_variables) {
451 return true;
452 }
453 last_num_fixed_variables_ = new_num_fixed_variables;
454 last_num_redundant_literals_ = new_num_redundant_literals;
455
456 // We need this temporary vector for the LRAT proof settings, otherwise
457 // we could just have done an in-place transformation.
458 std::vector<Literal> new_clause;
459
460 // Used to mark clause literals.
461 const int num_literals(sat_solver_->NumVariables() * 2);
462 util_intops::StrongVector<LiteralIndex, bool> marked(num_literals, false);
463
464 clause_manager_->DeleteRemovedClauses();
465 clause_manager_->DetachAllClauses();
466 for (SatClause* clause : clause_manager_->AllClausesInCreationOrder()) {
467 bool removed = false;
468 bool need_rewrite = false;
469
470 // We first do a loop to see if there is anything to do.
471 for (const Literal l : clause->AsSpan()) {
472 if (assignment_.LiteralIsTrue(l)) {
473 DCHECK(lrat_proof_handler_ == nullptr ||
474 trail_->GetUnitClauseId(l.Variable()) != kNoClauseId);
475 clause_manager_->LazyDelete(clause,
477 num_removed_literals += clause->size();
478 removed = true;
479 break;
480 }
481 if (assignment_.LiteralIsFalse(l) ||
482 implication_graph_->IsRedundant(l)) {
483 need_rewrite = true;
484 break;
485 }
486 }
487
488 num_inspected_literals += clause->size();
489 if (removed || !need_rewrite) continue;
490 num_inspected_literals += clause->size();
491
492 // Rewrite the clause.
493 new_clause.clear();
494 clause_ids_.clear();
495 for (const Literal l : clause->AsSpan()) {
496 const Literal r = implication_graph_->RepresentativeOf(l);
497 if (lrat_proof_handler_ != nullptr) {
498 if (!marked[r] && assignment_.LiteralIsFalse(r)) {
499 clause_ids_.push_back(trail_->GetUnitClauseId(r.Variable()));
500 }
501 if (r != l) {
502 clause_ids_.push_back(
503 implication_graph_->GetClauseId(l.Negated(), r));
504 }
505 }
506 if (marked[r] || assignment_.LiteralIsFalse(r)) {
507 continue;
508 }
509 if (marked[r.NegatedIndex()] || assignment_.LiteralIsTrue(r)) {
510 clause_manager_->LazyDelete(
512 num_removed_literals += clause->size();
513 removed = true;
514 break;
515 }
516 marked[r] = true;
517 new_clause.push_back(r);
518 }
519
520 // Restore marked.
521 for (const Literal l : new_clause) marked[l] = false;
522 if (removed) continue;
523
524 if (lrat_proof_handler_ != nullptr) {
525 clause_ids_.push_back(clause_manager_->GetClauseId(clause));
526 }
527 num_removed_literals += clause->size() - new_clause.size();
528 if (!clause_manager_->InprocessingRewriteClause(clause, new_clause,
529 clause_ids_)) {
530 return false;
531 }
532 }
533
534 // If clause became binary, make sure to clean up the relevant implication
535 // lists. This should be fast in all cases since it is incremental.
536 //
537 // Tricky: This might fix more variables in some corner case, so we need to
538 // loop to reach the "fixed point" and maintain the invariant that no clause
539 // contain fixed variable.
540 if (!implication_graph_->RemoveDuplicatesAndFixedVariables()) return false;
541 }
542
543 // Invariant. There should be no clause with fixed variables left.
544 if (DEBUG_MODE) {
545 for (SatClause* clause : clause_manager_->AllClausesInCreationOrder()) {
546 CHECK(!SomeLiteralAreAssigned(trail_->Assignment(), clause->AsSpan()));
547 }
548 }
549
550 // TODO(user): find a way to auto-tune that after a run on borg...
551 const double dtime = static_cast<double>(num_inspected_literals) * 1e-8;
552 time_limit_->AdvanceDeterministicTime(dtime);
553 LOG_IF(INFO, log_info) << "Cleanup. num_removed_literals: "
554 << num_removed_literals << " dtime: " << dtime
555 << " wtime: " << wall_timer.Get();
556
557 return true;
558}
559
560// TODO(user): Use better work limits, see SAT09.CRAFTED.ramseycube.Q3inK12
561//
562// TODO(user): Be more incremental, each time a clause is added/reduced track
563// which literal are impacted? Also try to do orthogonal reductions from one
564// round to the next.
566 WallTimer wall_timer;
567 wall_timer.Start();
568
569 int64_t num_subsumed_clauses = 0;
570 int64_t num_removed_literals = 0;
571 int64_t num_inspected_signatures = 0;
572 int64_t num_inspected_literals = 0;
573
574 // We need this temporary vector for the LRAT proof settings, otherwise
575 // we could just have done an in-place transformation.
576 std::vector<Literal> new_clause;
577
578 // This function needs the watcher to be detached as we might remove some
579 // of the watched literals.
580 //
581 // TODO(user): We could do that only if we do some reduction, but this is
582 // quite fast though.
583 clause_manager_->DeleteRemovedClauses();
584 clause_manager_->DetachAllClauses();
585
586 // Process clause by increasing sizes.
587 // TODO(user): probably faster without the size indirection.
588 std::vector<SatClause*> clauses =
589 clause_manager_->AllClausesInCreationOrder();
590 absl::c_stable_sort(clauses, [](SatClause* a, SatClause* b) {
591 return a->size() < b->size();
592 });
593
594 // Used to mark clause literals.
595 const LiteralIndex num_literals(sat_solver_->NumVariables() * 2);
596 SparseBitset<LiteralIndex> marked(num_literals);
597
598 // Clause index in clauses.
599 // TODO(user): Storing signatures here might be faster?
601 one_watcher(num_literals.value());
602
603 // Clause signatures in the same order as clauses.
604 std::vector<uint64_t> signatures(clauses.size());
605
606 // Literals which can be removed, and the reason why.
607 std::vector<std::pair<Literal, SatClause*>> candidates_for_removal;
608 for (int clause_index = 0; clause_index < clauses.size(); ++clause_index) {
609 SatClause* clause = clauses[clause_index];
610 DCHECK(!SomeLiteralAreAssigned(trail_->Assignment(), clause->AsSpan()));
611
612 // TODO(user): Better abort limit. We could also limit the watcher sizes and
613 // never look at really long clauses. Note that for an easier
614 // incrementality, it is better to reach some kind of completion so we know
615 // what new stuff need to be done.
616 if (num_inspected_literals + num_inspected_signatures > 1e9) {
617 break;
618 }
619
620 // Check for subsumption, note that this currently ignore all clauses in the
621 // binary implication graphs. Stamping is doing some of that (and some also
622 // happen during probing), but we could consider only direct implications
623 // here and be a bit more exhaustive than what stamping do with them (at
624 // least for node with many incoming and outgoing implications).
625 //
626 // TODO(user): Do some reduction using binary clauses. Note that only clause
627 // that never propagated since last round need to be checked for binary
628 // subsumption.
629
630 // Compute hash and mark literals.
631 uint64_t signature = 0;
632 marked.ResetAllToFalse();
633 for (const Literal l : clause->AsSpan()) {
634 marked.Set(l.Index());
635 signature |= (uint64_t{1} << (l.Variable().value() % 64));
636 }
637
638 // Look for clause that subsumes this one. Note that because we inspect
639 // all one watcher lists for the literals of this clause, if a clause is
640 // included inside this one, it must appear in one of these lists.
641 bool removed = false;
642 candidates_for_removal.clear();
643 const uint64_t mask = ~signature;
644 for (const Literal l : clause->AsSpan()) {
645 num_inspected_signatures += one_watcher[l].size();
646 for (const int i : one_watcher[l]) {
647 if ((mask & signatures[i]) != 0) continue;
648
649 bool subsumed = true;
650 bool stengthen = true;
651 LiteralIndex to_remove = kNoLiteralIndex;
652 num_inspected_literals += clauses[i]->size();
653 for (const Literal o : clauses[i]->AsSpan()) {
654 if (!marked[o]) {
655 subsumed = false;
656 if (to_remove == kNoLiteralIndex && marked[o.NegatedIndex()]) {
657 to_remove = o.NegatedIndex();
658 } else {
659 stengthen = false;
660 break;
661 }
662 }
663 }
664 if (subsumed) {
665 ++num_subsumed_clauses;
666 num_removed_literals += clause->size();
667 clause_manager_->LazyDelete(
669 removed = true;
670 break;
671 }
672 if (stengthen) {
673 CHECK_NE(kNoLiteralIndex, to_remove);
674 candidates_for_removal.emplace_back(Literal(to_remove), clauses[i]);
675 }
676 }
677 if (removed) break;
678 }
679 if (removed) continue;
680
681 // For strengthenning we also need to check the negative watcher lists.
682 for (const Literal l : clause->AsSpan()) {
683 num_inspected_signatures += one_watcher[l.NegatedIndex()].size();
684 for (const int i : one_watcher[l.NegatedIndex()]) {
685 if ((mask & signatures[i]) != 0) continue;
686
687 bool stengthen = true;
688 num_inspected_literals += clauses[i]->size();
689 for (const Literal o : clauses[i]->AsSpan()) {
690 if (o == l.Negated()) continue;
691 if (!marked[o]) {
692 stengthen = false;
693 break;
694 }
695 }
696 if (stengthen) {
697 candidates_for_removal.emplace_back(l, clauses[i]);
698 }
699 }
700 }
701
702 // Any literal here can be removed, but afterwards the other might not. For
703 // now we just remove the first one.
704 //
705 // TODO(user): remove first and see if other still removable. Alternatively
706 // use a "removed" marker and redo a check for each clause that simplifies
707 // this one? Or just remove the first one, and wait for next round.
708 if (!candidates_for_removal.empty()) {
709 new_clause.clear();
710 for (const Literal l : clause->AsSpan()) {
711 if (l == candidates_for_removal[0].first) continue;
712 new_clause.push_back(l);
713 }
714 CHECK_EQ(new_clause.size() + 1, clause->size());
715 CHECK_GE(new_clause.size(), 2); // No-unit here.
716
717 num_removed_literals += clause->size() - new_clause.size();
718 if (lrat_proof_handler_ != nullptr) {
719 if (!clause_manager_->InprocessingRewriteClause(
720 clause, new_clause,
721 {clause_manager_->GetClauseId(candidates_for_removal[0].second),
722 clause_manager_->GetClauseId(clause)})) {
723 return false;
724 }
725 } else if (!clause_manager_->InprocessingRewriteClause(clause,
726 new_clause)) {
727 return false;
728 }
729 if (clause->size() == 0) continue;
730
731 // Recompute signature.
732 signature = 0;
733 for (const Literal l : clause->AsSpan()) {
734 signature |= (uint64_t{1} << (l.Variable().value() % 64));
735 }
736 }
737
738 // Register one literal to watch. Any literal works, but we choose the
739 // smallest list.
740 //
741 // TODO(user): No need to add this clause if we know it cannot subsume
742 // any new clause since last round. i.e. unchanged clause that do not
743 // contains any literals of newly added clause do not need to be added
744 // here. We can track two bitset in LiteralWatchers via a register
745 // mechanism:
746 // - literal of newly watched clauses since last clear.
747 // - literal of reduced clauses since last clear.
748 //
749 // Important: we can only use this clause to subsume/strenghten others if
750 // it cannot be deleted later.
751 if (!clause_manager_->IsRemovable(clause)) {
752 int min_size = std::numeric_limits<int32_t>::max();
753 LiteralIndex min_literal = kNoLiteralIndex;
754 for (const Literal l : clause->AsSpan()) {
755 if (one_watcher[l].size() < min_size) {
756 min_size = one_watcher[l].size();
757 min_literal = l.Index();
758 }
759 }
760
761 // TODO(user): We could/should sort the literal in this clause by
762 // using literals that appear in a small number of clauses first so that
763 // we maximize the chance of early abort in the critical loops above.
764 //
765 // TODO(user): We could also move the watched literal first so we always
766 // skip it.
767 signatures[clause_index] = signature;
768 one_watcher[min_literal].push_back(clause_index);
769 }
770 }
771
772 // We might have fixed variables, finish the propagation.
773 if (!LevelZeroPropagate()) return false;
774
775 // TODO(user): tune the deterministic time.
776 const double dtime = static_cast<double>(num_inspected_signatures) * 1e-8 +
777 static_cast<double>(num_inspected_literals) * 5e-9;
778 time_limit_->AdvanceDeterministicTime(dtime);
779 LOG_IF(INFO, log_info) << "Subsume. num_removed_literals: "
780 << num_removed_literals
781 << " num_subsumed: " << num_subsumed_clauses
782 << " dtime: " << dtime
783 << " wtime: " << wall_timer.Get();
784 return true;
785}
786
788 WallTimer wall_timer;
789 wall_timer.Start();
790
791 dtime_ = 0.0;
792 num_subsumed_clauses_ = 0;
793 num_removed_literals_ = 0;
794 num_fixed_ = 0;
795
796 if (implication_graph_->IsEmpty()) return true;
797
798 if (!stamps_are_already_computed_) {
799 // We need a DAG so that we don't have cycle while we sample the tree.
800 // TODO(user): We could probably deal with it if needed so that we don't
801 // need to do equivalence detection each time we want to run this.
802 implication_graph_->RemoveFixedVariables();
803 if (!implication_graph_->DetectEquivalences(log_info)) return true;
805 if (!ComputeStamps()) return false;
806 }
807 stamps_are_already_computed_ = false;
808 if (!ProcessClauses()) return false;
809
810 // Note that num_removed_literals_ do not count the literals of the subsumed
811 // clauses.
812 time_limit_->AdvanceDeterministicTime(dtime_);
813 LOG_IF(INFO, log_info) << "Stamping. num_removed_literals: "
814 << num_removed_literals_
815 << " num_subsumed: " << num_subsumed_clauses_
816 << " num_fixed: " << num_fixed_ << " dtime: " << dtime_
817 << " wtime: " << wall_timer.Get();
818 return true;
819}
820
822 WallTimer wall_timer;
823 wall_timer.Start();
824 dtime_ = 0.0;
825 num_fixed_ = 0;
826
827 if (implication_graph_->IsEmpty()) return true;
828
829 implication_graph_->RemoveFixedVariables();
830 if (!implication_graph_->DetectEquivalences(log_info)) return true;
832 if (!ComputeStamps()) return false;
833 stamps_are_already_computed_ = true;
834
835 // TODO(user): compute some dtime, it is always zero currently.
836 time_limit_->AdvanceDeterministicTime(dtime_);
837 LOG_IF(INFO, log_info) << "Prestamping."
838 << " num_fixed: " << num_fixed_ << " dtime: " << dtime_
839 << " wtime: " << wall_timer.Get();
840 return true;
841}
842
844 const int size = implication_graph_->literal_size();
845 CHECK(implication_graph_->IsDag()); // so we don't have cycle.
846 parents_.resize(size);
847 for (LiteralIndex i(0); i < size; ++i) {
848 parents_[i] = i; // default.
849 if (implication_graph_->IsRedundant(Literal(i))) continue;
850 if (assignment_.LiteralIsAssigned(Literal(i))) continue;
851
852 // TODO(user): Better algo to not select redundant parent.
853 //
854 // TODO(user): if parents_[x] = y, try not to have parents_[not(y)] = not(x)
855 // because this is not as useful for the simplification power.
856 //
857 // TODO(user): More generally, we could sample a parent while probing so
858 // that we consider all hyper binary implications (in the case we don't add
859 // them to the implication graph already).
860 for (int num_tries = 0; num_tries < 10; ++num_tries) {
861 // We look for a random lit that implies i. For that we take a random
862 // literal in the direct implications of not(i) and reverse it.
863 const LiteralIndex index =
864 implication_graph_->RandomImpliedLiteral(Literal(i).Negated());
865 if (index == kNoLiteralIndex) break;
866
867 const Literal candidate = Literal(index).Negated();
868 if (implication_graph_->IsRedundant(candidate)) continue;
869 if (i == candidate.Index()) continue;
870
871 // We found an interesting parent.
872 parents_[i] = candidate.Index();
873 break;
874 }
875 }
876}
877
879 const int size = implication_graph_->literal_size();
880
881 // Adjacency list representation of the parents_ tree.
884 std::vector<LiteralIndex> children;
885
886 // Compute sizes.
887 sizes.assign(size, 0);
888 for (LiteralIndex i(0); i < size; ++i) {
889 if (parents_[i] == i) continue; // leaf.
890 sizes[parents_[i]]++;
891 }
892
893 // Compute starts in the children_ vector for each node.
894 starts.resize(size + 1); // We use a sentinel.
895 starts[LiteralIndex(0)] = 0;
896 for (LiteralIndex i(1); i <= size; ++i) {
897 starts[i] = starts[i - 1] + sizes[i - 1];
898 }
899
900 // Fill children. This messes up starts_.
901 children.resize(size);
902 for (LiteralIndex i(0); i < size; ++i) {
903 if (parents_[i] == i) continue; // leaf.
904 children[starts[parents_[i]]++] = i;
905 }
906
907 // Reset starts to correct value.
908 for (LiteralIndex i(0); i < size; ++i) {
909 starts[i] -= sizes[i];
910 }
911
912 if (DEBUG_MODE) {
913 CHECK_EQ(starts[LiteralIndex(0)], 0);
914 for (LiteralIndex i(1); i <= size; ++i) {
915 CHECK_EQ(starts[i], starts[i - 1] + sizes[i - 1]);
916 }
917 }
918
919 // Perform a DFS from each root to compute the stamps.
920 int64_t stamp = 0;
921 first_stamps_.resize(size);
922 last_stamps_.resize(size);
923 marked_.assign(size, false);
924 for (LiteralIndex i(0); i < size; ++i) {
925 if (parents_[i] != i) continue; // Not a root.
926 DCHECK(!marked_[i]);
927 const LiteralIndex tree_root = i;
928 dfs_stack_.push_back(i);
929 while (!dfs_stack_.empty()) {
930 const LiteralIndex top = dfs_stack_.back();
931 if (marked_[top]) {
932 dfs_stack_.pop_back();
933 last_stamps_[top] = stamp++;
934 continue;
935 }
936 first_stamps_[top] = stamp++;
937 marked_[top] = true;
938
939 // Failed literal detection. If the negation of top is in the same
940 // tree, we can fix the LCA of top and its negation to false.
941 if (marked_[Literal(top).NegatedIndex()] &&
942 first_stamps_[Literal(top).NegatedIndex()] >=
943 first_stamps_[tree_root]) {
944 // Find the LCA.
945 const int first_stamp = first_stamps_[Literal(top).NegatedIndex()];
946 LiteralIndex lca = top;
947 while (first_stamps_[lca] > first_stamp) {
948 lca = parents_[lca];
949 }
950 ++num_fixed_;
951 if (lrat_proof_handler_ != nullptr) {
952 clause_ids_.clear();
953 AppendImplicationChain(Literal(lca), Literal(top), clause_ids_);
954 AppendImplicationChain(Literal(lca), Literal(top).Negated(),
955 clause_ids_);
956 }
957 if (!clause_manager_->InprocessingFixLiteral(Literal(lca).Negated(),
958 clause_ids_)) {
959 return false;
960 }
961 }
962
963 const int end = starts[top + 1]; // Ok with sentinel.
964 for (int j = starts[top]; j < end; ++j) {
965 DCHECK_NE(top, children[j]); // We removed leaf self-loop.
966 DCHECK(!marked_[children[j]]); // This is a tree.
967 dfs_stack_.push_back(children[j]);
968 }
969 }
970 }
971 DCHECK_EQ(stamp, 2 * size);
972 return true;
973}
974
975namespace {
976class LratStampingHelper {
977 public:
978 void NewClause(absl::Span<const Literal> clause) {
979 clause_ = clause;
980 has_literals_to_remove_ = false;
981 }
982
983 void AddToRemove(int index, int reason, bool negated) {
984 if (!has_literals_to_remove_) {
985 has_literals_to_remove_ = true;
986 status_.assign(clause_.size(), {0, 0, false, false});
987 }
988 status_[index].reason = reason;
989 status_[index].negated = negated;
990 status_[index].to_remove = true;
991 }
992
993 void AppendImplicationChains(
994 absl::FunctionRef<void(Literal, Literal, bool)> append_chain) {
995 if (!has_literals_to_remove_) return;
996 // The proof for removing a literal 'a' can depend on another removed
997 // literal 'b'. In this case the proof that 'b' can be removed must appear
998 // before the one for 'a'. To ensure this we process them in topological
999 // order.
1000 for (const Status& status : status_) {
1001 if (status.to_remove && status_[status.reason].to_remove) {
1002 status_[status.reason].num_children++;
1003 }
1004 }
1005 reverse_removal_order_.clear();
1006 for (int i = 0; i < status_.size(); ++i) {
1007 if (status_[i].to_remove && status_[i].num_children == 0) {
1008 reverse_removal_order_.push_back(i);
1009 }
1010 }
1011 int num_visited = 0;
1012 while (num_visited < reverse_removal_order_.size()) {
1013 int parent = status_[reverse_removal_order_[num_visited++]].reason;
1014 if (--status_[parent].num_children == 0) {
1015 reverse_removal_order_.push_back(parent);
1016 }
1017 }
1018 for (int i = reverse_removal_order_.size() - 1; i >= 0; --i) {
1019 const int index = reverse_removal_order_[i];
1020 const Status& status = status_[index];
1021 DCHECK(status.to_remove);
1022 if (status.negated) {
1023 append_chain(clause_[status.reason].Negated(), clause_[index].Negated(),
1024 /*reversed=*/false);
1025 } else {
1026 append_chain(clause_[index], clause_[status.reason], /*reversed=*/true);
1027 }
1028 }
1029 }
1030
1031 private:
1032 // The status of each literal in the current clause. The fields other than
1033 // `to_remove` are only used when `to_remove` is true.
1034 struct Status {
1035 // The index in the clause of the other literal explaining why this literal
1036 // can be removed.
1037 int reason;
1038 // The number of literals which must be removed after this one.
1039 int num_children;
1040 // If false, the proof for removing the literal is "literal => ... =>
1041 // clause[reason]" (reversed). If true, it is "not(clause[reason]) =>
1042 // not(literal)" (not reversed). These are not equivalent in practice
1043 // because the StampingSimplifier's parents_ tree (used to find the
1044 // intermediate literals in the chain) may contain an implication but not
1045 // its contrapositive.
1046 bool negated;
1047 // Whether the literal can be removed.
1048 bool to_remove;
1049 };
1050
1051 absl::Span<const Literal> clause_;
1052 bool has_literals_to_remove_;
1053 // Same size as `clause_`, initialized lazily.
1054 std::vector<Status> status_;
1055 // The index of the literals to remove in `clause_`, in reverse removal order.
1056 std::vector<int> reverse_removal_order_;
1057};
1058} // namespace
1059
1061 struct Entry {
1062 int i; // Index in the clause.
1063 bool is_negated; // Correspond to clause[i] or clause[i].Negated();
1064 int start; // Note that all start stamps are different.
1065 int end;
1066 bool operator<(const Entry& o) const { return start < o.start; }
1067 };
1068 std::vector<int> to_remove;
1069 std::vector<Literal> new_clause;
1070 std::vector<Entry> entries;
1071 LratStampingHelper lrat_helper;
1072
1073 clause_manager_->DeleteRemovedClauses();
1074 clause_manager_->DetachAllClauses();
1075 clause_ids_.clear();
1076 for (SatClause* clause : clause_manager_->AllClausesInCreationOrder()) {
1077 const auto span = clause->AsSpan();
1078 if (span.empty()) continue;
1079
1080 // Note that we might fix literal as we perform the loop here, so we do
1081 // need to deal with them.
1082 //
1083 // For a and b in the clause, if not(a) => b is present, then the clause is
1084 // subsumed. If a => b, then a can be removed, and if not(a) => not(b) then
1085 // b can be removed. Nothing can be done if a => not(b).
1086 entries.clear();
1087 for (int i = 0; i < span.size(); ++i) {
1088 if (assignment_.LiteralIsTrue(span[i])) {
1089 clause_manager_->LazyDelete(clause,
1091 break;
1092 }
1093 if (assignment_.LiteralIsFalse(span[i])) continue;
1094 entries.push_back(
1095 {i, false, first_stamps_[span[i]], last_stamps_[span[i]]});
1096 entries.push_back({i, true, first_stamps_[span[i].NegatedIndex()],
1097 last_stamps_[span[i].NegatedIndex()]});
1098 }
1099 if (clause->IsRemoved()) continue;
1100
1101 // The sort should be dominant.
1102 if (!entries.empty()) {
1103 const double n = static_cast<double>(entries.size());
1104 dtime_ += 1.5e-8 * n * std::log(n);
1105 std::sort(entries.begin(), entries.end());
1106 }
1107
1108 Entry top_entry;
1109 top_entry.end = -1; // Sentinel.
1110 to_remove.clear();
1111 if (lrat_proof_handler_ != nullptr) {
1112 lrat_helper.NewClause(span);
1113 }
1114 for (const Entry& e : entries) {
1115 if (e.end < top_entry.end) {
1116 // We found an implication: top_entry => this entry.
1117 const Literal lhs = top_entry.is_negated ? span[top_entry.i].Negated()
1118 : span[top_entry.i];
1119 const Literal rhs = e.is_negated ? span[e.i].Negated() : span[e.i];
1120 DCHECK(ImplicationIsInTree(lhs, rhs));
1121
1122 if (top_entry.is_negated != e.is_negated) {
1123 // Failed literal?
1124 if (top_entry.i == e.i) {
1125 ++num_fixed_;
1126 if (top_entry.is_negated) {
1127 // not(span[i]) => span[i] so span[i] true.
1128 // And the clause is satisfied (so we count it as subsumed).
1129 if (lrat_proof_handler_ != nullptr) {
1130 clause_ids_.clear();
1131 AppendImplicationChain(lhs, rhs, clause_ids_);
1132 }
1133 if (!clause_manager_->InprocessingFixLiteral(span[top_entry.i],
1134 clause_ids_)) {
1135 return false;
1136 }
1137 } else {
1138 // span[i] => not(span[i]) so span[i] false.
1139 if (lrat_proof_handler_ != nullptr) {
1140 clause_ids_.clear();
1141 AppendImplicationChain(lhs, rhs, clause_ids_);
1142 }
1143 if (!clause_manager_->InprocessingFixLiteral(
1144 span[top_entry.i].Negated(), clause_ids_)) {
1145 return false;
1146 }
1147 to_remove.push_back(top_entry.i);
1148 continue;
1149 }
1150 }
1151
1152 // not(a) => b : subsumption.
1153 // a => not(b), we cannot deduce anything, but it might make sense
1154 // to see if not(b) implies anything instead of just keeping
1155 // top_entry. See TODO below.
1156 if (top_entry.is_negated) {
1157 num_subsumed_clauses_++;
1158 clause_manager_->LazyDelete(
1160 break;
1161 }
1162 } else {
1163 CHECK_NE(top_entry.i, e.i);
1164 if (top_entry.is_negated) {
1165 // not(a) => not(b), we can remove b.
1166 to_remove.push_back(e.i);
1167 if (lrat_proof_handler_ != nullptr) {
1168 lrat_helper.AddToRemove(e.i, top_entry.i, /*negated=*/true);
1169 }
1170 } else {
1171 // a => b, we can remove a.
1172 //
1173 // TODO(user): Note that it is okay to still use top_entry, but we
1174 // might miss the removal of b if b => c. Also the paper do things
1175 // differently. Make sure we don't miss any simplification
1176 // opportunites by not changing top_entry. Same in the other
1177 // branches.
1178 to_remove.push_back(top_entry.i);
1179 if (lrat_proof_handler_ != nullptr) {
1180 lrat_helper.AddToRemove(top_entry.i, e.i, /*negated=*/false);
1181 }
1182 }
1183 }
1184 } else {
1185 top_entry = e;
1186 }
1187 }
1188
1189 if (clause->IsRemoved()) continue;
1190
1191 // Strengthen the clause.
1192 if (!to_remove.empty() || entries.size() < span.size()) {
1193 new_clause.clear();
1195 clause_ids_.clear();
1196 int to_remove_index = 0;
1197 for (int i = 0; i < span.size(); ++i) {
1198 if (to_remove_index < to_remove.size() &&
1199 i == to_remove[to_remove_index]) {
1200 ++to_remove_index;
1201 continue;
1202 }
1203 if (assignment_.LiteralIsTrue(span[i])) {
1204 clause_manager_->LazyDelete(clause,
1206 continue;
1207 }
1208 if (assignment_.LiteralIsFalse(span[i])) {
1209 if (lrat_proof_handler_ != nullptr) {
1210 clause_ids_.push_back(trail_->GetUnitClauseId(span[i].Variable()));
1211 }
1212 continue;
1213 }
1214 new_clause.push_back(span[i]);
1215 }
1216 if (lrat_proof_handler_ != nullptr) {
1217 lrat_helper.AppendImplicationChains(
1218 [&](Literal a, Literal b, bool reversed) {
1219 AppendImplicationChain(a, b, clause_ids_, reversed);
1220 });
1221 clause_ids_.push_back(clause_manager_->GetClauseId(clause));
1222 }
1223 num_removed_literals_ += span.size() - new_clause.size();
1224 if (!clause_manager_->InprocessingRewriteClause(clause, new_clause,
1225 clause_ids_)) {
1226 return false;
1227 }
1228 }
1229 }
1230 return true;
1231}
1232
1233void StampingSimplifier::AppendImplicationChain(Literal a, Literal b,
1234 std::vector<ClauseId>& chain,
1235 bool reversed) {
1236 const int initial_size = chain.size();
1237 Literal l = b;
1238 while (l != a) {
1239 chain.push_back(implication_graph_->GetClauseId(
1240 Literal(parents_[l]).Negated(), Literal(l)));
1241 l = Literal(parents_[l]);
1242 }
1243 if (!reversed) {
1244 std::reverse(clause_ids_.begin() + initial_size, clause_ids_.end());
1245 }
1246}
1247
1249 WallTimer wall_timer;
1250 wall_timer.Start();
1251
1252 dtime_ = 0.0;
1253 num_blocked_clauses_ = 0;
1254 num_inspected_literals_ = 0;
1255
1256 InitializeForNewRound();
1257
1258 while (!time_limit_->LimitReached() && !queue_.empty()) {
1259 const Literal l = queue_.front();
1260 in_queue_[l] = false;
1261 queue_.pop_front();
1262
1263 // Avoid doing too much work here on large problem.
1264 // Note that we still what to empty the queue.
1265 if (num_inspected_literals_ <= 1e9) ProcessLiteral(l);
1266 }
1267
1268 // Release some memory.
1269 literal_to_clauses_.clear();
1270
1271 dtime_ += 1e-8 * num_inspected_literals_;
1272 time_limit_->AdvanceDeterministicTime(dtime_);
1273 log_info |= VLOG_IS_ON(2);
1274 LOG_IF(INFO, log_info) << "Blocked clause. num_blocked_clauses: "
1275 << num_blocked_clauses_ << " dtime: " << dtime_
1276 << " wtime: " << wall_timer.Get();
1277}
1278
1279void BlockedClauseSimplifier::InitializeForNewRound() {
1280 clauses_.clear();
1281 clause_manager_->DeleteRemovedClauses();
1282 clause_manager_->DetachAllClauses();
1283 for (SatClause* c : clause_manager_->AllClausesInCreationOrder()) {
1284 // We ignore redundant clause. This shouldn't cause any validity issue.
1285 if (clause_manager_->IsRemovable(c)) continue;
1286
1287 clauses_.push_back(c);
1288 }
1289 const int num_literals = clause_manager_->literal_size();
1290
1291 // TODO(user): process in order of increasing number of clause that contains
1292 // not(l)?
1293 in_queue_.assign(num_literals, true);
1294 for (LiteralIndex l(0); l < num_literals; ++l) {
1295 queue_.push_back(Literal(l));
1296 }
1297
1298 marked_.resize(num_literals);
1299 DCHECK(
1300 std::all_of(marked_.begin(), marked_.end(), [](bool b) { return !b; }));
1301
1302 // TODO(user): because we don't create new clause here we can use a flat
1303 // vector for literal_to_clauses_.
1304 literal_to_clauses_.clear();
1305 literal_to_clauses_.resize(num_literals);
1306 for (ClauseIndex i(0); i < clauses_.size(); ++i) {
1307 for (const Literal l : clauses_[i]->AsSpan()) {
1308 literal_to_clauses_[l].push_back(i);
1309 }
1310 num_inspected_literals_ += clauses_[i]->size();
1311 }
1312}
1313
1314void BlockedClauseSimplifier::ProcessLiteral(Literal current_literal) {
1315 if (assignment_.LiteralIsAssigned(current_literal)) return;
1316 if (implication_graph_->IsRemoved(current_literal)) return;
1317
1318 // We want to check first that this clause will resolve to trivial clause with
1319 // all binary containing not(current_literal). So mark all literal l so that
1320 // current_literal => l.
1321 //
1322 // TODO(user): We do not need to redo that each time we reprocess
1323 // current_literal.
1324 //
1325 // TODO(user): Ignore redundant literals. That might require pushing
1326 // equivalence to the postsolve stack though. Better to simply remove
1327 // these equivalence if we are allowed to and update the postsolve then.
1328 //
1329 // TODO(user): Make this work in the presence of at most ones.
1330 int num_binary = 0;
1331 const std::vector<Literal>& implications =
1332 implication_graph_->DirectImplications(current_literal);
1333 for (const Literal l : implications) {
1334 if (l == current_literal) continue;
1335 ++num_binary;
1336 marked_[l] = true;
1337 }
1338
1339 // TODO(user): We could also mark a small clause containing
1340 // current_literal.Negated(), and make sure we only include in
1341 // clauses_to_process clauses that resolve trivially with that clause.
1342 std::vector<ClauseIndex> clauses_to_process;
1343 for (const ClauseIndex i : literal_to_clauses_[current_literal]) {
1344 if (clauses_[i]->IsRemoved()) continue;
1345
1346 // Blocked with respect to binary clause only? all marked binary should have
1347 // their negation in clause.
1348 //
1349 // TODO(user): Abort if size left is too small.
1350 if (num_binary > 0) {
1351 if (clauses_[i]->size() <= num_binary) continue;
1352 int num_with_negation_marked = 0;
1353 for (const Literal l : clauses_[i]->AsSpan()) {
1354 if (l == current_literal) continue;
1355 if (marked_[l.NegatedIndex()]) {
1356 ++num_with_negation_marked;
1357 }
1358 }
1359 num_inspected_literals_ += clauses_[i]->size();
1360 if (num_with_negation_marked < num_binary) continue;
1361 }
1362 clauses_to_process.push_back(i);
1363 }
1364
1365 // Clear marked.
1366 for (const Literal l : implications) {
1367 marked_[l] = false;
1368 }
1369
1370 // TODO(user): There is a possible optimization: If we mark all literals of
1371 // all the clause to process, we can check that each clause containing
1372 // current_literal.Negated() contains at least one of these literal negated
1373 // other than current_literal. Otherwise none of the clause are blocked.
1374 //
1375 // TODO(user): If a clause cannot be blocked because of another clause, then
1376 // when we call ProcessLiteral(current_literal.Negated()) we can skip some
1377 // inspection.
1378 for (const ClauseIndex i : clauses_to_process) {
1379 const auto c = clauses_[i]->AsSpan();
1380 if (ClauseIsBlocked(current_literal, c)) {
1381 // Reprocess all clauses that have a negated literal in this one as
1382 // some might be blocked now.
1383 //
1384 // TODO(user): Maybe we can remember for which (literal, clause) pair this
1385 // was used as a certificate for "not-blocked" and just reprocess those,
1386 // but it might be memory intensive.
1387 for (const Literal l : c) {
1388 if (!in_queue_[l.NegatedIndex()]) {
1389 in_queue_[l.NegatedIndex()] = true;
1390 queue_.push_back(l.Negated());
1391 }
1392 }
1393
1394 // Add the clause to the postsolving set.
1395 postsolve_->AddClauseWithSpecialLiteral(current_literal, c);
1396
1397 // We can remove a blocked clause.
1398 ++num_blocked_clauses_;
1399 clause_manager_->LazyDelete(clauses_[i], DeletionSourceForStat::BLOCKED);
1400 }
1401 }
1402}
1403
1404// Note that this assume that the binary clauses have already been checked.
1405bool BlockedClauseSimplifier::ClauseIsBlocked(
1406 Literal current_literal, absl::Span<const Literal> clause) {
1407 bool is_blocked = true;
1408 for (const Literal l : clause) marked_[l] = true;
1409
1410 // TODO(user): For faster reprocessing of the same literal, we should move
1411 // all clauses that are used in a non-blocked certificate first in the list.
1412 for (const ClauseIndex i :
1413 literal_to_clauses_[current_literal.NegatedIndex()]) {
1414 if (clauses_[i]->IsRemoved()) continue;
1415 bool some_marked = false;
1416 for (const Literal l : clauses_[i]->AsSpan()) {
1417 // TODO(user): we can be faster here by only updating it at the end?
1418 ++num_inspected_literals_;
1419
1420 if (l == current_literal.Negated()) continue;
1421 if (marked_[l.NegatedIndex()]) {
1422 some_marked = true;
1423 break;
1424 }
1425 }
1426 if (!some_marked) {
1427 is_blocked = false;
1428 break;
1429 }
1430 }
1431
1432 for (const Literal l : clause) marked_[l] = false;
1433 return is_blocked;
1434}
1435
1437 WallTimer wall_timer;
1438 wall_timer.Start();
1439
1440 dtime_ = 0.0;
1441 num_inspected_literals_ = 0;
1442 num_eliminated_variables_ = 0;
1443 num_literals_diff_ = 0;
1444 num_clauses_diff_ = 0;
1445 num_simplifications_ = 0;
1446 num_blocked_clauses_ = 0;
1447
1448 clauses_.clear();
1449 clause_manager_->DeleteRemovedClauses();
1450 clause_manager_->DetachAllClauses();
1451 for (SatClause* c : clause_manager_->AllClausesInCreationOrder()) {
1452 // We ignore redundant clause. This shouldn't cause any validity issue.
1453 // TODO(user): but we shouldn't keep clauses containing removed literals.
1454 // It is still valid to do so, but it should be less efficient.
1455 if (clause_manager_->IsRemovable(c)) continue;
1456
1457 clauses_.push_back(c);
1458 }
1459 const int num_literals = clause_manager_->literal_size();
1460 const int num_variables = num_literals / 2;
1461
1462 literal_to_clauses_.clear();
1463 literal_to_clauses_.resize(num_literals);
1464 literal_to_num_clauses_.assign(num_literals, 0);
1465 for (ClauseIndex i(0); i < clauses_.size(); ++i) {
1466 for (const Literal l : clauses_[i]->AsSpan()) {
1467 literal_to_clauses_[l].push_back(i);
1468 literal_to_num_clauses_[l]++;
1469 }
1470 num_inspected_literals_ += clauses_[i]->size();
1471 }
1472
1473 const int saved_trail_index = trail_->Index();
1474 propagation_index_ = trail_->Index();
1475
1476 need_to_be_updated_.clear();
1477 in_need_to_be_updated_.resize(num_variables);
1478 DCHECK(absl::c_find(in_need_to_be_updated_, true) ==
1479 in_need_to_be_updated_.end());
1480 queue_.Reserve(num_variables);
1481 for (BooleanVariable v(0); v < num_variables; ++v) {
1482 if (assignment_.VariableIsAssigned(v)) continue;
1483 UpdatePriorityQueue(v);
1484 }
1485
1486 marked_.resize(num_literals);
1487 DCHECK(
1488 std::all_of(marked_.begin(), marked_.end(), [](bool b) { return !b; }));
1489
1490 // TODO(user): adapt the dtime limit depending on how much we want to spend on
1491 // inprocessing.
1492 while (!time_limit_->LimitReached() && !queue_.IsEmpty() && dtime_ < 10.0) {
1493 const BooleanVariable top = queue_.Top().var;
1494 queue_.Pop();
1495
1496 // Make sure we fix variables first if needed. Note that because new binary
1497 // clause might appear when we fix variables, we need a loop here.
1498 //
1499 // TODO(user): we might also find new equivalent variable l => var => l
1500 // here, but for now we ignore those.
1501 bool is_unsat = false;
1502 if (!Propagate()) return false;
1503 while (implication_graph_->FindFailedLiteralAroundVar(top, &is_unsat)) {
1504 if (!Propagate()) return false;
1505 }
1506 if (is_unsat) return false;
1507
1508 if (!CrossProduct(top)) return false;
1509
1510 for (const BooleanVariable v : need_to_be_updated_) {
1511 in_need_to_be_updated_[v] = false;
1512
1513 // Currently we never re-add top if we just processed it.
1514 if (v != top) UpdatePriorityQueue(v);
1515 }
1516 need_to_be_updated_.clear();
1517 }
1518
1519 if (!Propagate()) return false;
1520 implication_graph_->CleanupAllRemovedAndFixedVariables();
1521
1522 // Remove all redundant clause containing a removed literal. This avoid to
1523 // re-introduce a removed literal via conflict learning.
1524 for (SatClause* c : clause_manager_->AllClausesInCreationOrder()) {
1525 bool remove = false;
1526 for (const Literal l : c->AsSpan()) {
1527 if (implication_graph_->IsRemoved(l)) {
1528 remove = true;
1529 break;
1530 }
1531 }
1532 if (remove)
1533 clause_manager_->LazyDelete(c, DeletionSourceForStat::ELIMINATED);
1534 }
1535
1536 // Release some memory.
1537 literal_to_clauses_.clear();
1538 literal_to_num_clauses_.clear();
1539
1540 time_limit_->AdvanceDeterministicTime(dtime_);
1541 log_info |= VLOG_IS_ON(2);
1542 LOG_IF(INFO, log_info) << "BVE."
1543 << " num_fixed: "
1544 << trail_->Index() - saved_trail_index
1545 << " num_simplified_literals: " << num_simplifications_
1546 << " num_blocked_clauses_: " << num_blocked_clauses_
1547 << " num_eliminations: " << num_eliminated_variables_
1548 << " num_literals_diff: " << num_literals_diff_
1549 << " num_clause_diff: " << num_clauses_diff_
1550 << " dtime: " << dtime_
1551 << " wtime: " << wall_timer.Get();
1552 return true;
1553}
1554
1555bool BoundedVariableElimination::RemoveLiteralFromClause(
1556 Literal lit, SatClause* sat_clause) {
1557 num_literals_diff_ -= sat_clause->size();
1558 resolvant_.clear();
1559 for (const Literal l : sat_clause->AsSpan()) {
1560 if (l == lit || assignment_.LiteralIsFalse(l)) {
1561 literal_to_num_clauses_[l]--;
1562 continue;
1563 }
1564 if (assignment_.LiteralIsTrue(l)) {
1565 num_clauses_diff_--;
1566 clause_manager_->LazyDelete(sat_clause,
1568 return true;
1569 }
1570 resolvant_.push_back(l);
1571 }
1572 if (!clause_manager_->InprocessingRewriteClause(sat_clause, resolvant_)) {
1573 return false;
1574 }
1575 if (sat_clause->IsRemoved()) {
1576 --num_clauses_diff_;
1577 for (const Literal l : resolvant_) literal_to_num_clauses_[l]--;
1578 } else {
1579 num_literals_diff_ += sat_clause->size();
1580 }
1581 return true;
1582}
1583
1584bool BoundedVariableElimination::Propagate() {
1585 for (; propagation_index_ < trail_->Index(); ++propagation_index_) {
1586 // Make sure we always propagate the binary clauses first.
1587 if (!implication_graph_->Propagate(trail_)) return false;
1588
1589 const Literal l = (*trail_)[propagation_index_];
1590 for (const ClauseIndex index : literal_to_clauses_[l]) {
1591 if (clauses_[index]->IsRemoved()) continue;
1592 num_clauses_diff_--;
1593 num_literals_diff_ -= clauses_[index]->size();
1594 clause_manager_->LazyDelete(clauses_[index],
1596 }
1597 literal_to_clauses_[l].clear();
1598 for (const ClauseIndex index : literal_to_clauses_[l.NegatedIndex()]) {
1599 if (clauses_[index]->IsRemoved()) continue;
1600 if (!RemoveLiteralFromClause(l.Negated(), clauses_[index])) return false;
1601 }
1602 literal_to_clauses_[l.NegatedIndex()].clear();
1603 }
1604 return true;
1605}
1606
1607// Note that we use the estimated size here to make it fast. It is okay if the
1608// order of elimination is not perfect... We can improve on this later.
1609int BoundedVariableElimination::NumClausesContaining(Literal l) {
1610 return literal_to_num_clauses_[l] +
1611 implication_graph_->DirectImplicationsEstimatedSize(l.Negated());
1612}
1613
1614// TODO(user): Only enqueue variable that can be removed.
1615void BoundedVariableElimination::UpdatePriorityQueue(BooleanVariable var) {
1616 if (assignment_.VariableIsAssigned(var)) return;
1617 if (implication_graph_->IsRemoved(Literal(var, true))) return;
1618 if (implication_graph_->IsRedundant(Literal(var, true))) return;
1619 const int priority = -NumClausesContaining(Literal(var, true)) -
1620 NumClausesContaining(Literal(var, false));
1621 if (queue_.Contains(var.value())) {
1622 queue_.ChangePriority({var, priority});
1623 } else {
1624 queue_.Add({var, priority});
1625 }
1626}
1627
1628void BoundedVariableElimination::DeleteClause(SatClause* sat_clause) {
1629 const auto clause = sat_clause->AsSpan();
1630
1631 num_clauses_diff_--;
1632 num_literals_diff_ -= clause.size();
1633
1634 // Update literal <-> clause graph.
1635 for (const Literal l : clause) {
1636 literal_to_num_clauses_[l]--;
1637 if (!in_need_to_be_updated_[l.Variable()]) {
1638 in_need_to_be_updated_[l.Variable()] = true;
1639 need_to_be_updated_.push_back(l.Variable());
1640 }
1641 }
1642
1643 // Lazy deletion of the clause.
1644 clause_manager_->LazyDelete(sat_clause, DeletionSourceForStat::ELIMINATED);
1645}
1646
1647void BoundedVariableElimination::DeleteAllClausesContaining(Literal literal) {
1648 for (const ClauseIndex i : literal_to_clauses_[literal]) {
1649 const auto clause = clauses_[i]->AsSpan();
1650 if (clause.empty()) continue;
1651 postsolve_->AddClauseWithSpecialLiteral(literal, clause);
1652 DeleteClause(clauses_[i]);
1653 }
1654 literal_to_clauses_[literal].clear();
1655}
1656
1657void BoundedVariableElimination::AddClause(absl::Span<const Literal> clause) {
1658 SatClause* pt = clause_manager_->InprocessingAddClause(clause);
1659 if (pt == nullptr) return;
1660
1661 num_clauses_diff_++;
1662 num_literals_diff_ += clause.size();
1663
1664 const ClauseIndex index(clauses_.size());
1665 clauses_.push_back(pt);
1666 for (const Literal l : clause) {
1667 literal_to_num_clauses_[l]++;
1668 literal_to_clauses_[l].push_back(index);
1669 if (!in_need_to_be_updated_[l.Variable()]) {
1670 in_need_to_be_updated_[l.Variable()] = true;
1671 need_to_be_updated_.push_back(l.Variable());
1672 }
1673 }
1674}
1675
1676template <bool score_only, bool with_binary_only>
1677bool BoundedVariableElimination::ResolveAllClauseContaining(Literal lit) {
1678 const int clause_weight = parameters_.presolve_bve_clause_weight();
1679
1680 const std::vector<Literal>& implications =
1681 implication_graph_->DirectImplications(lit);
1682 auto& clause_containing_lit = literal_to_clauses_[lit];
1683 for (int i = 0; i < clause_containing_lit.size(); ++i) {
1684 const ClauseIndex clause_index = clause_containing_lit[i];
1685 const auto clause = clauses_[clause_index]->AsSpan();
1686 if (clause.empty()) continue;
1687
1688 if (!score_only) resolvant_.clear();
1689 for (const Literal l : clause) {
1690 if (!score_only && l != lit) resolvant_.push_back(l);
1691 marked_[l] = true;
1692 }
1693 DCHECK(marked_[lit]);
1694 num_inspected_literals_ += clause.size() + implications.size();
1695
1696 // If this is true, then "clause" is subsumed by one of its resolvant and we
1697 // can just remove lit from it. Then it doesn't need to be acounted at all.
1698 bool clause_can_be_simplified = false;
1699 const int64_t saved_score = new_score_;
1700
1701 // Resolution with binary clauses.
1702 for (const Literal l : implications) {
1703 CHECK_NE(l, lit);
1704 if (marked_[l.NegatedIndex()]) continue; // trivial.
1705 if (marked_[l]) {
1706 clause_can_be_simplified = true;
1707 break;
1708 } else {
1709 if (score_only) {
1710 new_score_ += clause_weight + clause.size();
1711 } else {
1712 resolvant_.push_back(l);
1713 AddClause(resolvant_);
1714 resolvant_.pop_back();
1715 }
1716 }
1717 }
1718
1719 // Resolution with non-binary clauses.
1720 if (!with_binary_only && !clause_can_be_simplified) {
1721 auto& clause_containing_not_lit = literal_to_clauses_[lit.NegatedIndex()];
1722 for (int j = 0; j < clause_containing_not_lit.size(); ++j) {
1723 if (score_only && new_score_ > score_threshold_) break;
1724 const ClauseIndex other_index = clause_containing_not_lit[j];
1725 const auto other = clauses_[other_index]->AsSpan();
1726 if (other.empty()) continue;
1727 bool trivial = false;
1728 int extra_size = 0;
1729 for (const Literal l : other) {
1730 // TODO(user): we can optimize this by updating it outside the loop.
1731 ++num_inspected_literals_;
1732 if (l == lit.Negated()) continue;
1733 if (marked_[l.NegatedIndex()]) {
1734 trivial = true;
1735 break;
1736 }
1737 if (!marked_[l]) {
1738 ++extra_size;
1739 if (!score_only) resolvant_.push_back(l);
1740 }
1741 }
1742 if (trivial) {
1743 if (!score_only) resolvant_.resize(resolvant_.size() - extra_size);
1744 continue;
1745 }
1746
1747 // If this is the case, the other clause is subsumed by the resolvant.
1748 // We can just remove not_lit from it and ignore it.
1749 if (score_only && clause.size() + extra_size <= other.size()) {
1750 // TODO(user): We should have an exact equality here, except if
1751 // presolve is off before the clause are added to the sat solver and
1752 // we have duplicate literals. The code should still work but it
1753 // wasn't written with that in mind nor tested like this, so we should
1754 // just enforce the invariant.
1755 if (false) DCHECK_EQ(clause.size() + extra_size, other.size());
1756 ++num_simplifications_;
1757
1758 // Note that we update the threshold since this clause was counted in
1759 // it.
1760 score_threshold_ -= clause_weight + other.size();
1761
1762 if (extra_size == 0) {
1763 // We have a double self-subsumption. We can just remove this
1764 // clause since it will be subsumed by the clause created in the
1765 // "clause_can_be_simplified" case below.
1766 DeleteClause(clauses_[other_index]);
1767 } else {
1768 if (!RemoveLiteralFromClause(lit.Negated(),
1769 clauses_[other_index])) {
1770 return false;
1771 }
1772 std::swap(clause_containing_not_lit[j],
1773 clause_containing_not_lit.back());
1774 clause_containing_not_lit.pop_back();
1775 --j; // Reprocess the new position.
1776 continue;
1777 }
1778 }
1779
1780 if (extra_size == 0) {
1781 clause_can_be_simplified = true;
1782 break;
1783 } else {
1784 if (score_only) {
1785 // Hack. We do not want to create long clauses during BVE.
1786 if (clause.size() - 1 + extra_size > 100) {
1787 new_score_ = score_threshold_ + 1;
1788 break;
1789 }
1790
1791 new_score_ += clause_weight + clause.size() - 1 + extra_size;
1792 } else {
1793 AddClause(resolvant_);
1794 resolvant_.resize(resolvant_.size() - extra_size);
1795 }
1796 }
1797 }
1798 }
1799
1800 // Note that we need to clear marked before aborting.
1801 for (const Literal l : clause) marked_[l] = false;
1802
1803 // In this case, we simplify and remove the clause from here.
1804 if (clause_can_be_simplified) {
1805 ++num_simplifications_;
1806
1807 // Note that we update the threshold as if this was simplified before.
1808 new_score_ = saved_score;
1809 score_threshold_ -= clause_weight + clause.size();
1810
1811 if (!RemoveLiteralFromClause(lit, clauses_[clause_index])) return false;
1812 std::swap(clause_containing_lit[i], clause_containing_lit.back());
1813 clause_containing_lit.pop_back();
1814 --i; // Reprocess the new position.
1815 }
1816
1817 if (score_only && new_score_ > score_threshold_) return true;
1818
1819 // When this happen, then the clause is blocked (i.e. all its resolvant are
1820 // trivial). So even if we do not actually perform the variable elimination,
1821 // we can still remove this clause. Note that we treat the score as if the
1822 // clause was removed before.
1823 //
1824 // Tricky: The detection only work if we didn't abort the computation above,
1825 // so we do that after the score_threshold_ check.
1826 //
1827 // TODO(user): Also detect blocked clause for not(lit)? It is not as cheap
1828 // though and require more code.
1829 if (score_only && !with_binary_only && !clause_can_be_simplified &&
1830 new_score_ == saved_score) {
1831 ++num_blocked_clauses_;
1832 score_threshold_ -= clause_weight + clause.size();
1833 postsolve_->AddClauseWithSpecialLiteral(lit, clause);
1834 DeleteClause(clauses_[clause_index]);
1835 }
1836 }
1837 return true;
1838}
1839
1840bool BoundedVariableElimination::CrossProduct(BooleanVariable var) {
1841 if (assignment_.VariableIsAssigned(var)) return true;
1842
1843 const Literal lit(var, true);
1844 const Literal not_lit(var, false);
1845 DCHECK(!implication_graph_->IsRedundant(lit));
1846 {
1847 const int s1 = NumClausesContaining(lit);
1848 const int s2 = NumClausesContaining(not_lit);
1849 if (s1 == 0 && s2 == 0) return true;
1850 if (s1 > 0 && s2 == 0) {
1851 num_eliminated_variables_++;
1852 if (!clause_manager_->InprocessingFixLiteral(lit)) return false;
1853 DeleteAllClausesContaining(lit);
1854 return true;
1855 }
1856 if (s1 == 0 && s2 > 0) {
1857 num_eliminated_variables_++;
1858 if (!clause_manager_->InprocessingFixLiteral(not_lit)) return false;
1859 DeleteAllClausesContaining(not_lit);
1860 return true;
1861 }
1862
1863 // Heuristic. Abort if the work required to decide if var should be removed
1864 // seems to big.
1865 if (s1 > 1 && s2 > 1 && s1 * s2 > parameters_.presolve_bve_threshold()) {
1866 return true;
1867 }
1868 }
1869
1870 // TODO(user): swap lit and not_lit for speed? it is unclear if we prefer
1871 // to minimize the number of clause containing lit or not_lit though. Also,
1872 // we might want to alternate since we also detect blocked clause containing
1873 // lit, but don't do it for not_lit.
1874
1875 // Compute the current score.
1876 // TODO(user): cleanup the list lazily at the same time?
1877 int64_t score = 0;
1878 const int clause_weight = parameters_.presolve_bve_clause_weight();
1879 score +=
1880 implication_graph_->DirectImplications(lit).size() * (clause_weight + 2);
1881 score += implication_graph_->DirectImplications(not_lit).size() *
1882 (clause_weight + 2);
1883 for (const ClauseIndex i : literal_to_clauses_[lit]) {
1884 const auto c = clauses_[i]->AsSpan();
1885 if (!c.empty()) score += clause_weight + c.size();
1886 dtime_ += 1e-8 * c.size();
1887 }
1888 for (const ClauseIndex i : literal_to_clauses_[not_lit]) {
1889 const auto c = clauses_[i]->AsSpan();
1890 if (!c.empty()) score += clause_weight + c.size();
1891 dtime_ += 1.0e-8 * c.size();
1892 }
1893
1894 // Compute the new score after BVE.
1895 // Abort as soon as it crosses the threshold.
1896 //
1897 // TODO(user): Experiment with leaving the implications graph as is. This will
1898 // not remove the variable completely, but it seems interesting since after
1899 // equivalent variable removal and failed literal probing, the cross product
1900 // of the implication always add a quadratic number of implication, except if
1901 // the in (or out) degree is zero or one.
1902 score_threshold_ = score;
1903 new_score_ = implication_graph_->NumImplicationOnVariableRemoval(var) *
1904 (clause_weight + 2);
1905 if (new_score_ > score_threshold_) return true;
1906 if (!ResolveAllClauseContaining</*score_only=*/true,
1907 /*with_binary_only=*/true>(not_lit)) {
1908 return false;
1909 }
1910 if (new_score_ > score_threshold_) return true;
1911 if (!ResolveAllClauseContaining</*score_only=*/true,
1912 /*with_binary_only=*/false>(lit)) {
1913 return false;
1914 }
1915 if (new_score_ > score_threshold_) return true;
1916
1917 // Perform BVE.
1918 //
1919 // TODO(user): If filter_sat_postsolve_clauses is true, only one of the two
1920 // sets need to be kept for postsolve.
1921 if (new_score_ > 0) {
1922 if (!ResolveAllClauseContaining</*score_only=*/false,
1923 /*with_binary_only=*/false>(lit)) {
1924 return false;
1925 }
1926 if (!ResolveAllClauseContaining</*score_only=*/false,
1927 /*with_binary_only=*/true>(not_lit)) {
1928 return false;
1929 }
1930 }
1931
1932 ++num_eliminated_variables_;
1933 implication_graph_->RemoveBooleanVariable(var, &postsolve_->clauses);
1934 DeleteAllClausesContaining(lit);
1935 DeleteAllClausesContaining(not_lit);
1936 return true;
1937}
1938
1940 if (!VLOG_IS_ON(1)) return;
1941 shared_stats_->AddStats({
1942 {"GateCongruenceClosure/dtime(int)", static_cast<int64_t>(total_dtime_)},
1943 {"GateCongruenceClosure/walltime(int)",
1944 static_cast<int64_t>(total_wtime_)},
1945 {"GateCongruenceClosure/gates", total_gates_},
1946 {"GateCongruenceClosure/units", total_num_units_},
1947 {"GateCongruenceClosure/equivalences", total_equivalences_},
1948 });
1949}
1950
1951template <int arity>
1952void GateCongruenceClosure::AddToTruthTable(
1953 SatClause* clause,
1954 absl::flat_hash_map<std::array<BooleanVariable, arity>, TruthTableId>&
1955 ids) {
1956 CHECK_EQ(clause->size(), arity);
1957 std::array<BooleanVariable, arity> key;
1958 SmallBitset bitmask;
1959 FillKeyAndBitmask(clause->AsSpan(), absl::MakeSpan(key), bitmask);
1960
1961 TruthTableId next_id(truth_tables_bitset_.size());
1962 auto [it, inserted] = ids.insert({key, next_id});
1963 const TruthTableId id = it->second;
1964 if (inserted) {
1965 truth_tables_inputs_.Add(key);
1966 truth_tables_bitset_.push_back(bitmask);
1967 if (lrat_proof_handler_ != nullptr) {
1968 tmp_ids_.push_back(id);
1969 tmp_clauses_.push_back(clause);
1970 }
1971 } else {
1972 const SmallBitset old = truth_tables_bitset_[id];
1973
1974 // Remove one value.
1975 truth_tables_bitset_[id] &= bitmask;
1976 if (lrat_proof_handler_ != nullptr && old != truth_tables_bitset_[id]) {
1977 tmp_ids_.push_back(id);
1978 tmp_clauses_.push_back(clause);
1979 }
1980 }
1981}
1982
1983namespace {
1984
1985// Given a set of feasible assignment of two variables, recover the
1986// corresponding binary clauses.
1987void AppendBinaryClausesFromTruthTable(
1988 absl::Span<const BooleanVariable> vars, SmallBitset table,
1989 std::vector<std::pair<Literal, Literal>>* binary_used) {
1990 DCHECK_EQ(vars.size(), 2);
1991 for (int b = 0; b < 4; ++b) {
1992 if (((table >> b) & 1) == 0) {
1993 binary_used->emplace_back(Literal(vars[0], (b & 1) == 0),
1994 Literal(vars[1], (b & 2) == 0));
1995 }
1996 }
1997}
1998
1999} // namespace
2000
2001// Note that this is the "hot" part of the algo, once we have the and gates,
2002// the congruence closure should be quite fast.
2003void GateCongruenceClosure::ExtractAndGatesAndFillShortTruthTables(
2004 PresolveTimer& timer) {
2005 ids3_.clear();
2006 ids4_.clear();
2007 ids5_.clear();
2008 truth_tables_inputs_.clear();
2009 truth_tables_bitset_.clear();
2010 truth_tables_clauses_.clear();
2011 tmp_ids_.clear();
2012 tmp_clauses_.clear();
2013
2014 // We deal with binary clause a bit differently.
2015 //
2016 // Tricky: We still include binary clause between fixed literal that haven't
2017 // been cleaned up yet, as these are needed to really recover all gates.
2018 //
2019 // TODO(user): Ideally the detection code should be robust to that.
2020 // TODO(user): Maybe we should always have an hash-map of binary up to date?
2021 int num_fn1 = 0;
2022 std::vector<std::pair<Literal, Literal>> binary_used;
2023 for (LiteralIndex a(0); a < implication_graph_->literal_size(); ++a) {
2024 // TODO(user): If we know we have too many implications for the time limit
2025 // We should just be better of not doing that loop at all.
2026 if (timer.WorkLimitIsReached()) break;
2027 if (implication_graph_->IsRedundant(Literal(a))) continue;
2028 const absl::Span<const Literal> implied =
2029 implication_graph_->Implications(Literal(a));
2030 timer.TrackHashLookups(implied.size());
2031 for (const Literal b : implied) {
2032 if (implication_graph_->IsRedundant(b)) continue;
2033
2034 std::array<BooleanVariable, 2> key2;
2035 SmallBitset bitmask;
2036 FillKeyAndBitmask({Literal(a).Negated(), b}, absl::MakeSpan(key2),
2037 bitmask);
2038 auto [it, inserted] = ids2_.insert({key2, bitmask});
2039 if (!inserted) {
2040 const SmallBitset old = it->second;
2041 it->second &= bitmask;
2042 if (it->second != old) {
2043 // This is either fixing or equivalence!
2044 //
2045 // Doing a run of DetectEquivalences() should fix that but then
2046 // new clause of size 3 might become binary, and the fix point might
2047 // require a lot of step. So it is important to do it here.
2048 const SmallBitset bitset2 = it->second;
2049 if (lrat_proof_handler_ != nullptr) {
2050 binary_used.clear();
2051 AppendBinaryClausesFromTruthTable(key2, bitset2, &binary_used);
2052 }
2053 // If we are equivalent, we always have 2 functions.
2054 // But if we fix a variable (like bitset2 = 0011) we just have one.
2055 const int num_added =
2056 ProcessTruthTable(key2, bitset2, {}, binary_used);
2057 CHECK_GE(num_added, 1) << std::bitset<4>(bitset2);
2058 num_fn1 += num_added;
2059 }
2060 }
2061 }
2062 }
2063 timer.AddCounter("t2", ids2_.size());
2064 timer.AddCounter("fn1", num_fn1);
2065
2066 std::vector<Literal> candidates;
2067 for (SatClause* clause : clause_manager_->AllClausesInCreationOrder()) {
2068 if (timer.WorkLimitIsReached()) break;
2069 if (clause->size() == 0) continue;
2070
2071 if (clause->size() == 3) {
2072 AddToTruthTable<3>(clause, ids3_);
2073
2074 // The AND gate of size 3 should be detected by the short table code, no
2075 // need to do the algo here which should be slower.
2076 continue;
2077 } else if (clause->size() == 4) {
2078 AddToTruthTable<4>(clause, ids4_);
2079 } else if (clause->size() == 5) {
2080 AddToTruthTable<5>(clause, ids5_);
2081 }
2082
2083 // Used for an optimization below.
2084 int min_num_implications = std::numeric_limits<int>::max();
2085 Literal lit_with_less_implications;
2086
2087 const int clause_size = clause->size();
2088 timer.TrackSimpleLoop(clause_size);
2089 candidates.clear();
2090 for (const Literal l : clause->AsSpan()) {
2091 // TODO(user): using Implications() only considers pure binary
2092 // clauses and not at_most_one. Also, if we do transitive reduction, we
2093 // might skip important literals here. Maybe a better alternative is
2094 // to detect clauses that "propagate" l back when we probe l...
2095 const int num_implications = implication_graph_->Implications(l).size();
2096 if (num_implications < min_num_implications) {
2097 min_num_implications = num_implications;
2098 lit_with_less_implications = l;
2099 }
2100
2101 if (num_implications >= clause_size - 1) {
2102 candidates.push_back(l);
2103 }
2104 }
2105 if (candidates.empty()) continue;
2106 if (min_num_implications == 0) continue;
2107
2108 marked_.ResetAllToFalse();
2109 for (const Literal l : clause->AsSpan()) marked_.Set(l);
2110 const auto is_clause_literal = marked_.const_view();
2111
2112 // There should be no duplicate in a clause.
2113 // And also not lit and lit.Negated(), but we don't check that here.
2114 CHECK_EQ(marked_.PositionsSetAtLeastOnce().size(), clause_size);
2115
2116 // These bitsets will contain the intersection of all the negated literals
2117 // implied by one of the clause literal. It is used for an "optimization" as
2118 // a literal can only be a "target" of a bool_and if it implies all other
2119 // literals of the clause to false. So by contraposition, any literal should
2120 // directly imply such a target to false.
2121 //
2122 // This relies on the fact that for any a => b directly stored in
2123 // BinaryImplicationGraph, we should always have not(b) => not(a). This
2124 // only applies to the result of Implications() though, not the one we
2125 // can infer by transitivity.
2126 //
2127 // We start with the variables implied by "lit_with_less_implications" and
2128 // at each iteration, we take the intersection with the variables implied by
2129 // our current "target".
2130 //
2131 // TODO(user): SparseBitset<> does not support swap.
2132 auto* is_potential_target = &seen_;
2133 auto* next_is_potential_target = &next_seen_;
2134
2135 // If we don't have lit_with_less_implications => not(target) then we
2136 // shouldn't have target => not(lit_with_less_implications).
2137 {
2138 is_potential_target->ResetAllToFalse();
2139 is_potential_target->Set(lit_with_less_implications);
2140 const absl::Span<const Literal> implications =
2141 implication_graph_->Implications(lit_with_less_implications);
2142 timer.TrackFastLoop(implications.size());
2143 for (const Literal implied : implications) {
2144 is_potential_target->Set(implied.Negated());
2145 }
2146 }
2147
2148 for (const Literal target : candidates) {
2149 if (!(*is_potential_target)[target]) continue;
2150
2151 int count = 0;
2152 next_is_potential_target->ResetAllToFalse();
2153 const absl::Span<const Literal> implications =
2154 implication_graph_->Implications(target);
2155 timer.TrackFastLoop(implications.size());
2156 for (const Literal implied : implications) {
2157 CHECK_NE(implied.Variable(), target.Variable());
2158
2159 if (is_clause_literal[implied.Negated()]) {
2160 // Set next_is_potential_target to the intersection of
2161 // is_potential_target and the one we see here.
2162 if ((*is_potential_target)[implied.Negated()]) {
2163 next_is_potential_target->Set(implied.Negated());
2164 }
2165 ++count;
2166 }
2167 }
2168 std::swap(is_potential_target, next_is_potential_target);
2169
2170 // Target should imply all other literal in the base clause to false.
2171 if (count < clause_size - 1) continue;
2172
2173 // Using only the "count" require that there is no duplicates. But
2174 // depending when this is run in the inprocessing loop, we might have
2175 // some. Redo a pass to double check.
2176 int second_count = 0;
2177 for (const Literal implied : implications) {
2178 if (implied.Variable() == target.Variable()) continue;
2179 if (is_clause_literal[implied.Negated()]) {
2180 ++second_count;
2181 marked_.Clear(implied.Negated());
2182 }
2183 }
2184
2185 // Restore is_clause_literal.
2186 for (const Literal l : clause->AsSpan()) {
2187 marked_.Set(l);
2188 }
2189 if (second_count != clause_size - 1) continue;
2190
2191 // We have an and_gate !
2192 // Add the detected gate (its inputs are the negation of each clause
2193 // literal other than the target).
2194 gates_target_.push_back(target.Index());
2195 gates_type_.push_back(kAndGateType);
2196
2197 const GateId gate_id = GateId(gates_inputs_.Add({}));
2198 for (const Literal l : clause->AsSpan()) {
2199 if (l == target) continue;
2200 gates_inputs_.AppendToLastVector(l.NegatedIndex());
2201 }
2202 if (lrat_proof_handler_ != nullptr) {
2203 gates_clauses_.Add({clause});
2204
2205 // Create temporary size 2 clauses for the needed binary.
2206 for (const Literal l : clause->AsSpan()) {
2207 if (l == target) continue;
2208 tmp_binary_clauses_.emplace_back(
2209 SatClause::Create({target.Negated(), l.Negated()}));
2210 gates_clauses_.AppendToLastVector(tmp_binary_clauses_.back().get());
2211 }
2212 }
2213
2214 // Canonicalize.
2215 absl::Span<LiteralIndex> gate = gates_inputs_[gate_id];
2216 std::sort(gate.begin(), gate.end());
2217
2218 // Even if we detected an and_gate from a base clause, we keep going
2219 // as their could me more than one. In the extreme of an "exactly_one",
2220 // a single base clause of size n will correspond to n and_gates !
2221 }
2222 }
2223
2224 timer.AddCounter("and_gates", gates_inputs_.size());
2225}
2226
2227int GateCongruenceClosure::CanonicalizeShortGate(GateId id) {
2228 // Deals with fixed input variable.
2229 absl::Span<LiteralIndex> inputs = gates_inputs_[id];
2230 int new_size = inputs.size();
2231
2232 for (int i = 0; i < new_size;) {
2233 if (assignment_.LiteralIsAssigned(Literal(inputs[i]))) {
2234 new_size =
2235 RemoveFixedInput(i, assignment_.LiteralIsTrue(Literal(inputs[i])),
2236 inputs.subspan(0, new_size), gates_type_[id]);
2237 } else {
2238 ++i;
2239 }
2240 }
2241
2242 // Now canonicalize.
2244 gates_target_[id], inputs.subspan(0, new_size), gates_type_[id]);
2245
2246 // Resize and return.
2247 if (new_size < gates_inputs_[id].size()) {
2248 gates_inputs_.Shrink(id, new_size);
2249 }
2250 DCHECK_EQ(gates_type_[id] >> (1 << (gates_inputs_[id].size())), 0);
2251 return new_size;
2252}
2253
2254int GateCongruenceClosure::ProcessTruthTable(
2255 absl::Span<const BooleanVariable> inputs, SmallBitset truth_table,
2256 absl::Span<const TruthTableId> ids_for_proof,
2257 absl::Span<const std::pair<Literal, Literal>> binary_used) {
2258 int num_detected = 0;
2259 for (int i = 0; i < inputs.size(); ++i) {
2260 if (!IsFunction(i, inputs.size(), truth_table)) continue;
2261 const int num_bits = inputs.size() - 1;
2262 ++num_detected;
2263
2264 const GateId new_id(gates_target_.size());
2265 gates_target_.push_back(Literal(inputs[i], true));
2266 gates_inputs_.Add({});
2267 for (int j = 0; j < inputs.size(); ++j) {
2268 if (i != j) {
2269 gates_inputs_.AppendToLastVector(Literal(inputs[j], true));
2270 }
2271 }
2272
2273 // Generate the function truth table as a type.
2274 // We will canonicalize it further in the main loop.
2275 unsigned int type = 0;
2276 for (int p = 0; p < (1 << num_bits); ++p) {
2277 // Expand from (num_bits == inputs.size() - 1) bits to (inputs.size())
2278 // bits.
2279 const int bigger_p = AddHoleAtPosition(i, p);
2280
2281 if ((truth_table >> (bigger_p + (1 << i))) & 1) {
2282 // target is 1 at this position.
2283 type |= 1 << p;
2284 DCHECK_NE((truth_table >> bigger_p) & 1, 1); // Proper function.
2285 } else {
2286 // Note that if there is no feasible assignment for a given p if
2287 // [(truth_table >> bigger_p) & 1] is not 1.
2288 //
2289 // Here we could learn smaller clause, but also we don't really care
2290 // what is the value of the target at that point p, so we use zero.
2291 //
2292 // TODO(user): This is not ideal if another function has a value of 1 at
2293 // point p, we could still merge it with this one. Shall we create two
2294 // gate type? or change the algo?
2295 }
2296 }
2297
2298 gates_type_.push_back(type);
2299 if (lrat_proof_handler_ != nullptr) {
2300 gates_clauses_.Add({});
2301 for (const TruthTableId id : ids_for_proof) {
2302 gates_clauses_.AppendToLastVector(truth_tables_clauses_[id]);
2303 }
2304 for (const auto [a, b] : binary_used) {
2305 tmp_binary_clauses_.emplace_back(SatClause::Create({a, b}));
2306 gates_clauses_.AppendToLastVector(tmp_binary_clauses_.back().get());
2307 }
2308 }
2309
2310 // Canonicalize right away to deal with corner case.
2311 CanonicalizeShortGate(new_id);
2312 }
2313 return num_detected;
2314}
2315
2316namespace {
2317
2318// Return a BooleanVariable from b that is not in a or kNoBooleanVariable;
2319BooleanVariable FindMissing(absl::Span<const BooleanVariable> vars_a,
2320 absl::Span<const BooleanVariable> vars_b) {
2321 for (const BooleanVariable b : vars_b) {
2322 bool found = false;
2323 for (const BooleanVariable a : vars_a) {
2324 if (a == b) {
2325 found = true;
2326 break;
2327 }
2328 }
2329 if (!found) return b;
2330 }
2331 return kNoBooleanVariable;
2332}
2333
2334} // namespace
2335
2336// TODO(user): It should be possible to extract ALL possible short gate, but
2337// we are not there yet.
2338void GateCongruenceClosure::ExtractShortGates(PresolveTimer& timer) {
2339 if (lrat_proof_handler_ != nullptr) {
2340 truth_tables_clauses_.ResetFromFlatMapping(
2341 tmp_ids_, tmp_clauses_,
2342 /*minimum_num_nodes=*/truth_tables_bitset_.size());
2343 CHECK_EQ(truth_tables_bitset_.size(), truth_tables_clauses_.size());
2344 }
2345
2346 // This is used to combine two 3 arity table into one 4 arity one if
2347 // they share two variables.
2348 absl::flat_hash_map<std::array<BooleanVariable, 2>, int> binary_index_map;
2349 std::vector<int> flat_binary_index;
2350 std::vector<TruthTableId> flat_table_id;
2351
2352 // Counters.
2353 // We only fill a subset of the entries, but that make the code shorter.
2354 std::vector<int> num_tables(6);
2355 std::vector<int> num_functions(6);
2356
2357 // Note that using the indirection via TruthTableId allow this code to
2358 // be deterministic.
2359 CHECK_EQ(truth_tables_bitset_.size(), truth_tables_inputs_.size());
2360
2361 // Given a table of arity 4, this merges all the information from the tables
2362 // of arity 3 included in it.
2363 int num_merges = 0;
2364 const auto merge3_into_4 = [this, &num_merges](
2365 absl::Span<const BooleanVariable> inputs,
2366 SmallBitset& truth_table,
2367 std::vector<TruthTableId>& ids_for_proof) {
2368 DCHECK_EQ(inputs.size(), 4);
2369 for (int i_to_remove = 0; i_to_remove < inputs.size(); ++i_to_remove) {
2370 int pos = 0;
2371 std::array<BooleanVariable, 3> key3;
2372 for (int i = 0; i < inputs.size(); ++i) {
2373 if (i == i_to_remove) continue;
2374 key3[pos++] = inputs[i];
2375 }
2376 const auto it = ids3_.find(key3);
2377 if (it == ids3_.end()) continue;
2378 ++num_merges;
2379
2380 // Extend the bitset from the table3 so that it is expressed correctly
2381 // for the given inputs.
2382 const TruthTableId id3 = it->second;
2383 std::array<BooleanVariable, 4> key4;
2384 for (int i = 0; i < 3; ++i) key4[i] = key3[i];
2385 key4[3] = FindMissing(key3, inputs);
2386 SmallBitset bitset = truth_tables_bitset_[id3];
2387 bitset |= bitset << (1 << 3); // Extend for a new variable.
2388 CanonicalizeTruthTable<BooleanVariable>(absl::MakeSpan(key4), bitset);
2389 CHECK_EQ(inputs, absl::MakeSpan(key4));
2390 truth_table &= bitset;
2391
2392 // We need to add the corresponding clause!
2393 if (lrat_proof_handler_ != nullptr) {
2394 ids_for_proof.push_back(id3);
2395 }
2396 }
2397 };
2398
2399 int num_merges2 = 0;
2400 const auto merge2_into_n =
2401 [this, &num_merges2](
2402 absl::Span<const BooleanVariable> inputs, SmallBitset& truth_table,
2403 std::vector<std::pair<Literal, Literal>>& binary_used) {
2404 for (int i = 0; i < inputs.size(); ++i) {
2405 for (int j = i + 1; j < inputs.size(); ++j) {
2406 std::array<BooleanVariable, 2> key2 = {inputs[i], inputs[j]};
2407 const auto it = ids2_.find(key2);
2408 if (it == ids2_.end()) continue;
2409
2410 const SmallBitset bitset2 = it->second;
2411 SmallBitset bitset = bitset2;
2412 int new_size = 0;
2413 std::vector<BooleanVariable> key(inputs.size());
2414 key[new_size++] = inputs[i];
2415 key[new_size++] = inputs[j];
2416 for (int t = 0; t < inputs.size(); ++t) {
2417 if (t != i && t != j) {
2418 key[new_size] = inputs[t];
2419 bitset |= bitset << (1 << new_size); // EXTEND
2420 ++new_size;
2421 }
2422 }
2423 CanonicalizeTruthTable<BooleanVariable>(absl::MakeSpan(key),
2424 bitset);
2425 CHECK_EQ(inputs, absl::MakeSpan(key));
2426
2427 const SmallBitset old = truth_table;
2428 truth_table &= bitset;
2429 if (old != truth_table) {
2430 AppendBinaryClausesFromTruthTable(key2, bitset2, &binary_used);
2431 ++num_merges2;
2432 }
2433 }
2434 }
2435 };
2436
2437 // Starts by processing all existing tables.
2438 //
2439 // TODO(user): Since we deal with and_gates differently, do we need to look at
2440 // binary clauses here ? or that is not needed ? I think there is only two
2441 // kind of Boolean function on two inputs (and_gates, with any possible
2442 // negation) and xor_gate.
2443 std::vector<TruthTableId> ids_for_proof;
2444 std::vector<std::pair<Literal, Literal>> binary_used;
2445 for (TruthTableId t_id(0); t_id < truth_tables_inputs_.size(); ++t_id) {
2446 ids_for_proof.clear();
2447 ids_for_proof.push_back(t_id);
2448 const absl::Span<const BooleanVariable> inputs = truth_tables_inputs_[t_id];
2449 SmallBitset truth_table = truth_tables_bitset_[t_id];
2450
2451 // TODO(user): it is unlcear why this is useful. Understand a bit more the
2452 // set of possible Boolean functions of 2 and 3 variables and their clause
2453 // encoding.
2454 binary_used.clear();
2455 merge2_into_n(inputs, truth_table, binary_used);
2456
2457 // Merge any size-3 table included inside a size-4 gate.
2458 // TODO(user): do it for larger gate too ?
2459 if (inputs.size() == 4) {
2460 merge3_into_4(inputs, truth_table, ids_for_proof);
2461 }
2462
2463 ++num_tables[inputs.size()];
2464 const int num_detected =
2465 ProcessTruthTable(inputs, truth_table, ids_for_proof, binary_used);
2466 num_functions[inputs.size() - 1] += num_detected;
2467
2468 // If this is not a function and of size 3, lets try to combine it with
2469 // another truth table of size 3 to get a table of size 4.
2470 if (inputs.size() == 3 && num_detected == 0) {
2471 for (int i = 0; i < 3; ++i) {
2472 std::array<BooleanVariable, 2> key{inputs[i != 0 ? 0 : 1],
2473 inputs[i != 2 ? 2 : 1]};
2474 DCHECK(std::is_sorted(key.begin(), key.end()));
2475 const auto [it, inserted] =
2476 binary_index_map.insert({key, binary_index_map.size()});
2477 flat_binary_index.push_back(it->second);
2478 flat_table_id.push_back(t_id);
2479 }
2480 }
2481 }
2482 gtl::STLClearObject(&binary_index_map);
2483
2484 // Detects ITE gates and potentially other 3-gates from a truth table of
2485 // 4-entries formed by two 3-entries table. This just create a 4-entries
2486 // table that will be processed below.
2487 CompactVectorVector<int, TruthTableId> candidates;
2488 candidates.ResetFromFlatMapping(flat_binary_index, flat_table_id);
2489 gtl::STLClearObject(&flat_binary_index);
2490 gtl::STLClearObject(&flat_table_id);
2491 int num_combinations = 0;
2492 for (int c = 0; c < candidates.size(); ++c) {
2493 if (candidates[c].size() < 2) continue;
2494 if (candidates[c].size() > 10) continue; // Too many? use heuristic.
2495
2496 for (int a = 0; a < candidates[c].size(); ++a) {
2497 for (int b = a + 1; b < candidates[c].size(); ++b) {
2498 const absl::Span<const BooleanVariable> inputs_a =
2499 truth_tables_inputs_[candidates[c][a]];
2500 const absl::Span<const BooleanVariable> inputs_b =
2501 truth_tables_inputs_[candidates[c][b]];
2502
2503 std::array<BooleanVariable, 4> key;
2504 for (int i = 0; i < 3; ++i) key[i] = inputs_a[i];
2505 key[3] = FindMissing(inputs_a, inputs_b);
2506 CHECK_NE(key[3], kNoBooleanVariable);
2507
2508 // Add an all allowed entry.
2509 SmallBitset bitmask = GetNumBitsAtOne(4);
2510 CanonicalizeTruthTable<BooleanVariable>(absl::MakeSpan(key), bitmask);
2511
2512 // If the key was not processed before, process it now.
2513 // Note that an old version created a TruthTableId for it, but that
2514 // waste a lot of space.
2515 //
2516 // On another hand, it is possible we process the same key up to
2517 // 4_choose_2 times, but this is rare...
2518 if (!ids4_.contains(key)) {
2519 ++num_combinations;
2520 ++num_tables[4];
2521 ids_for_proof.clear();
2522 binary_used.clear();
2523 merge2_into_n(key, bitmask, binary_used);
2524 merge3_into_4(key, bitmask, ids_for_proof);
2525 num_functions[3] +=
2526 ProcessTruthTable(key, bitmask, ids_for_proof, binary_used);
2527 }
2528 }
2529 }
2530 }
2531
2532 timer.AddCounter("combine3", num_combinations);
2533 timer.AddCounter("merges", num_merges);
2534 timer.AddCounter("merges2", num_merges2);
2535
2536 // Note that we only display non-zero counters.
2537 for (int i = 0; i < num_tables.size(); ++i) {
2538 timer.AddCounter(absl::StrCat("t", i), num_tables[i]);
2539 }
2540 for (int i = 0; i < num_functions.size(); ++i) {
2541 timer.AddCounter(absl::StrCat("fn", i), num_functions[i]);
2542 }
2543}
2544
2545namespace {
2546// Helper class to add LRAT proofs for equivalent gate target literals.
2547class LratGateCongruenceHelper {
2548 public:
2549 LratGateCongruenceHelper(
2550 const Trail* trail, const BinaryImplicationGraph* implication_graph,
2551 ClauseManager* clause_manager, ClauseIdGenerator* clause_id_generator,
2552 LratProofHandler* lrat_proof_handler,
2553 const util_intops::StrongVector<GateId, LiteralIndex>& gates_target,
2554 const CompactVectorVector<GateId, const SatClause*>& gates_clauses,
2555 DenseConnectedComponentsFinder& union_find)
2556 : trail_(trail),
2557 implication_graph_(implication_graph),
2558 clause_manager_(clause_manager),
2559 clause_id_generator_(clause_id_generator),
2560 lrat_proof_handler_(lrat_proof_handler),
2561 gates_target_(gates_target),
2562 gates_clauses_(gates_clauses),
2563 union_find_(union_find) {}
2564
2565 ~LratGateCongruenceHelper() {
2566 if (lrat_proof_handler_ != nullptr) {
2567 if (lrat_proof_handler_->drat_check_enabled() ||
2568 lrat_proof_handler_->drat_output_enabled()) {
2569 for (int i = 0; i < to_delete_.size(); ++i) {
2570 lrat_proof_handler_->DeleteClause(
2571 to_delete_[i],
2572 {clauses_to_delete_[i].first, clauses_to_delete_[i].second});
2573 }
2574 } else {
2575 for (const ClauseId id : to_delete_) {
2576 lrat_proof_handler_->DeleteClause(id, {});
2577 }
2578 }
2579 }
2580 }
2581
2582 // Adds direct LRAT equivalence clauses between l and its representative r, as
2583 // well as between each of its ancestor and r. Does nothing if r is equal to l
2584 // or its parent. This must be called before union_find_.FindRoot(l).
2585 void ShortenEquivalencesWithRepresentative(Literal l) {
2586 std::vector<Literal> literals;
2587 Literal representative;
2588 // Append l and its ancestors, excluding the representative, to `literals`.
2589 while (true) {
2590 if (IsRepresentative(l)) {
2591 representative = l;
2592 break;
2593 }
2594 literals.push_back(l);
2595 l = GetParent(l);
2596 }
2597
2598 // Add a direct equivalence between each literal in `literals` and
2599 // `representative` (except the last one, which already has a direct
2600 // equivalence). This is done in reverse order so that the proof for each
2601 // equivalence can use the one for the parent.
2602 for (int i = literals.size() - 2; i >= 0; --i) {
2603 const Literal parent = literals[i + 1];
2604 const Literal child = literals[i];
2605 DCHECK(parent_equivalence_.contains(parent));
2606 DCHECK(parent_equivalence_.contains(child));
2607 GateEquivalenceClauses& parent_clauses = parent_equivalence_[parent];
2608 GateEquivalenceClauses& child_clauses = parent_equivalence_[child];
2609
2610 const ClauseId rep_implies_child = clause_id_generator_->GetNextId();
2611 lrat_proof_handler_->AddInferredClause(
2612 rep_implies_child, {representative.Negated(), child},
2613 {parent_clauses.parent_implies_child,
2614 child_clauses.parent_implies_child});
2615 const ClauseId child_implies_rep = clause_id_generator_->GetNextId();
2616 lrat_proof_handler_->AddInferredClause(
2617 child_implies_rep, {child.Negated(), representative},
2618 {child_clauses.child_implies_parent,
2619 parent_clauses.child_implies_parent});
2620
2621 child_clauses.parent_implies_child = rep_implies_child;
2622 child_clauses.child_implies_parent = child_implies_rep;
2623 to_delete_.push_back(rep_implies_child);
2624 to_delete_.push_back(child_implies_rep);
2625 if (lrat_proof_handler_->drat_check_enabled() ||
2626 lrat_proof_handler_->drat_output_enabled()) {
2627 clauses_to_delete_.push_back({representative.Negated(), child});
2628 clauses_to_delete_.push_back({child.Negated(), representative});
2629 }
2630 }
2631 if (!literals.empty()) {
2632 // Make sure the parent links in union_find_ are shorten too, to keep the
2633 // consistency between the two data structures.
2634 union_find_.FindRoot(literals[0].Index().value());
2635 }
2636 }
2637
2638 // Returns an LRAT clause rep(gates_target[gate_a_id]) =>
2639 // rep(gates_target[gate_b_id]).
2640 ClauseId AddAndGateTargetImplication(GateId gate_a_id, GateId gate_b_id) {
2641 const Literal a = Literal(gates_target_[gate_a_id]);
2642 const Literal b = Literal(gates_target_[gate_b_id]);
2643 const Literal rep_a = GetRepresentativeWithProofSupport(a);
2644 const Literal rep_b = GetRepresentativeWithProofSupport(b);
2645 DCHECK(IsRepresentative(rep_a));
2646 DCHECK(IsRepresentative(rep_b));
2647 // Compute a sequence of clause IDs proving that rep(a) => rep(b).
2648 // The following only works for and gates.
2649 std::vector<ClauseId> clause_ids;
2650 // rep(a) => a:
2651 Append(clause_ids, GetRepresentativeImpliesLiteralClauseId(a));
2652 // For each original input l of gate_a_id, a => l => rep(l). The original
2653 // inputs are the negation of each clause literal other than the target.
2654 // TODO(user): this can add redundant clauses if two original inputs
2655 // have the same representative.
2656 for (const Literal lit : gates_clauses_[gate_a_id][0]->AsSpan()) {
2657 if (lit == a) continue;
2658 const Literal l = lit.Negated();
2659 clause_ids.push_back(implication_graph_->GetClauseId(a.Negated(), l));
2660 Append(clause_ids, GetLiteralImpliesRepresentativeClauseId(l));
2661 }
2662 // For each original input l of b, rep(l) => l. The original inputs are
2663 // the negation of each gate clause literal other than its target b.
2664 for (const Literal lit : gates_clauses_[gate_b_id][0]->AsSpan()) {
2665 if (lit == b) continue;
2666 const Literal l = lit.Negated();
2667 Append(clause_ids, GetRepresentativeImpliesLiteralClauseId(l));
2668 }
2669 // The original inputs of gate_b_id imply its target b:
2670 clause_ids.push_back(
2671 clause_manager_->GetClauseId(gates_clauses_[gate_b_id][0]));
2672 // b => rep(b):
2673 Append(clause_ids, GetLiteralImpliesRepresentativeClauseId(b));
2674
2675 const ClauseId clause_id = clause_id_generator_->GetNextId();
2676 lrat_proof_handler_->AddInferredClause(clause_id, {rep_a.Negated(), rep_b},
2677 clause_ids);
2678 return clause_id;
2679 }
2680
2681 void ClearTemporaryProof() {
2682 CHECK(lrat_proof_handler_ != nullptr);
2683 tmp_index_to_delete_.clear();
2684 tmp_proof_clauses_id_.clear();
2685 tmp_proof_clauses_.clear();
2686 marked_.ClearAndResize(LiteralIndex(clause_manager_->literal_size()));
2687 }
2688
2689 Literal GetRepresentativeWithProofSupport(Literal lit) {
2690 const int lit_index_as_int = lit.Index().value();
2691 if (union_find_.GetParent(lit_index_as_int) == lit_index_as_int) {
2692 return lit;
2693 }
2694 if (lrat_proof_handler_ != nullptr) {
2695 ShortenEquivalencesWithRepresentative(lit);
2696 }
2697 return Literal(LiteralIndex(union_find_.FindRoot(lit_index_as_int)));
2698 }
2699
2700 void AddGateClausesToTemporaryProof(GateId id) {
2701 CHECK(lrat_proof_handler_ != nullptr);
2702 const auto& assignment = trail_->Assignment();
2703 std::vector<Literal> fixed;
2704 for (const SatClause* clause : gates_clauses_[id]) {
2705 // We rewrite each clause using new equivalences found.
2706 marked_.ResetAllToFalse();
2707 tmp_literals_.clear();
2708 tmp_clause_ids_.clear();
2709 bool clause_is_trivial = false;
2710 bool some_change = false;
2711 for (const Literal lit : clause->AsSpan()) {
2712 const Literal rep = GetRepresentativeWithProofSupport(lit);
2713 if (assignment.LiteralIsAssigned(rep)) {
2714 fixed.push_back(rep);
2715 }
2716 if (rep != lit) {
2717 some_change = true;
2718 // We need not(rep) => not(lit). This should be equivalent to
2719 // getting lit => rep. Both should work.
2720 tmp_clause_ids_.push_back(
2721 GetLiteralImpliesRepresentativeClauseId(lit));
2722 }
2723 if (marked_[rep]) continue;
2724 if (marked_[rep.Negated()]) {
2725 clause_is_trivial = true;
2726 break;
2727 }
2728 marked_.Set(rep);
2729 tmp_literals_.push_back(rep);
2730 }
2731
2732 // If this is the case, we shouldn't need it for the proof.
2733 if (clause_is_trivial) continue;
2734
2735 ClauseId new_id =
2736 clause->size() == 2
2737 ? implication_graph_->GetClauseId(clause->FirstLiteral(),
2738 clause->SecondLiteral())
2739 : clause_manager_->GetClauseId(clause);
2740 CHECK_NE(new_id, kNoClauseId);
2741 if (some_change) {
2742 // If there is some change, we add a temporary clause id with the
2743 // proof to go from the original clause to this one.
2744 tmp_index_to_delete_.push_back(tmp_proof_clauses_.size());
2745 tmp_clause_ids_.push_back(new_id);
2746 new_id = clause_id_generator_->GetNextId();
2747 lrat_proof_handler_->AddInferredClause(new_id, tmp_literals_,
2748 tmp_clause_ids_);
2749 }
2750
2751 // Add that clause and its id to the set of clauses needed for the proof.
2752 tmp_proof_clauses_id_.push_back(new_id);
2753 tmp_proof_clauses_.Add(tmp_literals_);
2754 }
2755
2756 // Add size1 clauses.
2758 for (const Literal lit : fixed) {
2759 if (assignment.LiteralIsAssigned(lit)) {
2760 tmp_proof_clauses_id_.push_back(
2761 trail_->GetUnitClauseId(lit.Variable()));
2762 tmp_proof_clauses_.Add(
2763 {assignment.LiteralIsTrue(lit) ? lit : lit.Negated()});
2764 }
2765 }
2766 }
2767
2768 // Same as AddAndGateTargetImplication() but with truth table based gates.
2769 std::pair<ClauseId, ClauseId> AddShortGateEquivalence(
2770 Literal rep_a, Literal rep_b, absl::Span<const GateId> gate_ids) {
2771 if (lrat_proof_handler_ == nullptr) return {kNoClauseId, kNoClauseId};
2772
2773 // Just add all clauses from both gates.
2774 // But note that we need to remap them.
2775 ClearTemporaryProof();
2776 for (const GateId id : gate_ids) {
2777 AddGateClausesToTemporaryProof(id);
2778 }
2779
2780 // All clauses are now in tmp_proof_clauses_/tmp_proof_clauses_id_.
2781 // We can add both implications with proof.
2782 DCHECK(IsRepresentative(rep_a));
2783 DCHECK(IsRepresentative(rep_b));
2784 CHECK_NE(rep_a, rep_b);
2785
2786 if (rep_a.Negated() == rep_b) {
2787 // The model is UNSAT.
2788 //
2789 // TODO(user): AddAndProveInferredClauseByEnumeration() do not like
2790 // duplicates, but maybe we should just handle the case to have
2791 // less code here?
2792 const ClauseId not_rep_a_id =
2793 lrat_proof_handler_->AddAndProveInferredClauseByEnumeration(
2794 {rep_a.Negated()}, tmp_proof_clauses_id_, tmp_proof_clauses_);
2795 const ClauseId rep_a_id =
2796 lrat_proof_handler_->AddAndProveInferredClauseByEnumeration(
2797 {rep_a}, tmp_proof_clauses_id_, tmp_proof_clauses_);
2798 return {not_rep_a_id, rep_a_id};
2799 }
2800
2801 const ClauseId rep_a_implies_rep_b =
2802 lrat_proof_handler_->AddAndProveInferredClauseByEnumeration(
2803 {rep_a.Negated(), rep_b}, tmp_proof_clauses_id_,
2804 tmp_proof_clauses_);
2805 const ClauseId rep_b_implies_rep_a =
2806 lrat_proof_handler_->AddAndProveInferredClauseByEnumeration(
2807 {rep_b.Negated(), rep_a}, tmp_proof_clauses_id_,
2808 tmp_proof_clauses_);
2809
2810 for (const int i : tmp_index_to_delete_) {
2811 // Corner case if the proof used a temporary id.
2812 if (tmp_proof_clauses_id_[i] == rep_a_implies_rep_b) continue;
2813 if (tmp_proof_clauses_id_[i] == rep_b_implies_rep_a) continue;
2814 lrat_proof_handler_->DeleteClause(tmp_proof_clauses_id_[i],
2815 tmp_proof_clauses_[i]);
2816 }
2817 return {rep_a_implies_rep_b, rep_b_implies_rep_a};
2818 }
2819
2820 ClauseId ProofForFixingLiteral(Literal to_fix, GateId id) {
2821 if (lrat_proof_handler_ == nullptr) return kNoClauseId;
2822 CHECK(IsRepresentative(to_fix));
2823 ClearTemporaryProof();
2824 AddGateClausesToTemporaryProof(id);
2825 const ClauseId new_id =
2826 lrat_proof_handler_->AddAndProveInferredClauseByEnumeration(
2827 {to_fix}, tmp_proof_clauses_id_, tmp_proof_clauses_);
2828
2829 for (const int i : tmp_index_to_delete_) {
2830 // Corner case if the proof used a temporary id.
2831 if (tmp_proof_clauses_id_[i] == new_id) continue;
2832 lrat_proof_handler_->DeleteClause(tmp_proof_clauses_id_[i],
2833 tmp_proof_clauses_[i]);
2834 }
2835 return new_id;
2836 }
2837
2838 void AddGateEquivalenceClauses(Literal child, ClauseId child_implies_parent,
2839 ClauseId parent_implies_child) {
2840 DCHECK(!parent_equivalence_.contains(child));
2841 parent_equivalence_[child] = {
2842 .parent_implies_child = parent_implies_child,
2843 .child_implies_parent = child_implies_parent,
2844 };
2845 }
2846
2847 // Appends to `clause_ids` the clauses "gates_target[gate_id] => l => rep" and
2848 // "gates_target[gate_id] => m => not(rep)", proving that the gate target
2849 // literal can be fixed to false (assuming this is an and gate). A
2850 // precondition is that two original inputs l and m with rep(l) = rep and
2851 // rep(m) = not(rep) must exist.
2852 void AppendFixAndGateTargetClauses(
2853 GateId gate_id, Literal rep,
2854 absl::InlinedVector<ClauseId, 4>& clause_ids) {
2855 const Literal target = Literal(gates_target_[gate_id]);
2856 LiteralIndex l_index = kNoLiteralIndex;
2857 LiteralIndex m_index = kNoLiteralIndex;
2858 // Find l and m in the original inputs (the negation of each gate clause
2859 // literal other than its target).
2860 for (const Literal lit : gates_clauses_[gate_id][0]->AsSpan()) {
2861 if (l_index != kNoLiteralIndex && m_index != kNoLiteralIndex) break;
2862 const Literal l = lit.Negated();
2863 const Literal rep_l = GetRepresentativeWithProofSupport(l);
2864 if (rep_l == rep) l_index = l.Index();
2865 if (rep_l == rep.Negated()) m_index = l.Index();
2866 }
2867 clause_ids.push_back(
2868 implication_graph_->GetClauseId(target.Negated(), Literal(l_index)));
2869 Append(clause_ids,
2870 GetLiteralImpliesRepresentativeClauseId(Literal(l_index)));
2871 clause_ids.push_back(
2872 implication_graph_->GetClauseId(target.Negated(), Literal(m_index)));
2873 Append(clause_ids,
2874 GetLiteralImpliesRepresentativeClauseId(Literal(m_index)));
2875 Append(clause_ids, GetLiteralImpliesRepresentativeClauseId(target));
2876 }
2877
2878 private:
2879 // The IDs of the two implications of an equivalence between two gate targets.
2880 struct GateEquivalenceClauses {
2881 ClauseId parent_implies_child;
2882 ClauseId child_implies_parent;
2883 };
2884
2885 bool IsRepresentative(Literal l) const { return GetParent(l) == l; }
2886
2887 Literal GetParent(Literal l) const {
2888 return Literal(LiteralIndex(union_find_.GetParent(l.Index().value())));
2889 }
2890
2891 ClauseId GetLiteralImpliesRepresentativeClauseId(Literal l) {
2892 ShortenEquivalencesWithRepresentative(l);
2893 const auto it = parent_equivalence_.find(l);
2894 if (it == parent_equivalence_.end()) return kNoClauseId;
2895 return it->second.child_implies_parent;
2896 }
2897
2898 ClauseId GetRepresentativeImpliesLiteralClauseId(Literal l) {
2899 ShortenEquivalencesWithRepresentative(l);
2900 const auto it = parent_equivalence_.find(l);
2901 if (it == parent_equivalence_.end()) return kNoClauseId;
2902 return it->second.parent_implies_child;
2903 }
2904
2905 template <typename Vector>
2906 static void Append(Vector& clauses, ClauseId new_clause) {
2907 if (new_clause != kNoClauseId) {
2908 clauses.push_back(new_clause);
2909 }
2910 }
2911
2912 const Trail* trail_;
2913 const BinaryImplicationGraph* implication_graph_;
2914 ClauseManager* clause_manager_;
2915 ClauseIdGenerator* clause_id_generator_;
2916 LratProofHandler* lrat_proof_handler_;
2917 const util_intops::StrongVector<GateId, LiteralIndex>& gates_target_;
2918 const CompactVectorVector<GateId, const SatClause*>& gates_clauses_;
2919 DenseConnectedComponentsFinder& union_find_;
2920
2921 // For each gate target with a parent in `union_find_` different from itself,
2922 // the equivalence clauses with this parent literal.
2923 absl::flat_hash_map<Literal, GateEquivalenceClauses> parent_equivalence_;
2924 // Equivalence clauses which are not needed after the current round.
2925 std::vector<ClauseId> to_delete_;
2926 // The literals of the clauses in `to_delete_`. Only needed when checking
2927 // DRAT.
2928 std::vector<std::pair<Literal, Literal>> clauses_to_delete_;
2929
2930 // For AddShortGateTargetImplication().
2931 std::vector<int> tmp_index_to_delete_;
2932 std::vector<ClauseId> tmp_proof_clauses_id_;
2933 CompactVectorVector<int, Literal> tmp_proof_clauses_;
2934
2935 // For the simplification of clauses using equivalences in
2936 // AddGateClausesToTemporaryProof().
2937 SparseBitset<LiteralIndex> marked_;
2938 std::vector<ClauseId> tmp_clause_ids_;
2939 std::vector<Literal> tmp_literals_;
2940};
2941
2942} // namespace
2943
2945 // TODO(user): Remove this condition, it is possible there are no binary
2946 // and still gates!
2947 if (implication_graph_->IsEmpty()) return true;
2948
2949 PresolveTimer timer("GateCongruenceClosure", logger_, time_limit_);
2950 timer.OverrideLogging(log_info);
2951
2952 const int num_variables(sat_solver_->NumVariables());
2953 const int num_literals(num_variables * 2);
2954 marked_.ClearAndResize(Literal(num_literals));
2955 seen_.ClearAndResize(Literal(num_literals));
2956 next_seen_.ClearAndResize(Literal(num_literals));
2957
2958 gates_target_.clear();
2959 gates_inputs_.clear();
2960 gates_type_.clear();
2961 gates_clauses_.clear();
2962
2963 // Lets release the memory on exit.
2964 CHECK(tmp_binary_clauses_.empty());
2965 absl::Cleanup binary_cleanup = [this] { tmp_binary_clauses_.clear(); };
2966
2967 ExtractAndGatesAndFillShortTruthTables(timer);
2968 ExtractShortGates(timer);
2969
2970 // All vector have the same size.
2971 // Except gates_clauses_ which is only filled if we need proof.
2972 CHECK_EQ(gates_target_.size(), gates_type_.size());
2973 CHECK_EQ(gates_target_.size(), gates_inputs_.size());
2974 if (lrat_proof_handler_ != nullptr) {
2975 CHECK_EQ(gates_target_.size(), gates_clauses_.size());
2976 }
2977
2978 // If two gates have the same type and the same inputs, their targets are
2979 // equivalent. We use an hash set to detect that the inputs are the same.
2980 absl::flat_hash_set<GateId, GateHash, GateEq> gate_set(
2981 /*capacity=*/gates_inputs_.size(), GateHash(&gates_type_, &gates_inputs_),
2982 GateEq(&gates_type_, &gates_inputs_));
2983
2984 // Used to find representatives as we detect equivalent literal.
2986 union_find.SetNumberOfNodes(num_literals);
2987
2988 // This will also be updated as we detect equivalences and allows to find
2989 // all the gates with a given input literal, taking into account all its
2990 // equivalences.
2991 //
2992 // Tricky: we need to resize this to num_literals because the union_find that
2993 // merges target can choose for a representative a literal that is not in the
2994 // set of gate inputs.
2996 struct GetVarMapper {
2997 BooleanVariable operator()(LiteralIndex l) const {
2998 return Literal(l).Variable();
2999 }
3000 };
3001 input_var_to_gate.ResetFromTransposeMap<GetVarMapper>(gates_inputs_,
3002 num_variables);
3003
3004 LratGateCongruenceHelper lrat_helper(
3005 trail_, implication_graph_, clause_manager_, clause_id_generator_,
3006 lrat_proof_handler_, gates_target_, gates_clauses_, union_find);
3007
3008 // Stats + make sure we run it at exit.
3009 int num_units = 0;
3010 int num_equivalences = 0;
3011 int num_processed = 0;
3012 int arity1_equivalences = 0;
3013 absl::Cleanup stat_cleanup = [&] {
3014 total_wtime_ += timer.wtime();
3015 total_dtime_ += timer.deterministic_time();
3016 total_equivalences_ += num_equivalences;
3017 total_num_units_ += num_units;
3018 timer.AddCounter("processed", num_processed);
3019 timer.AddCounter("units", num_units);
3020 timer.AddCounter("f1_equiv", arity1_equivalences);
3021 timer.AddCounter("equiv", num_equivalences);
3022 };
3023
3024 // Starts with all gates in the queue.
3025 const int num_gates = gates_inputs_.size();
3026 total_gates_ += num_gates;
3027 std::vector<bool> in_queue(num_gates, true);
3028 std::vector<GateId> queue(num_gates);
3029 for (GateId id(0); id < num_gates; ++id) queue[id.value()] = id;
3030
3031 int num_processed_fixed_variables = trail_->Index();
3032
3033 const auto fix_literal = [&, this](Literal to_fix,
3034 absl::Span<const ClauseId> clause_ids) {
3035 DCHECK_EQ(to_fix, lrat_helper.GetRepresentativeWithProofSupport(to_fix));
3036 if (assignment_.LiteralIsTrue(to_fix)) return true;
3037 if (!clause_manager_->InprocessingFixLiteral(to_fix, clause_ids)) {
3038 return false;
3039 }
3040
3041 // This is quite tricky: as we fix a literal, we propagate right away
3042 // everything implied by it in the binary implication graph. So we need to
3043 // loop over all newly_fixed variable in order to properly reach the fix
3044 // point!
3045 ++num_units;
3046 for (; num_processed_fixed_variables < trail_->Index();
3047 ++num_processed_fixed_variables) {
3048 const Literal to_update = lrat_helper.GetRepresentativeWithProofSupport(
3049 (*trail_)[num_processed_fixed_variables]);
3050 for (const GateId gate_id : input_var_to_gate[to_update.Variable()]) {
3051 if (in_queue[gate_id.value()]) continue;
3052 queue.push_back(gate_id);
3053 in_queue[gate_id.value()] = true;
3054 }
3055 input_var_to_gate.ClearList(to_update.Variable());
3056 }
3057 return true;
3058 };
3059
3060 const auto get_unit_clause = [this](Literal a) {
3061 if (lrat_proof_handler_ == nullptr) return kNoClauseId;
3062 return trail_->GetUnitClauseId(a.Variable());
3063 };
3064
3065 const auto new_equivalence = [&, this](Literal a, Literal b,
3066 ClauseId a_implies_b,
3067 ClauseId b_implies_a) {
3068 // Lets propagate fixed variable as we find new equivalences.
3069 if (assignment_.LiteralIsAssigned(a)) {
3070 if (assignment_.LiteralIsTrue(a)) {
3071 return fix_literal(b, {a_implies_b, get_unit_clause(a)});
3072 } else {
3073 return fix_literal(b.Negated(), {b_implies_a, get_unit_clause(a)});
3074 }
3075 } else if (assignment_.LiteralIsAssigned(b)) {
3076 if (assignment_.LiteralIsTrue(b)) {
3077 return fix_literal(a, {b_implies_a, get_unit_clause(b)});
3078 } else {
3079 return fix_literal(a.Negated(), {a_implies_b, get_unit_clause(b)});
3080 }
3081 }
3082
3083 ++num_equivalences;
3084 DCHECK(!implication_graph_->IsRedundant(a));
3085 DCHECK(!implication_graph_->IsRedundant(b));
3086 if (!implication_graph_->AddBinaryClause(a_implies_b, a.Negated(), b) ||
3087 !implication_graph_->AddBinaryClause(b_implies_a, b.Negated(), a)) {
3088 return false;
3089 }
3090
3091 BooleanVariable to_merge_var = kNoBooleanVariable;
3092 BooleanVariable rep_var = kNoBooleanVariable;
3093 for (const bool negate : {false, true}) {
3094 const LiteralIndex x = negate ? a.NegatedIndex() : a.Index();
3095 const LiteralIndex y = negate ? b.NegatedIndex() : b.Index();
3096 const ClauseId x_implies_y = negate ? b_implies_a : a_implies_b;
3097 const ClauseId y_implies_x = negate ? a_implies_b : b_implies_a;
3098
3099 // Because x always refer to a and y to b, this should maintain
3100 // the invariant root(lit) = root(lit.Negated()).Negated().
3101 // This is checked below.
3102 union_find.AddEdge(x.value(), y.value());
3103 const LiteralIndex rep(union_find.FindRoot(y.value()));
3104 const LiteralIndex to_merge = rep == x ? y : x;
3105 if (to_merge_var == kNoBooleanVariable) {
3106 to_merge_var = Literal(to_merge).Variable();
3107 rep_var = Literal(rep).Variable();
3108 } else {
3109 // We should have the same var.
3110 CHECK_EQ(to_merge_var, Literal(to_merge).Variable());
3111 CHECK_EQ(rep_var, Literal(rep).Variable());
3112 }
3113
3114 if (lrat_proof_handler_ != nullptr) {
3115 if (rep == x) {
3116 lrat_helper.AddGateEquivalenceClauses(Literal(y), y_implies_x,
3117 x_implies_y);
3118 } else {
3119 lrat_helper.AddGateEquivalenceClauses(Literal(x), x_implies_y,
3120 y_implies_x);
3121 }
3122 }
3123 }
3124
3125 // Invariant.
3126 CHECK_EQ(
3127 lrat_helper.GetRepresentativeWithProofSupport(a),
3128 lrat_helper.GetRepresentativeWithProofSupport(a.Negated()).Negated());
3129 CHECK_EQ(
3130 lrat_helper.GetRepresentativeWithProofSupport(b),
3131 lrat_helper.GetRepresentativeWithProofSupport(b.Negated()).Negated());
3132
3133 // Re-add to the queue all gates with touched inputs.
3134 //
3135 // TODO(user): I think we could only add the gates of "to_merge"
3136 // before we merge. This part of the code is quite quick in any
3137 // case.
3138 input_var_to_gate.MergeInto(to_merge_var, rep_var);
3139 for (const GateId gate_id : input_var_to_gate[rep_var]) {
3140 if (in_queue[gate_id.value()]) continue;
3141 queue.push_back(gate_id);
3142 in_queue[gate_id.value()] = true;
3143 }
3144
3145 return true;
3146 };
3147
3148 // Main loop.
3149 while (!queue.empty()) {
3150 ++num_processed;
3151 const GateId id = queue.back();
3152 queue.pop_back();
3153 CHECK(in_queue[id.value()]);
3154 in_queue[id.value()] = false;
3155
3156 // Tricky: the hash-map might contain id not yet canonicalized. And in
3157 // particular might already contain the id we are processing.
3158 //
3159 // The first pass will check equivalence with the "non-canonical
3160 // version" and remove id if it was already there. The second will do it on
3161 // the canonicalized version.
3162 for (int pass = 0; pass < 2; ++pass) {
3163 GateId other_id(-1);
3164 bool is_equivalent = false;
3165 if (pass == 0) {
3166 const auto it = gate_set.find(id);
3167 if (it != gate_set.end()) {
3168 other_id = *it;
3169 if (id == other_id) {
3170 // This gate was already in the set, remove it before we
3171 // insert its potentially different canonicalized version.
3172 gate_set.erase(it);
3173 } else {
3174 is_equivalent = true;
3175 }
3176 }
3177 } else {
3178 const auto [it, inserted] = gate_set.insert(id);
3179 if (!inserted) {
3180 other_id = *it;
3181 is_equivalent = true;
3182 }
3183 }
3184
3185 if (is_equivalent) {
3186 CHECK_NE(id, other_id);
3187 CHECK_GE(other_id, 0);
3188 CHECK_EQ(gates_type_[id], gates_type_[other_id]);
3189 CHECK_EQ(gates_inputs_[id], gates_inputs_[other_id]);
3190
3191 input_var_to_gate.RemoveFromFutureOutput(id);
3192
3193 // We detected a <=> b (or, equivalently, rep(a) <=> rep(b)).
3194 const Literal a(gates_target_[id]);
3195 const Literal b(gates_target_[other_id]);
3196 const Literal rep_a = lrat_helper.GetRepresentativeWithProofSupport(a);
3197 const Literal rep_b = lrat_helper.GetRepresentativeWithProofSupport(b);
3198 if (rep_a != rep_b) {
3199 ClauseId rep_a_implies_rep_b = kNoClauseId;
3200 ClauseId rep_b_implies_rep_a = kNoClauseId;
3201 if (lrat_proof_handler_ != nullptr) {
3202 if (gates_type_[id] == kAndGateType) {
3203 rep_a_implies_rep_b =
3204 lrat_helper.AddAndGateTargetImplication(id, other_id);
3205 rep_b_implies_rep_a =
3206 lrat_helper.AddAndGateTargetImplication(other_id, id);
3207 } else {
3208 const auto [x, y] = lrat_helper.AddShortGateEquivalence(
3209 rep_a, rep_b, {id, other_id});
3210 rep_a_implies_rep_b = x;
3211 rep_b_implies_rep_a = y;
3212 }
3213 }
3214 if (!new_equivalence(rep_a, rep_b, rep_a_implies_rep_b,
3215 rep_b_implies_rep_a)) {
3216 return false;
3217 }
3218 }
3219 break;
3220 }
3221
3222 // Canonicalize on pass zero, otherwise loop.
3223 // Note that even if nothing change, we want to run canonicalize at
3224 // least once on the small "truth table" gates.
3225 //
3226 // Note that sorting works for and_gate and any gate that does not depend
3227 // on the order of its inputs. But if we add more fancy functions, we will
3228 // need to be careful.
3229 if (pass > 0) continue;
3230
3231 if (gates_type_[id] == kAndGateType) {
3232 absl::Span<LiteralIndex> inputs = gates_inputs_[id];
3233 marked_.ResetAllToFalse();
3234 int new_size = 0;
3235 bool is_unit = false;
3236 for (const LiteralIndex l : inputs) {
3237 if (lrat_proof_handler_ != nullptr) {
3238 lrat_helper.ShortenEquivalencesWithRepresentative(Literal(l));
3239 }
3240 const LiteralIndex rep(union_find.FindRoot(l.value()));
3241 if (marked_[rep]) continue;
3242
3243 // This only works for and-gate, if both lit and not(lit) are input,
3244 // then target must be false.
3245 if (marked_[Literal(rep).Negated()]) {
3246 is_unit = true;
3247 input_var_to_gate.RemoveFromFutureOutput(id);
3248
3249 const Literal initial_to_fix = Literal(gates_target_[id]).Negated();
3250 const Literal to_fix =
3251 lrat_helper.GetRepresentativeWithProofSupport(initial_to_fix);
3252 if (!assignment_.LiteralIsTrue(to_fix)) {
3253 absl::InlinedVector<ClauseId, 4> clause_ids;
3254 if (lrat_proof_handler_ != nullptr) {
3255 lrat_helper.AppendFixAndGateTargetClauses(id, Literal(rep),
3256 clause_ids);
3257 }
3258 if (!fix_literal(to_fix, clause_ids)) return false;
3259 }
3260 break;
3261 }
3262
3263 marked_.Set(rep);
3264 inputs[new_size++] = rep;
3265 }
3266
3267 if (is_unit) {
3268 break; // Abort the passes loop.
3269 }
3270
3271 // We need to re-sort, even if the new_size is the same, since
3272 // representatives might be different.
3273 CHECK_GT(new_size, 0);
3274 std::sort(inputs.begin(), inputs.begin() + new_size);
3275 gates_inputs_.Shrink(id, new_size);
3276
3277 // Lets convert a kAndGateType to the short "type" if we can. The truth
3278 // table is simply a 1 on the last position (where all inputs are ones).
3279 // We fall back to the case below to canonicalize further.
3280 //
3281 // This is needed because while our generic and_gate use 1 clause +
3282 // binary, it wont detect a kAndGateType "badly" encoded with 4 ternary
3283 // clauses for instance:
3284 //
3285 // b & c => a
3286 // not(b) & c => not(a)
3287 // b & not(c) => not(a)
3288 // not(b) & not(c) => not(a)
3289 //
3290 // This is even more important since "generic" short gates might get
3291 // simplified as we detect equialences, and become an and_gate later.
3292 if (new_size > 4) continue;
3293 gates_type_[id] = 1 << ((1 << new_size) - 1);
3294 }
3295
3296 // Generic "short" gates.
3297 // We just take the representative and re-canonicalize.
3298 DCHECK_GE(gates_type_[id], 0);
3299 DCHECK_EQ(gates_type_[id] >> (1 << (gates_inputs_[id].size())), 0);
3300 for (LiteralIndex& lit_ref : gates_inputs_[id]) {
3301 lit_ref =
3302 lrat_helper.GetRepresentativeWithProofSupport(Literal(lit_ref))
3303 .Index();
3304 }
3305
3306 const int new_size = CanonicalizeShortGate(id);
3307 if (new_size == 1) {
3308 // We have a function of size 1! This is an equivalence.
3309 input_var_to_gate.RemoveFromFutureOutput(id);
3310 const Literal a = Literal(gates_target_[id]);
3311 const Literal b = Literal(gates_inputs_[id][0]);
3312 const Literal rep_a = lrat_helper.GetRepresentativeWithProofSupport(a);
3313 const Literal rep_b = lrat_helper.GetRepresentativeWithProofSupport(b);
3314 if (rep_a != rep_b) {
3315 ++arity1_equivalences;
3316 const auto [a_to_b, b_to_a] =
3317 lrat_helper.AddShortGateEquivalence(rep_a, rep_b, {id});
3318 if (!new_equivalence(rep_a, rep_b, a_to_b, b_to_a)) {
3319 return false;
3320 }
3321 }
3322 break;
3323 } else if (new_size == 0) {
3324 // We have a fixed function! Just fix the literal.
3325 input_var_to_gate.RemoveFromFutureOutput(id);
3326 const Literal initial_to_fix =
3327 (gates_type_[id] & 1) == 1 ? Literal(gates_target_[id])
3328 : Literal(gates_target_[id]).Negated();
3329 const Literal to_fix =
3330 lrat_helper.GetRepresentativeWithProofSupport(initial_to_fix);
3331 if (!assignment_.LiteralIsTrue(to_fix)) {
3332 const ClauseId clause_id =
3333 lrat_helper.ProofForFixingLiteral(to_fix, id);
3334 if (!fix_literal(to_fix, {clause_id})) return false;
3335 }
3336 break;
3337 }
3338 }
3339 }
3340
3341 // DEBUG check that we reached the fix point correctly.
3342 if (DEBUG_MODE) {
3343 CHECK(queue.empty());
3344 gate_set.clear();
3345 for (GateId id(0); id < num_gates; ++id) {
3346 if (gates_type_[id] == kAndGateType) continue;
3347 if (assignment_.LiteralIsAssigned(Literal(gates_target_[id]))) continue;
3348
3349 const int new_size = CanonicalizeShortGate(id);
3350 if (new_size == 0) {
3351 CHECK_EQ(gates_type_[id] & 1, 0);
3352 const Literal initial_to_fix = Literal(gates_target_[id]).Negated();
3353 const Literal to_fix =
3354 lrat_helper.GetRepresentativeWithProofSupport(initial_to_fix);
3355 CHECK(assignment_.LiteralIsTrue(to_fix));
3356 } else if (new_size == 1) {
3357 CHECK(!assignment_.LiteralIsAssigned(Literal(gates_target_[id])));
3358 CHECK(!assignment_.LiteralIsAssigned(Literal(gates_inputs_[id][0])));
3359 CHECK_EQ(lrat_helper.GetRepresentativeWithProofSupport(
3360 Literal(gates_target_[id])),
3361 lrat_helper.GetRepresentativeWithProofSupport(
3362 Literal(gates_inputs_[id][0])))
3363 << id << " ";
3364 } else {
3365 const auto [it, inserted] = gate_set.insert(id);
3366 if (!inserted) {
3367 const GateId other_id = *it;
3368 CHECK_EQ(lrat_helper.GetRepresentativeWithProofSupport(
3369 Literal(gates_target_[id])),
3370 lrat_helper.GetRepresentativeWithProofSupport(
3371 Literal(gates_target_[other_id])))
3372 << id << " " << gates_inputs_[id] << " " << other_id << " "
3373 << gates_inputs_[other_id];
3374 }
3375 }
3376 }
3377 }
3378
3379 return true;
3380}
3381
3382} // namespace sat
3383} // namespace operations_research
bool AddEdge(int node1, int node2)
double Get() const
Definition timer.h:44
void Start()
Definition timer.h:30
void OverrideLogging(bool value)
Definition logging.h:160
void AddCounter(std::string name, int64_t count)
Definition logging.h:147
void Set(IntegerType index)
Definition bitset.h:878
ClauseId GetClauseId(Literal a, Literal b) const
Definition clause.h:672
void LazyDelete(SatClause *clause, DeletionSourceForStat source)
Definition clause.cc:436
bool IsRemovable(SatClause *const clause) const
Definition clause.h:254
const std::vector< SatClause * > & AllClausesInCreationOrder() const
Definition clause.h:246
int Add(absl::Span< const V > values)
Definition util.h:935
bool DetectEquivalencesAndStamp(bool use_transitive_reduction, bool log_info)
bool RemoveFixedAndEquivalentVariables(bool log_info)
bool PresolveLoop(SatPresolveOptions options)
LiteralIndex NegatedIndex() const
Definition sat_base.h:93
LiteralIndex Index() const
Definition sat_base.h:92
BooleanVariable Variable() const
Definition sat_base.h:88
void ResetFromTransposeMap(const Container &input, int min_transpose_size=0)
Definition util.h:197
void MergeInto(K to_merge, K representative)
Definition util.h:253
static SatClause * Create(absl::Span< const Literal > literals)
Definition clause.cc:3590
absl::Span< const Literal > AsSpan() const
Definition clause.h:100
bool ImplicationIsInTree(Literal a, Literal b) const
bool LiteralIsFalse(Literal literal) const
Definition sat_base.h:203
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:206
void assign(size_type n, const value_type &val)
void push_back(const value_type &val)
void resize(size_type new_size)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:55
void STLClearObject(T *obj)
Definition stl_util.h:120
int RemoveFixedInput(int i, bool at_true, absl::Span< LiteralIndex > inputs, int &int_function_values)
Definition gate_utils.h:101
const LiteralIndex kNoLiteralIndex(-1)
bool IsFunction(int i, int num_bits, SmallBitset truth_table)
Definition gate_utils.h:85
SmallBitset GetNumBitsAtOne(int num_bits)
Definition gate_utils.h:30
void CanonicalizeTruthTable(absl::Span< VarOrLiteral > key, SmallBitset &bitmask)
Definition gate_utils.h:40
constexpr ClauseId kNoClauseId(0)
int AddHoleAtPosition(int i, int bitset)
Definition gate_utils.h:97
int CanonicalizeFunctionTruthTable(LiteralIndex &target, absl::Span< LiteralIndex > inputs, int &int_function_values)
Definition gate_utils.h:126
void FillKeyAndBitmask(absl::Span< const Literal > clause, absl::Span< BooleanVariable > key, SmallBitset &bitmask)
Definition gate_utils.h:67
const BooleanVariable kNoBooleanVariable(-1)
OR-Tools root namespace.
ClosedInterval::Iterator end(ClosedInterval interval)
const bool DEBUG_MODE
Definition radix_sort.h:266
std::string FormatCounter(int64_t num)
Definition logging.cc:30
#define RETURN_IF_FALSE(f)
Definition diffn.cc:371
std::deque< std::vector< Literal > > clauses
void AddClauseWithSpecialLiteral(Literal literal, absl::Span< const Literal > clause)
#define FORCED_SOLVER_LOG(logger,...)
Definition logging.h:118
#define SOLVER_LOG(logger,...)
Definition logging.h:114