change API or LP writer to return util::status

This commit is contained in:
Laurent Perron
2018-02-12 11:38:22 +01:00
parent 377571c954
commit 07a1078e77
5 changed files with 92 additions and 73 deletions

View File

@@ -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, &parameters_)) {
lp_solver_.SetParameters(parameters_);
return true;
}
return false;
#else
const bool ok = google::protobuf::TextFormat::MergeFromString(parameters, &parameters_);
lp_solver_.SetParameters(parameters_);
return ok;
#endif
}
void GLOPInterface::NonIncrementalChange() {

View File

@@ -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.
//

View File

@@ -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<int>(std::max(1.0, log10(static_cast<double>(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<ResultStatus>(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<MPSolverResponseStatus>(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 ----------

View File

@@ -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

View File

@@ -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<bool> 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<double>::infinity()) {
std::string rhs_name = name;
if (lb != -std::numeric_limits<double>::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<double>::infinity()) {
std::string lhs_name = name;
if (ub != +std::numeric_limits<double>::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<double>::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<double>::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;
}