Google OR-Tools v9.12
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 <functional>
19#include <utility>
20#include <vector>
21
22#include "absl/container/btree_map.h"
23#include "absl/container/btree_set.h"
24#include "absl/container/inlined_vector.h"
25#include "absl/log/check.h"
26#include "absl/strings/string_view.h"
27#include "absl/types/span.h"
30#include "ortools/base/timer.h"
31#include "ortools/sat/clause.h"
33#include "ortools/sat/integer.h"
35#include "ortools/sat/model.h"
37#include "ortools/sat/sat_parameters.pb.h"
39#include "ortools/sat/util.h"
40#include "ortools/util/bitset.h"
45
46namespace operations_research {
47namespace sat {
48
50 : trail_(*model->GetOrCreate<Trail>()),
51 assignment_(model->GetOrCreate<SatSolver>()->Assignment()),
52 integer_trail_(model->GetOrCreate<IntegerTrail>()),
53 implied_bounds_(model->GetOrCreate<ImpliedBounds>()),
54 product_detector_(model->GetOrCreate<ProductDetector>()),
55 sat_solver_(model->GetOrCreate<SatSolver>()),
56 time_limit_(model->GetOrCreate<TimeLimit>()),
57 implication_graph_(model->GetOrCreate<BinaryImplicationGraph>()),
58 logger_(model->GetOrCreate<SolverLogger>()) {}
59
60bool Prober::ProbeBooleanVariables(const double deterministic_time_limit) {
61 const int num_variables = sat_solver_->NumVariables();
62 const VariablesAssignment& assignment = sat_solver_->Assignment();
63 std::vector<BooleanVariable> bool_vars;
64 for (BooleanVariable b(0); b < num_variables; ++b) {
65 if (assignment.VariableIsAssigned(b)) continue;
66 const Literal literal(b, true);
67 if (implication_graph_->RepresentativeOf(literal) != literal) {
68 continue;
69 }
70 bool_vars.push_back(b);
71 }
72 return ProbeBooleanVariables(deterministic_time_limit, bool_vars);
73}
74
75bool Prober::ProbeOneVariableInternal(BooleanVariable b) {
76 new_integer_bounds_.clear();
77 propagated_.SparseClearAll();
78 for (const Literal decision : {Literal(b, true), Literal(b, false)}) {
79 if (assignment_.LiteralIsAssigned(decision)) continue;
80
81 ++num_decisions_;
82 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
83 const int saved_index = trail_.Index();
84 sat_solver_->EnqueueDecisionAndBackjumpOnConflict(decision);
85 sat_solver_->AdvanceDeterministicTime(time_limit_);
86
87 if (sat_solver_->ModelIsUnsat()) return false;
88 if (sat_solver_->CurrentDecisionLevel() == 0) continue;
89 if (trail_.Index() > saved_index) {
90 if (callback_ != nullptr) callback_(decision);
91 }
92
93 if (!implied_bounds_->ProcessIntegerTrail(decision)) return false;
94 product_detector_->ProcessTrailAtLevelOne();
95 integer_trail_->AppendNewBounds(&new_integer_bounds_);
96 for (int i = saved_index + 1; i < trail_.Index(); ++i) {
97 const Literal l = trail_[i];
98
99 // We mark on the first run (b.IsPositive()) and check on the second.
100 if (decision.IsPositive()) {
101 propagated_.Set(l.Index());
102 } else {
103 if (propagated_[l]) {
104 to_fix_at_true_.push_back(l);
105 }
106 }
107
108 // Anything not propagated by the BinaryImplicationGraph is a "new"
109 // binary clause. This is because the BinaryImplicationGraph has the
110 // highest priority of all propagators.
111 if (trail_.AssignmentType(l.Variable()) !=
112 implication_graph_->PropagatorId()) {
113 new_binary_clauses_.push_back({decision.Negated(), l});
114 }
115 }
116
117 // Fix variable and add new binary clauses.
118 if (!sat_solver_->ResetToLevelZero()) return false;
119 for (const Literal l : to_fix_at_true_) {
120 if (!sat_solver_->AddUnitClause(l)) return false;
121 }
122 to_fix_at_true_.clear();
123 if (!sat_solver_->FinishPropagation()) return false;
124 num_new_binary_ += new_binary_clauses_.size();
125 for (auto binary : new_binary_clauses_) {
126 sat_solver_->AddBinaryClause(binary.first, binary.second);
127 }
128 new_binary_clauses_.clear();
129 if (!sat_solver_->FinishPropagation()) return false;
130 }
131
132 // We have at most two lower bounds for each variables (one for b==0 and one
133 // for b==1), so the min of the two is a valid level zero bound! More
134 // generally, the domain of a variable can be intersected with the union
135 // of the two propagated domains. This also allow to detect "holes".
136 //
137 // TODO(user): More generally, for any clauses (b or not(b) is one), we
138 // could probe all the literal inside, and for any integer variable, we can
139 // take the union of the propagated domain as a new domain.
140 //
141 // TODO(user): fix binary variable in the same way? It might not be as
142 // useful since probing on such variable will also fix it. But then we might
143 // abort probing early, so it might still be good.
144 std::sort(new_integer_bounds_.begin(), new_integer_bounds_.end(),
145 [](IntegerLiteral a, IntegerLiteral b) { return a.var < b.var; });
146
147 // This is used for the hole detection.
148 IntegerVariable prev_var = kNoIntegerVariable;
149 IntegerValue lb_max = kMinIntegerValue;
150 IntegerValue ub_min = kMaxIntegerValue;
151 new_integer_bounds_.push_back(IntegerLiteral()); // Sentinel.
152
153 const int limit = new_integer_bounds_.size();
154 for (int i = 0; i < limit; ++i) {
155 const IntegerVariable var = new_integer_bounds_[i].var;
156
157 // Hole detection.
158 if (i > 0 && PositiveVariable(var) != prev_var) {
159 if (ub_min + 1 < lb_max) {
160 // The variable cannot take value in (ub_min, lb_max) !
161 //
162 // TODO(user): do not create domain with a complexity that is too
163 // large?
164 const Domain old_domain =
165 integer_trail_->InitialVariableDomain(prev_var);
166 const Domain new_domain = old_domain.IntersectionWith(
167 Domain(ub_min.value() + 1, lb_max.value() - 1).Complement());
168 if (new_domain != old_domain) {
169 ++num_new_holes_;
170 if (!integer_trail_->UpdateInitialDomain(prev_var, new_domain)) {
171 return false;
172 }
173 }
174 }
175
176 // Reinitialize.
177 lb_max = kMinIntegerValue;
178 ub_min = kMaxIntegerValue;
179 }
180
181 prev_var = PositiveVariable(var);
182 if (VariableIsPositive(var)) {
183 lb_max = std::max(lb_max, new_integer_bounds_[i].bound);
184 } else {
185 ub_min = std::min(ub_min, -new_integer_bounds_[i].bound);
186 }
187
188 // Bound tightening.
189 if (i == 0 || new_integer_bounds_[i - 1].var != var) continue;
190 const IntegerValue new_bound = std::min(new_integer_bounds_[i - 1].bound,
191 new_integer_bounds_[i].bound);
192 if (new_bound > integer_trail_->LowerBound(var)) {
193 ++num_new_integer_bounds_;
194 if (!integer_trail_->Enqueue(
195 IntegerLiteral::GreaterOrEqual(var, new_bound), {}, {})) {
196 return false;
197 }
198 }
199 }
200
201 // We might have updated some integer domain, let's propagate.
202 return sat_solver_->FinishPropagation();
203}
204
205bool Prober::ProbeOneVariable(BooleanVariable b) {
206 // Resize the propagated sparse bitset.
207 const int num_variables = sat_solver_->NumVariables();
208 propagated_.ClearAndResize(LiteralIndex(2 * num_variables));
209
210 // Reset the solver in case it was already used.
211 if (!sat_solver_->ResetToLevelZero()) return false;
212
213 const int initial_num_fixed = sat_solver_->LiteralTrail().Index();
214 if (!ProbeOneVariableInternal(b)) return false;
215
216 // Statistics
217 const int num_fixed = sat_solver_->LiteralTrail().Index();
218 num_new_literals_fixed_ += num_fixed - initial_num_fixed;
219 return true;
220}
221
223 const double deterministic_time_limit,
224 absl::Span<const BooleanVariable> bool_vars) {
225 WallTimer wall_timer;
226 wall_timer.Start();
227
228 // Reset statistics.
229 num_decisions_ = 0;
230 num_new_binary_ = 0;
231 num_new_holes_ = 0;
232 num_new_integer_bounds_ = 0;
233 num_new_literals_fixed_ = 0;
234
235 // Resize the propagated sparse bitset.
236 const int num_variables = sat_solver_->NumVariables();
237 propagated_.ClearAndResize(LiteralIndex(2 * num_variables));
238
239 // Reset the solver in case it was already used.
240 if (!sat_solver_->ResetToLevelZero()) return false;
241
242 const int initial_num_fixed = sat_solver_->LiteralTrail().Index();
243 const double initial_deterministic_time =
244 time_limit_->GetElapsedDeterministicTime();
245 const double limit = initial_deterministic_time + deterministic_time_limit;
246
247 bool limit_reached = false;
248 int num_probed = 0;
249
250 for (const BooleanVariable b : bool_vars) {
251 const Literal literal(b, true);
252 if (implication_graph_->RepresentativeOf(literal) != literal) {
253 continue;
254 }
255
256 // TODO(user): Instead of an hard deterministic limit, we should probably
257 // use a lower one, but reset it each time we have found something useful.
258 if (time_limit_->LimitReached() ||
259 time_limit_->GetElapsedDeterministicTime() > limit) {
260 limit_reached = true;
261 break;
262 }
263
264 // Propagate b=1 and then b=0.
265 ++num_probed;
266 if (!ProbeOneVariableInternal(b)) {
267 return false;
268 }
269 }
270
271 // Update stats.
272 const int num_fixed = sat_solver_->LiteralTrail().Index();
273 num_new_literals_fixed_ = num_fixed - initial_num_fixed;
274
275 // Display stats.
276 if (logger_->LoggingIsEnabled()) {
277 const double time_diff =
278 time_limit_->GetElapsedDeterministicTime() - initial_deterministic_time;
279 SOLVER_LOG(logger_, "[Probing] deterministic_time: ", time_diff,
280 " (limit: ", deterministic_time_limit,
281 ") wall_time: ", wall_timer.Get(), " (",
282 (limit_reached ? "Aborted " : ""), num_probed, "/",
283 bool_vars.size(), ")");
284 if (num_new_literals_fixed_ > 0) {
285 SOLVER_LOG(logger_,
286 "[Probing] - new fixed Boolean: ", num_new_literals_fixed_,
287 " (", num_fixed, "/", sat_solver_->NumVariables(), ")");
288 }
289 if (num_new_holes_ > 0) {
290 SOLVER_LOG(logger_, "[Probing] - new integer holes: ", num_new_holes_);
291 }
292 if (num_new_integer_bounds_ > 0) {
293 SOLVER_LOG(logger_,
294 "[Probing] - new integer bounds: ", num_new_integer_bounds_);
295 }
296 if (num_new_binary_ > 0) {
297 SOLVER_LOG(logger_, "[Probing] - new binary clause: ", num_new_binary_);
298 }
299 }
300
301 return true;
302}
303
304bool Prober::ProbeDnf(absl::string_view name,
305 absl::Span<const std::vector<Literal>> dnf) {
306 if (dnf.size() <= 1) return true;
307
308 // Reset the solver in case it was already used.
309 if (!sat_solver_->ResetToLevelZero()) return false;
310
311 always_propagated_bounds_.clear();
312 always_propagated_literals_.clear();
313 int num_valid_conjunctions = 0;
314 for (const std::vector<Literal>& conjunction : dnf) {
315 if (!sat_solver_->ResetToLevelZero()) return false;
316 if (num_valid_conjunctions > 0 && always_propagated_bounds_.empty() &&
317 always_propagated_literals_.empty()) {
318 // We can exit safely as nothing will be propagated.
319 return true;
320 }
321
322 bool conjunction_is_valid = true;
323 int num_literals_enqueued = 0;
324 const int root_trail_index = trail_.Index();
325 const int root_integer_trail_index = integer_trail_->Index();
326 for (const Literal& lit : conjunction) {
327 if (assignment_.LiteralIsAssigned(lit)) {
328 if (assignment_.LiteralIsTrue(lit)) continue;
329 conjunction_is_valid = false;
330 break;
331 }
332 ++num_literals_enqueued;
333 const int decision_level_before_enqueue =
334 sat_solver_->CurrentDecisionLevel();
335 sat_solver_->EnqueueDecisionAndBackjumpOnConflict(lit);
336 sat_solver_->AdvanceDeterministicTime(time_limit_);
337 const int decision_level_after_enqueue =
338 sat_solver_->CurrentDecisionLevel();
339 ++num_decisions_;
340
341 if (sat_solver_->ModelIsUnsat()) return false;
342 // If the literal has been pushed without any conflict, the level should
343 // have been increased.
344 if (decision_level_after_enqueue <= decision_level_before_enqueue) {
345 conjunction_is_valid = false;
346 break;
347 }
348 // TODO(user): Can we use the callback_?
349 }
350 if (conjunction_is_valid) {
351 ++num_valid_conjunctions;
352 } else {
353 continue;
354 }
355
356 // Process propagated literals.
357 new_propagated_literals_.clear();
358 for (int i = root_trail_index; i < trail_.Index(); ++i) {
359 const LiteralIndex literal_index = trail_[i].Index();
360 if (num_valid_conjunctions == 1 ||
361 always_propagated_literals_.contains(literal_index)) {
362 new_propagated_literals_.insert(literal_index);
363 }
364 }
365 std::swap(new_propagated_literals_, always_propagated_literals_);
366
367 // Process propagated integer bounds.
368 new_integer_bounds_.clear();
369 integer_trail_->AppendNewBoundsFrom(root_integer_trail_index,
370 &new_integer_bounds_);
371 new_propagated_bounds_.clear();
372 for (const IntegerLiteral entry : new_integer_bounds_) {
373 const auto it = always_propagated_bounds_.find(entry.var);
374 if (num_valid_conjunctions == 1) { // First loop.
375 new_propagated_bounds_[entry.var] = entry.bound;
376 } else if (it != always_propagated_bounds_.end()) {
377 new_propagated_bounds_[entry.var] = std::min(entry.bound, it->second);
378 }
379 }
380 std::swap(new_propagated_bounds_, always_propagated_bounds_);
381 }
382
383 if (!sat_solver_->ResetToLevelZero()) return false;
384 // Fix literals implied by the dnf.
385 const int previous_num_literals_fixed = num_new_literals_fixed_;
386 for (const LiteralIndex literal_index : always_propagated_literals_) {
387 const Literal lit(literal_index);
388 if (assignment_.LiteralIsTrue(lit)) continue;
389 ++num_new_literals_fixed_;
390 if (!sat_solver_->AddUnitClause(lit)) return false;
391 }
392
393 // Fix integer bounds implied by the dnf.
394 int previous_num_integer_bounds = num_new_integer_bounds_;
395 for (const auto& [var, bound] : always_propagated_bounds_) {
396 if (bound > integer_trail_->LowerBound(var)) {
397 ++num_new_integer_bounds_;
398 if (!integer_trail_->Enqueue(IntegerLiteral::GreaterOrEqual(var, bound),
399 {}, {})) {
400 return false;
401 }
402 }
403 }
404
405 if (!sat_solver_->FinishPropagation()) return false;
406
407 if (num_new_integer_bounds_ > previous_num_integer_bounds ||
408 num_new_literals_fixed_ > previous_num_literals_fixed) {
409 VLOG(1) << "ProbeDnf(" << name << ", num_fixed_literals="
410 << num_new_literals_fixed_ - previous_num_literals_fixed
411 << ", num_pushed_integer_bounds="
412 << num_new_integer_bounds_ - previous_num_integer_bounds
413 << ", num_valid_conjunctions=" << num_valid_conjunctions << "/"
414 << dnf.size() << ")";
415 }
416
417 return true;
418}
419
420bool LookForTrivialSatSolution(double deterministic_time_limit, Model* model,
421 SolverLogger* logger) {
422 WallTimer wall_timer;
423 wall_timer.Start();
424
425 // Hack to not have empty logger.
426 if (logger == nullptr) logger = model->GetOrCreate<SolverLogger>();
427
428 // Reset the solver in case it was already used.
429 auto* sat_solver = model->GetOrCreate<SatSolver>();
430 if (!sat_solver->ResetToLevelZero()) return false;
431
432 auto* time_limit = model->GetOrCreate<TimeLimit>();
433 const int initial_num_fixed = sat_solver->LiteralTrail().Index();
434
435 // Note that this code do not care about the non-Boolean part and just try to
436 // assign the existing Booleans.
437 SatParameters initial_params = *model->GetOrCreate<SatParameters>();
438 SatParameters new_params = initial_params;
439 new_params.set_log_search_progress(false);
440 new_params.set_max_number_of_conflicts(1);
441 new_params.set_max_deterministic_time(deterministic_time_limit);
442
443 double elapsed_dtime = 0.0;
444
445 const int num_times = 1000;
446 bool limit_reached = false;
447 auto* random = model->GetOrCreate<ModelRandomGenerator>();
448 for (int i = 0; i < num_times; ++i) {
449 if (time_limit->LimitReached() ||
450 elapsed_dtime > deterministic_time_limit) {
451 limit_reached = true;
452 break;
453 }
454
455 // SetParameters() reset the deterministic time to zero inside time_limit.
456 sat_solver->SetParameters(new_params);
457 sat_solver->ResetDecisionHeuristic();
458 const SatSolver::Status result = sat_solver->SolveWithTimeLimit(time_limit);
459 elapsed_dtime += time_limit->GetElapsedDeterministicTime();
460
461 if (result == SatSolver::FEASIBLE) {
462 SOLVER_LOG(logger, "Trivial exploration found feasible solution!");
463 time_limit->AdvanceDeterministicTime(elapsed_dtime);
464 return true;
465 }
466
467 if (!sat_solver->ResetToLevelZero()) {
468 SOLVER_LOG(logger, "UNSAT during trivial exploration heuristic.");
469 time_limit->AdvanceDeterministicTime(elapsed_dtime);
470 return false;
471 }
472
473 // We randomize at the end so that the default params is executed
474 // at least once.
475 RandomizeDecisionHeuristic(*random, &new_params);
476 new_params.set_random_seed(i);
477 new_params.set_max_deterministic_time(deterministic_time_limit -
478 elapsed_dtime);
479 }
480
481 // Restore the initial parameters.
482 sat_solver->SetParameters(initial_params);
483 sat_solver->ResetDecisionHeuristic();
484 time_limit->AdvanceDeterministicTime(elapsed_dtime);
485 if (!sat_solver->ResetToLevelZero()) return false;
486
487 if (logger->LoggingIsEnabled()) {
488 const int num_fixed = sat_solver->LiteralTrail().Index();
489 const int num_newly_fixed = num_fixed - initial_num_fixed;
490 const int num_variables = sat_solver->NumVariables();
491 SOLVER_LOG(logger, "[Random exploration]", " num_fixed: +", num_newly_fixed,
492 " (", num_fixed, "/", num_variables, ")",
493 " dtime: ", elapsed_dtime, "/", deterministic_time_limit,
494 " wtime: ", wall_timer.Get(),
495 (limit_reached ? " (Aborted)" : ""));
496 }
497 return sat_solver->FinishPropagation();
498}
499
501 WallTimer wall_timer;
502 wall_timer.Start();
503 options.log_info |= VLOG_IS_ON(1);
504
505 // Reset the solver in case it was already used.
506 auto* sat_solver = model->GetOrCreate<SatSolver>();
507 if (!sat_solver->ResetToLevelZero()) return false;
508
509 // When called from Inprocessing, the implication graph should already be a
510 // DAG, so these two calls should return right away. But we do need them to
511 // get the topological order if this is used in isolation.
512 auto* implication_graph = model->GetOrCreate<BinaryImplicationGraph>();
513 if (!implication_graph->DetectEquivalences()) return false;
514 if (!sat_solver->FinishPropagation()) return false;
515
516 auto* time_limit = model->GetOrCreate<TimeLimit>();
517 const int initial_num_fixed = sat_solver->LiteralTrail().Index();
518 const double initial_deterministic_time =
519 time_limit->GetElapsedDeterministicTime();
520 const double limit = initial_deterministic_time + options.deterministic_limit;
521
522 const int num_variables = sat_solver->NumVariables();
523 SparseBitset<LiteralIndex> processed(LiteralIndex(2 * num_variables));
524
525 int64_t num_probed = 0;
526 int64_t num_explicit_fix = 0;
527 int64_t num_conflicts = 0;
528 int64_t num_new_binary = 0;
529 int64_t num_subsumed = 0;
530
531 const auto& trail = *(model->Get<Trail>());
532 const auto& assignment = trail.Assignment();
533 auto* clause_manager = model->GetOrCreate<ClauseManager>();
534 const int id = implication_graph->PropagatorId();
535 const int clause_id = clause_manager->PropagatorId();
536
537 // This is only needed when options.use_queue is true.
538 struct SavedNextLiteral {
539 LiteralIndex literal_index; // kNoLiteralIndex if we need to backtrack.
540 int rank; // Cached position_in_order, we prefer lower positions.
541
542 bool operator<(const SavedNextLiteral& o) const { return rank < o.rank; }
543 };
544 std::vector<SavedNextLiteral> queue;
546
547 // This is only needed when options use_queue is false;
549 if (!options.use_queue) starts.resize(2 * num_variables, 0);
550
551 // We delay fixing of already assigned literal once we go back to level
552 // zero.
553 std::vector<Literal> to_fix;
554
555 // Depending on the options. we do not use the same order.
556 // With tree look, it is better to start with "leaf" first since we try
557 // to reuse propagation as much as possible. This is also interesting to
558 // do when extracting binary clauses since we will need to propagate
559 // everyone anyway, and this should result in less clauses that can be
560 // removed later by transitive reduction.
561 //
562 // However, without tree-look and without the need to extract all binary
563 // clauses, it is better to just probe the root of the binary implication
564 // graph. This is exactly what happen when we probe using the topological
565 // order.
566 int order_index(0);
567 std::vector<LiteralIndex> probing_order =
568 implication_graph->ReverseTopologicalOrder();
569 if (!options.use_tree_look && !options.extract_binary_clauses) {
570 std::reverse(probing_order.begin(), probing_order.end());
571 }
572
573 // We only use this for the queue version.
574 if (options.use_queue) {
575 position_in_order.assign(2 * num_variables, -1);
576 for (int i = 0; i < probing_order.size(); ++i) {
577 position_in_order[probing_order[i]] = i;
578 }
579 }
580
581 while (!time_limit->LimitReached() &&
582 time_limit->GetElapsedDeterministicTime() <= limit) {
583 // We only enqueue literal at level zero if we don't use "tree look".
584 if (!options.use_tree_look) sat_solver->Backtrack(0);
585
586 LiteralIndex next_decision = kNoLiteralIndex;
587 if (options.use_queue && sat_solver->CurrentDecisionLevel() > 0) {
588 // TODO(user): Instead of minimizing index in topo order (which might be
589 // nice for binary extraction), we could try to maximize reusability in
590 // some way.
591 const Literal prev_decision =
592 sat_solver->Decisions()[sat_solver->CurrentDecisionLevel() - 1]
593 .literal;
594 const auto& list =
595 implication_graph->Implications(prev_decision.Negated());
596 const int saved_queue_size = queue.size();
597 for (const Literal l : list) {
598 const Literal candidate = l.Negated();
599 if (processed[candidate]) continue;
600 if (position_in_order[candidate] == -1) continue;
601 if (assignment.LiteralIsAssigned(candidate)) {
602 if (assignment.LiteralIsFalse(candidate)) {
603 to_fix.push_back(Literal(candidate.Negated()));
604 }
605 continue;
606 }
607 queue.push_back({candidate.Index(), -position_in_order[candidate]});
608 }
609 std::sort(queue.begin() + saved_queue_size, queue.end());
610
611 // Probe a literal that implies previous decision.
612 while (!queue.empty()) {
613 const LiteralIndex index = queue.back().literal_index;
614 queue.pop_back();
615 if (index == kNoLiteralIndex) {
616 // This is a backtrack marker, go back one level.
617 CHECK_GT(sat_solver->CurrentDecisionLevel(), 0);
618 sat_solver->Backtrack(sat_solver->CurrentDecisionLevel() - 1);
619 continue;
620 }
621 const Literal candidate(index);
622 if (processed[candidate]) continue;
623 if (assignment.LiteralIsAssigned(candidate)) {
624 if (assignment.LiteralIsFalse(candidate)) {
625 to_fix.push_back(Literal(candidate.Negated()));
626 }
627 continue;
628 }
629 next_decision = candidate.Index();
630 break;
631 }
632 }
633
634 if (sat_solver->CurrentDecisionLevel() == 0) {
635 // Fix any delayed fixed literal.
636 for (const Literal literal : to_fix) {
637 if (!assignment.LiteralIsTrue(literal)) {
638 ++num_explicit_fix;
639 if (!sat_solver->AddUnitClause(literal)) return false;
640 }
641 }
642 to_fix.clear();
643 if (!sat_solver->FinishPropagation()) return false;
644
645 // Probe an unexplored node.
646 for (; order_index < probing_order.size(); ++order_index) {
647 const Literal candidate(probing_order[order_index]);
648 if (processed[candidate]) continue;
649 if (assignment.LiteralIsAssigned(candidate)) continue;
650 next_decision = candidate.Index();
651 break;
652 }
653
654 // The pass is finished.
655 if (next_decision == kNoLiteralIndex) break;
656 } else if (next_decision == kNoLiteralIndex) {
657 const int level = sat_solver->CurrentDecisionLevel();
658 const Literal prev_decision = sat_solver->Decisions()[level - 1].literal;
659 const auto& list =
660 implication_graph->Implications(prev_decision.Negated());
661
662 // Probe a literal that implies previous decision.
663 //
664 // Note that contrary to the queue based implementation, this do not
665 // process them in a particular order.
666 int j = starts[prev_decision.NegatedIndex()];
667 for (int i = 0; i < list.size(); ++i, ++j) {
668 j %= list.size();
669 const Literal candidate = Literal(list[j]).Negated();
670 if (processed[candidate]) continue;
671 if (assignment.LiteralIsFalse(candidate)) {
672 // candidate => previous => not(candidate), so we can fix it.
673 to_fix.push_back(Literal(candidate.Negated()));
674 continue;
675 }
676 // This shouldn't happen if extract_binary_clauses is false.
677 // We have an equivalence.
678 if (assignment.LiteralIsTrue(candidate)) continue;
679 next_decision = candidate.Index();
680 break;
681 }
682 starts[prev_decision.NegatedIndex()] = j;
683 if (next_decision == kNoLiteralIndex) {
684 sat_solver->Backtrack(level - 1);
685 continue;
686 }
687 }
688
689 ++num_probed;
690 processed.Set(next_decision);
691 CHECK_NE(next_decision, kNoLiteralIndex);
692 queue.push_back({kNoLiteralIndex, 0}); // Backtrack marker.
693 const int level = sat_solver->CurrentDecisionLevel();
694 const int first_new_trail_index =
695 sat_solver->EnqueueDecisionAndBackjumpOnConflict(
696 Literal(next_decision));
697 const int new_level = sat_solver->CurrentDecisionLevel();
698 sat_solver->AdvanceDeterministicTime(time_limit);
699 if (sat_solver->ModelIsUnsat()) return false;
700 if (new_level <= level) {
701 ++num_conflicts;
702
703 // Sync the queue with the new level.
704 if (options.use_queue) {
705 if (new_level == 0) {
706 queue.clear();
707 } else {
708 int queue_level = level + 1;
709 while (queue_level > new_level) {
710 CHECK(!queue.empty());
711 if (queue.back().literal_index == kNoLiteralIndex) --queue_level;
712 queue.pop_back();
713 }
714 }
715 }
716
717 // Fix `next_decision` to `false` if not already done.
718 //
719 // Even if we fixed something at level zero, next_decision might not be
720 // fixed! But we can fix it. It can happen because when we propagate
721 // with clauses, we might have `a => b` but not `not(b) => not(a)`. Like
722 // `a => b` and clause `(not(a), not(b), c)`, propagating `a` will set
723 // `c`, but propagating `not(c)` will not do anything.
724 //
725 // We "delay" the fixing if we are not at level zero so that we can
726 // still reuse the current propagation work via tree look.
727 //
728 // TODO(user): Can we be smarter here? Maybe we can still fix the
729 // literal without going back to level zero by simply enqueuing it with
730 // no reason? it will be backtracked over, but we will still lazily fix
731 // it later.
732 if (sat_solver->CurrentDecisionLevel() != 0 ||
733 assignment.LiteralIsFalse(Literal(next_decision))) {
734 to_fix.push_back(Literal(next_decision).Negated());
735 }
736 }
737
738 // Inspect the newly propagated literals. Depending on the options, try to
739 // extract binary clauses via hyper binary resolution and/or mark the
740 // literals on the trail so that they do not need to be probed later.
741 if (new_level == 0) continue;
742 const Literal last_decision =
743 sat_solver->Decisions()[new_level - 1].literal;
744 int num_new_subsumed = 0;
745 for (int i = first_new_trail_index; i < trail.Index(); ++i) {
746 const Literal l = trail[i];
747 if (l == last_decision) continue;
748
749 // If we can extract a binary clause that subsume the reason clause, we
750 // do add the binary and remove the subsumed clause.
751 //
752 // TODO(user): We could be slightly more generic and subsume some
753 // clauses that do not contains last_decision.Negated().
754 bool subsumed = false;
755 if (options.subsume_with_binary_clause &&
756 trail.AssignmentType(l.Variable()) == clause_id) {
757 for (const Literal lit : trail.Reason(l.Variable())) {
758 if (lit == last_decision.Negated()) {
759 subsumed = true;
760 break;
761 }
762 }
763 if (subsumed) {
764 ++num_new_subsumed;
765 ++num_new_binary;
766 CHECK(implication_graph->AddBinaryClause(last_decision.Negated(), l));
767 const int trail_index = trail.Info(l.Variable()).trail_index;
768
769 int test = 0;
770 for (const Literal lit :
771 clause_manager->ReasonClause(trail_index)->AsSpan()) {
772 if (lit == l) ++test;
773 if (lit == last_decision.Negated()) ++test;
774 }
775 CHECK_EQ(test, 2);
776 clause_manager->LazyDetach(clause_manager->ReasonClause(trail_index));
777
778 // We need to change the reason now that the clause is cleared.
779 implication_graph->ChangeReason(trail_index, last_decision);
780 }
781 }
782
783 if (options.extract_binary_clauses) {
784 // Anything not propagated by the BinaryImplicationGraph is a "new"
785 // binary clause. This is because the BinaryImplicationGraph has the
786 // highest priority of all propagators.
787 //
788 // Note(user): This is not 100% true, since when we launch the clause
789 // propagation for one literal we do finish it before calling again
790 // the binary propagation.
791 //
792 // TODO(user): Think about trying to extract clause that will not
793 // get removed by transitive reduction later. If we can both extract
794 // a => c and b => c , ideally we don't want to extract a => c first
795 // if we already know that a => b.
796 //
797 // TODO(user): Similar to previous point, we could find the LCA
798 // of all literals in the reason for this propagation. And use this
799 // as a reason for later hyber binary resolution. Like we do when
800 // this clause subsume the reason.
801 if (!subsumed && trail.AssignmentType(l.Variable()) != id) {
802 ++num_new_binary;
803 CHECK(implication_graph->AddBinaryClause(last_decision.Negated(), l));
804 }
805 } else {
806 // If we don't extract binary, we don't need to explore any of
807 // these literal until more variables are fixed.
808 processed.Set(l.Index());
809 }
810 }
811
812 // Inspect the watcher list for last_decision, If we have a blocking
813 // literal at true (implied by last decision), then we have subsumptions.
814 //
815 // The intuition behind this is that if a binary clause (a,b) subsume a
816 // clause, and we watch a.Negated() for this clause with a blocking
817 // literal b, then this watch entry will never change because we always
818 // propagate binary clauses first and the blocking literal will always be
819 // true. So after many propagations, we hope to have such configuration
820 // which is quite cheap to test here.
821 if (options.subsume_with_binary_clause) {
822 // Tricky: If we have many "decision" and we do not extract the binary
823 // clause, then the fact that last_decision => literal might not be
824 // currently encoded in the problem clause, so if we use that relation
825 // to subsume, we should make sure it is added.
826 //
827 // Note that it is okay to add duplicate binary clause, we will clean that
828 // later.
829 const bool always_add_binary = sat_solver->CurrentDecisionLevel() > 1 &&
830 !options.extract_binary_clauses;
831
832 for (const auto& w :
833 clause_manager->WatcherListOnFalse(last_decision.Negated())) {
834 if (assignment.LiteralIsTrue(w.blocking_literal)) {
835 if (w.clause->IsRemoved()) continue;
836 CHECK_NE(w.blocking_literal, last_decision.Negated());
837
838 // Add the binary clause if needed. Note that we change the reason
839 // to a binary one so that we never add the same clause twice.
840 //
841 // Tricky: while last_decision would be a valid reason, we need a
842 // reason that was assigned before this literal, so we use the
843 // decision at the level where this literal was assigned which is an
844 // even better reason. Maybe it is just better to change all the
845 // reason above to a binary one so we don't have an issue here.
846 if (always_add_binary ||
847 trail.AssignmentType(w.blocking_literal.Variable()) != id) {
848 // If the variable was true at level zero, there is no point
849 // adding the clause.
850 const auto& info = trail.Info(w.blocking_literal.Variable());
851 if (info.level > 0) {
852 ++num_new_binary;
853 CHECK(implication_graph->AddBinaryClause(last_decision.Negated(),
854 w.blocking_literal));
855
856 const Literal d = sat_solver->Decisions()[info.level - 1].literal;
857 if (d != w.blocking_literal) {
858 implication_graph->ChangeReason(info.trail_index, d);
859 }
860 }
861 }
862
863 ++num_new_subsumed;
864 clause_manager->LazyDetach(w.clause);
865 }
866 }
867 }
868
869 if (num_new_subsumed > 0) {
870 // TODO(user): We might just want to do that even more lazily by
871 // checking for detached clause while propagating here? and do a big
872 // cleanup at the end.
873 clause_manager->CleanUpWatchers();
874 num_subsumed += num_new_subsumed;
875 }
876 }
877
878 if (!sat_solver->ResetToLevelZero()) return false;
879 for (const Literal literal : to_fix) {
880 ++num_explicit_fix;
881 if (!sat_solver->AddUnitClause(literal)) return false;
882 }
883 to_fix.clear();
884 if (!sat_solver->FinishPropagation()) return false;
885
886 // Display stats.
887 const int num_fixed = sat_solver->LiteralTrail().Index();
888 const int num_newly_fixed = num_fixed - initial_num_fixed;
889 const double time_diff =
890 time_limit->GetElapsedDeterministicTime() - initial_deterministic_time;
891 const bool limit_reached = time_limit->LimitReached() ||
892 time_limit->GetElapsedDeterministicTime() > limit;
893 LOG_IF(INFO, options.log_info)
894 << "Probing. "
895 << " num_probed: " << num_probed << " num_fixed: +" << num_newly_fixed
896 << " (" << num_fixed << "/" << num_variables << ")"
897 << " explicit_fix:" << num_explicit_fix
898 << " num_conflicts:" << num_conflicts
899 << " new_binary_clauses: " << num_new_binary
900 << " subsumed: " << num_subsumed << " dtime: " << time_diff
901 << " wtime: " << wall_timer.Get() << (limit_reached ? " (Aborted)" : "");
902 return sat_solver->FinishPropagation();
903}
904
905} // namespace sat
906} // namespace operations_research
double Get() const
Definition timer.h:46
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:32
bool LoggingIsEnabled() const
Returns true iff logging is enabled.
Definition logging.h:49
void Set(IntegerType index)
Definition bitset.h:885
bool ProcessIntegerTrail(Literal first_decision)
void AppendNewBounds(std::vector< IntegerLiteral > *output) const
Definition integer.cc:2066
LiteralIndex NegatedIndex() const
Definition sat_base.h:92
LiteralIndex Index() const
Definition sat_base.h:91
BooleanVariable Variable() const
Definition sat_base.h:87
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
Definition model.h:93
bool ProbeBooleanVariables(double deterministic_time_limit)
Definition probing.cc:60
bool ProbeDnf(absl::string_view name, absl::Span< const std::vector< Literal > > dnf)
Definition probing.cc:304
bool ProbeOneVariable(BooleanVariable b)
Definition probing.cc:205
int EnqueueDecisionAndBackjumpOnConflict(Literal true_literal)
void AdvanceDeterministicTime(TimeLimit *limit)
Definition sat_solver.h:481
bool VariableIsAssigned(BooleanVariable var) const
Returns true iff the given variable is assigned.
Definition sat_base.h:196
bool LiteralIsAssigned(Literal literal) const
Definition sat_base.h:191
void assign(size_type n, const value_type &val)
void resize(size_type new_size)
time_limit
Definition solve.cc:22
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
void RandomizeDecisionHeuristic(absl::BitGenRef random, SatParameters *parameters)
Randomizes the decision heuristic of the given SatParameters.
Definition util.cc:101
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:420
IntegerVariable PositiveVariable(IntegerVariable i)
bool FailedLiteralProbingRound(ProbingOptions options, Model *model)
Definition probing.cc:500
bool VariableIsPositive(IntegerVariable i)
In SWIG mode, we don't want anything besides these top-level includes.
trees with all degrees equal w the current value of w
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
bool log_info
We assume this is also true if –v 1 is activated.
Definition probing.h:228
#define SOLVER_LOG(logger,...)
Definition logging.h:109