Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
probing.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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/probing.h"
15
16#include <algorithm>
17#include <cstdint>
18#include <deque>
19#include <functional>
20#include <optional>
21#include <utility>
22#include <variant>
23#include <vector>
24
25#include "absl/algorithm/container.h"
26#include "absl/cleanup/cleanup.h"
27#include "absl/container/btree_map.h"
28#include "absl/container/btree_set.h"
29#include "absl/container/flat_hash_map.h"
30#include "absl/container/inlined_vector.h"
31#include "absl/log/check.h"
32#include "absl/log/log.h"
33#include "absl/log/vlog_is_on.h"
34#include "absl/strings/string_view.h"
35#include "absl/types/span.h"
38#include "ortools/base/timer.h"
39#include "ortools/sat/clause.h"
41#include "ortools/sat/integer.h"
44#include "ortools/sat/model.h"
48#include "ortools/sat/util.h"
49#include "ortools/util/bitset.h"
54
55namespace operations_research {
56namespace sat {
57
58// Holds a copy of the trail and of propagator reasons in order to be able to
59// build LRAT proofs after the trail has been modified.
60class TrailCopy {
61 public:
62 TrailCopy(const Trail& trail, const BinaryImplicationGraph& implication_graph,
63 const ClauseManager& clause_manager)
64 : trail_(trail),
65 implication_graph_(implication_graph),
66 clause_manager_(clause_manager) {}
67
68 // Updates this trail copy with the current trail state.
69 void CopyTrail() {
70 const int trail_size = trail_.Index();
71 trail_index_.resize(trail_.NumVariables(), -1);
72 trail_literals_.clear();
73 trail_info_.clear();
74 stored_reasons_.clear();
75 trail_literals_.reserve(trail_size);
76 trail_info_.reserve(trail_size);
77 for (int i = 0; i < trail_size; ++i) {
78 const Literal literal = trail_[i];
79 const BooleanVariable var = literal.Variable();
80 const AssignmentInfo& info = trail_.Info(var);
81 const int assignment_type = trail_.AssignmentType(var);
82 absl::Span<const Literal> reason;
83 // Get the clause ID only if it is very cheap to compute. Otherwise, delay
84 // its computation until it is really needed, in AppendClauseIdsFixing().
85 std::variant<ClauseId, const SatClause*> reason_clause = kNoClauseId;
86 if (assignment_type == AssignmentType::kCachedReason) {
87 const absl::Span<const Literal> cached_reason = trail_.Reason(var);
88 stored_reasons_.push_back({cached_reason.begin(), cached_reason.end()});
89 reason = stored_reasons_.back();
90 } else if (assignment_type == AssignmentType::kUnitReason) {
91 reason_clause = trail_.GetUnitClauseId(var);
92 } else if (assignment_type == implication_graph_.PropagatorId()) {
93 absl::Span<const Literal> original_reason = trail_.Reason(var);
94 DCHECK_EQ(original_reason.size(), 1);
95 reason = absl::MakeConstSpan(trail_literals_)
96 .subspan(trail_index_[original_reason[0].Variable()], 1);
97 DCHECK_EQ(reason[0], original_reason[0].Negated());
98 } else if (assignment_type == clause_manager_.PropagatorId()) {
99 const SatClause* sat_clause = clause_manager_.ReasonClauseOrNull(var);
100 if (sat_clause != nullptr) {
101 reason = sat_clause->AsSpan();
102 }
103 reason_clause = clause_manager_.ReasonClauseOrNull(var);
104 }
105 trail_index_[var] = i;
106 trail_literals_.push_back(literal);
107 trail_info_.push_back(
108 {info.level, assignment_type, reason, reason_clause});
109 }
110
111 const int num_decisions = trail_.CurrentDecisionLevel();
112 decisions_.clear();
113 decisions_.reserve(num_decisions);
114 for (int i = 0; i < num_decisions; ++i) {
115 decisions_.push_back(trail_.Decisions()[i].literal);
116 }
117 }
118
119 // Same as ClauseManager::AppendClauseIdsFixing(), but adapted to work on this
120 // trail copy instead of on the real trail.
122 absl::Span<const Literal> literals, std::vector<ClauseId>* clause_ids,
123 LiteralIndex decision,
124 absl::flat_hash_map<std::pair<Literal, Literal>, ClauseId>
125 tmp_binary_clause_ids) {
126 // Mark the literals whose reason must be expanded, and put them in a heap.
127 tmp_mark_.ClearAndResize(BooleanVariable(trail_index_.size()));
128 marked_trail_indices_heap_.clear();
129 for (const Literal lit : literals) {
130 tmp_mark_.Set(lit.Variable());
131 marked_trail_indices_heap_.push_back(trail_index_[lit.Variable()]);
132 }
133 absl::c_make_heap(marked_trail_indices_heap_);
134 const int current_level = decisions_.size();
135
136 // The min level of the expanded literals.
137 int min_level = current_level;
138
139 // Unit clauses must come first. We put them in clause_ids directly. We put
140 // the others in non_unit_clause_ids and append them to clause_ids at the
141 // end.
142 std::vector<ClauseId>& non_unit_clause_ids =
143 tmp_clause_ids_for_append_clauses_fixing_;
144 non_unit_clause_ids.clear();
145
146 while (!marked_trail_indices_heap_.empty()) {
147 absl::c_pop_heap(marked_trail_indices_heap_);
148 const int trail_index = marked_trail_indices_heap_.back();
149 marked_trail_indices_heap_.pop_back();
150 const Literal marked_literal = trail_literals_[trail_index];
151 const TrailInfo& trail_info = trail_info_[trail_index];
152
153 // Stop at decisions, at literals fixed at root, and at literals implied
154 // by the decision at their level.
155 const int level = trail_info.level;
156 if (level > 0) min_level = std::min(min_level, level);
157 if (trail_info.assignment_type == AssignmentType::kSearchDecision) {
158 continue;
159 }
160 if (level == 0) {
161 clause_ids->push_back(std::get<ClauseId>(trail_info.reason_clause));
162 continue;
163 }
164 const Literal level_decision = decisions_[level - 1];
165 ClauseId clause_id = implication_graph_.GetClauseId(
166 level_decision.Negated(), marked_literal);
167 if (clause_id == kNoClauseId) {
168 const auto it = tmp_binary_clause_ids.find(
169 std::minmax(level_decision.Negated(), marked_literal));
170 if (it != tmp_binary_clause_ids.end()) {
171 clause_id = it->second;
172 }
173 }
174 if (clause_id != kNoClauseId) {
175 non_unit_clause_ids.push_back(clause_id);
176 continue;
177 }
178
179 // Mark all the literals of its reason.
180 for (const Literal literal : trail_info.reason) {
181 const BooleanVariable var = literal.Variable();
182 if (!tmp_mark_[var]) {
183 const int trail_index = trail_index_[var];
184 const TrailInfo& info = trail_info_[trail_index];
185 tmp_mark_.Set(var);
186 if (info.level > 0) {
187 marked_trail_indices_heap_.push_back(trail_index);
188 absl::c_push_heap(marked_trail_indices_heap_);
189 } else {
190 clause_ids->push_back(std::get<ClauseId>(info.reason_clause));
191 }
192 }
193 }
194 non_unit_clause_ids.push_back(ReasonClauseId(trail_index));
195 }
196
197 // Add the implication chain from `decision` to all the decisions found
198 // during the expansion.
199 if (Literal(decision) != decisions_[current_level - 1]) {
200 // If `decision` is not the last decision, it must directly imply it.
201 clause_ids->push_back(implication_graph_.GetClauseId(
202 Literal(decision).Negated(), decisions_[current_level - 1]));
203 }
204 for (int level = current_level - 1; level >= min_level; --level) {
205 clause_ids->push_back(implication_graph_.GetClauseId(
206 decisions_[level].Negated(), decisions_[level - 1]));
207 }
208
209 clause_ids->insert(clause_ids->end(), non_unit_clause_ids.rbegin(),
210 non_unit_clause_ids.rend());
211 }
212
213 private:
214 // Same as ClauseManager::ReasonClauseId(), but adapted to work on this trail
215 // copy instead of on the real trail.
216 ClauseId ReasonClauseId(int trail_index) const {
217 const TrailInfo& trail_info = trail_info_[trail_index];
218 const int assignment_type = trail_info.assignment_type;
219 if (assignment_type == AssignmentType::kCachedReason ||
220 assignment_type == AssignmentType::kUnitReason) {
221 return std::get<ClauseId>(trail_info.reason_clause);
222 } else if (assignment_type == implication_graph_.PropagatorId()) {
223 return implication_graph_.GetClauseId(trail_literals_[trail_index],
224 trail_info.reason[0].Negated());
225 } else if (assignment_type == clause_manager_.PropagatorId()) {
226 const SatClause* reason =
227 std::get<const SatClause*>(trail_info.reason_clause);
228 if (reason != nullptr) {
229 return clause_manager_.GetClauseId(reason);
230 }
231 }
232 return kNoClauseId;
233 }
234
235 struct TrailInfo {
236 uint32_t level;
237 int assignment_type;
238 // For literals propagated by the BinaryImplicationGraph, the negation of
239 // the original reason. For literals propagated by the ClauseManager, *all*
240 // the literals of the SatClause (which includes the propagated variable).
241 // For kCachedReason, this is the stored reason. Empty for kUnitReason.
242 absl::Span<const Literal> reason;
243 // Holds a clause ID if `assignment_type` is kCachedReason or kUnitReason,
244 // or a SatClause* if `assignment_type` corresponds to the ClauseManager.
245 std::variant<ClauseId, const SatClause*> reason_clause;
246 };
247
248 const Trail& trail_;
249 const BinaryImplicationGraph& implication_graph_;
250 const ClauseManager& clause_manager_;
251
252 util_intops::StrongVector<BooleanVariable, int> trail_index_;
253 std::vector<Literal> trail_literals_;
254 std::vector<TrailInfo> trail_info_;
255 // We use a deque for pointer stability (they are kept in TrailInfo::reason).
256 std::deque<std::vector<Literal>> stored_reasons_;
257 std::vector<Literal> decisions_;
258
259 SparseBitset<BooleanVariable> tmp_mark_;
260 std::vector<int> marked_trail_indices_heap_;
261 std::vector<ClauseId> tmp_clause_ids_for_append_clauses_fixing_;
262};
263
265 : trail_(*model->GetOrCreate<Trail>()),
266 assignment_(model->GetOrCreate<SatSolver>()->Assignment()),
267 integer_trail_(model->GetOrCreate<IntegerTrail>()),
268 implied_bounds_(model->GetOrCreate<ImpliedBounds>()),
269 product_detector_(model->GetOrCreate<ProductDetector>()),
270 sat_solver_(model->GetOrCreate<SatSolver>()),
271 time_limit_(model->GetOrCreate<TimeLimit>()),
272 implication_graph_(model->GetOrCreate<BinaryImplicationGraph>()),
273 clause_manager_(model->GetOrCreate<ClauseManager>()),
274 clause_id_generator_(model->GetOrCreate<ClauseIdGenerator>()),
275 lrat_proof_handler_(model->Mutable<LratProofHandler>()),
276 trail_copy_(new TrailCopy(trail_, *implication_graph_, *clause_manager_)),
277 drat_enabled_(lrat_proof_handler_ != nullptr &&
278 (lrat_proof_handler_->drat_check_enabled() ||
279 lrat_proof_handler_->drat_output_enabled())),
280 logger_(model->GetOrCreate<SolverLogger>()) {}
281
282Prober::~Prober() { delete trail_copy_; }
283
284bool Prober::ProbeBooleanVariables(const double deterministic_time_limit) {
285 const int num_variables = sat_solver_->NumVariables();
286 const VariablesAssignment& assignment = sat_solver_->Assignment();
287 std::vector<BooleanVariable> bool_vars;
288 for (BooleanVariable b(0); b < num_variables; ++b) {
289 if (assignment.VariableIsAssigned(b)) continue;
290 const Literal literal(b, true);
291 if (implication_graph_->RepresentativeOf(literal) != literal) {
292 continue;
293 }
294 bool_vars.push_back(b);
295 }
296 return ProbeBooleanVariables(deterministic_time_limit, bool_vars);
297}
298
299bool Prober::ProbeOneVariableInternal(BooleanVariable b) {
300 new_integer_bounds_.clear();
301 propagated_.ResetAllToFalse();
302 tmp_binary_clause_ids_.clear();
303 // We block clause deletion since we compute some LRAT proofs in a delayed
304 // way, and we need to make sure the clauses that were used are still around.
305 sat_solver_->BlockClauseDeletion(true);
306 absl::Cleanup unblock_clause_deletion = [&] {
307 sat_solver_->BlockClauseDeletion(false);
308 };
309 for (const Literal decision : {Literal(b, true), Literal(b, false)}) {
310 if (assignment_.LiteralIsAssigned(decision)) continue;
311
312 ++num_decisions_;
313 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
314 const int saved_index = trail_.Index();
315 if (sat_solver_->EnqueueDecisionAndBackjumpOnConflict(decision) ==
317 return false;
318 }
319 sat_solver_->AdvanceDeterministicTime(time_limit_);
320
321 if (sat_solver_->ModelIsUnsat()) return false;
322 if (sat_solver_->CurrentDecisionLevel() == 0) continue;
323 if (trail_.Index() > saved_index) {
324 if (callback_ != nullptr) callback_(decision);
325 }
326
327 if (!implied_bounds_->ProcessIntegerTrail(decision)) return false;
328 product_detector_->ProcessTrailAtLevelOne();
329 integer_trail_->AppendNewBounds(&new_integer_bounds_);
330 to_fix_at_true_.clear();
331 new_literals_implied_by_decision_.clear();
332 new_implied_or_fixed_literals_.clear();
333 for (int i = saved_index + 1; i < trail_.Index(); ++i) {
334 const Literal l = trail_[i];
335
336 // We mark on the first pass (b.IsPositive()) and check on the second.
337 if (decision.IsPositive()) {
338 propagated_.Set(l.Index());
339 } else {
340 if (propagated_[l]) {
341 to_fix_at_true_.push_back(l);
342 if (lrat_proof_handler_ != nullptr) {
343 new_implied_or_fixed_literals_.push_back(l);
344 }
345 }
346 }
347
348 // Anything not propagated by the BinaryImplicationGraph is a "new"
349 // binary clause. This is because the BinaryImplicationGraph has the
350 // highest priority of all propagators.
351 if (trail_.AssignmentType(l.Variable()) !=
352 implication_graph_->PropagatorId()) {
353 if (l == decision.Negated()) {
354 to_fix_at_true_.push_back(l);
355 } else if (l != decision) {
356 new_literals_implied_by_decision_.push_back(l);
357 }
358 if (lrat_proof_handler_ != nullptr && l != decision) {
359 new_implied_or_fixed_literals_.push_back(l);
360 }
361 }
362 }
363
364 if (lrat_proof_handler_ != nullptr) {
365 for (const Literal l : new_implied_or_fixed_literals_) {
366 tmp_clause_ids_.clear();
367 clause_manager_->AppendClauseIdsFixing(
368 {l}, &tmp_clause_ids_, decision.Index(),
369 [&](int level, int trail_index) {
370 const Literal decision = trail_.Decisions()[level - 1].literal;
371 const Literal lit = trail_[trail_index];
372 const auto it = tmp_binary_clause_ids_.find(
373 std::minmax(decision.Negated(), lit));
374 if (it != tmp_binary_clause_ids_.end()) return it->second;
375 return kNoClauseId;
376 });
377 const ClauseId clause_id = clause_id_generator_->GetNextId();
378 lrat_proof_handler_->AddInferredClause(
379 clause_id, {decision.Negated(), l}, tmp_clause_ids_);
380 tmp_binary_clause_ids_[std::minmax(decision.Negated(), l)] = clause_id;
381 num_lrat_clauses_++;
382 num_lrat_proof_clauses_ += tmp_clause_ids_.size();
383 }
384 if (decision.IsPositive()) {
385 trail_copy_->CopyTrail();
386 } else if (!to_fix_at_true_.empty()) {
387 for (const Literal l : to_fix_at_true_) {
388 tmp_clause_ids_.clear();
389 trail_copy_->AppendClauseIdsFixing({l}, &tmp_clause_ids_,
390 decision.NegatedIndex(),
391 tmp_binary_clause_ids_);
392 const ClauseId clause_id = clause_id_generator_->GetNextId();
393 lrat_proof_handler_->AddInferredClause(clause_id, {decision, l},
394 tmp_clause_ids_);
395 tmp_binary_clause_ids_[std::minmax(decision, l)] = clause_id;
396 num_lrat_clauses_++;
397 num_lrat_proof_clauses_ += tmp_clause_ids_.size();
398 }
399 }
400 }
401
402 // Fix variables and add new binary clauses.
403 if (!sat_solver_->ResetToLevelZero()) return false;
404 for (const Literal l : to_fix_at_true_) {
405 ClauseId clause_id = kNoClauseId;
406 if (lrat_proof_handler_ != nullptr) {
407 clause_id = clause_id_generator_->GetNextId();
408 absl::InlinedVector<ClauseId, 2> clause_ids;
409 clause_ids.push_back(
410 tmp_binary_clause_ids_.at(std::minmax(decision.Negated(), l)));
411 if (l != decision.Negated()) {
412 clause_ids.push_back(
413 tmp_binary_clause_ids_.at(std::minmax(decision, l)));
414 }
415 lrat_proof_handler_->AddInferredClause(clause_id, {l}, clause_ids);
416 num_lrat_clauses_++;
417 num_lrat_proof_clauses_ += clause_ids.size();
418 }
419 if (!clause_manager_->InprocessingAddUnitClause(clause_id, l)) {
420 return false;
421 }
422 }
423 if (!sat_solver_->FinishPropagation()) return false;
424 for (const Literal l : new_literals_implied_by_decision_) {
425 // Some variables can be fixed by the above loop.
426 if (trail_.Assignment().LiteralIsAssigned(decision)) break;
427 if (trail_.Assignment().LiteralIsAssigned(l)) continue;
428 ClauseId clause_id = kNoClauseId;
429 if (lrat_proof_handler_ != nullptr) {
430 clause_id =
431 tmp_binary_clause_ids_.at(std::minmax(decision.Negated(), l));
432 }
433 num_new_binary_++;
434 // Tricky: by default AddBinaryClause() can delete the LRAT `clause_id`
435 // and create a new ID for a similar clause between the representatives.
436 // But `clause_id`, registered in tmp_binary_clause_ids_, can be needed in
437 // the next iteration for the proof of a new fixed literal. Hence we must
438 // not delete it here. Instead, it is deleted at the end of this method,
439 // with the other non-longer needed clauses.
440 // TODO(user): can we maintain a one to one correspondence of clauses
441 // in LRAT and in the binary implication graph to avoid this?
442 if (!implication_graph_->AddBinaryClause(
443 clause_id, decision.Negated(), l,
444 /*delete_non_representative_id=*/false)) {
445 return false;
446 }
447 }
448 if (!sat_solver_->FinishPropagation()) return false;
449 }
450 if (lrat_proof_handler_ != nullptr) {
451 // Delete the temporary LRAT clauses that have not been added to the binary
452 // implication graph.
453 for (const auto& [binary_clause, clause_id] : tmp_binary_clause_ids_) {
454 if (implication_graph_->GetClauseId(binary_clause.first,
455 binary_clause.second) != clause_id) {
456 lrat_proof_handler_->DeleteClause(
457 clause_id, {binary_clause.first, binary_clause.second});
458 num_unneeded_lrat_clauses_++;
459 }
460 }
461 }
462
463 // We have at most two lower bounds for each variables (one for b==0 and one
464 // for b==1), so the min of the two is a valid level zero bound! More
465 // generally, the domain of a variable can be intersected with the union
466 // of the two propagated domains. This also allow to detect "holes".
467 //
468 // TODO(user): More generally, for any clauses (b or not(b) is one), we
469 // could probe all the literal inside, and for any integer variable, we can
470 // take the union of the propagated domain as a new domain.
471 //
472 // TODO(user): fix binary variable in the same way? It might not be as
473 // useful since probing on such variable will also fix it. But then we might
474 // abort probing early, so it might still be good.
475 std::sort(new_integer_bounds_.begin(), new_integer_bounds_.end(),
476 [](IntegerLiteral a, IntegerLiteral b) { return a.var < b.var; });
477
478 // This is used for the hole detection.
479 IntegerVariable prev_var = kNoIntegerVariable;
480 IntegerValue lb_max = kMinIntegerValue;
481 IntegerValue ub_min = kMaxIntegerValue;
482 new_integer_bounds_.push_back(IntegerLiteral()); // Sentinel.
483
484 const int limit = new_integer_bounds_.size();
485 for (int i = 0; i < limit; ++i) {
486 const IntegerVariable var = new_integer_bounds_[i].var;
487
488 // Hole detection.
489 if (i > 0 && PositiveVariable(var) != prev_var) {
490 if (ub_min + 1 < lb_max) {
491 // The variable cannot take value in (ub_min, lb_max) !
492 //
493 // TODO(user): do not create domain with a complexity that is too
494 // large?
495 const Domain old_domain =
496 integer_trail_->InitialVariableDomain(prev_var);
497 const Domain new_domain = old_domain.IntersectionWith(
498 Domain(ub_min.value() + 1, lb_max.value() - 1).Complement());
499 if (new_domain != old_domain) {
500 ++num_new_holes_;
501 if (!integer_trail_->UpdateInitialDomain(prev_var, new_domain)) {
502 return false;
503 }
504 }
505 }
506
507 // Reinitialize.
508 lb_max = kMinIntegerValue;
509 ub_min = kMaxIntegerValue;
510 }
511
512 prev_var = PositiveVariable(var);
513 if (VariableIsPositive(var)) {
514 lb_max = std::max(lb_max, new_integer_bounds_[i].bound);
515 } else {
516 ub_min = std::min(ub_min, -new_integer_bounds_[i].bound);
517 }
518
519 // Bound tightening.
520 if (i == 0 || new_integer_bounds_[i - 1].var != var) continue;
521 const IntegerValue new_bound = std::min(new_integer_bounds_[i - 1].bound,
522 new_integer_bounds_[i].bound);
523 if (new_bound > integer_trail_->LowerBound(var)) {
524 ++num_new_integer_bounds_;
525 if (!integer_trail_->Enqueue(
526 IntegerLiteral::GreaterOrEqual(var, new_bound), {}, {})) {
527 return false;
528 }
529 }
530 }
531
532 // We might have updated some integer domain, let's propagate.
533 return sat_solver_->FinishPropagation();
534}
535
536bool Prober::ProbeOneVariable(BooleanVariable b) {
537 // Resize the propagated sparse bitset.
538 const int num_variables = sat_solver_->NumVariables();
539 propagated_.ClearAndResize(LiteralIndex(2 * num_variables));
540
541 // Reset the solver in case it was already used.
542 if (!sat_solver_->ResetToLevelZero()) return false;
543
544 const int initial_num_fixed = sat_solver_->LiteralTrail().Index();
545 if (!ProbeOneVariableInternal(b)) return false;
546
547 // Statistics
548 const int num_fixed = sat_solver_->LiteralTrail().Index();
549 num_new_literals_fixed_ += num_fixed - initial_num_fixed;
550 return true;
551}
552
554 const double deterministic_time_limit,
555 absl::Span<const BooleanVariable> bool_vars) {
556 WallTimer wall_timer;
557 wall_timer.Start();
558
559 // Reset statistics.
560 num_decisions_ = 0;
561 num_new_binary_ = 0;
562 num_new_holes_ = 0;
563 num_new_integer_bounds_ = 0;
564 num_new_literals_fixed_ = 0;
565 num_lrat_clauses_ = 0;
566 num_lrat_proof_clauses_ = 0;
567 num_unneeded_lrat_clauses_ = 0;
568
569 // Resize the propagated sparse bitset.
570 const int num_variables = sat_solver_->NumVariables();
571 propagated_.ClearAndResize(LiteralIndex(2 * num_variables));
572
573 // Reset the solver in case it was already used.
574 if (!sat_solver_->ResetToLevelZero()) return false;
575
576 const int initial_num_fixed = sat_solver_->LiteralTrail().Index();
577 const double initial_deterministic_time =
578 time_limit_->GetElapsedDeterministicTime();
579 const double limit = initial_deterministic_time + deterministic_time_limit;
580
581 bool limit_reached = false;
582 int num_probed = 0;
583
584 for (const BooleanVariable b : bool_vars) {
585 const Literal literal(b, true);
586 if (implication_graph_->RepresentativeOf(literal) != literal) {
587 continue;
588 }
589
590 // TODO(user): Instead of an hard deterministic limit, we should probably
591 // use a lower one, but reset it each time we have found something useful.
592 if (time_limit_->LimitReached() ||
593 time_limit_->GetElapsedDeterministicTime() > limit) {
594 limit_reached = true;
595 break;
596 }
597
598 // Propagate b=1 and then b=0.
599 ++num_probed;
600 if (!ProbeOneVariableInternal(b)) {
601 return false;
602 }
603 }
604
605 // Update stats.
606 const int num_fixed = sat_solver_->LiteralTrail().Index();
607 num_new_literals_fixed_ = num_fixed - initial_num_fixed;
608
609 // Display stats.
610 if (logger_->LoggingIsEnabled()) {
611 const double time_diff =
612 time_limit_->GetElapsedDeterministicTime() - initial_deterministic_time;
613 SOLVER_LOG(logger_, "[Probing] deterministic_time: ", time_diff,
614 " (limit: ", deterministic_time_limit,
615 ") wall_time: ", wall_timer.Get(), " (",
616 (limit_reached ? "Aborted " : ""), num_probed, "/",
617 bool_vars.size(), ")");
618 if (num_new_literals_fixed_ > 0) {
619 SOLVER_LOG(logger_,
620 "[Probing] - new fixed Boolean: ", num_new_literals_fixed_,
621 " (", FormatCounter(num_fixed), "/",
622 FormatCounter(sat_solver_->NumVariables()), ")");
623 }
624 if (num_new_holes_ > 0) {
625 SOLVER_LOG(logger_, "[Probing] - new integer holes: ",
626 FormatCounter(num_new_holes_));
627 }
628 if (num_new_integer_bounds_ > 0) {
629 SOLVER_LOG(logger_, "[Probing] - new integer bounds: ",
630 FormatCounter(num_new_integer_bounds_));
631 }
632 if (num_new_binary_ > 0) {
633 SOLVER_LOG(logger_, "[Probing] - new binary clause: ",
634 FormatCounter(num_new_binary_));
635 }
636 if (num_lrat_clauses_ > 0) {
637 SOLVER_LOG(logger_, "[Probing] - new LRAT clauses: ",
638 FormatCounter(num_lrat_clauses_));
639 }
640 if (num_lrat_proof_clauses_ > 0) {
641 SOLVER_LOG(logger_, "[Probing] - new LRAT proof clauses: ",
642 FormatCounter(num_lrat_proof_clauses_));
643 }
644 if (num_unneeded_lrat_clauses_ > 0) {
645 SOLVER_LOG(logger_, "[Probing] - unneeded LRAT clauses: ",
646 FormatCounter(num_unneeded_lrat_clauses_));
647 }
648 }
649
650 return true;
651}
652
653bool Prober::ProbeDnf(absl::string_view name,
654 absl::Span<const std::vector<Literal>> dnf,
655 DnfType dnf_type, const SatClause* dnf_clause) {
656 if (dnf.size() <= 1) return true;
657
658 // dnf_clause can be deleted as a side effect of probing, but is needed for
659 // LRAT in FixProbedDnfLiterals(). We thus copy its literals first, and
660 // prevent the corresponding LRAT clause from being deleted.
661 ClauseId dnf_clause_id = kNoClauseId;
662 std::vector<Literal> dnf_clause_literals;
663 if (dnf_clause != nullptr && lrat_proof_handler_ != nullptr) {
664 dnf_clause_id = clause_manager_->GetClauseId(dnf_clause);
665 dnf_clause_literals.assign(dnf_clause->AsSpan().begin(),
666 dnf_clause->AsSpan().end());
667 lrat_proof_handler_->PinClause(dnf_clause_id, dnf_clause_literals);
668 }
669 absl::Cleanup cleanup = [this, dnf_clause_id] {
670 if (dnf_clause_id != kNoClauseId) {
671 lrat_proof_handler_->UnpinClause(dnf_clause_id);
672 }
673 };
674
675 // Reset the solver in case it was already used.
676 if (!sat_solver_->ResetToLevelZero()) return false;
677
678 always_propagated_bounds_.clear();
679 always_propagated_literals_.clear();
680 int num_valid_conjunctions = 0;
681 for (absl::Span<const Literal> conjunction : dnf) {
682 // TODO(user): instead of going back to level zero, we could backtrack
683 // to level 'n', where n is the length of the longest prefix shared between
684 // the current conjunction and the previous one (more or less -- conjunction
685 // literals which are already assigned or lead to a conflict do not
686 // translate to a decision). For a kAtLeastOneCombination DNF with 8
687 // conjunctions, this would reduce the number of enqueues from 8*3=24 to 14.
688 if (!sat_solver_->ResetToLevelZero()) return false;
689 if (num_valid_conjunctions > 0 && always_propagated_bounds_.empty() &&
690 always_propagated_literals_.empty()) {
691 // We can exit safely as nothing will be propagated.
692 return true;
693 }
694
695 bool conjunction_is_valid = true;
696 const int root_trail_index = trail_.Index();
697 const int root_integer_trail_index = integer_trail_->Index();
698 for (const Literal& lit : conjunction) {
699 if (assignment_.LiteralIsAssigned(lit)) {
700 if (assignment_.LiteralIsTrue(lit)) continue;
701 conjunction_is_valid = false;
702 break;
703 }
704 const int decision_level_before_enqueue =
705 sat_solver_->CurrentDecisionLevel();
706 sat_solver_->EnqueueDecisionAndBackjumpOnConflict(lit);
707 sat_solver_->AdvanceDeterministicTime(time_limit_);
708 const int decision_level_after_enqueue =
709 sat_solver_->CurrentDecisionLevel();
710 ++num_decisions_;
711
712 if (sat_solver_->ModelIsUnsat()) return false;
713 // If the literal has been pushed without any conflict, the level should
714 // have been increased.
715 if (decision_level_after_enqueue <= decision_level_before_enqueue) {
716 conjunction_is_valid = false;
717 break;
718 }
719 // TODO(user): Can we use the callback_?
720 }
721 if (conjunction_is_valid) {
722 ++num_valid_conjunctions;
723 } else {
724 continue;
725 }
726
727 // Process propagated literals.
728 new_propagated_literals_.clear();
729 for (int i = root_trail_index; i < trail_.Index(); ++i) {
730 const LiteralIndex literal_index = trail_[i].Index();
731 if (num_valid_conjunctions == 1 ||
732 always_propagated_literals_.contains(literal_index)) {
733 new_propagated_literals_.insert(literal_index);
734 }
735 }
736 std::swap(new_propagated_literals_, always_propagated_literals_);
737
738 // Process propagated integer bounds.
739 new_integer_bounds_.clear();
740 integer_trail_->AppendNewBoundsFrom(root_integer_trail_index,
741 &new_integer_bounds_);
742 new_propagated_bounds_.clear();
743 for (const IntegerLiteral entry : new_integer_bounds_) {
744 const auto it = always_propagated_bounds_.find(entry.var);
745 if (num_valid_conjunctions == 1) { // First loop.
746 new_propagated_bounds_[entry.var] = entry.bound;
747 } else if (it != always_propagated_bounds_.end()) {
748 new_propagated_bounds_[entry.var] = std::min(entry.bound, it->second);
749 }
750 }
751 std::swap(new_propagated_bounds_, always_propagated_bounds_);
752 }
753
754 if (!sat_solver_->ResetToLevelZero()) return false;
755 // Fix literals implied by the dnf.
756 const int previous_num_literals_fixed = num_new_literals_fixed_;
757 if (lrat_proof_handler_ != nullptr) {
758 if (!FixProbedDnfLiterals(dnf, always_propagated_literals_, dnf_type,
759 dnf_clause_id, dnf_clause_literals)) {
760 return false;
761 }
762 } else {
763 for (const LiteralIndex literal_index : always_propagated_literals_) {
764 const Literal lit(literal_index);
765 if (assignment_.LiteralIsTrue(lit)) continue;
766 ++num_new_literals_fixed_;
767 if (!sat_solver_->AddUnitClause(lit)) return false;
768 }
769 }
770
771 // Fix integer bounds implied by the dnf.
772 int previous_num_integer_bounds = num_new_integer_bounds_;
773 for (const auto& [var, bound] : always_propagated_bounds_) {
774 if (bound > integer_trail_->LowerBound(var)) {
775 ++num_new_integer_bounds_;
776 if (!integer_trail_->Enqueue(IntegerLiteral::GreaterOrEqual(var, bound),
777 {}, {})) {
778 return false;
779 }
780 }
781 }
782
783 if (!sat_solver_->FinishPropagation()) return false;
784
785 if (num_new_integer_bounds_ > previous_num_integer_bounds ||
786 num_new_literals_fixed_ > previous_num_literals_fixed) {
787 VLOG(1) << "ProbeDnf(" << name << ", num_fixed_literals="
788 << num_new_literals_fixed_ - previous_num_literals_fixed
789 << ", num_pushed_integer_bounds="
790 << num_new_integer_bounds_ - previous_num_integer_bounds
791 << ", num_valid_conjunctions=" << num_valid_conjunctions << "/"
792 << dnf.size() << ")";
793 }
794
795 return true;
796}
797
798namespace {
799// Sets `implication` to the clause "conjunction => literal". Returns true if
800// `conjunction` does not contain `literal`. Otherwise ("conjunction => literal"
801// is a tautology), returns false and leaves `implication` in an undefined
802// state.
803bool GetConjunctionImpliesLiteralClause(absl::Span<const Literal> conjunction,
804 Literal literal,
805 std::vector<Literal>& implication) {
806 implication.clear();
807 for (const Literal lit : conjunction) {
808 if (lit == literal) return false;
809 if (lit.Negated() == literal) continue;
810 implication.push_back(lit.Negated());
811 }
812 implication.push_back(literal);
813 return true;
814}
815} // namespace
816
817bool Prober::FixProbedDnfLiterals(
818 absl::Span<const std::vector<Literal>> dnf,
819 const absl::btree_set<LiteralIndex>& propagated_literals, DnfType dnf_type,
820 ClauseId dnf_clause_id, absl::Span<const Literal> dnf_clause_literals) {
821 if (propagated_literals.empty()) return true;
822
823 // For each propagated literal (in propagated_literals order), and for each
824 // conjunction, the ID of a temporary LRAT clause "conjunction => propagated
825 // literal" (or kNoClauseId if "conjunction" contains "propagated_literal", if
826 // the clause has not been created yet, or has been deleted).
827 CompactVectorVector<int, ClauseId>& propagation_clause_ids =
828 tmp_dnf_clause_ids_;
829 propagation_clause_ids.clear();
830 propagation_clause_ids.reserve(propagated_literals.size() * dnf.size());
831 for (int i = 0; i < propagated_literals.size(); ++i) {
832 propagation_clause_ids.Add({});
833 for (int j = 0; j < dnf.size(); ++j) {
834 propagation_clause_ids.AppendToLastVector(kNoClauseId);
835 }
836 }
837 // Redo the loop that was done in ProbeDnf() to compute the LRAT proofs of the
838 // propagated literals. This allows computing proofs only for those literals
839 // (on the other hand, we need to redo the propagations). Another method might
840 // be to make copies of the trail (one per conjunction), but how to handle
841 // backjump on conflict in this case?.
842 for (int conjunction_index = 0; conjunction_index < dnf.size();
843 ++conjunction_index) {
844 absl::Span<const Literal> conjunction = dnf[conjunction_index];
845 // TODO(user): same comment as in ProbeDnf().
846 if (!sat_solver_->ResetToLevelZero()) return false;
847
848 // Enqueue the literals of `conjunction` one by one.
849 // The first literal of `conjunction` which is propagated to false, if any,
850 // and the ID of a temporary LRAT clause proving that the previous literals
851 // of `conjunction` imply this.
852 LiteralIndex first_false_literal = kNoLiteralIndex;
853 ClauseId first_false_literal_clause_id = kNoClauseId;
854 tmp_literals_.clear();
855 for (const Literal lit : conjunction) {
856 tmp_literals_.push_back(lit.Negated());
857 if (assignment_.LiteralIsAssigned(lit)) {
858 if (assignment_.LiteralIsTrue(lit)) continue;
859 first_false_literal = lit.Index();
860 first_false_literal_clause_id = clause_id_generator_->GetNextId();
861 tmp_clause_ids_.clear();
862 clause_manager_->AppendClauseIdsFixing({lit.Negated()},
863 &tmp_clause_ids_);
864 lrat_proof_handler_->AddInferredClause(first_false_literal_clause_id,
865 tmp_literals_, tmp_clause_ids_);
866 break;
867 }
868
869 // If enqueuing `lit` causes a conflict, the previous literals of
870 // `conjunction` imply not(lit). Use the learned conflict to prove that.
871 auto conflict_callback = [&](ClauseId conflict_id,
872 absl::Span<const Literal> conflict_clause) {
873 if (first_false_literal != kNoLiteralIndex) return;
874 first_false_literal = lit.Index();
875 first_false_literal_clause_id = clause_id_generator_->GetNextId();
876 tmp_clause_ids_.clear();
877 clause_manager_->AppendClauseIdsFixing(conflict_clause,
878 &tmp_clause_ids_);
879 tmp_clause_ids_.push_back(conflict_id);
880 lrat_proof_handler_->AddInferredClause(first_false_literal_clause_id,
881 tmp_literals_, tmp_clause_ids_);
882 };
883 sat_solver_->EnqueueDecisionAndBackjumpOnConflict(lit, conflict_callback);
884
885 if (sat_solver_->ModelIsUnsat()) return false;
886 if (first_false_literal != kNoLiteralIndex) break;
887 }
888
889 // Use the trail to compute the LRAT proofs that `conjunction` implies the
890 // propagated literals.
891 int i = 0;
892 for (const LiteralIndex literal_index : propagated_literals) {
893 const Literal propagated_lit(literal_index);
894 absl::Span<ClauseId> propagation_ids = propagation_clause_ids[i++];
895 // Create the clause "conjunction => propagated_lit".
896 if (!GetConjunctionImpliesLiteralClause(conjunction, propagated_lit,
897 tmp_literals_)) {
898 // The clause is a tautology.
899 continue;
900 }
901 // Compute its proof.
902 tmp_clause_ids_.clear();
903 if (first_false_literal_clause_id != kNoClauseId) {
904 // If some literals of `conjunction` imply that another one is false,
905 // the corresponding LRAT clause is sufficient to prove that
906 // `conjunction` is false and thus that "conjunction => propagated_lit".
907 tmp_clause_ids_.push_back(first_false_literal_clause_id);
908 } else {
909 // TODO(user): processing the propagated literals in trail order
910 // and reusing the previous proofs to compute new ones
911 // could reduce the algorithmic complexity here.
912 clause_manager_->AppendClauseIdsFixing({propagated_lit},
913 &tmp_clause_ids_);
914 }
915 // Add the inferred clause to the LratProofHandler.
916 const ClauseId clause_id = clause_id_generator_->GetNextId();
917 lrat_proof_handler_->AddInferredClause(clause_id, tmp_literals_,
918 tmp_clause_ids_);
919 propagation_ids[conjunction_index] = clause_id;
920 }
921 if (first_false_literal_clause_id != kNoClauseId) {
922 if (drat_enabled_) {
923 // DRAT needs the clause literals to delete a clause.
924 tmp_literals_.clear();
925 for (const Literal lit : conjunction) {
926 tmp_literals_.push_back(lit.Negated());
927 if (lit.Index() == first_false_literal) break;
928 }
929 lrat_proof_handler_->DeleteClause(first_false_literal_clause_id,
930 tmp_literals_);
931 } else {
932 lrat_proof_handler_->DeleteClause(first_false_literal_clause_id, {});
933 }
934 }
935 }
936
937 if (!sat_solver_->ResetToLevelZero()) return false;
938
939 // Fix literals implied by the dnf.
940 int i = 0;
941 for (const LiteralIndex literal_index : propagated_literals) {
942 const Literal propagated_lit(literal_index);
943 absl::Span<ClauseId> propagation_ids = propagation_clause_ids[i++];
944 if (assignment_.LiteralIsTrue(propagated_lit)) continue;
945
946 ++num_new_literals_fixed_;
947 switch (dnf_type) {
949 // `propagation_ids` contains the clauses "not(l_i) OR propagated_lit"
950 // for each literal l_i of the dnf. Together with the unit clauses for
951 // the already assigned literals of the original clause, and the clause
952 // itself, they prove that propagated_lit is true.
953 CHECK_NE(dnf_clause_id, kNoClauseId);
954 tmp_clause_ids_.clear();
955 for (const ClauseId clause_id : propagation_ids) {
956 if (clause_id == kNoClauseId) continue;
957 tmp_clause_ids_.push_back(clause_id);
958 }
959 for (const Literal lit : dnf_clause_literals) {
960 if (assignment_.LiteralIsAssigned(lit)) {
961 tmp_clause_ids_.push_back(trail_.GetUnitClauseId(lit.Variable()));
962 }
963 }
964 tmp_clause_ids_.push_back(dnf_clause_id);
965 if (!clause_manager_->InprocessingFixLiteral(propagated_lit,
966 tmp_clause_ids_)) {
967 return false;
968 }
969 break;
971 // `propagation_ids` contains the clauses "not(l_i) OR propagated_lit"
972 // (for each single literal conjunction), and "l1 OR ... OR ln OR
973 // propagated_lit", in this order. These are sufficient to prove that
974 // propagated_lit is true.
975 tmp_clause_ids_.clear();
976 for (const ClauseId clause_id : propagation_ids) {
977 if (clause_id == kNoClauseId) continue;
978 tmp_clause_ids_.push_back(clause_id);
979 }
980 if (!clause_manager_->InprocessingFixLiteral(propagated_lit,
981 tmp_clause_ids_)) {
982 return false;
983 }
984 break;
986 if (!FixLiteralImpliedByAnAtLeastOneCombinationDnf(dnf, propagation_ids,
987 propagated_lit)) {
988 return false;
989 }
990 break;
991 }
992 }
993
994 // Delete the temporary LRAT clauses.
995 i = 0;
996 for (const LiteralIndex literal_index : propagated_literals) {
997 const Literal propagated_lit(literal_index);
998 const absl::Span<ClauseId> propagation_ids = propagation_clause_ids[i++];
999 for (int j = 0; j < dnf.size(); ++j) {
1000 const ClauseId clause_id = propagation_ids[j];
1001 if (clause_id == kNoClauseId) continue;
1002 if (drat_enabled_) {
1003 // DRAT needs the clause literals to delete a clause.
1004 GetConjunctionImpliesLiteralClause(dnf[j], propagated_lit,
1005 tmp_literals_);
1006 lrat_proof_handler_->DeleteClause(clause_id, tmp_literals_);
1007 } else {
1008 lrat_proof_handler_->DeleteClause(clause_id, {});
1009 }
1010 }
1011 }
1012 return true;
1013}
1014
1015bool Prober::FixLiteralImpliedByAnAtLeastOneCombinationDnf(
1016 absl::Span<const std::vector<Literal>> conjunctions,
1017 absl::Span<ClauseId> clause_ids, Literal propagated_lit) {
1018 const int num_clauses = clause_ids.size();
1019 CHECK_EQ(conjunctions.size(), num_clauses);
1020 // Combine the clauses 2 by 2 repeatedly, to remove one literal from each
1021 // conjunction at each step, until we get the unit clause `propagated_lit`.
1022 // For instance, with 4 conjunctions:
1023 //
1024 // step1 step2
1025 // not(a) and not(b) => p ----> not(a) => p ----> p
1026 // not(a) and b => p _/ /
1027 // a and not(b) => p ----> a => p _/
1028 // a and b => p _/
1029 //
1030 // The combined clauses are stored in `clause_ids`, and replace the ones of
1031 // the previous step, which are deleted. At step i=0,1,..., each conjunction
1032 // has n-i remaining literals, and we combine the clauses at indices
1033 // (2*stride)k and (2*stride)k + stride, where stride = 2^i. This relies on
1034 // the conjunctions being sorted as described in kAtLeastOneCombination's
1035 // comment.
1036 int num_literals_per_conjunction = conjunctions[0].size();
1037 int stride = 1;
1038 while (true) {
1039 for (int i = 0; i < num_clauses; i += 2 * stride) {
1040 // The two clauses "... AND not(b) => propagated_lit" and "... AND b =>
1041 // propagated_lit" prove that "... => propagated_lit".
1042 tmp_clause_ids_.clear();
1043 // Tautologies have no clause ID.
1044 if (clause_ids[i] != kNoClauseId) {
1045 tmp_clause_ids_.push_back(clause_ids[i]);
1046 }
1047 if (clause_ids[i + stride] != kNoClauseId) {
1048 tmp_clause_ids_.push_back(clause_ids[i + stride]);
1049 }
1050 if (tmp_clause_ids_.empty()) continue;
1051 const ClauseId new_clause_id = clause_id_generator_->GetNextId();
1052 GetConjunctionImpliesLiteralClause(
1053 absl::MakeConstSpan(conjunctions[i])
1054 .subspan(0, num_literals_per_conjunction - 1),
1055 propagated_lit, tmp_literals_);
1056 lrat_proof_handler_->AddInferredClause(new_clause_id, tmp_literals_,
1057 tmp_clause_ids_);
1058 // Delete the clauses used to derive the new one.
1059 for (const int index : {i, i + stride}) {
1060 if (clause_ids[index] == kNoClauseId) continue;
1061 if (drat_enabled_) {
1062 // DRAT needs the clause literals to delete a clause.
1063 GetConjunctionImpliesLiteralClause(
1064 absl::MakeConstSpan(conjunctions[index])
1065 .subspan(0, num_literals_per_conjunction),
1066 propagated_lit, tmp_literals_);
1067 lrat_proof_handler_->DeleteClause(clause_ids[index], tmp_literals_);
1068 } else {
1069 lrat_proof_handler_->DeleteClause(clause_ids[index], {});
1070 }
1071 clause_ids[index] = kNoClauseId;
1072 }
1073 if (num_literals_per_conjunction == 1) {
1074 return clause_manager_->InprocessingAddUnitClause(new_clause_id,
1075 propagated_lit);
1076 }
1077 clause_ids[i] = new_clause_id;
1078 }
1079 num_literals_per_conjunction--;
1080 stride *= 2;
1081 }
1082}
1083
1084bool LookForTrivialSatSolution(double deterministic_time_limit, Model* model,
1085 SolverLogger* logger) {
1086 WallTimer wall_timer;
1087 wall_timer.Start();
1088
1089 // Hack to not have empty logger.
1090 if (logger == nullptr) logger = model->GetOrCreate<SolverLogger>();
1091
1092 // Reset the solver in case it was already used.
1093 auto* sat_solver = model->GetOrCreate<SatSolver>();
1094 if (!sat_solver->ResetToLevelZero()) return false;
1095
1096 auto* time_limit = model->GetOrCreate<TimeLimit>();
1097 const int initial_num_fixed = sat_solver->LiteralTrail().Index();
1098
1099 // Note that this code do not care about the non-Boolean part and just try to
1100 // assign the existing Booleans.
1101 SatParameters initial_params = *model->GetOrCreate<SatParameters>();
1102 SatParameters new_params = initial_params;
1103 new_params.set_log_search_progress(false);
1104 new_params.set_max_number_of_conflicts(1);
1105 new_params.set_max_deterministic_time(deterministic_time_limit);
1106
1107 double elapsed_dtime = 0.0;
1108
1109 // We need to keep a copy of the time limit to restore it later since we will
1110 // reset it by calling Model::SetParameters().
1111 TimeLimit original_time_limit;
1112 original_time_limit.MergeWithGlobalTimeLimit(model->GetOrCreate<TimeLimit>());
1113
1114 const int num_times = 1000;
1115 bool limit_reached = false;
1116 auto* random = model->GetOrCreate<ModelRandomGenerator>();
1117 for (int i = 0; i < num_times; ++i) {
1118 if (time_limit->LimitReached() ||
1119 elapsed_dtime > deterministic_time_limit) {
1120 limit_reached = true;
1121 break;
1122 }
1123
1124 // SetParameters() reset the deterministic time to zero inside time_limit.
1125 sat_solver->SetParameters(new_params);
1126 time_limit->MergeWithGlobalTimeLimit(&original_time_limit);
1127 sat_solver->ResetDecisionHeuristic();
1128 const SatSolver::Status result = sat_solver->SolveWithTimeLimit(time_limit);
1129 elapsed_dtime += time_limit->GetElapsedDeterministicTime();
1130
1131 if (result == SatSolver::FEASIBLE) {
1132 SOLVER_LOG(logger, "Trivial exploration found feasible solution!");
1133 time_limit->AdvanceDeterministicTime(elapsed_dtime);
1134 return true;
1135 }
1136
1137 if (!sat_solver->ResetToLevelZero()) {
1138 SOLVER_LOG(logger, "UNSAT during trivial exploration heuristic.");
1139 time_limit->AdvanceDeterministicTime(elapsed_dtime);
1140 return false;
1141 }
1142
1143 // We randomize at the end so that the default params is executed
1144 // at least once.
1145 RandomizeDecisionHeuristic(*random, &new_params);
1146 new_params.set_random_seed(i);
1147 new_params.set_max_deterministic_time(deterministic_time_limit -
1148 elapsed_dtime);
1149 }
1150
1151 // Restore the initial parameters.
1152 sat_solver->SetParameters(initial_params);
1153 sat_solver->ResetDecisionHeuristic();
1154 time_limit->MergeWithGlobalTimeLimit(&original_time_limit);
1155 time_limit->AdvanceDeterministicTime(elapsed_dtime);
1156 if (!sat_solver->ResetToLevelZero()) return false;
1157
1158 if (logger->LoggingIsEnabled()) {
1159 const int num_fixed = sat_solver->LiteralTrail().Index();
1160 const int num_newly_fixed = num_fixed - initial_num_fixed;
1161 const int num_variables = sat_solver->NumVariables();
1162 SOLVER_LOG(logger, "[Random exploration]", " num_fixed: +",
1163 FormatCounter(num_newly_fixed), " (", FormatCounter(num_fixed),
1164 "/", FormatCounter(num_variables), ")",
1165 " dtime: ", elapsed_dtime, "/", deterministic_time_limit,
1166 " wtime: ", wall_timer.Get(),
1167 (limit_reached ? " (Aborted)" : ""));
1168 }
1169 return sat_solver->FinishPropagation();
1170}
1171
1173 : sat_solver_(model->GetOrCreate<SatSolver>()),
1174 implication_graph_(model->GetOrCreate<BinaryImplicationGraph>()),
1175 time_limit_(model->GetOrCreate<TimeLimit>()),
1176 trail_(*model->GetOrCreate<Trail>()),
1177 assignment_(trail_.Assignment()),
1178 clause_manager_(model->GetOrCreate<ClauseManager>()),
1179 clause_id_generator_(model->GetOrCreate<ClauseIdGenerator>()),
1180 lrat_proof_handler_(model->Mutable<LratProofHandler>()),
1181 binary_propagator_id_(implication_graph_->PropagatorId()),
1182 clause_propagator_id_(clause_manager_->PropagatorId()) {}
1183
1184// TODO(user): This might be broken if backtrack() propagates and go further
1185// back. Investigate and fix any issue.
1187 WallTimer wall_timer;
1188 wall_timer.Start();
1189
1190 options.log_info |= VLOG_IS_ON(1);
1191
1192 num_variables_ = sat_solver_->NumVariables();
1193 to_fix_.clear();
1194 to_fix_unit_id_.clear();
1195 num_probed_ = 0;
1196 num_explicit_fix_ = 0;
1197 num_conflicts_ = 0;
1198 num_new_binary_ = 0;
1199 num_subsumed_ = 0;
1200 num_lrat_clauses_ = 0;
1201 num_lrat_proof_clauses_ = 0;
1202
1203 // Reset the solver in case it was already used.
1204 if (!sat_solver_->ResetToLevelZero()) return false;
1205
1206 // When called from Inprocessing, the implication graph should already be a
1207 // DAG, so these two calls should return right away. But we do need them to
1208 // get the topological order if this is used in isolation.
1209 if (!implication_graph_->DetectEquivalences()) return false;
1210 if (!sat_solver_->FinishPropagation()) return false;
1211
1212 const int initial_num_fixed = sat_solver_->LiteralTrail().Index();
1213 const double initial_deterministic_time =
1214 time_limit_->GetElapsedDeterministicTime();
1215 const double limit = initial_deterministic_time + options.deterministic_limit;
1216
1217 processed_.ClearAndResize(LiteralIndex(2 * num_variables_));
1218
1219 queue_.clear();
1220 starts_.clear();
1221 if (!options.use_queue) {
1222 starts_.resize(2 * num_variables_, 0);
1223 }
1224
1225 // Depending on the options, we do not use the same order.
1226 // With tree look, it is better to start with "leaf" first since we try
1227 // to reuse propagation as much as possible. This is also interesting to
1228 // do when extracting binary clauses since we will need to propagate
1229 // everyone anyway, and this should result in less clauses that can be
1230 // removed later by transitive reduction.
1231 //
1232 // However, without tree-look and without the need to extract all binary
1233 // clauses, it is better to just probe the root of the binary implication
1234 // graph. This is exactly what happen when we probe using the topological
1235 // order.
1236 probing_order_ = implication_graph_->ReverseTopologicalOrder();
1237 if (!options.use_tree_look && !options.extract_binary_clauses) {
1238 std::reverse(probing_order_.begin(), probing_order_.end());
1239 }
1240 order_index_ = 0;
1241
1242 // We only use this for the queue version.
1243 position_in_order_.clear();
1244 if (options.use_queue) {
1245 position_in_order_.assign(2 * num_variables_, -1);
1246 for (int i = 0; i < probing_order_.size(); ++i) {
1247 position_in_order_[probing_order_[i]] = i;
1248 }
1249 }
1250
1251 binary_clause_extracted_.assign(trail_.Index(), false);
1252 trail_implication_clauses_.assign(trail_.Index(), {kNoClauseId, false});
1253
1254 while (!time_limit_->LimitReached() &&
1255 time_limit_->GetElapsedDeterministicTime() <= limit) {
1256 // We only enqueue literal at level zero if we don't use "tree look".
1257 if (!options.use_tree_look) {
1258 if (!sat_solver_->BacktrackAndPropagateReimplications(0)) return false;
1259 DeleteTemporaryLratImplicationsAfterBacktrack();
1260 }
1261
1262 // Probing works by taking a series of decisions, and by analyzing what
1263 // they propagate. For efficiency, we only take a new decision d' if it
1264 // directly implies the last one d. By doing this we know that d' directly
1265 // or indirectly implies all the previous decisions, which then propagate
1266 // all the literals on the trail up to and excluding d'. The first step is
1267 // to find the next_decision d', which can be done in different ways
1268 // depending on the options.
1269 LiteralIndex next_decision = kNoLiteralIndex;
1270 if (sat_solver_->CurrentDecisionLevel() > 0 && options.use_queue) {
1271 if (!ComputeNextDecisionInOrder(next_decision)) return false;
1272 }
1273 if (sat_solver_->CurrentDecisionLevel() > 0 &&
1274 next_decision == kNoLiteralIndex) {
1275 if (!GetNextDecisionInNoParticularOrder(next_decision)) return false;
1276 if (next_decision == kNoLiteralIndex) continue;
1277 }
1278 if (sat_solver_->CurrentDecisionLevel() == 0) {
1279 if (!GetFirstDecision(next_decision)) return false;
1280 // The pass is finished.
1281 if (next_decision == kNoLiteralIndex) break;
1282 }
1283
1284 // We now have a next decision, enqueue it and propagate until fix point.
1285 int first_new_trail_index;
1286 if (!EnqueueDecisionAndBackjumpOnConflict(next_decision, options.use_queue,
1287 first_new_trail_index)) {
1288 return false;
1289 }
1290
1291 // Inspect the newly propagated literals. Depending on the options, try to
1292 // extract binary clauses via hyper binary resolution and/or mark the
1293 // literals on the trail so that they do not need to be probed later.
1294 const int new_level = sat_solver_->CurrentDecisionLevel();
1295 if (new_level == 0) continue;
1296 const Literal last_decision = trail_.Decisions()[new_level - 1].literal;
1297 binary_clauses_to_extract_.clear();
1298 for (int i = first_new_trail_index; i < trail_.Index(); ++i) {
1299 const Literal l = trail_[i];
1300 if (l == last_decision) continue;
1301
1302 // This part of the trail is new, so we didn't extract any binary clause
1303 // about it yet.
1304 DCHECK(!binary_clause_extracted_[i]);
1305
1306 if (options.subsume_with_binary_clause) {
1307 MaybeSubsumeWithBinaryClause(last_decision, l);
1308 }
1309 if (options.extract_binary_clauses) {
1310 // Note that we don't want to extract binary clauses that are already
1311 // present in some form in the binary implication graph. Thus we skip
1312 // anything that was propagated by it.
1313 //
1314 // Because the BinaryImplicationGraph has the highest priority of all
1315 // propagators, this should cover most of the cases.
1316 //
1317 // Note(user): This is not 100% true, since when we launch the clause
1318 // propagation for one literal we do finish it before calling again
1319 // the binary propagation.
1320 if (trail_.AssignmentType(l.Variable()) != binary_propagator_id_) {
1321 if (ShouldExtractImplication(l)) {
1322 binary_clauses_to_extract_.push_back(l);
1323 }
1324 }
1325 } else {
1326 // If we don't extract binary, we don't need to explore any of
1327 // these literals until more variables are fixed.
1328 processed_.Set(l.Index());
1329 }
1330 }
1331 if (!binary_clauses_to_extract_.empty()) {
1332 ExtractImplications(last_decision, binary_clauses_to_extract_);
1333 }
1334
1335 if (options.subsume_with_binary_clause) {
1336 SubsumeWithBinaryClauseUsingBlockingLiteral(last_decision);
1337 }
1338 }
1339
1340 if (!sat_solver_->ResetToLevelZero()) return false;
1341 if (!ProcessLiteralsToFix()) return false;
1342 DeleteTemporaryLratImplicationsAfterBacktrack();
1343 clause_manager_->CleanUpWatchers();
1344
1345 // Display stats.
1346 const int num_fixed = sat_solver_->LiteralTrail().Index();
1347 const int num_newly_fixed = num_fixed - initial_num_fixed;
1348 const double time_diff =
1349 time_limit_->GetElapsedDeterministicTime() - initial_deterministic_time;
1350 const bool limit_reached = time_limit_->LimitReached() ||
1351 time_limit_->GetElapsedDeterministicTime() > limit;
1352 LOG_IF(INFO, options.log_info)
1353 << "Probing. "
1354 << " num_probed: " << num_probed_ << "/" << probing_order_.size()
1355 << " num_fixed: +" << num_newly_fixed << " (" << num_fixed << "/"
1356 << num_variables_ << ")"
1357 << " explicit_fix:" << num_explicit_fix_
1358 << " num_conflicts:" << num_conflicts_
1359 << " new_binary_clauses: " << num_new_binary_
1360 << " num_lrat_clauses: " << num_lrat_clauses_
1361 << " num_lrat_proof_clauses: " << num_lrat_proof_clauses_
1362 << " subsumed: " << num_subsumed_ << " dtime: " << time_diff
1363 << " wtime: " << wall_timer.Get() << (limit_reached ? " (Aborted)" : "");
1364 return sat_solver_->FinishPropagation();
1365}
1366
1367bool FailedLiteralProbing::SkipCandidate(Literal last_decision,
1368 Literal candidate) {
1369 if (processed_[candidate]) return true;
1370 if (implication_graph_->IsRedundant(candidate)) return true;
1371 if (assignment_.LiteralIsAssigned(candidate)) {
1372 // candidate => last_decision => all previous decisions, which then
1373 // propagate not(candidate). Hence candidate must be false.
1374 if (assignment_.LiteralIsFalse(candidate)) {
1375 AddFailedLiteralToFix(candidate);
1376 } else {
1377 // Here we have candidate => last_decision => candidate.
1378 // So we have an equivalence.
1379 //
1380 // If we extract all binary clauses, this shall be seen by
1381 // implication_graph_.DetectEquivalences(), but if not, we want to extract
1382 // the last_decision => candidate implication so it can be found.
1383 MaybeExtractImplication(last_decision, candidate);
1384 }
1385 return true;
1386 }
1387 return false;
1388}
1389
1390// Sets `next_decision` to the unassigned literal which implies the last
1391// decision and which comes first in the probing order (which itself can be
1392// the topological order of the implication graph, or the reverse).
1393bool FailedLiteralProbing::ComputeNextDecisionInOrder(
1394 LiteralIndex& next_decision) {
1395 // TODO(user): Instead of minimizing index in topo order (which might be
1396 // nice for binary extraction), we could try to maximize reusability in
1397 // some way.
1398 const Literal last_decision =
1399 trail_.Decisions()[sat_solver_->CurrentDecisionLevel() - 1].literal;
1400 // If l => last_decision, then not(last_decision) => not(l). We can thus
1401 // find the candidates for the next decision by looking at all the
1402 // implications of not(last_decision).
1403 const absl::Span<const Literal> list =
1404 implication_graph_->Implications(last_decision.Negated());
1405 const int saved_queue_size = queue_.size();
1406 for (const Literal l : list) {
1407 const Literal candidate = l.Negated();
1408 if (position_in_order_[candidate] == -1) continue;
1409 if (SkipCandidate(last_decision, candidate)) continue;
1410 queue_.push_back({candidate.Index(), -position_in_order_[candidate]});
1411 }
1412 // Sort all the candidates.
1413 std::sort(queue_.begin() + saved_queue_size, queue_.end());
1414
1415 // Set next_decision to the first unassigned candidate.
1416 while (!queue_.empty()) {
1417 const LiteralIndex index = queue_.back().literal_index;
1418 queue_.pop_back();
1419 if (index == kNoLiteralIndex) {
1420 // This is a backtrack marker, go back one level.
1421 CHECK_GT(sat_solver_->CurrentDecisionLevel(), 0);
1422 if (!sat_solver_->BacktrackAndPropagateReimplications(
1423 sat_solver_->CurrentDecisionLevel() - 1)) {
1424 return false;
1425 }
1426 DeleteTemporaryLratImplicationsAfterBacktrack();
1427 continue;
1428 }
1429 const Literal candidate(index);
1430 if (SkipCandidate(last_decision, candidate)) continue;
1431 next_decision = candidate.Index();
1432 break;
1433 }
1434 return true;
1435}
1436
1437// Sets `next_decision` to the first unassigned literal we find which implies
1438// the last decision, in no particular order.
1439bool FailedLiteralProbing::GetNextDecisionInNoParticularOrder(
1440 LiteralIndex& next_decision) {
1441 const int level = sat_solver_->CurrentDecisionLevel();
1442 const Literal last_decision = trail_.Decisions()[level - 1].literal;
1443 const absl::Span<const Literal> list =
1444 implication_graph_->Implications(last_decision.Negated());
1445
1446 // If l => last_decision, then not(last_decision) => not(l). We can thus
1447 // find the candidates for the next decision by looking at all the
1448 // implications of not(last_decision).
1449 int j = starts_[last_decision.NegatedIndex()];
1450 for (int i = 0; i < list.size(); ++i, ++j) {
1451 j %= list.size();
1452 const Literal candidate = Literal(list[j]).Negated();
1453 if (SkipCandidate(last_decision, candidate)) continue;
1454 next_decision = candidate.Index();
1455 break;
1456 }
1457 starts_[last_decision.NegatedIndex()] = j;
1458 if (next_decision == kNoLiteralIndex) {
1459 if (!sat_solver_->BacktrackAndPropagateReimplications(level - 1)) {
1460 return false;
1461 }
1462 DeleteTemporaryLratImplicationsAfterBacktrack();
1463 }
1464 return true;
1465}
1466
1467// Sets `next_decision` to the first unassigned literal in probing_order (if
1468// there is no last decision we can use any literal as first decision).
1469bool FailedLiteralProbing::GetFirstDecision(LiteralIndex& next_decision) {
1470 // Fix any delayed fixed literal.
1471 if (!ProcessLiteralsToFix()) return false;
1472
1473 // Probe an unexplored node.
1474 for (; order_index_ < probing_order_.size(); ++order_index_) {
1475 const Literal candidate(probing_order_[order_index_]);
1476
1477 // Note that here this is a bit different than SkipCandidate().
1478 if (processed_[candidate]) continue;
1479 if (assignment_.LiteralIsAssigned(candidate)) continue;
1480 next_decision = candidate.Index();
1481 break;
1482 }
1483 return true;
1484}
1485
1486bool FailedLiteralProbing::EnqueueDecisionAndBackjumpOnConflict(
1487 LiteralIndex next_decision, bool use_queue, int& first_new_trail_index) {
1488 ++num_probed_;
1489 processed_.Set(next_decision);
1490 CHECK_NE(next_decision, kNoLiteralIndex);
1491 queue_.push_back({kNoLiteralIndex, 0}); // Backtrack marker.
1492 const int level = sat_solver_->CurrentDecisionLevel();
1493
1494 // The unit clause ID that fixes next_decision to false, if it causes a
1495 // conflict.
1496 ClauseId fixed_decision_unit_id = kNoClauseId;
1497 auto conflict_callback = [&](ClauseId conflict_id,
1498 absl::Span<const Literal> conflict_clause) {
1499 DeleteTemporaryLratImplicationsAfterBacktrack();
1500 if (fixed_decision_unit_id != kNoClauseId) return;
1501 tmp_clause_ids_.clear();
1502 clause_manager_->AppendClauseIdsFixing(conflict_clause, &tmp_clause_ids_,
1503 next_decision);
1504 tmp_clause_ids_.push_back(conflict_id);
1505 fixed_decision_unit_id = clause_id_generator_->GetNextId();
1506 lrat_proof_handler_->AddInferredClause(fixed_decision_unit_id,
1507 {Literal(next_decision).Negated()},
1508 tmp_clause_ids_);
1509 num_lrat_clauses_++;
1510 num_lrat_proof_clauses_ += tmp_clause_ids_.size();
1511 };
1512 first_new_trail_index = sat_solver_->EnqueueDecisionAndBackjumpOnConflict(
1513 Literal(next_decision),
1514 lrat_proof_handler_ != nullptr
1515 ? conflict_callback
1516 : std::optional<SatSolver::ConflictCallback>());
1517
1518 if (first_new_trail_index == kUnsatTrailIndex) return false;
1519 binary_clause_extracted_.resize(first_new_trail_index);
1520 binary_clause_extracted_.resize(trail_.Index(), false);
1521 trail_implication_clauses_.resize(first_new_trail_index);
1522 trail_implication_clauses_.resize(trail_.Index(), {kNoClauseId, false});
1523
1524 // This is tricky, depending on the parameters, and for integer problem,
1525 // EnqueueDecisionAndBackjumpOnConflict() might create new Booleans.
1526 if (sat_solver_->NumVariables() > num_variables_) {
1527 num_variables_ = sat_solver_->NumVariables();
1528 processed_.Resize(LiteralIndex(2 * num_variables_));
1529 if (!use_queue) {
1530 starts_.resize(2 * num_variables_, 0);
1531 } else {
1532 position_in_order_.resize(2 * num_variables_, -1);
1533 }
1534 }
1535
1536 const int new_level = sat_solver_->CurrentDecisionLevel();
1537 sat_solver_->AdvanceDeterministicTime(time_limit_);
1538 if (sat_solver_->ModelIsUnsat()) return false;
1539 if (new_level <= level) {
1540 ++num_conflicts_;
1541
1542 // Sync the queue with the new level.
1543 if (use_queue) {
1544 if (new_level == 0) {
1545 queue_.clear();
1546 } else {
1547 int queue_level = level + 1;
1548 while (queue_level > new_level) {
1549 CHECK(!queue_.empty());
1550 if (queue_.back().literal_index == kNoLiteralIndex) --queue_level;
1551 queue_.pop_back();
1552 }
1553 }
1554 }
1555
1556 // Fix `next_decision` to `false` if not already done.
1557 //
1558 // Even if we fixed something at level zero, next_decision might not be
1559 // fixed! But we can fix it. It can happen because when we propagate
1560 // with clauses, we might have `a => b` but not `not(b) => not(a)`. Like
1561 // `a => b` and clause `(not(a), not(b), c)`, propagating `a` will set
1562 // `c`, but propagating `not(c)` will not do anything.
1563 //
1564 // We "delay" the fixing if we are not at level zero so that we can
1565 // still reuse the current propagation work via tree look.
1566 //
1567 // TODO(user): Can we be smarter here? Maybe we can still fix the
1568 // literal without going back to level zero by simply enqueuing it with
1569 // no reason? it will be backtracked over, but we will still lazily fix
1570 // it later.
1571 if (sat_solver_->CurrentDecisionLevel() != 0 ||
1572 !assignment_.LiteralIsFalse(Literal(next_decision))) {
1573 to_fix_.push_back(Literal(next_decision).Negated());
1574 if (lrat_proof_handler_ != nullptr) {
1575 to_fix_unit_id_.push_back(fixed_decision_unit_id);
1576 }
1577 }
1578 }
1579 return true;
1580}
1581
1582bool FailedLiteralProbing::ShouldExtractImplication(const Literal l) {
1583 const auto& info = trail_.Info(l.Variable());
1584
1585 if (binary_clause_extracted_[info.trail_index]) return false;
1586 binary_clause_extracted_[info.trail_index] = true;
1587
1588 // If the variable was true at level zero, this is not necessary.
1589 return info.level > 0;
1590}
1591
1592void FailedLiteralProbing::ExtractImplication(const Literal last_decision,
1593 const Literal l, bool lrat_only) {
1594 const auto& info = trail_.Info(l.Variable());
1595
1596 // TODO(user): Think about trying to extract clause that will not
1597 // get removed by transitive reduction later. If we can both extract
1598 // a => c and b => c , ideally we don't want to extract a => c first
1599 // if we already know that a => b.
1600 //
1601 // TODO(user): Similar to previous point, we could find the LCA
1602 // of all literals in the reason for this propagation. And use this
1603 // as a reason for later hyber binary resolution. Like we do when
1604 // this clause subsumes the reason.
1605 DCHECK(assignment_.LiteralIsTrue(l));
1606 CHECK_NE(l.Variable(), last_decision.Variable());
1607
1608 // We should never probe a redundant literal.
1609 //
1610 // TODO(user): We should be able to enforce that l is non-redundant either if
1611 // we made sure the clause database is cleaned up before FailedLiteralProbing
1612 // is called. This should maybe simplify the ChangeReason() handling.
1613 DCHECK(!implication_graph_->IsRedundant(last_decision));
1614
1615 ClauseId clause_id = kNoClauseId;
1616 if (lrat_proof_handler_ != nullptr) {
1617 clause_id = clause_id_generator_->GetNextId();
1618 tmp_clause_ids_.clear();
1619 clause_manager_->AppendClauseIdsFixing(
1620 {l}, &tmp_clause_ids_, last_decision,
1621 [&](int /*level*/, int trail_index) {
1622 return trail_implication_clauses_[trail_index].first;
1623 });
1624 // Cache this LRAT clause so that it can be reused for later proofs. Do this
1625 // only if `l` is propagated by the last decision, so that this cache entry
1626 // remains valid when we backtrack later decisions.
1627 if (info.level == sat_solver_->CurrentDecisionLevel()) {
1628 trail_implication_clauses_[info.trail_index] = {clause_id, lrat_only};
1629 }
1630 lrat_proof_handler_->AddInferredClause(
1631 clause_id, {last_decision.Negated(), l}, tmp_clause_ids_);
1632 num_lrat_clauses_++;
1633 num_lrat_proof_clauses_ += tmp_clause_ids_.size();
1634 }
1635 if (lrat_only) return;
1636
1637 // Each time we extract a binary clause, we change the reason in the trail.
1638 // This is important as it will allow us to remove clauses that are now
1639 // subsumed by this binary, even if it was a reason.
1640 ++num_new_binary_;
1641 CHECK(implication_graph_->AddBinaryClauseAndChangeReason(
1642 clause_id, l, last_decision.Negated()));
1643}
1644
1645void FailedLiteralProbing::MaybeExtractImplication(const Literal last_decision,
1646 const Literal l) {
1647 if (ShouldExtractImplication(l)) {
1648 ExtractImplication(last_decision, l);
1649 }
1650}
1651
1652void FailedLiteralProbing::ExtractImplications(
1653 Literal last_decision, absl::Span<const Literal> literals) {
1654 // 1. Find all the literals which appear in the expansion of the reasons of
1655 // all the `literals` and collect them in reverse trail order in
1656 // `tmp_marked_literals_`.
1657 // 1.a Put the `literals` in a heap.
1658 tmp_mark_.ClearAndResize(BooleanVariable(trail_.NumVariables()));
1659 tmp_heap_.clear();
1660 const VariablesAssignment& assignment = trail_.Assignment();
1661 for (const Literal lit : literals) {
1662 CHECK(assignment.LiteralIsAssigned(lit));
1663 tmp_mark_.Set(lit.Variable());
1664 tmp_heap_.push_back(trail_.Info(lit.Variable()).trail_index);
1665 }
1666 absl::c_make_heap(tmp_heap_);
1667
1668 // 1.b Expand the reasons of all the literals in the heap and add the reason
1669 // literals back in the heap. Collect the literals in the order they are
1670 // popped from the heap in `tmp_marked_literals_`.
1671 tmp_marked_literals_.clear();
1672 while (!tmp_heap_.empty()) {
1673 absl::c_pop_heap(tmp_heap_);
1674 const int trail_index = tmp_heap_.back();
1675 tmp_heap_.pop_back();
1676 const Literal marked_literal = trail_[trail_index];
1677 tmp_marked_literals_.push_back(marked_literal);
1678
1679 DCHECK_GT(trail_.Info(marked_literal.Variable()).level, 0);
1680 DCHECK_NE(trail_.AssignmentType(marked_literal.Variable()),
1682
1683 for (const Literal literal : trail_.Reason(marked_literal.Variable())) {
1684 const BooleanVariable var = literal.Variable();
1685 const AssignmentInfo& info = trail_.Info(var);
1686 if (info.level > 0 && !tmp_mark_[var] &&
1687 trail_.AssignmentType(var) != AssignmentType::kSearchDecision) {
1688 tmp_mark_.Set(var);
1689 tmp_heap_.push_back(info.trail_index);
1690 absl::c_push_heap(tmp_heap_);
1691 }
1692 }
1693 }
1694
1695 // 2. Add an LRAT implication "last_decision => l" for each literal l in
1696 // `tmp_marked_literals_`, in increasing trail index order. Thanks to the
1697 // cache in ExtractImplication(), the proof for each new implication has
1698 // the same size as its reason. Also add a "real" implication in the binary
1699 // implication graph if `l` is in `literals`.
1700 tmp_mark_.ClearAndResize(BooleanVariable(trail_.NumVariables()));
1701 for (const Literal lit : literals) {
1702 tmp_mark_.Set(lit.Variable());
1703 }
1704 for (int i = tmp_marked_literals_.size() - 1; i >= 0; --i) {
1705 const bool lrat_only = !tmp_mark_[tmp_marked_literals_[i].Variable()];
1706 ExtractImplication(last_decision, tmp_marked_literals_[i], lrat_only);
1707 }
1708}
1709
1710// If we can extract a binary clause that subsume the reason clause, we do add
1711// the binary and remove the subsumed clause.
1712//
1713// TODO(user): We could be slightly more generic and subsume some clauses that
1714// do not contain last_decision.Negated().
1715void FailedLiteralProbing::MaybeSubsumeWithBinaryClause(
1716 const Literal last_decision, const Literal l) {
1717 const int trail_index = trail_.Info(l.Variable()).trail_index;
1718 if (trail_.AssignmentType(l.Variable()) != clause_propagator_id_) return;
1719 SatClause* clause = clause_manager_->ReasonClause(trail_index);
1720
1721 bool subsumed = false;
1722 for (const Literal lit : trail_.Reason(l.Variable())) {
1723 if (lit == last_decision.Negated()) {
1724 subsumed = true;
1725 break;
1726 }
1727 }
1728 if (!subsumed) {
1729 // The clause is not subsumed but its lbd is 2 when last_decision is
1730 // propagated. This is a "glue" clause.
1731 clause_manager_->ChangeLbdIfBetter(clause, 2);
1732 return;
1733 }
1734
1735 // Since we will remove the clause, we need to make sure we do have the
1736 // implication in our repository.
1737 MaybeExtractImplication(last_decision, l);
1738
1739 int test = 0;
1740 for (const Literal lit :
1741 clause_manager_->ReasonClause(trail_index)->AsSpan()) {
1742 if (lit == l) ++test;
1743 if (lit == last_decision.Negated()) ++test;
1744 }
1745 CHECK_EQ(test, 2);
1746
1747 // Because of MaybeExtractImplication(), this shouldn't be a reason anymore.
1748 CHECK(!clause_manager_->ClauseIsUsedAsReason(clause));
1749 ++num_subsumed_;
1750 clause_manager_->LazyDelete(clause,
1752}
1753
1754// Inspect the watcher list for last_decision, If we have a blocking
1755// literal at true (implied by last decision), then we have subsumptions.
1756//
1757// The intuition behind this is that if a binary clause (a,b) subsumes a
1758// clause, and we watch a.Negated() for this clause with a blocking
1759// literal b, then this watch entry will never change because we always
1760// propagate binary clauses first and the blocking literal will always be
1761// true. So after many propagations, we hope to have such configuration
1762// which is quite cheap to test here.
1763void FailedLiteralProbing::SubsumeWithBinaryClauseUsingBlockingLiteral(
1764 const Literal last_decision) {
1765 for (const auto& w :
1766 clause_manager_->WatcherListOnFalse(last_decision.Negated())) {
1767 if (!assignment_.LiteralIsTrue(w.blocking_literal)) continue;
1768 if (w.clause->IsRemoved()) continue;
1769
1770 // This should be enough for proof correctness.
1771 if (clause_manager_->ClauseIsUsedAsReason(w.clause)) {
1772 MaybeExtractImplication(last_decision, w.blocking_literal);
1773
1774 // It should have been replaced by a binary clause now.
1775 CHECK(!clause_manager_->ClauseIsUsedAsReason(w.clause));
1776 }
1777
1778 ++num_subsumed_;
1779 clause_manager_->LazyDelete(w.clause,
1781 }
1782}
1783
1784// Adds 'not(literal)' to `to_fix_`, assuming that 'literal' directly implies
1785// the current decision, which itself implies all the previous decisions, with
1786// some of them propagating 'not(literal)'.
1787void FailedLiteralProbing::AddFailedLiteralToFix(const Literal literal) {
1788 // TODO(user): skip if literal.Negated() is already in to_fix? This does
1789 // not seem to happen often enough to be a problem.
1790 if (trail_.AssignmentLevel(literal.Negated()) == 0) return;
1791 to_fix_.push_back(literal.Negated());
1792 if (lrat_proof_handler_ == nullptr) return;
1793
1794 tmp_clause_ids_.clear();
1795 clause_manager_->AppendClauseIdsFixing({literal.Negated()}, &tmp_clause_ids_,
1796 literal);
1797 const ClauseId unit_id = clause_id_generator_->GetNextId();
1798 lrat_proof_handler_->AddInferredClause(unit_id, {literal.Negated()},
1799 tmp_clause_ids_);
1800 to_fix_unit_id_.push_back({unit_id});
1801 num_lrat_clauses_++;
1802 num_lrat_proof_clauses_ += tmp_clause_ids_.size();
1803}
1804
1805// Fixes all the literals in to_fix_, and finish propagation.
1806bool FailedLiteralProbing::ProcessLiteralsToFix() {
1807 for (int i = 0; i < to_fix_.size(); ++i) {
1808 const Literal literal = to_fix_[i];
1809 if (assignment_.LiteralIsTrue(literal)) continue;
1810 ++num_explicit_fix_;
1811 const ClauseId clause_id =
1812 lrat_proof_handler_ != nullptr ? to_fix_unit_id_[i] : kNoClauseId;
1813 if (!clause_manager_->InprocessingAddUnitClause(clause_id, literal)) {
1814 return false;
1815 }
1816 }
1817 to_fix_.clear();
1818 to_fix_unit_id_.clear();
1819 return sat_solver_->FinishPropagation();
1820}
1821
1822void FailedLiteralProbing::DeleteTemporaryLratImplicationsAfterBacktrack() {
1823 if (lrat_proof_handler_ == nullptr) return;
1824 for (int i = trail_.Index(); i < trail_implication_clauses_.size(); ++i) {
1825 auto [clause_id, is_temporary] = trail_implication_clauses_[i];
1826 if (is_temporary) {
1827 lrat_proof_handler_->DeleteClause(clause_id, {});
1828 }
1829 }
1830 trail_implication_clauses_.resize(trail_.Index(), {kNoClauseId, false});
1831}
1832
1833} // namespace sat
1834} // namespace operations_research
double Get() const
Definition timer.h:44
void Start()
Definition timer.h:30
void MergeWithGlobalTimeLimit(const TimeLimit *other)
Definition time_limit.h:514
ClauseId GetClauseId(Literal a, Literal b) const
Definition clause.h:672
ClauseId GetClauseId(const SatClause *clause) const
Definition clause.h:295
bool DoOneRound(ProbingOptions options)
Definition probing.cc:1186
LiteralIndex Index() const
Definition sat_base.h:92
BooleanVariable Variable() const
Definition sat_base.h:88
bool ProbeBooleanVariables(double deterministic_time_limit)
Definition probing.cc:284
bool ProbeOneVariable(BooleanVariable b)
Definition probing.cc:536
bool ProbeDnf(absl::string_view name, absl::Span< const std::vector< Literal > > dnf, DnfType type, const SatClause *dnf_clause=nullptr)
Definition probing.cc:653
absl::Span< const Literal > AsSpan() const
Definition clause.h:100
int EnqueueDecisionAndBackjumpOnConflict(Literal true_literal, std::optional< ConflictCallback > callback=std::nullopt)
TrailCopy(const Trail &trail, const BinaryImplicationGraph &implication_graph, const ClauseManager &clause_manager)
Definition probing.cc:62
void AppendClauseIdsFixing(absl::Span< const Literal > literals, std::vector< ClauseId > *clause_ids, LiteralIndex decision, absl::flat_hash_map< std::pair< Literal, Literal >, ClauseId > tmp_binary_clause_ids)
Definition probing.cc:121
bool VariableIsAssigned(BooleanVariable var) const
Definition sat_base.h:214
bool LiteralIsAssigned(Literal literal) const
Definition sat_base.h:209
bool LiteralIsFalse(Literal literal) const
Definition sat_base.h:203
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
void RandomizeDecisionHeuristic(absl::BitGenRef random, SatParameters *parameters)
Definition util.cc:90
const LiteralIndex kNoLiteralIndex(-1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
bool LookForTrivialSatSolution(double deterministic_time_limit, Model *model, SolverLogger *logger)
Definition probing.cc:1084
IntegerVariable PositiveVariable(IntegerVariable i)
constexpr ClauseId kNoClauseId(0)
bool VariableIsPositive(IntegerVariable i)
OR-Tools root namespace.
std::string FormatCounter(int64_t num)
Definition logging.cc:30
trees with all degrees equal w the current value of w
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
#define SOLVER_LOG(logger,...)
Definition logging.h:114