direct gurobi proto solve; support SOS in gurobi proto solve

This commit is contained in:
Laurent Perron
2019-07-22 11:10:30 -07:00
parent 04e1c9ecc0
commit 738d23fe4a
6 changed files with 151 additions and 29 deletions

View File

@@ -17,6 +17,7 @@
#include <limits>
#include <memory>
#include <numeric>
#include <string>
#include <vector>
@@ -63,6 +64,39 @@ util::Status CreateEnvironment(GRBenv** gurobi) {
}
return util::OkStatus();
}
int AddSosConstraint(const MPSosConstraint& sos_cst, GRBmodel* gurobi_model,
std::vector<int>* tmp_variables,
std::vector<double>* tmp_weights) {
CHECK(gurobi_model != nullptr);
CHECK(tmp_variables != nullptr);
CHECK(tmp_weights != nullptr);
tmp_variables->resize(sos_cst.var_index_size(), 0);
for (int v = 0; v < sos_cst.var_index_size(); ++v) {
(*tmp_variables)[v] = sos_cst.var_index(v);
}
tmp_weights->resize(sos_cst.var_index_size(), 0);
if (sos_cst.weight_size() == sos_cst.var_index_size()) {
for (int w = 0; w < sos_cst.weight_size(); ++w) {
(*tmp_weights)[w] = sos_cst.weight(w);
}
} else {
DCHECK_EQ(sos_cst.weight_size(), 0);
// Gurobi requires variable weights in their SOS constraints.
std::iota(tmp_weights->begin(), tmp_weights->end(), 1);
}
std::vector<int> types = {sos_cst.type() == MPSosConstraint::SOS1_DEFAULT
? GRB_SOS_TYPE1
: GRB_SOS_TYPE2};
std::vector<int> begins = {0};
return GRBaddsos(gurobi_model, /*numsos=*/1,
/*nummembers=*/sos_cst.var_index_size(),
/*types=*/types.data(),
/*beg=*/begins.data(), /*ind=*/tmp_variables->data(),
/*weight*/ tmp_weights->data());
}
} // namespace
util::StatusOr<MPSolutionResponse> GurobiSolveProto(
@@ -79,11 +113,6 @@ util::StatusOr<MPSolutionResponse> GurobiSolveProto(
return util::UnimplementedError(
"Solver-specific parameters not supported.");
}
if (model.general_constraint_size() > 0) {
// TODO(user): Move indicator constraint logic from linear_solver.cc to
// this file.
return util::UnimplementedError("General constraints not supported.");
}
if (model.has_solution_hint()) {
// TODO(user): Support solution hints.
return util::UnimplementedError("Solution hint not supported.");
@@ -169,6 +198,23 @@ util::StatusOr<MPSolutionResponse> GurobiSolveProto(
/*upper=*/constraint.upper_bound(),
/*constrname=*/constraint.name().c_str()));
}
for (const auto& gen_cst : model.general_constraint()) {
switch (gen_cst.general_constraint_case()) {
// TODO(user): Move indicator constraint logic from
// linear_solver.cc to this file.
case MPGeneralConstraintProto::kSosConstraint: {
RETURN_IF_GUROBI_ERROR(AddSosConstraint(gen_cst.sos_constraint(),
gurobi_model, &ct_variables,
&ct_coefficients));
break;
}
default:
return util::UnimplementedError(
absl::StrFormat("General constraints of type %i not supported.",
gen_cst.general_constraint_case()));
}
}
}
RETURN_IF_GUROBI_ERROR(GRBsetintattr(gurobi_model, GRB_INT_ATTR_MODELSENSE,
@@ -230,25 +276,23 @@ util::StatusOr<MPSolutionResponse> GurobiSolveProto(
RETURN_IF_GUROBI_ERROR(
GRBgetdblattr(gurobi_model, GRB_DBL_ATTR_OBJVAL, &objective_value));
response.set_objective_value(objective_value);
if (has_integer_variables) {
double best_objective_bound = 0;
const int error = GRBgetdblattr(gurobi_model, GRB_DBL_ATTR_OBJBOUND,
&best_objective_bound);
if (response.status() == MPSOLVER_OPTIMAL &&
error == GRB_ERROR_DATA_NOT_AVAILABLE) {
// If the presolve deletes all variables, there's no best bound.
response.set_best_objective_bound(objective_value);
} else {
RETURN_IF_GUROBI_ERROR(error);
response.set_best_objective_bound(best_objective_bound);
}
double best_objective_bound = 0;
const int error = GRBgetdblattr(gurobi_model, GRB_DBL_ATTR_OBJBOUND,
&best_objective_bound);
if (response.status() == MPSOLVER_OPTIMAL &&
error == GRB_ERROR_DATA_NOT_AVAILABLE) {
// If the presolve deletes all variables, there's no best bound.
response.set_best_objective_bound(objective_value);
} else {
RETURN_IF_GUROBI_ERROR(error);
response.set_best_objective_bound(best_objective_bound);
}
response.mutable_variable_value()->Resize(variable_size, 0);
RETURN_IF_GUROBI_ERROR(
GRBgetdblattrarray(gurobi_model, GRB_DBL_ATTR_X, 0, variable_size,
response.mutable_variable_value()->mutable_data()));
if (!has_integer_variables) {
if (!has_integer_variables && model.general_constraint_size() == 0) {
response.mutable_dual_value()->Resize(model.constraint_size(), 0);
RETURN_IF_GUROBI_ERROR(GRBgetdblattrarray(
gurobi_model, GRB_DBL_ATTR_PI, 0, model.constraint_size(),

View File

@@ -375,6 +375,7 @@ PROTO2_RETURN(
%unignore operations_research::MPSolver::AT_UPPER_BOUND; // no test
%unignore operations_research::MPSolver::FIXED_VALUE; // no test
%unignore operations_research::MPSolver::BASIC;
%unignore operations_research::MPSolver::SetStartingLpBasis;
// MPVariable: writer API.
%rename (setInteger) operations_research::MPVariable::SetInteger;

View File

@@ -15,6 +15,7 @@
#include <limits>
#include "absl/strings/str_join.h"
#include "ortools/base/logging.h"
#include "ortools/linear_solver/linear_solver.h"
@@ -79,6 +80,69 @@ double LinearExpr::SolutionValue() const {
return solution;
}
namespace {
void AppendTerm(const double coef, const std::string& var_name,
const bool is_first, std::string* s) {
if (is_first) {
if (coef == 1.0) {
absl::StrAppend(s, var_name);
} else if (coef == -1.0) {
absl::StrAppend(s, "-", var_name);
} else {
absl::StrAppend(s, coef, "*", var_name);
}
} else {
const std::string op = coef < 0 ? "-" : "+";
const double abs_coef = std::abs(coef);
if (abs_coef == 1.0) {
absl::StrAppend(s, " ", op, " ", var_name);
} else {
absl::StrAppend(s, " ", op, " ", abs_coef, "*", var_name);
}
}
}
void AppendOffset(const double offset, const bool is_first, std::string* s) {
if (is_first) {
absl::StrAppend(s, offset);
} else {
if (offset != 0.0) {
const std::string op = offset < 0 ? "-" : "+";
absl::StrAppend(s, " ", op, " ", std::abs(offset));
}
}
}
} // namespace
std::string LinearExpr::ToString() const {
std::vector<const MPVariable*> vars_in_order;
for (const auto& var_val_pair : terms_) {
vars_in_order.push_back(var_val_pair.first);
}
std::sort(vars_in_order.begin(), vars_in_order.end(),
[](const MPVariable* v, const MPVariable* u) {
return v->index() < u->index();
});
std::string result;
bool is_first = true;
for (const MPVariable* var : vars_in_order) {
// MPSolver gives names to all variables, even if you don't.
DCHECK(!var->name().empty());
AppendTerm(terms_.at(var), var->name(), is_first, &result);
is_first = false;
}
AppendOffset(offset_, is_first, &result);
// TODO(user): support optionally cropping long strings.
return result;
}
std::ostream& operator<<(std::ostream& stream, const LinearExpr& linear_expr) {
stream << linear_expr.ToString();
return stream;
}
LinearExpr operator+(LinearExpr lhs, const LinearExpr& rhs) {
lhs += rhs;
return lhs;

View File

@@ -75,6 +75,10 @@
* * \code LinearExpr e1 = LinearExpr(x) + (y + 5); \endcode
* * \code LinearExpr e1 = y + 5 + LinearExpr(x); \endcode
*/
#include <ostream>
#include <string>
#include "absl/container/flat_hash_map.h"
namespace operations_research {
@@ -147,11 +151,19 @@ class LinearExpr {
*/
double SolutionValue() const;
/**
* A human readable representation of this. Variables will be printed in order
* of lowest index first.
*/
std::string ToString() const;
private:
double offset_;
absl::flat_hash_map<const MPVariable*, double> terms_;
};
std::ostream& operator<<(std::ostream& stream, const LinearExpr& linear_expr);
// NOTE(user): in the ops below, the non-"const LinearExpr&" are intentional.
// We need to create a new LinearExpr for the result, so we lose nothing by
// passing one argument by value, mutating it, and then returning it. In

View File

@@ -474,31 +474,31 @@ const
#else
constexpr
#endif
NamedOptimizationProblemType kOptimizationProblemTypeNames[] = {
{MPSolver::GLOP_LINEAR_PROGRAMMING, "glop"},
NamedOptimizationProblemType kOptimizationProblemTypeNames[] = {
{MPSolver::GLOP_LINEAR_PROGRAMMING, "glop"},
#if defined(USE_GLPK)
{MPSolver::GLPK_LINEAR_PROGRAMMING, "glpk_lp"},
{MPSolver::GLPK_LINEAR_PROGRAMMING, "glpk_lp"},
#endif
#if defined(USE_CLP)
{MPSolver::CLP_LINEAR_PROGRAMMING, "clp"},
{MPSolver::CLP_LINEAR_PROGRAMMING, "clp"},
#endif
#if defined(USE_GUROBI)
{MPSolver::GUROBI_LINEAR_PROGRAMMING, "gurobi_lp"},
{MPSolver::GUROBI_LINEAR_PROGRAMMING, "gurobi_lp"},
#endif
#if defined(USE_SCIP)
{MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING, "scip"},
{MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING, "scip"},
#endif
#if defined(USE_CBC)
{MPSolver::CBC_MIXED_INTEGER_PROGRAMMING, "cbc"},
{MPSolver::CBC_MIXED_INTEGER_PROGRAMMING, "cbc"},
#endif
#if defined(USE_GLPK)
{MPSolver::GLPK_MIXED_INTEGER_PROGRAMMING, "glpk_mip"},
{MPSolver::GLPK_MIXED_INTEGER_PROGRAMMING, "glpk_mip"},
#endif
#if defined(USE_BOP)
{MPSolver::BOP_INTEGER_PROGRAMMING, "bop"},
{MPSolver::BOP_INTEGER_PROGRAMMING, "bop"},
#endif
#if defined(USE_GUROBI)
{MPSolver::GUROBI_MIXED_INTEGER_PROGRAMMING, "gurobi_mip"},
{MPSolver::GUROBI_MIXED_INTEGER_PROGRAMMING, "gurobi_mip"},
#endif
};

View File

@@ -150,7 +150,8 @@ message MPSosConstraint {
repeated int32 var_index = 2;
// Optional: SOS weights. If non-empty, must be of the same size as
// "var_index", and strictly increasing.
// "var_index", and strictly increasing. If empty and required by the
// underlying solver, the 1..n sequence will be given as weights.
// SUBTLE: The weights can help the solver make branch-and-bound decisions
// that fit the underlying optimization model: after each LP relaxation, it
// will compute the "average weight" of the SOS variables, weighted by value