OR-Tools  9.6
integer_search.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 
15 
16 #include <algorithm>
17 #include <cmath>
18 #include <cstdint>
19 #include <functional>
20 #include <random>
21 #include <vector>
22 
23 #include "absl/container/flat_hash_set.h"
24 #include "absl/log/check.h"
25 #include "absl/strings/str_cat.h"
26 #include "absl/time/clock.h"
27 #include "absl/time/time.h"
28 #include "ortools/base/logging.h"
29 #include "ortools/sat/cp_model.pb.h"
32 #include "ortools/sat/integer.h"
33 #include "ortools/sat/intervals.h"
35 #include "ortools/sat/model.h"
36 #include "ortools/sat/probing.h"
38 #include "ortools/sat/restart.h"
39 #include "ortools/sat/rins.h"
40 #include "ortools/sat/sat_base.h"
43 #include "ortools/sat/sat_parameters.pb.h"
44 #include "ortools/sat/sat_solver.h"
46 #include "ortools/sat/util.h"
49 
50 namespace operations_research {
51 namespace sat {
52 
53 IntegerLiteral AtMinValue(IntegerVariable var, IntegerTrail* integer_trail) {
54  DCHECK(!integer_trail->IsCurrentlyIgnored(var));
55  const IntegerValue lb = integer_trail->LowerBound(var);
56  DCHECK_LE(lb, integer_trail->UpperBound(var));
57  if (lb == integer_trail->UpperBound(var)) return IntegerLiteral();
59 }
60 
62  const auto& variables =
63  model->GetOrCreate<ObjectiveDefinition>()->objective_impacting_variables;
64  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
65  if (variables.contains(var)) {
66  return AtMinValue(var, integer_trail);
67  } else if (variables.contains(NegationOf(var))) {
68  return AtMinValue(NegationOf(var), integer_trail);
69  }
70  return IntegerLiteral();
71 }
72 
74  IntegerTrail* integer_trail) {
75  const IntegerValue var_lb = integer_trail->LowerBound(var);
76  const IntegerValue var_ub = integer_trail->UpperBound(var);
77  CHECK_LT(var_lb, var_ub);
78 
79  const IntegerValue chosen_value =
80  var_lb + std::max(IntegerValue(1), (var_ub - var_lb) / IntegerValue(2));
81  return IntegerLiteral::GreaterOrEqual(var, chosen_value);
82 }
83 
84 IntegerLiteral SplitAroundGivenValue(IntegerVariable var, IntegerValue value,
85  Model* model) {
86  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
87  const IntegerValue lb = integer_trail->LowerBound(var);
88  const IntegerValue ub = integer_trail->UpperBound(var);
89 
90  const absl::flat_hash_set<IntegerVariable>& variables =
91  model->GetOrCreate<ObjectiveDefinition>()->objective_impacting_variables;
92 
93  // Heuristic: Prefer the objective direction first. Reference: Conflict-Driven
94  // Heuristics for Mixed Integer Programming (2019) by Jakob Witzig and Ambros
95  // Gleixner.
96  // NOTE: The value might be out of bounds. In that case we return
97  // kNoLiteralIndex.
98  const bool branch_down_feasible = value >= lb && value < ub;
99  const bool branch_up_feasible = value > lb && value <= ub;
100  if (variables.contains(var) && branch_down_feasible) {
102  } else if (variables.contains(NegationOf(var)) && branch_up_feasible) {
104  } else if (branch_down_feasible) {
106  } else if (branch_up_feasible) {
108  }
109  return IntegerLiteral();
110 }
111 
113  auto* parameters = model->GetOrCreate<SatParameters>();
114  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
115  auto* lp_dispatcher = model->GetOrCreate<LinearProgrammingDispatcher>();
116  DCHECK(!integer_trail->IsCurrentlyIgnored(var));
117 
118  const IntegerVariable positive_var = PositiveVariable(var);
119  const auto& it = lp_dispatcher->find(positive_var);
120  const LinearProgrammingConstraint* lp =
121  it == lp_dispatcher->end() ? nullptr : it->second;
122 
123  // We only use this if the sub-lp has a solution, and depending on the value
124  // of exploit_all_lp_solution() if it is a pure-integer solution.
125  if (lp == nullptr || !lp->HasSolution()) return IntegerLiteral();
126  if (!parameters->exploit_all_lp_solution() && !lp->SolutionIsInteger()) {
127  return IntegerLiteral();
128  }
129 
130  // TODO(user): Depending if we branch up or down, this might not exclude the
131  // LP value, which is potentially a bad thing.
132  //
133  // TODO(user): Why is the reduced cost doing things differently?
134  const IntegerValue value = IntegerValue(
135  static_cast<int64_t>(std::round(lp->GetSolutionValue(positive_var))));
136 
137  // Because our lp solution might be from higher up in the tree, it
138  // is possible that value is now outside the domain of positive_var.
139  // In this case, this function will return an invalid literal.
140  return SplitAroundGivenValue(positive_var, value, model);
141 }
142 
144  IntegerVariable var, const SharedSolutionRepository<int64_t>& solution_repo,
145  Model* model) {
146  if (solution_repo.NumSolutions() == 0) {
147  return IntegerLiteral();
148  }
149 
150  const IntegerVariable positive_var = PositiveVariable(var);
151  const int proto_var =
152  model->Get<CpModelMapping>()->GetProtoVariableFromIntegerVariable(
153  positive_var);
154 
155  if (proto_var < 0) {
156  return IntegerLiteral();
157  }
158 
159  const IntegerValue value(solution_repo.GetVariableValueInSolution(
160  proto_var, /*solution_index=*/0));
161  return SplitAroundGivenValue(positive_var, value, model);
162 }
163 
164 // TODO(user): the complexity caused by the linear scan in this heuristic and
165 // the one below is ok when search_branching is set to SAT_SEARCH because it is
166 // not executed often, but otherwise it is done for each search decision,
167 // which seems expensive. Improve.
169  const std::vector<IntegerVariable>& vars, Model* model) {
170  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
171  return [/*copy*/ vars, integer_trail]() {
172  for (const IntegerVariable var : vars) {
173  // Note that there is no point trying to fix a currently ignored variable.
174  if (integer_trail->IsCurrentlyIgnored(var)) continue;
175  const IntegerLiteral decision = AtMinValue(var, integer_trail);
176  if (decision.IsValid()) return BooleanOrIntegerLiteral(decision);
177  }
178  return BooleanOrIntegerLiteral();
179  };
180 }
181 
182 std::function<BooleanOrIntegerLiteral()>
184  const std::vector<IntegerVariable>& vars, Model* model) {
185  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
186  return [/*copy */ vars, integer_trail]() {
187  IntegerVariable candidate = kNoIntegerVariable;
188  IntegerValue candidate_lb;
189  for (const IntegerVariable var : vars) {
190  if (integer_trail->IsCurrentlyIgnored(var)) continue;
191  const IntegerValue lb = integer_trail->LowerBound(var);
192  if (lb < integer_trail->UpperBound(var) &&
193  (candidate == kNoIntegerVariable || lb < candidate_lb)) {
194  candidate = var;
195  candidate_lb = lb;
196  }
197  }
198  if (candidate == kNoIntegerVariable) return BooleanOrIntegerLiteral();
199  return BooleanOrIntegerLiteral(AtMinValue(candidate, integer_trail));
200  };
201 }
202 
204  std::vector<std::function<BooleanOrIntegerLiteral()>> heuristics) {
205  return [heuristics]() {
206  for (const auto& h : heuristics) {
207  const BooleanOrIntegerLiteral decision = h();
208  if (decision.HasValue()) return decision;
209  }
210  return BooleanOrIntegerLiteral();
211  };
212 }
213 
215  std::vector<std::function<IntegerLiteral(IntegerVariable)>>
216  value_selection_heuristics,
217  std::function<BooleanOrIntegerLiteral()> var_selection_heuristic,
218  Model* model) {
219  auto* encoder = model->GetOrCreate<IntegerEncoder>();
220  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
221  auto* sat_policy = model->GetOrCreate<SatDecisionPolicy>();
222  return [=]() {
223  // Get the current decision.
224  const BooleanOrIntegerLiteral current_decision = var_selection_heuristic();
225  if (!current_decision.HasValue()) return current_decision;
226 
227  // When we are in the "stable" phase, we prefer to follow the SAT polarity
228  // heuristic.
229  if (current_decision.boolean_literal_index != kNoLiteralIndex &&
230  sat_policy->InStablePhase()) {
231  return current_decision;
232  }
233 
234  // IntegerLiteral case.
235  if (current_decision.boolean_literal_index == kNoLiteralIndex) {
236  for (const auto& value_heuristic : value_selection_heuristics) {
237  const IntegerLiteral decision =
238  value_heuristic(current_decision.integer_literal.var);
239  if (decision.IsValid()) return BooleanOrIntegerLiteral(decision);
240  }
241  return current_decision;
242  }
243 
244  // Boolean case. We try to decode the Boolean decision to see if it is
245  // associated with an integer variable.
246  //
247  // TODO(user): we will likely stop at the first non-fixed variable.
248  for (const IntegerVariable var : encoder->GetAllAssociatedVariables(
249  Literal(current_decision.boolean_literal_index))) {
250  if (integer_trail->IsCurrentlyIgnored(var)) continue;
251 
252  // Sequentially try the value selection heuristics.
253  for (const auto& value_heuristic : value_selection_heuristics) {
254  const IntegerLiteral decision = value_heuristic(var);
255  if (decision.IsValid()) return BooleanOrIntegerLiteral(decision);
256  }
257  }
258 
259  return current_decision;
260  };
261 }
262 
264  auto* lp_constraints =
266  int num_lp_variables = 0;
267  for (LinearProgrammingConstraint* lp : *lp_constraints) {
268  num_lp_variables += lp->NumVariables();
269  }
270  const int num_integer_variables =
271  model->GetOrCreate<IntegerTrail>()->NumIntegerVariables().value() / 2;
272  return (num_integer_variables <= 2 * num_lp_variables);
273 }
274 
275 // Note that all these heuristic do not depend on the variable being positive
276 // or negative.
277 //
278 // TODO(user): Experiment more with value selection heuristics.
280  std::function<BooleanOrIntegerLiteral()> var_selection_heuristic,
281  Model* model) {
282  const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
283  std::vector<std::function<IntegerLiteral(IntegerVariable)>>
284  value_selection_heuristics;
285 
286  // LP based value.
287  //
288  // Note that we only do this if a big enough percentage of the problem
289  // variables appear in the LP relaxation.
291  (parameters.exploit_integer_lp_solution() ||
292  parameters.exploit_all_lp_solution())) {
293  value_selection_heuristics.push_back([model](IntegerVariable var) {
295  });
296  }
297 
298  // Solution based value.
299  if (parameters.exploit_best_solution()) {
300  auto* response_manager = model->Get<SharedResponseManager>();
301  if (response_manager != nullptr) {
302  VLOG(3) << "Using best solution value selection heuristic.";
303  value_selection_heuristics.push_back(
304  [model, response_manager](IntegerVariable var) {
306  var, response_manager->SolutionsRepository(), model);
307  });
308  }
309  }
310 
311  // Relaxation Solution based value.
312  if (parameters.exploit_relaxation_solution()) {
313  auto* relaxation_solutions =
315  if (relaxation_solutions != nullptr) {
316  value_selection_heuristics.push_back(
317  [model, relaxation_solutions](IntegerVariable var) {
318  VLOG(3) << "Using relaxation solution value selection heuristic.";
321  });
322  }
323  }
324 
325  // Objective based value.
326  if (parameters.exploit_objective()) {
327  value_selection_heuristics.push_back([model](IntegerVariable var) {
329  });
330  }
331 
332  return SequentialValueSelection(value_selection_heuristics,
333  var_selection_heuristic, model);
334 }
335 
337  SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
338  Trail* trail = model->GetOrCreate<Trail>();
339  SatDecisionPolicy* decision_policy = model->GetOrCreate<SatDecisionPolicy>();
340  return [sat_solver, trail, decision_policy] {
341  const bool all_assigned = trail->Index() == sat_solver->NumVariables();
342  if (all_assigned) return BooleanOrIntegerLiteral();
343  const Literal result = decision_policy->NextBranch();
344  CHECK(!sat_solver->Assignment().LiteralIsAssigned(result));
345  return BooleanOrIntegerLiteral(result.Index());
346  };
347 }
348 
349 // TODO(user): Do we need a mechanism to reduce the range of possible gaps
350 // when nothing gets proven? This could be a parameter or some adaptative code.
352  auto* objective_definition = model->GetOrCreate<ObjectiveDefinition>();
353  const IntegerVariable obj_var = objective_definition->objective_var;
354  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
355  auto* sat_solver = model->GetOrCreate<SatSolver>();
356  auto* random = model->GetOrCreate<ModelRandomGenerator>();
357 
358  return [obj_var, integer_trail, sat_solver, random]() {
360  const int level = sat_solver->CurrentDecisionLevel();
361  if (level > 0 || obj_var == kNoIntegerVariable) return result;
362 
363  const IntegerValue obj_lb = integer_trail->LowerBound(obj_var);
364  const IntegerValue obj_ub = integer_trail->UpperBound(obj_var);
365  const IntegerValue mid = (obj_ub - obj_lb) / 2;
366  const IntegerValue new_ub =
367  obj_lb + absl::LogUniform<int64_t>(*random, 0, mid.value());
368 
369  result.integer_literal = IntegerLiteral::LowerOrEqual(obj_var, new_ub);
370  return result;
371  };
372 }
373 
375  auto* objective = model->Get<ObjectiveDefinition>();
376  const bool has_objective =
377  objective != nullptr && objective->objective_var != kNoIntegerVariable;
378  if (!has_objective) {
379  return []() { return BooleanOrIntegerLiteral(); };
380  }
381 
382  auto* pseudo_costs = model->GetOrCreate<PseudoCosts>();
383  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
384  return [pseudo_costs, integer_trail]() {
385  const IntegerVariable chosen_var = pseudo_costs->GetBestDecisionVar();
386  if (chosen_var == kNoIntegerVariable) return BooleanOrIntegerLiteral();
387 
388  // TODO(user): This will be overridden by the value decision heuristic in
389  // almost all cases.
391  GreaterOrEqualToMiddleValue(chosen_var, integer_trail));
392  };
393 }
394 
395 // A simple heuristic for scheduling models.
397  Model* model) {
398  auto* repo = model->GetOrCreate<IntervalsRepository>();
399  auto* heuristic = model->GetOrCreate<SearchHeuristics>();
400  auto* trail = model->GetOrCreate<Trail>();
401  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
402  return [repo, heuristic, trail, integer_trail]() {
403  struct ToSchedule {
404  // Variable to fix.
405  LiteralIndex presence = kNoLiteralIndex;
408 
409  // Information to select best.
410  IntegerValue size_min = kMaxIntegerValue;
411  IntegerValue time = kMaxIntegerValue;
412  };
413  ToSchedule best;
414 
415  // TODO(user): we should also precompute fixed precedences and only fix
416  // interval that have all their predecessors fixed.
417  const int num_intervals = repo->NumIntervals();
418  for (IntervalVariable i(0); i < num_intervals; ++i) {
419  if (repo->IsAbsent(i)) continue;
420  if (!repo->IsPresent(i) || !integer_trail->IsFixed(repo->Start(i)) ||
421  !integer_trail->IsFixed(repo->End(i))) {
422  IntegerValue time = integer_trail->LowerBound(repo->Start(i));
423  if (repo->IsOptional(i)) {
424  // For task whose presence is still unknown, our propagators should
425  // have propagated the minimium time as if it was present. So this
426  // should reflect the earliest time at which this interval can be
427  // scheduled.
428  time = std::max(time, integer_trail->ConditionalLowerBound(
429  repo->PresenceLiteral(i), repo->Start(i)));
430  }
431 
432  // For variable size, we compute the min size once the start is fixed
433  // to time. This is needed to never pick the "artificial" makespan
434  // interval at the end in priority compared to intervals that still
435  // need to be scheduled.
436  const IntegerValue size_min =
437  std::max(integer_trail->LowerBound(repo->Size(i)),
438  integer_trail->LowerBound(repo->End(i)) - time);
439  if (time < best.time ||
440  (time == best.time && size_min < best.size_min)) {
441  best.presence = repo->IsOptional(i) ? repo->PresenceLiteral(i).Index()
442  : kNoLiteralIndex;
443  best.start = repo->Start(i);
444  best.end = repo->End(i);
445  best.time = time;
446  best.size_min = size_min;
447  }
448  }
449  }
450  if (best.time == kMaxIntegerValue) return BooleanOrIntegerLiteral();
451 
452  // Use the next_decision_override to fix in turn all the variables from
453  // the selected interval.
454  int num_times = 0;
455  heuristic->next_decision_override = [trail, integer_trail, best,
456  num_times]() mutable {
457  if (++num_times > 5) {
458  // We have been trying to fix this interval for a while. Do we miss
459  // some propagation? In any case, try to see if the heuristic above
460  // would select something else.
461  VLOG(3) << "Skipping ... ";
462  return BooleanOrIntegerLiteral();
463  }
464 
465  // First make sure the interval is present.
466  if (best.presence != kNoLiteralIndex) {
467  if (!trail->Assignment().LiteralIsAssigned(Literal(best.presence))) {
468  VLOG(3) << "assign " << best.presence;
469  return BooleanOrIntegerLiteral(best.presence);
470  }
471  if (trail->Assignment().LiteralIsFalse(Literal(best.presence))) {
472  VLOG(2) << "unperformed.";
473  return BooleanOrIntegerLiteral();
474  }
475  }
476 
477  // We assume that start_min is propagated by now.
478  if (!integer_trail->IsFixed(best.start)) {
479  const IntegerValue start_min = integer_trail->LowerBound(best.start);
480  VLOG(3) << "start == " << start_min;
481  return BooleanOrIntegerLiteral(best.start.LowerOrEqual(start_min));
482  }
483 
484  // We assume that end_min is propagated by now.
485  if (!integer_trail->IsFixed(best.end)) {
486  const IntegerValue end_min = integer_trail->LowerBound(best.end);
487  VLOG(3) << "end == " << end_min;
488  return BooleanOrIntegerLiteral(best.end.LowerOrEqual(end_min));
489  }
490 
491  // Everything is fixed, dettach the override.
492  const IntegerValue start = integer_trail->LowerBound(best.start);
493  VLOG(2) << "Fixed @[" << start << ","
494  << integer_trail->LowerBound(best.end) << "]"
495  << (best.presence != kNoLiteralIndex
496  ? absl::StrCat(" presence=",
497  Literal(best.presence).DebugString())
498  : "")
499  << (best.time < start
500  ? absl::StrCat(" start_at_selection=", best.time.value())
501  : "");
502  return BooleanOrIntegerLiteral();
503  };
504 
505  return heuristic->next_decision_override();
506  };
507 }
508 
510  Model* model) {
511  SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
512  SatDecisionPolicy* decision_policy = model->GetOrCreate<SatDecisionPolicy>();
513 
514  // TODO(user): Add other policy and perform more experiments.
515  std::function<BooleanOrIntegerLiteral()> sat_policy =
517  std::vector<std::function<BooleanOrIntegerLiteral()>> policies{
518  sat_policy, SequentialSearch({PseudoCost(model), sat_policy})};
519  // The higher weight for the sat policy is because this policy actually
520  // contains a lot of variation as we randomize the sat parameters.
521  // TODO(user): Do more experiments to find better distribution.
522  std::discrete_distribution<int> var_dist{3 /*sat_policy*/, 1 /*Pseudo cost*/};
523 
524  // Value selection.
525  std::vector<std::function<IntegerLiteral(IntegerVariable)>>
526  value_selection_heuristics;
527  std::vector<int> value_selection_weight;
528 
529  // LP Based value.
530  value_selection_heuristics.push_back([model](IntegerVariable var) {
532  });
533  value_selection_weight.push_back(8);
534 
535  // Solution based value.
536  auto* response_manager = model->Get<SharedResponseManager>();
537  if (response_manager != nullptr) {
538  value_selection_heuristics.push_back(
539  [model, response_manager](IntegerVariable var) {
541  var, response_manager->SolutionsRepository(), model);
542  });
543  value_selection_weight.push_back(5);
544  }
545 
546  // Relaxation solution based value.
548  if (relaxation_solutions != nullptr) {
549  value_selection_heuristics.push_back(
550  [model, relaxation_solutions](IntegerVariable var) {
553  });
554  value_selection_weight.push_back(3);
555  }
556 
557  // Middle value.
558  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
559  value_selection_heuristics.push_back([integer_trail](IntegerVariable var) {
560  return GreaterOrEqualToMiddleValue(var, integer_trail);
561  });
562  value_selection_weight.push_back(1);
563 
564  // Min value.
565  value_selection_heuristics.push_back([integer_trail](IntegerVariable var) {
566  return AtMinValue(var, integer_trail);
567  });
568  value_selection_weight.push_back(1);
569 
570  // Special case: Don't change the decision value.
571  value_selection_weight.push_back(10);
572 
573  // TODO(user): These distribution values are just guessed values. They need
574  // to be tuned.
575  std::discrete_distribution<int> val_dist(value_selection_weight.begin(),
576  value_selection_weight.end());
577 
578  int policy_index = 0;
579  int val_policy_index = 0;
580  auto* encoder = model->GetOrCreate<IntegerEncoder>();
581  return [=]() mutable {
582  if (sat_solver->CurrentDecisionLevel() == 0) {
583  auto* random = model->GetOrCreate<ModelRandomGenerator>();
584  RandomizeDecisionHeuristic(*random, model->GetOrCreate<SatParameters>());
585  decision_policy->ResetDecisionHeuristic();
586 
587  // Select the variable selection heuristic.
588  policy_index = var_dist(*(random));
589 
590  // Select the value selection heuristic.
591  val_policy_index = val_dist(*(random));
592  }
593 
594  // Get the current decision.
595  const BooleanOrIntegerLiteral current_decision = policies[policy_index]();
596  if (!current_decision.HasValue()) return current_decision;
597 
598  // Special case: Don't override the decision value.
599  if (val_policy_index >= value_selection_heuristics.size()) {
600  return current_decision;
601  }
602 
603  if (current_decision.boolean_literal_index == kNoLiteralIndex) {
604  const IntegerLiteral new_decision =
605  value_selection_heuristics[val_policy_index](
606  current_decision.integer_literal.var);
607  if (new_decision.IsValid()) return BooleanOrIntegerLiteral(new_decision);
608  return current_decision;
609  }
610 
611  // Decode the decision and get the variable.
612  for (const IntegerVariable var : encoder->GetAllAssociatedVariables(
613  Literal(current_decision.boolean_literal_index))) {
614  if (integer_trail->IsCurrentlyIgnored(var)) continue;
615 
616  // Try the selected policy.
617  const IntegerLiteral new_decision =
618  value_selection_heuristics[val_policy_index](var);
619  if (new_decision.IsValid()) return BooleanOrIntegerLiteral(new_decision);
620  }
621 
622  // Selected policy failed. Revert back to original decision.
623  return current_decision;
624  };
625 }
626 
628  const std::vector<BooleanOrIntegerVariable>& vars,
629  const std::vector<IntegerValue>& values, Model* model) {
630  auto* trail = model->GetOrCreate<Trail>();
631  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
632  auto* rev_int_repo = model->GetOrCreate<RevIntRepository>();
633 
634  // This is not ideal as we reserve an int for the full duration of the model
635  // even if we use this FollowHint() function just for a while. But it is
636  // an easy solution to not have reference to deleted memory in the
637  // RevIntRepository(). Note that once we backtrack, these reference will
638  // disappear.
639  int* rev_start_index = model->TakeOwnership(new int);
640  *rev_start_index = 0;
641 
642  return [=]() {
643  rev_int_repo->SaveState(rev_start_index);
644  for (int i = *rev_start_index; i < vars.size(); ++i) {
645  const IntegerValue value = values[i];
646  if (vars[i].bool_var != kNoBooleanVariable) {
647  if (trail->Assignment().VariableIsAssigned(vars[i].bool_var)) continue;
648 
649  // If we retake a decision at this level, we will restart from i.
650  *rev_start_index = i;
652  Literal(vars[i].bool_var, value == 1).Index());
653  } else {
654  const IntegerVariable integer_var = vars[i].int_var;
655  if (integer_trail->IsCurrentlyIgnored(integer_var)) continue;
656  if (integer_trail->IsFixed(integer_var)) continue;
657 
658  const IntegerVariable positive_var = PositiveVariable(integer_var);
659  const IntegerLiteral decision = SplitAroundGivenValue(
660  positive_var, positive_var != integer_var ? -value : value, model);
661  if (decision.IsValid()) {
662  // If we retake a decision at this level, we will restart from i.
663  *rev_start_index = i;
664  return BooleanOrIntegerLiteral(decision);
665  }
666 
667  // If the value is outside the current possible domain, we skip it.
668  continue;
669  }
670  }
671  return BooleanOrIntegerLiteral();
672  };
673 }
674 
675 std::function<bool()> RestartEveryKFailures(int k, SatSolver* solver) {
676  bool reset_at_next_call = true;
677  int next_num_failures = 0;
678  return [=]() mutable {
679  if (reset_at_next_call) {
680  next_num_failures = solver->num_failures() + k;
681  reset_at_next_call = false;
682  } else if (solver->num_failures() >= next_num_failures) {
683  reset_at_next_call = true;
684  }
685  return reset_at_next_call;
686  };
687 }
688 
689 std::function<bool()> SatSolverRestartPolicy(Model* model) {
690  auto policy = model->GetOrCreate<RestartPolicy>();
691  return [policy]() { return policy->ShouldRestart(); };
692 }
693 
694 namespace {
695 
696 std::function<BooleanOrIntegerLiteral()> WrapIntegerLiteralHeuristic(
697  std::function<IntegerLiteral()> f) {
698  return [f]() { return BooleanOrIntegerLiteral(f()); };
699 }
700 
701 } // namespace
702 
704  SearchHeuristics& heuristics = *model->GetOrCreate<SearchHeuristics>();
705  CHECK(heuristics.fixed_search != nullptr);
706  heuristics.policy_index = 0;
707  heuristics.decision_policies.clear();
708  heuristics.restart_policies.clear();
709 
710  const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
711  switch (parameters.search_branching()) {
712  case SatParameters::AUTOMATIC_SEARCH: {
713  std::function<BooleanOrIntegerLiteral()> decision_policy;
714  if (parameters.randomize_search()) {
715  decision_policy = RandomizeOnRestartHeuristic(model);
716  } else {
717  decision_policy = SatSolverHeuristic(model);
718  }
719  decision_policy =
720  SequentialSearch({decision_policy, heuristics.fixed_search});
721  decision_policy = IntegerValueSelectionHeuristic(decision_policy, model);
722  if (parameters.use_objective_lb_search()) {
723  heuristics.decision_policies = {
724  SequentialSearch({ShaveObjectiveLb(model), decision_policy})};
725  } else {
726  heuristics.decision_policies = {decision_policy};
727  }
729  return;
730  }
731  case SatParameters::FIXED_SEARCH: {
732  // Not all Boolean might appear in fixed_search(), so once there is no
733  // decision left, we fix all Booleans that are still undecided.
734  heuristics.decision_policies = {SequentialSearch(
735  {heuristics.fixed_search, SatSolverHeuristic(model)})};
736 
737  if (parameters.randomize_search()) {
739  return;
740  }
741 
742  // TODO(user): We might want to restart if external info is available.
743  // Code a custom restart for this?
744  auto no_restart = []() { return false; };
745  heuristics.restart_policies = {no_restart};
746  return;
747  }
748  case SatParameters::PARTIAL_FIXED_SEARCH: {
749  heuristics.decision_policies = {
751  heuristics.fixed_search})};
752  auto no_restart = []() { return false; };
753  heuristics.restart_policies = {no_restart};
754  return;
755  }
756  case SatParameters::HINT_SEARCH: {
757  CHECK(heuristics.hint_search != nullptr);
758  heuristics.decision_policies = {
760  heuristics.fixed_search})};
761  auto no_restart = []() { return false; };
762  heuristics.restart_policies = {no_restart};
763  return;
764  }
765  case SatParameters::PORTFOLIO_SEARCH: {
766  // TODO(user): This is not used in any of our default config. remove?
767  // It make also no sense to choose a value in the LP heuristic and then
768  // override it with IntegerValueSelectionHeuristic(), clean that up.
769  std::vector<std::function<BooleanOrIntegerLiteral()>> base_heuristics;
770  base_heuristics.push_back(heuristics.fixed_search);
771  for (const auto& ct :
772  *(model->GetOrCreate<LinearProgrammingConstraintCollection>())) {
773  base_heuristics.push_back(WrapIntegerLiteralHeuristic(
774  ct->HeuristicLpReducedCostBinary(model)));
775  base_heuristics.push_back(WrapIntegerLiteralHeuristic(
776  ct->HeuristicLpMostInfeasibleBinary(model)));
777  }
779  base_heuristics, SequentialSearch({SatSolverHeuristic(model),
780  heuristics.fixed_search}));
781  for (auto& ref : heuristics.decision_policies) {
783  }
784  heuristics.restart_policies.assign(heuristics.decision_policies.size(),
786  return;
787  }
788  case SatParameters::LP_SEARCH: {
789  std::vector<std::function<BooleanOrIntegerLiteral()>> lp_heuristics;
790  for (const auto& ct :
791  *(model->GetOrCreate<LinearProgrammingConstraintCollection>())) {
792  lp_heuristics.push_back(WrapIntegerLiteralHeuristic(
793  ct->HeuristicLpReducedCostAverageBranching()));
794  }
795  if (lp_heuristics.empty()) { // Revert to fixed search.
796  heuristics.decision_policies = {SequentialSearch(
797  {heuristics.fixed_search, SatSolverHeuristic(model)})},
799  return;
800  }
802  lp_heuristics, IntegerValueSelectionHeuristic(
804  heuristics.fixed_search}),
805  model));
806  heuristics.restart_policies.assign(heuristics.decision_policies.size(),
808  return;
809  }
810  case SatParameters::PSEUDO_COST_SEARCH: {
811  std::function<BooleanOrIntegerLiteral()> search =
813  heuristics.fixed_search});
814  heuristics.decision_policies = {
817  return;
818  }
819  case SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH: {
820  std::function<BooleanOrIntegerLiteral()> search = SequentialSearch(
822  heuristics.decision_policies = {search};
823  heuristics.restart_policies = {
824  RestartEveryKFailures(10, model->GetOrCreate<SatSolver>())};
825  return;
826  }
827  }
828 }
829 
830 std::vector<std::function<BooleanOrIntegerLiteral()>> CompleteHeuristics(
831  const std::vector<std::function<BooleanOrIntegerLiteral()>>&
832  incomplete_heuristics,
833  const std::function<BooleanOrIntegerLiteral()>& completion_heuristic) {
834  std::vector<std::function<BooleanOrIntegerLiteral()>> complete_heuristics;
835  complete_heuristics.reserve(incomplete_heuristics.size());
836  for (const auto& incomplete : incomplete_heuristics) {
837  complete_heuristics.push_back(
838  SequentialSearch({incomplete, completion_heuristic}));
839  }
840  return complete_heuristics;
841 }
842 
844  : parameters_(*model->GetOrCreate<SatParameters>()),
845  model_(model),
846  sat_solver_(model->GetOrCreate<SatSolver>()),
847  integer_trail_(model->GetOrCreate<IntegerTrail>()),
848  encoder_(model->GetOrCreate<IntegerEncoder>()),
849  implied_bounds_(model->GetOrCreate<ImpliedBounds>()),
850  prober_(model->GetOrCreate<Prober>()),
851  product_detector_(model->GetOrCreate<ProductDetector>()),
852  time_limit_(model->GetOrCreate<TimeLimit>()),
853  pseudo_costs_(model->GetOrCreate<PseudoCosts>()) {
854  // This is needed for recording the pseudo-costs.
855  const ObjectiveDefinition* objective = model->Get<ObjectiveDefinition>();
856  if (objective != nullptr) objective_var_ = objective->objective_var;
857 }
858 
860  // If we pushed root level deductions, we restart to incorporate them.
861  // Note that in the present of assumptions, it is important to return to
862  // the level zero first ! otherwise, the new deductions will not be
863  // incorporated and the solver will loop forever.
864  if (integer_trail_->HasPendingRootLevelDeduction()) {
865  sat_solver_->Backtrack(0);
866  if (!sat_solver_->RestoreSolverToAssumptionLevel()) {
867  return false;
868  }
869  }
870 
871  if (sat_solver_->CurrentDecisionLevel() == 0) {
872  auto* level_zero_callbacks = model_->GetOrCreate<LevelZeroCallbackHelper>();
873  for (const auto& cb : level_zero_callbacks->callbacks) {
874  if (!cb()) {
875  sat_solver_->NotifyThatModelIsUnsat();
876  return false;
877  }
878  }
879 
880  if (parameters_.use_sat_inprocessing() &&
881  !model_->GetOrCreate<Inprocessing>()->InprocessingRound()) {
882  sat_solver_->NotifyThatModelIsUnsat();
883  return false;
884  }
885  }
886  return true;
887 }
888 
890  const std::function<BooleanOrIntegerLiteral()>& f) {
891  LiteralIndex decision = kNoLiteralIndex;
892  while (!time_limit_->LimitReached()) {
893  BooleanOrIntegerLiteral new_decision;
894  if (integer_trail_->InPropagationLoop()) {
895  const IntegerVariable var =
896  integer_trail_->NextVariableToBranchOnInPropagationLoop();
897  if (var != kNoIntegerVariable) {
898  new_decision.integer_literal =
899  GreaterOrEqualToMiddleValue(var, integer_trail_);
900  }
901  }
902  if (!new_decision.HasValue()) {
903  new_decision = f();
904  }
905  if (!new_decision.HasValue() &&
906  integer_trail_->CurrentBranchHadAnIncompletePropagation()) {
907  const IntegerVariable var = integer_trail_->FirstUnassignedVariable();
908  if (var != kNoIntegerVariable) {
909  new_decision.integer_literal = AtMinValue(var, integer_trail_);
910  }
911  }
912  if (!new_decision.HasValue()) break;
913 
914  // Convert integer decision to literal one if needed.
915  //
916  // TODO(user): Ideally it would be cool to delay the creation even more
917  // until we have a conflict with these decisions, but it is currrently
918  // hard to do so.
919  if (new_decision.boolean_literal_index != kNoLiteralIndex) {
920  decision = new_decision.boolean_literal_index;
921  } else {
922  decision =
923  encoder_->GetOrCreateAssociatedLiteral(new_decision.integer_literal)
924  .Index();
925  }
926  if (sat_solver_->Assignment().LiteralIsAssigned(Literal(decision))) {
927  // TODO(user): It would be nicer if this can never happen. For now, it
928  // does because of the Propagate() not reaching the fixed point as
929  // mentionned in a TODO above. As a work-around, we display a message
930  // but do not crash and recall the decision heuristic.
931  VLOG(1) << "Trying to take a decision that is already assigned!"
932  << " Fix this. Continuing for now...";
933  continue;
934  }
935  break;
936  }
937  return decision;
938 }
939 
941  // Record the changelist and objective bounds for updating pseudo costs.
942  const std::vector<PseudoCosts::VariableBoundChange> bound_changes =
943  pseudo_costs_->GetBoundChanges(decision);
944  IntegerValue old_obj_lb = kMinIntegerValue;
945  IntegerValue old_obj_ub = kMaxIntegerValue;
946  if (objective_var_ != kNoIntegerVariable) {
947  old_obj_lb = integer_trail_->LowerBound(objective_var_);
948  old_obj_ub = integer_trail_->UpperBound(objective_var_);
949  }
950  const int old_level = sat_solver_->CurrentDecisionLevel();
951 
952  // Note that kUnsatTrailIndex might also mean ASSUMPTIONS_UNSAT.
953  //
954  // TODO(user): on some problems, this function can be quite long. Expand
955  // so that we can check the time limit at each step?
956  const int index = sat_solver_->EnqueueDecisionAndBackjumpOnConflict(decision);
957  if (index == kUnsatTrailIndex) return false;
958 
959  // Update the implied bounds each time we enqueue a literal at level zero.
960  // This is "almost free", so we might as well do it.
961  if (old_level == 0 && sat_solver_->CurrentDecisionLevel() == 1) {
962  if (!implied_bounds_->ProcessIntegerTrail(decision)) return false;
963  product_detector_->ProcessTrailAtLevelOne();
964  }
965 
966  // Update the pseudo costs.
967  if (sat_solver_->CurrentDecisionLevel() > old_level &&
968  objective_var_ != kNoIntegerVariable) {
969  const IntegerValue new_obj_lb = integer_trail_->LowerBound(objective_var_);
970  const IntegerValue new_obj_ub = integer_trail_->UpperBound(objective_var_);
971  const IntegerValue objective_bound_change =
972  (new_obj_lb - old_obj_lb) + (old_obj_ub - new_obj_ub);
973  pseudo_costs_->UpdateCost(bound_changes, objective_bound_change);
974  }
975 
976  sat_solver_->AdvanceDeterministicTime(time_limit_);
977  return sat_solver_->ReapplyAssumptionsIfNeeded();
978 }
979 
981  if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
982 
983  SearchHeuristics& heuristics = *model_->GetOrCreate<SearchHeuristics>();
984  const int num_policies = heuristics.decision_policies.size();
985  CHECK_NE(num_policies, 0);
986  CHECK_EQ(num_policies, heuristics.restart_policies.size());
987 
988  // Note that it is important to do the level-zero propagation if it wasn't
989  // already done because EnqueueDecisionAndBackjumpOnConflict() assumes that
990  // the solver is in a "propagated" state.
991  //
992  // TODO(user): We have the issue that at level zero. calling the propagation
993  // loop more than once can propagate more! This is because we call the LP
994  // again and again on each level zero propagation. This is causing some
995  // CHECKs() to fail in multithread (rarely) because when we associate new
996  // literals to integer ones, Propagate() is indirectly called. Not sure yet
997  // how to fix.
998  if (!sat_solver_->FinishPropagation()) return sat_solver_->UnsatStatus();
999 
1000  // Main search loop.
1001  const int64_t old_num_conflicts = sat_solver_->num_failures();
1002  const int64_t conflict_limit = parameters_.max_number_of_conflicts();
1003  int64_t num_decisions_since_last_lp_record_ = 0;
1004  int64_t num_decisions_without_probing = 0;
1005  while (!time_limit_->LimitReached() &&
1006  (sat_solver_->num_failures() - old_num_conflicts < conflict_limit)) {
1007  // If needed, restart and switch decision_policy.
1008  if (heuristics.restart_policies[heuristics.policy_index]()) {
1009  if (!sat_solver_->RestoreSolverToAssumptionLevel()) {
1010  return sat_solver_->UnsatStatus();
1011  }
1012  heuristics.policy_index = (heuristics.policy_index + 1) % num_policies;
1013  }
1014 
1015  if (!BeforeTakingDecision()) return sat_solver_->UnsatStatus();
1016 
1017  LiteralIndex decision = kNoLiteralIndex;
1018  while (true) {
1019  if (heuristics.next_decision_override != nullptr) {
1020  // Note that to properly count the num_times, we do not want to move
1021  // this function, but actually call that copy.
1022  decision = GetDecision(heuristics.next_decision_override);
1023  if (decision == kNoLiteralIndex) {
1024  heuristics.next_decision_override = nullptr;
1025  }
1026  }
1027  if (decision == kNoLiteralIndex) {
1028  decision =
1029  GetDecision(heuristics.decision_policies[heuristics.policy_index]);
1030  }
1031 
1032  // Probing?
1033  //
1034  // TODO(user): Be smarter about what variables we probe, we can
1035  // also do more than one.
1036  if (decision != kNoLiteralIndex &&
1037  sat_solver_->CurrentDecisionLevel() == 0 &&
1038  parameters_.probing_period_at_root() > 0 &&
1039  ++num_decisions_without_probing >=
1040  parameters_.probing_period_at_root()) {
1041  num_decisions_without_probing = 0;
1042  if (!prober_->ProbeOneVariable(Literal(decision).Variable())) {
1043  return SatSolver::INFEASIBLE;
1044  }
1045  DCHECK_EQ(sat_solver_->CurrentDecisionLevel(), 0);
1046 
1047  // We need to check after the probing that the literal is not fixed,
1048  // otherwise we just go to the next decision.
1049  if (sat_solver_->Assignment().LiteralIsAssigned(Literal(decision))) {
1050  continue;
1051  }
1052  }
1053  break;
1054  }
1055 
1056  // No decision means that we reached a leave of the search tree and that
1057  // we have a feasible solution.
1058  //
1059  // Tricky: If the time limit is reached during the final propagation when
1060  // all variables are fixed, there is no guarantee that the propagation
1061  // responsible for testing the validity of the solution was run to
1062  // completion. So we cannot report a feasible solution.
1063  if (time_limit_->LimitReached()) return SatSolver::LIMIT_REACHED;
1064  if (decision == kNoLiteralIndex) {
1065  // Save the current polarity of all Booleans in the solution. It will be
1066  // followed for the next SAT decisions. This is known to be a good policy
1067  // for optimization problem. Note that for decision problem we don't care
1068  // since we are just done as soon as a solution is found.
1069  //
1070  // This idea is kind of "well known", see for instance the "LinSBPS"
1071  // submission to the maxSAT 2018 competition by Emir Demirovic and Peter
1072  // Stuckey where they show it is a good idea and provide more references.
1073  if (parameters_.use_optimization_hints()) {
1074  auto* sat_decision = model_->GetOrCreate<SatDecisionPolicy>();
1075  const auto& trail = *model_->GetOrCreate<Trail>();
1076  for (int i = 0; i < trail.Index(); ++i) {
1077  sat_decision->SetAssignmentPreference(trail[i], 0.0);
1078  }
1079  }
1080  return SatSolver::FEASIBLE;
1081  }
1082 
1083  if (!TakeDecision(Literal(decision))) {
1084  return sat_solver_->UnsatStatus();
1085  }
1086 
1087  // In multi-thread, we really only want to save the LP relaxation for thread
1088  // with high linearization level to avoid to pollute the repository with
1089  // sub-par lp solutions.
1090  //
1091  // TODO(user): Experiment more around dynamically changing the
1092  // threshold for storing LP solutions in the pool. Alternatively expose
1093  // this as parameter so this can be tuned later.
1094  //
1095  // TODO(user): Avoid adding the same solution many time if the LP didn't
1096  // change. Avoid adding solution that are too deep in the tree (most
1097  // variable fixed). Also use a callback rather than having this here, we
1098  // don't want this file to depend on cp_model.proto.
1099  if (model_->Get<SharedLPSolutionRepository>() != nullptr &&
1100  parameters_.linearization_level() >= 2) {
1101  num_decisions_since_last_lp_record_++;
1102  if (num_decisions_since_last_lp_record_ >= 100) {
1103  // NOTE: We can actually record LP solutions more frequently. However
1104  // this process is time consuming and workers waste a lot of time doing
1105  // this. To avoid this we don't record solutions after each decision.
1106  RecordLPRelaxationValues(model_);
1107  num_decisions_since_last_lp_record_ = 0;
1108  }
1109  }
1110  }
1111  return SatSolver::Status::LIMIT_REACHED;
1112 }
1113 
1115  const std::vector<Literal>& assumptions, Model* model) {
1116  SatSolver* const solver = model->GetOrCreate<SatSolver>();
1117 
1118  // Sync the bound first.
1119  if (!solver->ResetToLevelZero()) return solver->UnsatStatus();
1120  auto* level_zero_callbacks = model->GetOrCreate<LevelZeroCallbackHelper>();
1121  for (const auto& cb : level_zero_callbacks->callbacks) {
1122  if (!cb()) {
1123  solver->NotifyThatModelIsUnsat();
1124  return solver->UnsatStatus();
1125  }
1126  }
1127 
1128  // Add the assumptions if any and solve.
1129  if (!solver->ResetWithGivenAssumptions(assumptions)) {
1130  return solver->UnsatStatus();
1131  }
1132  return model->GetOrCreate<IntegerSearchHelper>()->SolveIntegerProblem();
1133 }
1134 
1136  const IntegerVariable num_vars =
1137  model->GetOrCreate<IntegerTrail>()->NumIntegerVariables();
1138  std::vector<IntegerVariable> all_variables;
1139  for (IntegerVariable var(0); var < num_vars; ++var) {
1140  all_variables.push_back(var);
1141  }
1142 
1143  SearchHeuristics& heuristics = *model->GetOrCreate<SearchHeuristics>();
1144  heuristics.policy_index = 0;
1145  heuristics.decision_policies = {SequentialSearch(
1147  FirstUnassignedVarAtItsMinHeuristic(all_variables, model)})};
1149  return ResetAndSolveIntegerProblem(/*assumptions=*/{}, model);
1150 }
1151 
1153  Model* model)
1154  : model_(model),
1155  sat_solver_(model->GetOrCreate<SatSolver>()),
1156  time_limit_(model->GetOrCreate<TimeLimit>()),
1157  trail_(model->GetOrCreate<Trail>()),
1158  integer_trail_(model->GetOrCreate<IntegerTrail>()),
1159  encoder_(model->GetOrCreate<IntegerEncoder>()),
1160  parameters_(*(model->GetOrCreate<SatParameters>())),
1161  level_zero_callbacks_(model->GetOrCreate<LevelZeroCallbackHelper>()),
1162  prober_(model->GetOrCreate<Prober>()),
1163  shared_response_manager_(model->Mutable<SharedResponseManager>()),
1164  shared_bounds_manager_(model->Mutable<SharedBoundsManager>()),
1165  active_limit_(parameters_.shaving_search_deterministic_time()) {
1166  auto* mapping = model_->GetOrCreate<CpModelMapping>();
1167  absl::flat_hash_set<BooleanVariable> visited;
1168  for (int v = 0; v < model_proto.variables_size(); ++v) {
1169  if (mapping->IsBoolean(v)) {
1170  const BooleanVariable bool_var = mapping->Literal(v).Variable();
1171  const auto [_, inserted] = visited.insert(bool_var);
1172  if (inserted) {
1173  bool_vars_.push_back(bool_var);
1174  }
1175  } else {
1176  IntegerVariable var = mapping->Integer(v);
1177  if (integer_trail_->IsFixed(var)) continue;
1178  int_vars_.push_back(var);
1179  }
1180  }
1181  VLOG(2) << "Start continuous probing with " << bool_vars_.size()
1182  << " Boolean variables, and " << int_vars_.size()
1183  << " integer variables"
1184  << ", deterministic time limit = "
1185  << time_limit_->GetDeterministicLimit() << " on " << model_->Name();
1186  last_logging_time_ = absl::Now();
1187 }
1188 
1189 // Continuous probing procedure.
1190 // TODO(user):
1191 // - sort variables before the iteration (statically or dynamically)
1192 // - compress clause databases regularly (especially the implication graph)
1193 // - better interleaving of the probing and shaving phases
1194 // - move the shaving code directly in the probing class
1195 // - probe all variables and not just the model ones
1197  // Backtrack to level 0 in case we are not there.
1198  if (!sat_solver_->ResetToLevelZero()) return SatSolver::INFEASIBLE;
1199 
1200  while (!time_limit_->LimitReached()) {
1201  // Run sat in-processing to reduce the size of the clause database.
1202  if (parameters_.use_sat_inprocessing() &&
1203  !model_->GetOrCreate<Inprocessing>()->InprocessingRound()) {
1204  return SatSolver::INFEASIBLE;
1205  }
1206 
1207  // Probe each Boolean variable at most once per loop.
1208  probed_bool_vars_.clear();
1209  probed_literals_.clear();
1210 
1211  // Store current statistics to detect an iteration without any improvement.
1212  const int64_t initial_num_literals_fixed =
1213  prober_->num_new_literals_fixed();
1214  const int64_t initial_num_bounds_shaved = num_bounds_shaved_;
1215 
1216  // Probe variable bounds.
1217  // TODO(user): Probe optional variables.
1218  for (; current_int_var_ < int_vars_.size(); ++current_int_var_) {
1219  const IntegerVariable int_var = int_vars_[current_int_var_];
1220  if (integer_trail_->IsFixed(int_var) ||
1221  integer_trail_->IsOptional(int_var)) {
1222  continue;
1223  }
1224 
1225  if (!ImportFromSharedClasses()) {
1226  return SatSolver::INFEASIBLE;
1227  }
1228 
1229  if (time_limit_->LimitReached()) {
1230  return SatSolver::LIMIT_REACHED;
1231  }
1232 
1233  const BooleanVariable shave_lb =
1234  encoder_
1236  int_var, integer_trail_->LowerBound(int_var)))
1237  .Variable();
1238  const auto [_lb, lb_inserted] = probed_bool_vars_.insert(shave_lb);
1239  if (lb_inserted) {
1240  if (!prober_->ProbeOneVariable(shave_lb)) {
1241  return SatSolver::INFEASIBLE;
1242  }
1243  num_literals_probed_++;
1244  }
1245 
1246  const BooleanVariable shave_ub =
1247  encoder_
1249  int_var, integer_trail_->UpperBound(int_var)))
1250  .Variable();
1251  const auto [_ub, ub_inserted] = probed_bool_vars_.insert(shave_ub);
1252  if (ub_inserted) {
1253  if (!prober_->ProbeOneVariable(shave_ub)) {
1254  return SatSolver::INFEASIBLE;
1255  }
1256  num_literals_probed_++;
1257  }
1258 
1259  if (parameters_.use_shaving_in_probing_search()) {
1260  const SatSolver::Status lb_status =
1261  ShaveLiteral(Literal(shave_lb, true));
1262  if (ReportStatus(lb_status)) return lb_status;
1263 
1264  const SatSolver::Status ub_status =
1265  ShaveLiteral(Literal(shave_ub, true));
1266  if (ReportStatus(ub_status)) return ub_status;
1267  }
1268 
1269  LogStatistics();
1270  }
1271 
1272  // Probe Boolean variables from the model.
1273  for (; current_bool_var_ < bool_vars_.size(); ++current_bool_var_) {
1274  const BooleanVariable& bool_var = bool_vars_[current_bool_var_];
1275 
1276  if (sat_solver_->Assignment().VariableIsAssigned(bool_var)) continue;
1277 
1278  if (!ImportFromSharedClasses()) {
1279  return SatSolver::INFEASIBLE;
1280  }
1281 
1282  if (time_limit_->LimitReached()) {
1283  return SatSolver::LIMIT_REACHED;
1284  }
1285 
1286  const auto [_, inserted] = probed_bool_vars_.insert(bool_var);
1287  if (inserted) {
1288  if (!prober_->ProbeOneVariable(bool_var)) {
1289  return SatSolver::INFEASIBLE;
1290  }
1291  num_literals_probed_++;
1292  }
1293 
1294  const Literal literal(bool_var, true);
1295  if (parameters_.use_shaving_in_probing_search() &&
1296  !sat_solver_->Assignment().LiteralIsAssigned(literal)) {
1297  const SatSolver::Status true_status = ShaveLiteral(literal);
1298  if (ReportStatus(true_status)) return true_status;
1299  if (true_status == SatSolver::ASSUMPTIONS_UNSAT) continue;
1300 
1301  const SatSolver::Status false_status = ShaveLiteral(literal.Negated());
1302  if (ReportStatus(false_status)) return false_status;
1303  }
1304 
1305  LogStatistics();
1306  }
1307 
1308  // Adjust the active_limit.
1309  {
1310  const double deterministic_time =
1311  parameters_.shaving_search_deterministic_time();
1312  const bool something_has_been_detected =
1313  num_bounds_shaved_ != initial_num_bounds_shaved ||
1314  prober_->num_new_literals_fixed() != initial_num_literals_fixed;
1315  if (something_has_been_detected) { // Reset the limit.
1316  active_limit_ = deterministic_time;
1317  } else if (active_limit_ < 25 * deterministic_time) { // Bump the limit.
1318  active_limit_ += deterministic_time;
1319  }
1320  }
1321 
1322  ++iteration_;
1323  current_bool_var_ = 0;
1324  current_int_var_ = 0;
1325  }
1326  return SatSolver::LIMIT_REACHED;
1327 }
1328 
1329 bool ContinuousProber::ImportFromSharedClasses() {
1330  if (!sat_solver_->ResetToLevelZero()) return false;
1331  for (const auto& cb : level_zero_callbacks_->callbacks) {
1332  if (!cb()) {
1333  sat_solver_->NotifyThatModelIsUnsat();
1334  return false;
1335  }
1336  }
1337  return true;
1338 }
1339 
1340 SatSolver::Status ContinuousProber::ShaveLiteral(Literal literal) {
1341  const auto [_, inserted] = probed_literals_.insert(literal.Index());
1342  if (trail_->Assignment().LiteralIsAssigned(literal) || !inserted) {
1343  return SatSolver::LIMIT_REACHED;
1344  }
1345  num_bounds_tried_++;
1346 
1347  const double original_dtime_limit = time_limit_->GetDeterministicLimit();
1348  time_limit_->ChangeDeterministicLimit(
1349  std::min(original_dtime_limit,
1350  time_limit_->GetElapsedDeterministicTime() + active_limit_));
1351  const SatSolver::Status status =
1353  time_limit_->ChangeDeterministicLimit(original_dtime_limit);
1354  if (ReportStatus(status)) return status;
1355 
1357  num_bounds_shaved_++;
1358  }
1359 
1360  // Important: we want to reset the solver right away, as we check for
1361  // fixed variable in the main loop!
1362  if (!sat_solver_->ResetToLevelZero()) return SatSolver::INFEASIBLE;
1363  return status;
1364 }
1365 
1366 bool ContinuousProber::ReportStatus(const SatSolver::Status status) {
1368 }
1369 
1370 void ContinuousProber::LogStatistics() {
1371  if (shared_response_manager_ == nullptr ||
1372  shared_bounds_manager_ == nullptr) {
1373  return;
1374  }
1375  shared_response_manager_->LogPeriodicMessage(
1376  "Probe",
1377  absl::StrCat("#iterations:", iteration_, " #literals fixed/probed:",
1378  prober_->num_new_literals_fixed(), "/", num_literals_probed_,
1379  " #bounds shaved/tried:", num_bounds_shaved_, "/",
1380  num_bounds_tried_, " #new_integer_bounds:",
1381  shared_bounds_manager_->NumBoundsExported("probing"),
1382  ", #new_binary_clauses:", prober_->num_new_binary_clauses()),
1383  parameters_.log_frequency_in_seconds(), &last_logging_time_);
1384 }
1385 
1386 } // namespace sat
1387 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
double GetDeterministicLimit() const
Queries the deterministic time limit.
Definition: time_limit.h:303
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
void ChangeDeterministicLimit(double new_limit)
Overwrites the deterministic time limit with the new value.
Definition: time_limit.h:296
ContinuousProber(const CpModelProto &model_proto, Model *model)
sat::Literal Literal(int ref) const
bool ProcessIntegerTrail(Literal first_decision)
Literal GetOrCreateAssociatedLiteral(IntegerLiteral i_lit)
Definition: integer.cc:254
LiteralIndex GetDecision(const std::function< BooleanOrIntegerLiteral()> &f)
IntegerVariable FirstUnassignedVariable() const
Definition: integer.cc:1498
bool IsCurrentlyIgnored(IntegerVariable i) const
Definition: integer.h:775
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1565
IntegerVariable NextVariableToBranchOnInPropagationLoop() const
Definition: integer.cc:1465
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1561
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1557
bool IsOptional(IntegerVariable i) const
Definition: integer.h:772
LiteralIndex Index() const
Definition: sat_base.h:90
BooleanVariable Variable() const
Definition: sat_base.h:86
std::string DebugString() const
Definition: sat_base.h:99
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
const std::string & Name() const
Definition: sat/model.h:181
T Get(std::function< T(const Model &)> f) const
Similar to Add() but this is const.
Definition: sat/model.h:91
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
Definition: sat/model.h:110
int num_new_binary_clauses() const
Definition: probing.h:88
bool ProbeOneVariable(BooleanVariable b)
Definition: probing.cc:197
int num_new_literals_fixed() const
Definition: probing.h:87
std::vector< VariableBoundChange > GetBoundChanges(Literal decision)
void UpdateCost(const std::vector< VariableBoundChange > &bound_changes, IntegerValue obj_bound_improvement)
Definition: pseudo_costs.cc:50
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
void Backtrack(int target_level)
Definition: sat_solver.cc:1004
bool ResetWithGivenAssumptions(const std::vector< Literal > &assumptions)
Definition: sat_solver.cc:598
int NumBoundsExported(const std::string &worker_name)
void LogPeriodicMessage(const std::string &prefix, const std::string &message, double frequency_seconds, absl::Time *last_logging_time)
ValueType GetVariableValueInSolution(int var_index, int solution_index) const
const VariablesAssignment & Assignment() const
Definition: sat_base.h:402
bool LiteralIsAssigned(Literal literal) const
Definition: sat_base.h:167
bool VariableIsAssigned(BooleanVariable var) const
Definition: sat_base.h:172
SatParameters parameters
SharedRelaxationSolutionRepository * relaxation_solutions
CpModelProto const * model_proto
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
absl::Status status
Definition: g_gurobi.cc:41
GRBmodel * model
int index
void RandomizeDecisionHeuristic(absl::BitGenRef random, SatParameters *parameters)
Definition: sat/util.cc:61
std::function< BooleanOrIntegerLiteral()> FirstUnassignedVarAtItsMinHeuristic(const std::vector< IntegerVariable > &vars, Model *model)
std::function< int64_t(const Model &)> UpperBound(IntegerVariable v)
Definition: integer.h:1781
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
SatSolver::Status ResetAndSolveIntegerProblem(const std::vector< Literal > &assumptions, Model *model)
const LiteralIndex kNoLiteralIndex(-1)
IntegerLiteral AtMinValue(IntegerVariable var, IntegerTrail *integer_trail)
void RecordLPRelaxationValues(Model *model)
Definition: rins.cc:33
std::function< BooleanOrIntegerLiteral()> ShaveObjectiveLb(Model *model)
IntegerLiteral GreaterOrEqualToMiddleValue(IntegerVariable var, IntegerTrail *integer_trail)
IntegerLiteral SplitAroundGivenValue(IntegerVariable var, IntegerValue value, Model *model)
std::function< BooleanOrIntegerLiteral()> UnassignedVarWithLowestMinAtItsMinHeuristic(const std::vector< IntegerVariable > &vars, Model *model)
SatSolver::Status SolveIntegerProblemWithLazyEncoding(Model *model)
std::function< bool()> SatSolverRestartPolicy(Model *model)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
std::function< BooleanOrIntegerLiteral()> FollowHint(const std::vector< BooleanOrIntegerVariable > &vars, const std::vector< IntegerValue > &values, Model *model)
std::function< bool()> RestartEveryKFailures(int k, SatSolver *solver)
std::function< BooleanOrIntegerLiteral()> SchedulingSearchHeuristic(Model *model)
IntegerLiteral ChooseBestObjectiveValue(IntegerVariable var, Model *model)
std::function< BooleanOrIntegerLiteral()> RandomizeOnRestartHeuristic(Model *model)
void ConfigureSearchHeuristics(Model *model)
std::vector< std::function< BooleanOrIntegerLiteral()> > CompleteHeuristics(const std::vector< std::function< BooleanOrIntegerLiteral()>> &incomplete_heuristics, const std::function< BooleanOrIntegerLiteral()> &completion_heuristic)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition: integer.h:149
std::function< BooleanOrIntegerLiteral()> IntegerValueSelectionHeuristic(std::function< BooleanOrIntegerLiteral()> var_selection_heuristic, Model *model)
std::function< BooleanOrIntegerLiteral()> SatSolverHeuristic(Model *model)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:46
std::function< BooleanOrIntegerLiteral()> SequentialSearch(std::vector< std::function< BooleanOrIntegerLiteral()>> heuristics)
IntegerLiteral SplitAroundLpValue(IntegerVariable var, Model *model)
IntegerLiteral SplitUsingBestSolutionValueInRepository(IntegerVariable var, const SharedSolutionRepository< int64_t > &solution_repo, Model *model)
const BooleanVariable kNoBooleanVariable(-1)
bool LinearizedPartIsLarge(Model *model)
const int kUnsatTrailIndex
Definition: sat_solver.h:57
std::function< BooleanOrIntegerLiteral()> PseudoCost(Model *model)
std::function< BooleanOrIntegerLiteral()> SequentialValueSelection(std::vector< std::function< IntegerLiteral(IntegerVariable)>> value_selection_heuristics, std::function< BooleanOrIntegerLiteral()> var_selection_heuristic, Model *model)
Collection of objects used to extend the Constraint Solver library.
Literal literal
Definition: optimization.cc:88
int64_t time
Definition: resource.cc:1694
Rev< int64_t > start_min
Rev< int64_t > end_min
std::optional< int64_t > end
int64_t start
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1505
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1499
std::vector< std::function< bool()> > callbacks
std::vector< std::function< bool()> > restart_policies
std::function< BooleanOrIntegerLiteral()> hint_search
std::function< BooleanOrIntegerLiteral()> fixed_search
std::function< BooleanOrIntegerLiteral()> next_decision_override
std::function< BooleanOrIntegerLiteral()> user_search
std::vector< std::function< BooleanOrIntegerLiteral()> > decision_policies
#define VLOG(verboselevel)
Definition: vlog.h:39