use the solver logging class in GLOP
This commit is contained in:
@@ -14,10 +14,10 @@
|
||||
// Command line interface to the MPSolver class.
|
||||
// See linear_solver.h and kUsageStr below.
|
||||
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/parse.h"
|
||||
#include "absl/flags/usage.h"
|
||||
#include "absl/strings/match.h"
|
||||
@@ -31,12 +31,15 @@
|
||||
#include "ortools/linear_solver/linear_solver.pb.h"
|
||||
#include "ortools/lp_data/mps_reader.h"
|
||||
#include "ortools/lp_data/proto_utils.h"
|
||||
#include "ortools/sat/cp_model.pb.h"
|
||||
#include "ortools/sat/cp_model_solver.h"
|
||||
#include "ortools/util/file_util.h"
|
||||
#include "ortools/util/sigint.h"
|
||||
|
||||
ABSL_FLAG(std::string, input, "", "REQUIRED: Input file name.");
|
||||
ABSL_FLAG(std::string, solver, "glop",
|
||||
"The solver to use: bop, cbc, clp, glop, glpk_lp, glpk_mip, "
|
||||
"gurobi_lp, gurobi_mip, scip, knapsack.");
|
||||
"gurobi_lp, gurobi_mip, scip, knapsack, sat.");
|
||||
|
||||
ABSL_FLAG(int, num_threads, 1,
|
||||
"Number of threads to use by the underlying solver.");
|
||||
@@ -66,59 +69,63 @@ ABSL_FLAG(std::string, dump_model, "",
|
||||
ABSL_FLAG(std::string, dump_request, "",
|
||||
"If non-empty, dumps MPModelRequest there.");
|
||||
ABSL_FLAG(std::string, dump_response, "",
|
||||
"If non-empty, dumps MPModelResponse there.");
|
||||
"If non-empty, dumps MPSolutionResponse there.");
|
||||
|
||||
ABSL_FLAG(std::string, sol_file, "",
|
||||
"If non-empty, output the best solution in Miplib .sol format.");
|
||||
|
||||
ABSL_DECLARE_FLAG(bool, verify_solution); // Defined in ./linear_solver.cc
|
||||
ABSL_DECLARE_FLAG(
|
||||
bool,
|
||||
linear_solver_enable_verbose_output); // Defined in ./linear_solver.cc
|
||||
|
||||
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.";
|
||||
" - an MPModelRequest (binary or text, possibly gzipped).";
|
||||
|
||||
namespace operations_research {
|
||||
namespace {
|
||||
|
||||
MPModelRequest ReadMipModel(const std::string& input) {
|
||||
MPModelRequest request_proto;
|
||||
MPModelProto model_proto;
|
||||
if (absl::EndsWith(input, ".mps") || absl::EndsWith(input, ".mps.gz")) {
|
||||
QCHECK_OK(glop::MPSReader().ParseFile(input, &model_proto))
|
||||
<< "Error while parsing the mps file '" << input << "'.";
|
||||
} else {
|
||||
ReadFileToProto(input, &model_proto);
|
||||
ReadFileToProto(input, &request_proto);
|
||||
}
|
||||
// If the input is a proto 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 '" << 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.";
|
||||
} else {
|
||||
LOG(INFO) << "Read input proto as an MPModelProto.";
|
||||
model_proto.Swap(request_proto.mutable_model());
|
||||
}
|
||||
return request_proto;
|
||||
}
|
||||
|
||||
// Returns false if an error was encountered.
|
||||
// More details should be available in the logs.
|
||||
bool Run() {
|
||||
// Create the solver and set its parameters.
|
||||
MPSolver::OptimizationProblemType type;
|
||||
CHECK(MPSolver::ParseSolverType(absl::GetFlag(FLAGS_solver), &type))
|
||||
<< "Unsupported --solver: " << absl::GetFlag(FLAGS_solver);
|
||||
const MPSolver::OptimizationProblemType type =
|
||||
MPSolver::ParseSolverTypeOrDie(absl::GetFlag(FLAGS_solver));
|
||||
MPModelRequest request_proto = ReadMipModel(absl::GetFlag(FLAGS_input));
|
||||
|
||||
// Load the problem into an MPModelProto.
|
||||
MPModelProto model_proto;
|
||||
MPModelRequest request_proto;
|
||||
if (absl::EndsWith(absl::GetFlag(FLAGS_input), ".mps") ||
|
||||
absl::EndsWith(absl::GetFlag(FLAGS_input), ".mps.gz")) {
|
||||
CHECK_OK(
|
||||
glop::MPSReader().ParseFile(absl::GetFlag(FLAGS_input), &model_proto))
|
||||
<< "Error while parsing the mps file '" << absl::GetFlag(FLAGS_input)
|
||||
<< "'.";
|
||||
} else {
|
||||
ReadFileToProto(absl::GetFlag(FLAGS_input), &model_proto);
|
||||
ReadFileToProto(absl::GetFlag(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 '" << absl::GetFlag(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.";
|
||||
model_proto.Swap(request_proto.mutable_model());
|
||||
} else {
|
||||
LOG(INFO) << "Read input proto as an MPModelProto.";
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("%-12s: '%s'\n", "File", absl::GetFlag(FLAGS_input).c_str());
|
||||
|
||||
// Detect format to dump protos.
|
||||
@@ -135,13 +142,13 @@ bool Run() {
|
||||
}
|
||||
|
||||
// Create the solver, we use the name of the model as the solver name.
|
||||
MPSolver solver(model_proto.name(), type);
|
||||
MPSolver solver(request_proto.model().name(), type);
|
||||
const absl::Status set_num_threads_status =
|
||||
solver.SetNumThreads(absl::GetFlag(FLAGS_num_threads));
|
||||
if (set_num_threads_status.ok()) {
|
||||
LOG(INFO) << "Set number of threads to " << absl::GetFlag(FLAGS_num_threads)
|
||||
<< ".";
|
||||
} else {
|
||||
} else if (absl::GetFlag(FLAGS_num_threads) != 1) {
|
||||
LOG(ERROR) << "Failed to set number of threads due to: "
|
||||
<< set_num_threads_status.message() << ". Using 1 as default.";
|
||||
}
|
||||
@@ -168,16 +175,17 @@ bool Run() {
|
||||
|
||||
// If requested, save the model to file.
|
||||
if (!absl::GetFlag(FLAGS_dump_model).empty()) {
|
||||
CHECK(WriteProtoToFile(absl::GetFlag(FLAGS_dump_model), model_proto,
|
||||
write_format, absl::GetFlag(FLAGS_dump_gzip)));
|
||||
CHECK(WriteProtoToFile(absl::GetFlag(FLAGS_dump_model),
|
||||
request_proto.model(), write_format,
|
||||
absl::GetFlag(FLAGS_dump_gzip)));
|
||||
}
|
||||
|
||||
const MPSolverResponseStatus status =
|
||||
solver.LoadModelFromProtoWithUniqueNamesOrDie(model_proto,
|
||||
solver.LoadModelFromProtoWithUniqueNamesOrDie(request_proto.model(),
|
||||
&error_message);
|
||||
if (request_proto.has_solver_time_limit_seconds()) {
|
||||
solver.set_time_limit(
|
||||
static_cast<int64_t>(1000.0 * request_proto.solver_time_limit_seconds()));
|
||||
solver.set_time_limit(static_cast<int64_t>(
|
||||
1000.0 * request_proto.solver_time_limit_seconds()));
|
||||
}
|
||||
// Note, the underlying MPSolver treats time limit equal to 0 as no limit.
|
||||
if (absl::GetFlag(FLAGS_time_limit_ms) >= 0) {
|
||||
@@ -190,6 +198,13 @@ bool Run() {
|
||||
absl::PrintF("%-12s: %d x %d\n", "Dimension", solver.NumConstraints(),
|
||||
solver.NumVariables());
|
||||
|
||||
// Register a signal handler to interrupt the solve when the user presses ^C.
|
||||
// Note that we ignore all previously registered handler here. If SCIP is
|
||||
// used, this handler will be overridden by the one of SCIP that does the same
|
||||
// thing.
|
||||
SigintHandler handler;
|
||||
handler.Register([&solver] { solver.InterruptSolve(); });
|
||||
|
||||
// Solve.
|
||||
MPSolverParameters param;
|
||||
MPSolver::ResultStatus solve_status = MPSolver::NOT_SOLVED;
|
||||
@@ -200,21 +215,34 @@ bool Run() {
|
||||
|
||||
// If requested, re-create a corresponding MPModelRequest and save it to file.
|
||||
if (!absl::GetFlag(FLAGS_dump_request).empty()) {
|
||||
operations_research::MPModelRequest request;
|
||||
request.set_solver_type(
|
||||
request_proto.set_solver_type(
|
||||
static_cast<MPModelRequest::SolverType>(solver.ProblemType()));
|
||||
request.set_solver_time_limit_seconds(solver.time_limit_in_secs());
|
||||
request.set_solver_specific_parameters(
|
||||
request_proto.set_solver_time_limit_seconds(solver.time_limit_in_secs());
|
||||
request_proto.set_solver_specific_parameters(
|
||||
solver.GetSolverSpecificParametersAsString());
|
||||
*request.mutable_model() = model_proto;
|
||||
CHECK(WriteProtoToFile(absl::GetFlag(FLAGS_dump_request), request,
|
||||
CHECK(WriteProtoToFile(absl::GetFlag(FLAGS_dump_request), request_proto,
|
||||
write_format, absl::GetFlag(FLAGS_dump_gzip)));
|
||||
}
|
||||
|
||||
const bool has_solution =
|
||||
solve_status == MPSolver::OPTIMAL || solve_status == MPSolver::FEASIBLE;
|
||||
|
||||
// If requested, get the MPModelResponse and save it to file.
|
||||
if (!absl::GetFlag(FLAGS_sol_file).empty() && has_solution) {
|
||||
operations_research::MPSolutionResponse response;
|
||||
solver.FillSolutionResponseProto(&response);
|
||||
std::string sol_string;
|
||||
absl::StrAppend(&sol_string, "=obj= ", response.objective_value(), "\n");
|
||||
for (int i = 0; i < response.variable_value().size(); ++i) {
|
||||
absl::StrAppend(&sol_string, request_proto.model().variable(i).name(),
|
||||
" ", response.variable_value(i), "\n");
|
||||
}
|
||||
LOG(INFO) << "Writing .sol solution to '" << absl::GetFlag(FLAGS_sol_file)
|
||||
<< "'.\n";
|
||||
CHECK_OK(file::SetContents(absl::GetFlag(FLAGS_sol_file), sol_string,
|
||||
file::Defaults()));
|
||||
}
|
||||
|
||||
// If requested, get the MPSolutionResponse and save it to file.
|
||||
if (!absl::GetFlag(FLAGS_dump_response).empty() && has_solution) {
|
||||
operations_research::MPSolutionResponse response;
|
||||
solver.FillSolutionResponseProto(&response);
|
||||
@@ -226,8 +254,9 @@ bool Run() {
|
||||
solver.FillSolutionResponseProto(&result);
|
||||
std::string csv_file;
|
||||
for (int i = 0; i < result.variable_value_size(); ++i) {
|
||||
csv_file += absl::StrFormat("%s,%e\n", model_proto.variable(i).name(),
|
||||
result.variable_value(i));
|
||||
csv_file +=
|
||||
absl::StrFormat("%s,%e\n", request_proto.model().variable(i).name(),
|
||||
result.variable_value(i));
|
||||
}
|
||||
CHECK_OK(file::SetContents(absl::GetFlag(FLAGS_output_csv), csv_file,
|
||||
file::Defaults()));
|
||||
@@ -262,10 +291,23 @@ bool Run() {
|
||||
} // namespace operations_research
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
google::InitGoogleLogging(kUsageStr);
|
||||
absl::ParseCommandLine(argc, argv);
|
||||
CHECK(!absl::GetFlag(FLAGS_input).empty()) << "--input is required";
|
||||
operations_research::Run();
|
||||
QCHECK(!absl::GetFlag(FLAGS_input).empty()) << "--input is required";
|
||||
|
||||
if (!operations_research::Run()) {
|
||||
// If the solver is SAT and we encountered an error, display it in a format
|
||||
// interpretable by our scripts.
|
||||
const operations_research::MPSolver::OptimizationProblemType type =
|
||||
operations_research::MPSolver::ParseSolverTypeOrDie(
|
||||
absl::GetFlag(FLAGS_solver));
|
||||
if (type == operations_research::MPSolver::SAT_INTEGER_PROGRAMMING) {
|
||||
operations_research::sat::CpSolverResponse response;
|
||||
response.set_status(
|
||||
operations_research::sat::CpSolverStatus::MODEL_INVALID);
|
||||
LOG(INFO) << operations_research::sat::CpSolverResponseStats(response);
|
||||
}
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user