16 #include "absl/memory/memory.h"
17 #include "absl/strings/str_format.h"
31 using ::operations_research::sat::LinearBooleanProblem;
32 using ::operations_research::sat::LinearObjective;
35 void BuildObjectiveTerms(
const LinearBooleanProblem& problem,
37 CHECK(objective_terms !=
nullptr);
39 if (!objective_terms->empty())
return;
41 const LinearObjective& objective = problem.objective();
42 const size_t num_objective_terms = objective.literals_size();
43 CHECK_EQ(num_objective_terms, objective.coefficients_size());
44 for (
int i = 0; i < num_objective_terms; ++i) {
46 CHECK_NE(objective.coefficients(i), 0);
48 const VariableIndex var_id(objective.literals(i) - 1);
50 objective_terms->push_back(BopConstraintTerm(var_id,
weight));
60 const BopSolverOptimizerSet& optimizer_set,
const std::string&
name)
71 number_of_consecutive_failing_optimizers_(0) {
76 if (parameters_.log_search_progress() ||
VLOG_IS_ON(1)) {
77 std::string stats_string;
78 for (OptimizerIndex i(0); i < optimizers_.size(); ++i) {
79 if (selector_->NumCallsForOptimizer(i) > 0) {
80 stats_string += selector_->PrintStats(i);
83 if (!stats_string.empty()) {
84 LOG(
INFO) <<
"Stats. #new_solutions/#calls by optimizer:\n" +
96 if (state_update_stamp_ == problem_state.
update_stamp()) {
102 const bool first_time = (sat_propagator_.
NumVariables() == 0);
123 CHECK(learned_info !=
nullptr);
125 learned_info->
Clear();
128 SynchronizeIfNeeded(problem_state);
133 for (OptimizerIndex i(0); i < optimizers_.size(); ++i) {
134 selector_->SetOptimizerRunnability(
141 const double init_deterministic_time =
144 const OptimizerIndex selected_optimizer_id = selector_->SelectOptimizer();
146 LOG(
INFO) <<
"All the optimizers are done.";
150 optimizers_[selected_optimizer_id];
152 LOG(
INFO) <<
" " << lower_bound_ <<
" .. " << upper_bound_ <<
" "
153 <<
name() <<
" - " << selected_optimizer->
name()
154 <<
". Time limit: " <<
time_limit->GetTimeLeft() <<
" -- "
163 selector_->TemporarilyMarkOptimizerAsUnselectable(selected_optimizer_id);
174 const double spent_deterministic_time =
175 time_limit->GetElapsedDeterministicTime() - init_deterministic_time;
176 selector_->UpdateScore(gain, spent_deterministic_time);
180 return optimization_status;
184 if (
parameters.has_max_number_of_consecutive_failing_optimizer_calls() &&
186 number_of_consecutive_failing_optimizers_ =
189 : number_of_consecutive_failing_optimizers_ + 1;
190 if (number_of_consecutive_failing_optimizers_ >
191 parameters.max_number_of_consecutive_failing_optimizer_calls()) {
203 void PortfolioOptimizer::AddOptimizer(
204 const LinearBooleanProblem& problem,
const BopParameters&
parameters,
205 const BopOptimizerMethod& optimizer_method) {
206 switch (optimizer_method.type()) {
207 case BopOptimizerMethod::SAT_CORE_BASED:
210 case BopOptimizerMethod::SAT_LINEAR_SEARCH:
214 case BopOptimizerMethod::LINEAR_RELAXATION:
215 optimizers_.push_back(
218 case BopOptimizerMethod::LOCAL_SEARCH: {
219 for (
int i = 1; i <=
parameters.max_num_decisions_in_ls(); ++i) {
221 absl::StrFormat(
"LS_%d", i), i, &sat_propagator_));
224 case BopOptimizerMethod::RANDOM_FIRST_SOLUTION:
225 optimizers_.push_back(
new BopRandomFirstSolutionGenerator(
226 "SATRandomFirstSolution",
parameters, &sat_propagator_,
229 case BopOptimizerMethod::RANDOM_VARIABLE_LNS:
230 BuildObjectiveTerms(problem, &objective_terms_);
231 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
234 new ObjectiveBasedNeighborhood(&objective_terms_, random_.get()),
237 case BopOptimizerMethod::RANDOM_VARIABLE_LNS_GUIDED_BY_LP:
238 BuildObjectiveTerms(problem, &objective_terms_);
239 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
240 "RandomVariableLnsWithLp",
242 new ObjectiveBasedNeighborhood(&objective_terms_, random_.get()),
245 case BopOptimizerMethod::RANDOM_CONSTRAINT_LNS:
246 BuildObjectiveTerms(problem, &objective_terms_);
247 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
248 "RandomConstraintLns",
250 new ConstraintBasedNeighborhood(&objective_terms_, random_.get()),
253 case BopOptimizerMethod::RANDOM_CONSTRAINT_LNS_GUIDED_BY_LP:
254 BuildObjectiveTerms(problem, &objective_terms_);
255 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
256 "RandomConstraintLnsWithLp",
258 new ConstraintBasedNeighborhood(&objective_terms_, random_.get()),
261 case BopOptimizerMethod::RELATION_GRAPH_LNS:
262 BuildObjectiveTerms(problem, &objective_terms_);
263 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
266 new RelationGraphBasedNeighborhood(problem, random_.get()),
269 case BopOptimizerMethod::RELATION_GRAPH_LNS_GUIDED_BY_LP:
270 BuildObjectiveTerms(problem, &objective_terms_);
271 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
272 "RelationGraphLnsWithLp",
274 new RelationGraphBasedNeighborhood(problem, random_.get()),
277 case BopOptimizerMethod::COMPLETE_LNS:
278 BuildObjectiveTerms(problem, &objective_terms_);
279 optimizers_.push_back(
280 new BopCompleteLNSOptimizer(
"LNS", objective_terms_));
282 case BopOptimizerMethod::USER_GUIDED_FIRST_SOLUTION:
283 optimizers_.push_back(
new GuidedSatFirstSolutionGenerator(
284 "SATUserGuidedFirstSolution",
287 case BopOptimizerMethod::LP_FIRST_SOLUTION:
288 optimizers_.push_back(
new GuidedSatFirstSolutionGenerator(
289 "SATLPFirstSolution",
292 case BopOptimizerMethod::OBJECTIVE_FIRST_SOLUTION:
293 optimizers_.push_back(
new GuidedSatFirstSolutionGenerator(
294 "SATObjectiveFirstSolution",
298 LOG(
FATAL) <<
"Unknown optimizer type.";
302 void PortfolioOptimizer::CreateOptimizers(
303 const LinearBooleanProblem& problem,
const BopParameters&
parameters,
304 const BopSolverOptimizerSet& optimizer_set) {
305 random_ = absl::make_unique<MTRandom>(
parameters.random_seed());
308 VLOG(1) <<
"Finding symmetries of the problem.";
309 std::vector<std::unique_ptr<SparsePermutation>> generators;
311 std::unique_ptr<sat::SymmetryPropagator> propagator(
312 new sat::SymmetryPropagator);
313 for (
int i = 0; i < generators.size(); ++i) {
314 propagator->AddSymmetry(std::move(generators[i]));
320 const int max_num_optimizers =
321 optimizer_set.methods_size() +
parameters.max_num_decisions_in_ls() - 1;
322 optimizers_.reserve(max_num_optimizers);
323 for (
const BopOptimizerMethod& optimizer_method : optimizer_set.methods()) {
324 const OptimizerIndex old_size(optimizers_.size());
325 AddOptimizer(problem,
parameters, optimizer_method);
328 selector_ = absl::make_unique<OptimizerSelector>(optimizers_);
336 : run_infos_(), selected_index_(optimizers.size()) {
337 for (OptimizerIndex i(0); i < optimizers.
size(); ++i) {
338 info_positions_.
push_back(run_infos_.size());
339 run_infos_.push_back(RunInfo(i, optimizers[i]->
name()));
348 }
while (selected_index_ < run_infos_.size() &&
349 !run_infos_[selected_index_].RunnableAndSelectable());
351 if (selected_index_ >= run_infos_.size()) {
353 selected_index_ = -1;
354 for (
int i = 0; i < run_infos_.size(); ++i) {
355 if (run_infos_[i].RunnableAndSelectable()) {
365 bool too_much_time_spent =
false;
366 const double time_spent =
367 run_infos_[selected_index_].time_spent_since_last_solution;
368 for (
int i = 0; i < selected_index_; ++i) {
369 const RunInfo& info = run_infos_[i];
370 if (info.RunnableAndSelectable() &&
371 info.time_spent_since_last_solution < time_spent) {
372 too_much_time_spent =
true;
376 if (too_much_time_spent) {
384 ++run_infos_[selected_index_].num_calls;
385 return run_infos_[selected_index_].optimizer_index;
389 const bool new_solution_found = gain != 0;
390 if (new_solution_found) NewSolutionFound(gain);
391 UpdateDeterministicTime(time_spent);
393 const double new_score = time_spent == 0.0 ? 0.0 : gain / time_spent;
394 const double kErosion = 0.2;
395 const double kMinScore = 1E-6;
397 RunInfo& info = run_infos_[selected_index_];
398 const double old_score = info.score;
400 std::max(kMinScore, old_score * (1 - kErosion) + kErosion * new_score);
402 if (new_solution_found) {
404 selected_index_ = run_infos_.size();
409 OptimizerIndex optimizer_index) {
410 run_infos_[info_positions_[optimizer_index]].selectable =
false;
415 run_infos_[info_positions_[optimizer_index]].runnable = runnable;
419 OptimizerIndex optimizer_index)
const {
420 const RunInfo& info = run_infos_[info_positions_[optimizer_index]];
421 return absl::StrFormat(
422 " %40s : %3d/%-3d (%6.2f%%) Total gain: %6d Total Dtime: %0.3f "
424 info.name, info.num_successes, info.num_calls,
425 100.0 * info.num_successes / info.num_calls, info.total_gain,
426 info.time_spent, info.score);
430 OptimizerIndex optimizer_index)
const {
431 const RunInfo& info = run_infos_[info_positions_[optimizer_index]];
432 return info.num_calls;
437 for (
int i = 0; i < run_infos_.size(); ++i) {
438 const RunInfo& info = run_infos_[i];
439 LOG(
INFO) <<
" " << info.name <<
" " << info.total_gain
440 <<
" / " << info.time_spent <<
" = " << info.score <<
" "
441 << info.selectable <<
" " << info.time_spent_since_last_solution;
445 void OptimizerSelector::NewSolutionFound(
int64 gain) {
446 run_infos_[selected_index_].num_successes++;
447 run_infos_[selected_index_].total_gain += gain;
449 for (
int i = 0; i < run_infos_.size(); ++i) {
450 run_infos_[i].time_spent_since_last_solution = 0;
451 run_infos_[i].selectable =
true;
455 void OptimizerSelector::UpdateDeterministicTime(
double time_spent) {
456 run_infos_[selected_index_].time_spent += time_spent;
457 run_infos_[selected_index_].time_spent_since_last_solution += time_spent;
460 void OptimizerSelector::UpdateOrder() {
462 std::stable_sort(run_infos_.begin(), run_infos_.end(),
463 [](
const RunInfo&
a,
const RunInfo&
b) ->
bool {
464 if (a.total_gain == 0 && b.total_gain == 0)
465 return a.time_spent < b.time_spent;
466 return a.score > b.score;
470 for (
int i = 0; i < run_infos_.size(); ++i) {
471 info_positions_[run_infos_[i].optimizer_index] = i;