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