OR-Tools  9.0
cp_model_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 <cstdint>
17 #include <random>
18 
19 #include "absl/container/flat_hash_map.h"
20 #include "absl/strings/str_format.h"
22 #include "ortools/sat/util.h"
23 
24 namespace operations_research {
25 namespace sat {
26 
28  : mapping_(*model->GetOrCreate<CpModelMapping>()),
29  boolean_assignment_(model->GetOrCreate<Trail>()->Assignment()),
30  integer_trail_(*model->GetOrCreate<IntegerTrail>()) {}
31 
32 int CpModelView::NumVariables() const { return mapping_.NumProtoVariables(); }
33 
34 bool CpModelView::IsFixed(int var) const {
35  if (mapping_.IsBoolean(var)) {
36  return boolean_assignment_.VariableIsAssigned(
37  mapping_.Literal(var).Variable());
38  } else if (mapping_.IsInteger(var)) {
39  return integer_trail_.IsFixed(mapping_.Integer(var));
40  }
41  return true; // Default.
42 }
43 
45  return mapping_.IsInteger(var) &&
46  integer_trail_.IsCurrentlyIgnored(mapping_.Integer(var));
47 }
48 
49 int64_t CpModelView::Min(int var) const {
50  if (mapping_.IsBoolean(var)) {
51  const Literal l = mapping_.Literal(var);
52  return boolean_assignment_.LiteralIsTrue(l) ? 1 : 0;
53  } else if (mapping_.IsInteger(var)) {
54  return integer_trail_.LowerBound(mapping_.Integer(var)).value();
55  }
56  return 0; // Default.
57 }
58 
59 int64_t CpModelView::Max(int var) const {
60  if (mapping_.IsBoolean(var)) {
61  const Literal l = mapping_.Literal(var);
62  return boolean_assignment_.LiteralIsFalse(l) ? 0 : 1;
63  } else if (mapping_.IsInteger(var)) {
64  return integer_trail_.UpperBound(mapping_.Integer(var)).value();
65  }
66  return 0; // Default.
67 }
68 
70  int64_t value) const {
71  DCHECK(!IsFixed(var));
73  if (mapping_.IsBoolean(var)) {
74  DCHECK(value == 0 || value == 1);
75  if (value == 1) {
76  result.boolean_literal_index = mapping_.Literal(var).Index();
77  }
78  } else if (mapping_.IsInteger(var)) {
80  mapping_.Integer(var), IntegerValue(value));
81  }
82  return result;
83 }
84 
86  int64_t value) const {
87  DCHECK(!IsFixed(var));
89  if (mapping_.IsBoolean(var)) {
90  DCHECK(value == 0 || value == 1);
91  if (value == 0) {
92  result.boolean_literal_index = mapping_.Literal(var).NegatedIndex();
93  }
94  } else if (mapping_.IsInteger(var)) {
96  IntegerValue(value));
97  }
98  return result;
99 }
100 
101 // Stores one variable and its strategy value.
102 struct VarValue {
103  int ref;
104  int64_t value;
105 };
106 
108  const std::vector<DecisionStrategyProto>& strategies, Model* model) {
109  const auto& view = *model->GetOrCreate<CpModelView>();
110  const auto& parameters = *model->GetOrCreate<SatParameters>();
111  auto* random = model->GetOrCreate<ModelRandomGenerator>();
112 
113  // Note that we copy strategies to keep the return function validity
114  // independently of the life of the passed vector.
115  return [&view, &parameters, random, strategies]() {
116  for (const DecisionStrategyProto& strategy : strategies) {
117  int candidate;
118  int64_t candidate_value = kint64max;
119 
120  // TODO(user): Improve the complexity if this becomes an issue which
121  // may be the case if we do a fixed_search.
122 
123  // To store equivalent variables in randomized search.
124  std::vector<VarValue> active_refs;
125 
126  int t_index = 0; // Index in strategy.transformations().
127  for (int i = 0; i < strategy.variables().size(); ++i) {
128  const int ref = strategy.variables(i);
129  const int var = PositiveRef(ref);
130  if (view.IsFixed(var) || view.IsCurrentlyFree(var)) continue;
131 
132  int64_t coeff(1);
133  int64_t offset(0);
134  while (t_index < strategy.transformations().size() &&
135  strategy.transformations(t_index).index() < i) {
136  ++t_index;
137  }
138  if (t_index < strategy.transformations_size() &&
139  strategy.transformations(t_index).index() == i) {
140  coeff = strategy.transformations(t_index).positive_coeff();
141  offset = strategy.transformations(t_index).offset();
142  }
143 
144  // TODO(user): deal with integer overflow in case of wrongly specified
145  // coeff? Note that if this is filled by the presolve it shouldn't
146  // happen since any feasible value in the new variable domain should be
147  // a feasible value of the original variable domain.
148  int64_t value(0);
149  int64_t lb = view.Min(var);
150  int64_t ub = view.Max(var);
151  if (!RefIsPositive(ref)) {
152  lb = -view.Max(var);
153  ub = -view.Min(var);
154  }
155  switch (strategy.variable_selection_strategy()) {
156  case DecisionStrategyProto::CHOOSE_FIRST:
157  break;
158  case DecisionStrategyProto::CHOOSE_LOWEST_MIN:
159  value = coeff * lb + offset;
160  break;
161  case DecisionStrategyProto::CHOOSE_HIGHEST_MAX:
162  value = -(coeff * ub + offset);
163  break;
164  case DecisionStrategyProto::CHOOSE_MIN_DOMAIN_SIZE:
165  value = coeff * (ub - lb + 1);
166  break;
167  case DecisionStrategyProto::CHOOSE_MAX_DOMAIN_SIZE:
168  value = -coeff * (ub - lb + 1);
169  break;
170  default:
171  LOG(FATAL) << "Unknown VariableSelectionStrategy "
172  << strategy.variable_selection_strategy();
173  }
174  if (value < candidate_value) {
175  candidate = ref;
176  candidate_value = value;
177  }
178  if (strategy.variable_selection_strategy() ==
179  DecisionStrategyProto::CHOOSE_FIRST &&
180  !parameters.randomize_search()) {
181  break;
182  } else if (parameters.randomize_search()) {
183  if (value <=
184  candidate_value + parameters.search_randomization_tolerance()) {
185  active_refs.push_back({ref, value});
186  }
187  }
188  }
189 
190  if (candidate_value == kint64max) continue;
191  if (parameters.randomize_search()) {
192  CHECK(!active_refs.empty());
193  const IntegerValue threshold(
194  candidate_value + parameters.search_randomization_tolerance());
195  auto is_above_tolerance = [threshold](const VarValue& entry) {
196  return entry.value > threshold;
197  };
198  // Remove all values above tolerance.
199  active_refs.erase(std::remove_if(active_refs.begin(), active_refs.end(),
200  is_above_tolerance),
201  active_refs.end());
202  const int winner = absl::Uniform<int>(*random, 0, active_refs.size());
203  candidate = active_refs[winner].ref;
204  }
205 
206  DecisionStrategyProto::DomainReductionStrategy selection =
207  strategy.domain_reduction_strategy();
208  if (!RefIsPositive(candidate)) {
209  switch (selection) {
210  case DecisionStrategyProto::SELECT_MIN_VALUE:
211  selection = DecisionStrategyProto::SELECT_MAX_VALUE;
212  break;
213  case DecisionStrategyProto::SELECT_MAX_VALUE:
214  selection = DecisionStrategyProto::SELECT_MIN_VALUE;
215  break;
216  case DecisionStrategyProto::SELECT_LOWER_HALF:
217  selection = DecisionStrategyProto::SELECT_UPPER_HALF;
218  break;
219  case DecisionStrategyProto::SELECT_UPPER_HALF:
220  selection = DecisionStrategyProto::SELECT_LOWER_HALF;
221  break;
222  default:
223  break;
224  }
225  }
226 
227  const int var = PositiveRef(candidate);
228  const int64_t lb = view.Min(var);
229  const int64_t ub = view.Max(var);
230  switch (selection) {
231  case DecisionStrategyProto::SELECT_MIN_VALUE:
232  return view.LowerOrEqual(var, lb);
233  case DecisionStrategyProto::SELECT_MAX_VALUE:
234  return view.GreaterOrEqual(var, ub);
235  case DecisionStrategyProto::SELECT_LOWER_HALF:
236  return view.LowerOrEqual(var, lb + (ub - lb) / 2);
237  case DecisionStrategyProto::SELECT_UPPER_HALF:
238  return view.GreaterOrEqual(var, ub - (ub - lb) / 2);
239  case DecisionStrategyProto::SELECT_MEDIAN_VALUE:
240  // TODO(user): Implement the correct method.
241  return view.LowerOrEqual(var, lb);
242  default:
243  LOG(FATAL) << "Unknown DomainReductionStrategy "
244  << strategy.domain_reduction_strategy();
245  }
246  }
247  return BooleanOrIntegerLiteral();
248  };
249 }
250 
252  const CpModelProto& cp_model_proto,
253  const std::vector<IntegerVariable>& variable_mapping,
254  IntegerVariable objective_var, Model* model) {
255  // Default strategy is to instantiate the IntegerVariable in order.
256  std::function<BooleanOrIntegerLiteral()> default_search_strategy = nullptr;
257  const bool instantiate_all_variables =
258  model->GetOrCreate<SatParameters>()->instantiate_all_variables();
259 
260  if (instantiate_all_variables) {
261  std::vector<IntegerVariable> decisions;
262  for (const IntegerVariable var : variable_mapping) {
263  if (var == kNoIntegerVariable) continue;
264 
265  // Make sure we try to fix the objective to its lowest value first.
266  if (var == NegationOf(objective_var)) {
267  decisions.push_back(objective_var);
268  } else {
269  decisions.push_back(var);
270  }
271  }
272  default_search_strategy =
274  }
275 
276  std::vector<DecisionStrategyProto> strategies;
277  for (const DecisionStrategyProto& proto : cp_model_proto.search_strategy()) {
278  strategies.push_back(proto);
279  }
280  if (instantiate_all_variables) {
282  default_search_strategy});
283  } else {
284  return ConstructSearchStrategyInternal(strategies, model);
285  }
286 }
287 
289  const CpModelProto& cp_model_proto,
290  const std::vector<IntegerVariable>& variable_mapping,
291  const std::function<BooleanOrIntegerLiteral()>& instrumented_strategy,
292  Model* model) {
293  std::vector<int> ref_to_display;
294  for (int i = 0; i < cp_model_proto.variables_size(); ++i) {
295  if (variable_mapping[i] == kNoIntegerVariable) continue;
296  if (cp_model_proto.variables(i).name().empty()) continue;
297  ref_to_display.push_back(i);
298  }
299  std::sort(ref_to_display.begin(), ref_to_display.end(), [&](int i, int j) {
300  return cp_model_proto.variables(i).name() <
301  cp_model_proto.variables(j).name();
302  });
303 
304  std::vector<std::pair<int64_t, int64_t>> old_domains(variable_mapping.size());
305  return [instrumented_strategy, model, variable_mapping, cp_model_proto,
306  old_domains, ref_to_display]() mutable {
307  const BooleanOrIntegerLiteral decision = instrumented_strategy();
308  if (!decision.HasValue()) return decision;
309 
310  if (decision.boolean_literal_index != kNoLiteralIndex) {
311  const Literal l = Literal(decision.boolean_literal_index);
312  LOG(INFO) << "Boolean decision " << l;
313  for (const IntegerLiteral i_lit :
315  LOG(INFO) << " - associated with " << i_lit;
316  }
317  } else {
318  LOG(INFO) << "Integer decision " << decision.integer_literal;
319  }
320  const int level = model->Get<Trail>()->CurrentDecisionLevel();
321  std::string to_display =
322  absl::StrCat("Diff since last call, level=", level, "\n");
323  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
324  for (const int ref : ref_to_display) {
325  const IntegerVariable var = variable_mapping[ref];
326  const std::pair<int64_t, int64_t> new_domain(
327  integer_trail->LowerBound(var).value(),
328  integer_trail->UpperBound(var).value());
329  if (new_domain != old_domains[ref]) {
330  absl::StrAppend(&to_display, cp_model_proto.variables(ref).name(), " [",
331  old_domains[ref].first, ",", old_domains[ref].second,
332  "] -> [", new_domain.first, ",", new_domain.second,
333  "]\n");
334  old_domains[ref] = new_domain;
335  }
336  }
337  LOG(INFO) << to_display;
338  return decision;
339  };
340 }
341 
342 // Note: in flatzinc setting, we know we always have a fixed search defined.
343 //
344 // Things to try:
345 // - Specialize for purely boolean problems
346 // - Disable linearization_level options for non linear problems
347 // - Fast restart in randomized search
348 // - Different propatation levels for scheduling constraints
349 std::vector<SatParameters> GetDiverseSetOfParameters(
350  const SatParameters& base_params, const CpModelProto& cp_model,
351  const int num_workers) {
352  // Defines a set of named strategies so it is easier to read in one place
353  // the one that are used. See below.
354  std::map<std::string, SatParameters> strategies;
355 
356  // Lp variations only.
357  {
358  SatParameters new_params = base_params;
359  new_params.set_linearization_level(0);
360  strategies["no_lp"] = new_params;
361  new_params.set_linearization_level(1);
362  strategies["default_lp"] = new_params;
363  new_params.set_linearization_level(2);
364  new_params.set_add_lp_constraints_lazily(false);
365  strategies["max_lp"] = new_params;
366  }
367 
368  // Core. Note that we disable the lp here because it is faster on the minizinc
369  // benchmark.
370  //
371  // TODO(user): Do more experiments, the LP with core could be useful, but we
372  // probably need to incorporate the newly created integer variables from the
373  // core algorithm into the LP.
374  {
375  SatParameters new_params = base_params;
376  new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
377  new_params.set_optimize_with_core(true);
378  new_params.set_linearization_level(0);
379  strategies["core"] = new_params;
380  }
381 
382  // It can be interesting to try core and lp.
383  {
384  SatParameters new_params = base_params;
385  new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
386  new_params.set_optimize_with_core(true);
387  new_params.set_linearization_level(1);
388  strategies["core_default_lp"] = new_params;
389  }
390 
391  {
392  SatParameters new_params = base_params;
393  new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
394  new_params.set_optimize_with_core(true);
395  new_params.set_linearization_level(2);
396  strategies["core_max_lp"] = new_params;
397  }
398 
399  {
400  SatParameters new_params = base_params;
401  new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
402  new_params.set_use_probing_search(true);
403  strategies["probing"] = new_params;
404  }
405 
406  // Search variation.
407  {
408  SatParameters new_params = base_params;
409  new_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
410  strategies["auto"] = new_params;
411 
412  new_params.set_search_branching(SatParameters::FIXED_SEARCH);
413  strategies["fixed"] = new_params;
414 
415  new_params.set_search_branching(
416  SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH);
417  strategies["quick_restart"] = new_params;
418 
419  new_params.set_search_branching(
420  SatParameters::PORTFOLIO_WITH_QUICK_RESTART_SEARCH);
421  new_params.set_linearization_level(0);
422  strategies["quick_restart_no_lp"] = new_params;
423 
424  // We force the max lp here too.
425  new_params.set_linearization_level(2);
426  new_params.set_search_branching(SatParameters::LP_SEARCH);
427  strategies["reduced_costs"] = new_params;
428 
429  // For this one, we force other param too.
430  new_params.set_linearization_level(2);
431  new_params.set_search_branching(SatParameters::PSEUDO_COST_SEARCH);
432  new_params.set_exploit_best_solution(true);
433  strategies["pseudo_costs"] = new_params;
434  }
435 
436  // Less encoding.
437  {
438  SatParameters new_params = base_params;
439  new_params.set_boolean_encoding_level(0);
440  strategies["less_encoding"] = new_params;
441  }
442 
443  // Our current set of strategies
444  //
445  // TODO(user, fdid): Avoid launching two strategies if they are the same,
446  // like if there is no lp, or everything is already linearized at level 1.
447  std::vector<std::string> names;
448  if (base_params.reduce_memory_usage_in_interleave_mode() &&
449  base_params.interleave_search()) {
450  // Low memory mode for interleaved search in single thread.
451  if (cp_model.has_objective()) {
452  names.push_back("default_lp");
453  names.push_back(!cp_model.search_strategy().empty() ? "fixed"
454  : "pseudo_costs");
455  names.push_back(cp_model.objective().vars_size() > 1 ? "core" : "no_lp");
456  names.push_back("max_lp");
457  } else {
458  names.push_back("default_lp");
459  names.push_back(cp_model.search_strategy_size() > 0 ? "fixed" : "no_lp");
460  names.push_back("less_encoding");
461  names.push_back("max_lp");
462  names.push_back("quick_restart");
463  }
464  } else if (cp_model.has_objective()) {
465  names.push_back("default_lp");
466  names.push_back(!cp_model.search_strategy().empty() ? "fixed"
467  : "reduced_costs");
468  names.push_back("pseudo_costs");
469  names.push_back("no_lp");
470  names.push_back("max_lp");
471  if (cp_model.objective().vars_size() > 1) names.push_back("core");
472  // TODO(user): Experiment with core and LP.
473 
474  // Only add this strategy if we have enough worker left for LNS.
475  if (num_workers > 8 || base_params.interleave_search()) {
476  names.push_back("quick_restart");
477  }
478  if (num_workers > 10) {
479  names.push_back("quick_restart_no_lp");
480  }
481  } else {
482  names.push_back("default_lp");
483  if (cp_model.search_strategy_size() > 0) names.push_back("fixed");
484  names.push_back("less_encoding");
485  names.push_back("no_lp");
486  names.push_back("max_lp");
487  names.push_back("quick_restart");
488  if (num_workers > 10) {
489  names.push_back("quick_restart_no_lp");
490  }
491  }
492  if (num_workers > 12) {
493  names.push_back("probing");
494  }
495 
496  // Creates the diverse set of parameters with names and seed. We remove the
497  // last ones if needed below.
498  std::vector<SatParameters> result;
499  for (const std::string& name : names) {
500  SatParameters new_params = strategies.at(name);
501  new_params.set_name(name);
502  new_params.set_random_seed(result.size() + 1);
503  result.push_back(new_params);
504  }
505 
506  // If there is no objective, we complete with randomized fixed search.
507  // If there is an objective, the extra workers will use LNS.
508  if (!cp_model.has_objective()) {
509  int target = num_workers;
510 
511  // If strategies that do not require a full worker are present, leave one
512  // worker for them.
513  if (!base_params.interleave_search() &&
514  (base_params.use_rins_lns() || base_params.use_relaxation_lns() ||
515  base_params.use_feasibility_pump())) {
516  target = std::max(1, num_workers - 1);
517  }
518 
519  int index = 1;
520  while (result.size() < target) {
521  // TODO(user): This doesn't make sense if there is no fixed search.
522  SatParameters new_params = base_params;
523  new_params.set_search_branching(SatParameters::FIXED_SEARCH);
524  new_params.set_randomize_search(true);
525  new_params.set_search_randomization_tolerance(index);
526  new_params.set_random_seed(result.size() + 1);
527  new_params.set_name(absl::StrCat("random_", index));
528  result.push_back(new_params);
529  ++index;
530  }
531  }
532 
533  // If we are not in interleave search, we cannot run more strategies than
534  // the number of worker.
535  //
536  // TODO(user): consider using LNS if we use a small number of workers.
537  if (!base_params.interleave_search() && result.size() > num_workers) {
538  result.resize(num_workers);
539  }
540 
541  return result;
542 }
543 
544 } // namespace sat
545 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
#define CHECK(condition)
Definition: base/logging.h:498
#define LOG(severity)
Definition: base/logging.h:423
#define DCHECK(condition)
Definition: base/logging.h:892
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 LowerOrEqual(int var, int64_t value) const
const InlinedIntegerLiteralVector & GetAllIntegerLiterals(Literal lit) const
Definition: integer.h:405
bool IsCurrentlyIgnored(IntegerVariable i) const
Definition: integer.h:630
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1313
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1309
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1305
LiteralIndex NegatedIndex() const
Definition: sat_base.h:86
LiteralIndex Index() const
Definition: sat_base.h:85
BooleanVariable Variable() const
Definition: sat_base.h:81
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
bool VariableIsAssigned(BooleanVariable var) const
Definition: sat_base.h:159
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:151
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:148
SatParameters parameters
CpModelProto proto
const std::string name
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
static const int64_t kint64max
const int INFO
Definition: log_severity.h:31
const int FATAL
Definition: log_severity.h:32
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)
const IntegerVariable kNoIntegerVariable(-1)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:29
std::function< BooleanOrIntegerLiteral()> SequentialSearch(std::vector< std::function< BooleanOrIntegerLiteral()>> heuristics)
std::vector< SatParameters > GetDiverseSetOfParameters(const SatParameters &base_params, const CpModelProto &cp_model, const int num_workers)
std::function< BooleanOrIntegerLiteral()> ConstructSearchStrategy(const CpModelProto &cp_model_proto, const std::vector< IntegerVariable > &variable_mapping, IntegerVariable objective_var, Model *model)
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.
int index
Definition: pack.cc:509
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1275
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1269