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