OR-Tools  9.3
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 <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"
36#include "ortools/base/timer.h"
39#include "ortools/sat/model.h"
41#include "ortools/util/bitset.h"
42#include "ortools/util/stats.h"
45
46namespace operations_research {
47namespace sat {
48
49namespace {
50
51// Returns true if the given watcher list contains the given clause.
52template <typename Watcher>
53bool 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.
62template <typename Container, typename Predicate>
63void 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
87void 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.
96void 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
105bool 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
210absl::Span<const Literal> LiteralWatchers::Reason(const Trail& trail,
211 int trail_index) const {
212 return reasons_[trail_index]->PropagationReason();
213}
214
216 return reasons_[trail_index];
217}
218
219bool LiteralWatchers::AddClause(absl::Span<const Literal> literals) {
220 return AddClause(literals, trail_);
221}
222
223bool 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.
243bool 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
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
302void 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;
398 return true;
399 }
400
401 if (new_clause.size() == 2) {
402 implication_graph_->AddBinaryClause(new_clause[0], new_clause[1]);
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
495void BinaryImplicationGraph::Resize(int num_variables) {
496 SCOPED_TIME_STAT(&stats_);
497 implications_.resize(num_variables << 1);
498 is_redundant_.resize(implications_.size(), false);
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
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();
572bool 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.
587bool 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
710bool 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()) {
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
792absl::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 SparseBitset<BooleanVariable>* marked, 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
943void 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
1069 public:
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_ contraints, expanding them
1102 // implicitely 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, false);
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_[l.Index()] = true;
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_[literal.Index()] = true;
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
1521namespace {
1522
1523bool IntersectionIsEmpty(const std::vector<int>& a, const std::vector<int>& b) {
1524 DCHECK(std::is_sorted(a.begin(), a.end()));
1525 DCHECK(std::is_sorted(b.begin(), b.end()));
1526 int i = 0;
1527 int j = 0;
1528 for (; i < a.size() && j < b.size();) {
1529 if (a[i] == b[j]) return false;
1530 if (a[i] < b[j]) {
1531 ++i;
1532 } else {
1533 ++j;
1534 }
1535 }
1536 return true;
1537}
1538
1539// Used by TransformIntoMaxCliques().
1540struct VectorHash {
1541 std::size_t operator()(const std::vector<Literal>& at_most_one) const {
1542 size_t hash = 0;
1543 for (Literal literal : at_most_one) {
1544 hash = util_hash::Hash(literal.Index().value(), hash);
1545 }
1546 return hash;
1547 }
1548};
1549
1550} // namespace
1551
1553 std::vector<std::vector<Literal>>* at_most_ones,
1554 int64_t max_num_explored_nodes) {
1555 // The code below assumes a DAG.
1556 if (!DetectEquivalences()) return false;
1557 work_done_in_mark_descendants_ = 0;
1558
1559 int num_extended = 0;
1560 int num_removed = 0;
1561 int num_added = 0;
1562
1563 absl::flat_hash_set<std::vector<Literal>, VectorHash> max_cliques;
1565 implications_.size());
1566
1567 // We starts by processing larger constraints first.
1568 // But we want the output order to be stable.
1569 std::vector<std::pair<int, int>> index_size_vector;
1570 index_size_vector.reserve(at_most_ones->size());
1571 for (int i = 0; i < at_most_ones->size(); ++i) {
1572 index_size_vector.push_back({i, (*at_most_ones)[i].size()});
1573 }
1574 std::stable_sort(
1575 index_size_vector.begin(), index_size_vector.end(),
1576 [](const std::pair<int, int> a, const std::pair<int, int>& b) {
1577 return a.second > b.second;
1578 });
1579 for (const auto& [index, old_size] : index_size_vector) {
1580 std::vector<Literal>& clique = (*at_most_ones)[index];
1581 if (time_limit_->LimitReached()) break;
1582
1583 // Remap the clique to only use representative.
1584 //
1585 // Note(user): Because we always use literal with the smallest variable
1586 // indices as representative, this make sure that if possible, we express
1587 // the clique in term of user provided variable (that are always created
1588 // first).
1589 for (Literal& ref : clique) {
1590 DCHECK_LT(ref.Index(), representative_of_.size());
1591 const LiteralIndex rep = representative_of_[ref.Index()];
1592 if (rep == kNoLiteralIndex) continue;
1593 ref = Literal(rep);
1594 }
1595
1596 // Special case for clique of size 2, we don't expand them if they
1597 // are included in an already added clique.
1598 //
1599 // TODO(user): the second condition means the literal must be false!
1600 if (old_size == 2 && clique[0] != clique[1]) {
1601 if (!IntersectionIsEmpty(max_cliques_containing[clique[0].Index()],
1602 max_cliques_containing[clique[1].Index()])) {
1603 ++num_removed;
1604 clique.clear();
1605 continue;
1606 }
1607 }
1608
1609 // We only expand the clique as long as we didn't spend too much time.
1610 if (work_done_in_mark_descendants_ < max_num_explored_nodes) {
1611 clique = ExpandAtMostOne(clique, max_num_explored_nodes);
1612 }
1613 std::sort(clique.begin(), clique.end());
1614 if (!gtl::InsertIfNotPresent(&max_cliques, clique)) {
1615 ++num_removed;
1616 clique.clear();
1617 continue;
1618 }
1619
1620 const int clique_index = max_cliques.size();
1621 for (const Literal l : clique) {
1622 max_cliques_containing[l.Index()].push_back(clique_index);
1623 }
1624 if (clique.size() > old_size) ++num_extended;
1625 ++num_added;
1626 }
1627
1628 if (num_extended > 0 || num_removed > 0 || num_added > 0) {
1629 VLOG(1) << "Clique Extended: " << num_extended
1630 << " Removed: " << num_removed << " Added: " << num_added
1631 << (work_done_in_mark_descendants_ > max_num_explored_nodes
1632 ? " (Aborted)"
1633 : "");
1634 }
1635 return true;
1636}
1637
1638template <bool use_weight>
1640 const absl::Span<const Literal> at_most_one,
1641 const absl::StrongVector<LiteralIndex, bool>& can_be_included,
1642 const absl::StrongVector<LiteralIndex, double>& expanded_lp_values) {
1643 std::vector<Literal> clique(at_most_one.begin(), at_most_one.end());
1644 std::vector<LiteralIndex> intersection;
1645 double clique_weight = 0.0;
1646 const int64_t old_work = work_done_in_mark_descendants_;
1647 if (use_weight) {
1648 for (const Literal l : clique) {
1649 clique_weight += expanded_lp_values[l.Index()];
1650 }
1651 }
1652 for (int i = 0; i < clique.size(); ++i) {
1653 // Do not spend too much time here.
1654 if (work_done_in_mark_descendants_ - old_work > 1e8) break;
1655
1656 is_marked_.ClearAndResize(LiteralIndex(implications_.size()));
1657 MarkDescendants(clique[i]);
1658 if (i == 0) {
1659 for (const LiteralIndex index : is_marked_.PositionsSetAtLeastOnce()) {
1660 if (can_be_included[Literal(index).NegatedIndex()]) {
1661 intersection.push_back(index);
1662 }
1663 }
1664 for (const Literal l : clique) is_marked_.Clear(l.NegatedIndex());
1665 }
1666
1667 int new_size = 0;
1668 double intersection_weight = 0.0;
1669 is_marked_.Clear(clique[i].Index());
1670 is_marked_.Clear(clique[i].NegatedIndex());
1671 for (const LiteralIndex index : intersection) {
1672 if (!is_marked_[index]) continue;
1673 intersection[new_size++] = index;
1674 if (use_weight) {
1675 intersection_weight += expanded_lp_values[index];
1676 }
1677 }
1678 intersection.resize(new_size);
1679 if (intersection.empty()) break;
1680
1681 // We can't generate a violated cut this way. This is because intersection
1682 // contains all the possible ways to extend the current clique.
1683 if (use_weight && clique_weight + intersection_weight <= 1.0) {
1684 clique.clear();
1685 return clique;
1686 }
1687
1688 // Expand? The negation of any literal in the intersection is a valid way
1689 // to extend the clique.
1690 if (i + 1 == clique.size()) {
1691 // Heuristic: use literal with largest lp value. We randomize slightly.
1692 int index = -1;
1693 double max_lp = 0.0;
1694 for (int j = 0; j < intersection.size(); ++j) {
1695 // If we don't use weight, we prefer variable that comes first.
1696 const double lp =
1697 use_weight ? 1.0 - expanded_lp_values[intersection[j]] +
1698 absl::Uniform<double>(*random_, 0.0, 1e-4)
1699 : can_be_included.size() - intersection[j].value();
1700 if (index == -1 || lp > max_lp) {
1701 index = j;
1702 max_lp = lp;
1703 }
1704 }
1705 if (index != -1) {
1706 clique.push_back(Literal(intersection[index]).Negated());
1707 std::swap(intersection.back(), intersection[index]);
1708 intersection.pop_back();
1709 if (use_weight) {
1710 clique_weight += expanded_lp_values[clique.back().Index()];
1711 }
1712 }
1713 }
1714 }
1715 return clique;
1716}
1717
1718// Make sure both version are compiled.
1719template std::vector<Literal> BinaryImplicationGraph::ExpandAtMostOneWithWeight<
1720 true>(const absl::Span<const Literal> at_most_one,
1721 const absl::StrongVector<LiteralIndex, bool>& can_be_included,
1722 const absl::StrongVector<LiteralIndex, double>& expanded_lp_values);
1723template std::vector<Literal> BinaryImplicationGraph::ExpandAtMostOneWithWeight<
1724 false>(const absl::Span<const Literal> at_most_one,
1725 const absl::StrongVector<LiteralIndex, bool>& can_be_included,
1726 const absl::StrongVector<LiteralIndex, double>& expanded_lp_values);
1727
1728const std::vector<std::vector<Literal>>&
1730 const std::vector<Literal>& literals,
1731 const std::vector<double>& lp_values) {
1732 // We only want to generate a cut with literals from the LP, not extra ones.
1733 const int num_literals = implications_.size();
1734 absl::StrongVector<LiteralIndex, bool> can_be_included(num_literals, false);
1735 absl::StrongVector<LiteralIndex, double> expanded_lp_values(num_literals,
1736 0.0);
1737 const int size = literals.size();
1738 for (int i = 0; i < size; ++i) {
1739 const Literal l = literals[i];
1740 can_be_included[l.Index()] = true;
1741 can_be_included[l.NegatedIndex()] = true;
1742
1743 const double value = lp_values[i];
1744 expanded_lp_values[l.Index()] = value;
1745 expanded_lp_values[l.NegatedIndex()] = 1.0 - value;
1746 }
1747
1748 // We want highest sum first.
1749 struct Candidate {
1750 Literal a;
1751 Literal b;
1752 double sum;
1753 bool operator<(const Candidate& other) const { return sum > other.sum; }
1754 };
1755 std::vector<Candidate> candidates;
1756
1757 // First heuristic. Currently we only consider violated at most one of size 2,
1758 // and extend them. Right now, the code is a bit slow to try too many at every
1759 // LP node so it is why we are defensive like this. Note also that because we
1760 // currently still statically add the initial implications, this will only add
1761 // cut based on newly learned binary clause. Or the one that were not added
1762 // to the relaxation in the first place.
1763 for (int i = 0; i < size; ++i) {
1764 Literal current_literal = literals[i];
1765 double current_value = lp_values[i];
1766 if (trail_->Assignment().LiteralIsAssigned(current_literal)) continue;
1767 if (is_redundant_[current_literal.Index()]) continue;
1768
1769 if (current_value < 0.5) {
1770 current_literal = current_literal.Negated();
1771 current_value = 1.0 - current_value;
1772 }
1773
1774 // We consider only one candidate for each current_literal.
1775 LiteralIndex best = kNoLiteralIndex;
1776 double best_value = 0.0;
1777 for (const Literal l : implications_[current_literal.Index()]) {
1778 if (!can_be_included[l.Index()]) continue;
1779 const double activity =
1780 current_value + expanded_lp_values[l.NegatedIndex()];
1781 if (activity <= 1.01) continue;
1782 const double v = activity + absl::Uniform<double>(*random_, 0.0, 1e-4);
1783 if (best == kNoLiteralIndex || v > best_value) {
1784 best_value = v;
1785 best = l.NegatedIndex();
1786 }
1787 }
1788 if (best != kNoLiteralIndex) {
1789 const double activity = current_value + expanded_lp_values[best];
1790 candidates.push_back({current_literal, Literal(best), activity});
1791 }
1792 }
1793
1794 // Do not genate too many cut at once.
1795 const int kMaxNumberOfCutPerCall = 50;
1796 std::sort(candidates.begin(), candidates.end());
1797 if (candidates.size() > kMaxNumberOfCutPerCall) {
1798 candidates.resize(kMaxNumberOfCutPerCall);
1799 }
1800
1801 // Expand to a maximal at most one each candidates before returning them.
1802 // Note that we only expand using literal from the LP.
1803 tmp_cuts_.clear();
1804 std::vector<Literal> at_most_one;
1805 for (const Candidate& candidate : candidates) {
1806 at_most_one = ExpandAtMostOneWithWeight(
1807 {candidate.a, candidate.b}, can_be_included, expanded_lp_values);
1808 if (!at_most_one.empty()) tmp_cuts_.push_back(at_most_one);
1809 }
1810 return tmp_cuts_;
1811}
1812
1813// We use dfs_stack_ but we actually do a BFS.
1814void BinaryImplicationGraph::MarkDescendants(Literal root) {
1815 dfs_stack_ = {root};
1816 is_marked_.Set(root.Index());
1817 if (is_redundant_[root.Index()]) return;
1818 for (int j = 0; j < dfs_stack_.size(); ++j) {
1819 const Literal current = dfs_stack_[j];
1820 for (const Literal l : implications_[current.Index()]) {
1821 if (!is_marked_[l.Index()] && !is_redundant_[l.Index()]) {
1822 dfs_stack_.push_back(l);
1823 is_marked_.Set(l.Index());
1824 }
1825 }
1826
1827 if (current.Index() >= at_most_ones_.size()) continue;
1828 for (const int start : at_most_ones_[current.Index()]) {
1829 for (int i = start;; ++i) {
1830 const Literal l = at_most_one_buffer_[i];
1831 if (l.Index() == kNoLiteralIndex) break;
1832 if (l == current) continue;
1833 if (!is_marked_[l.NegatedIndex()] && !is_redundant_[l.NegatedIndex()]) {
1834 dfs_stack_.push_back(l.Negated());
1835 is_marked_.Set(l.NegatedIndex());
1836 }
1837 }
1838 }
1839 }
1840 work_done_in_mark_descendants_ += dfs_stack_.size();
1841}
1842
1843std::vector<Literal> BinaryImplicationGraph::ExpandAtMostOne(
1844 const absl::Span<const Literal> at_most_one,
1845 int64_t max_num_explored_nodes) {
1846 std::vector<Literal> clique(at_most_one.begin(), at_most_one.end());
1847
1848 // Optim.
1849 for (int i = 0; i < clique.size(); ++i) {
1850 if (implications_[clique[i].Index()].empty() ||
1851 is_redundant_[clique[i].Index()]) {
1852 return clique;
1853 }
1854 }
1855
1856 std::vector<LiteralIndex> intersection;
1857 for (int i = 0; i < clique.size(); ++i) {
1858 if (work_done_in_mark_descendants_ > max_num_explored_nodes) break;
1859 is_marked_.ClearAndResize(LiteralIndex(implications_.size()));
1860 MarkDescendants(clique[i]);
1861
1862 if (i == 0) {
1863 intersection = is_marked_.PositionsSetAtLeastOnce();
1864 for (const Literal l : clique) is_marked_.Clear(l.NegatedIndex());
1865 }
1866
1867 int new_size = 0;
1868 is_marked_.Clear(clique[i].NegatedIndex()); // TODO(user): explain.
1869 for (const LiteralIndex index : intersection) {
1870 if (is_marked_[index]) intersection[new_size++] = index;
1871 }
1872 intersection.resize(new_size);
1873 if (intersection.empty()) break;
1874
1875 // Expand?
1876 if (i + 1 == clique.size()) {
1877 clique.push_back(Literal(intersection.back()).Negated());
1878 intersection.pop_back();
1879 }
1880 }
1881 return clique;
1882}
1883
1884// TODO(user): lazy cleanup the lists on is_removed_?
1885// TODO(user): Mark fixed variable as is_removed_ for faster iteration?
1887 Literal literal) {
1888 CHECK(!is_removed_[literal.Index()]);
1889
1890 // Clear old state.
1891 for (const Literal l : direct_implications_) {
1892 in_direct_implications_[l.Index()] = false;
1893 }
1894 direct_implications_.clear();
1895
1896 // Fill new state.
1897 const VariablesAssignment& assignment = trail_->Assignment();
1898 CHECK(!assignment.LiteralIsAssigned(literal));
1899 for (const Literal l : implications_[literal.Index()]) {
1900 if (l == literal) continue;
1901 if (assignment.LiteralIsAssigned(l)) continue;
1902 if (!is_removed_[l.Index()] && !in_direct_implications_[l.Index()]) {
1903 in_direct_implications_[l.Index()] = true;
1904 direct_implications_.push_back(l);
1905 }
1906 }
1907 if (literal.Index() < at_most_ones_.size()) {
1908 if (is_redundant_[literal.Index()]) {
1909 CHECK(at_most_ones_[literal.Index()].empty());
1910 }
1911 for (const int start : at_most_ones_[literal.Index()]) {
1912 for (int i = start;; ++i) {
1913 const Literal l = at_most_one_buffer_[i];
1914 if (l.Index() == kNoLiteralIndex) break;
1915 if (l == literal) continue;
1916 if (assignment.LiteralIsAssigned(l)) continue;
1917 if (!is_removed_[l.Index()] &&
1918 !in_direct_implications_[l.NegatedIndex()]) {
1919 in_direct_implications_[l.NegatedIndex()] = true;
1920 direct_implications_.push_back(l.Negated());
1921 }
1922 }
1923 }
1924 }
1925 estimated_sizes_[literal.Index()] = direct_implications_.size();
1926 return direct_implications_;
1927}
1928
1930 bool* is_unsat) {
1931 const int saved_index = propagation_trail_index_;
1932 CHECK_EQ(propagation_trail_index_, trail_->Index()); // Propagation done.
1933
1934 const VariablesAssignment& assignment = trail_->Assignment();
1935 if (assignment.VariableIsAssigned(var)) return false;
1936
1937 const Literal literal(var, true);
1938 direct_implications_of_negated_literal_ =
1939 DirectImplications(literal.Negated());
1940 DirectImplications(literal); // Fill in_direct_implications_.
1941 for (const Literal l : direct_implications_of_negated_literal_) {
1942 if (in_direct_implications_[l.Index()]) {
1943 // not(l) => literal => l.
1944 if (!FixLiteral(l)) {
1945 *is_unsat = true;
1946 return false;
1947 }
1948 }
1949 }
1950
1951 return propagation_trail_index_ > saved_index;
1952}
1953
1955 BooleanVariable var) {
1956 const Literal literal(var, true);
1957 int64_t result = 0;
1958 direct_implications_of_negated_literal_ =
1959 DirectImplications(literal.Negated());
1960 const int64_t s1 = DirectImplications(literal).size();
1961 for (const Literal l : direct_implications_of_negated_literal_) {
1962 result += s1;
1963
1964 // We should have dealt with that in FindFailedLiteralAroundVar().
1965 CHECK(!in_direct_implications_[l.Index()]);
1966
1967 // l => literal => l: equivalent variable!
1968 if (in_direct_implications_[l.NegatedIndex()]) result--;
1969 }
1970 return result;
1971}
1972
1973// For all possible a => var => b, add a => b.
1975 BooleanVariable var, std::deque<std::vector<Literal>>* postsolve_clauses) {
1976 const Literal literal(var, true);
1977 direct_implications_of_negated_literal_ =
1978 DirectImplications(literal.Negated());
1979 for (const Literal b : DirectImplications(literal)) {
1980 estimated_sizes_[b.NegatedIndex()]--;
1981 for (const Literal a_negated : direct_implications_of_negated_literal_) {
1982 if (a_negated.Negated() == b) continue;
1983 AddImplication(a_negated.Negated(), b);
1984 }
1985 }
1986 for (const Literal a_negated : direct_implications_of_negated_literal_) {
1987 estimated_sizes_[a_negated.NegatedIndex()]--;
1988 }
1989
1990 // Notify the deletion to the proof checker and the postsolve.
1991 // Note that we want var first in these clauses for the postsolve.
1992 for (const Literal b : direct_implications_) {
1993 if (drat_proof_handler_ != nullptr) {
1994 drat_proof_handler_->DeleteClause({Literal(var, false), b});
1995 }
1996 postsolve_clauses->push_back({Literal(var, false), b});
1997 }
1998 for (const Literal a_negated : direct_implications_of_negated_literal_) {
1999 if (drat_proof_handler_ != nullptr) {
2000 drat_proof_handler_->DeleteClause({Literal(var, true), a_negated});
2001 }
2002 postsolve_clauses->push_back({Literal(var, true), a_negated});
2003 }
2004
2005 // We need to remove any occurrence of var in our implication lists, this will
2006 // be delayed to the CleanupAllRemovedVariables() call.
2007 for (LiteralIndex index : {literal.Index(), literal.NegatedIndex()}) {
2008 is_removed_[index] = true;
2009 if (!is_redundant_[index]) {
2010 ++num_redundant_literals_;
2011 is_redundant_[index] = true;
2012 }
2013 implications_[index].clear();
2014 }
2015}
2016
2018 for (auto& implication : implications_) {
2019 int new_size = 0;
2020 for (const Literal l : implication) {
2021 if (!is_removed_[l.Index()]) implication[new_size++] = l;
2022 }
2023 implication.resize(new_size);
2024 }
2025
2026 // Clean-up at most ones.
2027 at_most_ones_.clear();
2028 CleanUpAndAddAtMostOnes(/*base_index=*/0);
2029}
2030
2031// ----- SatClause -----
2032
2033// static
2034SatClause* SatClause::Create(absl::Span<const Literal> literals) {
2035 CHECK_GE(literals.size(), 2);
2036 SatClause* clause = reinterpret_cast<SatClause*>(
2037 ::operator new(sizeof(SatClause) + literals.size() * sizeof(Literal)));
2038 clause->size_ = literals.size();
2039 for (int i = 0; i < literals.size(); ++i) {
2040 clause->literals_[i] = literals[i];
2041 }
2042 return clause;
2043}
2044
2045// Note that for an attached clause, removing fixed literal is okay because if
2046// any of the watched literal is assigned, then the clause is necessarily true.
2048 const VariablesAssignment& assignment) {
2049 DCHECK(IsAttached());
2050 if (assignment.VariableIsAssigned(literals_[0].Variable()) ||
2051 assignment.VariableIsAssigned(literals_[1].Variable())) {
2052 DCHECK(IsSatisfied(assignment));
2053 return true;
2054 }
2055 int j = 2;
2056 while (j < size_ && !assignment.VariableIsAssigned(literals_[j].Variable())) {
2057 ++j;
2058 }
2059 for (int i = j; i < size_; ++i) {
2060 if (assignment.VariableIsAssigned(literals_[i].Variable())) {
2061 if (assignment.LiteralIsTrue(literals_[i])) return true;
2062 } else {
2063 std::swap(literals_[j], literals_[i]);
2064 ++j;
2065 }
2066 }
2067 size_ = j;
2068 return false;
2069}
2070
2071bool SatClause::IsSatisfied(const VariablesAssignment& assignment) const {
2072 for (const Literal literal : *this) {
2073 if (assignment.LiteralIsTrue(literal)) return true;
2074 }
2075 return false;
2076}
2077
2078std::string SatClause::DebugString() const {
2079 std::string result;
2080 for (const Literal literal : *this) {
2081 if (!result.empty()) result.append(" ");
2082 result.append(literal.DebugString());
2083 }
2084 return result;
2085}
2086
2087} // namespace sat
2088} // namespace operations_research
#define LOG_IF(severity, condition)
Definition: base/logging.h:479
#define CHECK(condition)
Definition: base/logging.h:495
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:893
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define CHECK_GE(val1, val2)
Definition: base/logging.h:707
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:895
#define CHECK_NE(val1, val2)
Definition: base/logging.h:704
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:894
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK(condition)
Definition: base/logging.h:890
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:891
#define VLOG(verboselevel)
Definition: base/logging.h:984
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)
IntegerType size() const
Definition: bitset.h:775
void Set(IntegerType index)
Definition: bitset.h:809
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition: bitset.h:819
void Clear(IntegerType index)
Definition: bitset.h:815
void Resize(IntegerType size)
Definition: bitset.h:795
void ClearAndResize(IntegerType size)
Definition: bitset.h:784
std::string StatString() const
Definition: stats.cc:71
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:546
void AdvanceDeterministicTime(double deterministic_duration)
Advances the deterministic time.
Definition: time_limit.h:227
int64_t NumImplicationOnVariableRemoval(BooleanVariable var)
Definition: clause.cc:1954
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
const std::vector< std::vector< Literal > > & GenerateAtMostOnesWithLargeWeight(const std::vector< Literal > &literals, const std::vector< double > &lp_values)
Definition: clause.cc:1729
absl::Span< const Literal > Reason(const Trail &trail, int trail_index) const final
Definition: clause.cc:792
void RemoveBooleanVariable(BooleanVariable var, std::deque< std::vector< Literal > > *postsolve_clauses)
Definition: clause.cc:1974
void MinimizeConflictFirstWithTransitiveReduction(const Trail &trail, std::vector< Literal > *c, SparseBitset< BooleanVariable > *marked, absl::BitGenRef random)
Definition: clause.cc:899
Literal RepresentativeOf(Literal l) const
Definition: clause.h:564
bool TransformIntoMaxCliques(std::vector< std::vector< Literal > > *at_most_ones, int64_t max_num_explored_nodes=1e8)
Definition: clause.cc:1552
const std::vector< Literal > & DirectImplications(Literal literal)
Definition: clause.cc:1886
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
bool FindFailedLiteralAroundVar(BooleanVariable var, bool *is_unsat)
Definition: clause.cc:1929
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:1639
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
void DeleteClause(absl::Span< const Literal > clause)
void AddClause(absl::Span< const Literal > clause)
LiteralIndex NegatedIndex() const
Definition: sat_base.h:88
LiteralIndex Index() const
Definition: sat_base.h:87
BooleanVariable Variable() const
Definition: sat_base.h:83
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
const Literal *const begin() const
Definition: clause.h:72
Literal SecondLiteral() const
Definition: clause.h:78
bool IsSatisfied(const VariablesAssignment &assignment) const
Definition: clause.cc:2071
bool RemoveFixedLiteralsAndTestIfTrue(const VariablesAssignment &assignment)
Definition: clause.cc:2047
std::string DebugString() const
Definition: clause.cc:2078
Literal FirstLiteral() const
Definition: clause.h:77
static SatClause * Create(absl::Span< const Literal > literals)
Definition: clause.cc:2034
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:560
void Enqueue(Literal true_literal, int propagator_id)
Definition: sat_base.h:253
const VariablesAssignment & Assignment() const
Definition: sat_base.h:383
const AssignmentInfo & Info(BooleanVariable var) const
Definition: sat_base.h:384
void EnqueueWithUnitReason(Literal true_literal)
Definition: sat_base.h:268
bool LiteralIsAssigned(Literal literal) const
Definition: sat_base.h:156
bool VariableIsAssigned(BooleanVariable var) const
Definition: sat_base.h:161
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:153
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:150
int64_t b
int64_t a
WallTimer * wall_timer
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
int index
const int INFO
Definition: log_severity.h:31
const bool DEBUG_MODE
Definition: macros.h:24
int64_t hash
Definition: matrix_utils.cc:61
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
bool InsertIfNotPresent(Collection *const collection, const typename Collection::value_type &value)
Definition: map_util.h:122
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:262
const LiteralIndex kNoLiteralIndex(-1)
Collection of objects used to extend the Constraint Solver library.
uint64_t Hash(uint64_t num, uint64_t c)
Definition: hash.h:150
Literal literal
Definition: optimization.cc:89
ColIndex representative
std::optional< int64_t > end
int64_t start
#define IF_STATS_ENABLED(instructions)
Definition: stats.h:437
#define SCOPED_TIME_STAT(stats)
Definition: stats.h:438
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:44