Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
mathopt_solve.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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/base/no_destructor.h"
36#include "absl/flags/flag.h"
37#include "absl/log/check.h"
38#include "absl/status/status.h"
39#include "absl/status/statusor.h"
40#include "absl/strings/str_cat.h"
41#include "absl/strings/str_join.h"
42#include "absl/strings/string_view.h"
43#include "absl/time/clock.h"
44#include "absl/time/time.h"
55#include "ortools/math_opt/parameters.pb.h"
57#include "ortools/util/sigint.h"
59
60namespace {
61
62struct SolverTypeProtoFormatter {
63 void operator()(
64 std::string* const out,
65 const operations_research::math_opt::SolverTypeProto solver_type) {
66 out->append(EnumToString(EnumFromProto(solver_type).value()));
67 }
68};
69
70} // namespace
71
72ABSL_FLAG(std::string, input_file, "",
73 "the file containing the model to solve; use --format to specify the "
74 "file format");
76 std::optional<operations_research::math_opt::FileFormat>, format,
77 std::nullopt,
78 absl::StrCat(
79 "the format of the --input_file; possible values:",
82 std::vector<std::string>, update_files, {},
83 absl::StrCat(
84 "the file containing ModelUpdateProto to apply to the --input_file; "
85 "when this flag is used, the --format must be either ",
87 operations_research::math_opt::FileFormat::kMathOptBinary),
88 " or ",
90 operations_research::math_opt::FileFormat::kMathOptText)));
91
94 absl::StrCat(
95 "the solver to use, possible values: ",
96 absl::StrJoin(
98 ->RegisteredSolvers(),
99 ", ", SolverTypeProtoFormatter())));
100ABSL_FLAG(bool, remote, false,
101 "solve by RPC instead of locally, using ~twice the time limit as the "
102 "RPC deadline, requires a time limit is set, see --time_limit");
104 "SolveParameters in text-proto format. Note that the time limit is "
105 "overridden by the --time_limit flag.");
106ABSL_FLAG(bool, solver_logs, false,
107 "use a message callback to print the solver convergence logs");
108ABSL_FLAG(absl::Duration, time_limit, absl::InfiniteDuration(),
109 "the time limit to use for the solve");
110ABSL_FLAG(bool, sigint_interrupt, true,
111 "interrupts the solve on the first SIGINT; kill the process on the "
112 "third one");
113
114ABSL_FLAG(bool, names, true,
115 "use the names in the input models; ignoring names is useful when "
116 "the input contains duplicates");
117ABSL_FLAG(bool, ranges, false,
118 "prints statistics about the ranges of the model values");
119ABSL_FLAG(bool, print_model, false, "prints the model to stdout");
120ABSL_FLAG(bool, lp_relaxation, false,
121 "relax all integer variables to continuous");
123 bool, check_solutions, false,
124 "check the solutions feasibility; use --absolute_constraint_tolerance, "
125 "--integrality_tolerance, and --nonzero_tolerance values for tolerances");
126ABSL_FLAG(double, absolute_constraint_tolerance,
129 "feasibility tolerance for constraints and variables bounds");
130ABSL_FLAG(double, integrality_tolerance,
133 "feasibility tolerance for variables' integrality");
135 double, nonzero_tolerance,
138 "tolerance for checking if a value is nonzero (e.g., in SOS constraints)");
139
141namespace {
142
143// Returns the ModelUpdateProto read from the given file. The format must be
144// kMathOptBinary or kMathOptText; other values will generate an error.
145absl::StatusOr<ModelUpdateProto> ReadModelUpdate(
146 const absl::string_view file_path, const FileFormat format) {
147 switch (format) {
148 case FileFormat::kMathOptBinary:
151 case FileFormat::kMathOptText:
153 default:
154 return util::InternalErrorBuilder() << "invalid format " << format;
155 }
156}
157
158struct ModelAndHint {
159 std::unique_ptr<Model> model;
160 std::optional<ModelSolveParameters::SolutionHint> hint;
161};
162
163absl::StatusOr<ModelAndHint> ParseModelAndHint() {
164 const std::string input_file_path = absl::GetFlag(FLAGS_input_file);
165 if (input_file_path.empty()) {
166 LOG(QFATAL) << "The flag --input_file is mandatory.";
167 }
168
169 // Parses --format.
170 const FileFormat format = [&]() {
171 const std::optional<FileFormat> format =
172 FormatFromFlagOrFilePath(absl::GetFlag(FLAGS_format), input_file_path);
173 if (format.has_value()) {
174 return *format;
175 }
176 LOG(QFATAL) << "Can't guess the format from the file extension, please "
177 "use --format to specify the file format explicitly.";
178 }();
179 // We deal with input validation in the ReadModel() function.
180
181 // Read the model and the optional updates.
182 const std::vector<std::string> update_file_paths =
183 absl::GetFlag(FLAGS_update_files);
184 if (!update_file_paths.empty() && format != FileFormat::kMathOptBinary &&
185 format != FileFormat::kMathOptText) {
186 LOG(QFATAL) << "Can't use --update_files with a input of format " << format
187 << ".";
188 }
189
190 OR_ASSIGN_OR_RETURN3((auto [model_proto, optional_hint]),
191 ReadModel(input_file_path, format),
192 _ << "failed to read " << input_file_path);
193
194 std::vector<ModelUpdateProto> model_updates;
195 for (const std::string& update_file_path : update_file_paths) {
196 ASSIGN_OR_RETURN(ModelUpdateProto update,
197 ReadModelUpdate(update_file_path, format));
198 model_updates.emplace_back(std::move(update));
199 }
200
201 if (!absl::GetFlag(FLAGS_names)) {
202 RemoveNames(model_proto);
203 for (ModelUpdateProto& update : model_updates) {
204 RemoveNames(update);
205 }
206 }
207
208 // Parse the problem and the updates.
209 ASSIGN_OR_RETURN(std::unique_ptr<Model> model,
210 Model::FromModelProto(model_proto));
211 for (int u = 0; u < model_updates.size(); ++u) {
212 const ModelUpdateProto& update = model_updates[u];
213 RETURN_IF_ERROR(model->ApplyUpdateProto(update))
214 << "failed to apply the update file: " << update_file_paths[u];
215 }
216 if (absl::GetFlag(FLAGS_lp_relaxation)) {
217 for (const Variable v : model->Variables()) {
218 model->set_continuous(v);
219 }
220 }
221 ModelAndHint result = {.model = std::move(model)};
222 if (optional_hint.has_value()) {
223 OR_ASSIGN_OR_RETURN3(ModelSolveParameters::SolutionHint hint,
225 *result.model, optional_hint.value()),
226 _ << "invalid solution hint");
227 result.hint = std::move(hint);
228 }
229 return std::move(result);
230}
231
232// Prints the summary of the solve result.
233//
234// If feasibility_check_tolerances is not nullopt then a check of feasibility of
235// solution is done with the provided tolerances.
236absl::Status PrintSummary(const Model& model, const SolveResult& result,
237 const std::optional<FeasibilityCheckerOptions>
238 feasibility_check_tolerances) {
239 std::cout << "Solve finished:\n"
240 << " termination: " << result.termination << "\n"
241 << " solve time: " << result.solve_stats.solve_time
242 << "\n best primal bound: "
243 << result.termination.objective_bounds.primal_bound
244 << "\n best dual bound: "
245 << result.termination.objective_bounds.dual_bound << std::endl;
246 if (result.solutions.empty()) {
247 std::cout << " no solution" << std::endl;
248 }
249 for (int i = 0; i < result.solutions.size(); ++i) {
250 const Solution& solution = result.solutions[i];
251 std::cout << " solution #" << (i + 1) << " objective: ";
252 if (solution.primal_solution.has_value()) {
253 std::cout << solution.primal_solution->objective_value;
254 if (feasibility_check_tolerances.has_value()) {
256 const ModelSubset broken_constraints,
258 model, solution.primal_solution->variable_values,
259 *feasibility_check_tolerances),
260 _ << "failed to check the primal solution feasibility of solution #"
261 << (i + 1));
262 if (!broken_constraints.empty()) {
263 std::cout << " (numerically infeasible: " << broken_constraints
264 << ')';
265 } else {
266 std::cout << " (numerically feasible)";
267 }
268 }
269 } else {
270 std::cout << "n/a";
271 }
272 std::cout << std::endl;
273 }
274
275 return absl::OkStatus();
276}
277
278absl::StatusOr<SolveResult> LocalOrRemoteSolve(
279 const Model& model, const SolverType solver_type,
280 const SolveParameters& params, const ModelSolveParameters& model_params,
281 MessageCallback msg_cb, const SolveInterrupter* const interrupter) {
282 if (absl::GetFlag(FLAGS_remote)) {
283 return absl::UnimplementedError("remote not yet supported.");
284 } else {
285 return Solve(model, solver_type,
286 {.parameters = params,
287 .model_parameters = model_params,
288 .message_callback = std::move(msg_cb),
289 .interrupter = interrupter});
290 }
291}
292
293absl::Status RunSolver() {
294 // We use absl::NoDestructor here so that the SIGINT handler is kept until the
295 // very end of the process, making sure a late Ctrl-C on the very end of the
296 // solve don't kill the process.
297 static absl::NoDestructor<SigintHandler> sigint_handler;
298 static const absl::NoDestructor<std::unique_ptr<SolveInterrupter>>
299 interrupter([&]() -> std::unique_ptr<SolveInterrupter> {
300 if (!absl::GetFlag(FLAGS_sigint_interrupt)) {
301 return nullptr;
302 }
303 auto interrupter =
304 std::make_unique<operations_research::SolveInterrupter>();
305 sigint_handler->Register(
306 [interrupter = interrupter.get()]() { interrupter->Interrupt(); });
307 return interrupter;
308 }());
309
310 if (absl::GetFlag(FLAGS_remote) &&
311 absl::GetFlag(FLAGS_time_limit) == absl::InfiniteDuration()) {
312 return absl::InvalidArgumentError(
313 "a finite time limit is required when solving remotely, e.g. "
314 "--time_limit=5m");
315 }
316 ASSIGN_OR_RETURN(const ModelAndHint model_and_hint, ParseModelAndHint());
317
318 if (absl::GetFlag(FLAGS_ranges)) {
319 std::cout << "Ranges of finite non-zero values in the model:\n"
320 << ComputeModelRanges(*model_and_hint.model) << std::endl;
321 }
322
323 // Optionally prints the problem.
324 if (absl::GetFlag(FLAGS_print_model)) {
325 std::cout << *model_and_hint.model;
326 std::cout.flush();
327 }
328
329 // Solve the problem.
330 SolveParameters solve_params = absl::GetFlag(FLAGS_solve_parameters);
331 solve_params.time_limit = absl::GetFlag(FLAGS_time_limit);
332 ModelSolveParameters model_params;
333 if (model_and_hint.hint.has_value()) {
334 model_params.solution_hints.push_back(*model_and_hint.hint);
335 std::cout << "Using the solution hint from the MPModelProto." << std::endl;
336 }
337 MessageCallback message_cb;
338 if (absl::GetFlag(FLAGS_solver_logs)) {
339 message_cb = PrinterMessageCallback(std::cout, "logs| ");
340 }
342 const SolveResult result,
343 LocalOrRemoteSolve(
344 *model_and_hint.model, absl::GetFlag(FLAGS_solver_type), solve_params,
345 model_params, std::move(message_cb), interrupter->get()),
346 _ << "the solver failed");
347
348 const FeasibilityCheckerOptions feasibility_checker_options = {
349 .absolute_constraint_tolerance =
350 absl::GetFlag(FLAGS_absolute_constraint_tolerance),
351 .integrality_tolerance = absl::GetFlag(FLAGS_integrality_tolerance),
352 .nonzero_tolerance = absl::GetFlag(FLAGS_nonzero_tolerance),
353 };
355 PrintSummary(*model_and_hint.model, result,
356 absl::GetFlag(FLAGS_check_solutions)
357 ? std::make_optional(feasibility_checker_options)
358 : std::nullopt));
359
360 return absl::OkStatus();
361}
362
363} // namespace
364} // namespace operations_research::math_opt
365
366int main(int argc, char* argv[]) {
367 InitGoogle(argv[0], &argc, &argv, /*remove_flags=*/true);
368
369 const absl::Status status = operations_research::math_opt::RunSolver();
370 // We don't use QCHECK_OK() here since the logged message contains more than
371 // the failing status.
372 if (!status.ok()) {
373 LOG(QFATAL) << status;
374 }
375
376 return 0;
377}
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
static absl::StatusOr< std::unique_ptr< Model > > FromModelProto(const ModelProto &model_proto)
Definition model.cc:53
int64_t value
* FileFormat
std::string AbslUnparseFlag(FileFormat f)
absl::Status status
Definition g_gurobi.cc:44
GRBmodel * model
void InitGoogle(const char *usage, int *argc, char ***argv, bool deprecated)
Definition init_google.h:34
time_limit
Definition solve.cc:22
int main(int argc, char *argv[])
ABSL_FLAG(std::string, input_file, "", "the file containing the model to solve; use --format to specify the " "file format")
std::optional< ModelSolveParameters::SolutionHint > hint
double solution
Options Defaults()
Definition file.h:109
absl::Status GetTextProto(absl::string_view filename, google::protobuf::Message *proto, Options options)
Definition file.cc:327
absl::Status GetBinaryProto(const absl::string_view filename, google::protobuf::Message *proto, Options options)
Definition file.cc:348
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28
std::optional< FileFormat > FormatFromFlagOrFilePath(const std::optional< FileFormat > format_flag_value, const absl::string_view file_path)
SolverType
The solvers supported by MathOpt.
Definition parameters.h:42
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
Definition solve.cc:62
std::optional< typename EnumProto< P >::Cpp > EnumFromProto(P proto_value)
Definition enums.h:281
std::function< void(const std::vector< std::string > &)> MessageCallback
ModelRanges ComputeModelRanges(const Model &model)
Returns the ranges of the finite non-zero values in the given model.
Definition statistics.cc:97
std::string OptionalFormatFlagPossibleValuesList()
MessageCallback PrinterMessageCallback(std::ostream &output_stream, const absl::string_view prefix)
absl::StatusOr< std::pair< ModelProto, std::optional< SolutionHintProto > > > ReadModel(const absl::string_view file_path, const FileFormat format)
void RemoveNames(ModelProto &model)
Removes the model, variables and constraints names of the provided model.
absl::StatusOr< ModelSubset > CheckPrimalSolutionFeasibility(const Model &model, const VariableMap< double > &variable_values, const FeasibilityCheckerOptions &options)
absl::string_view EnumToString(E value)
Definition enums.h:289
StatusBuilder InternalErrorBuilder()
static absl::StatusOr< SolutionHint > FromProto(const Model &model, const SolutionHintProto &hint_proto)
#define OR_ASSIGN_OR_RETURN3(lhs, rexpr, error_expression)