22#include "absl/status/status.h"
23#include "absl/status/statusor.h"
24#include "absl/strings/str_cat.h"
25#include "absl/strings/str_format.h"
26#include "absl/strings/str_join.h"
27#include "absl/strings/str_split.h"
28#include "absl/types/optional.h"
34#include "ortools/linear_solver/linear_solver.pb.h"
41constexpr int GRB_OK = 0;
43inline absl::Status GurobiCodeToUtilStatus(
int error_code,
44 const char* source_file,
46 const char* statement,
48 if (error_code == GRB_OK)
return absl::OkStatus();
49 return absl::InvalidArgumentError(absl::StrFormat(
50 "Gurobi error code %d (file '%s', line %d) on '%s': %s", error_code,
54int AddIndicatorConstraint(
const MPGeneralConstraintProto& gen_cst,
56 std::vector<int>* tmp_variables,
57 std::vector<double>* tmp_coefficients) {
58 CHECK(gurobi_model !=
nullptr);
59 CHECK(tmp_variables !=
nullptr);
60 CHECK(tmp_coefficients !=
nullptr);
62 const auto& ind_cst = gen_cst.indicator_constraint();
63 MPConstraintProto cst = ind_cst.constraint();
64 if (cst.lower_bound() > -std::numeric_limits<double>::infinity()) {
66 gurobi_model, gen_cst.name().c_str(), ind_cst.var_index(),
67 ind_cst.var_value(), cst.var_index_size(),
68 cst.mutable_var_index()->mutable_data(),
69 cst.mutable_coefficient()->mutable_data(),
74 if (cst.upper_bound() < std::numeric_limits<double>::infinity() &&
75 cst.lower_bound() != cst.upper_bound()) {
77 ind_cst.var_index(), ind_cst.var_value(),
79 cst.mutable_var_index()->mutable_data(),
80 cst.mutable_coefficient()->mutable_data(),
87int AddSosConstraint(
const MPSosConstraint& sos_cst,
GRBmodel* gurobi_model,
88 std::vector<int>* tmp_variables,
89 std::vector<double>* tmp_weights) {
90 CHECK(gurobi_model !=
nullptr);
91 CHECK(tmp_variables !=
nullptr);
92 CHECK(tmp_weights !=
nullptr);
94 tmp_variables->resize(sos_cst.var_index_size(), 0);
95 for (
int v = 0; v < sos_cst.var_index_size(); ++v) {
96 (*tmp_variables)[v] = sos_cst.var_index(v);
98 tmp_weights->resize(sos_cst.var_index_size(), 0);
99 if (sos_cst.weight_size() == sos_cst.var_index_size()) {
100 for (
int w = 0; w < sos_cst.weight_size(); ++w) {
101 (*tmp_weights)[w] = sos_cst.weight(w);
106 std::iota(tmp_weights->begin(), tmp_weights->end(), 1);
109 std::vector<int> types = {sos_cst.type() == MPSosConstraint::SOS1_DEFAULT
112 std::vector<int> begins = {0};
114 sos_cst.var_index_size(),
116 begins.data(), tmp_variables->data(),
117 tmp_weights->data());
120int AddQuadraticConstraint(
const MPGeneralConstraintProto& gen_cst,
122 CHECK(gurobi_model !=
nullptr);
123 constexpr double kInfinity = std::numeric_limits<double>::infinity();
125 CHECK(gen_cst.has_quadratic_constraint());
126 const MPQuadraticConstraint& quad_cst = gen_cst.quadratic_constraint();
128 auto addqconstr = [](
GRBmodel* gurobi_model, MPQuadraticConstraint quad_cst,
129 char sense,
double rhs,
const std::string&
name) {
132 quad_cst.var_index_size(),
133 quad_cst.mutable_var_index()->mutable_data(),
134 quad_cst.mutable_coefficient()->mutable_data(),
135 quad_cst.qvar1_index_size(),
136 quad_cst.mutable_qvar1_index()->mutable_data(),
137 quad_cst.mutable_qvar2_index()->mutable_data(),
138 quad_cst.mutable_qcoefficient()->mutable_data(),
144 if (quad_cst.has_lower_bound() && quad_cst.lower_bound() > -
kInfinity) {
145 const int grb_status =
146 addqconstr(gurobi_model, gen_cst.quadratic_constraint(),
148 gen_cst.has_name() ? gen_cst.name() +
"_lb" :
"");
149 if (grb_status != GRB_OK)
return grb_status;
151 if (quad_cst.has_upper_bound() && quad_cst.upper_bound() <
kInfinity) {
152 const int grb_status =
153 addqconstr(gurobi_model, gen_cst.quadratic_constraint(),
GRB_LESS_EQUAL,
154 quad_cst.upper_bound(),
155 gen_cst.has_name() ? gen_cst.name() +
"_ub" :
"");
156 if (grb_status != GRB_OK)
return grb_status;
162int AddAndConstraint(
const MPGeneralConstraintProto& gen_cst,
163 GRBmodel* gurobi_model, std::vector<int>* tmp_variables) {
164 CHECK(gurobi_model !=
nullptr);
165 CHECK(tmp_variables !=
nullptr);
167 auto and_cst = gen_cst.and_constraint();
170 gen_cst.name().c_str(),
171 and_cst.resultant_var_index(),
172 and_cst.var_index_size(),
173 and_cst.mutable_var_index()->mutable_data());
176int AddOrConstraint(
const MPGeneralConstraintProto& gen_cst,
177 GRBmodel* gurobi_model, std::vector<int>* tmp_variables) {
178 CHECK(gurobi_model !=
nullptr);
179 CHECK(tmp_variables !=
nullptr);
181 auto or_cst = gen_cst.or_constraint();
183 gen_cst.name().c_str(),
184 or_cst.resultant_var_index(),
185 or_cst.var_index_size(),
186 or_cst.mutable_var_index()->mutable_data());
189int AddMinConstraint(
const MPGeneralConstraintProto& gen_cst,
190 GRBmodel* gurobi_model, std::vector<int>* tmp_variables) {
191 CHECK(gurobi_model !=
nullptr);
192 CHECK(tmp_variables !=
nullptr);
194 auto min_cst = gen_cst.min_constraint();
197 gen_cst.name().c_str(),
198 min_cst.resultant_var_index(),
199 min_cst.var_index_size(),
200 min_cst.mutable_var_index()->mutable_data(),
201 min_cst.has_constant()
203 : std::numeric_limits<double>::infinity());
206int AddMaxConstraint(
const MPGeneralConstraintProto& gen_cst,
207 GRBmodel* gurobi_model, std::vector<int>* tmp_variables) {
208 CHECK(gurobi_model !=
nullptr);
209 CHECK(tmp_variables !=
nullptr);
211 auto max_cst = gen_cst.max_constraint();
214 gen_cst.name().c_str(),
215 max_cst.resultant_var_index(),
216 max_cst.var_index_size(),
217 max_cst.mutable_var_index()->mutable_data(),
218 max_cst.has_constant()
220 : -std::numeric_limits<double>::infinity());
226 if (
parameters.empty())
return absl::OkStatus();
227 std::vector<std::string> error_messages;
228 for (absl::string_view line : absl::StrSplit(
parameters,
'\n')) {
231 if (line[0] ==
'#')
continue;
232 for (absl::string_view token :
233 absl::StrSplit(line,
',', absl::SkipWhitespace())) {
234 if (token.empty())
continue;
235 std::vector<std::string> key_value =
236 absl::StrSplit(token, absl::ByAnyChar(
" ="), absl::SkipWhitespace());
238 if (key_value.size() != 2) {
239 const std::string current_message =
240 absl::StrCat(
"Cannot parse parameter '", token,
241 "'. Expected format is 'ParameterName value' or "
242 "'ParameterName=value'");
243 error_messages.push_back(current_message);
246 const int gurobi_code =
248 if (gurobi_code != GRB_OK) {
249 const std::string current_message = absl::StrCat(
250 "Error setting parameter '", key_value[0],
"' to value '",
252 error_messages.push_back(current_message);
255 VLOG(2) << absl::StrCat(
"Set parameter '", key_value[0],
"' to value '",
260 if (error_messages.empty())
return absl::OkStatus();
261 return absl::InvalidArgumentError(absl::StrJoin(error_messages,
"\n"));
265 const MPModelRequest& request,
GRBenv* gurobi_env) {
267 const absl::optional<LazyMutableCopy<MPModelProto>> optional_model =
269 if (!optional_model)
return response;
270 const MPModelProto&
model = optional_model->get();
275 bool gurobi_env_was_created =
false;
277 if (gurobi_env_was_created && gurobi_env !=
nullptr) {
281 if (gurobi_env ==
nullptr) {
283 gurobi_env_was_created =
true;
289 LOG_IF(DFATAL, error_code != GRB_OK)
290 <<
"GRBfreemodel failed with error " << error_code <<
": "
295#define RETURN_IF_GUROBI_ERROR(x) \
297 GurobiCodeToUtilStatus(x, __FILE__, __LINE__, #x, gurobi_env));
300 model.name().c_str(),
309 if (request.has_solver_specific_parameters()) {
311 request.solver_specific_parameters(), model_env);
312 if (!parameters_status.ok()) {
313 response.set_status(MPSOLVER_MODEL_INVALID_SOLVER_PARAMETERS);
315 std::string(parameters_status.message()));
319 if (request.solver_time_limit_seconds() > 0) {
321 request.solver_time_limit_seconds()));
325 request.enable_internal_solver_output()));
327 const int variable_size =
model.variable_size();
328 bool has_integer_variables =
false;
330 std::vector<double> obj_coeffs(variable_size, 0);
331 std::vector<double> lb(variable_size);
332 std::vector<double> ub(variable_size);
333 std::vector<char> ctype(variable_size);
334 std::vector<const char*> varnames(variable_size);
335 for (
int v = 0; v < variable_size; ++v) {
336 const MPVariableProto& variable =
model.variable(v);
337 obj_coeffs[v] = variable.objective_coefficient();
338 lb[v] = variable.lower_bound();
339 ub[v] = variable.upper_bound();
340 ctype[v] = variable.is_integer() &&
SolverTypeIsMip(request.solver_type())
343 if (variable.is_integer()) has_integer_variables =
true;
344 if (!variable.name().empty()) varnames[v] = variable.name().c_str();
348 GRBaddvars(gurobi_model, variable_size, 0,
nullptr,
nullptr,
nullptr,
350 lb.data(), ub.data(), ctype.data(),
351 const_cast<char**
>(varnames.data())));
354 for (
int i = 0; i <
model.solution_hint().var_index_size(); ++i) {
357 model.solution_hint().var_value(i)));
362 std::vector<int> ct_variables;
363 std::vector<double> ct_coefficients;
364 for (
int c = 0; c <
model.constraint_size(); ++c) {
365 const MPConstraintProto& constraint =
model.constraint(c);
366 const int size = constraint.var_index_size();
367 ct_variables.resize(size, 0);
368 ct_coefficients.resize(size, 0);
369 for (
int i = 0; i < size; ++i) {
370 ct_variables[i] = constraint.var_index(i);
371 ct_coefficients[i] = constraint.coefficient(i);
375 if (constraint.lower_bound() == constraint.upper_bound()) {
377 gurobi_model, size, ct_variables.data(),
378 ct_coefficients.data(),
380 constraint.name().c_str()));
381 }
else if (constraint.lower_bound() ==
382 -std::numeric_limits<double>::infinity()) {
384 gurobi_model, size, ct_variables.data(),
385 ct_coefficients.data(),
387 constraint.name().c_str()));
388 }
else if (constraint.upper_bound() ==
389 std::numeric_limits<double>::infinity()) {
391 gurobi_model, size, ct_variables.data(),
392 ct_coefficients.data(),
394 constraint.name().c_str()));
397 gurobi_model, size, ct_variables.data(),
398 ct_coefficients.data(),
399 constraint.lower_bound(),
400 constraint.upper_bound(),
401 constraint.name().c_str()));
405 for (
const auto& gen_cst :
model.general_constraint()) {
406 switch (gen_cst.general_constraint_case()) {
407 case MPGeneralConstraintProto::kIndicatorConstraint: {
409 gen_cst, gurobi_model, &ct_variables, &ct_coefficients));
412 case MPGeneralConstraintProto::kSosConstraint: {
414 gurobi_model, &ct_variables,
418 case MPGeneralConstraintProto::kQuadraticConstraint: {
422 case MPGeneralConstraintProto::kAbsConstraint: {
425 gen_cst.name().c_str(),
426 gen_cst.abs_constraint().resultant_var_index(),
427 gen_cst.abs_constraint().var_index()));
430 case MPGeneralConstraintProto::kAndConstraint: {
432 AddAndConstraint(gen_cst, gurobi_model, &ct_variables));
435 case MPGeneralConstraintProto::kOrConstraint: {
437 AddOrConstraint(gen_cst, gurobi_model, &ct_variables));
440 case MPGeneralConstraintProto::kMinConstraint: {
442 AddMinConstraint(gen_cst, gurobi_model, &ct_variables));
445 case MPGeneralConstraintProto::kMaxConstraint: {
447 AddMaxConstraint(gen_cst, gurobi_model, &ct_variables));
451 return absl::UnimplementedError(
452 absl::StrFormat(
"General constraints of type %i not supported.",
453 gen_cst.general_constraint_case()));
459 model.maximize() ? -1 : 1));
461 model.objective_offset()));
462 if (
model.has_quadratic_objective()) {
463 MPQuadraticObjective qobj =
model.quadratic_objective();
464 if (qobj.coefficient_size() > 0) {
467 qobj.mutable_qvar1_index()->mutable_data(),
468 qobj.mutable_qvar2_index()->mutable_data(),
469 qobj.mutable_coefficient()->mutable_data()));
475 const absl::Time time_before = absl::Now();
481 const absl::Duration solving_duration = absl::Now() - time_before;
483 VLOG(1) <<
"Finished solving in GurobiSolveProto(), walltime = "
484 << solving_duration <<
", usertime = " << user_timer.
GetDuration();
485 response.mutable_solve_info()->set_solve_wall_time_seconds(
486 absl::ToDoubleSeconds(solving_duration));
487 response.mutable_solve_info()->set_solve_user_time_seconds(
490 int optimization_status = 0;
493 int solution_count = 0;
496 switch (optimization_status) {
498 response.set_status(MPSOLVER_OPTIMAL);
501 DLOG(
INFO) <<
"Gurobi solve returned GRB_INF_OR_UNBD, which we treat as "
502 "INFEASIBLE even though it may mean UNBOUNDED.";
504 "The model may actually be unbounded: Gurobi returned "
506 ABSL_FALLTHROUGH_INTENDED;
508 response.set_status(MPSOLVER_INFEASIBLE);
511 response.set_status(MPSOLVER_UNBOUNDED);
514 if (solution_count > 0) {
515 response.set_status(MPSOLVER_FEASIBLE);
517 response.set_status(MPSOLVER_NOT_SOLVED);
519 absl::StrFormat(
"Gurobi status code %d", optimization_status));
525 if (solution_count > 0 && (
response.status() == MPSOLVER_FEASIBLE ||
526 response.status() == MPSOLVER_OPTIMAL)) {
531 double best_objective_bound = 0;
533 &best_objective_bound);
534 if (
response.status() == MPSOLVER_OPTIMAL &&
540 response.set_best_objective_bound(best_objective_bound);
543 response.mutable_variable_value()->Resize(variable_size, 0);
546 response.mutable_variable_value()->mutable_data()));
550 auto round_values_of_integer_variables_fn =
551 [&](google::protobuf::RepeatedField<double>* values) {
552 for (
int v = 0; v < variable_size; ++v) {
553 if (
model.variable(v).is_integer()) {
554 (*values)[v] = std::round((*values)[v]);
558 round_values_of_integer_variables_fn(
response.mutable_variable_value());
559 if (!has_integer_variables &&
model.general_constraint_size() == 0) {
560 response.mutable_dual_value()->Resize(
model.constraint_size(), 0);
563 response.mutable_dual_value()->mutable_data()));
565 const int additional_solutions =
std::min(
566 solution_count,
std::min(request.populate_additional_solutions_up_to(),
569 for (
int i = 1; i < additional_solutions; ++i) {
572 MPSolution* solution =
response.add_additional_solutions();
573 solution->mutable_variable_value()->Resize(variable_size, 0);
580 solution->mutable_variable_value()->mutable_data()));
581 round_values_of_integer_variables_fn(solution->mutable_variable_value());
584#undef RETURN_IF_GUROBI_ERROR
#define LOG_IF(severity, condition)
#define DCHECK_EQ(val1, val2)
#define VLOG(verboselevel)
#define ASSIGN_OR_RETURN(lhs, rexpr)
absl::Duration GetDuration() const
SharedResponseManager * response
#define GRB_DBL_ATTR_START
#define GRB_ERROR_DATA_NOT_AVAILABLE
#define GRB_INT_ATTR_MODELSENSE
#define GRB_GREATER_EQUAL
#define GRB_DBL_ATTR_OBJVAL
struct _GRBmodel GRBmodel
#define GRB_DBL_ATTR_OBJCON
#define GRB_INT_ATTR_STATUS
#define GRB_DBL_ATTR_POOLOBJVAL
#define GRB_INT_PAR_SOLUTIONNUMBER
#define GRB_INT_ATTR_SOLCOUNT
#define GRB_INT_PAR_OUTPUTFLAG
#define GRB_DBL_PAR_TIMELIMIT
#define GRB_DBL_ATTR_OBJBOUND
#define RETURN_IF_GUROBI_ERROR(x)
A C++ wrapper that provides a simple and unified interface to several linear programming and mixed in...
absl::Cleanup< absl::decay_t< Callback > > MakeCleanup(Callback &&callback)
Collection of objects used to extend the Constraint Solver library.
std::function< int(GRBmodel *model, const char *name, int resvar, int nvars, const int *vars, double constant)> GRBaddgenconstrMin
std::function< int(GRBmodel *model, int numnz, int *cind, double *cval, char sense, double rhs, const char *constrname)> GRBaddconstr
std::function< int(GRBmodel *model, const char *attrname, double *valueP)> GRBgetdblattr
std::function< int(GRBmodel *model, int numlnz, int *lind, double *lval, int numqnz, int *qrow, int *qcol, double *qval, char sense, double rhs, const char *QCname)> GRBaddqconstr
std::function< int(GRBenv *env, const char *paramname, const char *value)> GRBsetparam
std::function< int(GRBmodel *model, const char *attrname, int newvalue)> GRBsetintattr
bool SolverTypeIsMip(MPModelRequest::SolverType solver_type)
std::function< int(GRBenv *env, const char *paramname, int value)> GRBsetintparam
std::function< int(GRBmodel *model, const char *name, int resvar, int argvar)> GRBaddgenconstrAbs
std::function< int(GRBmodel *model)> GRBfreemodel
std::function< int(GRBmodel *model, int numqnz, int *qrow, int *qcol, double *qval)> GRBaddqpterms
std::function< const char *(GRBenv *env)> GRBgeterrormsg
absl::StatusOr< GRBenv * > GetGurobiEnv()
std::function< GRBenv *(GRBmodel *model)> GRBgetenv
std::function< int(GRBmodel *model, const char *name, int resvar, int nvars, const int *vars, double constant)> GRBaddgenconstrMax
std::function< int(GRBmodel *model, const char *attrname, int element, double newvalue)> GRBsetdblattrelement
std::function< int(GRBmodel *lp, const char *name, int binvar, int binval, int nvars, const int *vars, const double *vals, char sense, double rhs)> GRBaddgenconstrIndicator
std::function< int(GRBmodel *model, const char *attrname, int first, int len, double *values)> GRBgetdblattrarray
std::function< int(GRBmodel *model)> GRBupdatemodel
std::function< int(GRBmodel *model, const char *name, int resvar, int nvars, const int *vars)> GRBaddgenconstrAnd
absl::Status SetSolverSpecificParameters(const std::string ¶meters, GRBenv *gurobi)
std::function< int(GRBmodel *model, int numnz, int *cind, double *cval, double lower, double upper, const char *constrname)> GRBaddrangeconstr
absl::optional< LazyMutableCopy< MPModelProto > > ExtractValidMPModelOrPopulateResponseStatus(const MPModelRequest &request, MPSolutionResponse *response)
If the model is valid and non-empty, returns it (possibly after extracting the model_delta).
absl::StatusOr< MPSolutionResponse > GurobiSolveProto(const MPModelRequest &request, GRBenv *gurobi_env)
std::function< int(GRBmodel *model, const char *name, int resvar, int nvars, const int *vars)> GRBaddgenconstrOr
std::function< int(GRBmodel *model)> GRBoptimize
std::function< int(GRBenv *env, GRBmodel **modelP, const char *Pname, int numvars, double *obj, double *lb, double *ub, char *vtype, char **varnames)> GRBnewmodel
std::function< void(GRBenv *env)> GRBfreeenv
std::function< int(GRBmodel *model, int numsos, int nummembers, int *types, int *beg, int *ind, double *weight)> GRBaddsos
std::function< int(GRBmodel *model, const char *attrname, double newvalue)> GRBsetdblattr
std::function< int(GRBmodel *model, int numvars, int numnz, int *vbeg, int *vind, double *vval, double *obj, double *lb, double *ub, char *vtype, char **varnames)> GRBaddvars
std::function< int(GRBmodel *model, const char *attrname, int *valueP)> GRBgetintattr
std::function< int(GRBenv *env, const char *paramname, double value)> GRBsetdblparam