OR-Tools  8.2
bop_portfolio.cc
Go to the documentation of this file.
1 // Copyright 2010-2018 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
15 
16 #include "absl/memory/memory.h"
17 #include "absl/strings/str_format.h"
18 #include "ortools/base/stl_util.h"
20 #include "ortools/bop/bop_fs.h"
21 #include "ortools/bop/bop_lns.h"
22 #include "ortools/bop/bop_ls.h"
23 #include "ortools/bop/bop_util.h"
27 #include "ortools/sat/symmetry.h"
28 
29 namespace operations_research {
30 namespace bop {
31 
32 using ::operations_research::sat::LinearBooleanProblem;
33 using ::operations_research::sat::LinearObjective;
34 
35 namespace {
36 void BuildObjectiveTerms(const LinearBooleanProblem& problem,
37  BopConstraintTerms* objective_terms) {
38  CHECK(objective_terms != nullptr);
39 
40  if (!objective_terms->empty()) return;
41 
42  const LinearObjective& objective = problem.objective();
43  const size_t num_objective_terms = objective.literals_size();
44  CHECK_EQ(num_objective_terms, objective.coefficients_size());
45  for (int i = 0; i < num_objective_terms; ++i) {
46  CHECK_GT(objective.literals(i), 0);
47  CHECK_NE(objective.coefficients(i), 0);
48 
49  const VariableIndex var_id(objective.literals(i) - 1);
50  const int64 weight = objective.coefficients(i);
51  objective_terms->push_back(BopConstraintTerm(var_id, weight));
52  }
53 }
54 } // anonymous namespace
55 
56 //------------------------------------------------------------------------------
57 // PortfolioOptimizer
58 //------------------------------------------------------------------------------
60  const ProblemState& problem_state, const BopParameters& parameters,
61  const BopSolverOptimizerSet& optimizer_set, const std::string& name)
63  random_(),
64  state_update_stamp_(ProblemState::kInitialStampValue),
65  objective_terms_(),
66  selector_(),
67  optimizers_(),
68  sat_propagator_(),
69  parameters_(parameters),
70  lower_bound_(-glop::kInfinity),
71  upper_bound_(glop::kInfinity),
72  number_of_consecutive_failing_optimizers_(0) {
73  CreateOptimizers(problem_state.original_problem(), parameters, optimizer_set);
74 }
75 
77  if (parameters_.log_search_progress() || VLOG_IS_ON(1)) {
78  std::string stats_string;
79  for (OptimizerIndex i(0); i < optimizers_.size(); ++i) {
80  if (selector_->NumCallsForOptimizer(i) > 0) {
81  stats_string += selector_->PrintStats(i);
82  }
83  }
84  if (!stats_string.empty()) {
85  LOG(INFO) << "Stats. #new_solutions/#calls by optimizer:\n" +
86  stats_string;
87  }
88  }
89 
90  // Note that unique pointers are not used due to unsupported emplace_back
91  // in ITIVectors.
92  gtl::STLDeleteElements(&optimizers_);
93 }
94 
95 BopOptimizerBase::Status PortfolioOptimizer::SynchronizeIfNeeded(
96  const ProblemState& problem_state) {
97  if (state_update_stamp_ == problem_state.update_stamp()) {
99  }
100  state_update_stamp_ = problem_state.update_stamp();
101 
102  // Load any new information into the sat_propagator_.
103  const bool first_time = (sat_propagator_.NumVariables() == 0);
104  const BopOptimizerBase::Status status =
105  LoadStateProblemToSatSolver(problem_state, &sat_propagator_);
106  if (status != BopOptimizerBase::CONTINUE) return status;
107  if (first_time) {
108  // We configure the sat_propagator_ to use the objective as an assignment
109  // preference
111  &sat_propagator_);
112  }
113 
114  lower_bound_ = problem_state.GetScaledLowerBound();
115  upper_bound_ = problem_state.solution().IsFeasible()
116  ? problem_state.solution().GetScaledCost()
117  : glop::kInfinity;
119 }
120 
122  const BopParameters& parameters, const ProblemState& problem_state,
123  LearnedInfo* learned_info, TimeLimit* time_limit) {
124  CHECK(learned_info != nullptr);
125  CHECK(time_limit != nullptr);
126  learned_info->Clear();
127 
128  const BopOptimizerBase::Status sync_status =
129  SynchronizeIfNeeded(problem_state);
130  if (sync_status != BopOptimizerBase::CONTINUE) {
131  return sync_status;
132  }
133 
134  for (OptimizerIndex i(0); i < optimizers_.size(); ++i) {
135  selector_->SetOptimizerRunnability(
136  i, optimizers_[i]->ShouldBeRun(problem_state));
137  }
138 
139  const int64 init_cost = problem_state.solution().IsFeasible()
140  ? problem_state.solution().GetCost()
141  : kint64max;
142  const double init_deterministic_time =
143  time_limit->GetElapsedDeterministicTime();
144 
145  const OptimizerIndex selected_optimizer_id = selector_->SelectOptimizer();
146  if (selected_optimizer_id == kInvalidOptimizerIndex) {
147  LOG(INFO) << "All the optimizers are done.";
149  }
150  BopOptimizerBase* const selected_optimizer =
151  optimizers_[selected_optimizer_id];
152  if (parameters.log_search_progress() || VLOG_IS_ON(1)) {
153  LOG(INFO) << " " << lower_bound_ << " .. " << upper_bound_ << " "
154  << name() << " - " << selected_optimizer->name()
155  << ". Time limit: " << time_limit->GetTimeLeft() << " -- "
156  << time_limit->GetDeterministicTimeLeft();
157  }
158  const BopOptimizerBase::Status optimization_status =
159  selected_optimizer->Optimize(parameters, problem_state, learned_info,
160  time_limit);
161 
162  // ABORT means that this optimizer can't be run until we found a new solution.
163  if (optimization_status == BopOptimizerBase::ABORT) {
164  selector_->TemporarilyMarkOptimizerAsUnselectable(selected_optimizer_id);
165  }
166 
167  // The gain is defined as 1 for the first solution.
168  // TODO(user): Is 1 the right value? It might be better to use a percentage
169  // of the gap, or use the same gain as for the second solution.
170  const int64 gain = optimization_status == BopOptimizerBase::SOLUTION_FOUND
171  ? (init_cost == kint64max
172  ? 1
173  : init_cost - learned_info->solution.GetCost())
174  : 0;
175  const double spent_deterministic_time =
176  time_limit->GetElapsedDeterministicTime() - init_deterministic_time;
177  selector_->UpdateScore(gain, spent_deterministic_time);
178 
179  if (optimization_status == BopOptimizerBase::INFEASIBLE ||
180  optimization_status == BopOptimizerBase::OPTIMAL_SOLUTION_FOUND) {
181  return optimization_status;
182  }
183 
184  // Stop the portfolio optimizer after too many unsuccessful calls.
185  if (parameters.has_max_number_of_consecutive_failing_optimizer_calls() &&
186  problem_state.solution().IsFeasible()) {
187  number_of_consecutive_failing_optimizers_ =
188  optimization_status == BopOptimizerBase::SOLUTION_FOUND
189  ? 0
190  : number_of_consecutive_failing_optimizers_ + 1;
191  if (number_of_consecutive_failing_optimizers_ >
192  parameters.max_number_of_consecutive_failing_optimizer_calls()) {
194  }
195  }
196 
197  // TODO(user): don't penalize the SatCoreBasedOptimizer or the
198  // LinearRelaxation when they improve the lower bound.
199  // TODO(user): Do we want to re-order the optimizers in the selector when
200  // the status is BopOptimizerBase::INFORMATION_FOUND?
202 }
203 
204 void PortfolioOptimizer::AddOptimizer(
205  const LinearBooleanProblem& problem, const BopParameters& parameters,
206  const BopOptimizerMethod& optimizer_method) {
207  switch (optimizer_method.type()) {
208  case BopOptimizerMethod::SAT_CORE_BASED:
209  optimizers_.push_back(new SatCoreBasedOptimizer("SatCoreBasedOptimizer"));
210  break;
211  case BopOptimizerMethod::SAT_LINEAR_SEARCH:
212  optimizers_.push_back(new GuidedSatFirstSolutionGenerator(
214  break;
215  case BopOptimizerMethod::LINEAR_RELAXATION:
216  optimizers_.push_back(
217  new LinearRelaxation(parameters, "LinearRelaxation"));
218  break;
219  case BopOptimizerMethod::LOCAL_SEARCH: {
220  for (int i = 1; i <= parameters.max_num_decisions_in_ls(); ++i) {
221  optimizers_.push_back(new LocalSearchOptimizer(
222  absl::StrFormat("LS_%d", i), i, &sat_propagator_));
223  }
224  } break;
225  case BopOptimizerMethod::RANDOM_FIRST_SOLUTION:
226  optimizers_.push_back(new BopRandomFirstSolutionGenerator(
227  "SATRandomFirstSolution", parameters, &sat_propagator_,
228  random_.get()));
229  break;
230  case BopOptimizerMethod::RANDOM_VARIABLE_LNS:
231  BuildObjectiveTerms(problem, &objective_terms_);
232  optimizers_.push_back(new BopAdaptiveLNSOptimizer(
233  "RandomVariableLns",
234  /*use_lp_to_guide_sat=*/false,
235  new ObjectiveBasedNeighborhood(&objective_terms_, random_.get()),
236  &sat_propagator_));
237  break;
238  case BopOptimizerMethod::RANDOM_VARIABLE_LNS_GUIDED_BY_LP:
239  BuildObjectiveTerms(problem, &objective_terms_);
240  optimizers_.push_back(new BopAdaptiveLNSOptimizer(
241  "RandomVariableLnsWithLp",
242  /*use_lp_to_guide_sat=*/true,
243  new ObjectiveBasedNeighborhood(&objective_terms_, random_.get()),
244  &sat_propagator_));
245  break;
246  case BopOptimizerMethod::RANDOM_CONSTRAINT_LNS:
247  BuildObjectiveTerms(problem, &objective_terms_);
248  optimizers_.push_back(new BopAdaptiveLNSOptimizer(
249  "RandomConstraintLns",
250  /*use_lp_to_guide_sat=*/false,
251  new ConstraintBasedNeighborhood(&objective_terms_, random_.get()),
252  &sat_propagator_));
253  break;
254  case BopOptimizerMethod::RANDOM_CONSTRAINT_LNS_GUIDED_BY_LP:
255  BuildObjectiveTerms(problem, &objective_terms_);
256  optimizers_.push_back(new BopAdaptiveLNSOptimizer(
257  "RandomConstraintLnsWithLp",
258  /*use_lp_to_guide_sat=*/true,
259  new ConstraintBasedNeighborhood(&objective_terms_, random_.get()),
260  &sat_propagator_));
261  break;
262  case BopOptimizerMethod::RELATION_GRAPH_LNS:
263  BuildObjectiveTerms(problem, &objective_terms_);
264  optimizers_.push_back(new BopAdaptiveLNSOptimizer(
265  "RelationGraphLns",
266  /*use_lp_to_guide_sat=*/false,
267  new RelationGraphBasedNeighborhood(problem, random_.get()),
268  &sat_propagator_));
269  break;
270  case BopOptimizerMethod::RELATION_GRAPH_LNS_GUIDED_BY_LP:
271  BuildObjectiveTerms(problem, &objective_terms_);
272  optimizers_.push_back(new BopAdaptiveLNSOptimizer(
273  "RelationGraphLnsWithLp",
274  /*use_lp_to_guide_sat=*/true,
275  new RelationGraphBasedNeighborhood(problem, random_.get()),
276  &sat_propagator_));
277  break;
278  case BopOptimizerMethod::COMPLETE_LNS:
279  BuildObjectiveTerms(problem, &objective_terms_);
280  optimizers_.push_back(
281  new BopCompleteLNSOptimizer("LNS", objective_terms_));
282  break;
283  case BopOptimizerMethod::USER_GUIDED_FIRST_SOLUTION:
284  optimizers_.push_back(new GuidedSatFirstSolutionGenerator(
285  "SATUserGuidedFirstSolution",
287  break;
288  case BopOptimizerMethod::LP_FIRST_SOLUTION:
289  optimizers_.push_back(new GuidedSatFirstSolutionGenerator(
290  "SATLPFirstSolution",
292  break;
293  case BopOptimizerMethod::OBJECTIVE_FIRST_SOLUTION:
294  optimizers_.push_back(new GuidedSatFirstSolutionGenerator(
295  "SATObjectiveFirstSolution",
297  break;
298  default:
299  LOG(FATAL) << "Unknown optimizer type.";
300  }
301 }
302 
303 void PortfolioOptimizer::CreateOptimizers(
304  const LinearBooleanProblem& problem, const BopParameters& parameters,
305  const BopSolverOptimizerSet& optimizer_set) {
306  random_ = absl::make_unique<MTRandom>(parameters.random_seed());
307 
308  if (parameters.use_symmetry()) {
309  VLOG(1) << "Finding symmetries of the problem.";
310  std::vector<std::unique_ptr<SparsePermutation>> generators;
311  sat::FindLinearBooleanProblemSymmetries(problem, &generators);
312  std::unique_ptr<sat::SymmetryPropagator> propagator(
313  new sat::SymmetryPropagator);
314  for (int i = 0; i < generators.size(); ++i) {
315  propagator->AddSymmetry(std::move(generators[i]));
316  }
317  sat_propagator_.AddPropagator(propagator.get());
318  sat_propagator_.TakePropagatorOwnership(std::move(propagator));
319  }
320 
321  const int max_num_optimizers =
322  optimizer_set.methods_size() + parameters.max_num_decisions_in_ls() - 1;
323  optimizers_.reserve(max_num_optimizers);
324  for (const BopOptimizerMethod& optimizer_method : optimizer_set.methods()) {
325  const OptimizerIndex old_size(optimizers_.size());
326  AddOptimizer(problem, parameters, optimizer_method);
327  }
328 
329  selector_ = absl::make_unique<OptimizerSelector>(optimizers_);
330 }
331 
332 //------------------------------------------------------------------------------
333 // OptimizerSelector
334 //------------------------------------------------------------------------------
337  : run_infos_(), selected_index_(optimizers.size()) {
338  for (OptimizerIndex i(0); i < optimizers.size(); ++i) {
339  info_positions_.push_back(run_infos_.size());
340  run_infos_.push_back(RunInfo(i, optimizers[i]->name()));
341  }
342 }
343 
345  CHECK_GE(selected_index_, 0);
346 
347  do {
348  ++selected_index_;
349  } while (selected_index_ < run_infos_.size() &&
350  !run_infos_[selected_index_].RunnableAndSelectable());
351 
352  if (selected_index_ >= run_infos_.size()) {
353  // Select the first possible optimizer.
354  selected_index_ = -1;
355  for (int i = 0; i < run_infos_.size(); ++i) {
356  if (run_infos_[i].RunnableAndSelectable()) {
357  selected_index_ = i;
358  break;
359  }
360  }
361  if (selected_index_ == -1) return kInvalidOptimizerIndex;
362  } else {
363  // Select the next possible optimizer. If none, select the first one.
364  // Check that the time is smaller than all previous optimizers which are
365  // runnable.
366  bool too_much_time_spent = false;
367  const double time_spent =
368  run_infos_[selected_index_].time_spent_since_last_solution;
369  for (int i = 0; i < selected_index_; ++i) {
370  const RunInfo& info = run_infos_[i];
371  if (info.RunnableAndSelectable() &&
372  info.time_spent_since_last_solution < time_spent) {
373  too_much_time_spent = true;
374  break;
375  }
376  }
377  if (too_much_time_spent) {
378  // TODO(user): Remove this recursive call, even if in practice it's
379  // safe because the max depth is the number of optimizers.
380  return SelectOptimizer();
381  }
382  }
383 
384  // Select the optimizer.
385  ++run_infos_[selected_index_].num_calls;
386  return run_infos_[selected_index_].optimizer_index;
387 }
388 
389 void OptimizerSelector::UpdateScore(int64 gain, double time_spent) {
390  const bool new_solution_found = gain != 0;
391  if (new_solution_found) NewSolutionFound(gain);
392  UpdateDeterministicTime(time_spent);
393 
394  const double new_score = time_spent == 0.0 ? 0.0 : gain / time_spent;
395  const double kErosion = 0.2;
396  const double kMinScore = 1E-6;
397 
398  RunInfo& info = run_infos_[selected_index_];
399  const double old_score = info.score;
400  info.score =
401  std::max(kMinScore, old_score * (1 - kErosion) + kErosion * new_score);
402 
403  if (new_solution_found) { // Solution found
404  UpdateOrder();
405  selected_index_ = run_infos_.size();
406  }
407 }
408 
410  OptimizerIndex optimizer_index) {
411  run_infos_[info_positions_[optimizer_index]].selectable = false;
412 }
413 
414 void OptimizerSelector::SetOptimizerRunnability(OptimizerIndex optimizer_index,
415  bool runnable) {
416  run_infos_[info_positions_[optimizer_index]].runnable = runnable;
417 }
418 
420  OptimizerIndex optimizer_index) const {
421  const RunInfo& info = run_infos_[info_positions_[optimizer_index]];
422  return absl::StrFormat(
423  " %40s : %3d/%-3d (%6.2f%%) Total gain: %6d Total Dtime: %0.3f "
424  "score: %f\n",
425  info.name, info.num_successes, info.num_calls,
426  100.0 * info.num_successes / info.num_calls, info.total_gain,
427  info.time_spent, info.score);
428 }
429 
431  OptimizerIndex optimizer_index) const {
432  const RunInfo& info = run_infos_[info_positions_[optimizer_index]];
433  return info.num_calls;
434 }
435 
437  std::string str;
438  for (int i = 0; i < run_infos_.size(); ++i) {
439  const RunInfo& info = run_infos_[i];
440  LOG(INFO) << " " << info.name << " " << info.total_gain
441  << " / " << info.time_spent << " = " << info.score << " "
442  << info.selectable << " " << info.time_spent_since_last_solution;
443  }
444 }
445 
446 void OptimizerSelector::NewSolutionFound(int64 gain) {
447  run_infos_[selected_index_].num_successes++;
448  run_infos_[selected_index_].total_gain += gain;
449 
450  for (int i = 0; i < run_infos_.size(); ++i) {
451  run_infos_[i].time_spent_since_last_solution = 0;
452  run_infos_[i].selectable = true;
453  }
454 }
455 
456 void OptimizerSelector::UpdateDeterministicTime(double time_spent) {
457  run_infos_[selected_index_].time_spent += time_spent;
458  run_infos_[selected_index_].time_spent_since_last_solution += time_spent;
459 }
460 
461 void OptimizerSelector::UpdateOrder() {
462  // Re-sort optimizers.
463  std::stable_sort(run_infos_.begin(), run_infos_.end(),
464  [](const RunInfo& a, const RunInfo& b) -> bool {
465  if (a.total_gain == 0 && b.total_gain == 0)
466  return a.time_spent < b.time_spent;
467  return a.score > b.score;
468  });
469 
470  // Update the positions.
471  for (int i = 0; i < run_infos_.size(); ++i) {
472  info_positions_[run_infos_[i].optimizer_index] = i;
473  }
474 }
475 
476 } // namespace bop
477 } // namespace operations_research
int64 max
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:495
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:697
#define CHECK_GE(val1, val2)
Definition: base/logging.h:701
#define CHECK_GT(val1, val2)
Definition: base/logging.h:702
#define CHECK_NE(val1, val2)
Definition: base/logging.h:698
#define LOG(severity)
Definition: base/logging.h:420
#define VLOG(verboselevel)
Definition: base/logging.h:978
size_type size() const
void push_back(const value_type &x)
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
virtual Status Optimize(const BopParameters &parameters, const ProblemState &problem_state, LearnedInfo *learned_info, TimeLimit *time_limit)=0
const std::string & name() const
Definition: bop_base.h:47
std::string PrintStats(OptimizerIndex optimizer_index) const
int NumCallsForOptimizer(OptimizerIndex optimizer_index) const
OptimizerSelector(const absl::StrongVector< OptimizerIndex, BopOptimizerBase * > &optimizers)
void UpdateScore(int64 gain, double time_spent)
void TemporarilyMarkOptimizerAsUnselectable(OptimizerIndex optimizer_index)
void SetOptimizerRunnability(OptimizerIndex optimizer_index, bool runnable)
bool ShouldBeRun(const ProblemState &problem_state) const override
Definition: bop_portfolio.h:69
Status Optimize(const BopParameters &parameters, const ProblemState &problem_state, LearnedInfo *learned_info, TimeLimit *time_limit) override
PortfolioOptimizer(const ProblemState &problem_state, const BopParameters &parameters, const BopSolverOptimizerSet &optimizer_set, const std::string &name)
const sat::LinearBooleanProblem & original_problem() const
Definition: bop_base.h:199
const BopSolution & solution() const
Definition: bop_base.h:194
void AddPropagator(SatPropagator *propagator)
Definition: sat_solver.cc:405
void TakePropagatorOwnership(std::unique_ptr< SatPropagator > propagator)
Definition: sat_solver.h:142
SatParameters parameters
SharedTimeLimit * time_limit
const std::string name
static const int64 kint64max
int64_t int64
const int INFO
Definition: log_severity.h:31
const int FATAL
Definition: log_severity.h:32
void STLDeleteElements(T *container)
Definition: stl_util.h:372
BopOptimizerBase::Status LoadStateProblemToSatSolver(const ProblemState &problem_state, sat::SatSolver *sat_solver)
Definition: bop_util.cc:87
const OptimizerIndex kInvalidOptimizerIndex(-1)
absl::StrongVector< SparseIndex, BopConstraintTerm > BopConstraintTerms
Definition: bop_types.h:85
const double kInfinity
Definition: lp_types.h:83
void UseObjectiveForSatAssignmentPreference(const LinearBooleanProblem &problem, SatSolver *solver)
void FindLinearBooleanProblemSymmetries(const LinearBooleanProblem &problem, std::vector< std::unique_ptr< SparsePermutation >> *generators)
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...
int64 weight
Definition: pack.cc:509
BaseVariableAssignmentSelector *const selector_
Definition: search.cc:1856
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41