direct gurobi proto solve; support SOS in gurobi proto solve
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user