OR-Tools  9.1
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"
25 #include "ortools/sat/sat_base.h"
26 #include "ortools/sat/sat_solver.h"
27 #include "ortools/sat/util.h"
28 #include "ortools/util/logging.h"
30 
31 namespace operations_research {
32 namespace 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 
44 bool 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 
57 bool 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 
181 bool 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) {
202  wall_timer.Start();
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 
276 bool LookForTrivialSatSolution(double deterministic_time_limit, Model* model) {
278  wall_timer.Start();
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 
356  wall_timer.Start();
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;
400  absl::StrongVector<LiteralIndex, int> position_in_order;
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  ++num_new_binary;
694  implication_graph->AddBinaryClause(last_decision.Negated(),
695  w.blocking_literal);
696 
697  const auto& info = trail.Info(w.blocking_literal.Variable());
698  if (info.level > 0) {
699  const Literal d = sat_solver->Decisions()[info.level - 1].literal;
700  if (d != w.blocking_literal) {
701  implication_graph->ChangeReason(info.trail_index, d);
702  }
703  }
704  }
705 
706  ++num_new_subsumed;
707  clause_manager->LazyDetach(w.clause);
708  }
709  }
710  }
711 
712  if (num_new_subsumed > 0) {
713  // TODO(user): We might just want to do that even more lazily by
714  // checking for detached clause while propagating here? and do a big
715  // cleanup at the end.
716  clause_manager->CleanUpWatchers();
717  num_subsumed += num_new_subsumed;
718  }
719  }
720 
721  if (!sat_solver->ResetToLevelZero()) return false;
722  for (const Literal literal : to_fix) {
723  ++num_explicit_fix;
724  sat_solver->AddUnitClause(literal);
725  }
726  to_fix.clear();
727  if (!sat_solver->FinishPropagation()) return false;
728 
729  // Display stats.
730  const int num_fixed = sat_solver->LiteralTrail().Index();
731  const int num_newly_fixed = num_fixed - initial_num_fixed;
732  const double time_diff =
733  time_limit->GetElapsedDeterministicTime() - initial_deterministic_time;
734  const bool limit_reached = time_limit->LimitReached() ||
736  LOG_IF(INFO, options.log_info)
737  << "Probing. "
738  << " num_probed: " << num_probed << " num_fixed: +" << num_newly_fixed
739  << " (" << num_fixed << "/" << num_variables << ")"
740  << " explicit_fix:" << num_explicit_fix
741  << " num_conflicts:" << num_conflicts
742  << " new_binary_clauses: " << num_new_binary
743  << " subsumed: " << num_subsumed << " dtime: " << time_diff
744  << " wtime: " << wall_timer.Get() << (limit_reached ? " (Aborted)" : "");
745  return sat_solver->FinishPropagation();
746 }
747 
748 } // namespace sat
749 } // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:491
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
int64_t bound
int64_t min
Definition: alldiff_cst.cc:139
double Get() const
Definition: timer.h:45
void Set(IntegerType index)
Definition: bitset.h:804
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:63
void RandomizeDecisionHeuristic(URBG *random, SatParameters *parameters)
Definition: sat/util.h:109
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
ModelSharedTimeLimit * time_limit
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
void SetAssumptionLevel(int assumption_level)
Definition: sat_solver.cc:963
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition: integer.cc:682
#define CHECK_GT(val1, val2)
Definition: base/logging.h:703
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1345
LiteralIndex Index() const
Definition: sat_base.h:85
Literal RepresentativeOf(Literal l) const
Definition: clause.h:562
GRBmodel * model
bool AddUnitClause(Literal true_literal)
Definition: sat_solver.cc:165
void Start()
Definition: timer.h:31
bool AddBinaryClause(Literal a, Literal b)
Definition: sat_solver.cc:181
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
bool ProbeOneVariable(BooleanVariable b)
Definition: probing.cc:181
int64_t b
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1028
void AppendNewBounds(std::vector< IntegerLiteral > *output) const
Definition: integer.cc:1785
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:142
int64_t max
Definition: alldiff_cst.cc:140
void resize(size_type new_size)
LiteralIndex NegatedIndex() const
Definition: sat_base.h:86
BooleanVariable Variable() const
Definition: sat_base.h:81
int EnqueueDecisionAndBackjumpOnConflict(Literal true_literal)
Definition: sat_solver.cc:500
void ProcessIntegerTrail(Literal first_decision)
bool UpdateInitialDomain(IntegerVariable var, Domain domain)
Definition: integer.cc:686
bool LiteralIsAssigned(Literal literal) const
Definition: sat_base.h:154
void AdvanceDeterministicTime(double deterministic_duration)
Definition: time_limit.h:374
int index
Definition: pack.cc:509
An Assignment is a variable -> domains mapping, used to report solutions to the user.
void AdvanceDeterministicTime(TimeLimit *limit)
Definition: sat_solver.h:423
bool VariableIsPositive(IntegerVariable i)
Definition: integer.h:138
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
size_type size() const
bool FailedLiteralProbingRound(ProbingOptions options, Model *model)
Definition: probing.cc:354
#define LOG_IF(severity, condition)
Definition: base/logging.h:475
double GetElapsedDeterministicTime() const
Returns the elapsed deterministic time since the construction of this object.
Definition: time_limit.h:260
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
Definition: time_limit.h:533
void ClearAndResize(IntegerType size)
Definition: bitset.h:779
bool LookForTrivialSatSolution(double deterministic_time_limit, Model *model)
Definition: probing.cc:276
Collection of objects used to extend the Constraint Solver library.
const IntegerVariable kNoIntegerVariable(-1)
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1309
void assign(size_type n, const value_type &val)
double GetElapsedDeterministicTime() const
Definition: time_limit.h:384
int AssignmentType(BooleanVariable var) const
Definition: sat_base.h:573
const LiteralIndex kNoLiteralIndex(-1)
IntVar * var
Definition: expr_array.cc:1874
const Trail & LiteralTrail() const
Definition: sat_solver.h:362
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41
bool ProbeBooleanVariables(double deterministic_time_limit)
Definition: probing.cc:44
WallTimer * wall_timer
Literal literal
Definition: optimization.cc:85
#define CHECK_NE(val1, val2)
Definition: base/logging.h:699
const int INFO
Definition: log_severity.h:31
int64_t a