OR-Tools  9.0
integer_search.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
15 
16 #include <cmath>
17 #include <cstdint>
18 #include <functional>
19 #include <vector>
20 
21 #include "absl/container/flat_hash_set.h"
22 #include "absl/types/span.h"
25 #include "ortools/sat/integer.h"
27 #include "ortools/sat/probing.h"
29 #include "ortools/sat/rins.h"
30 #include "ortools/sat/sat_base.h"
35 #include "ortools/sat/util.h"
36 
37 namespace operations_research {
38 namespace sat {
39 
40 IntegerLiteral AtMinValue(IntegerVariable var, IntegerTrail* integer_trail) {
41  DCHECK(!integer_trail->IsCurrentlyIgnored(var));
42  const IntegerValue lb = integer_trail->LowerBound(var);
43  DCHECK_LE(lb, integer_trail->UpperBound(var));
44  if (lb == integer_trail->UpperBound(var)) return IntegerLiteral();
46 }
47 
49  const auto& variables =
50  model->GetOrCreate<ObjectiveDefinition>()->objective_impacting_variables;
51  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
52  if (variables.contains(var)) {
53  return AtMinValue(var, integer_trail);
54  } else if (variables.contains(NegationOf(var))) {
55  return AtMinValue(NegationOf(var), integer_trail);
56  }
57  return IntegerLiteral();
58 }
59 
61  IntegerTrail* integer_trail) {
62  const IntegerValue var_lb = integer_trail->LowerBound(var);
63  const IntegerValue var_ub = integer_trail->UpperBound(var);
64  CHECK_LT(var_lb, var_ub);
65 
66  const IntegerValue chosen_value =
67  var_lb + std::max(IntegerValue(1), (var_ub - var_lb) / IntegerValue(2));
68  return IntegerLiteral::GreaterOrEqual(var, chosen_value);
69 }
70 
71 IntegerLiteral SplitAroundGivenValue(IntegerVariable var, IntegerValue value,
72  Model* model) {
73  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
74  const IntegerValue lb = integer_trail->LowerBound(var);
75  const IntegerValue ub = integer_trail->UpperBound(var);
76 
77  const absl::flat_hash_set<IntegerVariable>& variables =
78  model->GetOrCreate<ObjectiveDefinition>()->objective_impacting_variables;
79 
80  // Heuristic: Prefer the objective direction first. Reference: Conflict-Driven
81  // Heuristics for Mixed Integer Programming (2019) by Jakob Witzig and Ambros
82  // Gleixner.
83  // NOTE: The value might be out of bounds. In that case we return
84  // kNoLiteralIndex.
85  const bool branch_down_feasible = value >= lb && value < ub;
86  const bool branch_up_feasible = value > lb && value <= ub;
87  if (variables.contains(var) && branch_down_feasible) {
89  } else if (variables.contains(NegationOf(var)) && branch_up_feasible) {
91  } else if (branch_down_feasible) {
93  } else if (branch_up_feasible) {
95  }
96  return IntegerLiteral();
97 }
98 
100  auto* parameters = model->GetOrCreate<SatParameters>();
101  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
102  auto* lp_dispatcher = model->GetOrCreate<LinearProgrammingDispatcher>();
103  DCHECK(!integer_trail->IsCurrentlyIgnored(var));
104 
105  const IntegerVariable positive_var = PositiveVariable(var);
106  const LinearProgrammingConstraint* lp =
107  gtl::FindWithDefault(*lp_dispatcher, positive_var, nullptr);
108 
109  // We only use this if the sub-lp has a solution, and depending on the value
110  // of exploit_all_lp_solution() if it is a pure-integer solution.
111  if (lp == nullptr || !lp->HasSolution()) return IntegerLiteral();
112  if (!parameters->exploit_all_lp_solution() && !lp->SolutionIsInteger()) {
113  return IntegerLiteral();
114  }
115 
116  // TODO(user): Depending if we branch up or down, this might not exclude the
117  // LP value, which is potentially a bad thing.
118  //
119  // TODO(user): Why is the reduced cost doing things differently?
120  const IntegerValue value = IntegerValue(
121  static_cast<int64_t>(std::round(lp->GetSolutionValue(positive_var))));
122 
123  // Because our lp solution might be from higher up in the tree, it
124  // is possible that value is now outside the domain of positive_var.
125  // In this case, this function will return an invalid literal.
126  return SplitAroundGivenValue(positive_var, value, model);
127 }
128 
130  IntegerVariable var, const SharedSolutionRepository<int64_t>& solution_repo,
131  Model* model) {
132  if (solution_repo.NumSolutions() == 0) {
133  return IntegerLiteral();
134  }
135 
136  const IntegerVariable positive_var = PositiveVariable(var);
137  const int proto_var =
138  model->Get<CpModelMapping>()->GetProtoVariableFromIntegerVariable(
139  positive_var);
140 
141  if (proto_var < 0) {
142  return IntegerLiteral();
143  }
144 
145  const IntegerValue value(solution_repo.GetVariableValueInSolution(
146  proto_var, /*solution_index=*/0));
147  return SplitAroundGivenValue(positive_var, value, model);
148 }
149 
150 // TODO(user): the complexity caused by the linear scan in this heuristic and
151 // the one below is ok when search_branching is set to SAT_SEARCH because it is
152 // not executed often, but otherwise it is done for each search decision,
153 // which seems expensive. Improve.
155  const std::vector<IntegerVariable>& vars, Model* model) {
156  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
157  return [/*copy*/ vars, integer_trail]() {
158  for (const IntegerVariable var : vars) {
159  // Note that there is no point trying to fix a currently ignored variable.
160  if (integer_trail->IsCurrentlyIgnored(var)) continue;
161  const IntegerLiteral decision = AtMinValue(var, integer_trail);
162  if (decision.IsValid()) return BooleanOrIntegerLiteral(decision);
163  }
164  return BooleanOrIntegerLiteral();
165  };
166 }
167 
168 std::function<BooleanOrIntegerLiteral()>
170  const std::vector<IntegerVariable>& vars, Model* model) {
171  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
172  return [/*copy */ vars, integer_trail]() {
173  IntegerVariable candidate = kNoIntegerVariable;
174  IntegerValue candidate_lb;
175  for (const IntegerVariable var : vars) {
176  if (integer_trail->IsCurrentlyIgnored(var)) continue;
177  const IntegerValue lb = integer_trail->LowerBound(var);
178  if (lb < integer_trail->UpperBound(var) &&
179  (candidate == kNoIntegerVariable || lb < candidate_lb)) {
180  candidate = var;
181  candidate_lb = lb;
182  }
183  }
184  if (candidate == kNoIntegerVariable) return BooleanOrIntegerLiteral();
185  return BooleanOrIntegerLiteral(AtMinValue(candidate, integer_trail));
186  };
187 }
188 
190  std::vector<std::function<BooleanOrIntegerLiteral()>> heuristics) {
191  return [heuristics]() {
192  for (const auto& h : heuristics) {
193  const BooleanOrIntegerLiteral decision = h();
194  if (decision.HasValue()) return decision;
195  }
196  return BooleanOrIntegerLiteral();
197  };
198 }
199 
201  std::vector<std::function<IntegerLiteral(IntegerVariable)>>
202  value_selection_heuristics,
203  std::function<BooleanOrIntegerLiteral()> var_selection_heuristic,
204  Model* model) {
205  auto* encoder = model->GetOrCreate<IntegerEncoder>();
206  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
207  auto* sat_policy = model->GetOrCreate<SatDecisionPolicy>();
208  return [=]() {
209  // Get the current decision.
210  const BooleanOrIntegerLiteral current_decision = var_selection_heuristic();
211  if (!current_decision.HasValue()) return current_decision;
212 
213  // When we are in the "stable" phase, we prefer to follow the SAT polarity
214  // heuristic.
215  if (current_decision.boolean_literal_index != kNoLiteralIndex &&
216  sat_policy->InStablePhase()) {
217  return current_decision;
218  }
219 
220  // IntegerLiteral case.
221  if (current_decision.boolean_literal_index == kNoLiteralIndex) {
222  for (const auto& value_heuristic : value_selection_heuristics) {
223  const IntegerLiteral decision =
224  value_heuristic(current_decision.integer_literal.var);
225  if (decision.IsValid()) return BooleanOrIntegerLiteral(decision);
226  }
227  return current_decision;
228  }
229 
230  // Boolean case. We try to decode the Boolean decision to see if it is
231  // associated with an integer variable.
232  for (const IntegerLiteral l : encoder->GetAllIntegerLiterals(
233  Literal(current_decision.boolean_literal_index))) {
234  if (integer_trail->IsCurrentlyIgnored(l.var)) continue;
235 
236  // Sequentially try the value selection heuristics.
237  for (const auto& value_heuristic : value_selection_heuristics) {
238  const IntegerLiteral decision = value_heuristic(l.var);
239  if (decision.IsValid()) return BooleanOrIntegerLiteral(decision);
240  }
241  }
242 
243  return current_decision;
244  };
245 }
246 
248  auto* lp_constraints =
250  int num_lp_variables = 0;
251  for (LinearProgrammingConstraint* lp : *lp_constraints) {
252  num_lp_variables += lp->NumVariables();
253  }
254  const int num_integer_variables =
255  model->GetOrCreate<IntegerTrail>()->NumIntegerVariables().value() / 2;
256  return (num_integer_variables <= 2 * num_lp_variables);
257 }
258 
259 // TODO(user): Experiment more with value selection heuristics.
261  std::function<BooleanOrIntegerLiteral()> var_selection_heuristic,
262  Model* model) {
263  const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
264  std::vector<std::function<IntegerLiteral(IntegerVariable)>>
265  value_selection_heuristics;
266 
267  // LP based value.
268  //
269  // Note that we only do this if a big enough percentage of the problem
270  // variables appear in the LP relaxation.
272  (parameters.exploit_integer_lp_solution() ||
273  parameters.exploit_all_lp_solution())) {
274  value_selection_heuristics.push_back([model](IntegerVariable var) {
276  });
277  }
278 
279  // Solution based value.
280  if (parameters.exploit_best_solution()) {
281  auto* response_manager = model->Get<SharedResponseManager>();
282  if (response_manager != nullptr) {
283  VLOG(2) << "Using best solution value selection heuristic.";
284  value_selection_heuristics.push_back(
285  [model, response_manager](IntegerVariable var) {
287  var, response_manager->SolutionsRepository(), model);
288  });
289  }
290  }
291 
292  // Relaxation Solution based value.
293  if (parameters.exploit_relaxation_solution()) {
294  auto* relaxation_solutions =
296  if (relaxation_solutions != nullptr) {
297  value_selection_heuristics.push_back(
298  [model, relaxation_solutions](IntegerVariable var) {
299  VLOG(2) << "Using relaxation solution value selection heuristic.";
302  });
303  }
304  }
305 
306  // Objective based value.
307  if (parameters.exploit_objective()) {
308  value_selection_heuristics.push_back([model](IntegerVariable var) {
310  });
311  }
312 
313  return SequentialValueSelection(value_selection_heuristics,
314  var_selection_heuristic, model);
315 }
316 
318  SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
319  Trail* trail = model->GetOrCreate<Trail>();
320  SatDecisionPolicy* decision_policy = model->GetOrCreate<SatDecisionPolicy>();
321  return [sat_solver, trail, decision_policy] {
322  const bool all_assigned = trail->Index() == sat_solver->NumVariables();
323  if (all_assigned) return BooleanOrIntegerLiteral();
324  const Literal result = decision_policy->NextBranch();
325  CHECK(!sat_solver->Assignment().LiteralIsAssigned(result));
326  return BooleanOrIntegerLiteral(result.Index());
327  };
328 }
329 
331  auto* objective = model->Get<ObjectiveDefinition>();
332  const bool has_objective =
333  objective != nullptr && objective->objective_var != kNoIntegerVariable;
334  if (!has_objective) {
335  return []() { return BooleanOrIntegerLiteral(); };
336  }
337 
338  auto* pseudo_costs = model->GetOrCreate<PseudoCosts>();
339  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
340  return [pseudo_costs, integer_trail]() {
341  const IntegerVariable chosen_var = pseudo_costs->GetBestDecisionVar();
342  if (chosen_var == kNoIntegerVariable) return BooleanOrIntegerLiteral();
343 
344  // TODO(user): This will be overidden by the value decision heuristic in
345  // almost all cases.
347  GreaterOrEqualToMiddleValue(chosen_var, integer_trail));
348  };
349 }
350 
352  Model* model) {
353  SatSolver* sat_solver = model->GetOrCreate<SatSolver>();
354  SatDecisionPolicy* decision_policy = model->GetOrCreate<SatDecisionPolicy>();
355 
356  // TODO(user): Add other policy and perform more experiments.
357  std::function<BooleanOrIntegerLiteral()> sat_policy =
359  std::vector<std::function<BooleanOrIntegerLiteral()>> policies{
360  sat_policy, SequentialSearch({PseudoCost(model), sat_policy})};
361  // The higher weight for the sat policy is because this policy actually
362  // contains a lot of variation as we randomize the sat parameters.
363  // TODO(user,user): Do more experiments to find better distribution.
364  std::discrete_distribution<int> var_dist{3 /*sat_policy*/, 1 /*Pseudo cost*/};
365 
366  // Value selection.
367  std::vector<std::function<IntegerLiteral(IntegerVariable)>>
368  value_selection_heuristics;
369  std::vector<int> value_selection_weight;
370 
371  // LP Based value.
372  value_selection_heuristics.push_back([model](IntegerVariable var) {
374  });
375  value_selection_weight.push_back(8);
376 
377  // Solution based value.
378  auto* response_manager = model->Get<SharedResponseManager>();
379  if (response_manager != nullptr) {
380  value_selection_heuristics.push_back(
381  [model, response_manager](IntegerVariable var) {
383  var, response_manager->SolutionsRepository(), model);
384  });
385  value_selection_weight.push_back(5);
386  }
387 
388  // Relaxation solution based value.
390  if (relaxation_solutions != nullptr) {
391  value_selection_heuristics.push_back(
392  [model, relaxation_solutions](IntegerVariable var) {
395  });
396  value_selection_weight.push_back(3);
397  }
398 
399  // Middle value.
400  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
401  value_selection_heuristics.push_back([integer_trail](IntegerVariable var) {
402  return GreaterOrEqualToMiddleValue(var, integer_trail);
403  });
404  value_selection_weight.push_back(1);
405 
406  // Min value.
407  value_selection_heuristics.push_back([integer_trail](IntegerVariable var) {
408  return AtMinValue(var, integer_trail);
409  });
410  value_selection_weight.push_back(1);
411 
412  // Special case: Don't change the decision value.
413  value_selection_weight.push_back(10);
414 
415  // TODO(user): These distribution values are just guessed values. They need
416  // to be tuned.
417  std::discrete_distribution<int> val_dist(value_selection_weight.begin(),
418  value_selection_weight.end());
419 
420  int policy_index = 0;
421  int val_policy_index = 0;
422  auto* encoder = model->GetOrCreate<IntegerEncoder>();
423  return [=]() mutable {
424  if (sat_solver->CurrentDecisionLevel() == 0) {
425  auto* random = model->GetOrCreate<ModelRandomGenerator>();
426  RandomizeDecisionHeuristic(random, model->GetOrCreate<SatParameters>());
427  decision_policy->ResetDecisionHeuristic();
428 
429  // Select the variable selection heuristic.
430  policy_index = var_dist(*(random));
431 
432  // Select the value selection heuristic.
433  val_policy_index = val_dist(*(random));
434  }
435 
436  // Get the current decision.
437  const BooleanOrIntegerLiteral current_decision = policies[policy_index]();
438  if (!current_decision.HasValue()) return current_decision;
439 
440  // Special case: Don't override the decision value.
441  if (val_policy_index >= value_selection_heuristics.size()) {
442  return current_decision;
443  }
444 
445  if (current_decision.boolean_literal_index == kNoLiteralIndex) {
446  const IntegerLiteral new_decision =
447  value_selection_heuristics[val_policy_index](
448  current_decision.integer_literal.var);
449  if (new_decision.IsValid()) return BooleanOrIntegerLiteral(new_decision);
450  return current_decision;
451  }
452 
453  // Decode the decision and get the variable.
454  for (const IntegerLiteral l : encoder->GetAllIntegerLiterals(
455  Literal(current_decision.boolean_literal_index))) {
456  if (integer_trail->IsCurrentlyIgnored(l.var)) continue;
457 
458  // Try the selected policy.
459  const IntegerLiteral new_decision =
460  value_selection_heuristics[val_policy_index](l.var);
461  if (new_decision.IsValid()) return BooleanOrIntegerLiteral(new_decision);
462  }
463 
464  // Selected policy failed. Revert back to original decision.
465  return current_decision;
466  };
467 }
468 
469 // TODO(user): Avoid the quadratic algorithm!!
471  const std::vector<BooleanOrIntegerVariable>& vars,
472  const std::vector<IntegerValue>& values, Model* model) {
473  const Trail* trail = model->GetOrCreate<Trail>();
474  const IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
475  return [=] { // copy
476  for (int i = 0; i < vars.size(); ++i) {
477  const IntegerValue value = values[i];
478  if (vars[i].bool_var != kNoBooleanVariable) {
479  if (trail->Assignment().VariableIsAssigned(vars[i].bool_var)) continue;
481  Literal(vars[i].bool_var, value == 1).Index());
482  } else {
483  const IntegerVariable integer_var = vars[i].int_var;
484  if (integer_trail->IsCurrentlyIgnored(integer_var)) continue;
485  if (integer_trail->IsFixed(integer_var)) continue;
486 
487  const IntegerVariable positive_var = PositiveVariable(integer_var);
488  const IntegerLiteral decision = SplitAroundGivenValue(
489  positive_var, positive_var != integer_var ? -value : value, model);
490  if (decision.IsValid()) return BooleanOrIntegerLiteral(decision);
491 
492  // If the value is outside the current possible domain, we skip it.
493  continue;
494  }
495  }
496  return BooleanOrIntegerLiteral();
497  };
498 }
499 
500 std::function<bool()> RestartEveryKFailures(int k, SatSolver* solver) {
501  bool reset_at_next_call = true;
502  int next_num_failures = 0;
503  return [=]() mutable {
504  if (reset_at_next_call) {
505  next_num_failures = solver->num_failures() + k;
506  reset_at_next_call = false;
507  } else if (solver->num_failures() >= next_num_failures) {
508  reset_at_next_call = true;
509  }
510  return reset_at_next_call;
511  };
512 }
513 
514 std::function<bool()> SatSolverRestartPolicy(Model* model) {
515  auto policy = model->GetOrCreate<RestartPolicy>();
516  return [policy]() { return policy->ShouldRestart(); };
517 }
518 
519 namespace {
520 
521 std::function<BooleanOrIntegerLiteral()> WrapIntegerLiteralHeuristic(
522  std::function<IntegerLiteral()> f) {
523  return [f]() { return BooleanOrIntegerLiteral(f()); };
524 }
525 
526 } // namespace
527 
529  SearchHeuristics& heuristics = *model->GetOrCreate<SearchHeuristics>();
530  CHECK(heuristics.fixed_search != nullptr);
531  heuristics.policy_index = 0;
532  heuristics.decision_policies.clear();
533  heuristics.restart_policies.clear();
534 
535  const SatParameters& parameters = *(model->GetOrCreate<SatParameters>());
536  switch (parameters.search_branching()) {
537  case SatParameters::AUTOMATIC_SEARCH: {
538  std::function<BooleanOrIntegerLiteral()> decision_policy;
539  if (parameters.randomize_search()) {
540  decision_policy = RandomizeOnRestartHeuristic(model);
541  } else {
542  decision_policy = SatSolverHeuristic(model);
543  }
544  decision_policy =
545  SequentialSearch({decision_policy, heuristics.fixed_search});
546  decision_policy = IntegerValueSelectionHeuristic(decision_policy, model);
547  heuristics.decision_policies = {decision_policy};
549  return;
550  }
551  case SatParameters::FIXED_SEARCH: {
552  // Not all Boolean might appear in fixed_search(), so once there is no
553  // decision left, we fix all Booleans that are still undecided.
554  heuristics.decision_policies = {SequentialSearch(
555  {heuristics.fixed_search, SatSolverHeuristic(model)})};
556 
557  if (parameters.randomize_search()) {
559  return;
560  }
561 
562  // TODO(user): We might want to restart if external info is available.
563  // Code a custom restart for this?
564  auto no_restart = []() { return false; };
565  heuristics.restart_policies = {no_restart};
566  return;
567  }
568  case SatParameters::HINT_SEARCH: {
569  CHECK(heuristics.hint_search != nullptr);
570  heuristics.decision_policies = {
572  heuristics.fixed_search})};
573  auto no_restart = []() { return false; };
574  heuristics.restart_policies = {no_restart};
575  return;
576  }
577  case SatParameters::PORTFOLIO_SEARCH: {
578  // TODO(user): This is not used in any of our default config. remove?
579  // It make also no sense to choose a value in the LP heuristic and then
580  // override it with IntegerValueSelectionHeuristic(), clean that up.
581  std::vector<std::function<BooleanOrIntegerLiteral()>> base_heuristics;
582  base_heuristics.push_back(heuristics.fixed_search);
583  for (const auto& ct :
584  *(model->GetOrCreate<LinearProgrammingConstraintCollection>())) {
585  base_heuristics.push_back(WrapIntegerLiteralHeuristic(
586  ct->HeuristicLpReducedCostBinary(model)));
587  base_heuristics.push_back(WrapIntegerLiteralHeuristic(
588  ct->HeuristicLpMostInfeasibleBinary(model)));
589  }
591  base_heuristics, SequentialSearch({SatSolverHeuristic(model),
592  heuristics.fixed_search}));
593  for (auto& ref : heuristics.decision_policies) {
595  }
596  heuristics.restart_policies.assign(heuristics.decision_policies.size(),
598  return;
599  }
600  case SatParameters::LP_SEARCH: {
601  std::vector<std::function<BooleanOrIntegerLiteral()>> lp_heuristics;
602  for (const auto& ct :
603  *(model->GetOrCreate<LinearProgrammingConstraintCollection>())) {
604  lp_heuristics.push_back(WrapIntegerLiteralHeuristic(
605  ct->HeuristicLpReducedCostAverageBranching()));
606  }
607  if (lp_heuristics.empty()) { // Revert to fixed search.
608  heuristics.decision_policies = {SequentialSearch(
609  {heuristics.fixed_search, SatSolverHeuristic(model)})},
611  return;
612  }
614  lp_heuristics, SequentialSearch({SatSolverHeuristic(model),
615  heuristics.fixed_search}));
616  heuristics.restart_policies.assign(heuristics.decision_policies.size(),
618  return;
619  }
620  case SatParameters::PSEUDO_COST_SEARCH: {
621  std::function<BooleanOrIntegerLiteral()> search =
623  heuristics.fixed_search});
624  heuristics.decision_policies = {
627  return;
628  }
629  case SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH: {
630  std::function<BooleanOrIntegerLiteral()> search = SequentialSearch(
632  heuristics.decision_policies = {search};
633  heuristics.restart_policies = {
634  RestartEveryKFailures(10, model->GetOrCreate<SatSolver>())};
635  return;
636  }
637  }
638 }
639 
640 std::vector<std::function<BooleanOrIntegerLiteral()>> CompleteHeuristics(
641  const std::vector<std::function<BooleanOrIntegerLiteral()>>&
642  incomplete_heuristics,
643  const std::function<BooleanOrIntegerLiteral()>& completion_heuristic) {
644  std::vector<std::function<BooleanOrIntegerLiteral()>> complete_heuristics;
645  complete_heuristics.reserve(incomplete_heuristics.size());
646  for (const auto& incomplete : incomplete_heuristics) {
647  complete_heuristics.push_back(
648  SequentialSearch({incomplete, completion_heuristic}));
649  }
650  return complete_heuristics;
651 }
652 
654  TimeLimit* time_limit = model->GetOrCreate<TimeLimit>();
655  if (time_limit->LimitReached()) return SatSolver::LIMIT_REACHED;
656 
657  SearchHeuristics& heuristics = *model->GetOrCreate<SearchHeuristics>();
658  const int num_policies = heuristics.decision_policies.size();
659  CHECK_NE(num_policies, 0);
660  CHECK_EQ(num_policies, heuristics.restart_policies.size());
661 
662  // This is needed for recording the pseudo-costs.
663  IntegerVariable objective_var = kNoIntegerVariable;
664  {
665  const ObjectiveDefinition* objective = model->Get<ObjectiveDefinition>();
666  if (objective != nullptr) objective_var = objective->objective_var;
667  }
668 
669  // Note that it is important to do the level-zero propagation if it wasn't
670  // already done because EnqueueDecisionAndBackjumpOnConflict() assumes that
671  // the solver is in a "propagated" state.
672  SatSolver* const sat_solver = model->GetOrCreate<SatSolver>();
673 
674  // TODO(user): We have the issue that at level zero. calling the propagation
675  // loop more than once can propagate more! This is because we call the LP
676  // again and again on each level zero propagation. This is causing some
677  // CHECKs() to fail in multithread (rarely) because when we associate new
678  // literals to integer ones, Propagate() is indirectly called. Not sure yet
679  // how to fix.
680  if (!sat_solver->FinishPropagation()) return sat_solver->UnsatStatus();
681 
682  // Create and initialize pseudo costs.
683  // TODO(user): If this ever shows up in a cpu profile, find a way to not
684  // execute the code when pseudo costs are not needed.
685  PseudoCosts* pseudo_costs = model->GetOrCreate<PseudoCosts>();
686 
687  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
688  auto* encoder = model->GetOrCreate<IntegerEncoder>();
689  auto* implied_bounds = model->GetOrCreate<ImpliedBounds>();
690  auto* prober = model->GetOrCreate<Prober>();
691 
692  const SatParameters& sat_parameters = *(model->GetOrCreate<SatParameters>());
693 
694  // Main search loop.
695  const int64_t old_num_conflicts = sat_solver->num_failures();
696  const int64_t conflict_limit = sat_parameters.max_number_of_conflicts();
697  int64_t num_decisions_since_last_lp_record_ = 0;
698  int64_t num_decisions_without_probing = 0;
699  while (!time_limit->LimitReached() &&
700  (sat_solver->num_failures() - old_num_conflicts < conflict_limit)) {
701  // If needed, restart and switch decision_policy.
702  if (heuristics.restart_policies[heuristics.policy_index]()) {
703  if (!sat_solver->RestoreSolverToAssumptionLevel()) {
704  return sat_solver->UnsatStatus();
705  }
706  heuristics.policy_index = (heuristics.policy_index + 1) % num_policies;
707  }
708 
709  // If we pushed root level deductions, we restart to incorporate them.
710  // Note that in the present of assumptions, it is important to return to
711  // the level zero first ! otherwise, the new deductions will not be
712  // incorporated and the solver will loop forever.
713  if (integer_trail->HasPendingRootLevelDeduction()) {
714  sat_solver->Backtrack(0);
715  if (!sat_solver->RestoreSolverToAssumptionLevel()) {
716  return sat_solver->UnsatStatus();
717  }
718  }
719 
720  if (sat_solver->CurrentDecisionLevel() == 0) {
721  if (!implied_bounds->EnqueueNewDeductions()) {
722  return SatSolver::INFEASIBLE;
723  }
724 
725  auto* level_zero_callbacks =
726  model->GetOrCreate<LevelZeroCallbackHelper>();
727  for (const auto& cb : level_zero_callbacks->callbacks) {
728  if (!cb()) {
729  return SatSolver::INFEASIBLE;
730  }
731  }
732 
733  if (sat_parameters.use_sat_inprocessing() &&
734  !model->GetOrCreate<Inprocessing>()->InprocessingRound()) {
735  return SatSolver::INFEASIBLE;
736  }
737  }
738 
739  LiteralIndex decision = kNoLiteralIndex;
740  while (true) {
741  BooleanOrIntegerLiteral new_decision;
742  if (integer_trail->InPropagationLoop()) {
743  const IntegerVariable var =
744  integer_trail->NextVariableToBranchOnInPropagationLoop();
745  if (var != kNoIntegerVariable) {
746  new_decision.integer_literal =
747  GreaterOrEqualToMiddleValue(var, integer_trail);
748  }
749  }
750  if (!new_decision.HasValue()) {
751  new_decision = heuristics.decision_policies[heuristics.policy_index]();
752  }
753  if (!new_decision.HasValue() &&
754  integer_trail->CurrentBranchHadAnIncompletePropagation()) {
755  const IntegerVariable var = integer_trail->FirstUnassignedVariable();
756  if (var != kNoIntegerVariable) {
757  new_decision.integer_literal = AtMinValue(var, integer_trail);
758  }
759  }
760  if (!new_decision.HasValue()) break;
761 
762  // Convert integer decision to literal one if needed.
763  //
764  // TODO(user): Ideally it would be cool to delay the creation even more
765  // until we have a conflict with these decisions, but it is currrently
766  // hard to do so.
767  if (new_decision.boolean_literal_index != kNoLiteralIndex) {
768  decision = new_decision.boolean_literal_index;
769  } else {
770  decision =
771  encoder->GetOrCreateAssociatedLiteral(new_decision.integer_literal)
772  .Index();
773  }
774 
775  if (sat_solver->Assignment().LiteralIsAssigned(Literal(decision))) {
776  // TODO(user): It would be nicer if this can never happen. For now, it
777  // does because of the Propagate() not reaching the fixed point as
778  // mentionned in a TODO above. As a work-around, we display a message
779  // but do not crash and recall the decision heuristic.
780  VLOG(1) << "Trying to take a decision that is already assigned!"
781  << " Fix this. Continuing for now...";
782  continue;
783  }
784 
785  // Probing.
786  if (sat_solver->CurrentDecisionLevel() == 0 &&
787  sat_parameters.probing_period_at_root() > 0 &&
788  ++num_decisions_without_probing >=
789  sat_parameters.probing_period_at_root()) {
790  num_decisions_without_probing = 0;
791  // TODO(user,user): Be smarter about what variables we probe, we can
792  // also do more than one.
793  if (!prober->ProbeOneVariable(Literal(decision).Variable())) {
794  return SatSolver::INFEASIBLE;
795  }
796  DCHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
797 
798  // We need to check after the probing that the literal is not fixed,
799  // otherwise we just go to the next decision.
800  if (sat_solver->Assignment().LiteralIsAssigned(Literal(decision))) {
801  continue;
802  }
803  }
804  break;
805  }
806 
807  // No decision means that we reached a leave of the search tree and that
808  // we have a feasible solution.
809  //
810  // Tricky: If the time limit is reached during the final propagation when
811  // all variables are fixed, there is no guarantee that the propagation
812  // responsible for testing the validity of the solution was run to
813  // completion. So we cannot report a feasible solution.
814  if (time_limit->LimitReached()) return SatSolver::LIMIT_REACHED;
815  if (decision == kNoLiteralIndex) {
816  // Save the current polarity of all Booleans in the solution. It will be
817  // followed for the next SAT decisions. This is known to be a good policy
818  // for optimization problem. Note that for decision problem we don't care
819  // since we are just done as soon as a solution is found.
820  //
821  // This idea is kind of "well known", see for instance the "LinSBPS"
822  // submission to the maxSAT 2018 competition by Emir Demirovic and Peter
823  // Stuckey where they show it is a good idea and provide more references.
824  if (model->GetOrCreate<SatParameters>()->use_optimization_hints()) {
825  auto* sat_decision = model->GetOrCreate<SatDecisionPolicy>();
826  const auto& trail = *model->GetOrCreate<Trail>();
827  for (int i = 0; i < trail.Index(); ++i) {
828  sat_decision->SetAssignmentPreference(trail[i], 0.0);
829  }
830  }
831  return SatSolver::FEASIBLE;
832  }
833 
834  // Record the changelist and objective bounds for updating pseudo costs.
835  const std::vector<PseudoCosts::VariableBoundChange> bound_changes =
836  GetBoundChanges(decision, model);
837  IntegerValue old_obj_lb = kMinIntegerValue;
838  IntegerValue old_obj_ub = kMaxIntegerValue;
839  if (objective_var != kNoIntegerVariable) {
840  old_obj_lb = integer_trail->LowerBound(objective_var);
841  old_obj_ub = integer_trail->UpperBound(objective_var);
842  }
843  const int old_level = sat_solver->CurrentDecisionLevel();
844 
845  // TODO(user): on some problems, this function can be quite long. Expand
846  // so that we can check the time limit at each step?
847  sat_solver->EnqueueDecisionAndBackjumpOnConflict(Literal(decision));
848 
849  // Update the implied bounds each time we enqueue a literal at level zero.
850  // This is "almost free", so we might as well do it.
851  if (old_level == 0 && sat_solver->CurrentDecisionLevel() == 1) {
852  implied_bounds->ProcessIntegerTrail(Literal(decision));
853  }
854 
855  // Update the pseudo costs.
856  if (sat_solver->CurrentDecisionLevel() > old_level &&
857  objective_var != kNoIntegerVariable) {
858  const IntegerValue new_obj_lb = integer_trail->LowerBound(objective_var);
859  const IntegerValue new_obj_ub = integer_trail->UpperBound(objective_var);
860  const IntegerValue objective_bound_change =
861  (new_obj_lb - old_obj_lb) + (old_obj_ub - new_obj_ub);
862  pseudo_costs->UpdateCost(bound_changes, objective_bound_change);
863  }
864 
866  if (!sat_solver->ReapplyAssumptionsIfNeeded()) {
867  return sat_solver->UnsatStatus();
868  }
869 
870  // TODO(user): Experiment more around dynamically changing the
871  // threshold for storing LP solutions in the pool. Alternatively expose
872  // this as parameter so this can be tuned later.
873  //
874  // TODO(user): Avoid adding the same solution many time if the LP didn't
875  // change. Avoid adding solution that are too deep in the tree (most
876  // variable fixed). Also use a callback rather than having this here, we
877  // don't want this file to depend on cp_model.proto.
878  if (model->Get<SharedLPSolutionRepository>() != nullptr) {
879  num_decisions_since_last_lp_record_++;
880  if (num_decisions_since_last_lp_record_ >= 100) {
881  // NOTE: We can actually record LP solutions more frequently. However
882  // this process is time consuming and workers waste a lot of time doing
883  // this. To avoid this we don't record solutions after each decision.
885  num_decisions_since_last_lp_record_ = 0;
886  }
887  }
888  }
889  return SatSolver::Status::LIMIT_REACHED;
890 }
891 
893  const std::vector<Literal>& assumptions, Model* model) {
894  SatSolver* const solver = model->GetOrCreate<SatSolver>();
895 
896  // Sync the bound first.
897  if (!solver->ResetToLevelZero()) return solver->UnsatStatus();
898  auto* level_zero_callbacks = model->GetOrCreate<LevelZeroCallbackHelper>();
899  for (const auto& cb : level_zero_callbacks->callbacks) {
900  if (!cb()) return SatSolver::INFEASIBLE;
901  }
902 
903  // Add the assumptions if any and solve.
904  if (!solver->ResetWithGivenAssumptions(assumptions)) {
905  return solver->UnsatStatus();
906  }
907  return SolveIntegerProblem(model);
908 }
909 
911  const IntegerVariable num_vars =
912  model->GetOrCreate<IntegerTrail>()->NumIntegerVariables();
913  std::vector<IntegerVariable> all_variables;
914  for (IntegerVariable var(0); var < num_vars; ++var) {
915  all_variables.push_back(var);
916  }
917 
918  SearchHeuristics& heuristics = *model->GetOrCreate<SearchHeuristics>();
919  heuristics.policy_index = 0;
920  heuristics.decision_policies = {SequentialSearch(
922  FirstUnassignedVarAtItsMinHeuristic(all_variables, model)})};
924  return ResetAndSolveIntegerProblem(/*assumptions=*/{}, model);
925 }
926 
928  const std::vector<BooleanVariable>& bool_vars,
929  const std::vector<IntegerVariable>& int_vars,
930  const std::function<void()>& feasible_solution_observer, Model* model) {
931  VLOG(1) << "Start continuous probing with " << bool_vars.size()
932  << " Boolean variables, and " << int_vars.size()
933  << " integer variables";
934 
935  SatSolver* solver = model->GetOrCreate<SatSolver>();
936  TimeLimit* time_limit = model->GetOrCreate<TimeLimit>();
937  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
938  IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
939  const SatParameters& sat_parameters = *(model->GetOrCreate<SatParameters>());
940  auto* level_zero_callbacks = model->GetOrCreate<LevelZeroCallbackHelper>();
941  Prober* prober = model->GetOrCreate<Prober>();
942 
943  std::vector<BooleanVariable> active_vars;
944  std::vector<BooleanVariable> integer_bounds;
945  absl::flat_hash_set<BooleanVariable> integer_bounds_set;
946 
947  int loop = 0;
948  while (!time_limit->LimitReached()) {
949  VLOG(1) << "Probing loop " << loop++;
950 
951  // Sync the bounds first.
952  auto SyncBounds = [solver, &level_zero_callbacks]() {
953  if (!solver->ResetToLevelZero()) return false;
954  for (const auto& cb : level_zero_callbacks->callbacks) {
955  if (!cb()) return false;
956  }
957  return true;
958  };
959  if (!SyncBounds()) {
960  return SatSolver::INFEASIBLE;
961  }
962 
963  // Run sat in-processing to reduce the size of the clause database.
964  if (sat_parameters.use_sat_inprocessing() &&
965  !model->GetOrCreate<Inprocessing>()->InprocessingRound()) {
966  return SatSolver::INFEASIBLE;
967  }
968 
969  // TODO(user): Explore fast probing methods.
970 
971  // Probe each Boolean variable at most once per loop.
972  absl::flat_hash_set<BooleanVariable> probed;
973 
974  // Probe variable bounds.
975  // TODO(user,user): Probe optional variables.
976  for (const IntegerVariable int_var : int_vars) {
977  if (integer_trail->IsFixed(int_var) ||
978  integer_trail->IsOptional(int_var)) {
979  continue;
980  }
981 
982  const BooleanVariable shave_lb =
983  encoder
984  ->GetOrCreateAssociatedLiteral(IntegerLiteral::LowerOrEqual(
985  int_var, integer_trail->LowerBound(int_var)))
986  .Variable();
987  if (!probed.contains(shave_lb)) {
988  probed.insert(shave_lb);
989  if (!prober->ProbeOneVariable(shave_lb)) {
990  return SatSolver::INFEASIBLE;
991  }
992  }
993 
994  const BooleanVariable shave_ub =
995  encoder
996  ->GetOrCreateAssociatedLiteral(IntegerLiteral::GreaterOrEqual(
997  int_var, integer_trail->UpperBound(int_var)))
998  .Variable();
999  if (!probed.contains(shave_ub)) {
1000  probed.insert(shave_ub);
1001  if (!prober->ProbeOneVariable(shave_ub)) {
1002  return SatSolver::INFEASIBLE;
1003  }
1004  }
1005 
1006  if (!SyncBounds()) {
1007  return SatSolver::INFEASIBLE;
1008  }
1009  if (time_limit->LimitReached()) {
1010  return SatSolver::LIMIT_REACHED;
1011  }
1012  }
1013 
1014  // Probe Boolean variables from the model.
1015  for (const BooleanVariable& bool_var : bool_vars) {
1016  if (solver->Assignment().VariableIsAssigned(bool_var)) continue;
1017  if (time_limit->LimitReached()) {
1018  return SatSolver::LIMIT_REACHED;
1019  }
1020  if (!SyncBounds()) {
1021  return SatSolver::INFEASIBLE;
1022  }
1023  if (!probed.contains(bool_var)) {
1024  probed.insert(bool_var);
1025  if (!prober->ProbeOneVariable(bool_var)) {
1026  return SatSolver::INFEASIBLE;
1027  }
1028  }
1029  }
1030  }
1031  return SatSolver::LIMIT_REACHED;
1032 }
1033 
1034 } // namespace sat
1035 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
#define CHECK(condition)
Definition: base/logging.h:498
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:895
#define CHECK_LT(val1, val2)
Definition: base/logging.h:708
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:705
#define CHECK_NE(val1, val2)
Definition: base/logging.h:706
#define DCHECK(condition)
Definition: base/logging.h:892
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:893
#define VLOG(verboselevel)
Definition: base/logging.h:986
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
bool IsCurrentlyIgnored(IntegerVariable i) const
Definition: integer.h:630
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1309
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1305
LiteralIndex Index() const
Definition: sat_base.h:85
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
void UpdateCost(const std::vector< VariableBoundChange > &bound_changes, IntegerValue obj_bound_improvement)
Definition: pseudo_costs.cc:44
void AdvanceDeterministicTime(TimeLimit *limit)
Definition: sat_solver.h:423
const VariablesAssignment & Assignment() const
Definition: sat_solver.h:363
int EnqueueDecisionAndBackjumpOnConflict(Literal true_literal)
Definition: sat_solver.cc:500
void Backtrack(int target_level)
Definition: sat_solver.cc:889
bool ResetWithGivenAssumptions(const std::vector< Literal > &assumptions)
Definition: sat_solver.cc:537
ValueType GetVariableValueInSolution(int var_index, int solution_index) const
const VariablesAssignment & Assignment() const
Definition: sat_base.h:381
bool LiteralIsAssigned(Literal literal) const
Definition: sat_base.h:154
bool VariableIsAssigned(BooleanVariable var) const
Definition: sat_base.h:159
SatParameters parameters
SharedRelaxationSolutionRepository * relaxation_solutions
SharedTimeLimit * time_limit
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
const Collection::value_type::second_type & FindWithDefault(const Collection &collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
Definition: map_util.h:29
std::function< BooleanOrIntegerLiteral()> FirstUnassignedVarAtItsMinHeuristic(const std::vector< IntegerVariable > &vars, Model *model)
std::function< int64_t(const Model &)> UpperBound(IntegerVariable v)
Definition: integer.h:1478
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:26
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
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)
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)
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:139
std::function< BooleanOrIntegerLiteral()> IntegerValueSelectionHeuristic(std::function< BooleanOrIntegerLiteral()> var_selection_heuristic, Model *model)
SatSolver::Status SolveIntegerProblem(Model *model)
std::function< BooleanOrIntegerLiteral()> SatSolverHeuristic(Model *model)
std::vector< PseudoCosts::VariableBoundChange > GetBoundChanges(LiteralIndex decision, Model *model)
Definition: pseudo_costs.cc:99
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:29
SatSolver::Status ContinuousProbing(const std::vector< BooleanVariable > &bool_vars, const std::vector< IntegerVariable > &int_vars, const std::function< void()> &feasible_solution_observer, Model *model)
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)
void RandomizeDecisionHeuristic(URBG *random, SatParameters *parameters)
Definition: sat/util.h:101
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.
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1275
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1269
std::vector< std::function< bool()> > restart_policies
std::function< BooleanOrIntegerLiteral()> hint_search
std::function< BooleanOrIntegerLiteral()> fixed_search
std::vector< std::function< BooleanOrIntegerLiteral()> > decision_policies