// Copyright 2010-2014 Google // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Command line interface to the MPSolver class. // See linear_solver.h and kUsageStr below. #include #include #include "base/commandlineflags.h" #include "base/commandlineflags.h" #include "base/timer.h" #include "base/file.h" #include "base/strutil.h" #include "glop/proto_utils.h" #include "linear_solver/linear_solver.h" #include "linear_solver/linear_solver.pb.h" #include "lp_data/mps_reader.h" #include "util/proto_tools.h" DEFINE_string(input, "", "REQUIRED: Input file name."); DEFINE_string(solver, "glop", "The solver to use: bop, cbc, clp, cplex, cplex_mip, glop, glpk_lp, " "glpk_mip, gurobi_lp, gurobi_mip, scip, knapsack."); DEFINE_string(params_file, "", "Solver specific parameters file. " "If this flag is set, the --params flag is ignored.");DEFINE_string(params, "", "Solver specific parameters"); DEFINE_int64(time_limit_ms, 0, "If stricitly positive, specifies a limit in ms on the solving" " time."); DEFINE_string(forced_mps_format, "", "Set to force the mps format to use: free, fixed"); DEFINE_string(output, "", "If non-empty, write the MPSolverResponse there. " "The format will be binary except if the name end with '.txt'."); DEFINE_string(output_csv, "", "If non-empty, write the returned solution in csv format with " "each line formed by a variable name and its value."); static const char kUsageStr[] = "Run MPSolver on the given input file. Many formats are supported: \n" " - a .mps or .mps.gz file,\n" " - an MPModelProto (binary or text, possibly gzipped),\n" " - an MPModelRequest (binary or text, possibly gzipped).\n" "MPModelProto and MPModelRequest files can comply with either the " "linear_solver.proto or the linear_solver.proto format."; namespace operations_research { namespace { void Run() { // Create the solver and set its parameters. MPSolver::OptimizationProblemType type; if (FLAGS_solver == "glop") { type = MPSolver::GLOP_LINEAR_PROGRAMMING; #if defined(USE_GLPK) } else if (FLAGS_solver == "glpk_lp") { type = MPSolver::GLPK_LINEAR_PROGRAMMING; #endif #if defined(USE_CLP) } else if (FLAGS_solver == "clp") { type = MPSolver::CLP_LINEAR_PROGRAMMING; #endif #if defined(USE_CPLEX) } else if (FLAGS_solver == "cplex") { type = MPSolver::CPLEX_LINEAR_PROGRAMMING; #endif #if defined(USE_GUROBI) } else if (FLAGS_solver == "gurobi_lp") { type = MPSolver::GUROBI_LINEAR_PROGRAMMING; #endif #if defined(USE_SCIP) } else if (FLAGS_solver == "scip") { type = MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING; #endif #if defined(USE_GUROBI) } else if (FLAGS_solver == "cbc") { type = MPSolver::CBC_MIXED_INTEGER_PROGRAMMING; #endif #if defined(USE_GLPK) } else if (FLAGS_solver == "glpk_mip") { type = MPSolver::GLPK_MIXED_INTEGER_PROGRAMMING; #endif #if defined(USE_CPLEX) } else if (FLAGS_solver == "cplex_mip") { type = MPSolver::CPLEX_MIXED_INTEGER_PROGRAMMING; #endif #if defined(USE_GUROBI) } else if (FLAGS_solver == "gurobi_mip") { type = MPSolver::GUROBI_MIXED_INTEGER_PROGRAMMING; #endif #if defined(USE_BOP) } else if (FLAGS_solver == "bop") { type = MPSolver::BOP_INTEGER_PROGRAMMING; #endif } else { LOG(FATAL) << "Unsupported --solver: " << FLAGS_solver; } MPSolver solver("command line solver", type); solver.EnableOutput(); if (!FLAGS_params_file.empty()) { std::string file_contents; CHECK_OK( file::GetContents(FLAGS_params_file, &file_contents, file::Defaults())) << "Could not read parameters file."; CHECK(solver.SetSolverSpecificParametersAsString(file_contents)); } else if (!FLAGS_params.empty()) { CHECK(solver.SetSolverSpecificParametersAsString(FLAGS_params)) << "Wrong --params format."; } printf("%-12s: %s\n", "Solver", MPModelRequest::SolverType_Name( static_cast(solver.ProblemType())) .c_str()); // Load the problem into an MPModelProto. MPModelProto model_proto; MPModelRequest request_proto; if (HasSuffixString(FLAGS_input, ".mps") || HasSuffixString(FLAGS_input, ".mps.gz")) { glop::LinearProgram linear_program_fixed; glop::LinearProgram linear_program_free; glop::MPSReader mps_reader; mps_reader.set_log_errors(FLAGS_forced_mps_format == "free" || FLAGS_forced_mps_format == "fixed"); const bool fixed_read = FLAGS_forced_mps_format != "free" && mps_reader.LoadFileWithMode(FLAGS_input, false, &linear_program_fixed); const bool free_read = FLAGS_forced_mps_format != "fixed" && mps_reader.LoadFileWithMode(FLAGS_input, true, &linear_program_free); CHECK(fixed_read || free_read) << "Error while parsing the mps file '" << FLAGS_input << "' " << "Use the --forced_mps_format flags to see the errors."; if (!fixed_read) { LOG(INFO) << "Read file in free format."; LinearProgramToMPModelProto(linear_program_free, &model_proto); } else { LOG(INFO) << "Read file in fixed format."; LinearProgramToMPModelProto(linear_program_fixed, &model_proto); } } else { ReadFileToProto(FLAGS_input, &model_proto); ReadFileToProto(FLAGS_input, &request_proto); // If the input proto is in binary format, both ReadFileToProto could return // true. Instead use the actual number of variables found to test the // correct format of the input. const bool is_model_proto = model_proto.variable_size() > 0; const bool is_request_proto = request_proto.model().variable_size() > 0; if (!is_model_proto && !is_request_proto) { LOG(FATAL) << "Failed to parse '" << FLAGS_input << "' as an MPModelProto or an MPModelRequest."; } else { CHECK(!(is_model_proto && is_request_proto)); if (is_request_proto) { LOG(INFO) << "Read input proto as an MPModelRequest."; if (request_proto.has_solver_time_limit_seconds()) { solver.set_time_limit(static_cast( request_proto.solver_time_limit_seconds() * 1000)); } model_proto.Swap(request_proto.mutable_model()); } else { LOG(INFO) << "Read input proto as an MPModelProto."; } } } printf("%-12s: '%s'\n", "File", FLAGS_input.c_str()); // Load the proto into the solver. std::string error_message; const MPSolverResponseStatus status = solver.LoadModelFromProto(model_proto, &error_message); if (FLAGS_time_limit_ms >= 0) { solver.set_time_limit(FLAGS_time_limit_ms); } CHECK_EQ(MPSOLVER_MODEL_IS_VALID, status) << MPSolverResponseStatus_Name(status) << ": " << error_message; printf("%-12s: %d x %d\n", "Dimension", solver.NumConstraints(), solver.NumVariables()); // Solve. MPSolverParameters param; MPSolver::ResultStatus solve_status = MPSolver::NOT_SOLVED; double solving_time_in_sec = 0; { ScopedWallTime timer(&solving_time_in_sec); solve_status = solver.Solve(param); } const bool has_solution = solve_status == MPSolver::OPTIMAL || solve_status == MPSolver::FEASIBLE; // If requested, get the solver result and output it. if (!FLAGS_output.empty() && has_solution) { operations_research::MPSolutionResponse result; solver.FillSolutionResponseProto(&result); if (HasSuffixString(FLAGS_output, ".txt")) { CHECK_OK(file::SetTextProto(FLAGS_output, result, file::Defaults())); } else { CHECK_OK(file::SetBinaryProto(FLAGS_output, result, file::Defaults())); } } if (!FLAGS_output_csv.empty() && has_solution) { operations_research::MPSolutionResponse result; solver.FillSolutionResponseProto(&result); std::string csv_file; for (int i = 0; i < result.variable_value_size(); ++i) { csv_file += StringPrintf("%s,%e\n", model_proto.variable(i).name().c_str(), result.variable_value(i)); } CHECK_OK(file::SetContents(FLAGS_output_csv, csv_file, file::Defaults())); } printf("%-12s: %s\n", "Status", MPSolverResponseStatus_Name( static_cast(solve_status)) .c_str()); printf("%-12s: %15.15e\n", "Objective", has_solution ? solver.Objective().Value() : 0.0); printf("%-12s: %-6.4g\n", "Time", solving_time_in_sec); } } // namespace } // namespace operations_research int main(int argc, char** argv) { gflags::ParseCommandLineFlags( &argc, &argv, /*remove_flags=*/true); CHECK(!FLAGS_input.empty()) << "--input is required"; operations_research::Run(); }