change API or LP writer to return util::status
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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 ----------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user