OR-Tools  9.6
clause.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 
14 #include "ortools/sat/clause.h"
15 
16 #include <stddef.h>
17 
18 #include <algorithm>
19 #include <cstdint>
20 #include <deque>
21 #include <string>
22 #include <utility>
23 #include <vector>
24 
25 #include "absl/container/flat_hash_map.h"
26 #include "absl/container/flat_hash_set.h"
27 #include "absl/container/inlined_vector.h"
28 #include "absl/random/bit_gen_ref.h"
29 #include "absl/random/distributions.h"
30 #include "absl/types/span.h"
31 #include "ortools/base/hash.h"
32 #include "ortools/base/logging.h"
33 #include "ortools/base/stl_util.h"
35 #include "ortools/base/timer.h"
38 #include "ortools/sat/inclusion.h"
39 #include "ortools/sat/model.h"
40 #include "ortools/sat/sat_base.h"
41 #include "ortools/util/bitset.h"
42 #include "ortools/util/stats.h"
45 
46 namespace operations_research {
47 namespace sat {
48 
49 namespace {
50 
51 // Returns true if the given watcher list contains the given clause.
52 template <typename Watcher>
53 bool WatcherListContains(const std::vector<Watcher>& list,
54  const SatClause& candidate) {
55  for (const Watcher& watcher : list) {
56  if (watcher.clause == &candidate) return true;
57  }
58  return false;
59 }
60 
61 // A simple wrapper to simplify the erase(std::remove_if()) pattern.
62 template <typename Container, typename Predicate>
63 void RemoveIf(Container c, Predicate p) {
64  c->erase(std::remove_if(c->begin(), c->end(), p), c->end());
65 }
66 
67 } // namespace
68 
69 // ----- LiteralWatchers -----
70 
72  : SatPropagator("LiteralWatchers"),
73  implication_graph_(model->GetOrCreate<BinaryImplicationGraph>()),
74  trail_(model->GetOrCreate<Trail>()),
75  num_inspected_clauses_(0),
76  num_inspected_clause_literals_(0),
77  num_watched_clauses_(0),
78  stats_("LiteralWatchers") {
79  trail_->RegisterPropagator(this);
80 }
81 
83  gtl::STLDeleteElements(&clauses_);
84  IF_STATS_ENABLED(LOG(INFO) << stats_.StatString());
85 }
86 
87 void LiteralWatchers::Resize(int num_variables) {
88  DCHECK(is_clean_);
89  watchers_on_false_.resize(num_variables << 1);
90  reasons_.resize(num_variables);
91  needs_cleaning_.Resize(LiteralIndex(num_variables << 1));
92 }
93 
94 // Note that this is the only place where we add Watcher so the DCHECK
95 // guarantees that there are no duplicates.
96 void LiteralWatchers::AttachOnFalse(Literal literal, Literal blocking_literal,
97  SatClause* clause) {
98  SCOPED_TIME_STAT(&stats_);
99  DCHECK(is_clean_);
100  DCHECK(!WatcherListContains(watchers_on_false_[literal.Index()], *clause));
101  watchers_on_false_[literal.Index()].push_back(
102  Watcher(clause, blocking_literal));
103 }
104 
105 bool LiteralWatchers::PropagateOnFalse(Literal false_literal, Trail* trail) {
106  SCOPED_TIME_STAT(&stats_);
107  DCHECK(is_clean_);
108  std::vector<Watcher>& watchers = watchers_on_false_[false_literal.Index()];
109  const VariablesAssignment& assignment = trail->Assignment();
110 
111  // Note(user): It sounds better to inspect the list in order, this is because
112  // small clauses like binary or ternary clauses will often propagate and thus
113  // stay at the beginning of the list.
114  auto new_it = watchers.begin();
115  const auto end = watchers.end();
116  while (new_it != end && assignment.LiteralIsTrue(new_it->blocking_literal)) {
117  ++new_it;
118  }
119  for (auto it = new_it; it != end; ++it) {
120  // Don't even look at the clause memory if the blocking literal is true.
121  if (assignment.LiteralIsTrue(it->blocking_literal)) {
122  *new_it++ = *it;
123  continue;
124  }
125  ++num_inspected_clauses_;
126 
127  // If the other watched literal is true, just change the blocking literal.
128  // Note that we use the fact that the first two literals of the clause are
129  // the ones currently watched.
130  Literal* literals = it->clause->literals();
131  const Literal other_watched_literal(
132  LiteralIndex(literals[0].Index().value() ^ literals[1].Index().value() ^
133  false_literal.Index().value()));
134  if (assignment.LiteralIsTrue(other_watched_literal)) {
135  *new_it = *it;
136  new_it->blocking_literal = other_watched_literal;
137  ++new_it;
138  ++num_inspected_clause_literals_;
139  continue;
140  }
141 
142  // Look for another literal to watch. We go through the list in a cyclic
143  // fashion from start. The first two literals can be ignored as they are the
144  // watched ones.
145  {
146  const int start = it->start_index;
147  const int size = it->clause->size();
148  DCHECK_GE(start, 2);
149 
150  int i = start;
151  while (i < size && assignment.LiteralIsFalse(literals[i])) ++i;
152  num_inspected_clause_literals_ += i - start + 2;
153  if (i >= size) {
154  i = 2;
155  while (i < start && assignment.LiteralIsFalse(literals[i])) ++i;
156  num_inspected_clause_literals_ += i - 2;
157  if (i >= start) i = size;
158  }
159  if (i < size) {
160  // literal[i] is unassigned or true, it's now the new literal to watch.
161  // Note that by convention, we always keep the two watched literals at
162  // the beginning of the clause.
163  literals[0] = other_watched_literal;
164  literals[1] = literals[i];
165  literals[i] = false_literal;
166  watchers_on_false_[literals[1].Index()].emplace_back(
167  it->clause, other_watched_literal, i + 1);
168  continue;
169  }
170  }
171 
172  // At this point other_watched_literal is either false or unassigned, all
173  // other literals are false.
174  if (assignment.LiteralIsFalse(other_watched_literal)) {
175  // Conflict: All literals of it->clause are false.
176  //
177  // Note(user): we could avoid a copy here, but the conflict analysis
178  // complexity will be a lot higher than this anyway.
179  trail->MutableConflict()->assign(it->clause->begin(), it->clause->end());
180  trail->SetFailingSatClause(it->clause);
181  num_inspected_clause_literals_ += it - watchers.begin() + 1;
182  watchers.erase(new_it, it);
183  return false;
184  } else {
185  // Propagation: other_watched_literal is unassigned, set it to true and
186  // put it at position 0. Note that the position 0 is important because
187  // we will need later to recover the literal that was propagated from the
188  // clause using this convention.
189  literals[0] = other_watched_literal;
190  literals[1] = false_literal;
191  reasons_[trail->Index()] = it->clause;
192  trail->Enqueue(other_watched_literal, propagator_id_);
193  *new_it++ = *it;
194  }
195  }
196  num_inspected_clause_literals_ += watchers.size(); // The blocking ones.
197  watchers.erase(new_it, end);
198  return true;
199 }
200 
202  const int old_index = trail->Index();
203  while (trail->Index() == old_index && propagation_trail_index_ < old_index) {
204  const Literal literal = (*trail)[propagation_trail_index_++];
205  if (!PropagateOnFalse(literal.Negated(), trail)) return false;
206  }
207  return true;
208 }
209 
210 absl::Span<const Literal> LiteralWatchers::Reason(const Trail& trail,
211  int trail_index) const {
212  return reasons_[trail_index]->PropagationReason();
213 }
214 
215 SatClause* LiteralWatchers::ReasonClause(int trail_index) const {
216  return reasons_[trail_index];
217 }
218 
219 bool LiteralWatchers::AddClause(absl::Span<const Literal> literals) {
220  return AddClause(literals, trail_);
221 }
222 
223 bool LiteralWatchers::AddClause(absl::Span<const Literal> literals,
224  Trail* trail) {
225  SatClause* clause = SatClause::Create(literals);
226  clauses_.push_back(clause);
227  return AttachAndPropagate(clause, trail);
228 }
229 
231  const std::vector<Literal>& literals, Trail* trail) {
232  SatClause* clause = SatClause::Create(literals);
233  clauses_.push_back(clause);
234  CHECK(AttachAndPropagate(clause, trail));
235  return clause;
236 }
237 
238 // Sets up the 2-watchers data structure. It selects two non-false literals
239 // and attaches the clause to the event: one of the watched literals become
240 // false. It returns false if the clause only contains literals assigned to
241 // false. If only one literals is not false, it propagates it to true if it
242 // is not already assigned.
243 bool LiteralWatchers::AttachAndPropagate(SatClause* clause, Trail* trail) {
244  SCOPED_TIME_STAT(&stats_);
245 
246  const int size = clause->size();
247  Literal* literals = clause->literals();
248 
249  // Select the first two literals that are not assigned to false and put them
250  // on position 0 and 1.
251  int num_literal_not_false = 0;
252  for (int i = 0; i < size; ++i) {
253  if (!trail->Assignment().LiteralIsFalse(literals[i])) {
254  std::swap(literals[i], literals[num_literal_not_false]);
255  ++num_literal_not_false;
256  if (num_literal_not_false == 2) {
257  break;
258  }
259  }
260  }
261 
262  // Returns false if all the literals were false.
263  // This should only happen on an UNSAT problem, and there is no need to attach
264  // the clause in this case.
265  if (num_literal_not_false == 0) return false;
266 
267  if (num_literal_not_false == 1) {
268  // To maintain the validity of the 2-watcher algorithm, we need to watch
269  // the false literal with the highest decision level.
270  int max_level = trail->Info(literals[1].Variable()).level;
271  for (int i = 2; i < size; ++i) {
272  const int level = trail->Info(literals[i].Variable()).level;
273  if (level > max_level) {
274  max_level = level;
275  std::swap(literals[1], literals[i]);
276  }
277  }
278 
279  // Propagates literals[0] if it is unassigned.
280  if (!trail->Assignment().LiteralIsTrue(literals[0])) {
281  reasons_[trail->Index()] = clause;
282  trail->Enqueue(literals[0], propagator_id_);
283  }
284  }
285 
286  ++num_watched_clauses_;
287  AttachOnFalse(literals[0], literals[1], clause);
288  AttachOnFalse(literals[1], literals[0], clause);
289  return true;
290 }
291 
292 void LiteralWatchers::Attach(SatClause* clause, Trail* trail) {
293  Literal* literals = clause->literals();
294  CHECK(!trail->Assignment().LiteralIsAssigned(literals[0]));
295  CHECK(!trail->Assignment().LiteralIsAssigned(literals[1]));
296 
297  ++num_watched_clauses_;
298  AttachOnFalse(literals[0], literals[1], clause);
299  AttachOnFalse(literals[1], literals[0], clause);
300 }
301 
302 void LiteralWatchers::InternalDetach(SatClause* clause) {
303  --num_watched_clauses_;
304  const size_t size = clause->size();
305  if (drat_proof_handler_ != nullptr && size > 2) {
306  drat_proof_handler_->DeleteClause({clause->begin(), size});
307  }
308  clauses_info_.erase(clause);
309  clause->Clear();
310 }
311 
313  InternalDetach(clause);
314  is_clean_ = false;
315  needs_cleaning_.Set(clause->FirstLiteral().Index());
316  needs_cleaning_.Set(clause->SecondLiteral().Index());
317 }
318 
320  InternalDetach(clause);
321  for (const Literal l : {clause->FirstLiteral(), clause->SecondLiteral()}) {
322  needs_cleaning_.Clear(l.Index());
323  RemoveIf(&(watchers_on_false_[l.Index()]), [](const Watcher& watcher) {
324  return !watcher.clause->IsAttached();
325  });
326  }
327 }
328 
330  if (!all_clauses_are_attached_) return;
331  all_clauses_are_attached_ = false;
332 
333  // This is easy, and this allows to reset memory if some watcher lists where
334  // really long at some point.
335  is_clean_ = true;
336  num_watched_clauses_ = 0;
337  watchers_on_false_.clear();
338 }
339 
341  if (all_clauses_are_attached_) return;
342  all_clauses_are_attached_ = true;
343 
344  needs_cleaning_.ClearAll(); // This doesn't resize it.
345  watchers_on_false_.resize(needs_cleaning_.size().value());
346 
348  for (SatClause* clause : clauses_) {
349  ++num_watched_clauses_;
350  CHECK_GE(clause->size(), 2);
351  AttachOnFalse(clause->FirstLiteral(), clause->SecondLiteral(), clause);
352  AttachOnFalse(clause->SecondLiteral(), clause->FirstLiteral(), clause);
353  }
354 }
355 
356 // This one do not need the clause to be detached.
358  CHECK_EQ(trail_->CurrentDecisionLevel(), 0);
359  if (drat_proof_handler_ != nullptr) {
360  drat_proof_handler_->AddClause({true_literal});
361  }
362  // TODO(user): remove the test when the DRAT issue with fixed literal is
363  // resolved.
364  if (!trail_->Assignment().LiteralIsTrue(true_literal)) {
365  trail_->EnqueueWithUnitReason(true_literal);
366 
367  // Even when all clauses are detached, we can propagate the implication
368  // graph and we do that right away.
369  return implication_graph_->Propagate(trail_);
370  }
371  return true;
372 }
373 
374 // TODO(user): We could do something slower if the clauses are attached like
375 // we do for InprocessingRewriteClause().
377  CHECK(!all_clauses_are_attached_);
378  if (drat_proof_handler_ != nullptr) {
379  drat_proof_handler_->DeleteClause(clause->AsSpan());
380  }
381  clauses_info_.erase(clause);
382  clause->Clear();
383 }
384 
386  SatClause* clause, absl::Span<const Literal> new_clause) {
387  if (new_clause.empty()) return false; // UNSAT.
388 
389  if (DEBUG_MODE) {
390  for (const Literal l : new_clause) {
391  CHECK(!trail_->Assignment().LiteralIsAssigned(l));
392  }
393  }
394 
395  if (new_clause.size() == 1) {
396  if (!InprocessingFixLiteral(new_clause[0])) return false;
397  InprocessingRemoveClause(clause);
398  return true;
399  }
400 
401  if (new_clause.size() == 2) {
402  implication_graph_->AddBinaryClause(new_clause[0], new_clause[1]);
403  InprocessingRemoveClause(clause);
404  return true;
405  }
406 
407  if (drat_proof_handler_ != nullptr) {
408  // We must write the new clause before we delete the old one.
409  drat_proof_handler_->AddClause(new_clause);
410  drat_proof_handler_->DeleteClause(clause->AsSpan());
411  }
412 
413  if (all_clauses_are_attached_) {
414  // We can still rewrite the clause, but it is inefficient. We first
415  // detach it in a non-lazy way.
416  --num_watched_clauses_;
417  clause->Clear();
418  for (const Literal l : {clause->FirstLiteral(), clause->SecondLiteral()}) {
419  needs_cleaning_.Clear(l.Index());
420  RemoveIf(&(watchers_on_false_[l.Index()]), [](const Watcher& watcher) {
421  return !watcher.clause->IsAttached();
422  });
423  }
424  }
425 
426  clause->Rewrite(new_clause);
427 
428  // And we re-attach it.
429  if (all_clauses_are_attached_) Attach(clause, trail_);
430  return true;
431 }
432 
434  absl::Span<const Literal> new_clause) {
435  CHECK(!new_clause.empty());
436  CHECK(!all_clauses_are_attached_);
437  if (DEBUG_MODE) {
438  for (const Literal l : new_clause) {
439  CHECK(!trail_->Assignment().LiteralIsAssigned(l));
440  }
441  }
442 
443  if (new_clause.size() == 1) {
444  // TODO(user): We should return false...
445  if (!InprocessingFixLiteral(new_clause[0])) return nullptr;
446  return nullptr;
447  }
448 
449  if (new_clause.size() == 2) {
450  implication_graph_->AddBinaryClause(new_clause[0], new_clause[1]);
451  return nullptr;
452  }
453 
454  SatClause* clause = SatClause::Create(new_clause);
455  clauses_.push_back(clause);
456  return clause;
457 }
458 
460  SCOPED_TIME_STAT(&stats_);
461  for (LiteralIndex index : needs_cleaning_.PositionsSetAtLeastOnce()) {
462  DCHECK(needs_cleaning_[index]);
463  RemoveIf(&(watchers_on_false_[index]), [](const Watcher& watcher) {
464  return !watcher.clause->IsAttached();
465  });
466  needs_cleaning_.Clear(index);
467  }
468  needs_cleaning_.NotifyAllClear();
469  is_clean_ = true;
470 }
471 
473  DCHECK(is_clean_);
474 
475  // Update to_minimize_index_.
476  if (to_minimize_index_ >= clauses_.size()) {
477  to_minimize_index_ = clauses_.size();
478  }
479  to_minimize_index_ =
480  std::stable_partition(clauses_.begin(),
481  clauses_.begin() + to_minimize_index_,
482  [](SatClause* a) { return a->IsAttached(); }) -
483  clauses_.begin();
484 
485  // Do the proper deletion.
486  std::vector<SatClause*>::iterator iter =
487  std::stable_partition(clauses_.begin(), clauses_.end(),
488  [](SatClause* a) { return a->IsAttached(); });
489  gtl::STLDeleteContainerPointers(iter, clauses_.end());
490  clauses_.erase(iter, clauses_.end());
491 }
492 
493 // ----- BinaryImplicationGraph -----
494 
495 void BinaryImplicationGraph::Resize(int num_variables) {
496  SCOPED_TIME_STAT(&stats_);
497  implications_.resize(num_variables << 1);
498  is_redundant_.resize(implications_.size());
499  is_removed_.resize(implications_.size(), false);
500  estimated_sizes_.resize(implications_.size(), 0);
501  in_direct_implications_.resize(implications_.size(), false);
502  reasons_.resize(num_variables);
503 }
504 
505 // TODO(user): Not all of the solver knows about representative literal, do
506 // use them here and in AddBinaryClauseDuringSearch() to maintains invariant?
507 // Explore this when we start cleaning our clauses using equivalence during
508 // search. We can easily do it for every conflict we learn instead of here.
510  SCOPED_TIME_STAT(&stats_);
511  if (drat_proof_handler_ != nullptr) {
512  // TODO(user): Like this we will duplicate all binary clause from the
513  // problem. However this leads to a simpler API (since we don't need to
514  // special case the loading of the original clauses) and we mainly use drat
515  // proof for testing anyway.
516  drat_proof_handler_->AddClause({a, b});
517  }
518  estimated_sizes_[a.NegatedIndex()]++;
519  estimated_sizes_[b.NegatedIndex()]++;
520  implications_[a.NegatedIndex()].push_back(b);
521  implications_[b.NegatedIndex()].push_back(a);
522  is_dag_ = false;
523  num_implications_ += 2;
524 }
525 
527  SCOPED_TIME_STAT(&stats_);
528 
529  // Tricky: If this is the first clause, the propagator will be added and
530  // assumed to be in a "propagated" state. This makes sure this is the case.
531  if (IsEmpty()) propagation_trail_index_ = trail_->Index();
532 
533  AddBinaryClause(a, b);
534 
535  const auto& assignment = trail_->Assignment();
536  if (assignment.LiteralIsFalse(a)) {
537  if (assignment.LiteralIsAssigned(b)) {
538  if (assignment.LiteralIsFalse(b)) return false;
539  } else {
540  reasons_[trail_->Index()] = a;
541  trail_->Enqueue(b, propagator_id_);
542  }
543  } else if (assignment.LiteralIsFalse(b)) {
544  if (!assignment.LiteralIsAssigned(a)) {
545  reasons_[trail_->Index()] = b;
546  trail_->Enqueue(a, propagator_id_);
547  }
548  }
549  is_dag_ = false;
550  return true;
551 }
552 
554  absl::Span<const Literal> at_most_one) {
555  CHECK_EQ(trail_->CurrentDecisionLevel(), 0);
556  if (at_most_one.size() <= 1) return true;
557 
558  // Temporarily copy the at_most_one constraint at the end of
559  // at_most_one_buffer_. It will be cleaned up and added by
560  // CleanUpAndAddAtMostOnes().
561  const int base_index = at_most_one_buffer_.size();
562  at_most_one_buffer_.insert(at_most_one_buffer_.end(), at_most_one.begin(),
563  at_most_one.end());
564  at_most_one_buffer_.push_back(Literal(kNoLiteralIndex));
565 
566  is_dag_ = false;
567  return CleanUpAndAddAtMostOnes(base_index);
568 }
569 
570 // TODO(user): remove duplication with
571 // LiteralWatchers::InprocessingFixLiteral();
572 bool BinaryImplicationGraph::FixLiteral(Literal true_literal) {
573  if (trail_->Assignment().LiteralIsTrue(true_literal)) return true;
574  if (trail_->Assignment().LiteralIsFalse(true_literal)) return false;
575 
576  if (drat_proof_handler_ != nullptr) {
577  drat_proof_handler_->AddClause({true_literal});
578  }
579 
580  trail_->EnqueueWithUnitReason(true_literal);
581  return Propagate(trail_);
582 }
583 
584 // This works by doing a linear scan on the at_most_one_buffer_ and
585 // cleaning/copying the at most ones on the fly to the beginning of the same
586 // buffer.
587 bool BinaryImplicationGraph::CleanUpAndAddAtMostOnes(const int base_index) {
588  const VariablesAssignment& assignment = trail_->Assignment();
589  int local_end = base_index;
590  const int buffer_size = at_most_one_buffer_.size();
591  for (int i = base_index; i < buffer_size; ++i) {
592  if (at_most_one_buffer_[i].Index() == kNoLiteralIndex) continue;
593 
594  // Process a new at most one.
595  // It will be copied into buffer[local_start, local_end].
596  const int local_start = local_end;
597  bool set_all_left_to_false = false;
598  for (;; ++i) {
599  const Literal l = at_most_one_buffer_[i];
600  if (l.Index() == kNoLiteralIndex) break;
601  if (assignment.LiteralIsFalse(l)) continue;
602  if (is_removed_[l.Index()]) continue;
603  if (!set_all_left_to_false && assignment.LiteralIsTrue(l)) {
604  set_all_left_to_false = true;
605  continue;
606  }
607  at_most_one_buffer_[local_end++] = RepresentativeOf(l);
608  }
609 
610  // Deal with duplicates.
611  // Any duplicate in an "at most one" must be false.
612  bool some_duplicates = false;
613  if (!set_all_left_to_false) {
614  int new_local_end = local_start;
615  std::sort(&at_most_one_buffer_[local_start],
616  &at_most_one_buffer_[local_end]);
617  LiteralIndex previous = kNoLiteralIndex;
618  bool remove_previous = false;
619  for (int j = local_start; j < local_end; ++j) {
620  const Literal l = at_most_one_buffer_[j];
621  if (l.Index() == previous) {
622  if (assignment.LiteralIsTrue(l)) return false;
623  if (!assignment.LiteralIsFalse(l)) {
624  if (!FixLiteral(l.Negated())) return false;
625  }
626  remove_previous = true;
627  some_duplicates = true;
628  continue;
629  }
630 
631  // We need to pay attention to triplet or more of equal elements, so
632  // it is why we need this boolean and can't just remove it right away.
633  if (remove_previous) {
634  --new_local_end;
635  remove_previous = false;
636  }
637  previous = l.Index();
638  at_most_one_buffer_[new_local_end++] = l;
639  }
640  if (remove_previous) --new_local_end;
641  local_end = new_local_end;
642  }
643 
644  // If there was some duplicates, we need to rescan to see if a literal
645  // didn't become true because its negation was appearing twice!
646  if (some_duplicates) {
647  int new_local_end = local_start;
648  for (int j = local_start; j < local_end; ++j) {
649  const Literal l = at_most_one_buffer_[j];
650  if (assignment.LiteralIsFalse(l)) continue;
651  if (!set_all_left_to_false && assignment.LiteralIsTrue(l)) {
652  set_all_left_to_false = true;
653  continue;
654  }
655  at_most_one_buffer_[new_local_end++] = l;
656  }
657  local_end = new_local_end;
658  }
659 
660  // Deal with all false.
661  if (set_all_left_to_false) {
662  for (int j = local_start; j < local_end; ++j) {
663  const Literal l = at_most_one_buffer_[j];
664  if (assignment.LiteralIsFalse(l)) continue;
665  if (assignment.LiteralIsTrue(l)) return false;
666  if (!FixLiteral(l.Negated())) return false;
667  }
668  local_end = local_start;
669  continue;
670  }
671 
672  // Create a Span<> to simplify the code below.
673  const absl::Span<const Literal> at_most_one(
674  &at_most_one_buffer_[local_start], local_end - local_start);
675 
676  // We expand small sizes into implications.
677  // TODO(user): Investigate what the best threshold is.
678  if (at_most_one.size() < 10) {
679  // Note that his automatically skip size 0 and 1.
680  for (const Literal a : at_most_one) {
681  for (const Literal b : at_most_one) {
682  if (a == b) continue;
683  implications_[a.Index()].push_back(b.Negated());
684  }
685  }
686  num_implications_ += at_most_one.size() * (at_most_one.size() - 1);
687 
688  // This will erase the at_most_one from the buffer.
689  local_end = local_start;
690  continue;
691  }
692 
693  // Index the new at most one.
694  for (const Literal l : at_most_one) {
695  if (l.Index() >= at_most_ones_.size()) {
696  at_most_ones_.resize(l.Index().value() + 1);
697  }
698  CHECK(!is_redundant_[l.Index()]);
699  at_most_ones_[l.Index()].push_back(local_start);
700  }
701 
702  // Add sentinel.
703  at_most_one_buffer_[local_end++] = Literal(kNoLiteralIndex);
704  }
705 
706  at_most_one_buffer_.resize(local_end);
707  return true;
708 }
709 
710 bool BinaryImplicationGraph::PropagateOnTrue(Literal true_literal,
711  Trail* trail) {
712  SCOPED_TIME_STAT(&stats_);
713 
714  const VariablesAssignment& assignment = trail->Assignment();
715  DCHECK(assignment.LiteralIsTrue(true_literal));
716 
717  // Note(user): This update is not exactly correct because in case of conflict
718  // we don't inspect that much clauses. But doing ++num_inspections_ inside the
719  // loop does slow down the code by a few percent.
720  num_inspections_ += implications_[true_literal.Index()].size();
721 
722  for (Literal literal : implications_[true_literal.Index()]) {
723  if (assignment.LiteralIsTrue(literal)) {
724  // Note(user): I tried to update the reason here if the literal was
725  // enqueued after the true_literal on the trail. This property is
726  // important for ComputeFirstUIPConflict() to work since it needs the
727  // trail order to be a topological order for the deduction graph.
728  // But the performance was not too good...
729  continue;
730  }
731 
732  ++num_propagations_;
733  if (assignment.LiteralIsFalse(literal)) {
734  // Conflict.
735  *(trail->MutableConflict()) = {true_literal.Negated(), literal};
736  return false;
737  } else {
738  // Propagation.
739  reasons_[trail->Index()] = true_literal.Negated();
740  trail->Enqueue(literal, propagator_id_);
741  }
742  }
743 
744  // Propagate the at_most_one constraints.
745  if (true_literal.Index() < at_most_ones_.size()) {
746  for (const int start : at_most_ones_[true_literal.Index()]) {
747  bool seen = false;
748  for (int i = start;; ++i) {
749  const Literal literal = at_most_one_buffer_[i];
750  if (literal.Index() == kNoLiteralIndex) break;
751 
752  ++num_inspections_;
753  if (literal == true_literal) {
754  if (DEBUG_MODE) {
755  CHECK(!seen);
756  seen = true;
757  }
758  continue;
759  }
760  if (assignment.LiteralIsFalse(literal)) continue;
761 
762  ++num_propagations_;
763  if (assignment.LiteralIsTrue(literal)) {
764  // Conflict.
765  *(trail->MutableConflict()) = {true_literal.Negated(),
766  literal.Negated()};
767  return false;
768  } else {
769  // Propagation.
770  reasons_[trail->Index()] = true_literal.Negated();
771  trail->Enqueue(literal.Negated(), propagator_id_);
772  }
773  }
774  }
775  }
776 
777  return true;
778 }
779 
781  if (IsEmpty()) {
782  propagation_trail_index_ = trail->Index();
783  return true;
784  }
785  while (propagation_trail_index_ < trail->Index()) {
786  const Literal literal = (*trail)[propagation_trail_index_++];
787  if (!PropagateOnTrue(literal, trail)) return false;
788  }
789  return true;
790 }
791 
792 absl::Span<const Literal> BinaryImplicationGraph::Reason(
793  const Trail& trail, int trail_index) const {
794  return {&reasons_[trail_index], 1};
795 }
796 
797 // Here, we remove all the literal whose negation are implied by the negation of
798 // the 1-UIP literal (which always appear first in the given conflict). Note
799 // that this algorithm is "optimal" in the sense that it leads to a minimized
800 // conflict with a backjump level as low as possible. However, not all possible
801 // literals are removed.
802 //
803 // TODO(user): Also consider at most one?
805  std::vector<Literal>* conflict) {
806  SCOPED_TIME_STAT(&stats_);
807  dfs_stack_.clear();
808 
809  // Compute the reachability from the literal "not(conflict->front())" using
810  // an iterative dfs.
811  const LiteralIndex root_literal_index = conflict->front().NegatedIndex();
812  is_marked_.ClearAndResize(LiteralIndex(implications_.size()));
813  is_marked_.Set(root_literal_index);
814 
815  // TODO(user): This sounds like a good idea, but somehow it seems better not
816  // to do that even though it is almost for free. Investigate more.
817  //
818  // The idea here is that since we already compute the reachability from the
819  // root literal, we can use this computation to remove any implication
820  // root_literal => b if there is already root_literal => a and b is reachable
821  // from a.
822  const bool also_prune_direct_implication_list = false;
823 
824  // We treat the direct implications differently so we can also remove the
825  // redundant implications from this list at the same time.
826  auto& direct_implications = implications_[root_literal_index];
827  for (const Literal l : direct_implications) {
828  if (is_marked_[l.Index()]) continue;
829  dfs_stack_.push_back(l);
830  while (!dfs_stack_.empty()) {
831  const LiteralIndex index = dfs_stack_.back().Index();
832  dfs_stack_.pop_back();
833  if (!is_marked_[index]) {
834  is_marked_.Set(index);
835  for (Literal implied : implications_[index]) {
836  if (!is_marked_[implied.Index()]) dfs_stack_.push_back(implied);
837  }
838  }
839  }
840 
841  // The "trick" is to unmark 'l'. This way, if we explore it twice, it means
842  // that this l is reachable from some other 'l' from the direct implication
843  // list. Remarks:
844  // - We don't loose too much complexity when this happen since a literal
845  // can be unmarked only once, so in the worst case we loop twice over its
846  // children. Moreover, this literal will be pruned for later calls.
847  // - This is correct, i.e. we can't prune too many literals because of a
848  // strongly connected component. Proof by contradiction: If we take the
849  // first (in direct_implications) literal from a removed SCC, it must
850  // have marked all the others. But because they are marked, they will not
851  // be explored again and so can't mark the first literal.
852  if (also_prune_direct_implication_list) {
853  is_marked_.Clear(l.Index());
854  }
855  }
856 
857  // Now we can prune the direct implications list and make sure are the
858  // literals there are marked.
859  if (also_prune_direct_implication_list) {
860  int new_size = 0;
861  for (const Literal l : direct_implications) {
862  if (!is_marked_[l.Index()]) {
863  is_marked_.Set(l.Index());
864  direct_implications[new_size] = l;
865  ++new_size;
866  }
867  }
868  if (new_size < direct_implications.size()) {
869  num_redundant_implications_ += direct_implications.size() - new_size;
870  direct_implications.resize(new_size);
871  }
872  }
873 
874  RemoveRedundantLiterals(conflict);
875 }
876 
877 // Same as MinimizeConflictWithReachability() but also mark (in the given
878 // SparseBitset) the reachable literal already assigned to false. These literals
879 // will be implied if the 1-UIP literal is assigned to false, and the classic
880 // minimization algorithm can take advantage of that.
882  const Trail& trail, std::vector<Literal>* conflict,
884  SCOPED_TIME_STAT(&stats_);
885  CHECK(!conflict->empty());
886  is_marked_.ClearAndResize(LiteralIndex(implications_.size()));
887  MarkDescendants(conflict->front().Negated());
888  for (const LiteralIndex i : is_marked_.PositionsSetAtLeastOnce()) {
889  if (trail.Assignment().LiteralIsFalse(Literal(i))) {
890  marked->Set(Literal(i).Variable());
891  }
892  }
893  RemoveRedundantLiterals(conflict);
894 }
895 
896 // Same as MinimizeConflictFirst() but take advantage of this reachability
897 // computation to remove redundant implication in the implication list of the
898 // first UIP conflict.
900  const Trail& trail, std::vector<Literal>* conflict,
901  absl::BitGenRef random) {
902  SCOPED_TIME_STAT(&stats_);
903  const LiteralIndex root_literal_index = conflict->front().NegatedIndex();
904  is_marked_.ClearAndResize(LiteralIndex(implications_.size()));
905  is_marked_.Set(root_literal_index);
906 
907  int new_size = 0;
908  auto& direct_implications = implications_[root_literal_index];
909 
910  // The randomization allow to find more redundant implication since to find
911  // a => b and remove b, a must be before b in direct_implications. Note that
912  // a std::reverse() could work too. But randomization seems to work better.
913  // Probably because it has other impact on the search tree.
914  std::shuffle(direct_implications.begin(), direct_implications.end(), random);
915  dfs_stack_.clear();
916  for (const Literal l : direct_implications) {
917  if (is_marked_[l.Index()]) {
918  // The literal is already marked! so it must be implied by one of the
919  // previous literal in the direct_implications list. We can safely remove
920  // it.
921  continue;
922  }
923  direct_implications[new_size++] = l;
924  dfs_stack_.push_back(l);
925  while (!dfs_stack_.empty()) {
926  const LiteralIndex index = dfs_stack_.back().Index();
927  dfs_stack_.pop_back();
928  if (!is_marked_[index]) {
929  is_marked_.Set(index);
930  for (Literal implied : implications_[index]) {
931  if (!is_marked_[implied.Index()]) dfs_stack_.push_back(implied);
932  }
933  }
934  }
935  }
936  if (new_size < direct_implications.size()) {
937  num_redundant_implications_ += direct_implications.size() - new_size;
938  direct_implications.resize(new_size);
939  }
940  RemoveRedundantLiterals(conflict);
941 }
942 
943 void BinaryImplicationGraph::RemoveRedundantLiterals(
944  std::vector<Literal>* conflict) {
945  SCOPED_TIME_STAT(&stats_);
946  int new_index = 1;
947  for (int i = 1; i < conflict->size(); ++i) {
948  if (!is_marked_[(*conflict)[i].NegatedIndex()]) {
949  (*conflict)[new_index] = (*conflict)[i];
950  ++new_index;
951  }
952  }
953  if (new_index < conflict->size()) {
954  ++num_minimization_;
955  num_literals_removed_ += conflict->size() - new_index;
956  conflict->resize(new_index);
957  }
958 }
959 
960 // TODO(user): Also consider at most one?
962  const Trail& trail, std::vector<Literal>* conflict) {
963  SCOPED_TIME_STAT(&stats_);
964  is_marked_.ClearAndResize(LiteralIndex(implications_.size()));
965  is_simplified_.ClearAndResize(LiteralIndex(implications_.size()));
966  for (Literal lit : *conflict) {
967  is_marked_.Set(lit.Index());
968  }
969 
970  // Identify and remove the redundant literals from the given conflict.
971  // 1/ If a -> b then a can be removed from the conflict clause.
972  // This is because not b -> not a.
973  // 2/ a -> b can only happen if level(a) <= level(b).
974  // 3/ Because of 2/, cycles can appear only at the same level.
975  // The vector is_simplified_ is used to avoid removing all elements of a
976  // cycle. Note that this is not optimal in the sense that we may not remove
977  // a literal that can be removed.
978  //
979  // Note that there is no need to explore the unique literal of the highest
980  // decision level since it can't be removed. Because this is a conflict, such
981  // literal is always at position 0, so we start directly at 1.
982  int index = 1;
983  for (int i = 1; i < conflict->size(); ++i) {
984  const Literal lit = (*conflict)[i];
985  const int lit_level = trail.Info(lit.Variable()).level;
986  bool keep_literal = true;
987  for (Literal implied : implications_[lit.Index()]) {
988  if (is_marked_[implied.Index()]) {
989  DCHECK_LE(lit_level, trail.Info(implied.Variable()).level);
990  if (lit_level == trail.Info(implied.Variable()).level &&
991  is_simplified_[implied.Index()]) {
992  continue;
993  }
994  keep_literal = false;
995  break;
996  }
997  }
998  if (keep_literal) {
999  (*conflict)[index] = lit;
1000  ++index;
1001  } else {
1002  is_simplified_.Set(lit.Index());
1003  }
1004  }
1005  if (index < conflict->size()) {
1006  ++num_minimization_;
1007  num_literals_removed_ += conflict->size() - index;
1008  conflict->erase(conflict->begin() + index, conflict->end());
1009  }
1010 }
1011 
1013  SCOPED_TIME_STAT(&stats_);
1014  CHECK_EQ(trail_->CurrentDecisionLevel(), 0);
1015  if (IsEmpty()) return;
1016 
1017  // Nothing to do if nothing changed since last call.
1018  const int new_num_fixed = trail_->Index();
1019  DCHECK_EQ(propagation_trail_index_, new_num_fixed);
1020  if (num_processed_fixed_variables_ == new_num_fixed) return;
1021 
1022  const VariablesAssignment& assignment = trail_->Assignment();
1023  is_marked_.ClearAndResize(LiteralIndex(implications_.size()));
1024  for (; num_processed_fixed_variables_ < new_num_fixed;
1025  ++num_processed_fixed_variables_) {
1026  const Literal true_literal = (*trail_)[num_processed_fixed_variables_];
1027  if (DEBUG_MODE) {
1028  // The code assumes that everything is already propagated.
1029  // Otherwise we will remove implications that didn't propagate yet!
1030  for (const Literal lit : implications_[true_literal.Index()]) {
1031  CHECK(trail_->Assignment().LiteralIsTrue(lit));
1032  }
1033  }
1034 
1035  // If b is true and a -> b then because not b -> not a, all the
1036  // implications list that contains b will be marked by this process.
1037  // And the ones that contains not(b) should correspond to a false literal!
1038  //
1039  // TODO(user): This might not be true if we remove implication by
1040  // transitive reduction and the process was aborted due to the computation
1041  // limit. I think it will be good to maintain that invariant though,
1042  // otherwise fixed literals might never be removed from these lists...
1043  for (const Literal lit : implications_[true_literal.NegatedIndex()]) {
1044  is_marked_.Set(lit.NegatedIndex());
1045  }
1046  gtl::STLClearObject(&(implications_[true_literal.Index()]));
1047  gtl::STLClearObject(&(implications_[true_literal.NegatedIndex()]));
1048 
1049  if (true_literal.Index() < at_most_ones_.size()) {
1050  gtl::STLClearObject(&(at_most_ones_[true_literal.Index()]));
1051  }
1052  if (true_literal.NegatedIndex() < at_most_ones_.size()) {
1053  gtl::STLClearObject(&(at_most_ones_[true_literal.NegatedIndex()]));
1054  }
1055  }
1056  for (const LiteralIndex i : is_marked_.PositionsSetAtLeastOnce()) {
1057  RemoveIf(&implications_[i], [&assignment](const Literal& lit) {
1058  return assignment.LiteralIsTrue(lit);
1059  });
1060  }
1061 
1062  // TODO(user): This might be a bit slow. Do not call all the time if needed,
1063  // this shouldn't change the correctness of the code.
1064  at_most_ones_.clear();
1065  CleanUpAndAddAtMostOnes(/*base_index=*/0);
1066 }
1067 
1068 class SccGraph {
1069  public:
1070  using Implication =
1072  using AtMostOne =
1074  using SccFinder =
1076  std::vector<std::vector<int32_t>>>;
1077 
1078  explicit SccGraph(SccFinder* finder, Implication* graph,
1079  AtMostOne* at_most_ones,
1080  std::vector<Literal>* at_most_one_buffer)
1081  : finder_(*finder),
1082  implications_(*graph),
1083  at_most_ones_(*at_most_ones),
1084  at_most_one_buffer_(*at_most_one_buffer) {}
1085 
1086  const std::vector<int32_t>& operator[](int32_t node) const {
1087  tmp_.clear();
1088  for (const Literal l : implications_[LiteralIndex(node)]) {
1089  tmp_.push_back(l.Index().value());
1090  if (finder_.NodeIsInCurrentDfsPath(l.NegatedIndex().value())) {
1091  to_fix_.push_back(l);
1092  }
1093  }
1094  if (node < at_most_ones_.size()) {
1095  for (const int start : at_most_ones_[LiteralIndex(node)]) {
1096  if (start >= at_most_one_already_explored_.size()) {
1097  at_most_one_already_explored_.resize(start + 1, false);
1098  previous_node_to_explore_at_most_one_.resize(start + 1);
1099  }
1100 
1101  // In the presence of at_most_ones_ constraints, expanding them
1102  // implicitly to implications in the SCC computation can result in a
1103  // quadratic complexity rather than a linear one in term of the input
1104  // data structure size. So this test here is critical on problem with
1105  // large at_most ones like the "ivu06-big.mps.gz" where without it, the
1106  // full FindStronglyConnectedComponents() take more than on hour instead
1107  // of less than a second!
1108  if (at_most_one_already_explored_[start]) {
1109  // We never expand a node twice.
1110  const int first_node = previous_node_to_explore_at_most_one_[start];
1111  CHECK_NE(node, first_node);
1112 
1113  if (finder_.NodeIsInCurrentDfsPath(first_node)) {
1114  // If the first node is not settled, then we do explore the
1115  // at_most_one constraint again. In "Mixed-Integer-Programming:
1116  // Analyzing 12 years of progress", Tobias Achterberg and Roland
1117  // Wunderling explains that an at most one need to be looped over at
1118  // most twice. I am not sure exactly how that works, so for now we
1119  // are not fully linear, but on actual instances, we only rarely
1120  // run into this case.
1121  //
1122  // Note that we change the previous node to explore at most one
1123  // since the current node will be settled before the old ones.
1124  //
1125  // TODO(user): avoid looping more than twice on the same at most one
1126  // constraints? Note that the second time we loop we have x => y =>
1127  // not(x), so we can already detect that x must be false which we
1128  // detect below.
1129  previous_node_to_explore_at_most_one_[start] = node;
1130  } else {
1131  // The first node is already settled and so are all its child. Only
1132  // not(first_node) might still need exploring.
1133  tmp_.push_back(
1134  Literal(LiteralIndex(first_node)).NegatedIndex().value());
1135  continue;
1136  }
1137  } else {
1138  at_most_one_already_explored_[start] = true;
1139  previous_node_to_explore_at_most_one_[start] = node;
1140  }
1141 
1142  for (int i = start;; ++i) {
1143  const Literal l = at_most_one_buffer_[i];
1144  if (l.Index() == kNoLiteralIndex) break;
1145  if (l.Index() == node) continue;
1146  tmp_.push_back(l.NegatedIndex().value());
1147  if (finder_.NodeIsInCurrentDfsPath(l.Index().value())) {
1148  to_fix_.push_back(l.Negated());
1149  }
1150  }
1151  }
1152  }
1153  work_done_ += tmp_.size();
1154  return tmp_;
1155  }
1156 
1157  // All these literals where detected to be true during the SCC computation.
1158  mutable std::vector<Literal> to_fix_;
1159 
1160  // For the deterministic time.
1161  mutable int64_t work_done_ = 0;
1162 
1163  private:
1164  const SccFinder& finder_;
1165  const Implication& implications_;
1166  const AtMostOne& at_most_ones_;
1167  const std::vector<Literal>& at_most_one_buffer_;
1168 
1169  mutable std::vector<int32_t> tmp_;
1170 
1171  // Used to get a non-quadratic complexity in the presence of at most ones.
1172  mutable std::vector<bool> at_most_one_already_explored_;
1173  mutable std::vector<int> previous_node_to_explore_at_most_one_;
1174 };
1175 
1177  // This was already called, and no new constraint where added. Note that new
1178  // fixed variable cannote create new equivalence, only new binary clauses do.
1179  if (is_dag_) return true;
1181  wall_timer.Start();
1182  log_info |= VLOG_IS_ON(1);
1183 
1184  // Lets remove all fixed variables first.
1185  if (!Propagate(trail_)) return false;
1187  const VariablesAssignment& assignment = trail_->Assignment();
1188 
1189  // TODO(user): We could just do it directly though.
1190  int num_fixed_during_scc = 0;
1191  const int32_t size(implications_.size());
1192  std::vector<std::vector<int32_t>> scc;
1193  double dtime = 0.0;
1194  {
1195  SccGraph::SccFinder finder;
1196  SccGraph graph(&finder, &implications_, &at_most_ones_,
1197  &at_most_one_buffer_);
1198  finder.FindStronglyConnectedComponents(size, graph, &scc);
1199  dtime += 4e-8 * graph.work_done_;
1200 
1201  for (const Literal l : graph.to_fix_) {
1202  if (assignment.LiteralIsFalse(l)) return false;
1203  if (assignment.LiteralIsTrue(l)) continue;
1204  ++num_fixed_during_scc;
1205  if (!FixLiteral(l)) return false;
1206  }
1207  }
1208 
1209  // The old values will still be valid.
1210  representative_of_.resize(size, kNoLiteralIndex);
1211  is_redundant_.resize(size);
1212 
1213  int num_equivalences = 0;
1214  reverse_topological_order_.clear();
1215  for (std::vector<int32_t>& component : scc) {
1216  // If one is fixed then all must be fixed. Note that the reason why the
1217  // propagation didn't already do that and we don't always get fixed
1218  // component of size 1 is because of the potential newly fixed literals
1219  // above.
1220  //
1221  // In any case, all fixed literals are marked as redundant.
1222  {
1223  bool all_fixed = false;
1224  bool all_true = false;
1225  for (const int32_t i : component) {
1226  const Literal l = Literal(LiteralIndex(i));
1227  if (trail_->Assignment().LiteralIsAssigned(l)) {
1228  all_fixed = true;
1229  all_true = trail_->Assignment().LiteralIsTrue(l);
1230  break;
1231  }
1232  }
1233  if (all_fixed) {
1234  for (const int32_t i : component) {
1235  const Literal l = Literal(LiteralIndex(i));
1236  if (!is_redundant_[l.Index()]) {
1237  ++num_redundant_literals_;
1238  is_redundant_.Set(l.Index());
1239  }
1240  const Literal to_fix = all_true ? l : l.Negated();
1241  if (assignment.LiteralIsFalse(to_fix)) return false;
1242  if (assignment.LiteralIsTrue(to_fix)) continue;
1243  ++num_fixed_during_scc;
1244  if (!FixLiteral(l)) return false;
1245  }
1246 
1247  // Next component.
1248  continue;
1249  }
1250  }
1251 
1252  // We ignore variable that appear in no constraints.
1253  if (component.size() == 1 && is_removed_[LiteralIndex(component[0])]) {
1254  continue;
1255  }
1256 
1257  // We always take the smallest literal index (which also corresponds to the
1258  // smallest BooleanVariable index) as a representative. This make sure that
1259  // the representative of a literal l and the one of not(l) will be the
1260  // negation of each other. There is also reason to think that it is
1261  // heuristically better to use a BooleanVariable that was created first.
1262  std::sort(component.begin(), component.end());
1263  const LiteralIndex representative(component[0]);
1264  reverse_topological_order_.push_back(representative);
1265 
1266  if (component.size() == 1) {
1267  // Note that because we process list in reverse topological order, this
1268  // is only needed if there is any equivalence before this point.
1269  if (num_equivalences > 0) {
1270  auto& representative_list = implications_[representative];
1271  for (Literal& ref : representative_list) {
1272  const LiteralIndex rep = representative_of_[ref.Index()];
1273  if (rep == representative) continue;
1274  if (rep == kNoLiteralIndex) continue;
1275  ref = Literal(rep);
1276  }
1277  gtl::STLSortAndRemoveDuplicates(&representative_list);
1278  }
1279  continue;
1280  }
1281 
1282  // Sets the representative.
1283  for (int i = 1; i < component.size(); ++i) {
1284  const Literal literal = Literal(LiteralIndex(component[i]));
1285  if (!is_redundant_[literal.Index()]) {
1286  ++num_redundant_literals_;
1287  is_redundant_.Set(literal.Index());
1288  }
1289  representative_of_[literal.Index()] = representative;
1290 
1291  // Detect if x <=> not(x) which means unsat. Note that we relly on the
1292  // fact that when sorted, they will both be consecutive in the list.
1293  if (Literal(LiteralIndex(component[i - 1])).Negated() == literal) {
1294  LOG_IF(INFO, log_info) << "Trivially UNSAT in DetectEquivalences()";
1295  return false;
1296  }
1297  }
1298 
1299  // Merge all the lists in implications_[representative].
1300  // Note that we do not want representative in its own list.
1301  auto& representative_list = implications_[representative];
1302  int new_size = 0;
1303  for (const Literal l : representative_list) {
1304  const Literal rep = RepresentativeOf(l);
1305  if (rep.Index() == representative) continue;
1306  representative_list[new_size++] = rep;
1307  }
1308  representative_list.resize(new_size);
1309  for (int i = 1; i < component.size(); ++i) {
1310  const Literal literal = Literal(LiteralIndex(component[i]));
1311  auto& ref = implications_[literal.Index()];
1312  for (const Literal l : ref) {
1313  const Literal rep = RepresentativeOf(l);
1314  if (rep.Index() != representative) representative_list.push_back(rep);
1315  }
1316 
1317  // Add representative <=> literal.
1318  //
1319  // Remark: this relation do not need to be added to a DRAT proof since
1320  // the redundant variables should never be used again for a pure SAT
1321  // problem.
1322  representative_list.push_back(literal);
1323  ref.clear();
1324  ref.push_back(Literal(representative));
1325  }
1326  gtl::STLSortAndRemoveDuplicates(&representative_list);
1327  num_equivalences += component.size() - 1;
1328  }
1329 
1330  is_dag_ = true;
1331  if (num_equivalences != 0) {
1332  // Remap all at most ones. Remove fixed variables, process duplicates. Note
1333  // that this might result in more implications when we expand small at most
1334  // one.
1335  at_most_ones_.clear();
1336  CleanUpAndAddAtMostOnes(/*base_index=*/0);
1337 
1338  num_implications_ = 0;
1339  for (LiteralIndex i(0); i < size; ++i) {
1340  num_implications_ += implications_[i].size();
1341  }
1342  dtime += 2e-8 * num_implications_;
1343  }
1344 
1345  time_limit_->AdvanceDeterministicTime(dtime);
1346  LOG_IF(INFO, log_info) << "SCC. " << num_equivalences
1347  << " redundant equivalent literals. "
1348  << num_fixed_during_scc << " fixed. "
1349  << num_implications_ << " implications left. "
1350  << implications_.size() << " literals."
1351  << " size of at_most_one buffer = "
1352  << at_most_one_buffer_.size() << "."
1353  << " dtime: " << dtime
1354  << " wtime: " << wall_timer.Get();
1355  return true;
1356 }
1357 
1358 // Note that as a side effect this also do a full "failed literal probing"
1359 // using the binary implication graph only.
1360 //
1361 // TODO(user): Track which literal have new implications, and only process
1362 // the antecedants of these.
1364  CHECK_EQ(trail_->CurrentDecisionLevel(), 0);
1365  if (!DetectEquivalences()) return false;
1366 
1367  // TODO(user): the situation with fixed variable is not really "clean".
1368  // Simplify the code so we are sure we don't run into issue or have to deal
1369  // with any of that here.
1370  if (!Propagate(trail_)) return false;
1372 
1373  log_info |= VLOG_IS_ON(1);
1375  wall_timer.Start();
1376 
1377  int64_t num_fixed = 0;
1378  int64_t num_new_redundant_implications = 0;
1379  bool aborted = false;
1380  work_done_in_mark_descendants_ = 0;
1381  int marked_index = 0;
1382 
1383  // For each node we do a graph traversal and only keep the literals
1384  // at maximum distance 1. This only works because we have a DAG when ignoring
1385  // the "redundant" literal marked by DetectEquivalences(). Note that we also
1386  // need no duplicates in the implications list for correctness which is also
1387  // guaranteed by DetectEquivalences().
1388  //
1389  // TODO(user): We should be able to reuse some propagation like it is done for
1390  // tree-look. Once a node is processed, we just need to process a node that
1391  // implies it. Test if we can make this faster. Alternatively, only clear
1392  // a part of is_marked_ (after the first child of root in reverse topo order).
1393  //
1394  // TODO(user): Can we exploit the fact that the implication graph is a
1395  // skew-symmetric graph (isomorphic to its transposed) so that we do less
1396  // work? Also it would be nice to keep the property that even if we abort
1397  // during the algorithm, if a => b, then not(b) => not(a) is also present in
1398  // the other direct implication list.
1399  const LiteralIndex size(implications_.size());
1400  LiteralIndex previous = kNoLiteralIndex;
1401  for (const LiteralIndex root : reverse_topological_order_) {
1402  // In most situation reverse_topological_order_ contains no redundant, fixed
1403  // or removed variables. But the reverse_topological_order_ is only
1404  // recomputed when new binary are added to the graph, not when new variable
1405  // are fixed.
1406  if (is_redundant_[root]) continue;
1407  if (trail_->Assignment().LiteralIsAssigned(Literal(root))) continue;
1408 
1409  auto& direct_implications = implications_[root];
1410  if (direct_implications.empty()) continue;
1411 
1412  // This is a "poor" version of the tree look stuff, but it does show good
1413  // improvement. If we just processed one of the child of root, we don't
1414  // need to re-explore it.
1415  //
1416  // TODO(user): Another optim we can do is that we never need to expand
1417  // any node with a reverse topo order smaller or equal to the min of the
1418  // ones in this list.
1419  bool clear_previous_reachability = true;
1420  for (const Literal direct_child : direct_implications) {
1421  if (direct_child.Index() == previous) {
1422  clear_previous_reachability = false;
1423  is_marked_.Clear(previous);
1424  break;
1425  }
1426  }
1427  if (clear_previous_reachability) {
1428  is_marked_.ClearAndResize(size);
1429  marked_index = 0;
1430  }
1431  previous = root;
1432 
1433  for (const Literal direct_child : direct_implications) {
1434  if (is_redundant_[direct_child.Index()]) continue;
1435  if (is_marked_[direct_child.Index()]) continue;
1436 
1437  // This is a corner case where because of equivalent literal, root
1438  // appear in implications_[root], we will remove it below.
1439  if (direct_child.Index() == root) continue;
1440 
1441  // When this happens, then root must be false, we handle this just after
1442  // the loop.
1443  if (direct_child.NegatedIndex() == root) {
1444  is_marked_.Set(direct_child.Index());
1445  break;
1446  }
1447 
1448  MarkDescendants(direct_child);
1449 
1450  // We have a DAG, so direct_child could only be marked first.
1451  is_marked_.Clear(direct_child.Index());
1452  }
1453  CHECK(!is_marked_[root])
1454  << "DetectEquivalences() should have removed cycles!";
1455  is_marked_.Set(root);
1456 
1457  // Failed literal probing. If both x and not(x) are marked then root must be
1458  // false. Note that because we process "roots" in reverse topological order,
1459  // we will fix the LCA of x and not(x) first.
1460  const auto& marked_positions = is_marked_.PositionsSetAtLeastOnce();
1461  for (; marked_index < marked_positions.size(); ++marked_index) {
1462  const LiteralIndex i = marked_positions[marked_index];
1463  if (is_marked_[Literal(i).NegatedIndex()]) {
1464  // We tested that at the beginning.
1465  CHECK(!trail_->Assignment().LiteralIsAssigned(Literal(root)));
1466 
1467  // We propagate right away the binary implications so that we do not
1468  // need to consider all antecedants of root in the transitive
1469  // reduction.
1470  ++num_fixed;
1471  if (!FixLiteral(Literal(root).Negated())) return false;
1472  break;
1473  }
1474  }
1475 
1476  // Note that direct_implications will be cleared by
1477  // RemoveFixedVariables() that will need to inspect it to completely
1478  // remove Literal(root) from all lists.
1479  if (trail_->Assignment().LiteralIsAssigned(Literal(root))) continue;
1480 
1481  // Only keep the non-marked literal (and the redundant one which are never
1482  // marked). We mark root to remove it in the corner case where it was
1483  // there.
1484  int new_size = 0;
1485  for (const Literal l : direct_implications) {
1486  if (!is_marked_[l.Index()]) {
1487  direct_implications[new_size++] = l;
1488  } else {
1489  CHECK(!is_redundant_[l.Index()]);
1490  }
1491  }
1492  const int diff = direct_implications.size() - new_size;
1493  direct_implications.resize(new_size);
1494  direct_implications.shrink_to_fit();
1495  num_new_redundant_implications += diff;
1496  num_implications_ -= diff;
1497 
1498  // Abort if the computation involved is too big.
1499  if (work_done_in_mark_descendants_ > 1e8) {
1500  aborted = true;
1501  break;
1502  }
1503  }
1504 
1505  is_marked_.ClearAndResize(size);
1506 
1507  const double dtime = 1e-8 * work_done_in_mark_descendants_;
1508  time_limit_->AdvanceDeterministicTime(dtime);
1509  num_redundant_implications_ += num_new_redundant_implications;
1510  LOG_IF(INFO, log_info) << "Transitive reduction removed "
1511  << num_new_redundant_implications << " literals. "
1512  << num_fixed << " fixed. " << num_implications_
1513  << " implications left. " << implications_.size()
1514  << " literals."
1515  << " dtime: " << dtime
1516  << " wtime: " << wall_timer.Get()
1517  << (aborted ? " Aborted." : "");
1518  return true;
1519 }
1520 
1521 namespace {
1522 
1523 int ElementInIntersectionOrMinusOne(const std::vector<int>& a,
1524  const std::vector<int>& b) {
1525  DCHECK(std::is_sorted(a.begin(), a.end()));
1526  DCHECK(std::is_sorted(b.begin(), b.end()));
1527  int i = 0;
1528  int j = 0;
1529  for (; i < a.size() && j < b.size();) {
1530  if (a[i] == b[j]) return a[i];
1531  if (a[i] < b[j]) {
1532  ++i;
1533  } else {
1534  ++j;
1535  }
1536  }
1537  return -1;
1538 }
1539 
1540 } // namespace
1541 
1543  std::vector<std::vector<Literal>>* at_most_ones,
1544  int64_t max_num_explored_nodes) {
1545  // The code below assumes a DAG.
1546  if (!DetectEquivalences()) return false;
1547  work_done_in_mark_descendants_ = 0;
1548 
1549  int num_extended = 0;
1550  int num_removed = 0;
1551  int num_added = 0;
1552 
1553  // Data to detect inclusion of base amo into extend amo.
1554  std::vector<int> detector_clique_index;
1555  CompactVectorVector<int> storage;
1556  InclusionDetector detector(storage);
1557  detector.SetWorkLimit(1e9);
1558 
1559  std::vector<int> dense_index_to_index;
1560  absl::StrongVector<LiteralIndex, std::vector<int>> max_cliques_containing(
1561  implications_.size());
1562 
1563  // We starts by processing larger constraints first.
1564  // But we want the output order to be stable.
1565  std::vector<std::pair<int, int>> index_size_vector;
1566  index_size_vector.reserve(at_most_ones->size());
1567  for (int index = 0; index < at_most_ones->size(); ++index) {
1568  std::vector<Literal>& clique = (*at_most_ones)[index];
1569  if (clique.size() <= 1) continue;
1570 
1571  // Note(user): Because we always use literal with the smallest variable
1572  // indices as representative, this make sure that if possible, we express
1573  // the clique in term of user provided variable (that are always created
1574  // first).
1575  //
1576  // Remap the clique to only use representative.
1577  for (Literal& ref : clique) {
1578  DCHECK_LT(ref.Index(), representative_of_.size());
1579  const LiteralIndex rep = representative_of_[ref.Index()];
1580  if (rep == kNoLiteralIndex) continue;
1581  ref = Literal(rep);
1582  }
1583 
1584  // We skip anything that can be presolved further as the code below do
1585  // not handle duplicate well.
1586  //
1587  // TODO(user): Shall we presolve it here?
1588  bool skip = false;
1589  std::sort(clique.begin(), clique.end());
1590  for (int i = 1; i < clique.size(); ++i) {
1591  if (clique[i] == clique[i - 1] || clique[i] == clique[i - i].Negated()) {
1592  skip = true;
1593  break;
1594  }
1595  }
1596  if (skip) continue;
1597 
1598  index_size_vector.push_back({index, clique.size()});
1599  }
1600  std::stable_sort(
1601  index_size_vector.begin(), index_size_vector.end(),
1602  [](const std::pair<int, int> a, const std::pair<int, int>& b) {
1603  return a.second > b.second;
1604  });
1605 
1606  absl::flat_hash_set<int> cannot_be_removed;
1607  std::vector<bool> was_extended(at_most_ones->size(), false);
1608  for (const auto& [index, old_size] : index_size_vector) {
1609  std::vector<Literal>& clique = (*at_most_ones)[index];
1610  if (time_limit_->LimitReached()) break;
1611 
1612  // Special case for clique of size 2, we don't expand them if they
1613  // are included in an already added clique.
1614  if (clique.size() == 2) {
1615  CHECK_NE(clique[0], clique[1]);
1616  const int dense_index = ElementInIntersectionOrMinusOne(
1617  max_cliques_containing[clique[0].Index()],
1618  max_cliques_containing[clique[1].Index()]);
1619  if (dense_index >= 0) {
1620  const int superset_index = dense_index_to_index[dense_index];
1621  if (was_extended[superset_index])
1622  cannot_be_removed.insert(superset_index);
1623  ++num_removed;
1624  clique.clear();
1625  continue;
1626  }
1627  }
1628 
1629  // Save the non-extended version as possible subset.
1630  // TODO(user): Detect on the fly is superset already exist.
1631  detector_clique_index.push_back(index);
1632  detector.AddPotentialSubset(storage.AddLiterals(clique));
1633 
1634  // We only expand the clique as long as we didn't spend too much time.
1635  if (work_done_in_mark_descendants_ < max_num_explored_nodes) {
1636  clique = ExpandAtMostOne(clique, max_num_explored_nodes);
1637  }
1638 
1639  // Save the extended version as possible superset.
1640  detector_clique_index.push_back(index);
1641  detector.AddPotentialSuperset(storage.AddLiterals(clique));
1642 
1643  // Also index clique for size 2 quick lookup.
1644  const int dense_index = dense_index_to_index.size();
1645  dense_index_to_index.push_back(index);
1646  for (const Literal l : clique) {
1647  max_cliques_containing[l.Index()].push_back(dense_index);
1648  }
1649 
1650  if (clique.size() > old_size) {
1651  was_extended[index] = true;
1652  ++num_extended;
1653  }
1654  ++num_added;
1655  }
1656 
1657  // Remove clique (before extension) that are included in an extended one.
1658  absl::flat_hash_set<int> cannot_be_superset;
1659  detector.DetectInclusions([&](int subset, int superset) {
1660  const int subset_index = detector_clique_index[subset];
1661  const int superset_index = detector_clique_index[superset];
1662  if (subset_index == superset_index) return;
1663 
1664  // Abort if one was already deleted.
1665  if ((*at_most_ones)[subset_index].empty()) return;
1666  if ((*at_most_ones)[superset_index].empty()) return;
1667 
1668  // If an extended clique already cover a deleted one, we cannot try to
1669  // remove it by looking at its non-extended version.
1670  if (cannot_be_removed.contains(subset_index)) return;
1671 
1672  ++num_removed;
1673  (*at_most_ones)[subset_index].clear();
1674  if (was_extended[superset_index]) cannot_be_removed.insert(superset_index);
1675  });
1676 
1677  if (num_extended > 0 || num_removed > 0 || num_added > 0) {
1678  VLOG(1) << "Clique Extended: " << num_extended
1679  << " Removed: " << num_removed << " Added: " << num_added
1680  << (work_done_in_mark_descendants_ > max_num_explored_nodes
1681  ? " (Aborted)"
1682  : "");
1683  }
1684  return true;
1685 }
1686 
1687 template <bool use_weight>
1689  const absl::Span<const Literal> at_most_one,
1690  const absl::StrongVector<LiteralIndex, bool>& can_be_included,
1691  const absl::StrongVector<LiteralIndex, double>& expanded_lp_values) {
1692  std::vector<Literal> clique(at_most_one.begin(), at_most_one.end());
1693  std::vector<LiteralIndex> intersection;
1694  double clique_weight = 0.0;
1695  const int64_t old_work = work_done_in_mark_descendants_;
1696  if (use_weight) {
1697  for (const Literal l : clique) {
1698  clique_weight += expanded_lp_values[l.Index()];
1699  }
1700  }
1701  for (int i = 0; i < clique.size(); ++i) {
1702  // Do not spend too much time here.
1703  if (work_done_in_mark_descendants_ - old_work > 1e8) break;
1704 
1705  is_marked_.ClearAndResize(LiteralIndex(implications_.size()));
1706  MarkDescendants(clique[i]);
1707  if (i == 0) {
1708  for (const LiteralIndex index : is_marked_.PositionsSetAtLeastOnce()) {
1709  if (can_be_included[Literal(index).NegatedIndex()]) {
1710  intersection.push_back(index);
1711  }
1712  }
1713  for (const Literal l : clique) is_marked_.Clear(l.NegatedIndex());
1714  }
1715 
1716  int new_size = 0;
1717  double intersection_weight = 0.0;
1718  is_marked_.Clear(clique[i].Index());
1719  is_marked_.Clear(clique[i].NegatedIndex());
1720  for (const LiteralIndex index : intersection) {
1721  if (!is_marked_[index]) continue;
1722  intersection[new_size++] = index;
1723  if (use_weight) {
1724  intersection_weight += expanded_lp_values[index];
1725  }
1726  }
1727  intersection.resize(new_size);
1728  if (intersection.empty()) break;
1729 
1730  // We can't generate a violated cut this way. This is because intersection
1731  // contains all the possible ways to extend the current clique.
1732  if (use_weight && clique_weight + intersection_weight <= 1.0) {
1733  clique.clear();
1734  return clique;
1735  }
1736 
1737  // Expand? The negation of any literal in the intersection is a valid way
1738  // to extend the clique.
1739  if (i + 1 == clique.size()) {
1740  // Heuristic: use literal with largest lp value. We randomize slightly.
1741  int index = -1;
1742  double max_lp = 0.0;
1743  for (int j = 0; j < intersection.size(); ++j) {
1744  // If we don't use weight, we prefer variable that comes first.
1745  const double lp =
1746  use_weight ? 1.0 - expanded_lp_values[intersection[j]] +
1747  absl::Uniform<double>(*random_, 0.0, 1e-4)
1748  : can_be_included.size() - intersection[j].value();
1749  if (index == -1 || lp > max_lp) {
1750  index = j;
1751  max_lp = lp;
1752  }
1753  }
1754  if (index != -1) {
1755  clique.push_back(Literal(intersection[index]).Negated());
1756  std::swap(intersection.back(), intersection[index]);
1757  intersection.pop_back();
1758  if (use_weight) {
1759  clique_weight += expanded_lp_values[clique.back().Index()];
1760  }
1761  }
1762  }
1763  }
1764  return clique;
1765 }
1766 
1767 // Make sure both version are compiled.
1768 template std::vector<Literal> BinaryImplicationGraph::ExpandAtMostOneWithWeight<
1769  true>(const absl::Span<const Literal> at_most_one,
1770  const absl::StrongVector<LiteralIndex, bool>& can_be_included,
1771  const absl::StrongVector<LiteralIndex, double>& expanded_lp_values);
1772 template std::vector<Literal> BinaryImplicationGraph::ExpandAtMostOneWithWeight<
1773  false>(const absl::Span<const Literal> at_most_one,
1774  const absl::StrongVector<LiteralIndex, bool>& can_be_included,
1775  const absl::StrongVector<LiteralIndex, double>& expanded_lp_values);
1776 
1777 const std::vector<std::vector<Literal>>&
1779  const std::vector<Literal>& literals,
1780  const std::vector<double>& lp_values) {
1781  // We only want to generate a cut with literals from the LP, not extra ones.
1782  const int num_literals = implications_.size();
1783  absl::StrongVector<LiteralIndex, bool> can_be_included(num_literals, false);
1784  absl::StrongVector<LiteralIndex, double> expanded_lp_values(num_literals,
1785  0.0);
1786  const int size = literals.size();
1787  for (int i = 0; i < size; ++i) {
1788  const Literal l = literals[i];
1789  can_be_included[l.Index()] = true;
1790  can_be_included[l.NegatedIndex()] = true;
1791 
1792  const double value = lp_values[i];
1793  expanded_lp_values[l.Index()] = value;
1794  expanded_lp_values[l.NegatedIndex()] = 1.0 - value;
1795  }
1796 
1797  // We want highest sum first.
1798  struct Candidate {
1799  Literal a;
1800  Literal b;
1801  double sum;
1802  bool operator<(const Candidate& other) const { return sum > other.sum; }
1803  };
1804  std::vector<Candidate> candidates;
1805 
1806  // First heuristic. Currently we only consider violated at most one of size 2,
1807  // and extend them. Right now, the code is a bit slow to try too many at every
1808  // LP node so it is why we are defensive like this. Note also that because we
1809  // currently still statically add the initial implications, this will only add
1810  // cut based on newly learned binary clause. Or the one that were not added
1811  // to the relaxation in the first place.
1812  for (int i = 0; i < size; ++i) {
1813  Literal current_literal = literals[i];
1814  double current_value = lp_values[i];
1815  if (trail_->Assignment().LiteralIsAssigned(current_literal)) continue;
1816  if (is_redundant_[current_literal.Index()]) continue;
1817 
1818  if (current_value < 0.5) {
1819  current_literal = current_literal.Negated();
1820  current_value = 1.0 - current_value;
1821  }
1822 
1823  // We consider only one candidate for each current_literal.
1824  LiteralIndex best = kNoLiteralIndex;
1825  double best_value = 0.0;
1826  for (const Literal l : implications_[current_literal.Index()]) {
1827  if (!can_be_included[l.Index()]) continue;
1828  const double activity =
1829  current_value + expanded_lp_values[l.NegatedIndex()];
1830  if (activity <= 1.01) continue;
1831  const double v = activity + absl::Uniform<double>(*random_, 0.0, 1e-4);
1832  if (best == kNoLiteralIndex || v > best_value) {
1833  best_value = v;
1834  best = l.NegatedIndex();
1835  }
1836  }
1837  if (best != kNoLiteralIndex) {
1838  const double activity = current_value + expanded_lp_values[best];
1839  candidates.push_back({current_literal, Literal(best), activity});
1840  }
1841  }
1842 
1843  // Do not genate too many cut at once.
1844  const int kMaxNumberOfCutPerCall = 10;
1845  std::sort(candidates.begin(), candidates.end());
1846  if (candidates.size() > kMaxNumberOfCutPerCall) {
1847  candidates.resize(kMaxNumberOfCutPerCall);
1848  }
1849 
1850  // Expand to a maximal at most one each candidates before returning them.
1851  // Note that we only expand using literal from the LP.
1852  tmp_cuts_.clear();
1853  std::vector<Literal> at_most_one;
1854  for (const Candidate& candidate : candidates) {
1855  at_most_one = ExpandAtMostOneWithWeight(
1856  {candidate.a, candidate.b}, can_be_included, expanded_lp_values);
1857  if (!at_most_one.empty()) tmp_cuts_.push_back(at_most_one);
1858  }
1859  return tmp_cuts_;
1860 }
1861 
1862 void BinaryImplicationGraph::MarkDescendants(Literal root) {
1863  bfs_stack_.resize(implications_.size());
1864  auto* const stack = bfs_stack_.data();
1865  auto* const amo_buffer = at_most_one_buffer_.data();
1866  const int amo_size = static_cast<int>(at_most_ones_.size());
1867  auto is_marked = is_marked_.const_view();
1868  auto is_redundant = is_redundant_.const_view();
1869  if (is_redundant[root.Index()]) return;
1870 
1871  int stack_size = 1;
1872  stack[0] = root;
1873  is_marked_.Set(root.Index());
1874  for (int j = 0; j < stack_size; ++j) {
1875  const Literal current = stack[j];
1876  for (const Literal l : implications_[current.Index()]) {
1877  if (!is_marked[l.Index()] && !is_redundant[l.Index()]) {
1878  is_marked_.SetUnsafe(l.Index());
1879  stack[stack_size++] = l;
1880  }
1881  }
1882 
1883  if (current.Index() >= amo_size) continue;
1884  for (const int start : at_most_ones_[current.Index()]) {
1885  for (int i = start;; ++i) {
1886  const Literal l = amo_buffer[i];
1887  if (l.Index() == kNoLiteralIndex) break;
1888  if (l == current) continue;
1889  if (!is_marked[l.NegatedIndex()] && !is_redundant[l.NegatedIndex()]) {
1890  is_marked_.SetUnsafe(l.NegatedIndex());
1891  stack[stack_size++] = l.Negated();
1892  }
1893  }
1894  }
1895  }
1896  work_done_in_mark_descendants_ += stack_size;
1897 }
1898 
1899 std::vector<Literal> BinaryImplicationGraph::ExpandAtMostOne(
1900  const absl::Span<const Literal> at_most_one,
1901  int64_t max_num_explored_nodes) {
1902  std::vector<Literal> clique(at_most_one.begin(), at_most_one.end());
1903 
1904  // Optim.
1905  for (int i = 0; i < clique.size(); ++i) {
1906  if (implications_[clique[i].Index()].empty() ||
1907  is_redundant_[clique[i].Index()]) {
1908  return clique;
1909  }
1910  }
1911 
1912  // TODO(user): Improve algorithm. If we do a dfs, we can know if a literal
1913  // has no descendant in the current intersection. We can keep such literal
1914  // marked.
1915  std::vector<LiteralIndex> intersection;
1916  for (int i = 0; i < clique.size(); ++i) {
1917  if (work_done_in_mark_descendants_ > max_num_explored_nodes) break;
1918  is_marked_.ClearAndResize(LiteralIndex(implications_.size()));
1919  MarkDescendants(clique[i]);
1920 
1921  if (i == 0) {
1922  intersection = is_marked_.PositionsSetAtLeastOnce();
1923  for (const Literal l : clique) is_marked_.Clear(l.NegatedIndex());
1924  }
1925 
1926  int new_size = 0;
1927  is_marked_.Clear(clique[i].NegatedIndex()); // TODO(user): explain.
1928  for (const LiteralIndex index : intersection) {
1929  if (is_marked_[index]) intersection[new_size++] = index;
1930  }
1931  intersection.resize(new_size);
1932  if (intersection.empty()) break;
1933 
1934  // TODO(user): If the intersection is small compared to the members of the
1935  // clique left to explore, we could look at the descendants of the negated
1936  // intersection instead.
1937 
1938  // Expand?
1939  if (i + 1 == clique.size()) {
1940  clique.push_back(Literal(intersection.back()).Negated());
1941  intersection.pop_back();
1942  }
1943  }
1944  return clique;
1945 }
1946 
1947 // TODO(user): lazy cleanup the lists on is_removed_?
1948 // TODO(user): Mark fixed variable as is_removed_ for faster iteration?
1950  Literal literal) {
1951  CHECK(!is_removed_[literal.Index()]);
1952 
1953  // Clear old state.
1954  for (const Literal l : direct_implications_) {
1955  in_direct_implications_[l.Index()] = false;
1956  }
1957  direct_implications_.clear();
1958 
1959  // Fill new state.
1960  const VariablesAssignment& assignment = trail_->Assignment();
1961  CHECK(!assignment.LiteralIsAssigned(literal));
1962  for (const Literal l : implications_[literal.Index()]) {
1963  if (l == literal) continue;
1964  if (assignment.LiteralIsAssigned(l)) continue;
1965  if (!is_removed_[l.Index()] && !in_direct_implications_[l.Index()]) {
1966  in_direct_implications_[l.Index()] = true;
1967  direct_implications_.push_back(l);
1968  }
1969  }
1970  if (literal.Index() < at_most_ones_.size()) {
1971  if (is_redundant_[literal.Index()]) {
1972  CHECK(at_most_ones_[literal.Index()].empty());
1973  }
1974  for (const int start : at_most_ones_[literal.Index()]) {
1975  for (int i = start;; ++i) {
1976  const Literal l = at_most_one_buffer_[i];
1977  if (l.Index() == kNoLiteralIndex) break;
1978  if (l == literal) continue;
1979  if (assignment.LiteralIsAssigned(l)) continue;
1980  if (!is_removed_[l.Index()] &&
1981  !in_direct_implications_[l.NegatedIndex()]) {
1982  in_direct_implications_[l.NegatedIndex()] = true;
1983  direct_implications_.push_back(l.Negated());
1984  }
1985  }
1986  }
1987  }
1988  estimated_sizes_[literal.Index()] = direct_implications_.size();
1989  return direct_implications_;
1990 }
1991 
1993  bool* is_unsat) {
1994  const int saved_index = propagation_trail_index_;
1995  CHECK_EQ(propagation_trail_index_, trail_->Index()); // Propagation done.
1996 
1997  const VariablesAssignment& assignment = trail_->Assignment();
1998  if (assignment.VariableIsAssigned(var)) return false;
1999 
2000  const Literal literal(var, true);
2001  direct_implications_of_negated_literal_ =
2002  DirectImplications(literal.Negated());
2003  DirectImplications(literal); // Fill in_direct_implications_.
2004  for (const Literal l : direct_implications_of_negated_literal_) {
2005  if (in_direct_implications_[l.Index()]) {
2006  // not(l) => literal => l.
2007  if (!FixLiteral(l)) {
2008  *is_unsat = true;
2009  return false;
2010  }
2011  }
2012  }
2013 
2014  return propagation_trail_index_ > saved_index;
2015 }
2016 
2018  BooleanVariable var) {
2019  const Literal literal(var, true);
2020  int64_t result = 0;
2021  direct_implications_of_negated_literal_ =
2022  DirectImplications(literal.Negated());
2023  const int64_t s1 = DirectImplications(literal).size();
2024  for (const Literal l : direct_implications_of_negated_literal_) {
2025  result += s1;
2026 
2027  // We should have dealt with that in FindFailedLiteralAroundVar().
2028  CHECK(!in_direct_implications_[l.Index()]);
2029 
2030  // l => literal => l: equivalent variable!
2031  if (in_direct_implications_[l.NegatedIndex()]) result--;
2032  }
2033  return result;
2034 }
2035 
2036 // For all possible a => var => b, add a => b.
2038  BooleanVariable var, std::deque<std::vector<Literal>>* postsolve_clauses) {
2039  const Literal literal(var, true);
2040  direct_implications_of_negated_literal_ =
2041  DirectImplications(literal.Negated());
2042  for (const Literal b : DirectImplications(literal)) {
2043  estimated_sizes_[b.NegatedIndex()]--;
2044  for (const Literal a_negated : direct_implications_of_negated_literal_) {
2045  if (a_negated.Negated() == b) continue;
2046  AddImplication(a_negated.Negated(), b);
2047  }
2048  }
2049  for (const Literal a_negated : direct_implications_of_negated_literal_) {
2050  estimated_sizes_[a_negated.NegatedIndex()]--;
2051  }
2052 
2053  // Notify the deletion to the proof checker and the postsolve.
2054  // Note that we want var first in these clauses for the postsolve.
2055  for (const Literal b : direct_implications_) {
2056  if (drat_proof_handler_ != nullptr) {
2057  drat_proof_handler_->DeleteClause({Literal(var, false), b});
2058  }
2059  postsolve_clauses->push_back({Literal(var, false), b});
2060  }
2061  for (const Literal a_negated : direct_implications_of_negated_literal_) {
2062  if (drat_proof_handler_ != nullptr) {
2063  drat_proof_handler_->DeleteClause({Literal(var, true), a_negated});
2064  }
2065  postsolve_clauses->push_back({Literal(var, true), a_negated});
2066  }
2067 
2068  // We need to remove any occurrence of var in our implication lists, this will
2069  // be delayed to the CleanupAllRemovedVariables() call.
2070  for (LiteralIndex index : {literal.Index(), literal.NegatedIndex()}) {
2071  is_removed_[index] = true;
2072  if (!is_redundant_[index]) {
2073  ++num_redundant_literals_;
2074  is_redundant_.Set(index);
2075  }
2076  implications_[index].clear();
2077  }
2078 }
2079 
2081  for (auto& implication : implications_) {
2082  int new_size = 0;
2083  for (const Literal l : implication) {
2084  if (!is_removed_[l.Index()]) implication[new_size++] = l;
2085  }
2086  implication.resize(new_size);
2087  }
2088 
2089  // Clean-up at most ones.
2090  at_most_ones_.clear();
2091  CleanUpAndAddAtMostOnes(/*base_index=*/0);
2092 }
2093 
2094 // ----- SatClause -----
2095 
2096 // static
2097 SatClause* SatClause::Create(absl::Span<const Literal> literals) {
2098  CHECK_GE(literals.size(), 2);
2099  SatClause* clause = reinterpret_cast<SatClause*>(
2100  ::operator new(sizeof(SatClause) + literals.size() * sizeof(Literal)));
2101  clause->size_ = literals.size();
2102  for (int i = 0; i < literals.size(); ++i) {
2103  clause->literals_[i] = literals[i];
2104  }
2105  return clause;
2106 }
2107 
2108 // Note that for an attached clause, removing fixed literal is okay because if
2109 // any of the watched literal is assigned, then the clause is necessarily true.
2111  const VariablesAssignment& assignment) {
2112  DCHECK(IsAttached());
2113  if (assignment.VariableIsAssigned(literals_[0].Variable()) ||
2114  assignment.VariableIsAssigned(literals_[1].Variable())) {
2115  DCHECK(IsSatisfied(assignment));
2116  return true;
2117  }
2118  int j = 2;
2119  while (j < size_ && !assignment.VariableIsAssigned(literals_[j].Variable())) {
2120  ++j;
2121  }
2122  for (int i = j; i < size_; ++i) {
2123  if (assignment.VariableIsAssigned(literals_[i].Variable())) {
2124  if (assignment.LiteralIsTrue(literals_[i])) return true;
2125  } else {
2126  std::swap(literals_[j], literals_[i]);
2127  ++j;
2128  }
2129  }
2130  size_ = j;
2131  return false;
2132 }
2133 
2134 bool SatClause::IsSatisfied(const VariablesAssignment& assignment) const {
2135  for (const Literal literal : *this) {
2136  if (assignment.LiteralIsTrue(literal)) return true;
2137  }
2138  return false;
2139 }
2140 
2141 std::string SatClause::DebugString() const {
2142  std::string result;
2143  for (const Literal literal : *this) {
2144  if (!result.empty()) result.append(" ");
2145  result.append(literal.DebugString());
2146  }
2147  return result;
2148 }
2149 
2150 } // namespace sat
2151 } // namespace operations_research
void FindStronglyConnectedComponents(const NodeIndex num_nodes, const Graph &graph, SccOutput *components)
void Start()
Definition: timer.h:31
double Get() const
Definition: timer.h:45
void resize(size_type new_size)
size_type size() const
bool empty() const
void push_back(const value_type &x)
void Set(IndexType i)
Definition: bitset.h:514
ConstView const_view() const
Definition: bitset.h:438
void resize(int size)
Definition: bitset.h:451
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition: bitset.h:806
IntegerType size() const
Definition: bitset.h:758
void Set(IntegerType index)
Definition: bitset.h:792
void SetUnsafe(IntegerType index)
Definition: bitset.h:798
Bitset64< IntegerType >::ConstView const_view() const
Definition: bitset.h:823
void Clear(IntegerType index)
Definition: bitset.h:802
void Resize(IntegerType size)
Definition: bitset.h:778
void ClearAndResize(IntegerType size)
Definition: bitset.h:767
std::string StatString() const
Definition: stats.cc:77
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:552
void AdvanceDeterministicTime(double deterministic_duration)
Advances the deterministic time.
Definition: time_limit.h:226
int64_t NumImplicationOnVariableRemoval(BooleanVariable var)
Definition: clause.cc:2017
void AddBinaryClause(Literal a, Literal b)
Definition: clause.cc:509
bool ComputeTransitiveReduction(bool log_info=false)
Definition: clause.cc:1363
void MinimizeConflictWithReachability(std::vector< Literal > *c)
Definition: clause.cc:804
bool AddBinaryClauseDuringSearch(Literal a, Literal b)
Definition: clause.cc:526
void MinimizeConflictFirstWithTransitiveReduction(const Trail &trail, std::vector< Literal > *c, absl::BitGenRef random)
Definition: clause.cc:899
const std::vector< std::vector< Literal > > & GenerateAtMostOnesWithLargeWeight(const std::vector< Literal > &literals, const std::vector< double > &lp_values)
Definition: clause.cc:1778
absl::Span< const Literal > Reason(const Trail &trail, int trail_index) const final
Definition: clause.cc:792
Literal RepresentativeOf(Literal l) const
Definition: clause.h:568
const std::vector< Literal > & DirectImplications(Literal literal)
Definition: clause.cc:1949
void AddImplication(Literal a, Literal b)
Definition: clause.h:491
void MinimizeConflictFirst(const Trail &trail, std::vector< Literal > *c, SparseBitset< BooleanVariable > *marked)
Definition: clause.cc:881
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
bool TransformIntoMaxCliques(std::vector< std::vector< Literal >> *at_most_ones, int64_t max_num_explored_nodes=1e8)
Definition: clause.cc:1542
std::vector< Literal > ExpandAtMostOneWithWeight(const absl::Span< const Literal > at_most_one, const absl::StrongVector< LiteralIndex, bool > &can_be_included, const absl::StrongVector< LiteralIndex, double > &expanded_lp_values)
Definition: clause.cc:1688
ABSL_MUST_USE_RESULT bool AddAtMostOne(absl::Span< const Literal > at_most_one)
Definition: clause.cc:553
void MinimizeConflictExperimental(const Trail &trail, std::vector< Literal > *c)
Definition: clause.cc:961
int AddLiterals(const std::vector< L > &data)
Definition: inclusion.h:54
void DeleteClause(absl::Span< const Literal > clause)
void AddClause(absl::Span< const Literal > clause)
void SetWorkLimit(uint64_t work_limit)
Definition: inclusion.h:133
void DetectInclusions(const std::function< void(int subset, int superset)> &process)
Definition: inclusion.h:257
LiteralIndex NegatedIndex() const
Definition: sat_base.h:91
LiteralIndex Index() const
Definition: sat_base.h:90
BooleanVariable Variable() const
Definition: sat_base.h:86
ABSL_MUST_USE_RESULT bool InprocessingFixLiteral(Literal true_literal)
Definition: clause.cc:357
bool Propagate(Trail *trail) final
Definition: clause.cc:201
void InprocessingRemoveClause(SatClause *clause)
Definition: clause.cc:376
absl::Span< const Literal > Reason(const Trail &trail, int trail_index) const final
Definition: clause.cc:210
SatClause * AddRemovableClause(const std::vector< Literal > &literals, Trail *trail)
Definition: clause.cc:230
SatClause * InprocessingAddClause(absl::Span< const Literal > new_clause)
Definition: clause.cc:433
void Attach(SatClause *clause, Trail *trail)
Definition: clause.cc:292
bool AddClause(absl::Span< const Literal > literals, Trail *trail)
Definition: clause.cc:223
SatClause * ReasonClause(int trail_index) const
Definition: clause.cc:215
ABSL_MUST_USE_RESULT bool InprocessingRewriteClause(SatClause *clause, absl::Span< const Literal > new_clause)
Definition: clause.cc:385
void LazyDetach(SatClause *clause)
Definition: clause.cc:312
void Detach(SatClause *clause)
Definition: clause.cc:319
void Resize(int num_variables)
Definition: clause.cc:87
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
absl::Span< const Literal > AsSpan() const
Definition: clause.h:93
Literal SecondLiteral() const
Definition: clause.h:78
bool IsSatisfied(const VariablesAssignment &assignment) const
Definition: clause.cc:2134
bool RemoveFixedLiteralsAndTestIfTrue(const VariablesAssignment &assignment)
Definition: clause.cc:2110
std::string DebugString() const
Definition: clause.cc:2141
Literal FirstLiteral() const
Definition: clause.h:77
static SatClause * Create(absl::Span< const Literal > literals)
Definition: clause.cc:2097
const Literal *const begin() const
Definition: clause.h:72
SccGraph(SccFinder *finder, Implication *graph, AtMostOne *at_most_ones, std::vector< Literal > *at_most_one_buffer)
Definition: clause.cc:1078
std::vector< Literal > to_fix_
Definition: clause.cc:1158
const std::vector< int32_t > & operator[](int32_t node) const
Definition: clause.cc:1086
void RegisterPropagator(SatPropagator *propagator)
Definition: sat_base.h:587
void Enqueue(Literal true_literal, int propagator_id)
Definition: sat_base.h:262
const AssignmentInfo & Info(BooleanVariable var) const
Definition: sat_base.h:403
const VariablesAssignment & Assignment() const
Definition: sat_base.h:402
void EnqueueWithUnitReason(Literal true_literal)
Definition: sat_base.h:277
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
WallTimer * wall_timer
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
int index
const bool DEBUG_MODE
Definition: macros.h:24
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
void STLDeleteElements(T *container)
Definition: stl_util.h:372
void STLDeleteContainerPointers(ForwardIterator begin, ForwardIterator end)
Definition: stl_util.h:314
void STLClearObject(T *obj)
Definition: stl_util.h:123
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
const LiteralIndex kNoLiteralIndex(-1)
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:88
if(!yyg->yy_init)
Definition: parser.yy.cc:965
ColIndex representative
std::optional< int64_t > end
int64_t start
#define IF_STATS_ENABLED(instructions)
Definition: stats.h:438
#define SCOPED_TIME_STAT(stats)
Definition: stats.h:439
#define VLOG(verboselevel)
Definition: vlog.h:39
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:47