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