// Copyright 2010-2021 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. // Tool to run MathOpt on the given problems. // // Examples: // // mathopt_solve --input_file model.pb // // mathopt_solve --input_file model.mps.gz --solver_type=glop // // mathopt_solve --input_file model --solver_logs --format=mathopt // #include #include #include #include #include #include #include "absl/flags/flag.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "ortools/base/file.h" #include "ortools/base/init_google.h" #include "ortools/base/logging.h" #include "ortools/base/status_macros.h" #include "ortools/math_opt/core/solver_interface.h" #include "ortools/math_opt/cpp/math_opt.h" #include "ortools/math_opt/cpp/statistics.h" #include "ortools/math_opt/io/mps_converter.h" #include "ortools/math_opt/io/names_removal.h" #include "ortools/math_opt/io/proto_converter.h" #include "ortools/math_opt/parameters.pb.h" #include "ortools/util/status_macros.h" inline constexpr absl::string_view kMathOptBinaryFormat = "mathopt"; inline constexpr absl::string_view kMathOptTextFormat = "mathopt_txt"; inline constexpr absl::string_view kLinearSolverBinaryFormat = "linear_solver"; inline constexpr absl::string_view kLinearSolverTextFormat = "linear_solver_txt"; inline constexpr absl::string_view kMPSFormat = "mps"; inline constexpr absl::string_view kAutoFormat = "auto"; inline constexpr absl::string_view kPbExt = ".pb"; inline constexpr absl::string_view kProtoExt = ".proto"; inline constexpr absl::string_view kPbTxtExt = ".pb.txt"; inline constexpr absl::string_view kTextProtoExt = ".textproto"; inline constexpr absl::string_view kMPSExt = ".mps"; inline constexpr absl::string_view kMPSGzipExt = ".mps.gz"; namespace { struct SolverTypeProtoFormatter { void operator()( std::string* const out, const operations_research::math_opt::SolverTypeProto solver_type) { out->append(EnumToString(EnumFromProto(solver_type).value())); } }; } // namespace ABSL_FLAG(std::string, input_file, "", "the file containing the model to solve; use --format to specify the " "file format"); ABSL_FLAG( std::string, format, "auto", absl::StrCat( "the format of the --input_file; possible values:\n", // "* ", kMathOptBinaryFormat, ": for a MathOpt ModelProto in binary\n", // "* ", kMathOptTextFormat, ": when the proto is in text\n", // "* ", kLinearSolverBinaryFormat, ": for a LinearSolver MPModelProto in binary\n", // "* ", kLinearSolverTextFormat, ": when the proto is in text\n", // "* ", kMPSFormat, ": for MPS file (which can be GZiped)\n", // "* ", kAutoFormat, ": to guess the format from the file extension:\n", // " - '", kPbExt, "', '", kProtoExt, "': ", kMathOptBinaryFormat, "\n", // " - '", kPbTxtExt, "', '", kTextProtoExt, "': ", kMathOptTextFormat, "\n", // " - '", kMPSExt, "', '", kMPSGzipExt, "': ", kMPSFormat)); ABSL_FLAG( std::vector, update_files, {}, absl::StrCat( "the file containing ModelUpdateProto to apply to the --input_file; " "when this flag is used, the --format must be either ", kMathOptBinaryFormat, " or ", kMathOptTextFormat)); ABSL_FLAG(operations_research::math_opt::SolverType, solver_type, operations_research::math_opt::SolverType::kGscip, absl::StrCat( "the solver to use, possible values: ", absl::StrJoin( operations_research::math_opt::AllSolversRegistry::Instance() ->RegisteredSolvers(), ", ", SolverTypeProtoFormatter()))); ABSL_FLAG(bool, solver_logs, false, "use a message callback to print the solver convergence logs"); ABSL_FLAG(absl::Duration, time_limit, absl::InfiniteDuration(), "the time limit to use for the solve"); ABSL_FLAG(bool, names, true, "use the names in the input models; ignoring names is useful when " "the input contains duplicates"); ABSL_FLAG(bool, ranges, false, "prints statistics about the ranges of the model values"); ABSL_FLAG(bool, print_model, false, "prints the model to stdout"); namespace operations_research { namespace math_opt { namespace { // Returned the guessed format (one of the kXxxFormat constant) from the file // extension; or nullopt. std::optional FormatFromFilePath( const absl::string_view file_path) { const std::vector> extension_to_format = { {kPbExt, kMathOptBinaryFormat}, {kProtoExt, kMathOptBinaryFormat}, {kPbTxtExt, kMathOptTextFormat}, {kTextProtoExt, kMathOptTextFormat}, {kMPSExt, kMPSFormat}, {kMPSGzipExt, kMPSFormat}, }; for (const auto& [ext, format] : extension_to_format) { if (absl::EndsWith(file_path, ext)) { return format; } } return std::nullopt; } // Returns the ModelProto read from the given file. The format must not be // kAutoFormat; other invalid values will be reported as QFATAL log mentioning // the --format flag. absl::StatusOr ReadModel(const absl::string_view file_path, const absl::string_view format) { if (format == kMathOptBinaryFormat) { return file::GetBinaryProto(file_path, file::Defaults()); } if (format == kMathOptTextFormat) { return file::GetTextProto(file_path, file::Defaults()); } if (format == kLinearSolverBinaryFormat || format == kLinearSolverTextFormat) { ASSIGN_OR_RETURN( MPModelProto linear_solver_model, format == kLinearSolverBinaryFormat ? file::GetBinaryProto(file_path, file::Defaults()) : file::GetTextProto(file_path, file::Defaults())); return MPModelProtoToMathOptModel(linear_solver_model); } if (format == kMPSFormat) { return ReadMpsFile(file_path); } LOG(QFATAL) << "Unsupported value of --format: " << format; } // Returns the ModelUpdateProto read from the given file. The format must be // kMathOptBinaryFormat or kMathOptTextFormat; other values will generate an // error. absl::StatusOr ReadModelUpdate( const absl::string_view file_path, const absl::string_view format) { if (format == kMathOptBinaryFormat) { return file::GetBinaryProto(file_path, file::Defaults()); } if (format == kMathOptTextFormat) { return file::GetTextProto(file_path, file::Defaults()); } return absl::InternalError( absl::StrCat("invalid format in ReadModelUpdate(): ", format)); } // Prints the model. void PrintModel(Model& model) { if (model.is_maximize()) { std::cout << "max"; } else { std::cout << "min"; } std::cout << ' ' << model.ObjectiveAsQuadraticExpression() << std::endl; std::cout << "variables:" << std::endl; for (const Variable v : model.SortedVariables()) { std::cout << ' ' << v.lower_bound() << " ≤ " << v << " ≤ " << v.upper_bound() << std::endl; } std::cout << "constraints:" << std::endl; for (const LinearConstraint c : model.SortedLinearConstraints()) { std::cout << ' ' << c << ": " << model.AsBoundedLinearExpression(c) << std::endl; } } // Prints the summary of the solve result. absl::Status PrintSummary(const SolveResult& result) { std::cout << "Solve finished:\n" << " termination: " << result.termination << "\n" << " solve time: " << result.solve_stats.solve_time << "\n best primal bound: " << result.solve_stats.best_primal_bound << "\n best dual bound: " << result.solve_stats.best_dual_bound << std::endl; if (result.solutions.empty()) { std::cout << " no solution" << std::endl; } for (int i = 0; i < result.solutions.size(); ++i) { const Solution& solution = result.solutions[i]; std::cout << " solution #" << (i + 1) << " objective: "; if (solution.primal_solution.has_value()) { std::cout << solution.primal_solution->objective_value; } else { std::cout << "n/a"; } std::cout << std::endl; } return absl::OkStatus(); } absl::Status RunSolver() { const std::string input_file_path = absl::GetFlag(FLAGS_input_file); if (input_file_path.empty()) { LOG(QFATAL) << "The flag --input_file is mandatory."; } // Parses --format. std::string format = absl::GetFlag(FLAGS_format); if (format == kAutoFormat) { const std::optional guessed_format = FormatFromFilePath(input_file_path); if (!guessed_format) { LOG(QFATAL) << "Can't guess the format from the file extension, please " "use --format to specify the file format explicitly."; } format = *guessed_format; } // We deal with input validation in the ReadModel() function. // Read the model and the optional updates. const std::vector update_file_paths = absl::GetFlag(FLAGS_update_files); if (!update_file_paths.empty() && format != kMathOptBinaryFormat && format != kMathOptTextFormat) { LOG(QFATAL) << "Can't use --update_files with a input of format " << format << "."; } OR_ASSIGN_OR_RETURN3(ModelProto model_proto, ReadModel(input_file_path, format), _ << "failed to read " << input_file_path); std::vector model_updates; for (const std::string& update_file_path : update_file_paths) { ASSIGN_OR_RETURN(ModelUpdateProto update, ReadModelUpdate(update_file_path, format)); model_updates.emplace_back(std::move(update)); } if (!absl::GetFlag(FLAGS_names)) { RemoveNames(model_proto); for (ModelUpdateProto& update : model_updates) { RemoveNames(update); } } // Parse the problem and the updates. ASSIGN_OR_RETURN(const std::unique_ptr model, Model::FromModelProto(model_proto)); for (int u = 0; u < model_updates.size(); ++u) { const ModelUpdateProto& update = model_updates[u]; RETURN_IF_ERROR(model->ApplyUpdateProto(update)) << "failed to apply the update file: " << update_file_paths[u]; } if (absl::GetFlag(FLAGS_ranges)) { std::cout << "Ranges of finite non-zero values in the model:\n" << ComputeModelRanges(*model) << std::endl; } // Optionally prints the problem. if (absl::GetFlag(FLAGS_print_model)) { PrintModel(*model); } // Solve the problem. SolveArguments solve_args = { .parameters = {.time_limit = absl::GetFlag(FLAGS_time_limit)}, }; if (absl::GetFlag(FLAGS_solver_logs)) { solve_args.message_callback = PrinterMessageCallback(std::cout, "logs| "); } OR_ASSIGN_OR_RETURN3( const SolveResult result, Solve(*model, absl::GetFlag(FLAGS_solver_type), solve_args), _ << "the solver failed"); RETURN_IF_ERROR(PrintSummary(result)); return absl::OkStatus(); } } // namespace } // namespace math_opt } // namespace operations_research int main(int argc, char* argv[]) { InitGoogle(argv[0], &argc, &argv, /*remove_flags=*/true); const absl::Status status = operations_research::math_opt::RunSolver(); // We don't use QCHECK_OK() here since the logged message contains more than // the failing status. if (!status.ok()) { LOG(QFATAL) << status; } return 0; }