OR-Tools  9.6
mathopt_solve_main.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 // Tool to run MathOpt on the given problems.
15 //
16 // Examples:
17 // * Solve a model stored as a proto (infer the file/proto type):
18 // mathopt_solve --input_file model.pb
19 // * Solve a gzipped mps file, pick your solver:
20 // mathopt_solve --input_file model.mps.gz --solver_type=glop
21 // * Set a time limit:
22 // mathopt_solve --input_file model.pb --time_limit 10s
23 // * Set solve parameters in proto text format (see parameters.proto):
24 // mathopt_solve --input_file model.pb --solve_parameters 'threads: 4'
25 // * Specify the file format:
26 // mathopt_solve --input_file model --format=mathopt
27 #include <iostream>
28 #include <memory>
29 #include <optional>
30 #include <ostream>
31 #include <string>
32 #include <utility>
33 #include <vector>
34 
35 #include "absl/flags/flag.h"
36 #include "absl/status/status.h"
37 #include "absl/status/statusor.h"
38 #include "absl/strings/match.h"
39 #include "absl/strings/str_cat.h"
40 #include "absl/strings/str_join.h"
41 #include "absl/strings/string_view.h"
42 #include "absl/time/time.h"
43 #include "google/protobuf/text_format.h"
44 #include "ortools/base/helpers.h"
46 #include "ortools/base/logging.h"
47 #include "ortools/base/options.h"
55 #include "ortools/math_opt/parameters.pb.h"
57 
58 inline constexpr absl::string_view kMathOptBinaryFormat = "mathopt";
59 inline constexpr absl::string_view kMathOptTextFormat = "mathopt_txt";
60 inline constexpr absl::string_view kLinearSolverBinaryFormat = "linear_solver";
61 inline constexpr absl::string_view kLinearSolverTextFormat =
62  "linear_solver_txt";
63 inline constexpr absl::string_view kMPSFormat = "mps";
64 inline constexpr absl::string_view kAutoFormat = "auto";
65 
66 inline constexpr absl::string_view kPbExt = ".pb";
67 inline constexpr absl::string_view kProtoExt = ".proto";
68 inline constexpr absl::string_view kPbTxtExt = ".pb.txt";
69 inline constexpr absl::string_view kTextProtoExt = ".textproto";
70 inline constexpr absl::string_view kMPSExt = ".mps";
71 inline constexpr absl::string_view kMPSGzipExt = ".mps.gz";
72 
73 namespace {
74 
75 struct SolverTypeProtoFormatter {
76  void operator()(
77  std::string* const out,
78  const operations_research::math_opt::SolverTypeProto solver_type) {
79  out->append(EnumToString(EnumFromProto(solver_type).value()));
80  }
81 };
82 
83 } // namespace
84 
85 ABSL_FLAG(std::string, input_file, "",
86  "the file containing the model to solve; use --format to specify the "
87  "file format");
89  std::string, format, "auto",
90  absl::StrCat(
91  "the format of the --input_file; possible values:\n",
92  //
93  "* ", kMathOptBinaryFormat, ": for a MathOpt ModelProto in binary\n",
94  //
95  "* ", kMathOptTextFormat, ": when the proto is in text\n",
96  //
98  ": for a LinearSolver MPModelProto in binary\n",
99  //
100  "* ", kLinearSolverTextFormat, ": when the proto is in text\n",
101  //
102  "* ", kMPSFormat, ": for MPS file (which can be GZiped)\n",
103  //
104  "* ", kAutoFormat, ": to guess the format from the file extension:\n",
105  //
106  " - '", kPbExt, "', '", kProtoExt, "': ", kMathOptBinaryFormat, "\n",
107  //
108  " - '", kPbTxtExt, "', '", kTextProtoExt, "': ", kMathOptTextFormat,
109  "\n",
110  //
111  " - '", kMPSExt, "', '", kMPSGzipExt, "': ", kMPSFormat));
113  std::vector<std::string>, update_files, {},
114  absl::StrCat(
115  "the file containing ModelUpdateProto to apply to the --input_file; "
116  "when this flag is used, the --format must be either ",
118 
121  absl::StrCat(
122  "the solver to use, possible values: ",
123  absl::StrJoin(
125  ->RegisteredSolvers(),
126  ", ", SolverTypeProtoFormatter())));
128  "SolveParameters in text-proto format. Note that the time limit is "
129  "overridden by the --time_limit flag.");
130 ABSL_FLAG(bool, solver_logs, false,
131  "use a message callback to print the solver convergence logs");
132 ABSL_FLAG(absl::Duration, time_limit, absl::InfiniteDuration(),
133  "the time limit to use for the solve");
134 
135 ABSL_FLAG(bool, names, true,
136  "use the names in the input models; ignoring names is useful when "
137  "the input contains duplicates");
138 ABSL_FLAG(bool, ranges, false,
139  "prints statistics about the ranges of the model values");
140 ABSL_FLAG(bool, print_model, false, "prints the model to stdout");
141 
142 namespace operations_research {
143 namespace math_opt {
144 namespace {
145 
146 // Returned the guessed format (one of the kXxxFormat constant) from the file
147 // extension; or nullopt.
148 std::optional<absl::string_view> FormatFromFilePath(
149  const absl::string_view file_path) {
150  const std::vector<std::pair<absl::string_view, absl::string_view>>
151  extension_to_format = {
155  };
156 
157  for (const auto& [ext, format] : extension_to_format) {
158  if (absl::EndsWith(file_path, ext)) {
159  return format;
160  }
161  }
162 
163  return std::nullopt;
164 }
165 
166 // Returns the ModelProto read from the given file. The format must not be
167 // kAutoFormat; other invalid values will be reported as QFATAL log mentioning
168 // the --format flag.
169 absl::StatusOr<ModelProto> ReadModel(const absl::string_view file_path,
170  const absl::string_view format) {
171  if (format == kMathOptBinaryFormat) {
172  return file::GetBinaryProto<ModelProto>(file_path, file::Defaults());
173  }
174  if (format == kMathOptTextFormat) {
175  return file::GetTextProto<ModelProto>(file_path, file::Defaults());
176  }
177  if (format == kLinearSolverBinaryFormat ||
178  format == kLinearSolverTextFormat) {
180  MPModelProto linear_solver_model,
181  format == kLinearSolverBinaryFormat
182  ? file::GetBinaryProto<MPModelProto>(file_path, file::Defaults())
183  : file::GetTextProto<MPModelProto>(file_path, file::Defaults()));
184  return MPModelProtoToMathOptModel(linear_solver_model);
185  }
186  if (format == kMPSFormat) {
187  return ReadMpsFile(file_path);
188  }
189  LOG(QFATAL) << "Unsupported value of --format: " << format;
190 }
191 
192 // Returns the ModelUpdateProto read from the given file. The format must be
193 // kMathOptBinaryFormat or kMathOptTextFormat; other values will generate an
194 // error.
195 absl::StatusOr<ModelUpdateProto> ReadModelUpdate(
196  const absl::string_view file_path, const absl::string_view format) {
197  if (format == kMathOptBinaryFormat) {
198  return file::GetBinaryProto<ModelUpdateProto>(file_path, file::Defaults());
199  }
200  if (format == kMathOptTextFormat) {
201  return file::GetTextProto<ModelUpdateProto>(file_path, file::Defaults());
202  }
203  return absl::InternalError(
204  absl::StrCat("invalid format in ReadModelUpdate(): ", format));
205 }
206 
207 // Prints the summary of the solve result.
208 absl::Status PrintSummary(const SolveResult& result) {
209  std::cout << "Solve finished:\n"
210  << " termination: " << result.termination << "\n"
211  << " solve time: " << result.solve_stats.solve_time
212  << "\n best primal bound: " << result.solve_stats.best_primal_bound
213  << "\n best dual bound: " << result.solve_stats.best_dual_bound
214  << std::endl;
215  if (result.solutions.empty()) {
216  std::cout << " no solution" << std::endl;
217  }
218  for (int i = 0; i < result.solutions.size(); ++i) {
219  const Solution& solution = result.solutions[i];
220  std::cout << " solution #" << (i + 1) << " objective: ";
221  if (solution.primal_solution.has_value()) {
222  std::cout << solution.primal_solution->objective_value;
223  } else {
224  std::cout << "n/a";
225  }
226  std::cout << std::endl;
227  }
228 
229  return absl::OkStatus();
230 }
231 
232 absl::Status RunSolver() {
233  const std::string input_file_path = absl::GetFlag(FLAGS_input_file);
234  if (input_file_path.empty()) {
235  LOG(QFATAL) << "The flag --input_file is mandatory.";
236  }
237 
238  // Parses --format.
239  std::string format = absl::GetFlag(FLAGS_format);
240  if (format == kAutoFormat) {
241  const std::optional<absl::string_view> guessed_format =
242  FormatFromFilePath(input_file_path);
243  if (!guessed_format) {
244  LOG(QFATAL) << "Can't guess the format from the file extension, please "
245  "use --format to specify the file format explicitly.";
246  }
247  format = *guessed_format;
248  }
249  // We deal with input validation in the ReadModel() function.
250 
251  // Read the model and the optional updates.
252  const std::vector<std::string> update_file_paths =
253  absl::GetFlag(FLAGS_update_files);
254  if (!update_file_paths.empty() && format != kMathOptBinaryFormat &&
255  format != kMathOptTextFormat) {
256  LOG(QFATAL) << "Can't use --update_files with a input of format " << format
257  << ".";
258  }
259 
261  ReadModel(input_file_path, format),
262  _ << "failed to read " << input_file_path);
263 
264  std::vector<ModelUpdateProto> model_updates;
265  for (const std::string& update_file_path : update_file_paths) {
266  ASSIGN_OR_RETURN(ModelUpdateProto update,
267  ReadModelUpdate(update_file_path, format));
268  model_updates.emplace_back(std::move(update));
269  }
270 
271  if (!absl::GetFlag(FLAGS_names)) {
273  for (ModelUpdateProto& update : model_updates) {
274  RemoveNames(update);
275  }
276  }
277 
278  // Parse the problem and the updates.
279  ASSIGN_OR_RETURN(const std::unique_ptr<Model> model,
281  for (int u = 0; u < model_updates.size(); ++u) {
282  const ModelUpdateProto& update = model_updates[u];
283  RETURN_IF_ERROR(model->ApplyUpdateProto(update))
284  << "failed to apply the update file: " << update_file_paths[u];
285  }
286 
287  if (absl::GetFlag(FLAGS_ranges)) {
288  std::cout << "Ranges of finite non-zero values in the model:\n"
289  << ComputeModelRanges(*model) << std::endl;
290  }
291 
292  // Optionally prints the problem.
293  if (absl::GetFlag(FLAGS_print_model)) {
294  std::cout << *model;
295  std::cout.flush();
296  }
297 
298  // Solve the problem.
299  SolveParameters solve_parameters = absl::GetFlag(FLAGS_solve_parameters);
300  solve_parameters.time_limit = absl::GetFlag(FLAGS_time_limit);
301  SolveArguments solve_args = {.parameters = solve_parameters};
302  if (absl::GetFlag(FLAGS_solver_logs)) {
303  solve_args.message_callback = PrinterMessageCallback(std::cout, "logs| ");
304  }
306  const SolveResult result,
307  Solve(*model, absl::GetFlag(FLAGS_solver_type), solve_args),
308  _ << "the solver failed");
309 
310  RETURN_IF_ERROR(PrintSummary(result));
311 
312  return absl::OkStatus();
313 }
314 
315 } // namespace
316 } // namespace math_opt
317 } // namespace operations_research
318 
319 int main(int argc, char* argv[]) {
320  InitGoogle(argv[0], &argc, &argv, /*remove_flags=*/true);
321 
322  const absl::Status status = operations_research::math_opt::RunSolver();
323  // We don't use QCHECK_OK() here since the logged message contains more than
324  // the failing status.
325  if (!status.ok()) {
326  LOG(QFATAL) << status;
327  }
328 
329  return 0;
330 }
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
static absl::StatusOr< std::unique_ptr< Model > > FromModelProto(const ModelProto &model_proto)
CpModelProto const * model_proto
ModelSharedTimeLimit * time_limit
int64_t value
absl::Status status
Definition: g_gurobi.cc:41
GRBmodel * model
void InitGoogle(const char *usage, int *argc, char ***argv, bool deprecated)
Definition: init_google.h:34
constexpr absl::string_view kMathOptTextFormat
int main(int argc, char *argv[])
constexpr absl::string_view kPbExt
constexpr absl::string_view kMathOptBinaryFormat
constexpr absl::string_view kMPSGzipExt
constexpr absl::string_view kMPSExt
constexpr absl::string_view kAutoFormat
constexpr absl::string_view kMPSFormat
constexpr absl::string_view kLinearSolverTextFormat
constexpr absl::string_view kTextProtoExt
ABSL_FLAG(std::string, input_file, "", "the file containing the model to solve; use --format to specify the " "file format")
constexpr absl::string_view kProtoExt
constexpr absl::string_view kLinearSolverBinaryFormat
constexpr absl::string_view kPbTxtExt
Options Defaults()
Definition: base/file.h:123
absl::string_view EnumToString(const E value)
Definition: enums.h:287
void RemoveNames(ModelProto &model)
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
MessageCallback PrinterMessageCallback(std::ostream &output_stream, const absl::string_view prefix)
absl::StatusOr<::operations_research::math_opt::ModelProto > MPModelProtoToMathOptModel(const ::operations_research::MPModelProto &model)
absl::StatusOr< ModelProto > ReadMpsFile(const absl::string_view filename)
ModelRanges ComputeModelRanges(const Model &model)
Definition: statistics.cc:96
std::optional< typename EnumProto< P >::Cpp > EnumFromProto(const P proto_value)
Definition: enums.h:279
Collection of objects used to extend the Constraint Solver library.
#define OR_ASSIGN_OR_RETURN3(lhs, rexpr, error_expression)