OR-Tools  9.6
cp_model_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 <cstdint>
18 #include <functional>
19 #include <limits>
20 #include <string>
21 #include <utility>
22 #include <vector>
23 
24 #include "absl/container/flat_hash_map.h"
25 #include "absl/container/flat_hash_set.h"
26 #include "absl/flags/flag.h"
27 #include "absl/random/distributions.h"
28 #include "absl/strings/str_cat.h"
29 #include "absl/strings/string_view.h"
30 #include "ortools/base/logging.h"
31 #include "ortools/sat/cp_model.pb.h"
34 #include "ortools/sat/integer.h"
36 #include "ortools/sat/model.h"
37 #include "ortools/sat/sat_base.h"
38 #include "ortools/sat/sat_parameters.pb.h"
39 #include "ortools/sat/util.h"
41 
42 // TODO(user): remove this when the code is stable and does not use SCIP
43 // anymore.
44 ABSL_FLAG(bool, cp_model_use_max_hs, false, "Use max_hs in search portfolio.");
45 
46 namespace operations_research {
47 namespace sat {
48 
50  : mapping_(*model->GetOrCreate<CpModelMapping>()),
51  boolean_assignment_(model->GetOrCreate<Trail>()->Assignment()),
52  integer_trail_(*model->GetOrCreate<IntegerTrail>()),
53  integer_encoder_(*model->GetOrCreate<IntegerEncoder>()) {}
54 
55 int CpModelView::NumVariables() const { return mapping_.NumProtoVariables(); }
56 
57 bool CpModelView::IsFixed(int var) const {
58  if (mapping_.IsBoolean(var)) {
59  return boolean_assignment_.VariableIsAssigned(
60  mapping_.Literal(var).Variable());
61  } else if (mapping_.IsInteger(var)) {
62  return integer_trail_.IsFixed(mapping_.Integer(var));
63  }
64  return true; // Default.
65 }
66 
68  return mapping_.IsInteger(var) &&
69  integer_trail_.IsCurrentlyIgnored(mapping_.Integer(var));
70 }
71 
72 int64_t CpModelView::Min(int var) const {
73  if (mapping_.IsBoolean(var)) {
74  const Literal l = mapping_.Literal(var);
75  return boolean_assignment_.LiteralIsTrue(l) ? 1 : 0;
76  } else if (mapping_.IsInteger(var)) {
77  return integer_trail_.LowerBound(mapping_.Integer(var)).value();
78  }
79  return 0; // Default.
80 }
81 
82 int64_t CpModelView::Max(int var) const {
83  if (mapping_.IsBoolean(var)) {
84  const Literal l = mapping_.Literal(var);
85  return boolean_assignment_.LiteralIsFalse(l) ? 0 : 1;
86  } else if (mapping_.IsInteger(var)) {
87  return integer_trail_.UpperBound(mapping_.Integer(var)).value();
88  }
89  return 0; // Default.
90 }
91 
93  int64_t value) const {
94  DCHECK(!IsFixed(var));
96  if (mapping_.IsBoolean(var)) {
97  DCHECK(value == 0 || value == 1);
98  if (value == 1) {
99  result.boolean_literal_index = mapping_.Literal(var).Index();
100  }
101  } else if (mapping_.IsInteger(var)) {
103  mapping_.Integer(var), IntegerValue(value));
104  }
105  return result;
106 }
107 
109  int64_t value) const {
110  DCHECK(!IsFixed(var));
112  if (mapping_.IsBoolean(var)) {
113  DCHECK(value == 0 || value == 1);
114  if (value == 0) {
115  result.boolean_literal_index = mapping_.Literal(var).NegatedIndex();
116  }
117  } else if (mapping_.IsInteger(var)) {
119  IntegerValue(value));
120  }
121  return result;
122 }
123 
125  DCHECK(!IsFixed(var));
127  if (mapping_.IsBoolean(var)) {
128  result.boolean_literal_index = mapping_.Literal(var).NegatedIndex();
129  } else if (mapping_.IsInteger(var)) {
130  const IntegerVariable variable = mapping_.Integer(var);
131  const std::vector<ValueLiteralPair> encoding =
132  integer_encoder_.FullDomainEncoding(variable);
133 
134  // 5 values -> returns the second.
135  // 4 values -> returns the second too.
136  // Array is 0 based.
137  const int target = (encoding.size() + 1) / 2 - 1;
138  result.boolean_literal_index = encoding[target].literal.Index();
139  }
140  return result;
141 }
142 
143 // Stores one variable and its strategy value.
144 struct VarValue {
145  int ref;
146  int64_t value;
147 };
148 
149 namespace {
150 
151 // TODO(user): Save this somewhere instead of recomputing it.
152 bool ModelHasSchedulingConstraints(const CpModelProto& cp_model_proto) {
153  for (const ConstraintProto& ct : cp_model_proto.constraints()) {
154  if (ct.constraint_case() == ConstraintProto::kNoOverlap) return true;
155  if (ct.constraint_case() == ConstraintProto::kCumulative) return true;
156  }
157  return false;
158 }
159 
160 void AddDualSchedulingHeuristics(SatParameters& new_params) {
161  new_params.set_exploit_all_precedences(true);
162  new_params.set_use_hard_precedences_in_cumulative(true);
163  new_params.set_use_overload_checker_in_cumulative(true);
164  new_params.set_use_strong_propagation_in_disjunctive(true);
165  new_params.set_use_timetable_edge_finding_in_cumulative(true);
166 }
167 
168 } // namespace
169 
171  const std::vector<DecisionStrategyProto>& strategies, Model* model) {
172  const auto& view = *model->GetOrCreate<CpModelView>();
173  const auto& parameters = *model->GetOrCreate<SatParameters>();
174  auto* random = model->GetOrCreate<ModelRandomGenerator>();
175 
176  // Note that we copy strategies to keep the return function validity
177  // independently of the life of the passed vector.
178  return [&view, &parameters, random, strategies]() {
179  for (const DecisionStrategyProto& strategy : strategies) {
180  int candidate;
181  int64_t candidate_value = std::numeric_limits<int64_t>::max();
182 
183  // TODO(user): Improve the complexity if this becomes an issue which
184  // may be the case if we do a fixed_search.
185 
186  // To store equivalent variables in randomized search.
187  std::vector<VarValue> active_refs;
188 
189  int t_index = 0; // Index in strategy.transformations().
190  for (int i = 0; i < strategy.variables().size(); ++i) {
191  const int ref = strategy.variables(i);
192  const int var = PositiveRef(ref);
193  if (view.IsFixed(var) || view.IsCurrentlyFree(var)) continue;
194 
195  int64_t coeff(1);
196  int64_t offset(0);
197  while (t_index < strategy.transformations().size() &&
198  strategy.transformations(t_index).index() < i) {
199  ++t_index;
200  }
201  if (t_index < strategy.transformations_size() &&
202  strategy.transformations(t_index).index() == i) {
203  coeff = strategy.transformations(t_index).positive_coeff();
204  offset = strategy.transformations(t_index).offset();
205  }
206 
207  // TODO(user): deal with integer overflow in case of wrongly specified
208  // coeff? Note that if this is filled by the presolve it shouldn't
209  // happen since any feasible value in the new variable domain should be
210  // a feasible value of the original variable domain.
211  int64_t value(0);
212  int64_t lb = view.Min(var);
213  int64_t ub = view.Max(var);
214  if (!RefIsPositive(ref)) {
215  lb = -view.Max(var);
216  ub = -view.Min(var);
217  }
218  switch (strategy.variable_selection_strategy()) {
219  case DecisionStrategyProto::CHOOSE_FIRST:
220  break;
221  case DecisionStrategyProto::CHOOSE_LOWEST_MIN:
222  value = coeff * lb + offset;
223  break;
224  case DecisionStrategyProto::CHOOSE_HIGHEST_MAX:
225  value = -(coeff * ub + offset);
226  break;
227  case DecisionStrategyProto::CHOOSE_MIN_DOMAIN_SIZE:
228  value = coeff * (ub - lb + 1);
229  break;
230  case DecisionStrategyProto::CHOOSE_MAX_DOMAIN_SIZE:
231  value = -coeff * (ub - lb + 1);
232  break;
233  default:
234  LOG(FATAL) << "Unknown VariableSelectionStrategy "
235  << strategy.variable_selection_strategy();
236  }
237  if (value < candidate_value) {
238  candidate = ref;
239  candidate_value = value;
240  }
241  if (strategy.variable_selection_strategy() ==
242  DecisionStrategyProto::CHOOSE_FIRST &&
243  !parameters.randomize_search()) {
244  break;
245  } else if (parameters.randomize_search()) {
246  if (value <=
247  candidate_value + parameters.search_randomization_tolerance()) {
248  active_refs.push_back({ref, value});
249  }
250  }
251  }
252 
253  if (candidate_value == std::numeric_limits<int64_t>::max()) continue;
254  if (parameters.randomize_search()) {
255  CHECK(!active_refs.empty());
256  const IntegerValue threshold(
257  candidate_value + parameters.search_randomization_tolerance());
258  auto is_above_tolerance = [threshold](const VarValue& entry) {
259  return entry.value > threshold;
260  };
261  // Remove all values above tolerance.
262  active_refs.erase(std::remove_if(active_refs.begin(), active_refs.end(),
263  is_above_tolerance),
264  active_refs.end());
265  const int winner = absl::Uniform<int>(*random, 0, active_refs.size());
266  candidate = active_refs[winner].ref;
267  }
268 
269  DecisionStrategyProto::DomainReductionStrategy selection =
270  strategy.domain_reduction_strategy();
271  if (!RefIsPositive(candidate)) {
272  switch (selection) {
273  case DecisionStrategyProto::SELECT_MIN_VALUE:
274  selection = DecisionStrategyProto::SELECT_MAX_VALUE;
275  break;
276  case DecisionStrategyProto::SELECT_MAX_VALUE:
277  selection = DecisionStrategyProto::SELECT_MIN_VALUE;
278  break;
279  case DecisionStrategyProto::SELECT_LOWER_HALF:
280  selection = DecisionStrategyProto::SELECT_UPPER_HALF;
281  break;
282  case DecisionStrategyProto::SELECT_UPPER_HALF:
283  selection = DecisionStrategyProto::SELECT_LOWER_HALF;
284  break;
285  default:
286  break;
287  }
288  }
289 
290  const int var = PositiveRef(candidate);
291  const int64_t lb = view.Min(var);
292  const int64_t ub = view.Max(var);
293  switch (selection) {
294  case DecisionStrategyProto::SELECT_MIN_VALUE:
295  return view.LowerOrEqual(var, lb);
296  case DecisionStrategyProto::SELECT_MAX_VALUE:
297  return view.GreaterOrEqual(var, ub);
298  case DecisionStrategyProto::SELECT_LOWER_HALF:
299  return view.LowerOrEqual(var, lb + (ub - lb) / 2);
300  case DecisionStrategyProto::SELECT_UPPER_HALF:
301  return view.GreaterOrEqual(var, ub - (ub - lb) / 2);
302  case DecisionStrategyProto::SELECT_MEDIAN_VALUE:
303  return view.MedianValue(var);
304  default:
305  LOG(FATAL) << "Unknown DomainReductionStrategy "
306  << strategy.domain_reduction_strategy();
307  }
308  }
309  return BooleanOrIntegerLiteral();
310  };
311 }
312 
314  const CpModelProto& cp_model_proto, Model* model) {
315  std::vector<DecisionStrategyProto> strategies;
316  for (const DecisionStrategyProto& proto : cp_model_proto.search_strategy()) {
317  strategies.push_back(proto);
318  }
319  return ConstructSearchStrategyInternal(strategies, model);
320 }
321 
323  const CpModelProto& cp_model_proto,
324  const std::vector<IntegerVariable>& variable_mapping,
325  IntegerVariable objective_var, Model* model) {
326  std::vector<std::function<BooleanOrIntegerLiteral()>> heuristics;
327 
328  // We start by the user specified heuristic.
329  const auto& params = *model->GetOrCreate<SatParameters>();
330  if (params.search_branching() != SatParameters::PARTIAL_FIXED_SEARCH) {
331  heuristics.push_back(ConstructUserSearchStrategy(cp_model_proto, model));
332  }
333 
334  // If there are some scheduling constraint, we complete with a custom
335  // "scheduling" strategy.
336  if (ModelHasSchedulingConstraints(cp_model_proto)) {
337  heuristics.push_back(SchedulingSearchHeuristic(model));
338  }
339 
340  // If needed, we finish by instantiating anything left.
341  if (params.instantiate_all_variables()) {
342  std::vector<IntegerVariable> decisions;
343  for (const IntegerVariable var : variable_mapping) {
344  if (var == kNoIntegerVariable) continue;
345 
346  // Make sure we try to fix the objective to its lowest value first.
347  if (var == NegationOf(objective_var)) {
348  decisions.push_back(objective_var);
349  } else {
350  decisions.push_back(var);
351  }
352  }
353  heuristics.push_back(FirstUnassignedVarAtItsMinHeuristic(decisions, model));
354  }
355 
356  return SequentialSearch(heuristics);
357 }
358 
360  const CpModelProto& cp_model_proto,
361  const std::vector<IntegerVariable>& variable_mapping,
362  const std::function<BooleanOrIntegerLiteral()>& instrumented_strategy,
363  Model* model) {
364  std::vector<int> ref_to_display;
365  for (int i = 0; i < cp_model_proto.variables_size(); ++i) {
366  if (variable_mapping[i] == kNoIntegerVariable) continue;
367  if (cp_model_proto.variables(i).name().empty()) continue;
368  ref_to_display.push_back(i);
369  }
370  std::sort(ref_to_display.begin(), ref_to_display.end(), [&](int i, int j) {
371  return cp_model_proto.variables(i).name() <
372  cp_model_proto.variables(j).name();
373  });
374 
375  std::vector<std::pair<int64_t, int64_t>> old_domains(variable_mapping.size());
376  return [instrumented_strategy, model, variable_mapping, cp_model_proto,
377  old_domains, ref_to_display]() mutable {
378  const BooleanOrIntegerLiteral decision = instrumented_strategy();
379  if (!decision.HasValue()) return decision;
380 
381  if (decision.boolean_literal_index != kNoLiteralIndex) {
382  const Literal l = Literal(decision.boolean_literal_index);
383  LOG(INFO) << "Boolean decision " << l;
384  const auto& encoder = model->Get<IntegerEncoder>();
385  for (const IntegerLiteral i_lit : encoder->GetIntegerLiterals(l)) {
386  LOG(INFO) << " - associated with " << i_lit;
387  }
388  for (const auto [var, value] : encoder->GetEqualityLiterals(l)) {
389  LOG(INFO) << " - associated with " << var << " == " << value;
390  }
391  } else {
392  LOG(INFO) << "Integer decision " << decision.integer_literal;
393  }
394  const int level = model->Get<Trail>()->CurrentDecisionLevel();
395  std::string to_display =
396  absl::StrCat("Diff since last call, level=", level, "\n");
397  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
398  for (const int ref : ref_to_display) {
399  const IntegerVariable var = variable_mapping[ref];
400  const std::pair<int64_t, int64_t> new_domain(
401  integer_trail->LowerBound(var).value(),
402  integer_trail->UpperBound(var).value());
403  if (new_domain != old_domains[ref]) {
404  absl::StrAppend(&to_display, cp_model_proto.variables(ref).name(), " [",
405  old_domains[ref].first, ",", old_domains[ref].second,
406  "] -> [", new_domain.first, ",", new_domain.second,
407  "]\n");
408  old_domains[ref] = new_domain;
409  }
410  }
411  LOG(INFO) << to_display;
412  return decision;
413  };
414 }
415 
416 namespace {
417 
418 // This generates a valid random seed (base_seed + delta) without overflow.
419 // We assume |delta| is small.
420 int ValidSumSeed(int base_seed, int delta) {
421  CHECK_GE(delta, 0);
422  int64_t result = int64_t{base_seed} + int64_t{delta};
423  const int64_t int32max = int64_t{std::numeric_limits<int>::max()};
424  while (result > int32max) {
425  result -= int32max;
426  }
427  return static_cast<int>(result);
428 }
429 
430 } // namespace
431 
432 // Note: in flatzinc setting, we know we always have a fixed search defined.
433 //
434 // Things to try:
435 // - Specialize for purely boolean problems
436 // - Disable linearization_level options for non linear problems
437 // - Fast restart in randomized search
438 // - Different propatation levels for scheduling constraints
439 std::vector<SatParameters> GetDiverseSetOfParameters(
440  const SatParameters& base_params, const CpModelProto& cp_model) {
441  // Defines a set of named strategies so it is easier to read in one place
442  // the one that are used. See below.
443  absl::flat_hash_map<std::string, SatParameters> strategies;
444 
445  // The "default" name can be used for the base_params unchanged.
446  strategies["default"] = base_params;
447 
448  // Lp variations only.
449  {
450  SatParameters new_params = base_params;
451  new_params.set_linearization_level(0);
452  strategies["no_lp"] = new_params;
453  new_params.set_linearization_level(1);
454  strategies["default_lp"] = new_params;
455  new_params.set_linearization_level(2);
456  new_params.set_add_lp_constraints_lazily(false);
457  strategies["max_lp"] = new_params;
458  }
459 
460  // Core. Note that we disable the lp here because it is faster on the minizinc
461  // benchmark.
462  //
463  // TODO(user): Do more experiments, the LP with core could be useful, but we
464  // probably need to incorporate the newly created integer variables from the
465  // core algorithm into the LP.
466  {
467  SatParameters new_params = base_params;
468  new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
469  new_params.set_optimize_with_core(true);
470  new_params.set_linearization_level(0);
471  strategies["core"] = new_params;
472  }
473 
474  // It can be interesting to try core and lp.
475  {
476  SatParameters new_params = base_params;
477  new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
478  new_params.set_optimize_with_core(true);
479  new_params.set_linearization_level(1);
480  strategies["core_default_lp"] = new_params;
481  }
482 
483  {
484  SatParameters new_params = base_params;
485  new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
486  new_params.set_optimize_with_core(true);
487  new_params.set_linearization_level(2);
488  strategies["core_max_lp"] = new_params;
489  }
490 
491  {
492  SatParameters new_params = base_params;
493  new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
494  new_params.set_optimize_with_core(true);
495  new_params.set_optimize_with_max_hs(true);
496  strategies["max_hs"] = new_params;
497  }
498 
499  {
500  SatParameters new_params = base_params;
501  new_params.set_optimize_with_lb_tree_search(true);
502  new_params.set_linearization_level(2);
503  if (base_params.use_dual_scheduling_heuristics()) {
504  AddDualSchedulingHeuristics(new_params);
505  }
506 
507  // We want to spend more time on the LP here.
508  new_params.set_add_lp_constraints_lazily(false);
509  new_params.set_root_lp_iterations(100'000);
510 
511  // We do not want to change the objective_var lb from outside as it gives
512  // better result to only use locally derived reason in that algo.
513  new_params.set_share_objective_bounds(false);
514  strategies["lb_tree_search"] = new_params;
515  }
516 
517  {
518  SatParameters new_params = base_params;
519  new_params.set_linearization_level(1);
520  new_params.set_use_objective_lb_search(true);
521  if (base_params.use_dual_scheduling_heuristics()) {
522  AddDualSchedulingHeuristics(new_params);
523  }
524  strategies["objective_lb_search"] = new_params;
525 
526  new_params.set_linearization_level(0);
527  strategies["objective_lb_search_no_lp"] = new_params;
528 
529  new_params.set_linearization_level(2);
530  strategies["objective_lb_search_max_lp"] = new_params;
531  }
532 
533  {
534  SatParameters new_params = base_params;
535  new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
536  new_params.set_use_probing_search(true);
537  if (base_params.use_dual_scheduling_heuristics()) {
538  AddDualSchedulingHeuristics(new_params);
539  }
540  strategies["probing"] = new_params;
541 
542  new_params.set_linearization_level(0);
543  strategies["probing_no_lp"] = new_params;
544 
545  new_params.set_linearization_level(2);
546  strategies["probing_max_lp"] = new_params;
547  }
548 
549  // Search variation.
550  {
551  SatParameters new_params = base_params;
552  new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
553  strategies["auto"] = new_params;
554 
555  new_params.set_search_branching(SatParameters::FIXED_SEARCH);
556  strategies["fixed"] = new_params;
557 
558  new_params.set_search_branching(
559  SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH);
560  strategies["quick_restart"] = new_params;
561 
562  new_params.set_search_branching(
563  SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH);
564  new_params.set_linearization_level(0);
565  strategies["quick_restart_no_lp"] = new_params;
566 
567  new_params.set_search_branching(
568  SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH);
569  new_params.set_linearization_level(2);
570  strategies["quick_restart_max_lp"] = new_params;
571  }
572 
573  {
574  SatParameters new_params = base_params;
575  new_params.set_linearization_level(2);
576  new_params.set_search_branching(SatParameters::LP_SEARCH);
577  if (base_params.use_dual_scheduling_heuristics()) {
578  AddDualSchedulingHeuristics(new_params);
579  }
580  strategies["reduced_costs"] = new_params;
581  }
582 
583  {
584  SatParameters new_params = base_params;
585  new_params.set_linearization_level(2);
586  new_params.set_search_branching(SatParameters::PSEUDO_COST_SEARCH);
587  new_params.set_exploit_best_solution(true);
588  strategies["pseudo_costs"] = new_params;
589  }
590 
591  // Less encoding.
592  {
593  SatParameters new_params = base_params;
594  new_params.set_boolean_encoding_level(0);
595  strategies["less_encoding"] = new_params;
596  }
597 
598  // Add user defined ones.
599  for (const SatParameters& params : base_params.subsolver_params()) {
600  strategies[params.name()] = params;
601  }
602 
603  // We only use a "fixed search" worker if some strategy is specified or
604  // if we have a scheduling model.
605  //
606  // TODO(user): For scheduling, this is important to find good first solution
607  // but afterwards it is not really great and should probably be replaced by a
608  // LNS worker.
609  const bool use_fixed_strategy = !cp_model.search_strategy().empty() ||
610  ModelHasSchedulingConstraints(cp_model);
611 
612  // Our current set of strategies
613  //
614  // TODO(user): Avoid launching two strategies if they are the same,
615  // like if there is no lp, or everything is already linearized at level 1.
616  std::vector<std::string> names;
617 
618  // We use the default if empty.
619  if (base_params.subsolvers().empty()) {
620  names.push_back("default_lp");
621  names.push_back("fixed");
622  names.push_back("less_encoding");
623 
624  names.push_back("no_lp");
625  names.push_back("max_lp");
626  names.push_back("core");
627 
628  names.push_back("reduced_costs");
629  names.push_back("pseudo_costs");
630 
631  names.push_back("quick_restart");
632  names.push_back("quick_restart_no_lp");
633  names.push_back("lb_tree_search");
634  // Do not add objective_lb_search if core is active and num_workers <= 16.
635  if (cp_model.has_objective() &&
636  (cp_model.objective().vars().size() == 1 || // core is not active
637  base_params.num_workers() > 16)) {
638  names.push_back("objective_lb_search");
639  }
640  names.push_back("probing");
641  if (base_params.num_workers() >= 20) {
642  names.push_back("probing_max_lp");
643  }
644  if (base_params.num_workers() >= 24) {
645  names.push_back("objective_lb_search_max_lp");
646  }
647 #if !defined(__PORTABLE_PLATFORM__) && defined(USE_SCIP)
648  if (absl::GetFlag(FLAGS_cp_model_use_max_hs)) names.push_back("max_hs");
649 #endif // !defined(__PORTABLE_PLATFORM__) && defined(USE_SCIP)
650  } else {
651  for (const std::string& name : base_params.subsolvers()) {
652  // Hack for flatzinc. At the time of parameter setting, the objective is
653  // not expanded. So we do not know if core is applicable or not.
654  if (name == "core_or_no_lp") {
655  if (!cp_model.has_objective() ||
656  cp_model.objective().vars_size() <= 1) {
657  names.push_back("no_lp");
658  } else {
659  names.push_back("core");
660  }
661  } else {
662  names.push_back(name);
663  }
664  }
665  }
666 
667  // Add subsolvers.
668  for (const std::string& name : base_params.extra_subsolvers()) {
669  names.push_back(name);
670  }
671 
672  // Remove the names that should be ignored.
673  absl::flat_hash_set<std::string> to_ignore;
674  for (const std::string& name : base_params.ignore_subsolvers()) {
675  to_ignore.insert(name);
676  }
677  int new_size = 0;
678  for (const std::string& name : names) {
679  if (to_ignore.contains(name)) continue;
680  names[new_size++] = name;
681  }
682  names.resize(new_size);
683 
684  // Creates the diverse set of parameters with names and seed.
685  std::vector<SatParameters> result;
686  for (const std::string& name : names) {
687  if (!strategies.contains(name)) {
688  // TODO(user): Check that at parameter validation and return nice error
689  // instead.
690  LOG(WARNING) << "Unknown parameter name '" << name << "'";
691  continue;
692  }
693  SatParameters params = strategies.at(name);
694 
695  // Do some filtering.
696  if (!use_fixed_strategy &&
697  params.search_branching() == SatParameters::FIXED_SEARCH) {
698  continue;
699  }
700  // TODO(user): Enable probing_search in deterministic mode.
701  // Currently it timeouts on small problems as the deterministic time limit
702  // never hits the sharding limit.
703  if (params.use_probing_search() && params.interleave_search()) continue;
704 
705  // In the corner case of empty variable, lets not schedule the probing as
706  // it currently just loop forever instead of returning right away.
707  if (params.use_probing_search() && cp_model.variables().empty()) continue;
708 
709  if (cp_model.has_objective() && !cp_model.objective().vars().empty()) {
710  // Disable core search if only 1 term in the objective.
711  if (cp_model.objective().vars().size() == 1 &&
712  params.optimize_with_core()) {
713  continue;
714  }
715 
716  if (name == "less_encoding") continue;
717 
718  // Disable subsolvers that do not implement the determistic mode.
719  //
720  // TODO(user): Enable lb_tree_search in deterministic mode.
721  if (params.interleave_search() &&
722  (params.optimize_with_lb_tree_search() ||
723  params.use_objective_lb_search())) {
724  continue;
725  }
726  } else {
727  // Remove subsolvers that require an objective.
728  if (params.optimize_with_lb_tree_search()) continue;
729  if (params.optimize_with_core()) continue;
730  if (params.use_objective_lb_search()) continue;
731  if (params.search_branching() == SatParameters::LP_SEARCH) continue;
732  if (params.search_branching() == SatParameters::PSEUDO_COST_SEARCH) {
733  continue;
734  }
735  }
736 
737  // Add this strategy.
738  //
739  // TODO(user): Find a better randomization for the seed so that changing
740  // random_seed() has more impact?
741  params.set_name(name);
742  params.set_random_seed(
743  ValidSumSeed(base_params.random_seed(), result.size() + 1));
744  result.push_back(params);
745  }
746 
747  if (cp_model.has_objective() && !cp_model.objective().vars().empty()) {
748  // If there is an objective, the extra workers will use LNS.
749  // Make sure we have at least min_num_lns_workers() of them.
750  const int target = std::max(
751  1, base_params.num_workers() - base_params.min_num_lns_workers());
752  if (!base_params.interleave_search() && result.size() > target) {
753  result.resize(target);
754  }
755  } else {
756  // If strategies that do not require a full worker are present, leave a
757  // few workers for them.
758  const bool need_extra_workers =
759  !base_params.interleave_search() &&
760  (base_params.use_rins_lns() || base_params.use_feasibility_pump());
761  int target = base_params.num_workers();
762  if (need_extra_workers && target > 4) {
763  if (target <= 8) {
764  target -= 1;
765  } else if (target == 9) {
766  target -= 2;
767  } else {
768  target -= 3;
769  }
770  }
771  if (!base_params.interleave_search() && result.size() > target) {
772  result.resize(target);
773  }
774  }
775  return result;
776 }
777 
778 std::vector<SatParameters> GetFirstSolutionParams(
779  const SatParameters& base_params, const CpModelProto& cp_model,
780  int num_params_to_generate) {
781  std::vector<SatParameters> result;
782  if (num_params_to_generate <= 0) return result;
783  int num_random = 0;
784  int num_random_qr = 0;
785  while (result.size() < num_params_to_generate) {
786  SatParameters new_params = base_params;
787  const int base_seed = base_params.random_seed();
788  if (num_random <= num_random_qr) { // Random search.
789  // Alternate between automatic search and fixed search (if defined).
790  //
791  // TODO(user): Maybe alternate between more search types.
792  // TODO(user): Check the randomization tolerance.
793  if (cp_model.search_strategy().empty() && num_random % 2 == 0) {
794  new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
795  } else {
796  new_params.set_search_branching(SatParameters::FIXED_SEARCH);
797  }
798  new_params.set_randomize_search(true);
799  new_params.set_search_randomization_tolerance(num_random + 1);
800  new_params.set_random_seed(ValidSumSeed(base_seed, 2 * num_random + 1));
801  new_params.set_name(absl::StrCat("random_", num_random));
802  num_random++;
803  } else { // Random quick restart.
804  new_params.set_search_branching(
805  SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH);
806  new_params.set_randomize_search(true);
807  new_params.set_search_randomization_tolerance(num_random_qr + 1);
808  new_params.set_random_seed(ValidSumSeed(base_seed, 2 * num_random_qr));
809  new_params.set_name(absl::StrCat("random_quick_restart_", num_random_qr));
810  num_random_qr++;
811  }
812  result.push_back(new_params);
813  }
814  return result;
815 }
816 
817 } // namespace sat
818 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
An Assignment is a variable -> domains mapping, used to report solutions to the user.
IntegerVariable Integer(int ref) const
sat::Literal Literal(int ref) const
BooleanOrIntegerLiteral GreaterOrEqual(int var, int64_t value) const
BooleanOrIntegerLiteral MedianValue(int var) const
BooleanOrIntegerLiteral LowerOrEqual(int var, int64_t value) const
std::vector< ValueLiteralPair > FullDomainEncoding(IntegerVariable var) const
Definition: integer.cc:140
bool IsCurrentlyIgnored(IntegerVariable i) const
Definition: integer.h:775
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1565
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1561
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1557
LiteralIndex NegatedIndex() const
Definition: sat_base.h:91
LiteralIndex Index() const
Definition: sat_base.h:90
BooleanVariable Variable() const
Definition: sat_base.h:86
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
bool VariableIsAssigned(BooleanVariable var) const
Definition: sat_base.h:172
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:164
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:161
SatParameters parameters
CpModelProto proto
ABSL_FLAG(bool, cp_model_use_max_hs, false, "Use max_hs in search portfolio.")
const std::string name
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
const std::function< BooleanOrIntegerLiteral()> ConstructSearchStrategyInternal(const std::vector< DecisionStrategyProto > &strategies, Model *model)
std::function< BooleanOrIntegerLiteral()> FirstUnassignedVarAtItsMinHeuristic(const std::vector< IntegerVariable > &vars, Model *model)
bool RefIsPositive(int ref)
const LiteralIndex kNoLiteralIndex(-1)
std::function< BooleanOrIntegerLiteral()> ConstructUserSearchStrategy(const CpModelProto &cp_model_proto, Model *model)
const IntegerVariable kNoIntegerVariable(-1)
std::function< BooleanOrIntegerLiteral()> SchedulingSearchHeuristic(Model *model)
std::function< BooleanOrIntegerLiteral()> ConstructFixedSearchStrategy(const CpModelProto &cp_model_proto, const std::vector< IntegerVariable > &variable_mapping, IntegerVariable objective_var, Model *model)
std::vector< SatParameters > GetDiverseSetOfParameters(const SatParameters &base_params, const CpModelProto &cp_model)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:46
std::function< BooleanOrIntegerLiteral()> SequentialSearch(std::vector< std::function< BooleanOrIntegerLiteral()>> heuristics)
std::vector< SatParameters > GetFirstSolutionParams(const SatParameters &base_params, const CpModelProto &cp_model, int num_params_to_generate)
std::function< BooleanOrIntegerLiteral()> InstrumentSearchStrategy(const CpModelProto &cp_model_proto, const std::vector< IntegerVariable > &variable_mapping, const std::function< BooleanOrIntegerLiteral()> &instrumented_strategy, Model *model)
Collection of objects used to extend the Constraint Solver library.
int64_t delta
Definition: resource.cc:1695
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1505
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1499