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