From 07a1078e7797b597c9415de07b1975deac3926d9 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Mon, 12 Feb 2018 11:38:22 +0100 Subject: [PATCH] change API or LP writer to return util::status --- ortools/linear_solver/glop_interface.cc | 14 ++--- ortools/linear_solver/linear_expr.h | 4 +- ortools/linear_solver/linear_solver.cc | 57 ++++++++++++-------- ortools/linear_solver/linear_solver.h | 19 +++---- ortools/linear_solver/model_exporter.cc | 71 ++++++++++++++----------- 5 files changed, 92 insertions(+), 73 deletions(-) diff --git a/ortools/linear_solver/glop_interface.cc b/ortools/linear_solver/glop_interface.cc index 673e572cd9..bbbd6e6f1e 100644 --- a/ortools/linear_solver/glop_interface.cc +++ b/ortools/linear_solver/glop_interface.cc @@ -30,11 +30,9 @@ #include "ortools/linear_solver/linear_solver.h" #include "ortools/lp_data/lp_data.h" #include "ortools/lp_data/lp_types.h" +#include "ortools/port/proto_utils.h" #include "ortools/util/time_limit.h" -#ifndef __PORTABLE_PLATFORM__ -#include "google/protobuf/text_format.h" -#endif namespace operations_research { @@ -426,17 +424,15 @@ void GLOPInterface::SetLpAlgorithm(int value) { bool GLOPInterface::SetSolverSpecificParametersAsString( const std::string& parameters) { -#ifdef __PORTABLE_PLATFORM__ // NOTE(user): Android build uses protocol buffers in lite mode, and // parsing data from text format is not supported there. To allow solver // specific parameters from std::string on Android, we first need to switch to // non-lite version of protocol buffers. + if (ProtobufTextFormatMergeFromString(parameters, ¶meters_)) { + lp_solver_.SetParameters(parameters_); + return true; + } return false; -#else - const bool ok = google::protobuf::TextFormat::MergeFromString(parameters, ¶meters_); - lp_solver_.SetParameters(parameters_); - return ok; -#endif } void GLOPInterface::NonIncrementalChange() { diff --git a/ortools/linear_solver/linear_expr.h b/ortools/linear_solver/linear_expr.h index bc54bf13c5..fb47f8a3bd 100644 --- a/ortools/linear_solver/linear_expr.h +++ b/ortools/linear_solver/linear_expr.h @@ -37,7 +37,7 @@ // const LinearExpr e1 = x + y; // const LinearExpr e2 = (e1 + 7.0 + z)/3.0; // const LinearRange r = e1 <= e2; -// solver.AddRowConstraint(r); +// solver.MakeRowConstraint(r); // // WARNING, AVOID THIS TRAP: // @@ -87,7 +87,7 @@ class MPVariable; // // * Create a constraint in your optimization, e.g. // -// solver.AddRowConstraint(linear_expr1 <= linear_expr2); +// solver.MakeRowConstraint(linear_expr1 <= linear_expr2); // // * Get the value of the quantity after solving, e.g. // diff --git a/ortools/linear_solver/linear_solver.cc b/ortools/linear_solver/linear_solver.cc index 021d039388..f1441861b9 100644 --- a/ortools/linear_solver/linear_solver.cc +++ b/ortools/linear_solver/linear_solver.cc @@ -341,10 +341,6 @@ extern MPSolverInterface* BuildCplexInterface(bool mip, MPSolver* const solver); extern MPSolverInterface* BuildGLOPInterface(MPSolver* const solver); #endif -// TODO(user): use portable static initialization method instead. -#ifdef __PORTABLE_PLATFORM__ -extern MPSolverInterface* BuildGLOPInterface(MPSolver* const solver); -#endif namespace { MPSolverInterface* BuildSolverInterface(MPSolver* const solver) { @@ -406,6 +402,11 @@ int NumDigits(int n) { return static_cast(std::max(1.0, log10(static_cast(n)) + 1.0)); #endif } + +MPSolver::OptimizationProblemType DetourProblemType( + MPSolver::OptimizationProblemType problem_type) { + return problem_type; +} } // namespace MPSolver::MPSolver(const std::string& name, OptimizationProblemType problem_type) @@ -581,6 +582,11 @@ MPSolverResponseStatus MPSolver::LoadModelFromProtoInternal( for (int i = 0; i < input_model.constraint_size(); ++i) { const MPConstraintProto& ct_proto = input_model.constraint(i); + if (ct_proto.lower_bound() == -infinity() && + ct_proto.upper_bound() == infinity()) { + continue; + } + MPConstraint* const ct = MakeRowConstraint(ct_proto.lower_bound(), ct_proto.upper_bound(), clear_names ? empty : ct_proto.name()); @@ -746,21 +752,24 @@ void MPSolver::ExportModelToProto(MPModelProto* output_model) const { output_model->set_objective_offset(Objective().offset()); } -bool MPSolver::LoadSolutionFromProto(const MPSolutionResponse& response) { +util::Status MPSolver::LoadSolutionFromProto( + const MPSolutionResponse& response) { interface_->result_status_ = static_cast(response.status()); if (response.status() != MPSOLVER_OPTIMAL && response.status() != MPSOLVER_FEASIBLE) { - LOG(ERROR) - << "Cannot load a solution unless its status is OPTIMAL or FEASIBLE."; - return false; + return util::InvalidArgumentError(absl::StrCat( + "Cannot load a solution unless its status is OPTIMAL or FEASIBLE" + " (status was: ", + ProtoEnumToString(response.status()), ")")); } // Before touching the variables, verify that the solution looks legit: // each variable of the MPSolver must have its value listed exactly once, and // each listed solution should correspond to a known variable. if (response.variable_value_size() != variables_.size()) { - LOG(ERROR) << "Trying to load a solution whose number of variables does not" - << " correspond to the Solver."; - return false; + return util::InvalidArgumentError(absl::StrCat( + "Trying to load a solution whose number of variables (", + response.variable_value_size(), + ") does not correspond to the Solver's (", variables_.size(), ")")); } double largest_error = 0; interface_->ExtractModel(); @@ -782,11 +791,17 @@ bool MPSolver::LoadSolutionFromProto(const MPSolutionResponse& response) { } } if (num_vars_out_of_bounds > 0) { - LOG(WARNING) - << "Loaded a solution whose variables matched the solver's, but " - << num_vars_out_of_bounds << " out of " << variables_.size() - << " exceed one of their bounds by more than the primal tolerance: " - << tolerance; + return util::InvalidArgumentError(absl::StrCat( + "Loaded a solution whose variables matched the solver's, but ", + num_vars_out_of_bounds, " of ", variables_.size(), + " variables were out of their bounds, by more than the primal" + " tolerance which is: ", + tolerance, ". Max error: ", largest_error, ", last offendir var is #", + last_offending_var, ": '", variables_[last_offending_var]->name(), + "'")); + } + for (int i = 0; i < response.variable_value_size(); ++i) { + variables_[i]->set_solution_value(response.variable_value(i)); } // Set the objective value, if is known. // NOTE(user): We do not verify the objective, even though we could! @@ -796,7 +811,7 @@ bool MPSolver::LoadSolutionFromProto(const MPSolutionResponse& response) { // Mark the status as SOLUTION_SYNCHRONIZED, so that users may inspect the // solution normally. interface_->sync_status_ = MPSolverInterface::SOLUTION_SYNCHRONIZED; - return true; + return util::OkStatus(); } void MPSolver::Clear() { @@ -1235,19 +1250,19 @@ bool MPSolver::OwnsVariable(const MPVariable* var) const { return variables_[var_index] == var; } -bool MPSolver::ExportModelAsLpFormat(bool obfuscate, std::string* output) { +bool MPSolver::ExportModelAsLpFormat(bool obfuscate, std::string* model_str) const { MPModelProto proto; ExportModelToProto(&proto); MPModelProtoExporter exporter(proto); - return exporter.ExportModelAsLpFormat(obfuscate, output); + return exporter.ExportModelAsLpFormat(obfuscate, model_str); } bool MPSolver::ExportModelAsMpsFormat(bool fixed_format, bool obfuscate, - std::string* output) { + std::string* model_str) const { MPModelProto proto; ExportModelToProto(&proto); MPModelProtoExporter exporter(proto); - return exporter.ExportModelAsMpsFormat(fixed_format, obfuscate, output); + return exporter.ExportModelAsMpsFormat(fixed_format, obfuscate, model_str); } // ---------- MPSolverInterface ---------- diff --git a/ortools/linear_solver/linear_solver.h b/ortools/linear_solver/linear_solver.h index 4cc1c9a1ed..918f40a180 100644 --- a/ortools/linear_solver/linear_solver.h +++ b/ortools/linear_solver/linear_solver.h @@ -153,6 +153,7 @@ #include "ortools/linear_solver/linear_expr.h" #include "ortools/linear_solver/linear_solver.pb.h" #include "ortools/port/proto_utils.h" +#include "ortools/base/status.h" namespace operations_research { @@ -423,7 +424,7 @@ class MPSolver { // // This can be replaced by a stubby call to the linear solver server. // MPSolver::SolveWithProto(model_proto, &solver_response); // if (solver_response.result_status() == MPSolutionResponse::OPTIMAL) { - // CHECK(my_solver.LoadSolutionFromProto(solver_response)); + // CHECK_OK(my_solver.LoadSolutionFromProto(solver_response)); // ... inspect the solution using the usual API: solution_value(), etc... // } // @@ -432,23 +433,23 @@ class MPSolver { // See /.linear_solver_server_integration_test.py. // // The response must be in OPTIMAL or FEASIBLE status. - // Returns false if a problem arised (typically, if it wasn't used like - // it should be): + // Returns a non-OK status if a problem arised (typically, if it wasn't used + // like it should be): // - loading a solution whose variables don't correspond to the solver's // current variables // - loading a solution with a status other than OPTIMAL / FEASIBLE. - // Note: the variable and objective values aren't checked. You can use - // VerifySolution() for that. - bool LoadSolutionFromProto(const MPSolutionResponse& response); + // Note: the objective value isnn't checked. You can use VerifySolution() + // for that. + util::Status LoadSolutionFromProto(const MPSolutionResponse& response); // ----- Export model to files or strings ----- // Shortcuts to the homonymous MPModelProtoExporter methods, via // exporting to a MPModelProto with ExportModelToProto() (see above). // // Produces empty std::string on portable platforms (e.g. android, ios). - bool ExportModelAsLpFormat(bool obfuscated, std::string* model_str); - bool ExportModelAsMpsFormat(bool fixed_format, bool obfuscated, - std::string* model_str); + bool ExportModelAsLpFormat(bool obfuscate, std::string* model_str) const; + bool ExportModelAsMpsFormat(bool fixed_format, bool obfuscate, + std::string* model_str) const; // ----- Misc ----- // Advanced usage: pass solver specific parameters in text format. The format diff --git a/ortools/linear_solver/model_exporter.cc b/ortools/linear_solver/model_exporter.cc index cf134b52d4..342db34bc7 100644 --- a/ortools/linear_solver/model_exporter.cc +++ b/ortools/linear_solver/model_exporter.cc @@ -23,12 +23,13 @@ #include "ortools/base/stringprintf.h" #include "ortools/base/join.h" #include "ortools/base/strutil.h" +#include "ortools/base/join.h" #include "ortools/base/map_util.h" #include "ortools/linear_solver/linear_solver.pb.h" #include "ortools/util/fp_utils.h" DEFINE_bool(lp_shows_unused_variables, false, - "Decides wether variable unused in the objective and constraints" + "Decides whether variable unused in the objective and constraints" " are shown when exported to a file using the lp format."); DEFINE_int32(lp_max_line_length, 10000, @@ -204,6 +205,12 @@ void LineBreaker::Append(const std::string& s) { StrAppend(&output_, s); } +std::string DoubleToStringWithForcedSign(double d) { + return StrCat((d < 0 ? "" : "+"), absl::LegacyPrecision(d)); +} + +std::string DoubleToString(double d) { return StrCat(absl::LegacyPrecision(d)); } + } // namespace bool MPModelProtoExporter::WriteLpTerm(int var_index, double coefficient, @@ -214,8 +221,8 @@ bool MPModelProtoExporter::WriteLpTerm(int var_index, double coefficient, return false; } if (coefficient != 0.0) { - *output = StringPrintf("%+.16G %-s ", coefficient, - exported_variable_names_[var_index].c_str()); + *output = StrCat(DoubleToStringWithForcedSign(coefficient), " ", + exported_variable_names_[var_index], " "); } return true; } @@ -260,8 +267,8 @@ bool MPModelProtoExporter::ExportModelAsLpFormat(bool obfuscated, LineBreaker obj_line_breaker(FLAGS_lp_max_line_length); obj_line_breaker.Append(" Obj: "); if (proto_.objective_offset() != 0.0) { - obj_line_breaker.Append(StringPrintf("%-+.16G Constant ", - proto_.objective_offset())); + obj_line_breaker.Append(StrCat( + DoubleToStringWithForcedSign(proto_.objective_offset()), " Constant ")); } std::vector show_variable(proto_.variable_size(), FLAGS_lp_shows_unused_variables); @@ -298,16 +305,16 @@ bool MPModelProtoExporter::ExportModelAsLpFormat(bool obfuscated, const double lb = ct_proto.lower_bound(); const double ub = ct_proto.upper_bound(); if (lb == ub) { - line_breaker.Append(StringPrintf(" = %-.16G\n", ub)); + line_breaker.Append(StrCat(" = ", DoubleToString(ub), "\n")); StrAppend(output, " ", name, ": ", line_breaker.GetOutput()); } else { if (ub != +std::numeric_limits::infinity()) { std::string rhs_name = name; if (lb != -std::numeric_limits::infinity()) { - rhs_name += "_rhs"; + StrAppend(&rhs_name, "_rhs"); } StrAppend(output, " ", rhs_name, ": ", line_breaker.GetOutput()); - const std::string relation = StringPrintf(" <= %-.16G\n", ub); + const std::string relation = StrCat(" <= ", DoubleToString(ub), "\n"); // Here we have to make sure we do not add the relation to the contents // of line_breaker, which may be used in the subsequent clause. if (!line_breaker.WillFit(relation)) StrAppend(output, "\n "); @@ -316,10 +323,10 @@ bool MPModelProtoExporter::ExportModelAsLpFormat(bool obfuscated, if (lb != -std::numeric_limits::infinity()) { std::string lhs_name = name; if (ub != +std::numeric_limits::infinity()) { - lhs_name += "_lhs"; + StrAppend(&lhs_name, "_lhs"); } StrAppend(output, " ", lhs_name, ": ", line_breaker.GetOutput()); - const std::string relation = StringPrintf(" >= %-.16G\n", lb); + const std::string relation = StrCat(" >= ", DoubleToString(lb), "\n"); if (!line_breaker.WillFit(relation)) StrAppend(output, "\n "); StrAppend(output, relation); } @@ -327,9 +334,9 @@ bool MPModelProtoExporter::ExportModelAsLpFormat(bool obfuscated, } // Bounds - output->append("Bounds\n"); + StrAppend(output, "Bounds\n"); if (proto_.objective_offset() != 0.0) { - output->append(" 1 <= Constant <= 1\n"); + StrAppend(output, " 1 <= Constant <= 1\n"); } for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) { if (!show_variable[var_index]) continue; @@ -341,19 +348,19 @@ bool MPModelProtoExporter::ExportModelAsLpFormat(bool obfuscated, exported_variable_names_[var_index].c_str(), ub); } else { if (lb != -std::numeric_limits::infinity()) { - StringAppendF(output, " %-.16G <= ", lb); + StrAppend(output, " ", DoubleToString(lb), " <= "); } - output->append(exported_variable_names_[var_index]); + StrAppend(output, exported_variable_names_[var_index]); if (ub != std::numeric_limits::infinity()) { - StringAppendF(output, " <= %-.16G", ub); + StrAppend(output, " <= ", DoubleToString(ub)); } - output->append("\n"); + StrAppend(output, "\n"); } } // Binaries if (num_binary_variables_ > 0) { - output->append("Binaries\n"); + StrAppend(output, "Binaries\n"); for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) { if (!show_variable[var_index]) continue; const MPVariableProto& var_proto = proto_.variable(var_index); @@ -366,17 +373,16 @@ bool MPModelProtoExporter::ExportModelAsLpFormat(bool obfuscated, // Generals if (num_integer_variables_ > 0) { - output->append("Generals\n"); + StrAppend(output, "Generals\n"); for (int var_index = 0; var_index < proto_.variable_size(); ++var_index) { if (!show_variable[var_index]) continue; const MPVariableProto& var_proto = proto_.variable(var_index); if (var_proto.is_integer() && !IsBoolean(var_proto)) { - StringAppendF(output, " %s\n", - exported_variable_names_[var_index].c_str()); + StrAppend(output, " ", exported_variable_names_[var_index], "\n"); } } } - output->append("End\n"); + StrAppend(output, "End\n"); return true; } @@ -389,12 +395,13 @@ void MPModelProtoExporter::AppendMpsPair(const std::string& name, double value, // Use the largest precision that can fit into the field witdh. while (value_str.size() > kFixedMpsDoubleWidth) { --precision; - value_str = StringPrintf("%.*G", precision, value); + value_str = StringPrintf("%.*g", precision, value); } StringAppendF(output, " %-8s %*s ", name.c_str(), kFixedMpsDoubleWidth, value_str.c_str()); } else { - StringAppendF(output, " %-16s %21.16G ", name.c_str(), value); + StringAppendF(output, " %-16s %21s ", name.c_str(), + DoubleToString(value).c_str()); } } @@ -408,7 +415,7 @@ void MPModelProtoExporter::AppendMpsLineHeader(const std::string& id, void MPModelProtoExporter::AppendMpsLineHeaderWithNewLine( const std::string& id, const std::string& name, std::string* output) const { AppendMpsLineHeader(id, name, output); - *output += "\n"; + StrAppend(output, "\n"); } void MPModelProtoExporter::AppendMpsTermWithContext(const std::string& head_name, @@ -427,13 +434,13 @@ void MPModelProtoExporter::AppendMpsBound(const std::string& bound_type, std::string* output) const { AppendMpsLineHeader(bound_type, "BOUND", output); AppendMpsPair(name, value, output); - *output += "\n"; + StrAppend(output, "\n"); } void MPModelProtoExporter::AppendNewLineIfTwoColumns(std::string* output) { ++current_mps_column_; if (current_mps_column_ == 2) { - *output += "\n"; + StrAppend(output, "\n"); current_mps_column_ = 0; } } @@ -504,7 +511,7 @@ bool MPModelProtoExporter::ExportModelAsMpsFormat(bool fixed_format, } } if (!rows_section.empty()) { - *output += "ROWS\n" + rows_section; + StrAppend(output, "ROWS\n", rows_section); } // As the information regarding a column needs to be contiguous, we create @@ -541,7 +548,7 @@ bool MPModelProtoExporter::ExportModelAsMpsFormat(bool fixed_format, } AppendMpsColumns(/*integrality=*/false, transpose, &columns_section); if (!columns_section.empty()) { - *output += "COLUMNS\n" + columns_section; + StrAppend(output, "COLUMNS\n", columns_section); } // RHS (right-hand-side) section. @@ -560,7 +567,7 @@ bool MPModelProtoExporter::ExportModelAsMpsFormat(bool fixed_format, } AppendNewLineIfTwoColumns(&rhs_section); if (!rhs_section.empty()) { - *output += "RHS\n" + rhs_section; + StrAppend(output, "RHS\n", rhs_section); } // RANGES section. @@ -576,7 +583,7 @@ bool MPModelProtoExporter::ExportModelAsMpsFormat(bool fixed_format, } AppendNewLineIfTwoColumns(&ranges_section); if (!ranges_section.empty()) { - *output += "RANGES\n" + ranges_section; + StrAppend(output, "RANGES\n", ranges_section); } // BOUNDS section. @@ -620,10 +627,10 @@ bool MPModelProtoExporter::ExportModelAsMpsFormat(bool fixed_format, } } if (!bounds_section.empty()) { - *output += "BOUNDS\n" + bounds_section; + StrAppend(output, "BOUNDS\n", bounds_section); } - *output += "ENDATA\n"; + StrAppend(output, "ENDATA\n"); return true; }