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