Permalink
Cannot retrieve contributors at this time
1735 lines (1577 sloc)
60.6 KB
| // 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 |