OR-Tools  9.2
probing.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#include "ortools/sat/probing.h"
15
16#include <cstdint>
17#include <set>
18
21#include "ortools/base/timer.h"
22#include "ortools/sat/clause.h"
24#include "ortools/sat/integer.h"
27#include "ortools/sat/util.h"
30
31namespace operations_research {
32namespace sat {
33
35 : trail_(*model->GetOrCreate<Trail>()),
36 assignment_(model->GetOrCreate<SatSolver>()->Assignment()),
37 integer_trail_(model->GetOrCreate<IntegerTrail>()),
38 implied_bounds_(model->GetOrCreate<ImpliedBounds>()),
39 sat_solver_(model->GetOrCreate<SatSolver>()),
40 time_limit_(model->GetOrCreate<TimeLimit>()),
41 implication_graph_(model->GetOrCreate<BinaryImplicationGraph>()),
42 logger_(model->GetOrCreate<SolverLogger>()) {}
43
44bool Prober::ProbeBooleanVariables(const double deterministic_time_limit) {
45 const int num_variables = sat_solver_->NumVariables();
46 std::vector<BooleanVariable> bool_vars;
47 for (BooleanVariable b(0); b < num_variables; ++b) {
48 const Literal literal(b, true);
49 if (implication_graph_->RepresentativeOf(literal) != literal) {
50 continue;
51 }
52 bool_vars.push_back(b);
53 }
54 return ProbeBooleanVariables(deterministic_time_limit, bool_vars);
55}
56
57bool Prober::ProbeOneVariableInternal(BooleanVariable b) {
58 new_integer_bounds_.clear();
59 propagated_.SparseClearAll();
60 for (const Literal decision : {Literal(b, true), Literal(b, false)}) {
61 if (assignment_.LiteralIsAssigned(decision)) continue;
62
63 CHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
64 const int saved_index = trail_.Index();
65 sat_solver_->EnqueueDecisionAndBackjumpOnConflict(decision);
66 sat_solver_->AdvanceDeterministicTime(time_limit_);
67
68 if (sat_solver_->IsModelUnsat()) return false;
69 if (sat_solver_->CurrentDecisionLevel() == 0) continue;
70
71 implied_bounds_->ProcessIntegerTrail(decision);
72 integer_trail_->AppendNewBounds(&new_integer_bounds_);
73 for (int i = saved_index + 1; i < trail_.Index(); ++i) {
74 const Literal l = trail_[i];
75
76 // We mark on the first run (b.IsPositive()) and check on the second.
77 if (decision.IsPositive()) {
78 propagated_.Set(l.Index());
79 } else {
80 if (propagated_[l.Index()]) {
81 to_fix_at_true_.push_back(l);
82 }
83 }
84
85 // Anything not propagated by the BinaryImplicationGraph is a "new"
86 // binary clause. This is because the BinaryImplicationGraph has the
87 // highest priority of all propagators.
88 if (trail_.AssignmentType(l.Variable()) !=
89 implication_graph_->PropagatorId()) {
90 new_binary_clauses_.push_back({decision.Negated(), l});
91 }
92 }
93
94 // Fix variable and add new binary clauses.
95 if (!sat_solver_->RestoreSolverToAssumptionLevel()) return false;
96 for (const Literal l : to_fix_at_true_) {
97 sat_solver_->AddUnitClause(l);
98 }
99 to_fix_at_true_.clear();
100 if (!sat_solver_->FinishPropagation()) return false;
101 num_new_binary_ += new_binary_clauses_.size();
102 for (auto binary : new_binary_clauses_) {
103 sat_solver_->AddBinaryClause(binary.first, binary.second);
104 }
105 new_binary_clauses_.clear();
106 if (!sat_solver_->FinishPropagation()) return false;
107 }
108
109 // We have at most two lower bounds for each variables (one for b==0 and one
110 // for b==1), so the min of the two is a valid level zero bound! More
111 // generally, the domain of a variable can be intersected with the union
112 // of the two propagated domains. This also allow to detect "holes".
113 //
114 // TODO(user): More generally, for any clauses (b or not(b) is one), we
115 // could probe all the literal inside, and for any integer variable, we can
116 // take the union of the propagated domain as a new domain.
117 //
118 // TODO(user): fix binary variable in the same way? It might not be as
119 // useful since probing on such variable will also fix it. But then we might
120 // abort probing early, so it might still be good.
121 std::sort(new_integer_bounds_.begin(), new_integer_bounds_.end(),
122 [](IntegerLiteral a, IntegerLiteral b) { return a.var < b.var; });
123
124 // This is used for the hole detection.
125 IntegerVariable prev_var = kNoIntegerVariable;
126 IntegerValue lb_max = kMinIntegerValue;
127 IntegerValue ub_min = kMaxIntegerValue;
128 new_integer_bounds_.push_back(IntegerLiteral()); // Sentinel.
129
130 for (int i = 0; i < new_integer_bounds_.size(); ++i) {
131 const IntegerVariable var = new_integer_bounds_[i].var;
132
133 // Hole detection.
134 if (i > 0 && PositiveVariable(var) != prev_var) {
135 if (ub_min + 1 < lb_max) {
136 // The variable cannot take value in (ub_min, lb_max) !
137 //
138 // TODO(user): do not create domain with a complexity that is too
139 // large?
140 const Domain old_domain =
141 integer_trail_->InitialVariableDomain(prev_var);
142 const Domain new_domain = old_domain.IntersectionWith(
143 Domain(ub_min.value() + 1, lb_max.value() - 1).Complement());
144 if (new_domain != old_domain) {
145 ++num_new_holes_;
146 if (!integer_trail_->UpdateInitialDomain(prev_var, new_domain)) {
147 return false;
148 }
149 }
150 }
151
152 // Reinitialize.
153 lb_max = kMinIntegerValue;
154 ub_min = kMaxIntegerValue;
155 }
156
157 prev_var = PositiveVariable(var);
158 if (VariableIsPositive(var)) {
159 lb_max = std::max(lb_max, new_integer_bounds_[i].bound);
160 } else {
161 ub_min = std::min(ub_min, -new_integer_bounds_[i].bound);
162 }
163
164 // Bound tightening.
165 if (i == 0 || new_integer_bounds_[i - 1].var != var) continue;
166 const IntegerValue new_bound = std::min(new_integer_bounds_[i - 1].bound,
167 new_integer_bounds_[i].bound);
168 if (new_bound > integer_trail_->LowerBound(var)) {
169 ++num_new_integer_bounds_;
170 if (!integer_trail_->Enqueue(
171 IntegerLiteral::GreaterOrEqual(var, new_bound), {}, {})) {
172 return false;
173 }
174 }
175 }
176
177 // We might have updated some integer domain, let's propagate.
178 return sat_solver_->FinishPropagation();
179}
180
181bool Prober::ProbeOneVariable(BooleanVariable b) {
182 // Reset statistics.
183 num_new_binary_ = 0;
184 num_new_holes_ = 0;
185 num_new_integer_bounds_ = 0;
186
187 // Resize the propagated sparse bitset.
188 const int num_variables = sat_solver_->NumVariables();
189 propagated_.ClearAndResize(LiteralIndex(2 * num_variables));
190
191 // Reset the solver in case it was already used.
192 sat_solver_->SetAssumptionLevel(0);
193 if (!sat_solver_->RestoreSolverToAssumptionLevel()) return false;
194
195 return ProbeOneVariableInternal(b);
196}
197
199 const double deterministic_time_limit,
200 absl::Span<const BooleanVariable> bool_vars) {
203
204 // Reset statistics.
205 num_new_binary_ = 0;
206 num_new_holes_ = 0;
207 num_new_integer_bounds_ = 0;
208
209 // Resize the propagated sparse bitset.
210 const int num_variables = sat_solver_->NumVariables();
211 propagated_.ClearAndResize(LiteralIndex(2 * num_variables));
212
213 // Reset the solver in case it was already used.
214 sat_solver_->SetAssumptionLevel(0);
215 if (!sat_solver_->RestoreSolverToAssumptionLevel()) return false;
216
217 const int initial_num_fixed = sat_solver_->LiteralTrail().Index();
218 const double initial_deterministic_time =
219 time_limit_->GetElapsedDeterministicTime();
220 const double limit = initial_deterministic_time + deterministic_time_limit;
221
222 bool limit_reached = false;
223 int num_probed = 0;
224
225 for (const BooleanVariable b : bool_vars) {
226 const Literal literal(b, true);
227 if (implication_graph_->RepresentativeOf(literal) != literal) {
228 continue;
229 }
230
231 // TODO(user): Instead of an hard deterministic limit, we should probably
232 // use a lower one, but reset it each time we have found something useful.
233 if (time_limit_->LimitReached() ||
234 time_limit_->GetElapsedDeterministicTime() > limit) {
235 limit_reached = true;
236 break;
237 }
238
239 // Propagate b=1 and then b=0.
240 ++num_probed;
241 if (!ProbeOneVariableInternal(b)) {
242 return false;
243 }
244 }
245
246 // Display stats.
247 if (logger_->LoggingIsEnabled()) {
248 const double time_diff =
249 time_limit_->GetElapsedDeterministicTime() - initial_deterministic_time;
250 const int num_fixed = sat_solver_->LiteralTrail().Index();
251 const int num_newly_fixed = num_fixed - initial_num_fixed;
252 SOLVER_LOG(logger_, "[Probing] deterministic_time: ", time_diff,
253 " (limit: ", deterministic_time_limit,
254 ") wall_time: ", wall_timer.Get(), " (",
255 (limit_reached ? "Aborted " : ""), num_probed, "/",
256 bool_vars.size(), ")");
257 if (num_newly_fixed > 0) {
258 SOLVER_LOG(logger_, "[Probing] - new fixed Boolean: ", num_newly_fixed,
259 " (", num_fixed, "/", sat_solver_->NumVariables(), ")");
260 }
261 if (num_new_holes_ > 0) {
262 SOLVER_LOG(logger_, "[Probing] - new integer holes: ", num_new_holes_);
263 }
264 if (num_new_integer_bounds_ > 0) {
265 SOLVER_LOG(logger_,
266 "[Probing] - new integer bounds: ", num_new_integer_bounds_);
267 }
268 if (num_new_binary_ > 0) {
269 SOLVER_LOG(logger_, "[Probing] - new binary clause: ", num_new_binary_);
270 }
271 }
272
273 return true;
274}
275
276bool LookForTrivialSatSolution(double deterministic_time_limit, Model* model) {
279
280 // Reset the solver in case it was already used.
281 auto* sat_solver = model->GetOrCreate<SatSolver>();
282 sat_solver->SetAssumptionLevel(0);
283 if (!sat_solver->RestoreSolverToAssumptionLevel()) return false;
284
285 auto* time_limit = model->GetOrCreate<TimeLimit>();
286 const int initial_num_fixed = sat_solver->LiteralTrail().Index();
287 auto* logger = model->GetOrCreate<SolverLogger>();
288
289 // Note that this code do not care about the non-Boolean part and just try to
290 // assign the existing Booleans.
291 SatParameters initial_params = *model->GetOrCreate<SatParameters>();
292 SatParameters new_params = initial_params;
293 new_params.set_log_search_progress(false);
294 new_params.set_max_number_of_conflicts(1);
295 new_params.set_max_deterministic_time(deterministic_time_limit);
296
297 double elapsed_dtime = 0.0;
298
299 const int num_times = 1000;
300 bool limit_reached = false;
301 auto* random = model->GetOrCreate<ModelRandomGenerator>();
302 for (int i = 0; i < num_times; ++i) {
303 if (time_limit->LimitReached() ||
304 elapsed_dtime > deterministic_time_limit) {
305 limit_reached = true;
306 break;
307 }
308
309 // SetParameters() reset the deterministic time to zero inside time_limit.
310 sat_solver->SetParameters(new_params);
311 sat_solver->ResetDecisionHeuristic();
312 const SatSolver::Status result = sat_solver->SolveWithTimeLimit(time_limit);
313 elapsed_dtime += time_limit->GetElapsedDeterministicTime();
314
315 if (result == SatSolver::FEASIBLE) {
316 SOLVER_LOG(logger, "Trivial exploration found feasible solution!");
317 time_limit->AdvanceDeterministicTime(elapsed_dtime);
318 return true;
319 }
320
321 if (!sat_solver->RestoreSolverToAssumptionLevel()) {
322 SOLVER_LOG(logger, "UNSAT during trivial exploration heuristic.");
323 time_limit->AdvanceDeterministicTime(elapsed_dtime);
324 return false;
325 }
326
327 // We randomize at the end so that the default params is executed
328 // at least once.
329 RandomizeDecisionHeuristic(*random, &new_params);
330 new_params.set_random_seed(i);
331 new_params.set_max_deterministic_time(deterministic_time_limit -
332 elapsed_dtime);
333 }
334
335 // Restore the initial parameters.
336 sat_solver->SetParameters(initial_params);
337 sat_solver->ResetDecisionHeuristic();
338 time_limit->AdvanceDeterministicTime(elapsed_dtime);
339 if (!sat_solver->RestoreSolverToAssumptionLevel()) return false;
340
341 if (logger->LoggingIsEnabled()) {
342 const int num_fixed = sat_solver->LiteralTrail().Index();
343 const int num_newly_fixed = num_fixed - initial_num_fixed;
344 const int num_variables = sat_solver->NumVariables();
345 SOLVER_LOG(logger, "Random exploration.", " num_fixed: +", num_newly_fixed,
346 " (", num_fixed, "/", num_variables, ")",
347 " dtime: ", elapsed_dtime, "/", deterministic_time_limit,
348 " wtime: ", wall_timer.Get(),
349 (limit_reached ? " (Aborted)" : ""));
350 }
351 return sat_solver->FinishPropagation();
352}
353
357 options.log_info |= VLOG_IS_ON(1);
358
359 // Reset the solver in case it was already used.
360 auto* sat_solver = model->GetOrCreate<SatSolver>();
361 sat_solver->SetAssumptionLevel(0);
362 if (!sat_solver->RestoreSolverToAssumptionLevel()) return false;
363
364 // When called from Inprocessing, the implication graph should already be a
365 // DAG, so these two calls should return right away. But we do need them to
366 // get the topological order if this is used in isolation.
367 auto* implication_graph = model->GetOrCreate<BinaryImplicationGraph>();
368 if (!implication_graph->DetectEquivalences()) return false;
369 if (!sat_solver->FinishPropagation()) return false;
370
371 auto* time_limit = model->GetOrCreate<TimeLimit>();
372 const int initial_num_fixed = sat_solver->LiteralTrail().Index();
373 const double initial_deterministic_time =
375 const double limit = initial_deterministic_time + options.deterministic_limit;
376
377 const int num_variables = sat_solver->NumVariables();
378 SparseBitset<LiteralIndex> processed(LiteralIndex(2 * num_variables));
379
380 int64_t num_probed = 0;
381 int64_t num_explicit_fix = 0;
382 int64_t num_conflicts = 0;
383 int64_t num_new_binary = 0;
384 int64_t num_subsumed = 0;
385
386 const auto& trail = *(model->Get<Trail>());
387 const auto& assignment = trail.Assignment();
388 auto* clause_manager = model->GetOrCreate<LiteralWatchers>();
389 const int id = implication_graph->PropagatorId();
390 const int clause_id = clause_manager->PropagatorId();
391
392 // This is only needed when options.use_queue is true.
393 struct SavedNextLiteral {
394 LiteralIndex literal_index; // kNoLiteralIndex if we need to backtrack.
395 int rank; // Cached position_in_order, we prefer lower positions.
396
397 bool operator<(const SavedNextLiteral& o) const { return rank < o.rank; }
398 };
399 std::vector<SavedNextLiteral> queue;
401
402 // This is only needed when options use_queue is false;
404 if (!options.use_queue) starts.resize(2 * num_variables, 0);
405
406 // We delay fixing of already assigned literal once we go back to level
407 // zero.
408 std::vector<Literal> to_fix;
409
410 // Depending on the options. we do not use the same order.
411 // With tree look, it is better to start with "leaf" first since we try
412 // to reuse propagation as much as possible. This is also interesting to
413 // do when extracting binary clauses since we will need to propagate
414 // everyone anyway, and this should result in less clauses that can be
415 // removed later by transitive reduction.
416 //
417 // However, without tree-look and without the need to extract all binary
418 // clauses, it is better to just probe the root of the binary implication
419 // graph. This is exactly what happen when we probe using the topological
420 // order.
421 int order_index(0);
422 std::vector<LiteralIndex> probing_order =
423 implication_graph->ReverseTopologicalOrder();
424 if (!options.use_tree_look && !options.extract_binary_clauses) {
425 std::reverse(probing_order.begin(), probing_order.end());
426 }
427
428 // We only use this for the queue version.
429 if (options.use_queue) {
430 position_in_order.assign(2 * num_variables, -1);
431 for (int i = 0; i < probing_order.size(); ++i) {
432 position_in_order[probing_order[i]] = i;
433 }
434 }
435
436 while (!time_limit->LimitReached() &&
438 // We only enqueue literal at level zero if we don't use "tree look".
439 if (!options.use_tree_look) sat_solver->Backtrack(0);
440
441 LiteralIndex next_decision = kNoLiteralIndex;
442 if (options.use_queue && sat_solver->CurrentDecisionLevel() > 0) {
443 // TODO(user): Instead of minimizing index in topo order (which might be
444 // nice for binary extraction), we could try to maximize reusability in
445 // some way.
446 const Literal prev_decision =
447 sat_solver->Decisions()[sat_solver->CurrentDecisionLevel() - 1]
448 .literal;
449 const auto& list =
450 implication_graph->Implications(prev_decision.Negated());
451 const int saved_queue_size = queue.size();
452 for (const Literal l : list) {
453 const Literal candidate = l.Negated();
454 if (processed[candidate.Index()]) continue;
455 if (position_in_order[candidate.Index()] == -1) continue;
456 if (assignment.LiteralIsAssigned(candidate)) {
457 if (assignment.LiteralIsFalse(candidate)) {
458 to_fix.push_back(Literal(candidate.Negated()));
459 }
460 continue;
461 }
462 queue.push_back(
463 {candidate.Index(), -position_in_order[candidate.Index()]});
464 }
465 std::sort(queue.begin() + saved_queue_size, queue.end());
466
467 // Probe a literal that implies previous decision.
468 while (!queue.empty()) {
469 const LiteralIndex index = queue.back().literal_index;
470 queue.pop_back();
471 if (index == kNoLiteralIndex) {
472 // This is a backtrack marker, go back one level.
473 CHECK_GT(sat_solver->CurrentDecisionLevel(), 0);
474 sat_solver->Backtrack(sat_solver->CurrentDecisionLevel() - 1);
475 continue;
476 }
477 const Literal candidate(index);
478 if (processed[candidate.Index()]) continue;
479 if (assignment.LiteralIsAssigned(candidate)) {
480 if (assignment.LiteralIsFalse(candidate)) {
481 to_fix.push_back(Literal(candidate.Negated()));
482 }
483 continue;
484 }
485 next_decision = candidate.Index();
486 break;
487 }
488 }
489
490 if (sat_solver->CurrentDecisionLevel() == 0) {
491 // Fix any delayed fixed literal.
492 for (const Literal literal : to_fix) {
493 if (!assignment.LiteralIsTrue(literal)) {
494 ++num_explicit_fix;
495 sat_solver->AddUnitClause(literal);
496 }
497 }
498 to_fix.clear();
499 if (!sat_solver->FinishPropagation()) return false;
500
501 // Probe an unexplored node.
502 for (; order_index < probing_order.size(); ++order_index) {
503 const Literal candidate(probing_order[order_index]);
504 if (processed[candidate.Index()]) continue;
505 if (assignment.LiteralIsAssigned(candidate)) continue;
506 next_decision = candidate.Index();
507 break;
508 }
509
510 // The pass is finished.
511 if (next_decision == kNoLiteralIndex) break;
512 } else if (next_decision == kNoLiteralIndex) {
513 const int level = sat_solver->CurrentDecisionLevel();
514 const Literal prev_decision = sat_solver->Decisions()[level - 1].literal;
515 const auto& list =
516 implication_graph->Implications(prev_decision.Negated());
517
518 // Probe a literal that implies previous decision.
519 //
520 // Note that contrary to the queue based implementation, this do not
521 // process them in a particular order.
522 int j = starts[prev_decision.NegatedIndex()];
523 for (int i = 0; i < list.size(); ++i, ++j) {
524 j %= list.size();
525 const Literal candidate = Literal(list[j]).Negated();
526 if (processed[candidate.Index()]) continue;
527 if (assignment.LiteralIsFalse(candidate)) {
528 // candidate => previous => not(candidate), so we can fix it.
529 to_fix.push_back(Literal(candidate.Negated()));
530 continue;
531 }
532 // This shouldn't happen if extract_binary_clauses is false.
533 // We have an equivalence.
534 if (assignment.LiteralIsTrue(candidate)) continue;
535 next_decision = candidate.Index();
536 break;
537 }
538 starts[prev_decision.NegatedIndex()] = j;
539 if (next_decision == kNoLiteralIndex) {
540 sat_solver->Backtrack(level - 1);
541 continue;
542 }
543 }
544
545 ++num_probed;
546 processed.Set(next_decision);
547 CHECK_NE(next_decision, kNoLiteralIndex);
548 queue.push_back({kNoLiteralIndex, 0}); // Backtrack marker.
549 const int level = sat_solver->CurrentDecisionLevel();
550 const int first_new_trail_index =
551 sat_solver->EnqueueDecisionAndBackjumpOnConflict(
552 Literal(next_decision));
553 const int new_level = sat_solver->CurrentDecisionLevel();
554 sat_solver->AdvanceDeterministicTime(time_limit);
555 if (sat_solver->IsModelUnsat()) return false;
556 if (new_level <= level) {
557 ++num_conflicts;
558
559 // Sync the queue with the new level.
560 if (options.use_queue) {
561 if (new_level == 0) {
562 queue.clear();
563 } else {
564 int queue_level = level + 1;
565 while (queue_level > new_level) {
566 CHECK(!queue.empty());
567 if (queue.back().literal_index == kNoLiteralIndex) --queue_level;
568 queue.pop_back();
569 }
570 }
571 }
572
573 // Fix next_decision to false if not already done.
574 //
575 // Even if we fixed something at evel zero, next_decision might not be
576 // fixed! But we can fix it. It can happen because when we propagate
577 // with clauses, we might have a => b but not not(b) => not(a). Like a
578 // => b and clause (not(a), not(b), c), propagating a will set c, but
579 // propagating not(c) will not do anything.
580 //
581 // We "delay" the fixing if we are not at level zero so that we can
582 // still reuse the current propagation work via tree look.
583 //
584 // TODO(user): Can we be smarter here? Maybe we can still fix the
585 // literal without going back to level zero by simply enqueing it with
586 // no reason? it will be bactracked over, but we will still lazily fix
587 // it later.
588 if (sat_solver->CurrentDecisionLevel() != 0 ||
589 assignment.LiteralIsFalse(Literal(next_decision))) {
590 to_fix.push_back(Literal(next_decision).Negated());
591 }
592 }
593
594 // Inspect the newly propagated literals. Depending on the options, try to
595 // extract binary clauses via hyper binary resolution and/or mark the
596 // literals on the trail so that they do not need to be probed later.
597 if (new_level == 0) continue;
598 const Literal last_decision =
599 sat_solver->Decisions()[new_level - 1].literal;
600 int num_new_subsumed = 0;
601 for (int i = first_new_trail_index; i < trail.Index(); ++i) {
602 const Literal l = trail[i];
603 if (l == last_decision) continue;
604
605 // If we can extract a binary clause that subsume the reason clause, we
606 // do add the binary and remove the subsumed clause.
607 //
608 // TODO(user): We could be slightly more generic and subsume some
609 // clauses that do not contains last_decision.Negated().
610 bool subsumed = false;
611 if (options.subsume_with_binary_clause &&
612 trail.AssignmentType(l.Variable()) == clause_id) {
613 for (const Literal lit : trail.Reason(l.Variable())) {
614 if (lit == last_decision.Negated()) {
615 subsumed = true;
616 break;
617 }
618 }
619 if (subsumed) {
620 ++num_new_subsumed;
621 ++num_new_binary;
622 implication_graph->AddBinaryClause(last_decision.Negated(), l);
623 const int trail_index = trail.Info(l.Variable()).trail_index;
624
625 int test = 0;
626 for (const Literal lit :
627 clause_manager->ReasonClause(trail_index)->AsSpan()) {
628 if (lit == l) ++test;
629 if (lit == last_decision.Negated()) ++test;
630 }
631 CHECK_EQ(test, 2);
632 clause_manager->LazyDetach(clause_manager->ReasonClause(trail_index));
633
634 // We need to change the reason now that the clause is cleared.
635 implication_graph->ChangeReason(trail_index, last_decision);
636 }
637 }
638
639 if (options.extract_binary_clauses) {
640 // Anything not propagated by the BinaryImplicationGraph is a "new"
641 // binary clause. This is because the BinaryImplicationGraph has the
642 // highest priority of all propagators.
643 //
644 // Note(user): This is not 100% true, since when we launch the clause
645 // propagation for one literal we do finish it before calling again
646 // the binary propagation.
647 //
648 // TODO(user): Think about trying to extract clause that will not
649 // get removed by transitive reduction later. If we can both extract
650 // a => c and b => c , ideally we don't want to extract a => c first
651 // if we already know that a => b.
652 //
653 // TODO(user): Similar to previous point, we could find the LCA
654 // of all literals in the reason for this propagation. And use this
655 // as a reason for later hyber binary resolution. Like we do when
656 // this clause subsume the reason.
657 if (!subsumed && trail.AssignmentType(l.Variable()) != id) {
658 ++num_new_binary;
659 implication_graph->AddBinaryClause(last_decision.Negated(), l);
660 }
661 } else {
662 // If we don't extract binary, we don't need to explore any of
663 // these literal until more variables are fixed.
664 processed.Set(l.Index());
665 }
666 }
667
668 // Inspect the watcher list for last_decision, If we have a blocking
669 // literal at true (implied by last decision), then we have subsumptions.
670 //
671 // The intuition behind this is that if a binary clause (a,b) subsume a
672 // clause, and we watch a.Negated() for this clause with a blocking
673 // literal b, then this watch entry will never change because we always
674 // propagate binary clauses first and the blocking literal will always be
675 // true. So after many propagations, we hope to have such configuration
676 // which is quite cheap to test here.
677 if (options.subsume_with_binary_clause) {
678 for (const auto& w :
679 clause_manager->WatcherListOnFalse(last_decision.Negated())) {
680 if (assignment.LiteralIsTrue(w.blocking_literal)) {
681 if (w.clause->empty()) continue;
682 CHECK_NE(w.blocking_literal, last_decision.Negated());
683
684 // Add the binary clause if needed. Note that we change the reason
685 // to a binary one so that we never add the same clause twice.
686 //
687 // Tricky: while last_decision would be a valid reason, we need a
688 // reason that was assigned before this literal, so we use the
689 // decision at the level where this literal was assigne which is an
690 // even better reasony. Maybe it is just better to change all the
691 // reason above to a binary one so we don't have an issue here.
692 if (trail.AssignmentType(w.blocking_literal.Variable()) != id) {
693 // If the variable was true at level zero, there is no point
694 // adding the clause.
695 const auto& info = trail.Info(w.blocking_literal.Variable());
696 if (info.level > 0) {
697 ++num_new_binary;
698 implication_graph->AddBinaryClause(last_decision.Negated(),
699 w.blocking_literal);
700
701 const Literal d = sat_solver->Decisions()[info.level - 1].literal;
702 if (d != w.blocking_literal) {
703 implication_graph->ChangeReason(info.trail_index, d);
704 }
705 }
706 }
707
708 ++num_new_subsumed;
709 clause_manager->LazyDetach(w.clause);
710 }
711 }
712 }
713
714 if (num_new_subsumed > 0) {
715 // TODO(user): We might just want to do that even more lazily by
716 // checking for detached clause while propagating here? and do a big
717 // cleanup at the end.
718 clause_manager->CleanUpWatchers();
719 num_subsumed += num_new_subsumed;
720 }
721 }
722
723 if (!sat_solver->ResetToLevelZero()) return false;
724 for (const Literal literal : to_fix) {
725 ++num_explicit_fix;
726 sat_solver->AddUnitClause(literal);
727 }
728 to_fix.clear();
729 if (!sat_solver->FinishPropagation()) return false;
730
731 // Display stats.
732 const int num_fixed = sat_solver->LiteralTrail().Index();
733 const int num_newly_fixed = num_fixed - initial_num_fixed;
734 const double time_diff =
735 time_limit->GetElapsedDeterministicTime() - initial_deterministic_time;
736 const bool limit_reached = time_limit->LimitReached() ||
738 LOG_IF(INFO, options.log_info)
739 << "Probing. "
740 << " num_probed: " << num_probed << " num_fixed: +" << num_newly_fixed
741 << " (" << num_fixed << "/" << num_variables << ")"
742 << " explicit_fix:" << num_explicit_fix
743 << " num_conflicts:" << num_conflicts
744 << " new_binary_clauses: " << num_new_binary
745 << " subsumed: " << num_subsumed << " dtime: " << time_diff
746 << " wtime: " << wall_timer.Get() << (limit_reached ? " (Aborted)" : "");
747 return sat_solver->FinishPropagation();
748}
749
750} // namespace sat
751} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define LOG_IF(severity, condition)
Definition: base/logging.h:476
#define CHECK(condition)
Definition: base/logging.h:492
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:699
#define CHECK_GT(val1, val2)
Definition: base/logging.h:704
#define CHECK_NE(val1, val2)
Definition: base/logging.h:700
void Start()
Definition: timer.h:31
double Get() const
Definition: timer.h:45
void assign(size_type n, const value_type &val)
void resize(size_type new_size)
size_type size() const
An Assignment is a variable -> domains mapping, used to report solutions to the user.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
double GetElapsedDeterministicTime() const
Definition: time_limit.h:385
void AdvanceDeterministicTime(double deterministic_duration)
Definition: time_limit.h:375
void Set(IntegerType index)
Definition: bitset.h:804
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:534
double GetElapsedDeterministicTime() const
Returns the elapsed deterministic time since the construction of this object.
Definition: time_limit.h:261
Literal RepresentativeOf(Literal l) const
Definition: clause.h:562
void ProcessIntegerTrail(Literal first_decision)
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1027
void AppendNewBounds(std::vector< IntegerLiteral > *output) const
Definition: integer.cc:1784
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1435
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition: integer.cc:664
bool UpdateInitialDomain(IntegerVariable var, Domain domain)
Definition: integer.cc:668
LiteralIndex NegatedIndex() const
Definition: sat_base.h:87
LiteralIndex Index() const
Definition: sat_base.h:86
BooleanVariable Variable() const
Definition: sat_base.h:82
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
bool ProbeOneVariable(BooleanVariable b)
Definition: probing.cc:181
bool ProbeBooleanVariables(double deterministic_time_limit)
Definition: probing.cc:44
const Trail & LiteralTrail() const
Definition: sat_solver.h:362
void SetAssumptionLevel(int assumption_level)
Definition: sat_solver.cc:963
void AdvanceDeterministicTime(TimeLimit *limit)
Definition: sat_solver.h:423
int EnqueueDecisionAndBackjumpOnConflict(Literal true_literal)
Definition: sat_solver.cc:500
bool AddBinaryClause(Literal a, Literal b)
Definition: sat_solver.cc:181
bool AddUnitClause(Literal true_literal)
Definition: sat_solver.cc:165
int AssignmentType(BooleanVariable var) const
Definition: sat_base.h:574
bool LiteralIsAssigned(Literal literal) const
Definition: sat_base.h:155
int64_t b
int64_t a
WallTimer * wall_timer
ModelSharedTimeLimit * time_limit
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
const int INFO
Definition: log_severity.h:31
void RandomizeDecisionHeuristic(absl::BitGenRef random, SatParameters *parameters)
Definition: sat/util.cc:37
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
bool LookForTrivialSatSolution(double deterministic_time_limit, Model *model)
Definition: probing.cc:276
const LiteralIndex kNoLiteralIndex(-1)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
const IntegerVariable kNoIntegerVariable(-1)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:143
bool FailedLiteralProbingRound(ProbingOptions options, Model *model)
Definition: probing.cc:354
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:139
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:85
int index
Definition: pack.cc:509
int64_t bound
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1377
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41