// Copyright 2010-2011 Google // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include #include "base/hash.h" #include #include #include #include #include "base/commandlineflags.h" #include "base/integral_types.h" #include "base/logging.h" #include "base/scoped_ptr.h" #include "base/stringprintf.h" #include "base/timer.h" #include "base/concise_iterator.h" #include "base/hash.h" #include "linear_solver/linear_solver.h" #if defined(USE_GLPK) extern "C" { #include "glpk.h" } DECLARE_double(solver_timeout_in_seconds); DECLARE_string(solver_write_model); namespace operations_research { // Class to store information gathered in the callback class GLPKInformation { public: explicit GLPKInformation(bool maximize): num_all_nodes_(0) { ResetBestObjectiveBound(maximize); } void Reset(bool maximize) { num_all_nodes_ = 0; ResetBestObjectiveBound(maximize); } void ResetBestObjectiveBound(bool maximize) { if (maximize) { best_objective_bound_ = std::numeric_limits::infinity(); } else { best_objective_bound_ = -std::numeric_limits::infinity(); } } int num_all_nodes_; double best_objective_bound_; }; // Function to be called in the GLPK callback void GLPKGatherInformationCallback(glp_tree* tree, void* info) { CHECK_NOTNULL(tree); CHECK_NOTNULL(info); GLPKInformation* glpk_info = reinterpret_cast(info); switch (glp_ios_reason(tree)) { // The best bound and the number of nodes change only when GLPK // branches, generates cuts or finds an integer solution. case GLP_ISELECT: case GLP_IROWGEN: case GLP_IBINGO: { // Get total number of nodes glp_ios_tree_size(tree, NULL, NULL, &glpk_info->num_all_nodes_); // Get best bound int node_id = glp_ios_best_node(tree); if (node_id > 0) { glpk_info->best_objective_bound_ = glp_ios_node_bound(tree, node_id); } break; } default: break; } } // ----- GLPK Solver ----- class GLPKInterface : public MPSolverInterface { public: // Constructor that takes a name for the underlying glpk solver. GLPKInterface(MPSolver* const solver, bool mip); ~GLPKInterface(); // Sets the optimization direction (min/max). virtual void SetOptimizationDirection(bool maximize); // ----- Solve ----- // Solve the problem using the parameter values specified. virtual MPSolver::ResultStatus Solve(const MPSolverParameters& param); // ----- Model modifications and extraction ----- // Resets extracted model virtual void Reset(); // Modify bounds. virtual void SetVariableBounds(int var_index, double lb, double ub); virtual void SetVariableInteger(int var_index, bool integer); virtual void SetConstraintBounds(int row_index, double lb, double ub); // Add Constraint incrementally. void AddRowConstraint(MPConstraint* const ct); // Add variable incrementally. void AddVariable(MPVariable* const var); // Change a coefficient in a constraint. virtual void SetCoefficient(MPConstraint* const constraint, MPVariable* const variable, double new_value, double old_value); // Clear a constraint from all its terms. virtual void ClearConstraint(MPConstraint* const constraint); // Change a coefficient in the linear objective virtual void SetObjectiveCoefficient(MPVariable* const variable, double coefficient); // Change the constant term in the linear objective. virtual void SetObjectiveOffset(double value); // Clear the objective from all its terms. virtual void ClearObjective(); // ------ Query statistics on the solution and the solve ------ // Number of simplex iterations virtual int64 iterations() const; // Number of branch-and-bound nodes. Only available for discrete problems. virtual int64 nodes() const; // Best objective bound. Only available for discrete problems. virtual double best_objective_bound() const; // Checks whether a feasible solution exists. virtual void CheckSolutionExists() const; // Checks whether information on the best objective bound exists. virtual void CheckBestObjectiveBoundExists() const; // ----- Misc ----- // Write model virtual void WriteModel(const string& filename); // Query problem type. virtual bool IsContinuous() const { return IsLP(); } virtual bool IsLP() const { return !mip_; } virtual bool IsMIP() const { return mip_; } virtual void ExtractNewVariables(); virtual void ExtractNewConstraints(); virtual void ExtractObjective(); virtual string SolverVersion() const { return StringPrintf("GLPK %s", glp_version()); } private: // Configure the solver's parameters. void ConfigureGLPKParameters(const MPSolverParameters& param); // Set all parameters in the underlying solver. virtual void SetParameters(const MPSolverParameters& param); // Set each parameter in the underlying solver. virtual void SetRelativeMipGap(double value); virtual void SetPresolveMode(int value); virtual void SetLpAlgorithm(int value); void ExtractOldConstraints(); void ExtractOneConstraint(MPConstraint* const constraint, int* const indices, double* const coefs); glp_prob* lp_; bool mip_; // Parameters glp_smcp lp_param_; glp_iocp mip_param_; // For the callback scoped_ptr mip_callback_info_; }; // Creates a LP/MIP instance with the specified name and minimization objective. GLPKInterface::GLPKInterface(MPSolver* const solver, bool mip) : MPSolverInterface(solver), lp_(NULL), mip_(mip), mip_callback_info_(NULL) { lp_ = glp_create_prob(); glp_set_prob_name(lp_, solver_->name_.c_str()); glp_set_obj_dir(lp_, GLP_MIN); mip_callback_info_.reset(new GLPKInformation(maximize_)); } // Frees the LP memory allocations. GLPKInterface::~GLPKInterface() { CHECK_NOTNULL(lp_); glp_delete_prob(lp_); lp_ = NULL; } void GLPKInterface::Reset() { CHECK_NOTNULL(lp_); glp_delete_prob(lp_); lp_ = glp_create_prob(); glp_set_prob_name(lp_, solver_->name_.c_str()); glp_set_obj_dir(lp_, maximize_ ? GLP_MAX : GLP_MIN); ResetExtractionInformation(); } void GLPKInterface::WriteModel(const string& filename) { if (solver_->IsLPFormat(filename)) { glp_write_lp(lp_, NULL, filename.c_str()); } else { glp_write_mps(lp_, GLP_MPS_DECK, NULL, filename.c_str()); } } // ------ Model modifications and extraction ----- // Not cached void GLPKInterface::SetOptimizationDirection(bool maximize) { InvalidateSolutionSynchronization(); glp_set_obj_dir(lp_, maximize ? GLP_MAX : GLP_MIN); } void GLPKInterface::SetVariableBounds(int var_index, double lb, double ub) { InvalidateSolutionSynchronization(); if (var_index != kNoIndex) { // Not cached if the variable has been extracted. DCHECK(lp_ != NULL); const double infinity = solver_->infinity(); if (lb != -infinity) { if (ub != infinity) { if (lb == ub) { glp_set_col_bnds(lp_, var_index, GLP_FX, lb, ub); } else { glp_set_col_bnds(lp_, var_index, GLP_DB, lb, ub); } } else { glp_set_col_bnds(lp_, var_index, GLP_LO, lb, 0.0); } } else if (ub != infinity) { glp_set_col_bnds(lp_, var_index, GLP_UP, 0.0, ub); } else { glp_set_col_bnds(lp_, var_index, GLP_FR, 0.0, 0.0); } } else { sync_status_ = MUST_RELOAD; } } void GLPKInterface::SetVariableInteger(int var_index, bool integer) { InvalidateSolutionSynchronization(); if (mip_) { if (var_index != kNoIndex) { // Not cached if the variable has been extracted. glp_set_col_kind(lp_, var_index, integer ? GLP_IV : GLP_CV); } else { sync_status_ = MUST_RELOAD; } } } void GLPKInterface::SetConstraintBounds(int index, double lb, double ub) { InvalidateSolutionSynchronization(); if (index != kNoIndex) { // Not cached if the row has been extracted DCHECK(lp_ != NULL); const double infinity = solver_->infinity(); if (lb != -infinity) { if (ub != infinity) { if (lb == ub) { glp_set_row_bnds(lp_, index, GLP_FX, lb, ub); } else { glp_set_row_bnds(lp_, index, GLP_DB, lb, ub); } } else { glp_set_row_bnds(lp_, index, GLP_LO, lb, 0.0); } } else if (ub != infinity) { glp_set_row_bnds(lp_, index, GLP_UP, 0.0, ub); } else { glp_set_row_bnds(lp_, index, GLP_FR, 0.0, 0.0); } } else { sync_status_ = MUST_RELOAD; } } void GLPKInterface::SetCoefficient(MPConstraint* const constraint, MPVariable* const variable, double new_value, double old_value) { InvalidateSolutionSynchronization(); // GLPK does not allow to modify one coefficient at a time, so we // extract the whole constraint again, if it has been extracted // already and if it does not contain new variables. Otherwise, we // cache the modification. if (constraint->index() != kNoIndex && (sync_status_ == MODEL_SYNCHRONIZED || !constraint->ContainsNewVariables())) { const int size = constraint->coefficients_.size(); scoped_array indices(new int[size + 1]); scoped_array coefs(new double[size + 1]); ExtractOneConstraint(constraint, indices.get(), coefs.get()); } } // Not cached void GLPKInterface::ClearConstraint(MPConstraint* const constraint) { InvalidateSolutionSynchronization(); const int constraint_index = constraint->index(); // Constraint may have not been extracted yet. if (constraint_index != kNoIndex) { glp_set_mat_row(lp_, constraint_index, 0, NULL, NULL); } } // Cached void GLPKInterface::SetObjectiveCoefficient(MPVariable* const variable, double coefficient) { sync_status_ = MUST_RELOAD; } // Cached void GLPKInterface::SetObjectiveOffset(double value) { sync_status_ = MUST_RELOAD; } // Clear objective of all its terms (linear) void GLPKInterface::ClearObjective() { InvalidateSolutionSynchronization(); for (ConstIter > it(solver_->linear_objective_.coefficients_); !it.at_end(); ++it) { const int var_index = it->first->index(); // Variable may have not been extracted yet. if (var_index == kNoIndex) { DCHECK_NE(MODEL_SYNCHRONIZED, sync_status_); } else { glp_set_obj_coef(lp_, var_index, 0.0); } } // Constant term. glp_set_obj_coef(lp_, 0, 0.0); } void GLPKInterface::AddRowConstraint(MPConstraint* const ct) { sync_status_ = MUST_RELOAD; } void GLPKInterface::AddVariable(MPVariable* const var) { sync_status_ = MUST_RELOAD; } // Define new variables and add them to existing constraints. void GLPKInterface::ExtractNewVariables() { int total_num_vars = solver_->variables_.size(); if (total_num_vars > last_variable_index_) { glp_add_cols(lp_, total_num_vars - last_variable_index_); for (int j = last_variable_index_; j < solver_->variables_.size(); ++j) { MPVariable* const var = solver_->variables_[j]; // GLPK convention is to start indexing at 1. const int var_index = j + 1; var->set_index(var_index); if (!var->name().empty()) { glp_set_col_name(lp_, var_index, var->name().c_str()); } SetVariableBounds(var->index(), var->lb(), var->ub()); SetVariableInteger(var->index(), var->integer()); // The true objective coefficient will be set later in ExtractObjective. double tmp_obj_coef = 0.0; glp_set_obj_coef(lp_, var->index(), tmp_obj_coef); } // Add new variables to the existing constraints. ExtractOldConstraints(); } } // Extract again existing constraints if they contain new variables. void GLPKInterface::ExtractOldConstraints() { int max_constraint_size = solver_->ComputeMaxConstraintSize( 0, last_constraint_index_); // The first entry in the following arrays is dummy, to be // consistent with glpk API. scoped_array indices(new int[max_constraint_size + 1]); scoped_array coefs(new double[max_constraint_size + 1]); for (int i = 0; i < last_constraint_index_; ++i) { MPConstraint* const ct = solver_->constraints_[i]; DCHECK_NE(kNoIndex, ct->index()); const int size = ct->coefficients_.size(); if (size == 0) { continue; } // Update the constraint's coefficients if it contains new variables. if (ct->ContainsNewVariables()) { ExtractOneConstraint(ct, indices.get(), coefs.get()); } } } // Extract one constraint. Arrays indices and coefs must be // preallocated to have enough space to contain the constraint's // coefficients. void GLPKInterface::ExtractOneConstraint(MPConstraint* const constraint, int* const indices, double* const coefs) { // GLPK convention is to start indexing at 1. int k = 1; for (ConstIter > it(constraint->coefficients_); !it.at_end(); ++it) { const int var_index = it->first->index(); DCHECK_NE(kNoIndex, var_index); indices[k] = var_index; coefs[k] = it->second; ++k; } glp_set_mat_row(lp_, constraint->index(), k-1, indices, coefs); } // Define new constraints on old and new variables. void GLPKInterface::ExtractNewConstraints() { int total_num_rows = solver_->constraints_.size(); if (last_constraint_index_ < total_num_rows) { // Define new constraints glp_add_rows(lp_, total_num_rows - last_constraint_index_); int num_coefs = 0; for (int i = last_constraint_index_; i < total_num_rows; ++i) { // GLPK convention is to start indexing at 1. const int constraint_index = i + 1; MPConstraint* ct = solver_->constraints_[i]; ct->set_index(constraint_index); if (ct->name().empty()) { glp_set_row_name(lp_, constraint_index, StringPrintf("ct_%i", i).c_str()); } else { glp_set_row_name(lp_, constraint_index, ct->name().c_str()); } // All constraints are set to be of the type <= limit_ . SetConstraintBounds(constraint_index, ct->lb(), ct->ub()); num_coefs += ct->coefficients_.size(); } // Fill new constraints with coefficients if (last_variable_index_ == 0 && last_constraint_index_ == 0) { // Faster extraction when nothing has been extracted yet: build // and load whole matrix at once instead of constructing rows // separately. // The first entry in the following arrays is dummy, to be // consistent with glpk API. scoped_array variable_indices(new int[num_coefs + 1]); scoped_array constraint_indices(new int[num_coefs + 1]); scoped_array coefs(new double[num_coefs + 1]); int k = 1; for (int i = 0; i < solver_->constraints_.size(); ++i) { MPConstraint* ct = solver_->constraints_[i]; for (hash_map::const_iterator it = ct->coefficients_.begin(); it != ct->coefficients_.end(); ++it) { DCHECK_NE(kNoIndex, it->first->index()); constraint_indices[k] = ct->index(); variable_indices[k] = it->first->index(); coefs[k] = it->second; ++k; } } CHECK_EQ(num_coefs + 1, k); glp_load_matrix(lp_, num_coefs, constraint_indices.get(), variable_indices.get(), coefs.get()); } else { // Build each new row separately. int max_constraint_size = solver_->ComputeMaxConstraintSize( last_constraint_index_, total_num_rows); // The first entry in the following arrays is dummy, to be // consistent with glpk API. scoped_array indices(new int[max_constraint_size + 1]); scoped_array coefs(new double[max_constraint_size + 1]); for (int i = last_constraint_index_; i < total_num_rows; i++) { ExtractOneConstraint(solver_->constraints_[i], indices.get(), coefs.get()); } } } } void GLPKInterface::ExtractObjective() { // Linear objective: set objective coefficients for all variables // (some might have been modified). for (hash_map::const_iterator it = solver_->linear_objective_.coefficients_.begin(); it != solver_->linear_objective_.coefficients_.end(); ++it) { glp_set_obj_coef(lp_, it->first->index(), it->second); } // Constant term. glp_set_obj_coef(lp_, 0, solver_->linear_objective_.offset_); } // Solve the problem using the parameter values specified. MPSolver::ResultStatus GLPKInterface::Solve(const MPSolverParameters& param) { WallTimer timer; timer.Start(); // Set log level. if (quiet_) { glp_term_out(GLP_OFF); } else { glp_term_out(GLP_ON); } ExtractModel(); VLOG(1) << StringPrintf("Model built in %.3f seconds.", timer.Get()); WriteModelToPredefinedFiles(); // Configure parameters at every solve, even when the model has not // been changed, in case some of the parameters such as the time // limit have been changed since the last solve. ConfigureGLPKParameters(param); // Solve timer.Restart(); if (mip_) { // glp_intopt requires to solve the root LP separately. int simplex_status = glp_simplex(lp_, &lp_param_); // If the root LP was solved successfully, solve the MIP. if (simplex_status == 0) { glp_intopt(lp_, &mip_param_); } else { // Something abnormal occurred during the root LP solve. It is // highly unlikely that an integer feasible solution is // available at this point, so we don't put any effort in trying // to recover it. result_status_ = MPSolver::ABNORMAL; sync_status_ = SOLUTION_SYNCHRONIZED; return result_status_; } } else { glp_simplex(lp_, &lp_param_); } VLOG(1) << StringPrintf("Solved in %.3f seconds.", timer.Get()); // Get the results. if (mip_) { objective_value_ = glp_mip_obj_val(lp_); } else { objective_value_ = glp_get_obj_val(lp_); } VLOG(1) << "objective=" << objective_value_; for (int i = 0; i < solver_->variables_.size(); ++i) { MPVariable* const var = solver_->variables_[i]; double val; if (mip_) { val = glp_mip_col_val(lp_, var->index()); } else { val = glp_get_col_prim(lp_, var->index()); } var->set_solution_value(val); VLOG(3) << var->name() << ": value =" << val; if (!mip_) { double reduced_cost; reduced_cost = glp_get_col_dual(lp_, var->index()); var->set_reduced_cost(reduced_cost); VLOG(4) << var->name() << ": reduced cost = " << reduced_cost; } } if (!mip_) { for (int i = 0; i < solver_->constraints_.size(); ++i) { MPConstraint* const ct = solver_->constraints_[i]; double dual_value = glp_get_row_dual(lp_, ct->index()); ct->set_dual_value(dual_value); VLOG(4) << "row " << ct->index() << ": dual value = " << dual_value; } } // Check the status: optimal, infeasible, etc. if (mip_) { int tmp_status = glp_mip_status(lp_); VLOG(1) << "gplk result status: " << tmp_status; if (tmp_status == GLP_OPT) { result_status_ = MPSolver::OPTIMAL; } else if (tmp_status == GLP_FEAS) { result_status_ = MPSolver::FEASIBLE; } else if (tmp_status == GLP_NOFEAS) { // For infeasible problems, GLPK actually seems to return // GLP_UNDEF. So this is never (?) reached. Return infeasible // in case GLPK returns a correct status in future versions. result_status_ = MPSolver::INFEASIBLE; } else { result_status_ = MPSolver::ABNORMAL; // GLPK does not have a status code for unbounded MIP models, so // we return an abnormal status instead. } } else { int tmp_status = glp_get_status(lp_); VLOG(1) << "gplk result status: " << tmp_status; if (tmp_status == GLP_OPT) { result_status_ = MPSolver::OPTIMAL; } else if (tmp_status == GLP_FEAS) { result_status_ = MPSolver::FEASIBLE; } else if (tmp_status == GLP_NOFEAS || tmp_status == GLP_INFEAS) { // For infeasible problems, GLPK actually seems to return // GLP_UNDEF. So this is never (?) reached. Return infeasible // in case GLPK returns a correct status in future versions. result_status_ = MPSolver::INFEASIBLE; } else if (tmp_status == GLP_UNBND) { // For unbounded problems, GLPK actually seems to return // GLP_UNDEF. So this is never (?) reached. Return unbounded // in case GLPK returns a correct status in future versions. result_status_ = MPSolver::UNBOUNDED; } else { result_status_ = MPSolver::ABNORMAL; } } sync_status_ = SOLUTION_SYNCHRONIZED; return result_status_; } MPSolverInterface* BuildGLPKInterface(MPSolver* const solver, bool mip) { return new GLPKInterface(solver, mip); } // ------ Query statistics on the solution and the solve ------ int64 GLPKInterface::iterations() const { if (mip_) { LOG(WARNING) << "Total number of iterations is not available"; return kUnknownNumberOfIterations; } else { CheckSolutionIsSynchronized(); return lpx_get_int_parm(lp_, LPX_K_ITCNT); } } int64 GLPKInterface::nodes() const { if (mip_) { CheckSolutionIsSynchronized(); return mip_callback_info_->num_all_nodes_; } else { LOG(FATAL) << "Number of nodes only available for discrete problems"; return kUnknownNumberOfNodes; } } double GLPKInterface::best_objective_bound() const { if (mip_) { CheckSolutionIsSynchronized(); CheckBestObjectiveBoundExists(); if (solver_->variables_.size() == 0 && solver_->constraints_.size() == 0) { // Special case for empty model. return solver_->linear_objective_.offset_; } else { return mip_callback_info_->best_objective_bound_; } } else { LOG(FATAL) << "Best objective bound only available for discrete problems"; return 0.0; } } void GLPKInterface::CheckSolutionExists() const { if (result_status_ == MPSolver::ABNORMAL) { LOG(WARNING) << "Ignoring ABNORMAL status from GLPK: This status may or may" << " not indicate that a solution exists."; } else { // Call default implementation MPSolverInterface::CheckSolutionExists(); } } void GLPKInterface::CheckBestObjectiveBoundExists() const { if (result_status_ == MPSolver::ABNORMAL) { LOG(WARNING) << "Ignoring ABNORMAL status from GLPK: This status may or may" << " not indicate that information is available on the best" << " objective bound."; } else { // Call default implementation MPSolverInterface::CheckBestObjectiveBoundExists(); } } // ------ Parameters ------ void GLPKInterface::ConfigureGLPKParameters(const MPSolverParameters& param) { if (mip_) { glp_init_iocp(&mip_param_); // Time limit if (solver_->time_limit()) { VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms."; mip_param_.tm_lim = solver_->time_limit(); } // Initialize structures related to the callback. mip_param_.cb_func = GLPKGatherInformationCallback; mip_callback_info_->Reset(maximize_); mip_param_.cb_info = mip_callback_info_.get(); // TODO(user): switch some cuts on? All cuts are off by default!? } // Configure LP parameters in all cases since they will be used to // solve the root LP in the MIP case. glp_init_smcp(&lp_param_); // Time limit if (solver_->time_limit()) { VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms."; lp_param_.tm_lim = solver_->time_limit(); } // Should give a numerically better representation of the problem. glp_scale_prob(lp_, GLP_SF_AUTO); // Use advanced initial basis (options: standard / advanced / Bixby's). glp_adv_basis(lp_, NULL); // Set parameters specified by the user. SetParameters(param); } void GLPKInterface::SetParameters(const MPSolverParameters& param) { SetCommonParameters(param); if (mip_) { SetMIPParameters(param); } } void GLPKInterface::SetRelativeMipGap(double value) { if (mip_) { mip_param_.mip_gap = value; } else { LOG(WARNING) << "The relative MIP gap is only available " << "for discrete problems."; } } void GLPKInterface::SetPresolveMode(int value) { switch (value) { case MPSolverParameters::PRESOLVE_OFF: { mip_param_.presolve = GLP_OFF; lp_param_.presolve = GLP_OFF; break; } case MPSolverParameters::PRESOLVE_ON: { mip_param_.presolve = GLP_ON; lp_param_.presolve = GLP_ON; break; } default: { SetIntegerParamToUnsupportedValue(MPSolverParameters::PRESOLVE, value); } } } void GLPKInterface::SetLpAlgorithm(int value) { switch (value) { case MPSolverParameters::DUAL: { // Use dual, and if it fails, switch to primal. lp_param_.meth = GLP_DUALP; break; } case MPSolverParameters::PRIMAL: { lp_param_.meth = GLP_PRIMAL; break; } case MPSolverParameters::BARRIER: default: { SetIntegerParamToUnsupportedValue(MPSolverParameters::LP_ALGORITHM, value); } } } } // namespace operations_research #endif // #if defined(USE_GLPK)