Files
ortools-clone/src/bop/bop_solver.cc
lperron@google.com 1aaf2814d7 misc
2015-01-16 17:02:32 +00:00

390 lines
14 KiB
C++

// Copyright 2010-2014 Google
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "bop/bop_solver.h"
#include <string>
#include <vector>
#include "base/commandlineflags.h"
#include "base/stringprintf.h"
#include "google/protobuf/text_format.h"
#include "base/threadpool.h"
#include "base/stl_util.h"
#include "bop/bop_fs.h"
#include "bop/bop_lns.h"
#include "bop/bop_ls.h"
#include "bop/bop_portfolio.h"
#include "bop/bop_util.h"
#include "bop/complete_optimizer.h"
#include "glop/lp_solver.h"
#include "lp_data/lp_print_utils.h"
#include "sat/boolean_problem.h"
#include "sat/lp_utils.h"
#include "sat/sat_solver.h"
#include "util/bitset.h"
using operations_research::glop::ColIndex;
using operations_research::glop::DenseRow;
using operations_research::glop::GlopParameters;
using operations_research::glop::LinearProgram;
using operations_research::glop::LPSolver;
using operations_research::LinearBooleanProblem;
using operations_research::LinearBooleanConstraint;
using operations_research::LinearObjective;
using operations_research::glop::ProblemStatus;
namespace operations_research {
namespace bop {
namespace {
// Updates the problem_state when the solution is proved optimal or the problem
// is proved infeasible.
// Returns true when the problem_state has been changed.
bool UpdateProblemStateBasedOnStatus(BopOptimizerBase::Status status,
ProblemState* problem_state) {
CHECK(nullptr != problem_state);
if (BopOptimizerBase::OPTIMAL_SOLUTION_FOUND == status) {
problem_state->MarkAsOptimal();
return true;
}
if (BopOptimizerBase::INFEASIBLE == status) {
problem_state->MarkAsInfeasible();
return true;
}
return false;
}
//------------------------------------------------------------------------------
// SolverSynchronizer
//------------------------------------------------------------------------------
// This internal class is a safety layer that defines the API the solvers have
// to use to synchronize with other solvers.
class SolverSynchronizer {
public:
struct Info {
Info(const BopParameters& params, const LinearBooleanProblem& problem)
: parameters(params), problem_state(problem), learned_infos() {
problem_state.SetParameters(params);
}
BopParameters parameters;
ProblemState problem_state;
StampedLearnedInfo learned_infos;
};
SolverSynchronizer(
int solver_index, const LinearBooleanProblem& problem,
std::vector<std::unique_ptr<SolverSynchronizer::Info>>* all_infos);
// Adds the learned info at the given stamp to the solver.
void AddLearnedInfo(SolverTimeStamp stamp, const LearnedInfo& learned_info);
// Synchronizes the problem state of the solver as defined by the
// BopParameters synchronization_type.
// Returns *problem_changed true when the state problem has changed due to
// synchronization.
// Returns *stop_solver true when the solver should be stopped as the solvers
// it might wait for have stopped.
void SynchronizeSolverInfos(SolverTimeStamp stamp, bool* problem_changed,
bool* stop_solver);
// Marks the solver as done, i.e. it reached its last stamp.
void MarkLastStampReached();
// Returns the mutable problem state of the solver.
ProblemState* GetMutableProblemState() const;
// Returns the parameters of the solver.
const BopParameters& GetParameters() const;
// Returns the index of the solver.
int solver_index() const { return solver_index_; }
private:
int solver_index_;
std::vector<std::unique_ptr<SolverSynchronizer::Info>>* all_infos_;
std::vector<int> solvers_to_sync_;
LearnedInfo learned_info_;
};
SolverSynchronizer::SolverSynchronizer(
int solver_index, const LinearBooleanProblem& problem,
std::vector<std::unique_ptr<SolverSynchronizer::Info>>* all_infos)
: solver_index_(solver_index),
all_infos_(all_infos),
solvers_to_sync_(),
learned_info_(problem) {
CHECK(nullptr != all_infos_);
CHECK_LT(solver_index_, all_infos_->size());
switch ((*all_infos_)[solver_index]->parameters.synchronization_type()) {
case BopParameters::NO_SYNCHRONIZATION:
solvers_to_sync_.push_back(solver_index);
break;
case BopParameters::SYNCHRONIZE_ALL:
for (int index = 0; index < all_infos_->size(); ++index) {
solvers_to_sync_.push_back(index);
}
break;
case BopParameters::SYNCHRONIZE_ON_RIGHT:
for (int index = 0; index <= solver_index; ++index) {
solvers_to_sync_.push_back(index);
}
break;
default:
LOG(FATAL) << "Unknown synchronization type.";
}
}
void SolverSynchronizer::AddLearnedInfo(SolverTimeStamp stamp,
const LearnedInfo& learned_info) {
(*all_infos_)[solver_index_]->learned_infos.AddLearnedInfo(stamp,
learned_info);
}
void SolverSynchronizer::SynchronizeSolverInfos(SolverTimeStamp stamp,
bool* problem_changed,
bool* stop_solver) {
CHECK(nullptr != problem_changed);
CHECK(nullptr != stop_solver);
*problem_changed = false;
*stop_solver = false;
for (const int index : solvers_to_sync_) {
learned_info_.Clear();
if (!(*all_infos_)[index]->learned_infos.GetLearnedInfo(stamp,
&learned_info_)) {
// Limit reached on solver index, stop search.
*stop_solver = true;
return;
}
*problem_changed =
GetMutableProblemState()->MergeLearnedInfo(learned_info_) ||
*problem_changed;
}
}
void SolverSynchronizer::MarkLastStampReached() {
(*all_infos_)[solver_index_]->learned_infos.MarkLastStampReached();
}
const BopParameters& SolverSynchronizer::GetParameters() const {
return (*all_infos_)[solver_index_]->parameters;
}
ProblemState* SolverSynchronizer::GetMutableProblemState() const {
return &((*all_infos_)[solver_index_]->problem_state);
}
// Runs a new solver until the time limit is reached. The result of the run
// is submitted to the SolverSynchronizer.
void RunOptimizer(const std::string& name, SolverSynchronizer* solver_sync) {
CHECK(nullptr != solver_sync);
const BopParameters& parameters = solver_sync->GetParameters();
ProblemState* const problem_state = solver_sync->GetMutableProblemState();
TimeLimit time_limit(parameters.max_time_in_seconds(),
parameters.max_deterministic_time());
LOG(INFO) << "limit: " << parameters.max_time_in_seconds() << " -- "
<< parameters.max_deterministic_time();
const int solver_index = solver_sync->solver_index();
CHECK_LT(0, parameters.solver_optimizer_sets_size());
// Use the last defined method set if needed.
const BopSolverOptimizerSet& solver_optimizer_sets =
solver_index >= parameters.solver_optimizer_sets_size()
? parameters.solver_optimizer_sets(
parameters.solver_optimizer_sets_size() - 1)
: parameters.solver_optimizer_sets(solver_index);
PortfolioOptimizer optimizer(*problem_state, parameters,
solver_optimizer_sets,
StringPrintf("Portfolio_%d", solver_index));
const BopOptimizerBase::Status sync_status =
optimizer.Synchronize(*problem_state);
if (UpdateProblemStateBasedOnStatus(sync_status, problem_state)) return;
LearnedInfo learned_info(problem_state->original_problem());
SolverTimeStamp stamp(0);
while (!time_limit.LimitReached()) {
const BopOptimizerBase::Status optimization_status =
optimizer.Optimize(parameters, &learned_info, &time_limit);
solver_sync->AddLearnedInfo(stamp, learned_info);
bool problem_changed = false;
bool stop_solver = false;
solver_sync->SynchronizeSolverInfos(stamp, &problem_changed, &stop_solver);
if (stop_solver || problem_state->IsOptimal() ||
problem_state->IsInfeasible()) {
return;
}
if (optimization_status == BopOptimizerBase::SOLUTION_FOUND) {
CHECK(problem_state->solution().IsFeasible());
LOG(INFO) << problem_state->solution().GetScaledCost()
<< " New solution! " << name;
}
if (problem_changed) {
// The problem has changed, re-synchronize the optimizer.
const BopOptimizerBase::Status sync_status =
optimizer.Synchronize(*problem_state);
if (UpdateProblemStateBasedOnStatus(sync_status, problem_state)) return;
problem_state->SynchronizationDone();
}
if (optimization_status == BopOptimizerBase::ABORT) {
break;
}
++stamp;
}
// The search is finished for this run, mark the last stamped reached
// to not block other runs waiting for updates on the learned_infos.
solver_sync->MarkLastStampReached();
}
} // anonymous namespace
//------------------------------------------------------------------------------
// BopSolver
//------------------------------------------------------------------------------
BopSolver::BopSolver(const LinearBooleanProblem& problem)
: problem_(problem),
problem_state_(problem),
parameters_(),
stats_("BopSolver") {
SCOPED_TIME_STAT(&stats_);
CHECK(sat::ValidateBooleanProblem(problem).ok());
}
BopSolver::~BopSolver() { IF_STATS_ENABLED(LOG(INFO) << stats_.StatString()); }
BopSolveStatus BopSolver::Solve() {
SCOPED_TIME_STAT(&stats_);
UpdateParameters();
// Create parameters, learned info and problem states for each solver.
const int num_solvers = parameters_.number_of_solvers();
LearnedInfo learned_info = problem_state_.GetLearnedInfo();
std::vector<std::unique_ptr<SolverSynchronizer::Info>> all_infos;
for (int index = 0; index < num_solvers; ++index) {
all_infos.emplace_back(new SolverSynchronizer::Info(parameters_, problem_));
all_infos.back()->parameters.set_random_seed(parameters_.random_seed() +
index);
all_infos.back()->problem_state.MergeLearnedInfo(learned_info);
}
// Build dedicated synchronizers to forbid unsafe access to the memory of the
// other solvers.
std::vector<SolverSynchronizer> synchronizers;
for (int index = 0; index < num_solvers; ++index) {
synchronizers.push_back(SolverSynchronizer(index, problem_, &all_infos));
}
if (num_solvers > 1) {
LOG(FATAL) << "Multi threading is not supported";
// ThreadPool thread_pool("ParallelSolve", num_solvers);
// thread_pool.StartWorkers();
// for (int index = 0; index < num_solvers; ++index) {
// const std::string solver_name = StringPrintf("Solver_%d", index);
// SolverSynchronizer* const solver_sync = &synchronizers[index];
// thread_pool.Schedule([solver_name, solver_sync]() {
// RunOptimizer(solver_name, solver_sync);
// });
// }
} else {
// TODO(user): Consider having a dedicated method to solve with only one
// solver.
const std::string solver_name = StringPrintf("Solver_%d", 0);
SolverSynchronizer* const solver_sync = &synchronizers[0];
RunOptimizer(solver_name, solver_sync);
}
// Synchronize the BopSolver::problem_state_ from the solvers' problem states.
for (int index = 0; index < num_solvers; ++index) {
LOG(INFO) << "Solution of solver " << index << ": "
<< all_infos[index]->problem_state.solution().GetScaledCost();
learned_info.Clear();
learned_info.solution = all_infos[index]->problem_state.solution();
learned_info.lower_bound = all_infos[index]->problem_state.lower_bound();
problem_state_.MergeLearnedInfo(learned_info);
if (all_infos[index]->problem_state.IsInfeasible()) {
problem_state_.MarkAsInfeasible();
}
}
return problem_state_.solution().IsFeasible()
? (problem_state_.IsOptimal()
? BopSolveStatus::OPTIMAL_SOLUTION_FOUND
: BopSolveStatus::FEASIBLE_SOLUTION_FOUND)
: (problem_state_.IsInfeasible()
? BopSolveStatus::INFEASIBLE_PROBLEM
: BopSolveStatus::NO_SOLUTION_FOUND);
}
BopSolveStatus BopSolver::Solve(const BopSolution& first_solution) {
SCOPED_TIME_STAT(&stats_);
CHECK(first_solution.IsFeasible());
LearnedInfo learned_info(problem_);
learned_info.solution = first_solution;
if (problem_state_.MergeLearnedInfo(learned_info) &&
problem_state_.IsOptimal()) {
return BopSolveStatus::OPTIMAL_SOLUTION_FOUND;
}
CHECK(problem_state_.solution().IsFeasible());
return Solve();
}
double BopSolver::GetScaledBestBound() const {
return sat::AddOffsetAndScaleObjectiveValue(
problem_, sat::Coefficient(problem_state_.lower_bound()));
}
double BopSolver::GetScaledGap() const {
return 100.0 * std::abs(problem_state_.solution().GetScaledCost() -
GetScaledBestBound()) /
std::abs(problem_state_.solution().GetScaledCost());
}
void BopSolver::UpdateParameters() {
if (parameters_.solver_optimizer_sets_size() == 0) {
// No user defined optimizers: Implement the default behavior.
const std::vector<std::string> default_optimizers = {
"type:LINEAR_RELAXATION initial_score:1.0 time_limit_ratio:0.15",
"type:LP_FIRST_SOLUTION initial_score:0.1",
"type:OBJECTIVE_FIRST_SOLUTION initial_score:0.1",
"type:RANDOM_FIRST_SOLUTION initial_score:0.1",
"type:SAT_CORE_BASED initial_score:1.0",
"type:LOCAL_SEARCH initial_score:2.0",
"type:RANDOM_CONSTRAINT_LNS initial_score:0.1",
"type:RANDOM_LNS_PROPAGATION initial_score:1.0",
"type:RANDOM_LNS_SAT initial_score:1.0",
"type:COMPLETE_LNS initial_score:0.1"};
BopSolverOptimizerSet* const solver_methods =
parameters_.add_solver_optimizer_sets();
for (const std::string& default_optimizer : default_optimizers) {
CHECK(::google::protobuf::TextFormat::ParseFromString(
default_optimizer, solver_methods->add_methods()));
}
}
problem_state_.SetParameters(parameters_);
}
} // namespace bop
} // namespace operations_research