Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1735 lines (1577 sloc)
60.6 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2010-2018 Google LLC | |
// 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 "ortools/linear_solver/linear_solver.h" | |
#if !defined(_MSC_VER) | |
#include <unistd.h> | |
#endif | |
#include <cmath> | |
#include <cstddef> | |
#include <utility> | |
#include "ortools/base/commandlineflags.h" | |
#include "ortools/base/integral_types.h" | |
#include "ortools/base/logging.h" | |
#include "ortools/base/timer.h" | |
#include "ortools/port/file.h" | |
#include "ortools/base/accurate_sum.h" | |
#include "ortools/base/canonical_errors.h" | |
#include "ortools/base/join.h" | |
#include "ortools/base/map_util.h" | |
#include "ortools/base/mutex.h" | |
#include "ortools/base/stl_util.h" | |
#include "ortools/base/stringprintf.h" | |
#include "ortools/linear_solver/linear_solver.pb.h" | |
#include "ortools/linear_solver/model_exporter.h" | |
#include "ortools/linear_solver/model_validator.h" | |
#include "ortools/util/fp_utils.h" | |
DEFINE_bool(verify_solution, false, | |
"Systematically verify the solution when calling Solve()" | |
", and change the return value of Solve() to ABNORMAL if" | |
" an error was detected."); | |
DEFINE_bool(log_verification_errors, true, | |
"If --verify_solution is set: LOG(ERROR) all errors detected" | |
" during the verification of the solution."); | |
DEFINE_bool(linear_solver_enable_verbose_output, false, | |
"If set, enables verbose output for the solver. Setting this flag" | |
" is the same as calling MPSolver::EnableOutput()."); | |
DEFINE_bool(mpsolver_bypass_model_validation, false, | |
"If set, the user-provided Model won't be verified before Solve()." | |
" Invalid models will typically trigger various error responses" | |
" from the underlying solvers; sometimes crashes."); | |
namespace operations_research { | |
double MPConstraint::GetCoefficient(const MPVariable* const var) const { | |
DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var; | |
if (var == nullptr) return 0.0; | |
return gtl::FindWithDefault(coefficients_, var, 0.0); | |
} | |
void MPConstraint::SetCoefficient(const MPVariable* const var, double coeff) { | |
DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var; | |
if (var == nullptr) return; | |
if (coeff == 0.0) { | |
auto it = coefficients_.find(var); | |
// If setting a coefficient to 0 when this coefficient did not | |
// exist or was already 0, do nothing: skip | |
// interface_->SetCoefficient() and do not store a coefficient in | |
// the map. Note that if the coefficient being set to 0 did exist | |
// and was not 0, we do have to keep a 0 in the coefficients_ map, | |
// because the extraction of the constraint might rely on it, | |
// depending on the underlying solver. | |
if (it != coefficients_.end() && it->second != 0.0) { | |
const double old_value = it->second; | |
it->second = 0.0; | |
interface_->SetCoefficient(this, var, 0.0, old_value); | |
} | |
return; | |
} | |
auto insertion_result = coefficients_.insert(std::make_pair(var, coeff)); | |
const double old_value = | |
insertion_result.second ? 0.0 : insertion_result.first->second; | |
insertion_result.first->second = coeff; | |
interface_->SetCoefficient(this, var, coeff, old_value); | |
} | |
void MPConstraint::Clear() { | |
interface_->ClearConstraint(this); | |
coefficients_.clear(); | |
} | |
void MPConstraint::SetBounds(double lb, double ub) { | |
const bool change = lb != lb_ || ub != ub_; | |
lb_ = lb; | |
ub_ = ub; | |
if (change && interface_->constraint_is_extracted(index_)) { | |
interface_->SetConstraintBounds(index_, lb_, ub_); | |
} | |
} | |
double MPConstraint::dual_value() const { | |
if (!interface_->IsContinuous()) { | |
LOG(DFATAL) << "Dual value only available for continuous problems"; | |
return 0.0; | |
} | |
if (!interface_->CheckSolutionIsSynchronizedAndExists()) return 0.0; | |
return dual_value_; | |
} | |
MPSolver::BasisStatus MPConstraint::basis_status() const { | |
if (!interface_->IsContinuous()) { | |
LOG(DFATAL) << "Basis status only available for continuous problems"; | |
return MPSolver::FREE; | |
} | |
if (!interface_->CheckSolutionIsSynchronizedAndExists()) { | |
return MPSolver::FREE; | |
} | |
// This is done lazily as this method is expected to be rarely used. | |
return interface_->row_status(index_); | |
} | |
bool MPConstraint::ContainsNewVariables() { | |
const int last_variable_index = interface_->last_variable_index(); | |
for (const auto& entry : coefficients_) { | |
const int variable_index = entry.first->index(); | |
if (variable_index >= last_variable_index || | |
!interface_->variable_is_extracted(variable_index)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
// ----- MPObjective ----- | |
double MPObjective::GetCoefficient(const MPVariable* const var) const { | |
DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var; | |
if (var == nullptr) return 0.0; | |
return gtl::FindWithDefault(coefficients_, var, 0.0); | |
} | |
void MPObjective::SetCoefficient(const MPVariable* const var, double coeff) { | |
DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(var)) << var; | |
if (var == nullptr) return; | |
if (coeff == 0.0) { | |
auto it = coefficients_.find(var); | |
// See the discussion on MPConstraint::SetCoefficient() for 0 coefficients, | |
// the same reasoning applies here. | |
if (it == coefficients_.end() || it->second == 0.0) return; | |
it->second = 0.0; | |
} else { | |
coefficients_[var] = coeff; | |
} | |
interface_->SetObjectiveCoefficient(var, coeff); | |
} | |
void MPObjective::SetOffset(double value) { | |
offset_ = value; | |
interface_->SetObjectiveOffset(offset_); | |
} | |
namespace { | |
void CheckLinearExpr(const MPSolver& solver, const LinearExpr& linear_expr) { | |
for (auto var_value_pair : linear_expr.terms()) { | |
CHECK(solver.OwnsVariable(var_value_pair.first)) | |
<< "Bad MPVariable* in LinearExpr, did you try adding an integer to an " | |
"MPVariable* directly?"; | |
} | |
} | |
} // namespace | |
void MPObjective::OptimizeLinearExpr(const LinearExpr& linear_expr, | |
bool is_maximization) { | |
CheckLinearExpr(*interface_->solver_, linear_expr); | |
interface_->ClearObjective(); | |
coefficients_.clear(); | |
offset_ = linear_expr.offset(); | |
for (const auto& kv : linear_expr.terms()) { | |
SetCoefficient(kv.first, kv.second); | |
} | |
SetOptimizationDirection(is_maximization); | |
} | |
void MPObjective::AddLinearExpr(const LinearExpr& linear_expr) { | |
CheckLinearExpr(*interface_->solver_, linear_expr); | |
offset_ += linear_expr.offset(); | |
for (const auto& kv : linear_expr.terms()) { | |
SetCoefficient(kv.first, GetCoefficient(kv.first) + kv.second); | |
} | |
} | |
void MPObjective::Clear() { | |
interface_->ClearObjective(); | |
coefficients_.clear(); | |
offset_ = 0.0; | |
SetMinimization(); | |
} | |
void MPObjective::SetOptimizationDirection(bool maximize) { | |
// Note(user): The maximize_ bool would more naturally belong to the | |
// MPObjective, but it actually has to be a member of MPSolverInterface, | |
// because some implementations (such as GLPK) need that bool for the | |
// MPSolverInterface constructor, i.e at a time when the MPObjective is not | |
// constructed yet (MPSolverInterface is always built before MPObjective | |
// when a new MPSolver is constructed). | |
interface_->maximize_ = maximize; | |
interface_->SetOptimizationDirection(maximize); | |
} | |
bool MPObjective::maximization() const { return interface_->maximize_; } | |
bool MPObjective::minimization() const { return !interface_->maximize_; } | |
double MPObjective::Value() const { | |
// Note(user): implementation-wise, the objective value belongs more | |
// naturally to the MPSolverInterface, since all of its implementations write | |
// to it directly. | |
return interface_->objective_value(); | |
} | |
double MPObjective::BestBound() const { | |
// Note(user): the best objective bound belongs to the interface for the | |
// same reasons as the objective value does. | |
return interface_->best_objective_bound(); | |
} | |
// ----- MPVariable ----- | |
double MPVariable::solution_value() const { | |
if (!interface_->CheckSolutionIsSynchronizedAndExists()) return 0.0; | |
// If the underlying solver supports integer variables, and this is an integer | |
// variable, we round the solution value (i.e., clients usually expect precise | |
// integer values for integer variables). | |
return (integer_ && interface_->IsMIP()) ? round(solution_value_) | |
: solution_value_; | |
} | |
double MPVariable::unrounded_solution_value() const { | |
if (!interface_->CheckSolutionIsSynchronizedAndExists()) return 0.0; | |
return solution_value_; | |
} | |
double MPVariable::reduced_cost() const { | |
if (!interface_->IsContinuous()) { | |
LOG(DFATAL) << "Reduced cost only available for continuous problems"; | |
return 0.0; | |
} | |
if (!interface_->CheckSolutionIsSynchronizedAndExists()) return 0.0; | |
return reduced_cost_; | |
} | |
MPSolver::BasisStatus MPVariable::basis_status() const { | |
if (!interface_->IsContinuous()) { | |
LOG(DFATAL) << "Basis status only available for continuous problems"; | |
return MPSolver::FREE; | |
} | |
if (!interface_->CheckSolutionIsSynchronizedAndExists()) { | |
return MPSolver::FREE; | |
} | |
// This is done lazily as this method is expected to be rarely used. | |
return interface_->column_status(index_); | |
} | |
void MPVariable::SetBounds(double lb, double ub) { | |
const bool change = lb != lb_ || ub != ub_; | |
lb_ = lb; | |
ub_ = ub; | |
if (change && interface_->variable_is_extracted(index_)) { | |
interface_->SetVariableBounds(index_, lb_, ub_); | |
} | |
} | |
void MPVariable::SetInteger(bool integer) { | |
if (integer_ != integer) { | |
integer_ = integer; | |
if (interface_->variable_is_extracted(index_)) { | |
interface_->SetVariableInteger(index_, integer); | |
} | |
} | |
} | |
// ----- Interface shortcuts ----- | |
bool MPSolver::IsMIP() const { return interface_->IsMIP(); } | |
std::string MPSolver::SolverVersion() const { | |
return interface_->SolverVersion(); | |
} | |
void* MPSolver::underlying_solver() { return interface_->underlying_solver(); } | |
// ---- Solver-specific parameters ---- | |
bool MPSolver::SetSolverSpecificParametersAsString( | |
const std::string& parameters) { | |
solver_specific_parameter_string_ = parameters; | |
return interface_->SetSolverSpecificParametersAsString(parameters); | |
} | |
// ----- Solver ----- | |
#if defined(USE_CLP) || defined(USE_CBC) | |
extern MPSolverInterface* BuildCLPInterface(MPSolver* const solver); | |
#endif | |
#if defined(USE_CBC) | |
extern MPSolverInterface* BuildCBCInterface(MPSolver* const solver); | |
#endif | |
#if defined(USE_GLPK) | |
extern MPSolverInterface* BuildGLPKInterface(bool mip, MPSolver* const solver); | |
#endif | |
#if defined(USE_BOP) | |
extern MPSolverInterface* BuildBopInterface(MPSolver* const solver); | |
#endif | |
#if defined(USE_GLOP) | |
extern MPSolverInterface* BuildGLOPInterface(MPSolver* const solver); | |
#endif | |
#if defined(USE_SCIP) | |
extern MPSolverInterface* BuildSCIPInterface(MPSolver* const solver); | |
#endif | |
#if defined(USE_GUROBI) | |
extern MPSolverInterface* BuildGurobiInterface(bool mip, | |
MPSolver* const solver); | |
#endif | |
#if defined(USE_CPLEX) | |
extern MPSolverInterface* BuildCplexInterface(bool mip, MPSolver* const solver); | |
extern MPSolverInterface* BuildGLOPInterface(MPSolver* const solver); | |
#endif | |
namespace { | |
MPSolverInterface* BuildSolverInterface(MPSolver* const solver) { | |
DCHECK(solver != nullptr); | |
switch (solver->ProblemType()) { | |
#if defined(USE_BOP) | |
case MPSolver::BOP_INTEGER_PROGRAMMING: | |
return BuildBopInterface(solver); | |
#endif | |
#if defined(USE_GLOP) | |
case MPSolver::GLOP_LINEAR_PROGRAMMING: | |
return BuildGLOPInterface(solver); | |
#endif | |
#if defined(USE_GLPK) | |
case MPSolver::GLPK_LINEAR_PROGRAMMING: | |
return BuildGLPKInterface(false, solver); | |
case MPSolver::GLPK_MIXED_INTEGER_PROGRAMMING: | |
return BuildGLPKInterface(true, solver); | |
#endif | |
#if defined(USE_CLP) || defined(USE_CBC) | |
case MPSolver::CLP_LINEAR_PROGRAMMING: | |
return BuildCLPInterface(solver); | |
#endif | |
#if defined(USE_CBC) | |
case MPSolver::CBC_MIXED_INTEGER_PROGRAMMING: | |
return BuildCBCInterface(solver); | |
#endif | |
#if defined(USE_SCIP) | |
case MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING: | |
return BuildSCIPInterface(solver); | |
#endif | |
#if defined(USE_GUROBI) | |
case MPSolver::GUROBI_LINEAR_PROGRAMMING: | |
return BuildGurobiInterface(false, solver); | |
case MPSolver::GUROBI_MIXED_INTEGER_PROGRAMMING: | |
return BuildGurobiInterface(true, solver); | |
#endif | |
#if defined(USE_CPLEX) | |
case MPSolver::CPLEX_LINEAR_PROGRAMMING: | |
return BuildCplexInterface(false, solver); | |
case MPSolver::CPLEX_MIXED_INTEGER_PROGRAMMING: | |
return BuildCplexInterface(true, solver); | |
#endif | |
default: | |
// TODO(user): Revert to the best *available* interface. | |
LOG(FATAL) << "Linear solver not recognized."; | |
} | |
return nullptr; | |
} | |
} // namespace | |
namespace { | |
int NumDigits(int n) { | |
// Number of digits needed to write a non-negative integer in base 10. | |
// Note(user): max(1, log(0) + 1) == max(1, -inf) == 1. | |
#if defined(_MSC_VER) | |
return static_cast<int>(std::max(1.0L, log(1.0L * n) / log(10.0L) + 1.0)); | |
#else | |
return static_cast<int>(std::max(1.0, log10(static_cast<double>(n)) + 1.0)); | |
#endif | |
} | |
MPSolver::OptimizationProblemType DetourProblemType( | |
MPSolver::OptimizationProblemType problem_type) { | |
return problem_type; | |
} | |
} // namespace | |
MPSolver::MPSolver(const std::string& name, | |
OptimizationProblemType problem_type) | |
: name_(name), | |
problem_type_(DetourProblemType(problem_type)), | |
time_limit_(0.0) { | |
timer_.Restart(); | |
interface_.reset(BuildSolverInterface(this)); | |
if (FLAGS_linear_solver_enable_verbose_output) { | |
EnableOutput(); | |
} | |
objective_.reset(new MPObjective(interface_.get())); | |
} | |
MPSolver::~MPSolver() { Clear(); } | |
// static | |
bool MPSolver::SupportsProblemType(OptimizationProblemType problem_type) { | |
#ifdef USE_CLP | |
if (problem_type == CLP_LINEAR_PROGRAMMING) return true; | |
#endif | |
#ifdef USE_GLPK | |
if (problem_type == GLPK_LINEAR_PROGRAMMING) return true; | |
if (problem_type == GLPK_MIXED_INTEGER_PROGRAMMING) return true; | |
#endif | |
#ifdef USE_BOP | |
if (problem_type == BOP_INTEGER_PROGRAMMING) return true; | |
#endif | |
#ifdef USE_GLOP | |
if (problem_type == GLOP_LINEAR_PROGRAMMING) return true; | |
#endif | |
#ifdef USE_GUROBI | |
if (problem_type == GUROBI_LINEAR_PROGRAMMING) return true; | |
if (problem_type == GUROBI_MIXED_INTEGER_PROGRAMMING) return true; | |
#endif | |
#ifdef USE_SCIP | |
if (problem_type == SCIP_MIXED_INTEGER_PROGRAMMING) return true; | |
#endif | |
#ifdef USE_CBC | |
if (problem_type == CBC_MIXED_INTEGER_PROGRAMMING) return true; | |
#endif | |
return false; | |
} | |
// static | |
bool MPSolver::ParseSolverType(absl::string_view solver, | |
MPSolver::OptimizationProblemType* type) { | |
if (solver == "glop") { | |
*type = MPSolver::GLOP_LINEAR_PROGRAMMING; | |
#if defined(USE_GLPK) | |
} else if (solver == "glpk_lp") { | |
*type = MPSolver::GLPK_LINEAR_PROGRAMMING; | |
#endif | |
#if defined(USE_CLP) | |
} else if (solver == "clp") { | |
*type = MPSolver::CLP_LINEAR_PROGRAMMING; | |
#endif | |
#if defined(USE_GUROBI) | |
} else if (solver == "gurobi_lp") { | |
*type = MPSolver::GUROBI_LINEAR_PROGRAMMING; | |
#endif | |
#if defined(USE_SCIP) | |
} else if (solver == "scip") { | |
*type = MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING; | |
#endif | |
#if defined(USE_GUROBI) | |
} else if (solver == "cbc") { | |
*type = MPSolver::CBC_MIXED_INTEGER_PROGRAMMING; | |
#endif | |
#if defined(USE_GLPK) | |
} else if (solver == "glpk_mip") { | |
*type = MPSolver::GLPK_MIXED_INTEGER_PROGRAMMING; | |
#endif | |
#if defined(USE_GUROBI) | |
} else if (solver == "gurobi_mip") { | |
*type = MPSolver::GUROBI_MIXED_INTEGER_PROGRAMMING; | |
#endif | |
#if defined(USE_BOP) | |
} else if (solver == "bop") { | |
*type = MPSolver::BOP_INTEGER_PROGRAMMING; | |
#endif | |
} else { | |
return false; | |
} | |
return true; | |
} | |
MPVariable* MPSolver::LookupVariableOrNull(const std::string& var_name) const { | |
if (!variable_name_to_index_) GenerateVariableNameIndex(); | |
std::unordered_map<std::string, int>::const_iterator it = | |
variable_name_to_index_->find(var_name); | |
if (it == variable_name_to_index_->end()) return nullptr; | |
return variables_[it->second]; | |
} | |
MPConstraint* MPSolver::LookupConstraintOrNull( | |
const std::string& constraint_name) const { | |
if (!constraint_name_to_index_) GenerateConstraintNameIndex(); | |
const auto it = constraint_name_to_index_->find(constraint_name); | |
if (it == constraint_name_to_index_->end()) return nullptr; | |
return constraints_[it->second]; | |
} | |
// ----- Methods using protocol buffers ----- | |
MPSolverResponseStatus MPSolver::LoadModelFromProto( | |
const MPModelProto& input_model, std::string* error_message) { | |
// The variable and constraint names are dropped, because we allow | |
// duplicate names in the proto (they're not considered as 'ids'), | |
// unlike the MPSolver C++ API which crashes if there are duplicate names. | |
// Clearing the names makes the MPSolver generate unique names. | |
return LoadModelFromProtoInternal(input_model, /*clear_names=*/true, | |
error_message); | |
} | |
MPSolverResponseStatus MPSolver::LoadModelFromProtoWithUniqueNamesOrDie( | |
const MPModelProto& input_model, std::string* error_message) { | |
// Force variable and constraint name indexing (which CHECKs name uniqueness). | |
GenerateVariableNameIndex(); | |
GenerateConstraintNameIndex(); | |
return LoadModelFromProtoInternal(input_model, /*clear_names=*/false, | |
error_message); | |
} | |
MPSolverResponseStatus MPSolver::LoadModelFromProtoInternal( | |
const MPModelProto& input_model, bool clear_names, | |
std::string* error_message) { | |
CHECK(error_message != nullptr); | |
const std::string error = FindErrorInMPModelProto(input_model); | |
if (!error.empty()) { | |
*error_message = error; | |
LOG_IF(INFO, OutputIsEnabled()) | |
<< "Invalid model given to LoadModelFromProto(): " << error; | |
if (FLAGS_mpsolver_bypass_model_validation) { | |
LOG_IF(INFO, OutputIsEnabled()) | |
<< "Ignoring the model error(s) because of" | |
<< " --mpsolver_bypass_model_validation."; | |
} else { | |
return error.find("Infeasible") == std::string::npos | |
? MPSOLVER_MODEL_INVALID | |
: MPSOLVER_INFEASIBLE; | |
} | |
} | |
MPObjective* const objective = MutableObjective(); | |
// Passing empty names makes the MPSolver generate unique names. | |
const std::string empty; | |
for (int i = 0; i < input_model.variable_size(); ++i) { | |
const MPVariableProto& var_proto = input_model.variable(i); | |
MPVariable* variable = | |
MakeNumVar(var_proto.lower_bound(), var_proto.upper_bound(), | |
clear_names ? empty : var_proto.name()); | |
variable->SetInteger(var_proto.is_integer()); | |
objective->SetCoefficient(variable, var_proto.objective_coefficient()); | |
} | |
for (int i = 0; i < input_model.constraint_size(); ++i) { | |
const MPConstraintProto& ct_proto = input_model.constraint(i); | |
if (ct_proto.lower_bound() == -infinity() && | |
ct_proto.upper_bound() == infinity()) { | |
continue; | |
} | |
MPConstraint* const ct = | |
MakeRowConstraint(ct_proto.lower_bound(), ct_proto.upper_bound(), | |
clear_names ? empty : ct_proto.name()); | |
ct->set_is_lazy(ct_proto.is_lazy()); | |
for (int j = 0; j < ct_proto.var_index_size(); ++j) { | |
ct->SetCoefficient(variables_[ct_proto.var_index(j)], | |
ct_proto.coefficient(j)); | |
} | |
} | |
objective->SetOptimizationDirection(input_model.maximize()); | |
if (input_model.has_objective_offset()) { | |
objective->SetOffset(input_model.objective_offset()); | |
} | |
// Stores any hints about where to start the solve. | |
solution_hint_.clear(); | |
for (int i = 0; i < input_model.solution_hint().var_index_size(); ++i) { | |
solution_hint_.push_back( | |
std::make_pair(variables_[input_model.solution_hint().var_index(i)], | |
input_model.solution_hint().var_value(i))); | |
} | |
return MPSOLVER_MODEL_IS_VALID; | |
} | |
namespace { | |
MPSolverResponseStatus ResultStatusToMPSolverResponseStatus( | |
MPSolver::ResultStatus status) { | |
switch (status) { | |
case MPSolver::OPTIMAL: | |
return MPSOLVER_OPTIMAL; | |
case MPSolver::FEASIBLE: | |
return MPSOLVER_FEASIBLE; | |
case MPSolver::INFEASIBLE: | |
return MPSOLVER_INFEASIBLE; | |
case MPSolver::UNBOUNDED: | |
return MPSOLVER_UNBOUNDED; | |
case MPSolver::ABNORMAL: | |
return MPSOLVER_ABNORMAL; | |
case MPSolver::MODEL_INVALID: | |
return MPSOLVER_MODEL_INVALID; | |
case MPSolver::NOT_SOLVED: | |
return MPSOLVER_NOT_SOLVED; | |
} | |
return MPSOLVER_UNKNOWN_STATUS; | |
} | |
} // namespace | |
void MPSolver::FillSolutionResponseProto(MPSolutionResponse* response) const { | |
CHECK(response != nullptr); | |
response->Clear(); | |
response->set_status( | |
ResultStatusToMPSolverResponseStatus(interface_->result_status_)); | |
if (interface_->result_status_ == MPSolver::OPTIMAL || | |
interface_->result_status_ == MPSolver::FEASIBLE) { | |
response->set_objective_value(Objective().Value()); | |
for (int i = 0; i < variables_.size(); ++i) { | |
response->add_variable_value(variables_[i]->solution_value()); | |
} | |
if (interface_->IsMIP()) { | |
response->set_best_objective_bound(interface_->best_objective_bound()); | |
} else { | |
// Dual values have no meaning in MIP. | |
for (int j = 0; j < constraints_.size(); ++j) { | |
response->add_dual_value(constraints_[j]->dual_value()); | |
} | |
// Reduced cost have no meaning in MIP. | |
for (int i = 0; i < variables_.size(); ++i) { | |
response->add_reduced_cost(variables_[i]->reduced_cost()); | |
} | |
} | |
} | |
} | |
// static | |
void MPSolver::SolveWithProto(const MPModelRequest& model_request, | |
MPSolutionResponse* response) { | |
CHECK(response != nullptr); | |
const MPModelProto& model = model_request.model(); | |
MPSolver solver(model.name(), static_cast<MPSolver::OptimizationProblemType>( | |
model_request.solver_type())); | |
if (model_request.enable_internal_solver_output()) { | |
solver.EnableOutput(); | |
} | |
std::string error_message; | |
response->set_status(solver.LoadModelFromProto(model, &error_message)); | |
if (response->status() != MPSOLVER_MODEL_IS_VALID) { | |
LOG(WARNING) << "Loading model from protocol buffer failed, load status = " | |
<< ProtoEnumToString<MPSolverResponseStatus>( | |
response->status()) | |
<< " (" << response->status() << "); Error: " << error_message; | |
return; | |
} | |
if (model_request.has_solver_time_limit_seconds()) { | |
double time_limit_ms = model_request.solver_time_limit_seconds() * 1000.0; | |
if (time_limit_ms < | |
static_cast<double>(std::numeric_limits<int64>::max())) { | |
// static_cast<int64> avoids a warning with -Wreal-conversion. This | |
// helps catching bugs with unwanted conversions from double to ints. | |
solver.set_time_limit(static_cast<int64>(time_limit_ms)); | |
} | |
} | |
solver.SetSolverSpecificParametersAsString( | |
model_request.solver_specific_parameters()); | |
solver.Solve(); | |
solver.FillSolutionResponseProto(response); | |
} | |
void MPSolver::ExportModelToProto(MPModelProto* output_model) const { | |
DCHECK(output_model != nullptr); | |
output_model->Clear(); | |
// Name | |
output_model->set_name(Name()); | |
// Variables | |
for (int j = 0; j < variables_.size(); ++j) { | |
const MPVariable* const var = variables_[j]; | |
MPVariableProto* const variable_proto = output_model->add_variable(); | |
// TODO(user): Add option to avoid filling the var name to avoid overly | |
// large protocol buffers. | |
variable_proto->set_name(var->name()); | |
variable_proto->set_lower_bound(var->lb()); | |
variable_proto->set_upper_bound(var->ub()); | |
variable_proto->set_is_integer(var->integer()); | |
if (objective_->GetCoefficient(var) != 0.0) { | |
variable_proto->set_objective_coefficient( | |
objective_->GetCoefficient(var)); | |
} | |
} | |
// Map the variables to their indices. This is needed to output the | |
// variables in the order they were created, which in turn is needed to have | |
// repeatable results with ExportModelAsLpString and ExportModelAsMpsString. | |
// This step is needed as long as the variable indices are given by the | |
// underlying solver at the time of model extraction. | |
// TODO(user): remove this step. | |
std::unordered_map<const MPVariable*, int> var_to_index; | |
for (int j = 0; j < variables_.size(); ++j) { | |
var_to_index[variables_[j]] = j; | |
} | |
// Constraints | |
for (int i = 0; i < constraints_.size(); ++i) { | |
MPConstraint* const constraint = constraints_[i]; | |
MPConstraintProto* const constraint_proto = output_model->add_constraint(); | |
constraint_proto->set_name(constraint->name()); | |
constraint_proto->set_lower_bound(constraint->lb()); | |
constraint_proto->set_upper_bound(constraint->ub()); | |
constraint_proto->set_is_lazy(constraint->is_lazy()); | |
// Vector linear_term will contain pairs (variable index, coeff), that will | |
// be sorted by variable index. | |
std::vector<std::pair<int, double> > linear_term; | |
for (const auto& entry : constraint->coefficients_) { | |
const MPVariable* const var = entry.first; | |
const int var_index = gtl::FindWithDefault(var_to_index, var, -1); | |
DCHECK_NE(-1, var_index); | |
const double coeff = entry.second; | |
linear_term.push_back(std::pair<int, double>(var_index, coeff)); | |
} | |
// The cost of sort is expected to be low as constraints usually have very | |
// few terms. | |
std::sort(linear_term.begin(), linear_term.end()); | |
// Now use linear term. | |
for (const std::pair<int, double> var_and_coeff : linear_term) { | |
constraint_proto->add_var_index(var_and_coeff.first); | |
constraint_proto->add_coefficient(var_and_coeff.second); | |
} | |
} | |
output_model->set_maximize(Objective().maximization()); | |
output_model->set_objective_offset(Objective().offset()); | |
if (!solution_hint_.empty()) { | |
PartialVariableAssignment* const hint = | |
output_model->mutable_solution_hint(); | |
for (const auto& var_value_pair : solution_hint_) { | |
hint->add_var_index(var_value_pair.first->index()); | |
hint->add_var_value(var_value_pair.second); | |
} | |
} | |
} | |
util::Status MPSolver::LoadSolutionFromProto(const MPSolutionResponse& response, | |
double tolerance) { | |
interface_->result_status_ = static_cast<ResultStatus>(response.status()); | |
if (response.status() != MPSOLVER_OPTIMAL && | |
response.status() != MPSOLVER_FEASIBLE) { | |
return util::InvalidArgumentError(absl::StrCat( | |
"Cannot load a solution unless its status is OPTIMAL or FEASIBLE" | |
" (status was: ", | |
ProtoEnumToString<MPSolverResponseStatus>(response.status()), ")")); | |
} | |
// Before touching the variables, verify that the solution looks legit: | |
// each variable of the MPSolver must have its value listed exactly once, and | |
// each listed solution should correspond to a known variable. | |
if (response.variable_value_size() != variables_.size()) { | |
return util::InvalidArgumentError(absl::StrCat( | |
"Trying to load a solution whose number of variables (", | |
response.variable_value_size(), | |
") does not correspond to the Solver's (", variables_.size(), ")")); | |
} | |
interface_->ExtractModel(); | |
if (tolerance != infinity()) { | |
// Look further: verify that the variable values are within the bounds. | |
double largest_error = 0; | |
int num_vars_out_of_bounds = 0; | |
int last_offending_var = -1; | |
for (int i = 0; i < response.variable_value_size(); ++i) { | |
const double var_value = response.variable_value(i); | |
MPVariable* var = variables_[i]; | |
// TODO(user): Use parameter when they become available in this class. | |
const double lb_error = var->lb() - var_value; | |
const double ub_error = var_value - var->ub(); | |
if (lb_error > tolerance || ub_error > tolerance) { | |
++num_vars_out_of_bounds; | |
largest_error = std::max(largest_error, std::max(lb_error, ub_error)); | |
last_offending_var = i; | |
} | |
} | |
if (num_vars_out_of_bounds > 0) { | |
return util::InvalidArgumentError(absl::StrCat( | |
"Loaded a solution whose variables matched the solver's, but ", | |
num_vars_out_of_bounds, " of ", variables_.size(), | |
" variables were out of their bounds, by more than the primal" | |
" tolerance which is: ", | |
tolerance, ". Max error: ", largest_error, ", last offender var is #", | |
last_offending_var, ": '", variables_[last_offending_var]->name(), | |
"'")); | |
} | |
} | |
for (int i = 0; i < response.variable_value_size(); ++i) { | |
variables_[i]->set_solution_value(response.variable_value(i)); | |
} | |
// Set the objective value, if is known. | |
// NOTE(user): We do not verify the objective, even though we could! | |
if (response.has_objective_value()) { | |
interface_->objective_value_ = response.objective_value(); | |
} | |
// Mark the status as SOLUTION_SYNCHRONIZED, so that users may inspect the | |
// solution normally. | |
interface_->sync_status_ = MPSolverInterface::SOLUTION_SYNCHRONIZED; | |
return util::OkStatus(); | |
} | |
void MPSolver::Clear() { | |
MutableObjective()->Clear(); | |
gtl::STLDeleteElements(&variables_); | |
gtl::STLDeleteElements(&constraints_); | |
variables_.clear(); | |
if (variable_name_to_index_) { | |
variable_name_to_index_->clear(); | |
} | |
variable_is_extracted_.clear(); | |
constraints_.clear(); | |
if (constraint_name_to_index_) { | |
constraint_name_to_index_->clear(); | |
} | |
constraint_is_extracted_.clear(); | |
interface_->Reset(); | |
solution_hint_.clear(); | |
} | |
void MPSolver::Reset() { interface_->Reset(); } | |
bool MPSolver::InterruptSolve() { return interface_->InterruptSolve(); } | |
void MPSolver::SetStartingLpBasis( | |
const std::vector<BasisStatus>& variable_statuses, | |
const std::vector<BasisStatus>& constraint_statuses) { | |
interface_->SetStartingLpBasis(variable_statuses, constraint_statuses); | |
} | |
MPVariable* MPSolver::MakeVar(double lb, double ub, bool integer, | |
const std::string& name) { | |
const int var_index = NumVariables(); | |
MPVariable* v = | |
new MPVariable(var_index, lb, ub, integer, name, interface_.get()); | |
if (variable_name_to_index_) { | |
gtl::InsertOrDie(&*variable_name_to_index_, v->name(), var_index); | |
} | |
variables_.push_back(v); | |
variable_is_extracted_.push_back(false); | |
interface_->AddVariable(v); | |
return v; | |
} | |
MPVariable* MPSolver::MakeNumVar(double lb, double ub, | |
const std::string& name) { | |
return MakeVar(lb, ub, false, name); | |
} | |
MPVariable* MPSolver::MakeIntVar(double lb, double ub, | |
const std::string& name) { | |
return MakeVar(lb, ub, true, name); | |
} | |
MPVariable* MPSolver::MakeBoolVar(const std::string& name) { | |
return MakeVar(0.0, 1.0, true, name); | |
} | |
void MPSolver::MakeVarArray(int nb, double lb, double ub, bool integer, | |
const std::string& name, | |
std::vector<MPVariable*>* vars) { | |
DCHECK_GE(nb, 0); | |
if (nb <= 0) return; | |
const int num_digits = NumDigits(nb); | |
for (int i = 0; i < nb; ++i) { | |
if (name.empty()) { | |
vars->push_back(MakeVar(lb, ub, integer, name)); | |
} else { | |
std::string vname = | |
absl::StrFormat("%s%0*d", name.c_str(), num_digits, i); | |
vars->push_back(MakeVar(lb, ub, integer, vname)); | |
} | |
} | |
} | |
void MPSolver::MakeNumVarArray(int nb, double lb, double ub, | |
const std::string& name, | |
std::vector<MPVariable*>* vars) { | |
MakeVarArray(nb, lb, ub, false, name, vars); | |
} | |
void MPSolver::MakeIntVarArray(int nb, double lb, double ub, | |
const std::string& name, | |
std::vector<MPVariable*>* vars) { | |
MakeVarArray(nb, lb, ub, true, name, vars); | |
} | |
void MPSolver::MakeBoolVarArray(int nb, const std::string& name, | |
std::vector<MPVariable*>* vars) { | |
MakeVarArray(nb, 0.0, 1.0, true, name, vars); | |
} | |
MPConstraint* MPSolver::MakeRowConstraint(double lb, double ub) { | |
return MakeRowConstraint(lb, ub, ""); | |
} | |
MPConstraint* MPSolver::MakeRowConstraint() { | |
return MakeRowConstraint(-infinity(), infinity(), ""); | |
} | |
MPConstraint* MPSolver::MakeRowConstraint(double lb, double ub, | |
const std::string& name) { | |
const int constraint_index = NumConstraints(); | |
MPConstraint* const constraint = | |
new MPConstraint(constraint_index, lb, ub, name, interface_.get()); | |
if (constraint_name_to_index_) { | |
gtl::InsertOrDie(&*constraint_name_to_index_, constraint->name(), | |
constraint_index); | |
} | |
constraints_.push_back(constraint); | |
constraint_is_extracted_.push_back(false); | |
interface_->AddRowConstraint(constraint); | |
return constraint; | |
} | |
MPConstraint* MPSolver::MakeRowConstraint(const std::string& name) { | |
return MakeRowConstraint(-infinity(), infinity(), name); | |
} | |
MPConstraint* MPSolver::MakeRowConstraint(const LinearRange& range) { | |
return MakeRowConstraint(range, ""); | |
} | |
MPConstraint* MPSolver::MakeRowConstraint(const LinearRange& range, | |
const std::string& name) { | |
CheckLinearExpr(*this, range.linear_expr()); | |
MPConstraint* constraint = | |
MakeRowConstraint(range.lower_bound(), range.upper_bound(), name); | |
for (const auto& kv : range.linear_expr().terms()) { | |
constraint->SetCoefficient(kv.first, kv.second); | |
} | |
return constraint; | |
} | |
int MPSolver::ComputeMaxConstraintSize(int min_constraint_index, | |
int max_constraint_index) const { | |
int max_constraint_size = 0; | |
DCHECK_GE(min_constraint_index, 0); | |
DCHECK_LE(max_constraint_index, constraints_.size()); | |
for (int i = min_constraint_index; i < max_constraint_index; ++i) { | |
MPConstraint* const ct = constraints_[i]; | |
if (ct->coefficients_.size() > max_constraint_size) { | |
max_constraint_size = ct->coefficients_.size(); | |
} | |
} | |
return max_constraint_size; | |
} | |
bool MPSolver::HasInfeasibleConstraints() const { | |
bool hasInfeasibleConstraints = false; | |
for (int i = 0; i < constraints_.size(); ++i) { | |
if (constraints_[i]->lb() > constraints_[i]->ub()) { | |
LOG(WARNING) << "Constraint " << constraints_[i]->name() << " (" << i | |
<< ") has contradictory bounds:" | |
<< " lower bound = " << constraints_[i]->lb() | |
<< " upper bound = " << constraints_[i]->ub(); | |
hasInfeasibleConstraints = true; | |
} | |
} | |
return hasInfeasibleConstraints; | |
} | |
bool MPSolver::HasIntegerVariables() const { | |
for (const MPVariable* const variable : variables_) { | |
if (variable->integer()) return true; | |
} | |
return false; | |
} | |
MPSolver::ResultStatus MPSolver::Solve() { | |
MPSolverParameters default_param; | |
return Solve(default_param); | |
} | |
MPSolver::ResultStatus MPSolver::Solve(const MPSolverParameters& param) { | |
// Special case for infeasible constraints so that all solvers have | |
// the same behavior. | |
// TODO(user): replace this by model extraction to proto + proto validation | |
// (the proto has very low overhead compared to the wrapper, both in | |
// performance and memory, so it's ok). | |
if (HasInfeasibleConstraints()) { | |
interface_->result_status_ = MPSolver::INFEASIBLE; | |
return interface_->result_status_; | |
} | |
MPSolver::ResultStatus status = interface_->Solve(param); | |
if (FLAGS_verify_solution) { | |
if (status != MPSolver::OPTIMAL && status != MPSolver::FEASIBLE) { | |
VLOG(1) << "--verify_solution enabled, but the solver did not find a" | |
<< " solution: skipping the verification."; | |
} else if (!VerifySolution( | |
param.GetDoubleParam(MPSolverParameters::PRIMAL_TOLERANCE), | |
FLAGS_log_verification_errors)) { | |
status = MPSolver::ABNORMAL; | |
interface_->result_status_ = status; | |
} | |
} | |
DCHECK_EQ(interface_->result_status_, status); | |
return status; | |
} | |
void MPSolver::Write(const std::string& file_name) { | |
interface_->Write(file_name); | |
} | |
namespace { | |
std::string PrettyPrintVar(const MPVariable& var) { | |
const std::string prefix = "Variable '" + var.name() + "': domain = "; | |
if (var.lb() >= MPSolver::infinity() || var.ub() <= -MPSolver::infinity() || | |
var.lb() > var.ub()) { | |
return prefix + "∅"; // Empty set. | |
} | |
// Special case: integer variable with at most two possible values | |
// (and potentially none). | |
if (var.integer() && var.ub() - var.lb() <= 1) { | |
const int64 lb = static_cast<int64>(ceil(var.lb())); | |
const int64 ub = static_cast<int64>(floor(var.ub())); | |
if (lb > ub) { | |
return prefix + "∅"; | |
} else if (lb == ub) { | |
return absl::StrFormat("%s{ %lld }", prefix.c_str(), lb); | |
} else { | |
return absl::StrFormat("%s{ %lld, %lld }", prefix.c_str(), lb, ub); | |
} | |
} | |
// Special case: single (non-infinite) real value. | |
if (var.lb() == var.ub()) { | |
return absl::StrFormat("%s{ %f }", prefix.c_str(), var.lb()); | |
} | |
return prefix + (var.integer() ? "Integer" : "Real") + " in " + | |
(var.lb() <= -MPSolver::infinity() | |
? std::string("]-∞") | |
: absl::StrFormat("[%f", var.lb())) + | |
", " + | |
(var.ub() >= MPSolver::infinity() ? std::string("+∞[") | |
: absl::StrFormat("%f]", var.ub())); | |
} | |
std::string PrettyPrintConstraint(const MPConstraint& constraint) { | |
std::string prefix = "Constraint '" + constraint.name() + "': "; | |
if (constraint.lb() >= MPSolver::infinity() || | |
constraint.ub() <= -MPSolver::infinity() || | |
constraint.lb() > constraint.ub()) { | |
return prefix + "ALWAYS FALSE"; | |
} | |
if (constraint.lb() <= -MPSolver::infinity() && | |
constraint.ub() >= MPSolver::infinity()) { | |
return prefix + "ALWAYS TRUE"; | |
} | |
prefix += "<linear expr>"; | |
// Equality. | |
if (constraint.lb() == constraint.ub()) { | |
return absl::StrFormat("%s = %f", prefix.c_str(), constraint.lb()); | |
} | |
// Inequalities. | |
if (constraint.lb() <= -MPSolver::infinity()) { | |
return absl::StrFormat("%s ≤ %f", prefix.c_str(), constraint.ub()); | |
} | |
if (constraint.ub() >= MPSolver::infinity()) { | |
return absl::StrFormat("%s ≥ %f", prefix.c_str(), constraint.lb()); | |
} | |
return absl::StrFormat("%s ∈ [%f, %f]", prefix.c_str(), constraint.lb(), | |
constraint.ub()); | |
} | |
} // namespace | |
util::Status MPSolver::ClampSolutionWithinBounds() { | |
interface_->ExtractModel(); | |
for (MPVariable* const variable : variables_) { | |
const double value = variable->solution_value(); | |
if (std::isnan(value)) { | |
return util::InvalidArgumentError( | |
absl::StrCat("NaN value for ", PrettyPrintVar(*variable))); | |
} | |
if (value < variable->lb()) { | |
variable->set_solution_value(variable->lb()); | |
} else if (value > variable->ub()) { | |
variable->set_solution_value(variable->ub()); | |
} | |
} | |
interface_->sync_status_ = MPSolverInterface::SOLUTION_SYNCHRONIZED; | |
return util::OkStatus(); | |
} | |
std::vector<double> MPSolver::ComputeConstraintActivities() const { | |
// TODO(user): test this failure case. | |
if (!interface_->CheckSolutionIsSynchronizedAndExists()) return {}; | |
std::vector<double> activities(constraints_.size(), 0.0); | |
for (int i = 0; i < constraints_.size(); ++i) { | |
const MPConstraint& constraint = *constraints_[i]; | |
AccurateSum<double> sum; | |
for (const auto& entry : constraint.coefficients_) { | |
sum.Add(entry.first->solution_value() * entry.second); | |
} | |
activities[i] = sum.Value(); | |
} | |
return activities; | |
} | |
// TODO(user): split. | |
bool MPSolver::VerifySolution(double tolerance, bool log_errors) const { | |
double max_observed_error = 0; | |
if (tolerance < 0) tolerance = infinity(); | |
int num_errors = 0; | |
// Verify variables. | |
for (int i = 0; i < variables_.size(); ++i) { | |
const MPVariable& var = *variables_[i]; | |
const double value = var.solution_value(); | |
// Check for NaN. | |
if (std::isnan(value)) { | |
++num_errors; | |
max_observed_error = infinity(); | |
LOG_IF(ERROR, log_errors) << "NaN value for " << PrettyPrintVar(var); | |
continue; | |
} | |
// Check lower bound. | |
if (var.lb() != -infinity()) { | |
if (value < var.lb() - tolerance) { | |
++num_errors; | |
max_observed_error = std::max(max_observed_error, var.lb() - value); | |
LOG_IF(ERROR, log_errors) | |
<< "Value " << value << " too low for " << PrettyPrintVar(var); | |
} | |
} | |
// Check upper bound. | |
if (var.ub() != infinity()) { | |
if (value > var.ub() + tolerance) { | |
++num_errors; | |
max_observed_error = std::max(max_observed_error, value - var.ub()); | |
LOG_IF(ERROR, log_errors) | |
<< "Value " << value << " too high for " << PrettyPrintVar(var); | |
} | |
} | |
// Check integrality. | |
if (IsMIP() && var.integer()) { | |
if (fabs(value - round(value)) > tolerance) { | |
++num_errors; | |
max_observed_error = | |
std::max(max_observed_error, fabs(value - round(value))); | |
LOG_IF(ERROR, log_errors) | |
<< "Non-integer value " << value << " for " << PrettyPrintVar(var); | |
} | |
} | |
} | |
if (!IsMIP() && HasIntegerVariables()) { | |
LOG_IF(INFO, log_errors) << "Skipped variable integrality check, because " | |
<< "a continuous relaxation of the model was " | |
<< "solved (i.e., the selected solver does not " | |
<< "support integer variables)."; | |
} | |
// Verify constraints. | |
const std::vector<double> activities = ComputeConstraintActivities(); | |
for (int i = 0; i < constraints_.size(); ++i) { | |
const MPConstraint& constraint = *constraints_[i]; | |
const double activity = activities[i]; | |
// Re-compute the activity with a inaccurate summing algorithm. | |
double inaccurate_activity = 0.0; | |
for (const auto& entry : constraint.coefficients_) { | |
inaccurate_activity += entry.first->solution_value() * entry.second; | |
} | |
// Catch NaNs. | |
if (std::isnan(activity) || std::isnan(inaccurate_activity)) { | |
++num_errors; | |
max_observed_error = infinity(); | |
LOG_IF(ERROR, log_errors) | |
<< "NaN value for " << PrettyPrintConstraint(constraint); | |
continue; | |
} | |
// Check bounds. | |
if (constraint.lb() != -infinity()) { | |
if (activity < constraint.lb() - tolerance) { | |
++num_errors; | |
max_observed_error = | |
std::max(max_observed_error, constraint.lb() - activity); | |
LOG_IF(ERROR, log_errors) << "Activity " << activity << " too low for " | |
<< PrettyPrintConstraint(constraint); | |
} else if (inaccurate_activity < constraint.lb() - tolerance) { | |
LOG_IF(WARNING, log_errors) | |
<< "Activity " << activity << ", computed with the (inaccurate)" | |
<< " standard sum of its terms, is too low for " | |
<< PrettyPrintConstraint(constraint); | |
} | |
} | |
if (constraint.ub() != infinity()) { | |
if (activity > constraint.ub() + tolerance) { | |
++num_errors; | |
max_observed_error = | |
std::max(max_observed_error, activity - constraint.ub()); | |
LOG_IF(ERROR, log_errors) << "Activity " << activity << " too high for " | |
<< PrettyPrintConstraint(constraint); | |
} else if (inaccurate_activity > constraint.ub() + tolerance) { | |
LOG_IF(WARNING, log_errors) | |
<< "Activity " << activity << ", computed with the (inaccurate)" | |
<< " standard sum of its terms, is too high for " | |
<< PrettyPrintConstraint(constraint); | |
} | |
} | |
} | |
// Verify that the objective value wasn't reported incorrectly. | |
const MPObjective& objective = Objective(); | |
AccurateSum<double> objective_sum; | |
objective_sum.Add(objective.offset()); | |
double inaccurate_objective_value = objective.offset(); | |
for (const auto& entry : objective.coefficients_) { | |
const double term = entry.first->solution_value() * entry.second; | |
objective_sum.Add(term); | |
inaccurate_objective_value += term; | |
} | |
const double actual_objective_value = objective_sum.Value(); | |
if (!AreWithinAbsoluteOrRelativeTolerances( | |
objective.Value(), actual_objective_value, tolerance, tolerance)) { | |
++num_errors; | |
max_observed_error = std::max( | |
max_observed_error, fabs(actual_objective_value - objective.Value())); | |
LOG_IF(ERROR, log_errors) | |
<< "Objective value " << objective.Value() << " isn't accurate" | |
<< ", it should be " << actual_objective_value | |
<< " (delta=" << actual_objective_value - objective.Value() << ")."; | |
} else if (!AreWithinAbsoluteOrRelativeTolerances(objective.Value(), | |
inaccurate_objective_value, | |
tolerance, tolerance)) { | |
LOG_IF(WARNING, log_errors) | |
<< "Objective value " << objective.Value() << " doesn't correspond" | |
<< " to the value computed with the standard (and therefore inaccurate)" | |
<< " sum of its terms."; | |
} | |
if (num_errors > 0) { | |
LOG_IF(ERROR, log_errors) | |
<< "There were " << num_errors << " errors above the tolerance (" | |
<< tolerance << "), the largest was " << max_observed_error; | |
return false; | |
} | |
return true; | |
} | |
bool MPSolver::OutputIsEnabled() const { return !interface_->quiet(); } | |
void MPSolver::EnableOutput() { interface_->set_quiet(false); } | |
void MPSolver::SuppressOutput() { interface_->set_quiet(true); } | |
int64 MPSolver::iterations() const { return interface_->iterations(); } | |
int64 MPSolver::nodes() const { return interface_->nodes(); } | |
double MPSolver::ComputeExactConditionNumber() const { | |
return interface_->ComputeExactConditionNumber(); | |
} | |
bool MPSolver::OwnsVariable(const MPVariable* var) const { | |
if (var == nullptr) return false; | |
if (var->index() >= 0 && var->index() < variables_.size()) { | |
// Then, verify that the variable with this index has the same address. | |
return variables_[var->index()] == var; | |
} | |
return false; | |
} | |
bool MPSolver::ExportModelAsLpFormat(bool obfuscate, | |
std::string* model_str) const { | |
MPModelProto proto; | |
ExportModelToProto(&proto); | |
MPModelProtoExporter exporter(proto); | |
return exporter.ExportModelAsLpFormat(obfuscate, model_str); | |
} | |
bool MPSolver::ExportModelAsMpsFormat(bool fixed_format, bool obfuscate, | |
std::string* model_str) const { | |
MPModelProto proto; | |
ExportModelToProto(&proto); | |
MPModelProtoExporter exporter(proto); | |
return exporter.ExportModelAsMpsFormat(fixed_format, obfuscate, model_str); | |
} | |
void MPSolver::SetHint(std::vector<std::pair<MPVariable*, double> > hint) { | |
for (const auto& var_value_pair : hint) { | |
CHECK(OwnsVariable(var_value_pair.first)) | |
<< "hint variable does not belong to this solver"; | |
} | |
solution_hint_ = std::move(hint); | |
} | |
void MPSolver::GenerateVariableNameIndex() const { | |
if (variable_name_to_index_) return; | |
variable_name_to_index_ = std::unordered_map<std::string, int>(); | |
for (const MPVariable* const var : variables_) { | |
gtl::InsertOrDie(&*variable_name_to_index_, var->name(), var->index()); | |
} | |
} | |
void MPSolver::GenerateConstraintNameIndex() const { | |
if (constraint_name_to_index_) return; | |
constraint_name_to_index_ = std::unordered_map<std::string, int>(); | |
for (const MPConstraint* const cst : constraints_) { | |
gtl::InsertOrDie(&*constraint_name_to_index_, cst->name(), cst->index()); | |
} | |
} | |
bool MPSolver::NextSolution() { return interface_->NextSolution(); } | |
// ---------- MPSolverInterface ---------- | |
const int MPSolverInterface::kDummyVariableIndex = 0; | |
MPSolverInterface::MPSolverInterface(MPSolver* const solver) | |
: solver_(solver), | |
sync_status_(MODEL_SYNCHRONIZED), | |
result_status_(MPSolver::NOT_SOLVED), | |
maximize_(false), | |
last_constraint_index_(0), | |
last_variable_index_(0), | |
objective_value_(0.0), | |
quiet_(true) {} | |
MPSolverInterface::~MPSolverInterface() {} | |
void MPSolverInterface::Write(const std::string& filename) { | |
LOG(WARNING) << "Writing model not implemented in this solver interface."; | |
} | |
void MPSolverInterface::ExtractModel() { | |
switch (sync_status_) { | |
case MUST_RELOAD: { | |
ExtractNewVariables(); | |
ExtractNewConstraints(); | |
ExtractObjective(); | |
last_constraint_index_ = solver_->constraints_.size(); | |
last_variable_index_ = solver_->variables_.size(); | |
sync_status_ = MODEL_SYNCHRONIZED; | |
break; | |
} | |
case MODEL_SYNCHRONIZED: { | |
// Everything has already been extracted. | |
DCHECK_EQ(last_constraint_index_, solver_->constraints_.size()); | |
DCHECK_EQ(last_variable_index_, solver_->variables_.size()); | |
break; | |
} | |
case SOLUTION_SYNCHRONIZED: { | |
// Nothing has changed since last solve. | |
DCHECK_EQ(last_constraint_index_, solver_->constraints_.size()); | |
DCHECK_EQ(last_variable_index_, solver_->variables_.size()); | |
break; | |
} | |
} | |
} | |
// TODO(user): remove this method. | |
void MPSolverInterface::ResetExtractionInformation() { | |
sync_status_ = MUST_RELOAD; | |
last_constraint_index_ = 0; | |
last_variable_index_ = 0; | |
solver_->variable_is_extracted_.assign(solver_->variables_.size(), false); | |
solver_->constraint_is_extracted_.assign(solver_->constraints_.size(), false); | |
} | |
bool MPSolverInterface::CheckSolutionIsSynchronized() const { | |
if (sync_status_ != SOLUTION_SYNCHRONIZED) { | |
LOG(DFATAL) | |
<< "The model has been changed since the solution was last computed." | |
<< " MPSolverInterface::sync_status_ = " << sync_status_; | |
return false; | |
} | |
return true; | |
} | |
// Default version that can be overwritten by a solver-specific | |
// version to accommodate for the quirks of each solver. | |
bool MPSolverInterface::CheckSolutionExists() const { | |
if (result_status_ != MPSolver::OPTIMAL && | |
result_status_ != MPSolver::FEASIBLE) { | |
LOG(DFATAL) << "No solution exists. MPSolverInterface::result_status_ = " | |
<< result_status_; | |
return false; | |
} | |
return true; | |
} | |
// Default version that can be overwritten by a solver-specific | |
// version to accommodate for the quirks of each solver. | |
bool MPSolverInterface::CheckBestObjectiveBoundExists() const { | |
if (result_status_ != MPSolver::OPTIMAL && | |
result_status_ != MPSolver::FEASIBLE) { | |
LOG(DFATAL) << "No information is available for the best objective bound." | |
<< " MPSolverInterface::result_status_ = " << result_status_; | |
return false; | |
} | |
return true; | |
} | |
double MPSolverInterface::trivial_worst_objective_bound() const { | |
return maximize_ ? -std::numeric_limits<double>::infinity() | |
: std::numeric_limits<double>::infinity(); | |
} | |
double MPSolverInterface::objective_value() const { | |
if (!CheckSolutionIsSynchronizedAndExists()) return 0; | |
return objective_value_; | |
} | |
void MPSolverInterface::InvalidateSolutionSynchronization() { | |
if (sync_status_ == SOLUTION_SYNCHRONIZED) { | |
sync_status_ = MODEL_SYNCHRONIZED; | |
} | |
} | |
double MPSolverInterface::ComputeExactConditionNumber() const { | |
// Override this method in interfaces that actually support it. | |
LOG(DFATAL) << "ComputeExactConditionNumber not implemented for " | |
<< ProtoEnumToString<MPModelRequest::SolverType>( | |
static_cast<MPModelRequest::SolverType>( | |
solver_->ProblemType())); | |
return 0.0; | |
} | |
void MPSolverInterface::SetCommonParameters(const MPSolverParameters& param) { | |
// TODO(user): Overhaul the code that sets parameters to enable changing | |
// GLOP parameters without issuing warnings. | |
// By default, we let GLOP keep its own default tolerance, much more accurate | |
// than for the rest of the solvers. | |
// | |
#if defined(USE_GLOP) | |
if (solver_->ProblemType() != MPSolver::GLOP_LINEAR_PROGRAMMING) { | |
#endif | |
SetPrimalTolerance( | |
param.GetDoubleParam(MPSolverParameters::PRIMAL_TOLERANCE)); | |
SetDualTolerance(param.GetDoubleParam(MPSolverParameters::DUAL_TOLERANCE)); | |
#if defined(USE_GLOP) | |
} | |
#endif | |
SetPresolveMode(param.GetIntegerParam(MPSolverParameters::PRESOLVE)); | |
// TODO(user): In the future, we could distinguish between the | |
// algorithm to solve the root LP and the algorithm to solve node | |
// LPs. Not sure if underlying solvers support it. | |
int value = param.GetIntegerParam(MPSolverParameters::LP_ALGORITHM); | |
if (value != MPSolverParameters::kDefaultIntegerParamValue) { | |
SetLpAlgorithm(value); | |
} | |
} | |
void MPSolverInterface::SetMIPParameters(const MPSolverParameters& param) { | |
#if defined(USE_GLOP) | |
if (solver_->ProblemType() != MPSolver::GLOP_LINEAR_PROGRAMMING) { | |
#endif | |
SetRelativeMipGap( | |
param.GetDoubleParam(MPSolverParameters::RELATIVE_MIP_GAP)); | |
#if defined(USE_GLOP) | |
} | |
#endif | |
} | |
void MPSolverInterface::SetUnsupportedDoubleParam( | |
MPSolverParameters::DoubleParam param) const { | |
LOG(WARNING) << "Trying to set an unsupported parameter: " << param << "."; | |
} | |
void MPSolverInterface::SetUnsupportedIntegerParam( | |
MPSolverParameters::IntegerParam param) const { | |
LOG(WARNING) << "Trying to set an unsupported parameter: " << param << "."; | |
} | |
void MPSolverInterface::SetDoubleParamToUnsupportedValue( | |
MPSolverParameters::DoubleParam param, double value) const { | |
LOG(WARNING) << "Trying to set a supported parameter: " << param | |
<< " to an unsupported value: " << value; | |
} | |
void MPSolverInterface::SetIntegerParamToUnsupportedValue( | |
MPSolverParameters::IntegerParam param, int value) const { | |
LOG(WARNING) << "Trying to set a supported parameter: " << param | |
<< " to an unsupported value: " << value; | |
} | |
bool MPSolverInterface::SetSolverSpecificParametersAsString( | |
const std::string& parameters) { | |
// Note(user): this method needs to return a success/failure boolean | |
// immediately, so we also perform the actual parameter parsing right away. | |
// Some implementations will keep them forever and won't need to re-parse | |
// them; some (eg. SCIP, Gurobi) need to re-parse the parameters every time | |
// they do Solve(). We just store the parameters std::string anyway. | |
// | |
// Note(user): This is not implemented on Android because there is no | |
// temporary directory to write files to without a pointer to the Java | |
// environment. | |
if (parameters.empty()) return true; | |
std::string extension = ValidFileExtensionForParameterFile(); | |
#if defined(__linux) | |
int32 tid = static_cast<int32>(pthread_self()); | |
#else // defined(__linux__) | |
int32 tid = 123; | |
#endif // defined(__linux__) | |
#if !defined(_MSC_VER) | |
int32 pid = static_cast<int32>(getpid()); | |
#else // _MSC_VER | |
int32 pid = 456; | |
#endif // _MSC_VER | |
int64 now = absl::GetCurrentTimeNanos(); | |
std::string filename = | |
absl::StrFormat("/tmp/parameters-tempfile-%x-%d-%llx%s", tid, pid, now, | |
extension.c_str()); | |
bool no_error_so_far = true; | |
if (no_error_so_far) { | |
no_error_so_far = FileSetContents(filename, parameters).ok(); | |
} | |
if (no_error_so_far) { | |
no_error_so_far = ReadParameterFile(filename); | |
// We need to clean up the file even if ReadParameterFile() returned | |
// false. In production we can continue even if the deletion failed. | |
if (!DeleteFile(filename).ok()) { | |
LOG(DFATAL) << "Couldn't delete temporary parameters file: " << filename; | |
} | |
} | |
if (!no_error_so_far) { | |
LOG(WARNING) << "Error in SetSolverSpecificParametersAsString() " | |
<< "for solver type: " | |
<< ProtoEnumToString<MPModelRequest::SolverType>( | |
static_cast<MPModelRequest::SolverType>( | |
solver_->ProblemType())); | |
} | |
return no_error_so_far; | |
} | |
bool MPSolverInterface::ReadParameterFile(const std::string& filename) { | |
LOG(WARNING) << "ReadParameterFile() not supported by this solver."; | |
return false; | |
} | |
std::string MPSolverInterface::ValidFileExtensionForParameterFile() const { | |
return ".tmp"; | |
} | |
// ---------- MPSolverParameters ---------- | |
const double MPSolverParameters::kDefaultRelativeMipGap = 1e-4; | |
// For the primal and dual tolerances, choose the same default as CLP and GLPK. | |
const double MPSolverParameters::kDefaultPrimalTolerance = | |
operations_research::kDefaultPrimalTolerance; | |
const double MPSolverParameters::kDefaultDualTolerance = 1e-7; | |
const MPSolverParameters::PresolveValues MPSolverParameters::kDefaultPresolve = | |
MPSolverParameters::PRESOLVE_ON; | |
const MPSolverParameters::IncrementalityValues | |
MPSolverParameters::kDefaultIncrementality = | |
MPSolverParameters::INCREMENTALITY_ON; | |
const double MPSolverParameters::kDefaultDoubleParamValue = -1.0; | |
const int MPSolverParameters::kDefaultIntegerParamValue = -1; | |
const double MPSolverParameters::kUnknownDoubleParamValue = -2.0; | |
const int MPSolverParameters::kUnknownIntegerParamValue = -2; | |
// The constructor sets all parameters to their default value. | |
MPSolverParameters::MPSolverParameters() | |
: relative_mip_gap_value_(kDefaultRelativeMipGap), | |
primal_tolerance_value_(kDefaultPrimalTolerance), | |
dual_tolerance_value_(kDefaultDualTolerance), | |
presolve_value_(kDefaultPresolve), | |
scaling_value_(kDefaultIntegerParamValue), | |
lp_algorithm_value_(kDefaultIntegerParamValue), | |
incrementality_value_(kDefaultIncrementality), | |
lp_algorithm_is_default_(true) {} | |
void MPSolverParameters::SetDoubleParam(MPSolverParameters::DoubleParam param, | |
double value) { | |
switch (param) { | |
case RELATIVE_MIP_GAP: { | |
relative_mip_gap_value_ = value; | |
break; | |
} | |
case PRIMAL_TOLERANCE: { | |
primal_tolerance_value_ = value; | |
break; | |
} | |
case DUAL_TOLERANCE: { | |
dual_tolerance_value_ = value; | |
break; | |
} | |
default: { | |
LOG(ERROR) << "Trying to set an unknown parameter: " << param << "."; | |
} | |
} | |
} | |
void MPSolverParameters::SetIntegerParam(MPSolverParameters::IntegerParam param, | |
int value) { | |
switch (param) { | |
case PRESOLVE: { | |
if (value != PRESOLVE_OFF && value != PRESOLVE_ON) { | |
LOG(ERROR) << "Trying to set a supported parameter: " << param | |
<< " to an unknown value: " << value; | |
} | |
presolve_value_ = value; | |
break; | |
} | |
case SCALING: { | |
if (value != SCALING_OFF && value != SCALING_ON) { | |
LOG(ERROR) << "Trying to set a supported parameter: " << param | |
<< " to an unknown value: " << value; | |
} | |
scaling_value_ = value; | |
break; | |
} | |
case LP_ALGORITHM: { | |
if (value != DUAL && value != PRIMAL && value != BARRIER) { | |
LOG(ERROR) << "Trying to set a supported parameter: " << param | |
<< " to an unknown value: " << value; | |
} | |
lp_algorithm_value_ = value; | |
lp_algorithm_is_default_ = false; | |
break; | |
} | |
case INCREMENTALITY: { | |
if (value != INCREMENTALITY_OFF && value != INCREMENTALITY_ON) { | |
LOG(ERROR) << "Trying to set a supported parameter: " << param | |
<< " to an unknown value: " << value; | |
} | |
incrementality_value_ = value; | |
break; | |
} | |
default: { | |
LOG(ERROR) << "Trying to set an unknown parameter: " << param << "."; | |
} | |
} | |
} | |
void MPSolverParameters::ResetDoubleParam( | |
MPSolverParameters::DoubleParam param) { | |
switch (param) { | |
case RELATIVE_MIP_GAP: { | |
relative_mip_gap_value_ = kDefaultRelativeMipGap; | |
break; | |
} | |
case PRIMAL_TOLERANCE: { | |
primal_tolerance_value_ = kDefaultPrimalTolerance; | |
break; | |
} | |
case DUAL_TOLERANCE: { | |
dual_tolerance_value_ = kDefaultDualTolerance; | |
break; | |
} | |
default: { | |
LOG(ERROR) << "Trying to reset an unknown parameter: " << param << "."; | |
} | |
} | |
} | |
void MPSolverParameters::ResetIntegerParam( | |
MPSolverParameters::IntegerParam param) { | |
switch (param) { | |
case PRESOLVE: { | |
presolve_value_ = kDefaultPresolve; | |
break; | |
} | |
case SCALING: { | |
scaling_value_ = kDefaultIntegerParamValue; | |
break; | |
} | |
case LP_ALGORITHM: { | |
lp_algorithm_is_default_ = true; | |
break; | |
} | |
case INCREMENTALITY: { | |
incrementality_value_ = kDefaultIncrementality; | |
break; | |
} | |
default: { | |
LOG(ERROR) << "Trying to reset an unknown parameter: " << param << "."; | |
} | |
} | |
} | |
void MPSolverParameters::Reset() { | |
ResetDoubleParam(RELATIVE_MIP_GAP); | |
ResetDoubleParam(PRIMAL_TOLERANCE); | |
ResetDoubleParam(DUAL_TOLERANCE); | |
ResetIntegerParam(PRESOLVE); | |
ResetIntegerParam(SCALING); | |
ResetIntegerParam(LP_ALGORITHM); | |
ResetIntegerParam(INCREMENTALITY); | |
} | |
double MPSolverParameters::GetDoubleParam( | |
MPSolverParameters::DoubleParam param) const { | |
switch (param) { | |
case RELATIVE_MIP_GAP: { | |
return relative_mip_gap_value_; | |
} | |
case PRIMAL_TOLERANCE: { | |
return primal_tolerance_value_; | |
} | |
case DUAL_TOLERANCE: { | |
return dual_tolerance_value_; | |
} | |
default: { | |
LOG(ERROR) << "Trying to get an unknown parameter: " << param << "."; | |
return kUnknownDoubleParamValue; | |
} | |
} | |
} | |
int MPSolverParameters::GetIntegerParam( | |
MPSolverParameters::IntegerParam param) const { | |
switch (param) { | |
case PRESOLVE: { | |
return presolve_value_; | |
} | |
case LP_ALGORITHM: { | |
if (lp_algorithm_is_default_) return kDefaultIntegerParamValue; | |
return lp_algorithm_value_; | |
} | |
case INCREMENTALITY: { | |
return incrementality_value_; | |
} | |
case SCALING: { | |
return scaling_value_; | |
} | |
default: { | |
LOG(ERROR) << "Trying to get an unknown parameter: " << param << "."; | |
return kUnknownIntegerParamValue; | |
} | |
} | |
} | |
} // namespace operations_research |