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"
62struct SolverTypeProtoFormatter {
64 std::string*
const out,
65 const operations_research::math_opt::SolverTypeProto solver_type) {
73 "the file containing the model to solve; use --format to specify the "
76 std::optional<operations_research::math_opt::FileFormat>, format,
79 "the format of the --input_file; possible values:",
82 std::vector<std::string>, update_files, {},
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),
90 operations_research::math_opt::FileFormat::kMathOptText)));
95 "the solver to use, possible values: ",
98 ->RegisteredSolvers(),
99 ", ", SolverTypeProtoFormatter())));
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.");
107 "use a message callback to print the solver convergence logs");
109 "the time limit to use for the solve");
111 "interrupts the solve on the first SIGINT; kill the process on the "
115 "use the names in the input models; ignoring names is useful when "
116 "the input contains duplicates");
118 "prints statistics about the ranges of the model values");
119ABSL_FLAG(
bool, print_model,
false,
"prints the model to stdout");
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");
129 "feasibility tolerance for constraints and variables bounds");
133 "feasibility tolerance for variables' integrality");
135 double, nonzero_tolerance,
138 "tolerance for checking if a value is nonzero (e.g., in SOS constraints)");
145absl::StatusOr<ModelUpdateProto> ReadModelUpdate(
146 const absl::string_view file_path,
const FileFormat format) {
148 case FileFormat::kMathOptBinary:
151 case FileFormat::kMathOptText:
160 std::optional<ModelSolveParameters::SolutionHint>
hint;
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.";
171 const std::optional<FileFormat> format =
173 if (format.has_value()) {
176 LOG(QFATAL) <<
"Can't guess the format from the file extension, please "
177 "use --format to specify the file format explicitly.";
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
192 _ <<
"failed to read " << input_file_path);
194 std::vector<ModelUpdateProto> model_updates;
195 for (
const std::string& update_file_path : update_file_paths) {
197 ReadModelUpdate(update_file_path, format));
198 model_updates.emplace_back(std::move(update));
201 if (!absl::GetFlag(FLAGS_names)) {
203 for (ModelUpdateProto& update : model_updates) {
211 for (
int u = 0; u < model_updates.size(); ++u) {
212 const ModelUpdateProto& update = model_updates[u];
214 <<
"failed to apply the update file: " << update_file_paths[u];
216 if (absl::GetFlag(FLAGS_lp_relaxation)) {
217 for (
const Variable v :
model->Variables()) {
218 model->set_continuous(v);
221 ModelAndHint result = {.model = std::move(
model)};
222 if (optional_hint.has_value()) {
225 *result.model, optional_hint.value()),
226 _ <<
"invalid solution hint");
227 result.hint = std::move(
hint);
229 return std::move(result);
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;
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,
259 *feasibility_check_tolerances),
260 _ <<
"failed to check the primal solution feasibility of solution #"
262 if (!broken_constraints.empty()) {
263 std::cout <<
" (numerically infeasible: " << broken_constraints
266 std::cout <<
" (numerically feasible)";
272 std::cout << std::endl;
275 return absl::OkStatus();
278absl::StatusOr<SolveResult> LocalOrRemoteSolve(
280 const SolveParameters& params,
const ModelSolveParameters& model_params,
282 if (absl::GetFlag(FLAGS_remote)) {
283 return absl::UnimplementedError(
"remote not yet supported.");
286 {.parameters = params,
287 .model_parameters = model_params,
288 .message_callback = std::move(msg_cb),
289 .interrupter = interrupter});
293absl::Status RunSolver() {
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)) {
304 std::make_unique<operations_research::SolveInterrupter>();
305 sigint_handler->Register(
306 [interrupter = interrupter.get()]() { interrupter->Interrupt(); });
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. "
318 if (absl::GetFlag(FLAGS_ranges)) {
319 std::cout <<
"Ranges of finite non-zero values in the model:\n"
324 if (absl::GetFlag(FLAGS_print_model)) {
325 std::cout << *model_and_hint.model;
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;
338 if (absl::GetFlag(FLAGS_solver_logs)) {
342 const SolveResult result,
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");
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),
355 PrintSummary(*model_and_hint.model, result,
356 absl::GetFlag(FLAGS_check_solutions)
357 ? std::make_optional(feasibility_checker_options)
360 return absl::OkStatus();
366int main(
int argc,
char* argv[]) {
369 const absl::Status
status = operations_research::math_opt::RunSolver();
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
static AllSolversRegistry * Instance()
static absl::StatusOr< std::unique_ptr< Model > > FromModelProto(const ModelProto &model_proto)
void InitGoogle(const char *usage, int *argc, char ***argv, bool deprecated)
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
absl::Status GetTextProto(absl::string_view filename, google::protobuf::Message *proto, Options options)
absl::Status GetBinaryProto(const absl::string_view filename, google::protobuf::Message *proto, Options options)
An object oriented wrapper for quadratic constraints in ModelStorage.
std::optional< FileFormat > FormatFromFlagOrFilePath(const std::optional< FileFormat > format_flag_value, const absl::string_view file_path)
SolverType
The solvers supported by MathOpt.
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
std::optional< typename EnumProto< P >::Cpp > EnumFromProto(P proto_value)
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.
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)
StatusBuilder InternalErrorBuilder()
double absolute_constraint_tolerance
double integrality_tolerance
static absl::StatusOr< SolutionHint > FromProto(const Model &model, const SolutionHintProto &hint_proto)
#define OR_ASSIGN_OR_RETURN3(lhs, rexpr, error_expression)