more constraints in linear solver

This commit is contained in:
Laurent Perron
2019-08-09 11:53:35 -07:00
parent a39b544fde
commit 6e5b4a9320
5 changed files with 256 additions and 27 deletions

View File

@@ -122,6 +122,67 @@ int AddQuadraticConstraint(const MPGeneralConstraintProto& gen_cst,
return GRB_OK;
}
int AddAndConstraint(const MPGeneralConstraintProto& gen_cst,
GRBmodel* gurobi_model, std::vector<int>* tmp_variables) {
CHECK(gurobi_model != nullptr);
CHECK(tmp_variables != nullptr);
auto and_cst = gen_cst.and_constraint();
return GRBaddgenconstrAnd(
gurobi_model,
/*name=*/gen_cst.name().c_str(),
/*resvar=*/and_cst.resultant_var_index(),
/*nvars=*/and_cst.var_index_size(),
/*vars=*/and_cst.mutable_var_index()->mutable_data());
}
int AddOrConstraint(const MPGeneralConstraintProto& gen_cst,
GRBmodel* gurobi_model, std::vector<int>* tmp_variables) {
CHECK(gurobi_model != nullptr);
CHECK(tmp_variables != nullptr);
auto or_cst = gen_cst.or_constraint();
return GRBaddgenconstrOr(gurobi_model,
/*name=*/gen_cst.name().c_str(),
/*resvar=*/or_cst.resultant_var_index(),
/*nvars=*/or_cst.var_index_size(),
/*vars=*/or_cst.mutable_var_index()->mutable_data());
}
int AddMinConstraint(const MPGeneralConstraintProto& gen_cst,
GRBmodel* gurobi_model, std::vector<int>* tmp_variables) {
CHECK(gurobi_model != nullptr);
CHECK(tmp_variables != nullptr);
auto min_cst = gen_cst.min_constraint();
return GRBaddgenconstrMin(
gurobi_model,
/*name=*/gen_cst.name().c_str(),
/*resvar=*/min_cst.resultant_var_index(),
/*nvars=*/min_cst.var_index_size(),
/*vars=*/min_cst.mutable_var_index()->mutable_data(),
/*constant=*/min_cst.has_constant()
? min_cst.constant()
: std::numeric_limits<double>::infinity());
}
int AddMaxConstraint(const MPGeneralConstraintProto& gen_cst,
GRBmodel* gurobi_model, std::vector<int>* tmp_variables) {
CHECK(gurobi_model != nullptr);
CHECK(tmp_variables != nullptr);
auto max_cst = gen_cst.max_constraint();
return GRBaddgenconstrMax(
gurobi_model,
/*name=*/gen_cst.name().c_str(),
/*resvar=*/max_cst.resultant_var_index(),
/*nvars=*/max_cst.var_index_size(),
/*vars=*/max_cst.mutable_var_index()->mutable_data(),
/*constant=*/max_cst.has_constant()
? max_cst.constant()
: -std::numeric_limits<double>::infinity());
}
} // namespace
util::StatusOr<MPSolutionResponse> GurobiSolveProto(
@@ -238,6 +299,34 @@ util::StatusOr<MPSolutionResponse> GurobiSolveProto(
RETURN_IF_GUROBI_ERROR(AddQuadraticConstraint(gen_cst, gurobi_model));
break;
}
case MPGeneralConstraintProto::kAbsConstraint: {
RETURN_IF_GUROBI_ERROR(GRBaddgenconstrAbs(
gurobi_model,
/*name=*/gen_cst.name().c_str(),
/*resvar=*/gen_cst.abs_constraint().resultant_var_index(),
/*argvar=*/gen_cst.abs_constraint().var_index()));
break;
}
case MPGeneralConstraintProto::kAndConstraint: {
RETURN_IF_GUROBI_ERROR(
AddAndConstraint(gen_cst, gurobi_model, &ct_variables));
break;
}
case MPGeneralConstraintProto::kOrConstraint: {
RETURN_IF_GUROBI_ERROR(
AddOrConstraint(gen_cst, gurobi_model, &ct_variables));
break;
}
case MPGeneralConstraintProto::kMinConstraint: {
RETURN_IF_GUROBI_ERROR(
AddMinConstraint(gen_cst, gurobi_model, &ct_variables));
break;
}
case MPGeneralConstraintProto::kMaxConstraint: {
RETURN_IF_GUROBI_ERROR(
AddMaxConstraint(gen_cst, gurobi_model, &ct_variables));
break;
}
default:
return util::UnimplementedError(
absl::StrFormat("General constraints of type %i not supported.",

View File

@@ -695,9 +695,11 @@ MPSolverResponseStatus MPSolver::LoadModelFromProtoInternal(
break;
}
default:
*error_message =
absl::StrCat("Solver doesn't support general constraints of type ",
general_constraint.general_constraint_case());
*error_message = absl::StrFormat(
"Optimizing general constraints of type %i is only supported "
"through direct proto solves. Please use MPSolver::SolveWithProto, "
"or the solver's direct proto solve function.",
general_constraint.general_constraint_case());
return MPSOLVER_MODEL_INVALID;
}
}

View File

@@ -111,8 +111,16 @@ message MPGeneralConstraintProto {
MPSosConstraint sos_constraint = 3;
MPQuadraticConstraint quadratic_constraint = 4;
MPAbsConstraint abs_constraint = 5;
MPAndConstraint and_constraint = 6;
MPOrConstraint or_constraint = 7;
// All variables in "and" constraints must be Boolean.
// resultant_var = and(var_1, var_2... var_n)
MPArrayConstraint and_constraint = 6;
// All variables in "or" constraints must be Boolean.
// resultant_var = or(var_1, var_2... var_n)
MPArrayConstraint or_constraint = 7;
// resultant_var = min(var_1, var_2, ..., constant)
MPArrayWithConstantConstraint min_constraint = 8;
// resultant_var = max(var_1, var_2, ..., constant)
MPArrayWithConstantConstraint max_constraint = 9;
}
}
@@ -142,7 +150,7 @@ message MPSosConstraint {
enum Type {
// At most one variable in `var_index` must be non-zero.
SOS1_DEFAULT = 0;
// At most two consecutive variables from `var_index` must be non-zero (i.e.
// At most two consecutive variables from `var_index` can be non-zero (i.e.
// for some i, var_index[i] and var_index[i+1]). See
// http://www.eudoxus.com/lp-training/5/5-6-special-ordered-sets-of-type-2
SOS2 = 1;
@@ -205,22 +213,21 @@ message MPAbsConstraint {
optional int32 resultant_var_index = 2;
}
// Sets a binary variable's value equal to one if and only if all variables in a
// set of binary variables are true.
message MPAndConstraint {
// Sets a variable's value equal to a function on a set of variables.
message MPArrayConstraint {
// Variable indices are relative to the "variable" field in MPModelProto.
// resultant_var = and(var_1, var_2... var_n)
repeated int32 var_index = 1;
optional int32 resultant_var_index = 2;
}
// Sets a binary variable's value equal to one if and only if at least one
// variable in a set of binary variables is true.
message MPOrConstraint {
// Sets a variable's value equal to a function on a set of variables and,
// optionally, a constant.
message MPArrayWithConstantConstraint {
// Variable indices are relative to the "variable" field in MPModelProto.
// resultant_var = or(var_1, var_2... var_n)
// resultant_var = f(var_1, var_2, ..., constant)
repeated int32 var_index = 1;
optional int32 resultant_var_index = 2;
optional double constant = 2;
optional int32 resultant_var_index = 3;
}
// Quadratic part of a model's objective. Added with other objectives (such as

View File

@@ -263,9 +263,8 @@ std::string FindErrorInMPAbsConstraint(const MPModelProto& model,
return "";
}
template <typename MPAndOrConstraint>
std::string FindErrorInMPAndOrConstraint(const MPModelProto& model,
const MPAndOrConstraint& and_or) {
const MPArrayConstraint& and_or) {
if (and_or.var_index_size() == 0) {
return "var_index cannot be empty.";
}
@@ -294,6 +293,34 @@ std::string FindErrorInMPAndOrConstraint(const MPModelProto& model,
return "";
}
std::string FindErrorInMPMinMaxConstraint(
const MPModelProto& model, const MPArrayWithConstantConstraint& min_max) {
if (min_max.var_index_size() == 0) {
return "var_index cannot be empty.";
}
if (!min_max.has_resultant_var_index()) {
return "resultant_var_index is required.";
}
if (!std::isfinite(min_max.constant())) {
return absl::StrCat("Invalid constant: ", (min_max.constant()));
}
const int num_vars = model.variable_size();
for (int i = 0; i < min_max.var_index_size(); ++i) {
if (min_max.var_index(i) < 0 || min_max.var_index(i) >= num_vars) {
return absl::StrCat("var_index(", i, ")=", min_max.var_index(i),
" is invalid.", " It must be in [0, ", num_vars, ")");
}
}
if (min_max.resultant_var_index() < 0 ||
min_max.resultant_var_index() >= num_vars) {
return absl::StrCat("resultant_var_index=", min_max.resultant_var_index(),
" is invalid.", " It must be in [0, ", num_vars, ")");
}
return "";
}
std::string FindErrorInQuadraticObjective(const MPQuadraticObjective& qobj,
int num_vars) {
if (qobj.qvar1_index_size() != qobj.qvar2_index_size() ||
@@ -417,6 +444,15 @@ std::string FindErrorInMPModelProto(const MPModelProto& model) {
FindErrorInMPAndOrConstraint(model, gen_constraint.or_constraint());
break;
case MPGeneralConstraintProto::kMinConstraint:
error = FindErrorInMPMinMaxConstraint(model,
gen_constraint.min_constraint());
break;
case MPGeneralConstraintProto::kMaxConstraint:
error = FindErrorInMPMinMaxConstraint(model,
gen_constraint.max_constraint());
break;
default:
return absl::StrCat("Unknown general constraint type ",
gen_constraint.general_constraint_case());

View File

@@ -332,10 +332,9 @@ util::Status AddQuadraticConstraint(
// y = x OR y = -x
util::Status AddAbsConstraint(const MPGeneralConstraintProto& gen_cst,
const std::vector<SCIP_VAR*>& scip_variables,
SCIP* scip,
std::vector<SCIP_CONS*>* scip_constraints) {
SCIP* scip, SCIP_CONS** scip_cst) {
CHECK(scip != nullptr);
CHECK(scip_constraints != nullptr);
CHECK(scip_cst != nullptr);
CHECK(gen_cst.has_abs_constraint());
const auto& abs = gen_cst.abs_constraint();
SCIP_VAR* scip_var = scip_variables[abs.var_index()];
@@ -351,15 +350,16 @@ util::Status AddAbsConstraint(const MPGeneralConstraintProto& gen_cst,
std::vector<SCIP_CONS*> cons;
auto add_abs_constraint =
[&](const std::string& name_prefix) -> util::Status {
scip_constraints->push_back(nullptr);
SCIP_CONS* scip_cons;
const std::string name =
gen_cst.has_name() ? absl::StrCat(gen_cst.name(), name_prefix) : "";
RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicLinear(
scip, /*cons=*/&scip_constraints->back(),
scip, /*cons=*/&scip_cons,
/*name=*/name.c_str(), /*nvars=*/2, /*vars=*/vars.data(),
/*vals=*/vals.data(), /*lhs=*/0.0, /*rhs=*/0.0));
// Note that the constraints are, by design, not added into the model using
// SCIPaddCons.
cons.push_back(scip_cons);
return util::OkStatus();
};
@@ -367,20 +367,18 @@ util::Status AddAbsConstraint(const MPGeneralConstraintProto& gen_cst,
vars = {scip_resultant_var, scip_var};
vals = {1, 1};
RETURN_IF_ERROR(add_abs_constraint("_neg"));
cons.push_back(scip_constraints->back());
// Create an intermediary constraint such that y = x
vals = {1, -1};
RETURN_IF_ERROR(add_abs_constraint("_pos"));
cons.push_back(scip_constraints->back());
// Activate at least one of the two above constraints.
const std::string name =
gen_cst.has_name() ? absl::StrCat(gen_cst.name(), "_disj") : "";
RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicDisjunction(
scip, /*cons=*/&scip_constraints->back(), /*name=*/name.c_str(),
scip, /*cons=*/scip_cst, /*name=*/name.c_str(),
/*nconss=*/2, /*conss=*/cons.data(), /*relaxcons=*/nullptr));
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, scip_constraints->back()));
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
return util::OkStatus();
}
@@ -433,6 +431,96 @@ util::Status AddOrConstraint(const MPGeneralConstraintProto& gen_cst,
return util::OkStatus();
}
// Models the constraint y = min(x1, x2, ... xn, c) with c being a constant with
// - n + 1 constraints to ensure y <= min(x1, x2, ... xn, c)
// - one disjunction constraint among all of the possible y = x1, y = x2, ...
// y = xn, y = c constraints
// Does the equivalent thing for max (with y >= max(...) instead).
util::Status AddMinMaxConstraint(const MPGeneralConstraintProto& gen_cst,
const std::vector<SCIP_VAR*>& scip_variables,
SCIP* scip, SCIP_CONS** scip_cst,
std::vector<SCIP_CONS*>* scip_constraints,
std::vector<SCIP_VAR*>* tmp_variables) {
CHECK(scip != nullptr);
CHECK(scip_cst != nullptr);
CHECK(tmp_variables != nullptr);
CHECK(gen_cst.has_min_constraint() || gen_cst.has_max_constraint());
const auto& minmax = gen_cst.has_min_constraint() ? gen_cst.min_constraint()
: gen_cst.max_constraint();
SCIP_VAR* scip_resultant_var = scip_variables[minmax.resultant_var_index()];
std::vector<SCIP_VAR*> vars;
std::vector<double> vals;
std::vector<SCIP_CONS*> cons;
auto add_lin_constraint = [&](const std::string& name_prefix,
double lower_bound = 0.0,
double upper_bound = 0.0) -> util::Status {
SCIP_CONS* scip_cons;
const std::string name =
gen_cst.has_name() ? absl::StrCat(gen_cst.name(), name_prefix) : "";
RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicLinear(
scip, /*cons=*/&scip_cons,
/*name=*/name.c_str(), /*nvars=*/2, /*vars=*/vars.data(),
/*vals=*/vals.data(), /*lhs=*/lower_bound, /*rhs=*/upper_bound));
// Note that the constraints are, by design, not added into the model using
// SCIPaddCons.
cons.push_back(scip_cons);
return util::OkStatus();
};
// Create intermediary constraints such that y = xi
for (const int var_index : minmax.var_index()) {
vars = {scip_resultant_var, scip_variables[var_index]};
vals = {1, -1};
RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_", var_index)));
}
// Create an intermediary constraint such that y = c
if (minmax.has_constant()) {
vars = {scip_resultant_var};
vals = {1};
RETURN_IF_ERROR(
add_lin_constraint("_constant", minmax.constant(), minmax.constant()));
}
// Activate at least one of the above constraints.
const std::string name =
gen_cst.has_name() ? absl::StrCat(gen_cst.name(), "_disj") : "";
RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicDisjunction(
scip, /*cons=*/scip_cst, /*name=*/name.c_str(),
/*nconss=*/2, /*conss=*/cons.data(), /*relaxcons=*/nullptr));
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
// Add all of the inequality constraints.
constexpr double kInfinity = std::numeric_limits<double>::infinity();
cons.clear();
for (const int var_index : minmax.var_index()) {
vars = {scip_resultant_var, scip_variables[var_index]};
vals = {1, -1};
if (gen_cst.has_min_constraint()) {
RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_", var_index),
-kInfinity, 0.0));
} else {
RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_", var_index), 0.0,
kInfinity));
}
}
vars = {scip_resultant_var};
vals = {1};
if (gen_cst.has_min_constraint()) {
RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_constant"),
-kInfinity, minmax.constant()));
} else {
RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_constant"),
minmax.constant(), kInfinity));
}
for (SCIP_CONS* scip_cons : cons) {
scip_constraints->push_back(scip_cons);
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, scip_cons));
}
return util::OkStatus();
}
util::Status AddQuadraticObjective(const MPQuadraticObjective& quadobj,
SCIP* scip,
std::vector<SCIP_VAR*>* scip_variables,
@@ -689,7 +777,7 @@ util::StatusOr<MPSolutionResponse> ScipSolveProto(
}
case MPGeneralConstraintProto::kAbsConstraint: {
RETURN_IF_ERROR(AddAbsConstraint(gen_cst, scip_variables, scip,
&scip_constraints));
&scip_constraints[lincst_size + c]));
break;
}
case MPGeneralConstraintProto::kAndConstraint: {
@@ -704,6 +792,13 @@ util::StatusOr<MPSolutionResponse> ScipSolveProto(
&ct_variables));
break;
}
case MPGeneralConstraintProto::kMinConstraint:
case MPGeneralConstraintProto::kMaxConstraint: {
RETURN_IF_ERROR(AddMinMaxConstraint(
gen_cst, scip_variables, scip, &scip_constraints[lincst_size + c],
&scip_constraints, &ct_variables));
break;
}
default:
return util::UnimplementedError(
absl::StrFormat("General constraints of type %i not supported.",