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