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