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;
34 void BuildObjectiveTerms(
const LinearBooleanProblem& problem,
36 CHECK(objective_terms !=
nullptr);
38 if (!objective_terms->empty())
return;
40 const LinearObjective& objective = problem.objective();
41 const size_t num_objective_terms = objective.literals_size();
42 CHECK_EQ(num_objective_terms, objective.coefficients_size());
43 for (
int i = 0; i < num_objective_terms; ++i) {
44 CHECK_GT(objective.literals(i), 0);
45 CHECK_NE(objective.coefficients(i), 0);
47 const VariableIndex var_id(objective.literals(i) - 1);
49 objective_terms->push_back(BopConstraintTerm(var_id,
weight));
59 const BopSolverOptimizerSet& optimizer_set,
const std::string&
name)
70 number_of_consecutive_failing_optimizers_(0) {
75 if (parameters_.log_search_progress() || VLOG_IS_ON(1)) {
76 std::string stats_string;
77 for (OptimizerIndex i(0); i < optimizers_.size(); ++i) {
78 if (selector_->NumCallsForOptimizer(i) > 0) {
79 stats_string += selector_->PrintStats(i);
82 if (!stats_string.empty()) {
83 LOG(INFO) <<
"Stats. #new_solutions/#calls by optimizer:\n" +
95 if (state_update_stamp_ == problem_state.
update_stamp()) {
101 const bool first_time = (sat_propagator_.
NumVariables() == 0);
122 CHECK(learned_info !=
nullptr);
124 learned_info->
Clear();
127 SynchronizeIfNeeded(problem_state);
132 for (OptimizerIndex i(0); i < optimizers_.size(); ++i) {
133 selector_->SetOptimizerRunnability(
140 const double init_deterministic_time =
143 const OptimizerIndex selected_optimizer_id = selector_->SelectOptimizer();
145 LOG(INFO) <<
"All the optimizers are done.";
149 optimizers_[selected_optimizer_id];
150 if (
parameters.log_search_progress() || VLOG_IS_ON(1)) {
151 LOG(INFO) <<
" " << lower_bound_ <<
" .. " << upper_bound_ <<
" "
152 <<
name() <<
" - " << selected_optimizer->
name()
153 <<
". Time limit: " <<
time_limit->GetTimeLeft() <<
" -- "
162 selector_->TemporarilyMarkOptimizerAsUnselectable(selected_optimizer_id);
173 const double spent_deterministic_time =
174 time_limit->GetElapsedDeterministicTime() - init_deterministic_time;
175 selector_->UpdateScore(gain, spent_deterministic_time);
179 return optimization_status;
183 if (
parameters.has_max_number_of_consecutive_failing_optimizer_calls() &&
185 number_of_consecutive_failing_optimizers_ =
188 : number_of_consecutive_failing_optimizers_ + 1;
189 if (number_of_consecutive_failing_optimizers_ >
190 parameters.max_number_of_consecutive_failing_optimizer_calls()) {
202 void PortfolioOptimizer::AddOptimizer(
203 const LinearBooleanProblem& problem,
const BopParameters&
parameters,
204 const BopOptimizerMethod& optimizer_method) {
205 switch (optimizer_method.type()) {
206 case BopOptimizerMethod::SAT_CORE_BASED:
209 case BopOptimizerMethod::SAT_LINEAR_SEARCH:
213 case BopOptimizerMethod::LINEAR_RELAXATION:
214 optimizers_.push_back(
217 case BopOptimizerMethod::LOCAL_SEARCH: {
218 for (
int i = 1; i <=
parameters.max_num_decisions_in_ls(); ++i) {
220 absl::StrFormat(
"LS_%d", i), i, &sat_propagator_));
223 case BopOptimizerMethod::RANDOM_FIRST_SOLUTION:
224 optimizers_.push_back(
new BopRandomFirstSolutionGenerator(
225 "SATRandomFirstSolution",
parameters, &sat_propagator_,
228 case BopOptimizerMethod::RANDOM_VARIABLE_LNS:
229 BuildObjectiveTerms(problem, &objective_terms_);
230 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
233 new ObjectiveBasedNeighborhood(&objective_terms_, random_.get()),
236 case BopOptimizerMethod::RANDOM_VARIABLE_LNS_GUIDED_BY_LP:
237 BuildObjectiveTerms(problem, &objective_terms_);
238 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
239 "RandomVariableLnsWithLp",
241 new ObjectiveBasedNeighborhood(&objective_terms_, random_.get()),
244 case BopOptimizerMethod::RANDOM_CONSTRAINT_LNS:
245 BuildObjectiveTerms(problem, &objective_terms_);
246 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
247 "RandomConstraintLns",
249 new ConstraintBasedNeighborhood(&objective_terms_, random_.get()),
252 case BopOptimizerMethod::RANDOM_CONSTRAINT_LNS_GUIDED_BY_LP:
253 BuildObjectiveTerms(problem, &objective_terms_);
254 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
255 "RandomConstraintLnsWithLp",
257 new ConstraintBasedNeighborhood(&objective_terms_, random_.get()),
260 case BopOptimizerMethod::RELATION_GRAPH_LNS:
261 BuildObjectiveTerms(problem, &objective_terms_);
262 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
265 new RelationGraphBasedNeighborhood(problem, random_.get()),
268 case BopOptimizerMethod::RELATION_GRAPH_LNS_GUIDED_BY_LP:
269 BuildObjectiveTerms(problem, &objective_terms_);
270 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
271 "RelationGraphLnsWithLp",
273 new RelationGraphBasedNeighborhood(problem, random_.get()),
276 case BopOptimizerMethod::COMPLETE_LNS:
277 BuildObjectiveTerms(problem, &objective_terms_);
278 optimizers_.push_back(
279 new BopCompleteLNSOptimizer(
"LNS", objective_terms_));
281 case BopOptimizerMethod::USER_GUIDED_FIRST_SOLUTION:
282 optimizers_.push_back(
new GuidedSatFirstSolutionGenerator(
283 "SATUserGuidedFirstSolution",
286 case BopOptimizerMethod::LP_FIRST_SOLUTION:
287 optimizers_.push_back(
new GuidedSatFirstSolutionGenerator(
288 "SATLPFirstSolution",
291 case BopOptimizerMethod::OBJECTIVE_FIRST_SOLUTION:
292 optimizers_.push_back(
new GuidedSatFirstSolutionGenerator(
293 "SATObjectiveFirstSolution",
297 LOG(FATAL) <<
"Unknown optimizer type.";
301 void PortfolioOptimizer::CreateOptimizers(
302 const LinearBooleanProblem& problem,
const BopParameters&
parameters,
303 const BopSolverOptimizerSet& optimizer_set) {
304 random_ = absl::make_unique<MTRandom>(
parameters.random_seed());
307 VLOG(1) <<
"Finding symmetries of the problem.";
308 std::vector<std::unique_ptr<SparsePermutation>> generators;
310 std::unique_ptr<sat::SymmetryPropagator> propagator(
311 new sat::SymmetryPropagator);
312 for (
int i = 0; i < generators.size(); ++i) {
313 propagator->AddSymmetry(std::move(generators[i]));
319 const int max_num_optimizers =
320 optimizer_set.methods_size() +
parameters.max_num_decisions_in_ls() - 1;
321 optimizers_.reserve(max_num_optimizers);
322 for (
const BopOptimizerMethod& optimizer_method : optimizer_set.methods()) {
323 const OptimizerIndex old_size(optimizers_.size());
324 AddOptimizer(problem,
parameters, optimizer_method);
327 selector_ = absl::make_unique<OptimizerSelector>(optimizers_);
335 : run_infos_(), selected_index_(optimizers.size()) {
336 for (OptimizerIndex i(0); i < optimizers.
size(); ++i) {
337 info_positions_.
push_back(run_infos_.size());
338 run_infos_.push_back(RunInfo(i, optimizers[i]->
name()));
343 CHECK_GE(selected_index_, 0);
347 }
while (selected_index_ < run_infos_.size() &&
348 !run_infos_[selected_index_].RunnableAndSelectable());
350 if (selected_index_ >= run_infos_.size()) {
352 selected_index_ = -1;
353 for (
int i = 0; i < run_infos_.size(); ++i) {
354 if (run_infos_[i].RunnableAndSelectable()) {
364 bool too_much_time_spent =
false;
365 const double time_spent =
366 run_infos_[selected_index_].time_spent_since_last_solution;
367 for (
int i = 0; i < selected_index_; ++i) {
368 const RunInfo& info = run_infos_[i];
369 if (info.RunnableAndSelectable() &&
370 info.time_spent_since_last_solution < time_spent) {
371 too_much_time_spent =
true;
375 if (too_much_time_spent) {
383 ++run_infos_[selected_index_].num_calls;
384 return run_infos_[selected_index_].optimizer_index;
388 const bool new_solution_found = gain != 0;
389 if (new_solution_found) NewSolutionFound(gain);
390 UpdateDeterministicTime(time_spent);
392 const double new_score = time_spent == 0.0 ? 0.0 : gain / time_spent;
393 const double kErosion = 0.2;
394 const double kMinScore = 1E-6;
396 RunInfo& info = run_infos_[selected_index_];
397 const double old_score = info.score;
399 std::max(kMinScore, old_score * (1 - kErosion) + kErosion * new_score);
401 if (new_solution_found) {
403 selected_index_ = run_infos_.size();
408 OptimizerIndex optimizer_index) {
409 run_infos_[info_positions_[optimizer_index]].selectable =
false;
414 run_infos_[info_positions_[optimizer_index]].runnable = runnable;
418 OptimizerIndex optimizer_index)
const {
419 const RunInfo& info = run_infos_[info_positions_[optimizer_index]];
420 return absl::StrFormat(
421 " %40s : %3d/%-3d (%6.2f%%) Total gain: %6d Total Dtime: %0.3f "
423 info.name, info.num_successes, info.num_calls,
424 100.0 * info.num_successes / info.num_calls, info.total_gain,
425 info.time_spent, info.score);
429 OptimizerIndex optimizer_index)
const {
430 const RunInfo& info = run_infos_[info_positions_[optimizer_index]];
431 return info.num_calls;
436 for (
int i = 0; i < run_infos_.size(); ++i) {
437 const RunInfo& info = run_infos_[i];
438 LOG(INFO) <<
" " << info.name <<
" " << info.total_gain
439 <<
" / " << info.time_spent <<
" = " << info.score <<
" "
440 << info.selectable <<
" " << info.time_spent_since_last_solution;
444 void OptimizerSelector::NewSolutionFound(
int64 gain) {
445 run_infos_[selected_index_].num_successes++;
446 run_infos_[selected_index_].total_gain += gain;
448 for (
int i = 0; i < run_infos_.size(); ++i) {
449 run_infos_[i].time_spent_since_last_solution = 0;
450 run_infos_[i].selectable =
true;
454 void OptimizerSelector::UpdateDeterministicTime(
double time_spent) {
455 run_infos_[selected_index_].time_spent += time_spent;
456 run_infos_[selected_index_].time_spent_since_last_solution += time_spent;
459 void OptimizerSelector::UpdateOrder() {
461 std::stable_sort(run_infos_.begin(), run_infos_.end(),
462 [](
const RunInfo&
a,
const RunInfo&
b) ->
bool {
463 if (a.total_gain == 0 && b.total_gain == 0)
464 return a.time_spent < b.time_spent;
465 return a.score > b.score;
469 for (
int i = 0; i < run_infos_.size(); ++i) {
470 info_positions_[run_infos_[i].optimizer_index] = i;