26#include "absl/log/check.h"
27#include "absl/strings/str_format.h"
28#include "absl/strings/string_view.h"
37#include "ortools/bop/bop_parameters.pb.h"
44#include "ortools/sat/boolean_problem.pb.h"
54using ::operations_research::sat::LinearBooleanProblem;
55using ::operations_research::sat::LinearObjective;
58void BuildObjectiveTerms(
const LinearBooleanProblem& problem,
60 CHECK(objective_terms !=
nullptr);
62 if (!objective_terms->empty())
return;
64 const LinearObjective& objective = problem.objective();
65 const size_t num_objective_terms = objective.literals_size();
66 CHECK_EQ(num_objective_terms, objective.coefficients_size());
67 for (
int i = 0;
i < num_objective_terms; ++
i) {
68 CHECK_GT(objective.literals(i), 0);
69 CHECK_NE(objective.coefficients(i), 0);
71 const VariableIndex var_id(objective.literals(i) - 1);
72 const int64_t
weight = objective.coefficients(i);
73 objective_terms->push_back(BopConstraintTerm(var_id,
weight));
82 const ProblemState& problem_state,
const BopParameters&
parameters,
83 const BopSolverOptimizerSet& optimizer_set, absl::string_view
name)
92 lower_bound_(-glop::kInfinity),
93 upper_bound_(glop::kInfinity),
94 number_of_consecutive_failing_optimizers_(0) {
95 CreateOptimizers(problem_state.original_problem(),
parameters, optimizer_set);
99 if (parameters_.log_search_progress() || VLOG_IS_ON(1)) {
100 std::string stats_string;
101 for (OptimizerIndex i(0); i < optimizers_.size(); ++i) {
102 if (selector_->NumCallsForOptimizer(i) > 0) {
103 stats_string += selector_->PrintStats(i);
106 if (!stats_string.empty()) {
107 LOG(INFO) <<
"Stats. #new_solutions/#calls by optimizer:\n" +
118 const ProblemState& problem_state) {
119 if (state_update_stamp_ == problem_state.update_stamp()) {
122 state_update_stamp_ = problem_state.update_stamp();
125 const bool first_time = (sat_propagator_.
NumVariables() == 0);
136 lower_bound_ = problem_state.GetScaledLowerBound();
137 upper_bound_ = problem_state.solution().IsFeasible()
138 ? problem_state.solution().GetScaledCost()
144 const BopParameters&
parameters,
const ProblemState& problem_state,
146 CHECK(learned_info !=
nullptr);
148 learned_info->
Clear();
151 SynchronizeIfNeeded(problem_state);
156 for (OptimizerIndex i(0); i < optimizers_.size(); ++i) {
157 selector_->SetOptimizerRunnability(
161 const int64_t init_cost = problem_state.solution().IsFeasible()
162 ? problem_state.solution().GetCost()
163 : std::numeric_limits<int64_t>::max();
164 const double init_deterministic_time =
167 const OptimizerIndex selected_optimizer_id = selector_->SelectOptimizer();
169 LOG(INFO) <<
"All the optimizers are done.";
173 optimizers_[selected_optimizer_id];
174 if (
parameters.log_search_progress() || VLOG_IS_ON(1)) {
175 LOG(INFO) <<
" " << lower_bound_ <<
" .. " << upper_bound_ <<
" "
176 <<
name() <<
" - " << selected_optimizer->
name()
177 <<
". Time limit: " <<
time_limit->GetTimeLeft() <<
" -- "
186 selector_->TemporarilyMarkOptimizerAsUnselectable(selected_optimizer_id);
194 ? (init_cost == std::numeric_limits<int64_t>::max()
198 const double spent_deterministic_time =
199 time_limit->GetElapsedDeterministicTime() - init_deterministic_time;
200 selector_->UpdateScore(gain, spent_deterministic_time);
204 return optimization_status;
208 if (
parameters.has_max_number_of_consecutive_failing_optimizer_calls() &&
209 problem_state.solution().IsFeasible()) {
210 number_of_consecutive_failing_optimizers_ =
213 : number_of_consecutive_failing_optimizers_ + 1;
214 if (number_of_consecutive_failing_optimizers_ >
215 parameters.max_number_of_consecutive_failing_optimizer_calls()) {
227void PortfolioOptimizer::AddOptimizer(
228 const LinearBooleanProblem& problem,
const BopParameters&
parameters,
229 const BopOptimizerMethod& optimizer_method) {
230 switch (optimizer_method.type()) {
231 case BopOptimizerMethod::SAT_CORE_BASED:
234 case BopOptimizerMethod::SAT_LINEAR_SEARCH:
238 case BopOptimizerMethod::LINEAR_RELAXATION:
239 optimizers_.push_back(
242 case BopOptimizerMethod::LOCAL_SEARCH: {
243 for (
int i = 1; i <=
parameters.max_num_decisions_in_ls(); ++i) {
245 absl::StrFormat(
"LS_%d", i), i, random_, &sat_propagator_));
248 case BopOptimizerMethod::RANDOM_FIRST_SOLUTION:
249 optimizers_.push_back(
new BopRandomFirstSolutionGenerator(
250 "SATRandomFirstSolution",
parameters, &sat_propagator_, random_));
252 case BopOptimizerMethod::RANDOM_VARIABLE_LNS:
253 BuildObjectiveTerms(problem, &objective_terms_);
254 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
257 new ObjectiveBasedNeighborhood(&objective_terms_, random_),
260 case BopOptimizerMethod::RANDOM_VARIABLE_LNS_GUIDED_BY_LP:
261 BuildObjectiveTerms(problem, &objective_terms_);
262 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
263 "RandomVariableLnsWithLp",
265 new ObjectiveBasedNeighborhood(&objective_terms_, random_),
268 case BopOptimizerMethod::RANDOM_CONSTRAINT_LNS:
269 BuildObjectiveTerms(problem, &objective_terms_);
270 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
271 "RandomConstraintLns",
273 new ConstraintBasedNeighborhood(&objective_terms_, random_),
276 case BopOptimizerMethod::RANDOM_CONSTRAINT_LNS_GUIDED_BY_LP:
277 BuildObjectiveTerms(problem, &objective_terms_);
278 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
279 "RandomConstraintLnsWithLp",
281 new ConstraintBasedNeighborhood(&objective_terms_, random_),
284 case BopOptimizerMethod::RELATION_GRAPH_LNS:
285 BuildObjectiveTerms(problem, &objective_terms_);
286 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
289 new RelationGraphBasedNeighborhood(problem, random_),
292 case BopOptimizerMethod::RELATION_GRAPH_LNS_GUIDED_BY_LP:
293 BuildObjectiveTerms(problem, &objective_terms_);
294 optimizers_.push_back(
new BopAdaptiveLNSOptimizer(
295 "RelationGraphLnsWithLp",
297 new RelationGraphBasedNeighborhood(problem, random_),
300 case BopOptimizerMethod::COMPLETE_LNS:
301 BuildObjectiveTerms(problem, &objective_terms_);
302 optimizers_.push_back(
303 new BopCompleteLNSOptimizer(
"LNS", objective_terms_));
305 case BopOptimizerMethod::USER_GUIDED_FIRST_SOLUTION:
306 optimizers_.push_back(
new GuidedSatFirstSolutionGenerator(
307 "SATUserGuidedFirstSolution",
310 case BopOptimizerMethod::LP_FIRST_SOLUTION:
311 optimizers_.push_back(
new GuidedSatFirstSolutionGenerator(
312 "SATLPFirstSolution",
315 case BopOptimizerMethod::OBJECTIVE_FIRST_SOLUTION:
316 optimizers_.push_back(
new GuidedSatFirstSolutionGenerator(
317 "SATObjectiveFirstSolution",
321 LOG(FATAL) <<
"Unknown optimizer type.";
325void PortfolioOptimizer::CreateOptimizers(
326 const LinearBooleanProblem& problem,
const BopParameters&
parameters,
327 const BopSolverOptimizerSet& optimizer_set) {
329 VLOG(1) <<
"Finding symmetries of the problem.";
330 std::vector<std::unique_ptr<SparsePermutation>> generators;
332 std::unique_ptr<sat::SymmetryPropagator> propagator(
333 new sat::SymmetryPropagator);
334 for (
int i = 0;
i < generators.size(); ++
i) {
335 propagator->AddSymmetry(std::move(generators[i]));
341 const int max_num_optimizers =
342 optimizer_set.methods_size() +
parameters.max_num_decisions_in_ls() - 1;
343 optimizers_.reserve(max_num_optimizers);
344 for (
const BopOptimizerMethod& optimizer_method : optimizer_set.methods()) {
345 const OptimizerIndex old_size(optimizers_.size());
346 AddOptimizer(problem,
parameters, optimizer_method);
349 selector_ = std::make_unique<OptimizerSelector>(optimizers_);
358 : run_infos_(), selected_index_(optimizers.
size()) {
359 for (OptimizerIndex i(0); i < optimizers.size(); ++i) {
360 info_positions_.push_back(run_infos_.size());
361 run_infos_.push_back(RunInfo(i, optimizers[i]->
name()));
366 CHECK_GE(selected_index_, 0);
370 }
while (selected_index_ < run_infos_.size() &&
371 !run_infos_[selected_index_].RunnableAndSelectable());
373 if (selected_index_ >= run_infos_.size()) {
375 selected_index_ = -1;
376 for (
int i = 0; i < run_infos_.size(); ++i) {
377 if (run_infos_[i].RunnableAndSelectable()) {
387 bool too_much_time_spent =
false;
388 const double time_spent =
389 run_infos_[selected_index_].time_spent_since_last_solution;
390 for (
int i = 0; i < selected_index_; ++i) {
391 const RunInfo& info = run_infos_[i];
392 if (info.RunnableAndSelectable() &&
393 info.time_spent_since_last_solution < time_spent) {
394 too_much_time_spent =
true;
398 if (too_much_time_spent) {
406 ++run_infos_[selected_index_].num_calls;
407 return run_infos_[selected_index_].optimizer_index;
411 const bool new_solution_found = gain != 0;
412 if (new_solution_found) NewSolutionFound(gain);
413 UpdateDeterministicTime(time_spent);
415 const double new_score = time_spent == 0.0 ? 0.0 : gain / time_spent;
416 const double kErosion = 0.2;
417 const double kMinScore = 1E-6;
419 RunInfo& info = run_infos_[selected_index_];
420 const double old_score = info.score;
422 std::max(kMinScore, old_score * (1 - kErosion) + kErosion * new_score);
424 if (new_solution_found) {
426 selected_index_ = run_infos_.size();
431 OptimizerIndex optimizer_index) {
432 run_infos_[info_positions_[optimizer_index]].selectable =
false;
437 run_infos_[info_positions_[optimizer_index]].runnable = runnable;
441 OptimizerIndex optimizer_index)
const {
442 const RunInfo& info = run_infos_[info_positions_[optimizer_index]];
443 return absl::StrFormat(
444 " %40s : %3d/%-3d (%6.2f%%) Total gain: %6d Total Dtime: %0.3f "
446 info.name, info.num_successes, info.num_calls,
447 100.0 * info.num_successes / info.num_calls, info.total_gain,
448 info.time_spent, info.score);
452 OptimizerIndex optimizer_index)
const {
453 const RunInfo& info = run_infos_[info_positions_[optimizer_index]];
454 return info.num_calls;
459 for (
int i = 0; i < run_infos_.size(); ++i) {
460 const RunInfo& info = run_infos_[i];
461 LOG(INFO) <<
" " << info.name <<
" " << info.total_gain
462 <<
" / " << info.time_spent <<
" = " << info.score <<
" "
463 << info.selectable <<
" " << info.time_spent_since_last_solution;
467void OptimizerSelector::NewSolutionFound(int64_t gain) {
468 run_infos_[selected_index_].num_successes++;
469 run_infos_[selected_index_].total_gain += gain;
471 for (
int i = 0; i < run_infos_.size(); ++i) {
472 run_infos_[i].time_spent_since_last_solution = 0;
473 run_infos_[i].selectable =
true;
477void OptimizerSelector::UpdateDeterministicTime(
double time_spent) {
478 run_infos_[selected_index_].time_spent += time_spent;
479 run_infos_[selected_index_].time_spent_since_last_solution += time_spent;
482void OptimizerSelector::UpdateOrder() {
484 std::stable_sort(run_infos_.begin(), run_infos_.end(),
485 [](
const RunInfo&
a,
const RunInfo&
b) ->
bool {
486 if (a.total_gain == 0 && b.total_gain == 0)
487 return a.time_spent < b.time_spent;
488 return a.score > b.score;
492 for (
int i = 0;
i < run_infos_.size(); ++
i) {
493 info_positions_[run_infos_[
i].optimizer_index] =
i;
virtual Status Optimize(const BopParameters ¶meters, const ProblemState &problem_state, LearnedInfo *learned_info, TimeLimit *time_limit)=0
@ ABORT
There is no need to call this optimizer again on the same problem state.
const std::string & name() const
Returns the name given at construction.
OptimizerIndex SelectOptimizer()
std::string PrintStats(OptimizerIndex optimizer_index) const
Returns statistics about the given optimizer.
void TemporarilyMarkOptimizerAsUnselectable(OptimizerIndex optimizer_index)
void SetOptimizerRunnability(OptimizerIndex optimizer_index, bool runnable)
void UpdateScore(int64_t gain, double time_spent)
int NumCallsForOptimizer(OptimizerIndex optimizer_index) const
void DebugPrint() const
Prints some debug information. Should not be used in production.
OptimizerSelector(const util_intops::StrongVector< OptimizerIndex, BopOptimizerBase * > &optimizers)
bool ShouldBeRun(const ProblemState &problem_state) const override
Status Optimize(const BopParameters ¶meters, const ProblemState &problem_state, LearnedInfo *learned_info, TimeLimit *time_limit) override
~PortfolioOptimizer() override
PortfolioOptimizer(const ProblemState &problem_state, const BopParameters ¶meters, const BopSolverOptimizerSet &optimizer_set, absl::string_view name)
void TakePropagatorOwnership(std::unique_ptr< SatPropagator > propagator)
void AddPropagator(SatPropagator *propagator)
const std::string name
A name for logging purposes.
void STLDeleteElements(T *container)
BopOptimizerBase::Status LoadStateProblemToSatSolver(const ProblemState &problem_state, sat::SatSolver *sat_solver)
util_intops::StrongVector< SparseIndex, BopConstraintTerm > BopConstraintTerms
const OptimizerIndex kInvalidOptimizerIndex(-1)
constexpr double kInfinity
Infinity for type Fractional.
void FindLinearBooleanProblemSymmetries(const LinearBooleanProblem &problem, std::vector< std::unique_ptr< SparsePermutation > > *generators)
void UseObjectiveForSatAssignmentPreference(const LinearBooleanProblem &problem, SatSolver *solver)
In SWIG mode, we don't want anything besides these top-level includes.
BopSolution solution
New solution. Note that the solution might be infeasible.