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