Files
ortools-clone/ortools/linear_solver/scip_proto_solver.cc

780 lines
30 KiB
C++

// Copyright 2010-2018 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#if defined(USE_SCIP)
#include "ortools/linear_solver/scip_proto_solver.h"
#include <cmath>
#include <limits>
#include <memory>
#include <numeric>
#include <string>
#include <vector>
#include "absl/strings/ascii.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#include "ortools/base/canonical_errors.h"
#include "ortools/base/cleanup.h"
#include "ortools/base/status.h"
#include "ortools/base/status_macros.h"
#include "ortools/linear_solver/linear_solver.pb.h"
#include "ortools/linear_solver/model_validator.h"
#include "ortools/linear_solver/scip_helper_macros.h"
#include "scip/cons_disjunction.h"
#include "scip/cons_linear.h"
#include "scip/pub_var.h"
#include "scip/scip.h"
#include "scip/scip_param.h"
#include "scip/scip_prob.h"
#include "scip/scip_var.h"
#include "scip/scipdefplugins.h"
#include "scip/set.h"
#include "scip/struct_paramset.h"
#include "scip/type_cons.h"
#include "scip/type_paramset.h"
#include "scip/type_var.h"
namespace operations_research {
util::Status ScipSetSolverSpecificParameters(const std::string& parameters,
SCIP* scip) {
for (const auto parameter :
absl::StrSplit(parameters, '\n', absl::SkipWhitespace())) {
std::vector<std::string> key_value =
absl::StrSplit(parameter, '=', absl::SkipWhitespace());
if (key_value.size() != 2) {
return util::InvalidArgumentError(
absl::StrFormat("Cannot parse parameter '%s'. Expected format is "
"'parameter/name = value'",
parameter));
}
std::string name = key_value[0];
absl::RemoveExtraAsciiWhitespace(&name);
std::string value = key_value[1];
absl::RemoveExtraAsciiWhitespace(&value);
SCIP_PARAM* param = SCIPgetParam(scip, name.c_str());
if (param == nullptr) {
return util::InvalidArgumentError(
absl::StrFormat("Invalid parameter name '%s'", name));
}
switch (param->paramtype) {
case SCIP_PARAMTYPE_BOOL: {
bool parsed_value;
if (absl::SimpleAtob(value, &parsed_value)) {
RETURN_IF_SCIP_ERROR(
SCIPsetBoolParam(scip, name.c_str(), parsed_value));
continue;
}
break;
}
case SCIP_PARAMTYPE_INT: {
int parsed_value;
if (absl::SimpleAtoi(value, &parsed_value)) {
RETURN_IF_SCIP_ERROR(
SCIPsetIntParam(scip, name.c_str(), parsed_value));
continue;
}
break;
}
case SCIP_PARAMTYPE_LONGINT: {
int64 parsed_value;
if (absl::SimpleAtoi(value, &parsed_value)) {
RETURN_IF_SCIP_ERROR(
SCIPsetLongintParam(scip, name.c_str(), parsed_value));
continue;
}
break;
}
case SCIP_PARAMTYPE_REAL: {
double parsed_value;
if (absl::SimpleAtod(value, &parsed_value)) {
RETURN_IF_SCIP_ERROR(
SCIPsetRealParam(scip, name.c_str(), parsed_value));
continue;
}
break;
}
case SCIP_PARAMTYPE_CHAR: {
if (value.size() == 1) {
RETURN_IF_SCIP_ERROR(SCIPsetCharParam(scip, name.c_str(), value[0]));
continue;
}
break;
}
case SCIP_PARAMTYPE_STRING: {
if (value.front() == '"' && value.back() == '"') {
value.erase(value.begin());
value.erase(value.end() - 1);
}
RETURN_IF_SCIP_ERROR(
SCIPsetStringParam(scip, name.c_str(), value.c_str()));
continue;
}
}
return util::InvalidArgumentError(
absl::StrFormat("Invalid parameter value '%s'", parameter));
}
return util::OkStatus();
}
namespace {
// This function will create a new constraint if the indicator constraint has
// both a lower bound and an upper bound.
util::Status AddIndicatorConstraint(
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,
std::vector<double>* tmp_coefficients) {
CHECK(scip != nullptr);
CHECK(scip_cst != nullptr);
CHECK(scip_constraints != nullptr);
CHECK(tmp_variables != nullptr);
CHECK(tmp_coefficients != nullptr);
CHECK(gen_cst.has_indicator_constraint());
constexpr double kInfinity = std::numeric_limits<double>::infinity();
const auto& ind = gen_cst.indicator_constraint();
if (!ind.has_constraint()) return util::OkStatus();
const MPConstraintProto& constraint = ind.constraint();
const int size = constraint.var_index_size();
tmp_variables->resize(size, nullptr);
tmp_coefficients->resize(size, 0);
for (int i = 0; i < size; ++i) {
(*tmp_variables)[i] = scip_variables[constraint.var_index(i)];
(*tmp_coefficients)[i] = constraint.coefficient(i);
}
SCIP_VAR* ind_var = scip_variables[ind.var_index()];
if (ind.var_value() == 0) {
RETURN_IF_SCIP_ERROR(
SCIPgetNegatedVar(scip, scip_variables[ind.var_index()], &ind_var));
}
if (ind.constraint().upper_bound() < kInfinity) {
RETURN_IF_SCIP_ERROR(SCIPcreateConsIndicator(
scip, scip_cst, gen_cst.name().c_str(), ind_var, size,
tmp_variables->data(), tmp_coefficients->data(),
ind.constraint().upper_bound(),
/*initial=*/!ind.constraint().is_lazy(),
/*separate=*/true,
/*enforce=*/true,
/*check=*/true,
/*propagate=*/true,
/*local=*/false,
/*dynamic=*/false,
/*removable=*/ind.constraint().is_lazy(),
/*stickingatnode=*/false));
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
scip_constraints->push_back(nullptr);
scip_cst = &scip_constraints->back();
}
if (ind.constraint().lower_bound() > -kInfinity) {
for (int i = 0; i < size; ++i) {
(*tmp_coefficients)[i] *= -1;
}
RETURN_IF_SCIP_ERROR(SCIPcreateConsIndicator(
scip, scip_cst, gen_cst.name().c_str(), ind_var, size,
tmp_variables->data(), tmp_coefficients->data(),
-ind.constraint().lower_bound(),
/*initial=*/!ind.constraint().is_lazy(),
/*separate=*/true,
/*enforce=*/true,
/*check=*/true,
/*propagate=*/true,
/*local=*/false,
/*dynamic=*/false,
/*removable=*/ind.constraint().is_lazy(),
/*stickingatnode=*/false));
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
}
return util::OkStatus();
}
util::Status AddSosConstraint(const MPGeneralConstraintProto& gen_cst,
const std::vector<SCIP_VAR*>& scip_variables,
SCIP* scip, SCIP_CONS** scip_cst,
std::vector<SCIP_VAR*>* tmp_variables,
std::vector<double>* tmp_weights) {
CHECK(scip != nullptr);
CHECK(scip_cst != nullptr);
CHECK(tmp_variables != nullptr);
CHECK(tmp_weights != nullptr);
CHECK(gen_cst.has_sos_constraint());
const MPSosConstraint& sos_cst = gen_cst.sos_constraint();
// SOS constraints of type N indicate at most N variables are non-zero.
// Constraints with N variables or less are valid, but useless. They also
// crash SCIP, so we skip them.
if (sos_cst.var_index_size() <= 1) return util::OkStatus();
if (sos_cst.type() == MPSosConstraint::SOS2 &&
sos_cst.var_index_size() <= 2) {
return util::OkStatus();
}
tmp_variables->resize(sos_cst.var_index_size(), nullptr);
for (int v = 0; v < sos_cst.var_index_size(); ++v) {
(*tmp_variables)[v] = scip_variables[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 {
// In theory, SCIP should accept empty weight arrays and use natural
// ordering, but in practice, this crashes their code.
std::iota(tmp_weights->begin(), tmp_weights->end(), 1);
}
switch (sos_cst.type()) {
case MPSosConstraint::SOS1_DEFAULT:
RETURN_IF_SCIP_ERROR(
SCIPcreateConsBasicSOS1(scip,
/*cons=*/scip_cst,
/*name=*/gen_cst.name().c_str(),
/*nvars=*/sos_cst.var_index_size(),
/*vars=*/tmp_variables->data(),
/*weights=*/tmp_weights->data()));
break;
case MPSosConstraint::SOS2:
RETURN_IF_SCIP_ERROR(
SCIPcreateConsBasicSOS2(scip,
/*cons=*/scip_cst,
/*name=*/gen_cst.name().c_str(),
/*nvars=*/sos_cst.var_index_size(),
/*vars=*/tmp_variables->data(),
/*weights=*/tmp_weights->data()));
break;
}
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
return util::OkStatus();
}
util::Status AddQuadraticConstraint(
const MPGeneralConstraintProto& gen_cst,
const std::vector<SCIP_VAR*>& scip_variables, SCIP* scip,
SCIP_CONS** scip_cst, std::vector<SCIP_VAR*>* tmp_variables,
std::vector<double>* tmp_coefficients,
std::vector<SCIP_VAR*>* tmp_qvariables1,
std::vector<SCIP_VAR*>* tmp_qvariables2,
std::vector<double>* tmp_qcoefficients) {
CHECK(scip != nullptr);
CHECK(scip_cst != nullptr);
CHECK(tmp_variables != nullptr);
CHECK(tmp_coefficients != nullptr);
CHECK(tmp_qvariables1 != nullptr);
CHECK(tmp_qvariables2 != nullptr);
CHECK(tmp_qcoefficients != nullptr);
CHECK(gen_cst.has_quadratic_constraint());
const MPQuadraticConstraint& quad_cst = gen_cst.quadratic_constraint();
// Process linear part of the constraint.
const int lsize = quad_cst.var_index_size();
CHECK_EQ(quad_cst.coefficient_size(), lsize);
tmp_variables->resize(lsize, nullptr);
tmp_coefficients->resize(lsize, 0.0);
for (int i = 0; i < lsize; ++i) {
(*tmp_variables)[i] = scip_variables[quad_cst.var_index(i)];
(*tmp_coefficients)[i] = quad_cst.coefficient(i);
}
// Process quadratic part of the constraint.
const int qsize = quad_cst.qvar1_index_size();
CHECK_EQ(quad_cst.qvar2_index_size(), qsize);
CHECK_EQ(quad_cst.qcoefficient_size(), qsize);
tmp_qvariables1->resize(qsize, nullptr);
tmp_qvariables2->resize(qsize, nullptr);
tmp_qcoefficients->resize(qsize, 0.0);
for (int i = 0; i < qsize; ++i) {
(*tmp_qvariables1)[i] = scip_variables[quad_cst.qvar1_index(i)];
(*tmp_qvariables2)[i] = scip_variables[quad_cst.qvar2_index(i)];
(*tmp_qcoefficients)[i] = quad_cst.qcoefficient(i);
}
RETURN_IF_SCIP_ERROR(
SCIPcreateConsBasicQuadratic(scip,
/*cons=*/scip_cst,
/*name=*/gen_cst.name().c_str(),
/*nlinvars=*/lsize,
/*linvars=*/tmp_variables->data(),
/*lincoefs=*/tmp_coefficients->data(),
/*nquadterms=*/qsize,
/*quadvars1=*/tmp_qvariables1->data(),
/*quadvars2=*/tmp_qvariables2->data(),
/*quadcoefs=*/tmp_qcoefficients->data(),
/*lhs=*/quad_cst.lower_bound(),
/*rhs=*/quad_cst.upper_bound()));
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
return util::OkStatus();
}
// Models the constraint y = |x| as y >= 0 plus one disjunction constraint:
// 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) {
CHECK(scip != nullptr);
CHECK(scip_constraints != nullptr);
CHECK(gen_cst.has_abs_constraint());
const auto& abs = gen_cst.abs_constraint();
SCIP_VAR* scip_var = scip_variables[abs.var_index()];
SCIP_VAR* scip_resultant_var = scip_variables[abs.resultant_var_index()];
// Set the resultant variable's lower bound to zero if it's negative.
if (SCIPvarGetLbLocal(scip_resultant_var) < 0.0) {
RETURN_IF_SCIP_ERROR(SCIPchgVarLb(scip, scip_resultant_var, 0.0));
}
std::vector<SCIP_VAR*> vars;
std::vector<double> vals;
std::vector<SCIP_CONS*> cons;
auto add_abs_constraint =
[&](const std::string& name_prefix) -> util::Status {
scip_constraints->push_back(nullptr);
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(),
/*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.
return util::OkStatus();
};
// Create an intermediary constraint such that y = -x
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(),
/*nconss=*/2, /*conss=*/cons.data(), /*relaxcons=*/nullptr));
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, scip_constraints->back()));
return util::OkStatus();
}
util::Status AddAndConstraint(const MPGeneralConstraintProto& gen_cst,
const std::vector<SCIP_VAR*>& scip_variables,
SCIP* scip, SCIP_CONS** scip_cst,
std::vector<SCIP_VAR*>* tmp_variables) {
CHECK(scip != nullptr);
CHECK(scip_cst != nullptr);
CHECK(tmp_variables != nullptr);
CHECK(gen_cst.has_and_constraint());
const auto& andcst = gen_cst.and_constraint();
tmp_variables->resize(andcst.var_index_size(), nullptr);
for (int i = 0; i < andcst.var_index_size(); ++i) {
(*tmp_variables)[i] = scip_variables[andcst.var_index(i)];
}
RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicAnd(
scip, /*cons=*/scip_cst,
/*name=*/gen_cst.name().c_str(),
/*resvar=*/scip_variables[andcst.resultant_var_index()],
/*nvars=*/andcst.var_index_size(),
/*vars=*/tmp_variables->data()));
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
return util::OkStatus();
}
util::Status AddOrConstraint(const MPGeneralConstraintProto& gen_cst,
const std::vector<SCIP_VAR*>& scip_variables,
SCIP* scip, SCIP_CONS** scip_cst,
std::vector<SCIP_VAR*>* tmp_variables) {
CHECK(scip != nullptr);
CHECK(scip_cst != nullptr);
CHECK(tmp_variables != nullptr);
CHECK(gen_cst.has_or_constraint());
const auto& orcst = gen_cst.or_constraint();
tmp_variables->resize(orcst.var_index_size(), nullptr);
for (int i = 0; i < orcst.var_index_size(); ++i) {
(*tmp_variables)[i] = scip_variables[orcst.var_index(i)];
}
RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicOr(
scip, /*cons=*/scip_cst,
/*name=*/gen_cst.name().c_str(),
/*resvar=*/scip_variables[orcst.resultant_var_index()],
/*nvars=*/orcst.var_index_size(),
/*vars=*/tmp_variables->data()));
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
return util::OkStatus();
}
util::Status AddQuadraticObjective(const MPQuadraticObjective& quadobj,
SCIP* scip,
std::vector<SCIP_VAR*>* scip_variables,
std::vector<SCIP_CONS*>* scip_constraints) {
CHECK(scip != nullptr);
CHECK(scip_variables != nullptr);
CHECK(scip_constraints != nullptr);
constexpr double kInfinity = std::numeric_limits<double>::infinity();
const int size = quadobj.coefficient_size();
if (size == 0) return util::OkStatus();
// SCIP supports quadratic objectives by adding a quadratic constraint. We
// need to create an extra variable to hold this quadratic objective.
scip_variables->push_back(nullptr);
RETURN_IF_SCIP_ERROR(SCIPcreateVarBasic(scip, /*var=*/&scip_variables->back(),
/*name=*/"quadobj",
/*lb=*/-kInfinity, /*ub=*/kInfinity,
/*obj=*/1,
/*vartype=*/SCIP_VARTYPE_CONTINUOUS));
RETURN_IF_SCIP_ERROR(SCIPaddVar(scip, scip_variables->back()));
scip_constraints->push_back(nullptr);
SCIP_VAR* linvars[1] = {scip_variables->back()};
double lincoefs[1] = {-1};
std::vector<SCIP_VAR*> quadvars1(size, nullptr);
std::vector<SCIP_VAR*> quadvars2(size, nullptr);
std::vector<double> quadcoefs(size, 0);
for (int i = 0; i < size; ++i) {
quadvars1[i] = scip_variables->at(quadobj.qvar1_index(i));
quadvars2[i] = scip_variables->at(quadobj.qvar2_index(i));
quadcoefs[i] = quadobj.coefficient(i);
}
RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicQuadratic(
scip, /*cons=*/&scip_constraints->back(), /*name=*/"quadobj",
/*nlinvars=*/1, /*linvars=*/linvars, /*lincoefs=*/lincoefs,
/*nquadterms=*/size, /*quadvars1=*/quadvars1.data(),
/*quadvars2=*/quadvars2.data(), /*quadcoefs=*/quadcoefs.data(),
/*lhs=*/0, /*rhs=*/0));
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, scip_constraints->back()));
return util::OkStatus();
}
util::Status AddSolutionHint(const MPModelProto& model, SCIP* scip,
const std::vector<SCIP_VAR*>& scip_variables) {
CHECK(scip != nullptr);
if (!model.has_solution_hint()) return util::OkStatus();
const PartialVariableAssignment& solution_hint = model.solution_hint();
SCIP_SOL* solution;
bool is_solution_partial =
solution_hint.var_index_size() != model.variable_size();
if (is_solution_partial) {
RETURN_IF_SCIP_ERROR(
SCIPcreatePartialSol(scip, /*sol=*/&solution, /*heur=*/nullptr));
} else {
RETURN_IF_SCIP_ERROR(
SCIPcreateSol(scip, /*sol=*/&solution, /*heur=*/nullptr));
}
for (int i = 0; i < solution_hint.var_index_size(); ++i) {
RETURN_IF_SCIP_ERROR(SCIPsetSolVal(
scip, solution, scip_variables[solution_hint.var_index(i)],
solution_hint.var_value(i)));
}
SCIP_Bool is_stored;
RETURN_IF_SCIP_ERROR(SCIPaddSolFree(scip, &solution, &is_stored));
return util::OkStatus();
}
bool MPModelIsInvalidForScip(const MPModelProto& model, SCIP* scip,
MPSolutionResponse* response) {
CHECK(scip != nullptr);
CHECK(response != nullptr);
const double infinity = SCIPinfinity(scip);
for (int v = 0; v < model.variable_size(); ++v) {
const MPVariableProto& variable = model.variable(v);
if (variable.lower_bound() >= infinity) {
response->set_status(MPSOLVER_MODEL_INVALID);
response->set_status_str(absl::StrFormat(
"Variable %i's lower bound is considered +infinity", v));
return true;
}
if (variable.upper_bound() <= -infinity) {
response->set_status(MPSOLVER_MODEL_INVALID);
response->set_status_str(absl::StrFormat(
"Variable %i's lower bound is considered -infinity", v));
return true;
}
const double coeff = variable.objective_coefficient();
if (coeff >= infinity || coeff <= -infinity) {
response->set_status(MPSOLVER_MODEL_INVALID);
response->set_status_str(absl::StrFormat(
"Variable %i's objective coefficient is considered infinite", v));
return true;
}
}
if (model.has_solution_hint()) {
for (int i = 0; i < model.solution_hint().var_value_size(); ++i) {
const double value = model.solution_hint().var_value(i);
if (value >= infinity || value <= -infinity) {
response->set_status(MPSOLVER_MODEL_INVALID);
response->set_status_str(absl::StrFormat(
"Variable %i's solution hint is considered infinite",
model.solution_hint().var_index(i)));
return true;
}
}
}
if (model.objective_offset() >= infinity ||
model.objective_offset() <= -infinity) {
response->set_status(MPSOLVER_MODEL_INVALID);
response->set_status_str(
"Model's objective offset is considered infinite.");
return true;
}
return false;
}
} // namespace
util::StatusOr<MPSolutionResponse> ScipSolveProto(
const MPModelRequest& request) {
MPSolutionResponse response;
if (MPRequestIsEmptyOrInvalid(request, &response)) return response;
const MPModelProto& model = request.model();
SCIP* scip = nullptr;
std::vector<SCIP_VAR*> scip_variables(model.variable_size(), nullptr);
std::vector<SCIP_CONS*> scip_constraints(
model.constraint_size() + model.general_constraint_size(), nullptr);
auto delete_scip_objects = [&]() -> util::Status {
// Release all created pointers.
if (scip == nullptr) return util::OkStatus();
for (SCIP_VAR* variable : scip_variables) {
if (variable != nullptr) {
RETURN_IF_SCIP_ERROR(SCIPreleaseVar(scip, &variable));
}
}
for (SCIP_CONS* constraint : scip_constraints) {
if (constraint != nullptr) {
RETURN_IF_SCIP_ERROR(SCIPreleaseCons(scip, &constraint));
}
}
RETURN_IF_SCIP_ERROR(SCIPfree(&scip));
return util::OkStatus();
};
auto scip_deleter = gtl::MakeCleanup([delete_scip_objects]() {
const util::Status deleter_status = delete_scip_objects();
LOG_IF(DFATAL, !deleter_status.ok()) << deleter_status;
});
RETURN_IF_SCIP_ERROR(SCIPcreate(&scip));
RETURN_IF_SCIP_ERROR(SCIPincludeDefaultPlugins(scip));
if (MPModelIsInvalidForScip(model, scip, &response)) return response;
const auto parameters_status = ScipSetSolverSpecificParameters(
request.solver_specific_parameters(), scip);
if (!parameters_status.ok()) {
response.set_status(MPSOLVER_MODEL_INVALID_SOLVER_PARAMETERS);
response.set_status_str(parameters_status.error_message());
return response;
}
if (request.solver_time_limit_seconds() > 0 &&
request.solver_time_limit_seconds() < 1e20) {
RETURN_IF_SCIP_ERROR(SCIPsetRealParam(scip, "limits/time",
request.solver_time_limit_seconds()));
}
SCIPsetMessagehdlrQuiet(scip, !request.enable_internal_solver_output());
RETURN_IF_SCIP_ERROR(SCIPcreateProbBasic(scip, model.name().c_str()));
if (model.maximize()) {
RETURN_IF_SCIP_ERROR(SCIPsetObjsense(scip, SCIP_OBJSENSE_MAXIMIZE));
}
for (int v = 0; v < model.variable_size(); ++v) {
const MPVariableProto& variable = model.variable(v);
RETURN_IF_SCIP_ERROR(SCIPcreateVarBasic(
scip, /*var=*/&scip_variables[v], /*name=*/variable.name().c_str(),
/*lb=*/variable.lower_bound(), /*ub=*/variable.upper_bound(),
/*obj=*/variable.objective_coefficient(),
/*vartype=*/variable.is_integer() ? SCIP_VARTYPE_INTEGER
: SCIP_VARTYPE_CONTINUOUS));
RETURN_IF_SCIP_ERROR(SCIPaddVar(scip, scip_variables[v]));
}
{
std::vector<SCIP_VAR*> ct_variables;
std::vector<double> ct_coefficients;
for (int c = 0; c < model.constraint_size(); ++c) {
const MPConstraintProto& constraint = model.constraint(c);
const int size = constraint.var_index_size();
ct_variables.resize(size, nullptr);
ct_coefficients.resize(size, 0);
for (int i = 0; i < size; ++i) {
ct_variables[i] = scip_variables[constraint.var_index(i)];
ct_coefficients[i] = constraint.coefficient(i);
}
RETURN_IF_SCIP_ERROR(SCIPcreateConsLinear(
scip, /*cons=*/&scip_constraints[c],
/*name=*/constraint.name().c_str(),
/*nvars=*/constraint.var_index_size(), /*vars=*/ct_variables.data(),
/*vals=*/ct_coefficients.data(),
/*lhs=*/constraint.lower_bound(), /*rhs=*/constraint.upper_bound(),
/*initial=*/!constraint.is_lazy(),
/*separate=*/true,
/*enforce=*/true,
/*check=*/true,
/*propagate=*/true,
/*local=*/false,
/*modifiable=*/false,
/*dynamic=*/false,
/*removable=*/constraint.is_lazy(),
/*stickingatnode=*/false));
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, scip_constraints[c]));
}
// These extra arrays are used by quadratic constraints.
std::vector<SCIP_VAR*> ct_qvariables1;
std::vector<SCIP_VAR*> ct_qvariables2;
std::vector<double> ct_qcoefficients;
const int lincst_size = model.constraint_size();
for (int c = 0; c < model.general_constraint_size(); ++c) {
const MPGeneralConstraintProto& gen_cst = model.general_constraint(c);
switch (gen_cst.general_constraint_case()) {
case MPGeneralConstraintProto::kIndicatorConstraint: {
RETURN_IF_ERROR(AddIndicatorConstraint(
gen_cst, scip_variables, scip, &scip_constraints[lincst_size + c],
&scip_constraints, &ct_variables, &ct_coefficients));
break;
}
case MPGeneralConstraintProto::kSosConstraint: {
RETURN_IF_ERROR(AddSosConstraint(gen_cst, scip_variables, scip,
&scip_constraints[lincst_size + c],
&ct_variables, &ct_coefficients));
break;
}
case MPGeneralConstraintProto::kQuadraticConstraint: {
RETURN_IF_ERROR(AddQuadraticConstraint(
gen_cst, scip_variables, scip, &scip_constraints[lincst_size + c],
&ct_variables, &ct_coefficients, &ct_qvariables1, &ct_qvariables2,
&ct_qcoefficients));
break;
}
case MPGeneralConstraintProto::kAbsConstraint: {
RETURN_IF_ERROR(AddAbsConstraint(gen_cst, scip_variables, scip,
&scip_constraints));
break;
}
case MPGeneralConstraintProto::kAndConstraint: {
RETURN_IF_ERROR(AddAndConstraint(gen_cst, scip_variables, scip,
&scip_constraints[lincst_size + c],
&ct_variables));
break;
}
case MPGeneralConstraintProto::kOrConstraint: {
RETURN_IF_ERROR(AddOrConstraint(gen_cst, scip_variables, scip,
&scip_constraints[lincst_size + c],
&ct_variables));
break;
}
default:
return util::UnimplementedError(
absl::StrFormat("General constraints of type %i not supported.",
gen_cst.general_constraint_case()));
}
}
}
if (model.has_quadratic_objective()) {
RETURN_IF_ERROR(AddQuadraticObjective(model.quadratic_objective(), scip,
&scip_variables, &scip_constraints));
}
RETURN_IF_SCIP_ERROR(SCIPaddOrigObjoffset(scip, model.objective_offset()));
RETURN_IF_ERROR(AddSolutionHint(model, scip, scip_variables));
RETURN_IF_SCIP_ERROR(SCIPsolve(scip));
SCIP_SOL* const solution = SCIPgetBestSol(scip);
if (solution != nullptr) {
response.set_objective_value(SCIPgetSolOrigObj(scip, solution));
response.set_best_objective_bound(SCIPgetDualbound(scip));
for (int v = 0; v < model.variable_size(); ++v) {
double value = SCIPgetSolVal(scip, solution, scip_variables[v]);
if (model.variable(v).is_integer()) value = std::round(value);
response.add_variable_value(value);
}
}
const SCIP_STATUS scip_status = SCIPgetStatus(scip);
switch (scip_status) {
case SCIP_STATUS_OPTIMAL:
response.set_status(MPSOLVER_OPTIMAL);
break;
case SCIP_STATUS_GAPLIMIT:
// To be consistent with the other solvers.
response.set_status(MPSOLVER_OPTIMAL);
break;
case SCIP_STATUS_INFORUNBD:
// NOTE(user): After looking at the SCIP code on 2019-06-14, it seems
// that this will mostly happen for INFEASIBLE problems in practice.
// Since most (all?) users shouldn't have their application behave very
// differently upon INFEASIBLE or UNBOUNDED, the potential error that we
// are making here seems reasonable (and not worth a LOG, unless in
// debug mode).
DLOG(INFO) << "SCIP solve returned SCIP_STATUS_INFORUNBD, which we treat "
"as INFEASIBLE even though it may mean UNBOUNDED.";
response.set_status_str(
"The model may actually be unbounded: SCIP returned "
"SCIP_STATUS_INFORUNBD");
ABSL_FALLTHROUGH_INTENDED;
case SCIP_STATUS_INFEASIBLE:
response.set_status(MPSOLVER_INFEASIBLE);
break;
case SCIP_STATUS_UNBOUNDED:
response.set_status(MPSOLVER_UNBOUNDED);
break;
default:
if (solution != nullptr) {
response.set_status(MPSOLVER_FEASIBLE);
} else {
response.set_status(MPSOLVER_NOT_SOLVED);
response.set_status_str(absl::StrFormat("SCIP status code %d",
static_cast<int>(scip_status)));
}
break;
}
return response;
}
} // namespace operations_research
#endif // #if defined(USE_SCIP)