OR-Tools  9.6
gurobi_solver.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
15 
16 #include <algorithm>
17 #include <cmath>
18 #include <cstdint>
19 #include <functional>
20 #include <limits>
21 #include <memory>
22 #include <optional>
23 #include <string>
24 #include <string_view>
25 #include <utility>
26 #include <vector>
27 
28 #include "absl/container/flat_hash_map.h"
29 #include "absl/container/flat_hash_set.h"
30 #include "absl/memory/memory.h"
31 #include "absl/status/status.h"
32 #include "absl/status/statusor.h"
33 #include "absl/strings/escaping.h"
34 #include "absl/strings/str_cat.h"
35 #include "absl/strings/str_join.h"
36 #include "absl/strings/string_view.h"
37 #include "absl/time/clock.h"
38 #include "absl/time/time.h"
39 #include "absl/types/span.h"
40 #include "absl/log/check.h"
42 #include "ortools/base/logging.h"
43 #include "ortools/base/map_util.h"
44 #include "ortools/base/protoutil.h"
46 #include "ortools/math_opt/callback.pb.h"
54 #include "ortools/math_opt/model.pb.h"
55 #include "ortools/math_opt/model_parameters.pb.h"
56 #include "ortools/math_opt/model_update.pb.h"
57 #include "ortools/math_opt/parameters.pb.h"
58 #include "ortools/math_opt/result.pb.h"
59 #include "ortools/math_opt/solution.pb.h"
60 #include "ortools/math_opt/solvers/gurobi.pb.h"
64 #include "ortools/math_opt/sparse_containers.pb.h"
67 
68 namespace operations_research {
69 namespace math_opt {
70 namespace {
71 
72 constexpr SupportedProblemStructures kGurobiSupportedStructures = {
73  .integer_variables = SupportType::kSupported,
74  .quadratic_objectives = SupportType::kSupported,
75  .quadratic_constraints = SupportType::kSupported,
76  .sos1_constraints = SupportType::kSupported,
77  .sos2_constraints = SupportType::kSupported,
78  .indicator_constraints = SupportType::kSupported};
79 
80 absl::StatusOr<std::unique_ptr<Gurobi>> GurobiFromInitArgs(
81  const SolverInterface::InitArgs& init_args) {
82  // We don't test or return an error for incorrect non streamable arguments
83  // type since it is already tested by the Solver class.
84  const NonStreamableGurobiInitArguments* const non_streamable_args =
85  init_args.non_streamable != nullptr
86  ? init_args.non_streamable->ToNonStreamableGurobiInitArguments()
87  : nullptr;
88  std::unique_ptr<Gurobi> gurobi;
89  if (non_streamable_args != nullptr &&
90  non_streamable_args->primary_env != nullptr) {
91  return Gurobi::NewWithSharedPrimaryEnv(non_streamable_args->primary_env);
92  } else if (init_args.streamable.has_gurobi() &&
93  init_args.streamable.gurobi().has_isv_key()) {
95  GRBenvUniquePtr env,
96  NewPrimaryEnvironment(init_args.streamable.gurobi().isv_key()));
97  return Gurobi::New(std::move(env));
98  } else {
99  return Gurobi::New();
100  }
101 }
102 
103 inline BasisStatusProto ConvertVariableStatus(const int status) {
104  switch (status) {
105  case GRB_BASIC:
106  return BASIS_STATUS_BASIC;
107  case GRB_NONBASIC_LOWER:
108  return BASIS_STATUS_AT_LOWER_BOUND;
109  case GRB_NONBASIC_UPPER:
110  return BASIS_STATUS_AT_UPPER_BOUND;
111  case GRB_SUPERBASIC:
112  return BASIS_STATUS_FREE;
113  default:
114  return BASIS_STATUS_UNSPECIFIED;
115  }
116 }
117 
118 inline int GrbVariableStatus(const BasisStatusProto status) {
119  switch (status) {
120  case BASIS_STATUS_BASIC:
121  return GRB_BASIC;
122  case BASIS_STATUS_AT_LOWER_BOUND:
123  case BASIS_STATUS_FIXED_VALUE:
124  return GRB_NONBASIC_LOWER;
125  case BASIS_STATUS_AT_UPPER_BOUND:
126  return GRB_NONBASIC_UPPER;
127  case BASIS_STATUS_FREE:
128  return GRB_SUPERBASIC;
129  case BASIS_STATUS_UNSPECIFIED:
130  default:
131  LOG(FATAL) << "Unexpected invalid initial_basis.";
132  return 0;
133  }
134 }
135 
136 GurobiParametersProto MergeParameters(
137  const SolveParametersProto& solve_parameters) {
138  GurobiParametersProto merged_parameters;
139 
140  {
141  GurobiParametersProto::Parameter* const parameter =
142  merged_parameters.add_parameters();
143  parameter->set_name(GRB_INT_PAR_LOGTOCONSOLE);
144  parameter->set_value(solve_parameters.enable_output() ? "1" : "0");
145  }
146 
147  if (solve_parameters.has_time_limit()) {
148  const double time_limit = absl::ToDoubleSeconds(
149  util_time::DecodeGoogleApiProto(solve_parameters.time_limit()).value());
150  GurobiParametersProto::Parameter* const parameter =
151  merged_parameters.add_parameters();
152  parameter->set_name(GRB_DBL_PAR_TIMELIMIT);
153  parameter->set_value(absl::StrCat(time_limit));
154  }
155 
156  if (solve_parameters.has_node_limit()) {
157  GurobiParametersProto::Parameter* const parameter =
158  merged_parameters.add_parameters();
159  parameter->set_name(GRB_DBL_PAR_NODELIMIT);
160  parameter->set_value(absl::StrCat(solve_parameters.node_limit()));
161  }
162 
163  if (solve_parameters.has_threads()) {
164  const int threads = solve_parameters.threads();
165  GurobiParametersProto::Parameter* const parameter =
166  merged_parameters.add_parameters();
167  parameter->set_name(GRB_INT_PAR_THREADS);
168  parameter->set_value(absl::StrCat(threads));
169  }
170 
171  if (solve_parameters.has_absolute_gap_tolerance()) {
172  const double absolute_gap_tolerance =
173  solve_parameters.absolute_gap_tolerance();
174  GurobiParametersProto::Parameter* const parameter =
175  merged_parameters.add_parameters();
176  parameter->set_name(GRB_DBL_PAR_MIPGAPABS);
177  parameter->set_value(absl::StrCat(absolute_gap_tolerance));
178  }
179 
180  if (solve_parameters.has_relative_gap_tolerance()) {
181  const double relative_gap_tolerance =
182  solve_parameters.relative_gap_tolerance();
183  GurobiParametersProto::Parameter* const parameter =
184  merged_parameters.add_parameters();
185  parameter->set_name(GRB_DBL_PAR_MIPGAP);
186  parameter->set_value(absl::StrCat(relative_gap_tolerance));
187  }
188 
189  if (solve_parameters.has_cutoff_limit()) {
190  GurobiParametersProto::Parameter* const parameter =
191  merged_parameters.add_parameters();
192  parameter->set_name(GRB_DBL_PAR_CUTOFF);
193  parameter->set_value(absl::StrCat(solve_parameters.cutoff_limit()));
194  }
195 
196  if (solve_parameters.has_objective_limit()) {
197  GurobiParametersProto::Parameter* const parameter =
198  merged_parameters.add_parameters();
199  parameter->set_name(GRB_DBL_PAR_BESTOBJSTOP);
200  parameter->set_value(absl::StrCat(solve_parameters.objective_limit()));
201  }
202 
203  if (solve_parameters.has_best_bound_limit()) {
204  GurobiParametersProto::Parameter* const parameter =
205  merged_parameters.add_parameters();
206  parameter->set_name(GRB_DBL_PAR_BESTBDSTOP);
207  parameter->set_value(absl::StrCat(solve_parameters.best_bound_limit()));
208  }
209 
210  if (solve_parameters.has_solution_limit()) {
211  GurobiParametersProto::Parameter* const parameter =
212  merged_parameters.add_parameters();
213  parameter->set_name(GRB_INT_PAR_SOLUTIONLIMIT);
214  parameter->set_value(absl::StrCat(solve_parameters.solution_limit()));
215  }
216 
217  if (solve_parameters.has_random_seed()) {
218  const int random_seed =
219  std::min(GRB_MAXINT, std::max(solve_parameters.random_seed(), 0));
220  GurobiParametersProto::Parameter* const parameter =
221  merged_parameters.add_parameters();
222  parameter->set_name(GRB_INT_PAR_SEED);
223  parameter->set_value(absl::StrCat(random_seed));
224  }
225 
226  if (solve_parameters.has_solution_pool_size()) {
227  GurobiParametersProto::Parameter* const solution_pool_size =
228  merged_parameters.add_parameters();
229  solution_pool_size->set_name(GRB_INT_PAR_POOLSOLUTIONS);
230  solution_pool_size->set_value(
231  absl::StrCat(solve_parameters.solution_pool_size()));
232  }
233 
234  if (solve_parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
235  GurobiParametersProto::Parameter* const parameter =
236  merged_parameters.add_parameters();
237  parameter->set_name(GRB_INT_PAR_METHOD);
238  switch (solve_parameters.lp_algorithm()) {
239  case LP_ALGORITHM_PRIMAL_SIMPLEX:
240  parameter->set_value(absl::StrCat(GRB_METHOD_PRIMAL));
241  break;
242  case LP_ALGORITHM_DUAL_SIMPLEX:
243  parameter->set_value(absl::StrCat(GRB_METHOD_DUAL));
244  break;
245  case LP_ALGORITHM_BARRIER:
246  parameter->set_value(absl::StrCat(GRB_METHOD_BARRIER));
247  break;
248  default:
249  LOG(FATAL) << "LPAlgorithm: "
250  << ProtoEnumToString(solve_parameters.lp_algorithm())
251  << " unknown, error setting Gurobi parameters";
252  }
253  }
254 
255  if (solve_parameters.scaling() != EMPHASIS_UNSPECIFIED) {
256  GurobiParametersProto::Parameter* const parameter =
257  merged_parameters.add_parameters();
258  parameter->set_name(GRB_INT_PAR_SCALEFLAG);
259  switch (solve_parameters.scaling()) {
260  case EMPHASIS_OFF:
261  parameter->set_value(absl::StrCat(0));
262  break;
263  case EMPHASIS_LOW:
264  case EMPHASIS_MEDIUM:
265  parameter->set_value(absl::StrCat(1));
266  break;
267  case EMPHASIS_HIGH:
268  parameter->set_value(absl::StrCat(2));
269  break;
270  case EMPHASIS_VERY_HIGH:
271  parameter->set_value(absl::StrCat(3));
272  break;
273  default:
274  LOG(FATAL) << "Scaling emphasis: "
275  << ProtoEnumToString(solve_parameters.scaling())
276  << " unknown, error setting Gurobi parameters";
277  }
278  }
279 
280  if (solve_parameters.cuts() != EMPHASIS_UNSPECIFIED) {
281  GurobiParametersProto::Parameter* const parameter =
282  merged_parameters.add_parameters();
283  parameter->set_name(GRB_INT_PAR_CUTS);
284  switch (solve_parameters.cuts()) {
285  case EMPHASIS_OFF:
286  parameter->set_value(absl::StrCat(0));
287  break;
288  case EMPHASIS_LOW:
289  case EMPHASIS_MEDIUM:
290  parameter->set_value(absl::StrCat(1));
291  break;
292  case EMPHASIS_HIGH:
293  parameter->set_value(absl::StrCat(2));
294  break;
295  case EMPHASIS_VERY_HIGH:
296  parameter->set_value(absl::StrCat(3));
297  break;
298  default:
299  LOG(FATAL) << "Cuts emphasis: "
300  << ProtoEnumToString(solve_parameters.cuts())
301  << " unknown, error setting Gurobi parameters";
302  }
303  }
304 
305  if (solve_parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
306  GurobiParametersProto::Parameter* const parameter =
307  merged_parameters.add_parameters();
308  parameter->set_name(GRB_DBL_PAR_HEURISTICS);
309  switch (solve_parameters.heuristics()) {
310  case EMPHASIS_OFF:
311  parameter->set_value(absl::StrCat(0.0));
312  break;
313  case EMPHASIS_LOW:
314  parameter->set_value(absl::StrCat(0.025));
315  break;
316  case EMPHASIS_MEDIUM:
317  // As of Gurobi 9.1 this is the default value.
318  // https://www.gurobi.com/documentation/9.1/refman/heuristics.html
319  parameter->set_value(absl::StrCat(0.05));
320  break;
321  case EMPHASIS_HIGH:
322  parameter->set_value(absl::StrCat(0.1));
323  break;
324  case EMPHASIS_VERY_HIGH:
325  parameter->set_value(absl::StrCat(0.2));
326  break;
327  default:
328  LOG(FATAL) << "Heuristics emphasis: "
329  << ProtoEnumToString(solve_parameters.heuristics())
330  << " unknown, error setting Gurobi parameters";
331  }
332  }
333 
334  if (solve_parameters.presolve() != EMPHASIS_UNSPECIFIED) {
335  GurobiParametersProto::Parameter* const parameter =
336  merged_parameters.add_parameters();
337  parameter->set_name(GRB_INT_PAR_PRESOLVE);
338  switch (solve_parameters.presolve()) {
339  case EMPHASIS_OFF:
340  parameter->set_value(absl::StrCat(0));
341  break;
342  case EMPHASIS_LOW:
343  case EMPHASIS_MEDIUM:
344  parameter->set_value(absl::StrCat(1));
345  break;
346  case EMPHASIS_HIGH:
347  case EMPHASIS_VERY_HIGH:
348  parameter->set_value(absl::StrCat(2));
349  break;
350  default:
351  LOG(FATAL) << "Presolve emphasis: "
352  << ProtoEnumToString(solve_parameters.presolve())
353  << " unknown, error setting Gurobi parameters";
354  }
355  }
356 
357  if (solve_parameters.has_iteration_limit()) {
358  GurobiParametersProto::Parameter* const iterationlimit =
359  merged_parameters.add_parameters();
360  iterationlimit->set_name(GRB_DBL_PAR_ITERATIONLIMIT);
361  iterationlimit->set_value(absl::StrCat(solve_parameters.iteration_limit()));
362  GurobiParametersProto::Parameter* const bariterlimit =
363  merged_parameters.add_parameters();
364  bariterlimit->set_name(GRB_INT_PAR_BARITERLIMIT);
365  double val = std::min<double>(std::numeric_limits<int32_t>::max(),
366  solve_parameters.iteration_limit());
367  bariterlimit->set_value(absl::StrCat(val));
368  }
369 
370  for (const GurobiParametersProto::Parameter& parameter :
371  solve_parameters.gurobi().parameters()) {
372  *merged_parameters.add_parameters() = parameter;
373  }
374 
375  return merged_parameters;
376 }
377 
378 absl::StatusOr<int64_t> SafeInt64FromDouble(const double d) {
379  const int64_t result = static_cast<int64_t>(d);
380  if (static_cast<double>(result) != d) {
381  return absl::InternalError(
382  absl::StrCat("Expected double ", d, " to contain an int64_t."));
383  }
384  return result;
385 }
386 
387 const absl::flat_hash_set<CallbackEventProto>& SupportedMIPEvents() {
388  static const auto* const kEvents =
389  new absl::flat_hash_set<CallbackEventProto>({
390  CALLBACK_EVENT_PRESOLVE, CALLBACK_EVENT_SIMPLEX, CALLBACK_EVENT_MIP,
391  CALLBACK_EVENT_MIP_SOLUTION, CALLBACK_EVENT_MIP_NODE,
392  // CALLBACK_EVENT_BARRIER is not supported when solving MIPs; it turns
393  // out that Gurobi uses a barrier algorithm to solve the root node
394  // relaxation (from the traces) but does not call the associated
395  // callback.
396  });
397  return *kEvents;
398 }
399 
400 const absl::flat_hash_set<CallbackEventProto>& SupportedLPEvents() {
401  static const auto* const kEvents =
402  new absl::flat_hash_set<CallbackEventProto>({
403  CALLBACK_EVENT_PRESOLVE,
404  CALLBACK_EVENT_SIMPLEX,
405  CALLBACK_EVENT_BARRIER,
406  });
407  return *kEvents;
408 }
409 
410 // Gurobi names (model, variables and constraints) must be no longer than 255
411 // characters; or Gurobi fails with an error.
412 constexpr std::size_t kMaxNameSize = 255;
413 
414 // Returns a string of at most kMaxNameSize max size.
415 std::string TruncateName(const std::string_view original_name) {
416  return std::string(
417  original_name.substr(0, std::min(kMaxNameSize, original_name.size())));
418 }
419 
420 // Truncate the names of variables and constraints.
421 std::vector<std::string> TruncateNames(
422  const google::protobuf::RepeatedPtrField<std::string>& original_names) {
423  std::vector<std::string> result;
424  result.reserve(original_names.size());
425  for (const std::string& original_name : original_names) {
426  result.push_back(TruncateName(original_name));
427  }
428  return result;
429 }
430 
431 absl::Status SafeGurobiDouble(const double d) {
432  if (std::isfinite(d) && std::abs(d) >= GRB_INFINITY) {
434  << "finite value: " << d << " will be treated as infinite by Gurobi";
435  }
436  return absl::OkStatus();
437 }
438 
439 std::string EscapedNameForLogging(const absl::string_view name) {
440  return absl::StrCat("\"", absl::Utf8SafeCEscape(name), "\"");
441 }
442 
443 constexpr int kDeletedIndex = -1;
444 constexpr int kUnsetIndex = -2;
445 // Returns a vector of length `size_before_delete` that logically provides a
446 // mapping from the starting contiguous range [0, ..., size_before_delete) to
447 // a potentially smaller range [0, ..., num_remaining_elems) after deleting
448 // each element in `deletes` and shifting the remaining elements such that they
449 // are contiguous starting at 0. The elements in the output point to the new
450 // shifted index, or `kDeletedIndex` if the starting index was deleted.
451 std::vector<int> IndexUpdateMap(const int size_before_delete,
452  const std::vector<int>& deletes) {
453  std::vector<int> result(size_before_delete, kUnsetIndex);
454  for (const int del : deletes) {
455  result[del] = kDeletedIndex;
456  }
457  int next_free = 0;
458  for (int& r : result) {
459  if (r != kDeletedIndex) {
460  r = next_free;
461  ++next_free;
462  }
463  CHECK_GT(r, kUnsetIndex);
464  }
465  return result;
466 }
467 
468 } // namespace
469 
470 GurobiSolver::GurobiSolver(std::unique_ptr<Gurobi> g_gurobi)
471  : gurobi_(std::move(g_gurobi)) {}
472 
473 absl::StatusOr<TerminationProto> GurobiSolver::ConvertTerminationReason(
474  const int gurobi_status, const SolutionClaims solution_claims) {
475  switch (gurobi_status) {
476  case GRB_OPTIMAL:
477  return TerminateForReason(TERMINATION_REASON_OPTIMAL);
478  case GRB_INFEASIBLE:
479  return TerminateForReason(TERMINATION_REASON_INFEASIBLE);
480  case GRB_UNBOUNDED:
481  if (solution_claims.primal_feasible_solution_exists) {
482  return TerminateForReason(TERMINATION_REASON_UNBOUNDED);
483  }
484  return TerminateForReason(TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
485  "Gurobi status GRB_UNBOUNDED");
486  case GRB_INF_OR_UNBD:
487  return TerminateForReason(TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
488  "Gurobi status GRB_INF_OR_UNBD");
489  case GRB_CUTOFF:
490  return TerminateForLimit(LIMIT_CUTOFF,
491  /*feasible=*/false, "Gurobi status GRB_CUTOFF");
492  case GRB_ITERATION_LIMIT:
493  return TerminateForLimit(
494  LIMIT_ITERATION,
495  /*feasible=*/solution_claims.primal_feasible_solution_exists);
496  case GRB_NODE_LIMIT:
497  return TerminateForLimit(
498  LIMIT_NODE,
499  /*feasible=*/solution_claims.primal_feasible_solution_exists);
500  case GRB_TIME_LIMIT:
501  return TerminateForLimit(
502  LIMIT_TIME,
503  /*feasible=*/solution_claims.primal_feasible_solution_exists);
504  case GRB_SOLUTION_LIMIT:
505  return TerminateForLimit(
506  LIMIT_SOLUTION,
507  /*feasible=*/solution_claims.primal_feasible_solution_exists);
508  case GRB_INTERRUPTED:
509  return TerminateForLimit(
510  LIMIT_INTERRUPTED,
511  /*feasible=*/solution_claims.primal_feasible_solution_exists);
512  case GRB_NUMERIC:
513  return TerminateForReason(TERMINATION_REASON_NUMERICAL_ERROR);
514  case GRB_SUBOPTIMAL:
515  return TerminateForReason(TERMINATION_REASON_IMPRECISE);
516  case GRB_USER_OBJ_LIMIT:
517  // TODO(b/214567536): maybe we should override
518  // solution_claims.primal_feasible_solution_exists to true or false
519  // depending on whether objective_limit and best_bound_limit triggered
520  // this. Not sure if it's possible to detect this though.
521  return TerminateForLimit(
522  LIMIT_OBJECTIVE,
523  /*feasible=*/solution_claims.primal_feasible_solution_exists);
524  case GRB_LOADED:
525  return absl::InternalError(
526  "Error creating termination reason, unexpected gurobi status code "
527  "GRB_LOADED.");
528  case GRB_INPROGRESS:
529  return absl::InternalError(
530  "Error creating termination reason, unexpected gurobi status code "
531  "GRB_INPROGRESS.");
532  default:
533  return absl::InternalError(absl::StrCat(
534  "Missing Gurobi optimization status code case: ", gurobi_status));
535  }
536 }
537 
538 absl::StatusOr<bool> GurobiSolver::IsMaximize() const {
539  ASSIGN_OR_RETURN(const int obj_sense,
540  gurobi_->GetIntAttr(GRB_INT_ATTR_MODELSENSE));
541  return obj_sense == GRB_MAXIMIZE;
542 }
543 
544 absl::StatusOr<bool> GurobiSolver::IsMIP() const {
545  ASSIGN_OR_RETURN(const int is_mip, gurobi_->GetIntAttr(GRB_INT_ATTR_IS_MIP));
546  return static_cast<bool>(is_mip);
547 }
548 
549 // TODO(b/204595455): Revisit logic when nonconvex QP support is decided upon
550 absl::StatusOr<bool> GurobiSolver::IsQP() const {
551  ASSIGN_OR_RETURN(const int is_qp, gurobi_->GetIntAttr(GRB_INT_ATTR_IS_QP));
552  return static_cast<bool>(is_qp);
553 }
554 
555 absl::StatusOr<bool> GurobiSolver::IsQCP() const {
556  ASSIGN_OR_RETURN(const int is_qcp, gurobi_->GetIntAttr(GRB_INT_ATTR_IS_QCP));
557  return static_cast<bool>(is_qcp);
558 }
559 
560 // TODO(user): switch the use of this function to something closer to
561 // GetGurobiDualRay()
562 template <typename T>
563 void GurobiSolver::GurobiVectorToSparseDoubleVector(
564  const absl::Span<const double> gurobi_values, const T& map,
565  SparseDoubleVectorProto& result,
566  const SparseVectorFilterProto& filter) const {
567  SparseVectorFilterPredicate predicate(filter);
568  for (auto [id, gurobi_data] : map) {
569  const double value = gurobi_values[get_model_index(gurobi_data)];
570  if (predicate.AcceptsAndUpdate(id, value)) {
571  result.add_ids(id);
572  result.add_values(value);
573  }
574  }
575 }
576 
577 absl::Status GurobiSolver::SetGurobiBasis(const BasisProto& basis) {
578  std::vector<int> gurobi_variable_basis_status(num_gurobi_variables_);
579  for (const auto [id, value] : MakeView(basis.variable_status())) {
580  gurobi_variable_basis_status[variables_map_.at(id)] =
581  GrbVariableStatus(static_cast<BasisStatusProto>(value));
582  }
583 
584  std::vector<int> gurobi_constraint_basis_status;
585  gurobi_constraint_basis_status.reserve(num_gurobi_lin_cons_);
586  for (const auto [id, value] : MakeView(basis.constraint_status())) {
587  const LinearConstraintData& constraint_data =
588  linear_constraints_map_.at(id);
589  // Non-ranged constraints
590  if (constraint_data.slack_index == kUnspecifiedIndex) {
591  if (value == BASIS_STATUS_BASIC) {
592  gurobi_constraint_basis_status.push_back(kGrbBasicConstraint);
593  } else {
594  gurobi_constraint_basis_status.push_back(kGrbNonBasicConstraint);
595  }
596  // Ranged constraints
597  } else if (value == BASIS_STATUS_BASIC) {
598  // Either constraint or MathOpt slack is basic, but not both (because
599  // columns for MathOpt slack and internal Gurobi slack are linearly
600  // dependent). We choose the MathOpt slack to be basic.
601  gurobi_variable_basis_status[constraint_data.slack_index] = GRB_BASIC;
602  gurobi_constraint_basis_status.push_back(kGrbNonBasicConstraint);
603  } else {
604  gurobi_variable_basis_status[constraint_data.slack_index] =
605  GrbVariableStatus(static_cast<BasisStatusProto>(value));
606  gurobi_constraint_basis_status.push_back(kGrbNonBasicConstraint);
607  }
608  }
609  RETURN_IF_ERROR(gurobi_->SetIntAttrArray(GRB_INT_ATTR_VBASIS,
610  gurobi_variable_basis_status));
611  RETURN_IF_ERROR(gurobi_->SetIntAttrArray(GRB_INT_ATTR_CBASIS,
612  gurobi_constraint_basis_status));
613  return absl::OkStatus();
614 }
615 
616 absl::StatusOr<BasisProto> GurobiSolver::GetGurobiBasis() {
617  BasisProto basis;
619  const std::vector<int> gurobi_variable_basis_status,
620  gurobi_->GetIntAttrArray(GRB_INT_ATTR_VBASIS, num_gurobi_variables_));
621 
622  for (auto [variable_id, gurobi_variable_index] : variables_map_) {
623  basis.mutable_variable_status()->add_ids(variable_id);
624  const BasisStatusProto variable_status = ConvertVariableStatus(
625  gurobi_variable_basis_status[gurobi_variable_index]);
626  if (variable_status == BASIS_STATUS_UNSPECIFIED) {
627  return absl::InternalError(
628  absl::StrCat("Invalid Gurobi variable basis status: ",
629  gurobi_variable_basis_status[gurobi_variable_index]));
630  }
631  basis.mutable_variable_status()->add_values(variable_status);
632  }
633 
635  const std::vector<int> gurobi_constraint_basis_status,
636  gurobi_->GetIntAttrArray(GRB_INT_ATTR_CBASIS, num_gurobi_lin_cons_));
637  for (auto [constraint_id, gurobi_data] : linear_constraints_map_) {
638  basis.mutable_constraint_status()->add_ids(constraint_id);
639  const int gurobi_constraint_status =
640  gurobi_constraint_basis_status[gurobi_data.constraint_index];
641  if ((gurobi_constraint_status != kGrbBasicConstraint) &&
642  (gurobi_constraint_status != kGrbNonBasicConstraint)) {
643  return absl::InternalError(
644  absl::StrCat("Invalid Gurobi constraint basis status: ",
645  gurobi_constraint_status));
646  }
647  // linear_terms <= upper_bound
648  if (gurobi_data.lower_bound <= -GRB_INFINITY &&
649  gurobi_data.upper_bound < GRB_INFINITY) {
650  if (gurobi_constraint_status == kGrbBasicConstraint) {
651  basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
652  } else {
653  basis.mutable_constraint_status()->add_values(
654  BASIS_STATUS_AT_UPPER_BOUND);
655  }
656  // linear_terms >= lower_bound
657  } else if (gurobi_data.lower_bound > -GRB_INFINITY &&
658  gurobi_data.upper_bound >= GRB_INFINITY) {
659  if (gurobi_constraint_status == kGrbBasicConstraint) {
660  basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
661  } else {
662  basis.mutable_constraint_status()->add_values(
663  BASIS_STATUS_AT_LOWER_BOUND);
664  }
665  // linear_terms == xxxxx_bound
666  } else if (gurobi_data.lower_bound == gurobi_data.upper_bound) {
667  if (gurobi_constraint_status == kGrbBasicConstraint) {
668  basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
669  } else {
670  // TODO(user): consider refining this to
671  // AT_LOWER_BOUND/AT_UPPER_BOUND using the sign of the dual variable.
672  basis.mutable_constraint_status()->add_values(BASIS_STATUS_FIXED_VALUE);
673  }
674  // linear_term - slack == 0 (ranged constraint)
675  } else {
676  const BasisStatusProto slack_status = ConvertVariableStatus(
677  gurobi_variable_basis_status[gurobi_data.slack_index]);
678  if (slack_status == BASIS_STATUS_UNSPECIFIED) {
679  return absl::InternalError(absl::StrCat(
680  "Invalid Gurobi slack variable basis status: ", slack_status));
681  }
682  if ((gurobi_constraint_status == kGrbBasicConstraint) ||
683  (slack_status == BASIS_STATUS_BASIC)) {
684  basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
685  } else {
686  basis.mutable_constraint_status()->add_values(slack_status);
687  }
688  }
689  }
690  return basis;
691 }
692 
693 // See go/mathopt-dev-transformations#gurobi-inf for details of this
694 // transformation, comments inside the function refer to the notation there.
695 absl::StatusOr<DualRayProto> GurobiSolver::GetGurobiDualRay(
696  const SparseVectorFilterProto& linear_constraints_filter,
697  const SparseVectorFilterProto& variables_filter, const bool is_maximize) {
698  // farkas_dual = lambda
699  ASSIGN_OR_RETURN(const std::vector<double> farkas_dual,
700  gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_FARKASDUAL,
701  num_gurobi_lin_cons_));
702 
703  DualRayProto dual_ray;
704 
705  // Compute y = -lambda
706  {
707  SparseVectorFilterPredicate predicate(linear_constraints_filter);
708  for (auto [constraint_id, gurobi_data] : linear_constraints_map_) {
709  // constraint_dual_value = y[constraint_id]
710  const double value = -farkas_dual[gurobi_data.constraint_index];
711  if (predicate.AcceptsAndUpdate(constraint_id, value)) {
712  dual_ray.mutable_dual_values()->add_ids(constraint_id);
713  if (is_maximize) {
714  dual_ray.mutable_dual_values()->add_values(-value);
715  } else {
716  dual_ray.mutable_dual_values()->add_values(value);
717  }
718  }
719  }
720  }
721 
722  // Compute r = \bar{a} = A^T lambda
723  {
724  SparseVectorFilterPredicate predicate(variables_filter);
725  for (auto [var_id, gurobi_variable_index] : variables_map_) {
726  // reduced_cost_value = r[gurobi_variable_index]
727  // = \bar{a}[gurobi_variable_index]
728  double reduced_cost_value = 0.0;
729  ASSIGN_OR_RETURN(Gurobi::SparseMat column,
730  gurobi_->GetVars(gurobi_variable_index, 1));
731  for (int i = 0; i < column.inds.size(); ++i) {
732  reduced_cost_value += farkas_dual[column.inds[i]] * column.vals[i];
733  }
734  if (predicate.AcceptsAndUpdate(var_id, reduced_cost_value)) {
735  dual_ray.mutable_reduced_costs()->add_ids(var_id);
736  if (is_maximize) {
737  dual_ray.mutable_reduced_costs()->add_values(-reduced_cost_value);
738  } else {
739  dual_ray.mutable_reduced_costs()->add_values(reduced_cost_value);
740  }
741  }
742  }
743  }
744  return dual_ray;
745 }
746 
747 absl::StatusOr<ProblemStatusProto> GurobiSolver::GetProblemStatus(
748  const int grb_termination, const SolutionClaims solution_claims) {
749  ProblemStatusProto problem_status;
750 
751  // Set default statuses
752  problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
753  problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
754 
755  // Set feasibility statuses
756  if (solution_claims.primal_feasible_solution_exists) {
757  problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
758  }
759  if (solution_claims.dual_feasible_solution_exists) {
760  problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
761  }
762 
763  // Process infeasible conclusions from grb_termination.
764  switch (grb_termination) {
765  case GRB_INFEASIBLE:
766  problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
767  if (solution_claims.primal_feasible_solution_exists) {
768  return absl::InternalError(
769  "GRB_INT_ATTR_STATUS == GRB_INFEASIBLE, but a primal feasible "
770  "solution was returned.");
771  }
772  break;
773  case GRB_UNBOUNDED:
774  // GRB_UNBOUNDED does necessarily imply the primal is feasible
775  // https://www.gurobi.com/documentation/9.1/refman/optimization_status_codes.html
776  problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
777  if (solution_claims.dual_feasible_solution_exists) {
778  return absl::InternalError(
779  "GRB_INT_ATTR_STATUS == GRB_UNBOUNDED, but a dual feasible "
780  "solution was returned or exists.");
781  }
782  break;
783  case GRB_INF_OR_UNBD:
784  problem_status.set_primal_or_dual_infeasible(true);
785  if (solution_claims.primal_feasible_solution_exists) {
786  return absl::InternalError(
787  "GRB_INT_ATTR_STATUS == GRB_INF_OR_UNBD, but a primal feasible "
788  "solution was returned.");
789  }
790  if (solution_claims.dual_feasible_solution_exists) {
791  return absl::InternalError(
792  "GRB_INT_ATTR_STATUS == GRB_INF_OR_UNBD, but a dual feasible "
793  "solution was returned or exists.");
794  }
795  break;
796  }
797  return problem_status;
798 }
799 
800 absl::StatusOr<SolveResultProto> GurobiSolver::ExtractSolveResultProto(
801  const absl::Time start, const ModelSolveParametersProto& model_parameters) {
802  SolveResultProto result;
803 
804  ASSIGN_OR_RETURN((auto [solutions, solution_claims]),
805  GetSolutions(model_parameters));
806 
807  // TODO(b/195295177): Add tests for rays in unbounded MIPs
808  RETURN_IF_ERROR(FillRays(model_parameters, solution_claims, result));
809 
810  for (auto& solution : solutions) {
811  *result.add_solutions() = std::move(solution);
812  }
813 
814  ASSIGN_OR_RETURN(*result.mutable_solve_stats(),
815  GetSolveStats(start, solution_claims));
816 
817  ASSIGN_OR_RETURN(const int grb_termination,
818  gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
819  ASSIGN_OR_RETURN(*result.mutable_termination(),
820  ConvertTerminationReason(grb_termination, solution_claims));
821  return std::move(result);
822 }
823 
824 absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetSolutions(
825  const ModelSolveParametersProto& model_parameters) {
826  ASSIGN_OR_RETURN(const bool is_mip, IsMIP());
827  ASSIGN_OR_RETURN(const bool is_qp, IsQP());
828  ASSIGN_OR_RETURN(const bool is_qcp, IsQCP());
829 
830  if (is_mip) {
831  return GetMipSolutions(model_parameters);
832  } else if (is_qcp) {
833  return GetQcpSolution(model_parameters);
834  } else if (is_qp) {
835  return GetQpSolution(model_parameters);
836  } else {
837  return GetLpSolution(model_parameters);
838  }
839 }
840 
841 absl::StatusOr<SolveStatsProto> GurobiSolver::GetSolveStats(
842  const absl::Time start, const SolutionClaims solution_claims) {
843  SolveStatsProto solve_stats;
844 
845  CHECK_OK(util_time::EncodeGoogleApiProto(absl::Now() - start,
846  solve_stats.mutable_solve_time()));
847 
848  ASSIGN_OR_RETURN(const double best_primal_bound,
849  GetBestPrimalBound(
850  /*has_primal_feasible_solution=*/solution_claims
851  .primal_feasible_solution_exists));
852  solve_stats.set_best_primal_bound(best_primal_bound);
853 
854  ASSIGN_OR_RETURN(double best_dual_bound, GetBestDualBound());
855  solve_stats.set_best_dual_bound(best_dual_bound);
856 
857  ASSIGN_OR_RETURN(const int grb_termination,
858  gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
859  ASSIGN_OR_RETURN((*solve_stats.mutable_problem_status()),
860  GetProblemStatus(grb_termination, solution_claims));
861 
862  if (gurobi_->IsAttrAvailable(GRB_DBL_ATTR_ITERCOUNT)) {
863  ASSIGN_OR_RETURN(const double simplex_iters_double,
864  gurobi_->GetDoubleAttr(GRB_DBL_ATTR_ITERCOUNT));
865  ASSIGN_OR_RETURN(const int64_t simplex_iters,
866  SafeInt64FromDouble(simplex_iters_double));
867  solve_stats.set_simplex_iterations(simplex_iters);
868  }
869 
870  if (gurobi_->IsAttrAvailable(GRB_INT_ATTR_BARITERCOUNT)) {
871  ASSIGN_OR_RETURN(const int barrier_iters,
872  gurobi_->GetIntAttr(GRB_INT_ATTR_BARITERCOUNT));
873  solve_stats.set_barrier_iterations(barrier_iters);
874  }
875 
876  if (gurobi_->IsAttrAvailable(GRB_DBL_ATTR_NODECOUNT)) {
877  ASSIGN_OR_RETURN(const double nodes_double,
878  gurobi_->GetDoubleAttr(GRB_DBL_ATTR_NODECOUNT));
879  ASSIGN_OR_RETURN(const int64_t nodes, SafeInt64FromDouble(nodes_double));
880  solve_stats.set_node_count(nodes);
881  }
882  return solve_stats;
883 }
884 
885 absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetMipSolutions(
886  const ModelSolveParametersProto& model_parameters) {
887  int num_solutions = 0;
888  if (gurobi_->IsAttrAvailable(GRB_INT_ATTR_SOLCOUNT)) {
889  ASSIGN_OR_RETURN(num_solutions, gurobi_->GetIntAttr(GRB_INT_ATTR_SOLCOUNT));
890  }
891  std::vector<SolutionProto> solutions;
892  solutions.reserve(num_solutions);
893  for (int i = 0; i < num_solutions; ++i) {
894  RETURN_IF_ERROR(gurobi_->SetIntParam(GRB_INT_PAR_SOLUTIONNUMBER, i));
895 
896  PrimalSolutionProto primal_solution;
897  ASSIGN_OR_RETURN(const double sol_val,
898  gurobi_->GetDoubleAttr(GRB_DBL_ATTR_POOLOBJVAL));
899  primal_solution.set_objective_value(sol_val);
900  primal_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
902  const std::vector<double> grb_var_values,
903  gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_XN, num_gurobi_variables_));
904  GurobiVectorToSparseDoubleVector(grb_var_values, variables_map_,
905  *primal_solution.mutable_variable_values(),
906  model_parameters.variable_values_filter());
907  *solutions.emplace_back(SolutionProto()).mutable_primal_solution() =
908  std::move(primal_solution);
909  }
910 
911  // Set solution claims
912  ASSIGN_OR_RETURN(const double best_dual_bound, GetBestDualBound());
913  // Note: here the existence of a dual solution refers to a dual solution to
914  // some convex relaxation of the MIP. This convex relaxation can likely be
915  // interpreted as an LP between the LP relaxation of the MIP and the convex
916  // hull of feasible solutions of the MIP. However, here we only use the fact
917  // that best_dual_bound being finite implies the existence of the trivial
918  // convex relaxation given by (assuming a minimization problem with objective
919  // function c^T x): min{c^T x : c^T x >= best_dual_bound}.
920  const SolutionClaims solution_claims = {
921  .primal_feasible_solution_exists = num_solutions > 0,
922  .dual_feasible_solution_exists = std::isfinite(best_dual_bound)};
923 
924  // Check consistency of solutions, bounds and statuses.
925  ASSIGN_OR_RETURN(const int grb_termination,
926  gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
927  if (grb_termination == GRB_OPTIMAL && num_solutions == 0) {
928  return absl::InternalError(
929  "GRB_INT_ATTR_STATUS == GRB_OPTIMAL, but solution pool is empty.");
930  }
931  if (grb_termination == GRB_OPTIMAL && !std::isfinite(best_dual_bound)) {
932  return absl::InternalError(
933  "GRB_INT_ATTR_STATUS == GRB_OPTIMAL, but GRB_DBL_ATTR_OBJBOUND is "
934  "unavailable or infinite.");
935  }
936 
937  return SolutionsAndClaims{.solutions = std::move(solutions),
938  .solution_claims = solution_claims};
939 }
940 
941 absl::StatusOr<GurobiSolver::SolutionAndClaim<PrimalSolutionProto>>
942 GurobiSolver::GetConvexPrimalSolutionIfAvailable(
943  const ModelSolveParametersProto& model_parameters) {
944  if (!gurobi_->IsAttrAvailable(GRB_DBL_ATTR_X)) {
945  return SolutionAndClaim<PrimalSolutionProto>{
946  .solution = std::nullopt, .feasible_solution_exists = false};
947  }
948  ASSIGN_OR_RETURN(const int grb_termination,
949  gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
950 
951  // Get primal solutions if available.
953  const std::vector<double> grb_var_values,
954  gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_X, num_gurobi_variables_));
955 
956  PrimalSolutionProto primal_solution;
957  // As noted in go/gurobi-objval-bug the objective value may be missing for
958  // primal feasible solutions for unbounded problems.
959  // TODO(b/195295177): for GRB_ITERATION_LIMIT an objective value of 0.0 is
960  // returned which breaks LpIncompleteSolveTest.PrimalSimplexAlgorithm. Explore
961  // more and make simple example to file a bug.
962  if (gurobi_->IsAttrAvailable(GRB_DBL_ATTR_OBJVAL) &&
963  grb_termination != GRB_ITERATION_LIMIT) {
964  ASSIGN_OR_RETURN(const double sol_val,
965  gurobi_->GetDoubleAttr(GRB_DBL_ATTR_OBJVAL));
966  primal_solution.set_objective_value(sol_val);
967  } else {
968  double objective_value = 0.0;
970  const std::vector<double> linear_obj_coefs,
971  gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_OBJ, num_gurobi_variables_));
972  for (int i = 0; i < num_gurobi_variables_; ++i) {
973  objective_value += linear_obj_coefs[i] * grb_var_values[i];
974  }
975  primal_solution.set_objective_value(objective_value);
976  }
977 
978  primal_solution.set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
979  if (grb_termination == GRB_OPTIMAL) {
980  primal_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
981  } else if (grb_termination == GRB_INFEASIBLE) {
982  primal_solution.set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
983  } else if (PrimalSolutionQualityAvailable()) {
984  ASSIGN_OR_RETURN(const double solution_quality, GetPrimalSolutionQuality());
985  ASSIGN_OR_RETURN(const double tolerance,
986  gurobi_->GetDoubleParam(GRB_DBL_PAR_FEASIBILITYTOL));
987  if (solution_quality <= tolerance) {
988  primal_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
989  } else {
990  primal_solution.set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
991  }
992  }
993 
994  GurobiVectorToSparseDoubleVector(grb_var_values, variables_map_,
995  *primal_solution.mutable_variable_values(),
996  model_parameters.variable_values_filter());
997  const bool primal_feasible_solution_exists =
998  (primal_solution.feasibility_status() == SOLUTION_STATUS_FEASIBLE);
999  return SolutionAndClaim<PrimalSolutionProto>{
1000  .solution = std::move(primal_solution),
1001  .feasible_solution_exists = primal_feasible_solution_exists};
1002 }
1003 
1004 bool GurobiSolver::PrimalSolutionQualityAvailable() const {
1005  return gurobi_->IsAttrAvailable(GRB_DBL_ATTR_CONSTR_RESIDUAL) &&
1006  gurobi_->IsAttrAvailable(GRB_DBL_ATTR_CONSTR_VIO) &&
1007  gurobi_->IsAttrAvailable(GRB_DBL_ATTR_BOUND_VIO) &&
1008  gurobi_->IsAttrAvailable(GRB_DBL_ATTR_CONSTR_SRESIDUAL) &&
1009  gurobi_->IsAttrAvailable(GRB_DBL_ATTR_CONSTR_SVIO) &&
1010  gurobi_->IsAttrAvailable(GRB_DBL_ATTR_BOUND_SVIO);
1011 }
1012 
1013 absl::StatusOr<double> GurobiSolver::GetPrimalSolutionQuality() const {
1014  ASSIGN_OR_RETURN(const double constraint_residual,
1015  gurobi_->GetDoubleAttr(GRB_DBL_ATTR_CONSTR_RESIDUAL));
1016  ASSIGN_OR_RETURN(const double constraint_violation,
1017  gurobi_->GetDoubleAttr(GRB_DBL_ATTR_CONSTR_VIO));
1018  ASSIGN_OR_RETURN(const double bound_violation,
1019  gurobi_->GetDoubleAttr(GRB_DBL_ATTR_BOUND_VIO));
1020  ASSIGN_OR_RETURN(const double constraint_scaled_residual,
1021  gurobi_->GetDoubleAttr(GRB_DBL_ATTR_CONSTR_SRESIDUAL));
1022  ASSIGN_OR_RETURN(const double constraint_scaled_violation,
1023  gurobi_->GetDoubleAttr(GRB_DBL_ATTR_CONSTR_SVIO));
1024  ASSIGN_OR_RETURN(const double bound_scaled_violation,
1025  gurobi_->GetDoubleAttr(GRB_DBL_ATTR_BOUND_SVIO));
1026  return std::max({constraint_residual, constraint_violation, bound_violation,
1027  constraint_scaled_residual, constraint_scaled_violation,
1028  bound_scaled_violation});
1029 }
1030 
1031 absl::StatusOr<double> GurobiSolver::GetBestPrimalBound(
1032  const bool has_primal_feasible_solution) {
1033  ASSIGN_OR_RETURN(const bool is_maximize, IsMaximize());
1034  // We need has_primal_feasible_solution because, as noted in
1035  // go/gurobi-objval-bug, GRB_DBL_ATTR_OBJVAL may be available and finite for
1036  // primal infeasible solutions.
1037  if (has_primal_feasible_solution &&
1038  gurobi_->IsAttrAvailable(GRB_DBL_ATTR_OBJVAL)) {
1039  // TODO(b/195295177): Discuss if this should be removed. Unlike the dual
1040  // case below, it appears infesible models do not return GRB_DBL_ATTR_OBJVAL
1041  // equal to GRB_INFINITY (GRB_DBL_ATTR_OBJVAL is just unavailable). Hence,
1042  // this may not be needed and may not be consistent (e.g. we should explore
1043  // whether GRB_DBL_ATTR_OBJVAL = GRB_INFINITY may happen for a primal
1044  // feasible solution, in which the conversion of +/-GRB_INFINITY to +/-kInf
1045  // would not be consistent). Note that unlike the dual case removing this
1046  // does not break any test.
1047  ASSIGN_OR_RETURN(const double obj_val,
1048  gurobi_->GetDoubleAttr(GRB_DBL_ATTR_OBJVAL));
1049  if (std::abs(obj_val) < GRB_INFINITY) {
1050  return obj_val;
1051  }
1052  }
1053  return is_maximize ? -kInf : kInf;
1054 }
1055 
1056 absl::StatusOr<double> GurobiSolver::GetBestDualBound() {
1057  if (gurobi_->IsAttrAvailable(GRB_DBL_ATTR_OBJBOUND)) {
1058  ASSIGN_OR_RETURN(const double obj_bound,
1059  gurobi_->GetDoubleAttr(GRB_DBL_ATTR_OBJBOUND));
1060  // Note: Unbounded models return GRB_DBL_ATTR_OBJBOUND = GRB_INFINITY so
1061  // the conversion of +/-GRB_INFINITY to +/-kInf is needed and consistent.
1062  if (std::abs(obj_bound) < GRB_INFINITY) {
1063  return obj_bound;
1064  }
1065  }
1066  ASSIGN_OR_RETURN(const bool is_maximize, IsMaximize());
1067  return is_maximize ? kInf : -kInf;
1068 }
1069 
1070 absl::StatusOr<std::optional<BasisProto>> GurobiSolver::GetBasisIfAvailable() {
1071  if (gurobi_->IsAttrAvailable(GRB_INT_ATTR_VBASIS) &&
1072  gurobi_->IsAttrAvailable(GRB_INT_ATTR_CBASIS)) {
1073  ASSIGN_OR_RETURN(BasisProto basis, GetGurobiBasis());
1074  ASSIGN_OR_RETURN(const int grb_termination,
1075  gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
1076  basis.set_basic_dual_feasibility(SOLUTION_STATUS_UNDETERMINED);
1077  if (grb_termination == GRB_OPTIMAL) {
1078  basis.set_basic_dual_feasibility(SOLUTION_STATUS_FEASIBLE);
1079  } else if (grb_termination == GRB_UNBOUNDED) {
1080  basis.set_basic_dual_feasibility(SOLUTION_STATUS_INFEASIBLE);
1081  }
1082  // TODO(b/195295177): double check if the move is needed
1083  return std::move(basis);
1084  }
1085  return std::nullopt;
1086 }
1087 
1088 absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetLpSolution(
1089  const ModelSolveParametersProto& model_parameters) {
1090  ASSIGN_OR_RETURN(auto primal_solution_and_claim,
1091  GetConvexPrimalSolutionIfAvailable(model_parameters));
1092  ASSIGN_OR_RETURN(auto dual_solution_and_claim,
1093  GetLpDualSolutionIfAvailable(model_parameters));
1094  ASSIGN_OR_RETURN(auto basis, GetBasisIfAvailable());
1095  const SolutionClaims solution_claims = {
1096  .primal_feasible_solution_exists =
1097  primal_solution_and_claim.feasible_solution_exists,
1098  .dual_feasible_solution_exists =
1099  dual_solution_and_claim.feasible_solution_exists};
1100 
1101  if (!primal_solution_and_claim.solution.has_value() &&
1102  !dual_solution_and_claim.solution.has_value() && !basis.has_value()) {
1103  return SolutionsAndClaims{.solution_claims = solution_claims};
1104  }
1105  SolutionsAndClaims solution_and_claims{.solution_claims = solution_claims};
1106  SolutionProto& solution =
1107  solution_and_claims.solutions.emplace_back(SolutionProto());
1108  if (primal_solution_and_claim.solution.has_value()) {
1109  *solution.mutable_primal_solution() =
1110  std::move(*primal_solution_and_claim.solution);
1111  }
1112  if (dual_solution_and_claim.solution.has_value()) {
1113  *solution.mutable_dual_solution() =
1114  std::move(*dual_solution_and_claim.solution);
1115  }
1116  if (basis.has_value()) {
1117  *solution.mutable_basis() = std::move(*basis);
1118  }
1119  return solution_and_claims;
1120 }
1121 
1122 absl::StatusOr<GurobiSolver::SolutionAndClaim<DualSolutionProto>>
1123 GurobiSolver::GetLpDualSolutionIfAvailable(
1124  const ModelSolveParametersProto& model_parameters) {
1125  if (!gurobi_->IsAttrAvailable(GRB_DBL_ATTR_PI) ||
1126  !gurobi_->IsAttrAvailable(GRB_DBL_ATTR_RC)) {
1127  return SolutionAndClaim<DualSolutionProto>{
1128  .solution = std::nullopt, .feasible_solution_exists = false};
1129  }
1130 
1131  // Note that we can ignore the reduced costs of the slack variables for
1132  // ranged constraints because of
1133  // go/mathopt-dev-transformations#slack-var-range-constraint
1134  DualSolutionProto dual_solution;
1135  bool dual_feasible_solution_exists = false;
1137  const std::vector<double> grb_constraint_duals,
1138  gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_PI, num_gurobi_lin_cons_));
1139  GurobiVectorToSparseDoubleVector(grb_constraint_duals,
1140  linear_constraints_map_,
1141  *dual_solution.mutable_dual_values(),
1142  model_parameters.dual_values_filter());
1143 
1145  const std::vector<double> grb_reduced_cost_values,
1146  gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_RC, num_gurobi_variables_));
1147  GurobiVectorToSparseDoubleVector(grb_reduced_cost_values, variables_map_,
1148  *dual_solution.mutable_reduced_costs(),
1149  model_parameters.reduced_costs_filter());
1150 
1151  ASSIGN_OR_RETURN(const int grb_termination,
1152  gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
1153  if (grb_termination == GRB_OPTIMAL &&
1154  gurobi_->IsAttrAvailable(GRB_DBL_ATTR_OBJVAL)) {
1155  ASSIGN_OR_RETURN(const double obj_val,
1156  gurobi_->GetDoubleAttr(GRB_DBL_ATTR_OBJVAL));
1157  dual_solution.set_objective_value(obj_val);
1158  }
1159  // TODO(b/195295177): explore using GRB_DBL_ATTR_OBJBOUND to set the dual
1160  // objective. As described in go/gurobi-objval-bug, this could provide the
1161  // dual objective in some cases.
1162 
1163  dual_solution.set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
1164  if (grb_termination == GRB_OPTIMAL) {
1165  dual_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1166  dual_feasible_solution_exists = true;
1167  } else if (grb_termination == GRB_UNBOUNDED) {
1168  dual_solution.set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
1169  }
1170  // TODO(b/195295177): We could use gurobi's dual solution quality measures
1171  // for further upgrade the dual feasibility but it likely is only useful
1172  // for phase II of dual simplex because:
1173  // * the quality measures seem to evaluate if the basis is dual feasible
1174  // so for primal simplex we would not improve over checking
1175  // GRB_OPTIMAL.
1176  // * for phase I dual simplex we cannot rely on the quality measures
1177  // because of go/gurobi-solution-quality-bug.
1178  // We could also use finiteness of GRB_DBL_ATTR_OBJBOUND to deduce dual
1179  // feasibility as described in go/gurobi-objval-bug.
1180 
1181  // Note: as shown in go/gurobi-objval-bug, GRB_DBL_ATTR_OBJBOUND can
1182  // sometimes provide the objective value of a sub-optimal dual feasible
1183  // solution. Here we only use it to possibly update
1184  // dual_feasible_solution_exists (Otherwise
1185  // StatusTest.PrimalInfeasibleAndDualFeasible for pure dual simplex would
1186  // fail because go/gurobi-solution-quality-bug prevents us from certifying
1187  // feasibility of the dual solution found in this case).
1188  ASSIGN_OR_RETURN(const double best_dual_bound, GetBestDualBound());
1189  if (dual_feasible_solution_exists || std::isfinite(best_dual_bound)) {
1190  dual_feasible_solution_exists = true;
1191  } else if (grb_termination == GRB_OPTIMAL) {
1192  return absl::InternalError(
1193  "GRB_INT_ATTR_STATUS == GRB_OPTIMAL, but GRB_DBL_ATTR_OBJBOUND is "
1194  "unavailable or infinite, and no dual feasible solution is returned");
1195  }
1196  return SolutionAndClaim<DualSolutionProto>{
1197  .solution = std::move(dual_solution),
1198  .feasible_solution_exists = dual_feasible_solution_exists};
1199 }
1200 
1201 absl::Status GurobiSolver::FillRays(
1202  const ModelSolveParametersProto& model_parameters,
1203  const SolutionClaims solution_claims, SolveResultProto& result) {
1204  ASSIGN_OR_RETURN(const bool is_maximize, IsMaximize());
1205  // GRB_DBL_ATTR_UNBDRAY is sometimes incorrectly available for problems
1206  // without variables. We also give priority to the conclusions obtained from
1207  // dual solutions or bounds.
1208  if (!solution_claims.dual_feasible_solution_exists &&
1209  num_gurobi_variables_ > 0 &&
1210  gurobi_->IsAttrAvailable(GRB_DBL_ATTR_UNBDRAY)) {
1211  ASSIGN_OR_RETURN(const std::vector<double> grb_ray_var_values,
1212  gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_UNBDRAY,
1213  num_gurobi_variables_));
1214  PrimalRayProto* const primal_ray = result.add_primal_rays();
1215  GurobiVectorToSparseDoubleVector(grb_ray_var_values, variables_map_,
1216  *primal_ray->mutable_variable_values(),
1217  model_parameters.variable_values_filter());
1218  }
1219  // GRB_DBL_ATTR_FARKASDUAL is sometimes incorrectly available for problems
1220  // without constraints. We also give priority to the conclusions obtained from
1221  // primal solutions.
1222  if (!solution_claims.primal_feasible_solution_exists &&
1223  num_gurobi_lin_cons_ > 0 &&
1224  gurobi_->IsAttrAvailable(GRB_DBL_ATTR_FARKASDUAL)) {
1226  DualRayProto dual_ray,
1227  GetGurobiDualRay(model_parameters.dual_values_filter(),
1228  model_parameters.reduced_costs_filter(), is_maximize));
1229  result.mutable_dual_rays()->Add(std::move(dual_ray));
1230  }
1231  return absl::OkStatus();
1232 }
1233 
1234 absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetQpSolution(
1235  const ModelSolveParametersProto& model_parameters) {
1236  ASSIGN_OR_RETURN((auto [primal_solution, found_primal_feasible_solution]),
1237  GetConvexPrimalSolutionIfAvailable(model_parameters));
1238  // TODO(b/225189115): Expand QpDualsTest to check maximization problems and
1239  // other edge cases.
1240  ASSIGN_OR_RETURN((auto [dual_solution, found_dual_feasible_solution]),
1241  GetLpDualSolutionIfAvailable(model_parameters));
1242  // Basis information is available when Gurobi uses QP simplex. As of v9.1 this
1243  // is not the default [1], so a user will need to explicitly set the Method
1244  // parameter in order for the following call to do anything interesting.
1245  // [1] https://www.gurobi.com/documentation/9.1/refman/method.html
1246  ASSIGN_OR_RETURN(auto basis, GetBasisIfAvailable());
1247 
1248  const SolutionClaims solution_claims = {
1249  .primal_feasible_solution_exists = found_primal_feasible_solution,
1250  .dual_feasible_solution_exists = found_dual_feasible_solution};
1251 
1252  if (!primal_solution.has_value() && !basis.has_value()) {
1253  return GurobiSolver::SolutionsAndClaims{.solution_claims = solution_claims};
1254  }
1255  SolutionsAndClaims solution_and_claims{.solution_claims = solution_claims};
1256  SolutionProto& solution =
1257  solution_and_claims.solutions.emplace_back(SolutionProto());
1258  if (primal_solution.has_value()) {
1259  *solution.mutable_primal_solution() = *std::move(primal_solution);
1260  }
1261  if (dual_solution.has_value()) {
1262  *solution.mutable_dual_solution() = *std::move(dual_solution);
1263  }
1264  if (basis.has_value()) {
1265  *solution.mutable_basis() = *std::move(basis);
1266  }
1267  return solution_and_claims;
1268 }
1269 
1270 absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetQcpSolution(
1271  const ModelSolveParametersProto& model_parameters) {
1272  ASSIGN_OR_RETURN((auto [primal_solution, found_primal_feasible_solution]),
1273  GetConvexPrimalSolutionIfAvailable(model_parameters));
1274  // TODO(b/227217735): Expose duals. Note that the user must set the QCPDual
1275  // parameter for Gurobi to return any dual values.
1276  ASSIGN_OR_RETURN((auto [_, found_dual_feasible_solution]),
1277  GetLpDualSolutionIfAvailable(model_parameters));
1278 
1279  ASSIGN_OR_RETURN(const int grb_termination,
1280  gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
1281  // By default, Gurobi will not return duals for optimally solved QCPs.
1282  const bool proven_feasible = grb_termination == GRB_OPTIMAL;
1283  const SolutionClaims solution_claims = {
1284  .primal_feasible_solution_exists = found_primal_feasible_solution,
1285  .dual_feasible_solution_exists =
1286  found_dual_feasible_solution || proven_feasible};
1287 
1288  SolutionsAndClaims solution_and_claims{.solution_claims = solution_claims};
1289  if (primal_solution.has_value()) {
1290  *solution_and_claims.solutions.emplace_back().mutable_primal_solution() =
1291  *std::move(primal_solution);
1292  }
1293  return solution_and_claims;
1294 }
1295 
1296 absl::Status GurobiSolver::SetParameters(
1297  const SolveParametersProto& parameters) {
1298  const GurobiParametersProto gurobi_parameters = MergeParameters(parameters);
1299  std::vector<std::string> parameter_errors;
1300  for (const GurobiParametersProto::Parameter& parameter :
1301  gurobi_parameters.parameters()) {
1302  absl::Status param_status =
1303  gurobi_->SetParam(parameter.name().c_str(), parameter.value());
1304  if (!param_status.ok()) {
1305  parameter_errors.emplace_back(std::move(param_status).message());
1306  }
1307  }
1308  if (!parameter_errors.empty()) {
1309  return absl::InvalidArgumentError(absl::StrJoin(parameter_errors, "; "));
1310  }
1311  return absl::OkStatus();
1312 }
1313 
1314 absl::Status GurobiSolver::AddNewVariables(
1315  const VariablesProto& new_variables) {
1316  const int num_new_variables = new_variables.lower_bounds().size();
1317  std::vector<char> variable_type(num_new_variables);
1318  for (int j = 0; j < num_new_variables; ++j) {
1319  const VariableId id = new_variables.ids(j);
1320  gtl::InsertOrDie(&variables_map_, id, j + num_gurobi_variables_);
1321  variable_type[j] = new_variables.integers(j) ? GRB_INTEGER : GRB_CONTINUOUS;
1322  }
1323  // We need to copy the names, RepeatedPtrField cannot be converted to
1324  // absl::Span<std::string>.
1325  const std::vector<std::string> variable_names =
1326  TruncateNames(new_variables.names());
1327  RETURN_IF_ERROR(gurobi_->AddVars(
1328  /*obj=*/{},
1329  /*lb=*/new_variables.lower_bounds(),
1330  /*ub=*/new_variables.upper_bounds(),
1331  /*vtype=*/variable_type, variable_names));
1332  num_gurobi_variables_ += num_new_variables;
1333 
1334  return absl::OkStatus();
1335 }
1336 
1337 // Given a vector of pairs<LinearConstraintId, LinearConstraintData&> add a
1338 // slack variable for each of the constraints in the underlying `gurobi_` using
1339 // the referenced bounds.
1340 absl::Status GurobiSolver::AddNewSlacks(
1341  const std::vector<LinearConstraintData*>& new_slacks) {
1342  // Note that we are really adding the sub-matrix
1343  // D * slack
1344  // to the set of linear constraints, and the D matrix is stored in compressed
1345  // sparse column (CSC) format. In our particular case, D is a diagonal matrix
1346  // with -1.0 coefficients for each new slack in the row indicated in the
1347  // row_indices vector.
1348  const int num_slacks = new_slacks.size();
1349  if (num_slacks == 0) {
1350  return absl::OkStatus();
1351  }
1352  // Build the D matrix in CSC format.
1353  const std::vector<double> column_non_zeros(num_slacks, -1.0);
1354  std::vector<double> lower_bounds;
1355  std::vector<double> upper_bounds;
1356  const std::vector<char> vtypes(num_slacks, GRB_CONTINUOUS);
1357  std::vector<GurobiLinearConstraintIndex> row_indices;
1358  std::vector<int> column_non_zero_begin;
1359  column_non_zero_begin.reserve(num_slacks);
1360  row_indices.reserve(num_slacks);
1361  lower_bounds.reserve(num_slacks);
1362  upper_bounds.reserve(num_slacks);
1363  for (int k = 0; k < num_slacks; ++k) {
1364  CHECK_NE(new_slacks[k], nullptr);
1365  const LinearConstraintData& constraint_data = *new_slacks[k];
1366  row_indices.push_back(constraint_data.constraint_index);
1367  lower_bounds.push_back(constraint_data.lower_bound);
1368  upper_bounds.push_back(constraint_data.upper_bound);
1369  column_non_zero_begin.push_back(k);
1370  }
1371  // Add variables to the underlying model.
1372  RETURN_IF_ERROR(gurobi_->AddVars(/*vbegin=*/column_non_zero_begin,
1373  /*vind=*/row_indices,
1374  /*vval=*/column_non_zeros, /*obj=*/{},
1375  /*lb=*/lower_bounds, /*ub=*/upper_bounds,
1376  /*vtype=*/vtypes, /*names=*/{}));
1377  num_gurobi_variables_ += num_slacks;
1378  return absl::OkStatus();
1379 }
1380 
1381 absl::Status GurobiSolver::AddNewLinearConstraints(
1382  const LinearConstraintsProto& constraints) {
1383  const int num_new_constraints = constraints.lower_bounds().size();
1384 
1385  // We need to copy the names, RepeatedPtrField cannot be converted to
1386  // absl::Span<std::string>.
1387  const std::vector<std::string> constraint_names =
1388  TruncateNames(constraints.names());
1389  // Constraints are translated into:
1390  // 1. ax <= upper_bound (if lower bound <= -GRB_INFINITY, and upper_bound
1391  // is finite and less than GRB_INFINITY)
1392  // 2. ax >= lower_bound (if upper bound >= GRB_INFINITY, and lower_bound is
1393  // finite and greater than -GRB_INFINITY)
1394  // 3. ax == xxxxx_bound (if both bounds are finite, equal, and their
1395  // absolute values less than GRB_INFINITY)
1396  // 4. ax - slack = 0.0 (otherwise,
1397  // slack bounds == [lower_bound, upper_bound])
1398  std::vector<double> constraint_rhs;
1399  std::vector<char> constraint_sense;
1400  std::vector<LinearConstraintData*> new_slacks;
1401  constraint_rhs.reserve(num_new_constraints);
1402  constraint_sense.reserve(num_new_constraints);
1403  new_slacks.reserve(num_new_constraints);
1404  for (int i = 0; i < num_new_constraints; ++i) {
1405  const int64_t id = constraints.ids(i);
1406  LinearConstraintData& constraint_data =
1407  gtl::InsertKeyOrDie(&linear_constraints_map_, id);
1408  const double lb = constraints.lower_bounds(i);
1409  const double ub = constraints.upper_bounds(i);
1410  RETURN_IF_ERROR(SafeGurobiDouble(lb))
1411  << "lower bound for linear constraint " << id << ": "
1412  << EscapedNameForLogging(
1413  constraints.names().empty() ? "" : constraints.names(i));
1414  RETURN_IF_ERROR(SafeGurobiDouble(ub))
1415  << "upper bound for linear constraint " << id << ": "
1416  << EscapedNameForLogging(
1417  constraints.names().empty() ? "" : constraints.names(i));
1418  constraint_data.lower_bound = lb;
1419  constraint_data.upper_bound = ub;
1420  constraint_data.constraint_index = i + num_gurobi_lin_cons_;
1421  char sense = GRB_EQUAL;
1422  double rhs = 0.0;
1423  const bool lb_is_grb_neg_inf = lb <= -GRB_INFINITY;
1424  const bool ub_is_grb_pos_inf = ub >= GRB_INFINITY;
1425  if (lb_is_grb_neg_inf && !ub_is_grb_pos_inf) {
1426  sense = GRB_LESS_EQUAL;
1427  rhs = ub;
1428  } else if (!lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
1429  sense = GRB_GREATER_EQUAL;
1430  rhs = lb;
1431  } else if (lb == ub) {
1432  sense = GRB_EQUAL;
1433  rhs = lb;
1434  } else {
1435  // Note that constraints where the lower bound and the upper bound are
1436  // -+infinity translate into a range constraint with an unbounded slack.
1437  constraint_data.slack_index = new_slacks.size() + num_gurobi_variables_;
1438  new_slacks.push_back(&constraint_data);
1439  }
1440  constraint_rhs.emplace_back(rhs);
1441  constraint_sense.emplace_back(sense);
1442  }
1443  // Add all constraints in one call.
1445  gurobi_->AddConstrs(constraint_sense, constraint_rhs, constraint_names));
1446  num_gurobi_lin_cons_ += num_new_constraints;
1447  // Add slacks for true ranged constraints (if needed)
1448  if (!new_slacks.empty()) {
1449  RETURN_IF_ERROR(AddNewSlacks(new_slacks));
1450  }
1451  return absl::OkStatus();
1452 }
1453 
1454 absl::Status GurobiSolver::AddNewQuadraticConstraints(
1455  const google::protobuf::Map<QuadraticConstraintId,
1456  QuadraticConstraintProto>& constraints) {
1457  // Constraints are translated into:
1458  // 1. ax <= upper_bound (if lower bound <= -GRB_INFINITY, and upper_bound
1459  // is finite and less than GRB_INFINITY)
1460  // 2. ax >= lower_bound (if upper bound >= GRB_INFINITY, and lower_bound is
1461  // finite and greater than -GRB_INFINITY)
1462  // 3. ax == xxxxx_bound (if both bounds are finite, equal, and their
1463  // absolute values less than GRB_INFINITY)
1464  // 4. Return an error otherwise, we do not currently support ranged quadratic
1465  // constraints.
1466  for (const auto& [id, constraint] : constraints) {
1467  char sense = GRB_EQUAL;
1468  double rhs = 0.0;
1469  const double lb = constraint.lower_bound();
1470  const double ub = constraint.upper_bound();
1471  RETURN_IF_ERROR(SafeGurobiDouble(lb))
1472  << "lower bound for quadratic constraint " << id << ": "
1473  << EscapedNameForLogging(constraint.name());
1474  RETURN_IF_ERROR(SafeGurobiDouble(ub))
1475  << "upper bound for quadratic constraint " << id << ": "
1476  << EscapedNameForLogging(constraint.name());
1477  const bool lb_is_grb_neg_inf = lb <= -GRB_INFINITY;
1478  const bool ub_is_grb_pos_inf = ub >= GRB_INFINITY;
1479  if (lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
1480  // The constraint is vacuous, so we just skip it.
1481  // TODO(b/227217735): Ensure duals properly account for this constraint.
1482  continue;
1483  } else if (lb_is_grb_neg_inf && !ub_is_grb_pos_inf) {
1484  sense = GRB_LESS_EQUAL;
1485  rhs = ub;
1486  } else if (!lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
1487  sense = GRB_GREATER_EQUAL;
1488  rhs = lb;
1489  } else if (lb == ub) {
1490  sense = GRB_EQUAL;
1491  rhs = lb;
1492  } else {
1493  // We do not currently support ranged quadratic constraints, though it is
1494  // possible to support this if there is a need.
1495  return absl::UnimplementedError(
1496  "ranged quadratic constraints are not currently supported in Gurobi "
1497  "interface");
1498  }
1499  const SparseDoubleVectorProto& linear_coeffs = constraint.linear_terms();
1500  const int num_linear_coeffs = linear_coeffs.ids_size();
1501  std::vector<GurobiVariableIndex> linear_col_index(num_linear_coeffs);
1502  for (int k = 0; k < num_linear_coeffs; ++k) {
1503  linear_col_index[k] = variables_map_.at(linear_coeffs.ids(k));
1504  }
1505  const SparseDoubleMatrixProto& quad_coeffs = constraint.quadratic_terms();
1506  const int num_quad_coeffs = quad_coeffs.row_ids_size();
1507  std::vector<GurobiVariableIndex> quad_row_index(num_quad_coeffs);
1508  std::vector<GurobiVariableIndex> quad_col_index(num_quad_coeffs);
1509  for (int k = 0; k < num_quad_coeffs; ++k) {
1510  quad_row_index[k] = variables_map_.at(quad_coeffs.row_ids(k));
1511  quad_col_index[k] = variables_map_.at(quad_coeffs.column_ids(k));
1512  }
1513  RETURN_IF_ERROR(gurobi_->AddQConstr(
1514  linear_col_index, linear_coeffs.values(), quad_row_index,
1515  quad_col_index, quad_coeffs.coefficients(), sense, rhs,
1516  TruncateName(constraint.name())));
1517  gtl::InsertOrDie(&quadratic_constraints_map_, id, num_gurobi_quad_cons_);
1518  ++num_gurobi_quad_cons_;
1519  }
1520  return absl::OkStatus();
1521 }
1522 
1523 absl::Status GurobiSolver::AddNewSosConstraints(
1524  const google::protobuf::Map<AnyConstraintId, SosConstraintProto>&
1525  constraints,
1526  const int sos_type,
1527  absl::flat_hash_map<int64_t, SosConstraintData>& constraints_map) {
1528  for (const auto& [id, constraint] : constraints) {
1529  SosConstraintData& constraint_data =
1530  gtl::InsertKeyOrDie(&constraints_map, id);
1531  constraint_data.constraint_index = num_gurobi_sos_cons_;
1532  std::vector<GurobiVariableIndex> sos_var_indices;
1533  std::vector<double> weights;
1534  for (int i = 0; i < constraint.expressions_size(); ++i) {
1535  const LinearExpressionProto& expression = constraint.expressions(i);
1536  weights.push_back(constraint.weights().empty() ? i + 1
1537  : constraint.weights(i));
1538  if (expression.offset() == 0 && expression.ids_size() == 1 &&
1539  expression.coefficients(0) == 1) {
1540  const VariableId var_id = expression.ids(0);
1541  // In this case, the expression is equivalent to just a single variable.
1542  // Therefore, we can safely pass this variable to the SOS constraint,
1543  // and avoid adding a slack variable.
1544  sos_var_indices.push_back(variables_map_.at(var_id));
1545  // If this variable is deleted, Gurobi will drop the corresponding term
1546  // from the SOS constraint, potentially changing the meaning of an SOS2.
1547  if (sos_type == 2) {
1548  undeletable_variables_.insert(var_id);
1549  }
1550  continue;
1551  }
1552  // This term in the SOS constraint is a nontrivial expression `expr`, but
1553  // Gurobi only accepts a single variable. Therefore we introduce a new
1554  // `slack` variable and add the linear constraint: `expr` == `slack`.
1555  sos_var_indices.push_back(num_gurobi_variables_);
1556  constraint_data.slack_variables.push_back(num_gurobi_variables_);
1557  constraint_data.slack_constraints.push_back(num_gurobi_lin_cons_);
1558  std::vector<GurobiVariableIndex> slack_col_indices = {
1559  num_gurobi_variables_};
1560  std::vector<double> slack_coeffs = {-1.0};
1561  for (int j = 0; j < expression.ids_size(); ++j) {
1562  slack_col_indices.push_back(variables_map_.at(expression.ids(j)));
1563  slack_coeffs.push_back(expression.coefficients(j));
1564  }
1565  RETURN_IF_ERROR(gurobi_->AddVar(0, -kInf, kInf, GRB_CONTINUOUS, ""));
1566  ++num_gurobi_variables_;
1567  RETURN_IF_ERROR(gurobi_->AddConstr(slack_col_indices, slack_coeffs,
1568  GRB_EQUAL, -expression.offset(), ""));
1569  ++num_gurobi_lin_cons_;
1570  }
1571  RETURN_IF_ERROR(gurobi_->AddSos({sos_type}, {0}, sos_var_indices, weights));
1572  ++num_gurobi_sos_cons_;
1573  }
1574  return absl::OkStatus();
1575 }
1576 
1577 absl::Status GurobiSolver::AddNewIndicatorConstraints(
1578  const google::protobuf::Map<IndicatorConstraintId,
1579  IndicatorConstraintProto>& constraints) {
1580  for (const auto& [id, constraint] : constraints) {
1581  if (!constraint.has_indicator_id()) {
1582  gtl::InsertOrDie(&indicator_constraints_map_, id, std::nullopt);
1583  continue;
1584  }
1585  const int num_terms = constraint.expression().ids_size();
1586  std::vector<GurobiVariableIndex> grb_ids(num_terms);
1587  for (int k = 0; k < num_terms; ++k) {
1588  grb_ids[k] = variables_map_.at(constraint.expression().ids(k));
1589  }
1590  char sense = GRB_EQUAL;
1591  double rhs = 0.0;
1592  const double lb = constraint.lower_bound();
1593  const double ub = constraint.upper_bound();
1594  RETURN_IF_ERROR(SafeGurobiDouble(lb))
1595  << "lower bound for indicator constraint " << id << ": "
1596  << EscapedNameForLogging(constraint.name());
1597  RETURN_IF_ERROR(SafeGurobiDouble(ub))
1598  << "upper bound for indicator constraint " << id << ": "
1599  << EscapedNameForLogging(constraint.name());
1600  const bool lb_is_grb_neg_inf = lb <= -GRB_INFINITY;
1601  const bool ub_is_grb_pos_inf = ub >= GRB_INFINITY;
1602  if (lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
1603  // The constraint is vacuous, so we just skip it.
1604  continue;
1605  } else if (lb_is_grb_neg_inf && !ub_is_grb_pos_inf) {
1606  sense = GRB_LESS_EQUAL;
1607  rhs = ub;
1608  } else if (!lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
1609  sense = GRB_GREATER_EQUAL;
1610  rhs = lb;
1611  } else if (lb == ub) {
1612  sense = GRB_EQUAL;
1613  rhs = lb;
1614  } else {
1615  // We do not currently support ranged indicator constraints, though it is
1616  // possible to support this if there is a need.
1617  return absl::UnimplementedError(
1618  "ranged indicator constraints are not currently supported in Gurobi "
1619  "interface");
1620  }
1621  RETURN_IF_ERROR(gurobi_->AddIndicator(
1622  /*name=*/constraint.name(),
1623  /*binvar=*/variables_map_.at(constraint.indicator_id()),
1624  /*binval=*/constraint.activate_on_zero() ? 0 : 1,
1625  /*ind=*/grb_ids, /*val=*/constraint.expression().values(),
1626  /*sense=*/sense, /*rhs=*/rhs));
1627  gtl::InsertOrDie(&indicator_constraints_map_, id,
1628  IndicatorConstraintData{
1629  .constraint_index = num_gurobi_gen_cons_,
1630  .indicator_variable_id = constraint.indicator_id()});
1631  ++num_gurobi_gen_cons_;
1632  // Deleting the indicator variable, but not the associated indicator
1633  // constraint, will lead to a Gurobi error.
1634  undeletable_variables_.insert(constraint.indicator_id());
1635  }
1636  return absl::OkStatus();
1637 }
1638 
1639 absl::Status GurobiSolver::ChangeCoefficients(
1640  const SparseDoubleMatrixProto& matrix) {
1641  const int num_coefficients = matrix.row_ids().size();
1642  std::vector<GurobiLinearConstraintIndex> row_index(num_coefficients);
1643  std::vector<GurobiVariableIndex> col_index(num_coefficients);
1644  for (int k = 0; k < num_coefficients; ++k) {
1645  row_index[k] =
1646  linear_constraints_map_.at(matrix.row_ids(k)).constraint_index;
1647  col_index[k] = variables_map_.at(matrix.column_ids(k));
1648  }
1649  return gurobi_->ChgCoeffs(row_index, col_index, matrix.coefficients());
1650 }
1651 
1652 absl::Status GurobiSolver::UpdateDoubleListAttribute(
1653  const SparseDoubleVectorProto& update, const char* attribute_name,
1654  const IdHashMap& id_hash_map) {
1655  if (update.ids_size() == 0) {
1656  return absl::OkStatus();
1657  }
1658  std::vector<int> index;
1659  index.reserve(update.ids_size());
1660  for (const int64_t id : update.ids()) {
1661  index.push_back(id_hash_map.at(id));
1662  }
1663  return gurobi_->SetDoubleAttrList(attribute_name, index, update.values());
1664 }
1665 
1666 absl::Status GurobiSolver::UpdateInt32ListAttribute(
1667  const SparseInt32VectorProto& update, const char* attribute_name,
1668  const IdHashMap& id_hash_map) {
1669  if (update.ids_size() == 0) {
1670  return absl::OkStatus();
1671  }
1672  std::vector<int> index;
1673  index.reserve(update.ids_size());
1674  for (const int64_t id : update.ids()) {
1675  index.push_back(id_hash_map.at(id));
1676  }
1677  return gurobi_->SetIntAttrList(attribute_name, index, update.values());
1678 }
1679 
1680 absl::Status GurobiSolver::LoadModel(const ModelProto& input_model) {
1681  CHECK(gurobi_ != nullptr);
1682  RETURN_IF_ERROR(gurobi_->SetStringAttr(GRB_STR_ATTR_MODELNAME,
1683  TruncateName(input_model.name())));
1684  RETURN_IF_ERROR(AddNewVariables(input_model.variables()));
1685 
1686  RETURN_IF_ERROR(AddNewLinearConstraints(input_model.linear_constraints()));
1688  AddNewQuadraticConstraints(input_model.quadratic_constraints()));
1689 
1690  RETURN_IF_ERROR(AddNewSosConstraints(input_model.sos1_constraints(),
1691  GRB_SOS_TYPE1, sos1_constraints_map_));
1692  RETURN_IF_ERROR(AddNewSosConstraints(input_model.sos2_constraints(),
1693  GRB_SOS_TYPE2, sos2_constraints_map_));
1695  AddNewIndicatorConstraints(input_model.indicator_constraints()));
1696 
1697  RETURN_IF_ERROR(ChangeCoefficients(input_model.linear_constraint_matrix()));
1698 
1699  const int model_sense =
1700  input_model.objective().maximize() ? GRB_MAXIMIZE : GRB_MINIMIZE;
1701  RETURN_IF_ERROR(gurobi_->SetIntAttr(GRB_INT_ATTR_MODELSENSE, model_sense));
1702  RETURN_IF_ERROR(gurobi_->SetDoubleAttr(GRB_DBL_ATTR_OBJCON,
1703  input_model.objective().offset()));
1704 
1706  UpdateDoubleListAttribute(input_model.objective().linear_coefficients(),
1707  GRB_DBL_ATTR_OBJ, variables_map_));
1708  RETURN_IF_ERROR(ResetQuadraticObjectiveTerms(
1709  input_model.objective().quadratic_coefficients()));
1710  return absl::OkStatus();
1711 }
1712 
1713 absl::Status GurobiSolver::ResetQuadraticObjectiveTerms(
1714  const SparseDoubleMatrixProto& terms) {
1715  quadratic_objective_coefficients_.clear();
1716  RETURN_IF_ERROR(gurobi_->DelQ());
1717  const int num_terms = terms.row_ids().size();
1718  if (num_terms > 0) {
1719  std::vector<GurobiVariableIndex> first_var_index(num_terms);
1720  std::vector<GurobiVariableIndex> second_var_index(num_terms);
1721  for (int k = 0; k < num_terms; ++k) {
1722  const VariableId row_id = terms.row_ids(k);
1723  const VariableId column_id = terms.column_ids(k);
1724  first_var_index[k] = variables_map_.at(row_id);
1725  second_var_index[k] = variables_map_.at(column_id);
1726  quadratic_objective_coefficients_[{row_id, column_id}] =
1727  terms.coefficients(k);
1728  }
1729  RETURN_IF_ERROR(gurobi_->AddQpTerms(first_var_index, second_var_index,
1730  terms.coefficients()));
1731  }
1732  return absl::OkStatus();
1733 }
1734 
1735 absl::Status GurobiSolver::UpdateQuadraticObjectiveTerms(
1736  const SparseDoubleMatrixProto& terms) {
1737  CHECK(gurobi_ != nullptr);
1738  const int num_terms = terms.row_ids().size();
1739  if (num_terms > 0) {
1740  std::vector<GurobiVariableIndex> first_var_index(num_terms);
1741  std::vector<GurobiVariableIndex> second_var_index(num_terms);
1742  std::vector<double> coefficient_updates(num_terms);
1743  for (int k = 0; k < num_terms; ++k) {
1744  const VariableId row_id = terms.row_ids(k);
1745  const VariableId column_id = terms.column_ids(k);
1746  first_var_index[k] = variables_map_.at(row_id);
1747  second_var_index[k] = variables_map_.at(column_id);
1748  const std::pair<VariableId, VariableId> qp_term_key(row_id, column_id);
1749  const double new_coefficient = terms.coefficients(k);
1750  // Gurobi will maintain any existing quadratic coefficients unless we
1751  // call GRBdelq (which we don't). So, since stored entries in terms
1752  // specify the target coefficients, we need to compute the difference from
1753  // the existing coefficient with Gurobi, if any.
1754  coefficient_updates[k] =
1755  new_coefficient - quadratic_objective_coefficients_[qp_term_key];
1756  quadratic_objective_coefficients_[qp_term_key] = new_coefficient;
1757  }
1758  RETURN_IF_ERROR(gurobi_->AddQpTerms(first_var_index, second_var_index,
1759  coefficient_updates));
1760  }
1761  return absl::OkStatus();
1762 }
1763 
1764 // Bound changes in constraints can induce new variables, and also remove
1765 // some slacks. We first add all new variables, and queue all deletions to be
1766 // dealt with later on.
1767 absl::Status GurobiSolver::UpdateLinearConstraints(
1768  const LinearConstraintUpdatesProto& constraints_update,
1769  std::vector<GurobiVariableIndex>& deleted_variables_index) {
1770  const SparseDoubleVectorProto& constraint_lower_bounds =
1771  constraints_update.lower_bounds();
1772  const SparseDoubleVectorProto& constraint_upper_bounds =
1773  constraints_update.upper_bounds();
1774 
1775  // If no update, just return.
1776  if (constraint_lower_bounds.ids().empty() &&
1777  constraint_upper_bounds.ids().empty()) {
1778  return absl::OkStatus();
1779  }
1780 
1781  // We want to avoid changing the right-hand-side, sense, or slacks of each
1782  // constraint more than once. Since we can refer to the same constraint ID
1783  // both in the `constraint_upper_bounds` and `constraint_lower_bounds` sparse
1784  // vectors, we collect all changes into a single structure:
1785  struct UpdateConstraintData {
1786  LinearConstraintId constraint_id;
1787  LinearConstraintData& source;
1788  double new_lower_bound;
1789  double new_upper_bound;
1790  UpdateConstraintData(const LinearConstraintId id,
1791  LinearConstraintData& reference)
1792  : constraint_id(id),
1793  source(reference),
1794  new_lower_bound(reference.lower_bound),
1795  new_upper_bound(reference.upper_bound) {}
1796  };
1797  const int upper_bounds_size = constraint_upper_bounds.ids().size();
1798  const int lower_bounds_size = constraint_lower_bounds.ids().size();
1799  std::vector<UpdateConstraintData> update_vector;
1800  update_vector.reserve(upper_bounds_size + lower_bounds_size);
1801  // We exploit the fact that IDs are sorted in increasing order to merge
1802  // changes into a vector of aggregated changes.
1803  for (int lower_index = 0, upper_index = 0;
1804  lower_index < lower_bounds_size || upper_index < upper_bounds_size;) {
1805  VariableId lower_id = std::numeric_limits<int64_t>::max();
1806  if (lower_index < lower_bounds_size) {
1807  lower_id = constraint_lower_bounds.ids(lower_index);
1808  }
1809  VariableId upper_id = std::numeric_limits<int64_t>::max();
1810  if (upper_index < upper_bounds_size) {
1811  upper_id = constraint_upper_bounds.ids(upper_index);
1812  }
1813  const VariableId id = std::min(lower_id, upper_id);
1814  DCHECK(id < std::numeric_limits<int64_t>::max());
1815  UpdateConstraintData update(id, linear_constraints_map_.at(id));
1816  if (lower_id == upper_id) {
1817  update.new_lower_bound = constraint_lower_bounds.values(lower_index++);
1818  update.new_upper_bound = constraint_upper_bounds.values(upper_index++);
1819  } else if (lower_id < upper_id) {
1820  update.new_lower_bound = constraint_lower_bounds.values(lower_index++);
1821  } else { /* upper_id < lower_id */
1822  update.new_upper_bound = constraint_upper_bounds.values(upper_index++);
1823  }
1824  update_vector.emplace_back(update);
1825  }
1826 
1827  // We have grouped all changes in update_vector, now generate changes in
1828  // slack bounds, rhs, senses, new slacks, and deleted_slacks (to be dealt
1829  // with later, outside this function).
1830  // These three vectors keep changes to right-hand-side and senses.
1831  std::vector<char> sense_data;
1832  std::vector<double> rhs_data;
1833  std::vector<GurobiLinearConstraintIndex> rhs_index;
1834  // These three vectors keep changes to bounds on existing slack.
1835  std::vector<double> lower_bound_data;
1836  std::vector<double> upper_bound_data;
1837  std::vector<GurobiVariableIndex> bound_index;
1838  // This vector keep newly introduced slacks.
1839  std::vector<LinearConstraintData*> new_slacks;
1840  // Iterate on the changes, and populate the three possible changes.
1841  for (UpdateConstraintData& update_data : update_vector) {
1842  const bool same_lower_bound =
1843  (update_data.source.lower_bound == update_data.new_lower_bound) ||
1844  ((update_data.source.lower_bound <= -GRB_INFINITY) &&
1845  (update_data.new_lower_bound <= -GRB_INFINITY));
1846  const bool same_upper_bound =
1847  (update_data.source.upper_bound == update_data.new_upper_bound) ||
1848  ((update_data.source.upper_bound >= GRB_INFINITY) &&
1849  (update_data.new_upper_bound >= GRB_INFINITY));
1850  if (same_upper_bound && same_lower_bound) continue;
1851  // Save into linear_constraints_map_[id] the new bounds for the linear
1852  // constraint.
1853  update_data.source.lower_bound = update_data.new_lower_bound;
1854  update_data.source.upper_bound = update_data.new_upper_bound;
1855  bool delete_slack = false;
1856  // Detect the type of constraint to add and store RHS and bounds.
1857  if (update_data.new_lower_bound <= -GRB_INFINITY &&
1858  update_data.new_upper_bound < GRB_INFINITY) {
1859  delete_slack = true;
1860  rhs_index.emplace_back(update_data.source.constraint_index);
1861  rhs_data.emplace_back(update_data.new_upper_bound);
1862  sense_data.emplace_back(GRB_LESS_EQUAL);
1863  } else if (update_data.new_lower_bound > -GRB_INFINITY &&
1864  update_data.new_upper_bound >= GRB_INFINITY) {
1865  delete_slack = true;
1866  rhs_index.emplace_back(update_data.source.constraint_index);
1867  rhs_data.emplace_back(update_data.new_lower_bound);
1868  sense_data.emplace_back(GRB_GREATER_EQUAL);
1869  } else if (update_data.new_lower_bound == update_data.new_upper_bound) {
1870  delete_slack = true;
1871  rhs_index.emplace_back(update_data.source.constraint_index);
1872  rhs_data.emplace_back(update_data.new_lower_bound);
1873  sense_data.emplace_back(GRB_EQUAL);
1874  } else {
1875  // Note that constraints where the lower bound and the upper bound are
1876  // -+infinity translated into a range constraint with an unbounded
1877  // slack.
1878  if (update_data.source.slack_index != kUnspecifiedIndex) {
1879  bound_index.emplace_back(update_data.source.slack_index);
1880  lower_bound_data.emplace_back(update_data.new_lower_bound);
1881  upper_bound_data.emplace_back(update_data.new_upper_bound);
1882  } else {
1883  // Note that if we add a new slack, we must both reset the sense and
1884  // right hand side for the inequality.
1885  rhs_index.emplace_back(update_data.source.constraint_index);
1886  rhs_data.emplace_back(0.0);
1887  sense_data.emplace_back(GRB_EQUAL);
1888  // Update the slack_index in the linear_constraints_map_[id]
1889  update_data.source.slack_index =
1890  new_slacks.size() + num_gurobi_variables_;
1891  // Save the data needed to add the new slack.
1892  new_slacks.push_back(&update_data.source);
1893  }
1894  }
1895  // If the constraint had a slack, and now is marked for deletion, we reset
1896  // the stored slack_index in linear_constraints_map_[id], save the index
1897  // in the list of variables to be deleted later on and remove the constraint
1898  // from slack_map_.
1899  if (delete_slack && update_data.source.slack_index != kUnspecifiedIndex) {
1900  deleted_variables_index.emplace_back(update_data.source.slack_index);
1901  update_data.source.slack_index = kUnspecifiedIndex;
1902  }
1903  }
1904 
1905  // Pass down changes to Gurobi.
1906  if (!rhs_index.empty()) {
1908  gurobi_->SetDoubleAttrList(GRB_DBL_ATTR_RHS, rhs_index, rhs_data));
1910  gurobi_->SetCharAttrList(GRB_CHAR_ATTR_SENSE, rhs_index, sense_data));
1911  } // rhs changes
1912  if (!bound_index.empty()) {
1913  RETURN_IF_ERROR(gurobi_->SetDoubleAttrList(GRB_DBL_ATTR_LB, bound_index,
1914  lower_bound_data));
1915  RETURN_IF_ERROR(gurobi_->SetDoubleAttrList(GRB_DBL_ATTR_UB, bound_index,
1916  upper_bound_data));
1917  } // Slack bound changes.
1918 
1919  if (!new_slacks.empty()) {
1920  RETURN_IF_ERROR(AddNewSlacks(new_slacks));
1921  }
1922  return absl::OkStatus();
1923 }
1924 
1925 // This function re-assign indices for variables and constraints after
1926 // deletion. The updated indices are computed from the previous indices, sorted
1927 // in incremental form, but re-assigned so that all indices are contiguous
1928 // between [0, num_variables-1], [0, num_linear_constraints-1], and [0,
1929 // num_quad_constraints-1].
1930 void GurobiSolver::UpdateGurobiIndices(const DeletedIndices& deleted_indices) {
1931  // Recover the updated indices of variables.
1932  if (!deleted_indices.variables.empty()) {
1933  const std::vector<GurobiVariableIndex> old_to_new =
1934  IndexUpdateMap(num_gurobi_variables_, deleted_indices.variables);
1935  for (auto& [_, grb_index] : variables_map_) {
1936  grb_index = old_to_new[grb_index];
1937  CHECK_NE(grb_index, kDeletedIndex);
1938  }
1939  for (auto& [_, lin_con_data] : linear_constraints_map_) {
1940  if (lin_con_data.slack_index != kUnspecifiedIndex) {
1941  lin_con_data.slack_index = old_to_new[lin_con_data.slack_index];
1942  CHECK_NE(lin_con_data.slack_index, kDeletedIndex);
1943  }
1944  }
1945  for (auto& [_, sos1_con_data] : sos1_constraints_map_) {
1946  for (GurobiVariableIndex& index : sos1_con_data.slack_variables) {
1947  index = old_to_new[index];
1948  CHECK_NE(index, kDeletedIndex);
1949  }
1950  }
1951  for (auto& [_, sos2_con_data] : sos2_constraints_map_) {
1952  for (GurobiVariableIndex& index : sos2_con_data.slack_variables) {
1953  index = old_to_new[index];
1954  CHECK_NE(index, kDeletedIndex);
1955  }
1956  }
1957  }
1958  // Recover the updated indices of linear constraints.
1959  if (!deleted_indices.linear_constraints.empty()) {
1960  const std::vector<GurobiLinearConstraintIndex> old_to_new = IndexUpdateMap(
1961  num_gurobi_lin_cons_, deleted_indices.linear_constraints);
1962  for (auto& [_, lin_con_data] : linear_constraints_map_) {
1963  lin_con_data.constraint_index = old_to_new[lin_con_data.constraint_index];
1964  CHECK_NE(lin_con_data.constraint_index, kDeletedIndex);
1965  }
1966  for (auto& [_, sos1_con_data] : sos1_constraints_map_) {
1967  for (GurobiLinearConstraintIndex& index :
1968  sos1_con_data.slack_constraints) {
1969  index = old_to_new[index];
1970  CHECK_NE(index, kDeletedIndex);
1971  }
1972  }
1973  for (auto& [_, sos2_con_data] : sos2_constraints_map_) {
1974  for (GurobiLinearConstraintIndex& index :
1975  sos2_con_data.slack_constraints) {
1976  index = old_to_new[index];
1977  CHECK_NE(index, kDeletedIndex);
1978  }
1979  }
1980  }
1981  // Recover the updated indices of quadratic constraints.
1982  if (!deleted_indices.quadratic_constraints.empty()) {
1983  const std::vector<GurobiQuadraticConstraintIndex> old_to_new =
1984  IndexUpdateMap(num_gurobi_quad_cons_,
1985  deleted_indices.quadratic_constraints);
1986  for (auto& [_, grb_index] : quadratic_constraints_map_) {
1987  grb_index = old_to_new[grb_index];
1988  CHECK_NE(grb_index, kDeletedIndex);
1989  }
1990  }
1991  // Recover the updated indices of SOS constraints.
1992  if (!deleted_indices.sos_constraints.empty()) {
1993  const std::vector<GurobiSosConstraintIndex> old_to_new =
1994  IndexUpdateMap(num_gurobi_sos_cons_, deleted_indices.sos_constraints);
1995  for (auto& [_, sos1_data] : sos1_constraints_map_) {
1996  GurobiSosConstraintIndex& grb_index = sos1_data.constraint_index;
1997  grb_index = old_to_new[grb_index];
1998  CHECK_NE(grb_index, kDeletedIndex);
1999  }
2000  for (auto& [_, sos2_data] : sos2_constraints_map_) {
2001  GurobiSosConstraintIndex& grb_index = sos2_data.constraint_index;
2002  grb_index = old_to_new[grb_index];
2003  CHECK_NE(grb_index, kDeletedIndex);
2004  }
2005  }
2006  // Recover the updated indices of general constraints.
2007  if (!deleted_indices.general_constraints.empty()) {
2008  const std::vector<GurobiGeneralConstraintIndex> old_to_new = IndexUpdateMap(
2009  num_gurobi_gen_cons_, deleted_indices.general_constraints);
2010  for (auto& [_, indicator_data] : indicator_constraints_map_) {
2011  if (!indicator_data.has_value()) {
2012  continue;
2013  }
2014  GurobiGeneralConstraintIndex& grb_index =
2015  indicator_data->constraint_index;
2016  grb_index = old_to_new[grb_index];
2017  CHECK_NE(grb_index, kDeletedIndex);
2018  }
2019  }
2020 }
2021 
2022 absl::StatusOr<bool> GurobiSolver::Update(
2023  const ModelUpdateProto& model_update) {
2024  if (!undeletable_variables_.empty()) {
2025  for (const VariableId id : model_update.deleted_variable_ids()) {
2026  if (undeletable_variables_.contains(id)) {
2027  return false;
2028  }
2029  }
2030  }
2031  if (!UpdateIsSupported(model_update, kGurobiSupportedStructures)) {
2032  return false;
2033  }
2034 
2035  RETURN_IF_ERROR(AddNewVariables(model_update.new_variables()));
2036 
2038  AddNewLinearConstraints(model_update.new_linear_constraints()));
2039 
2040  RETURN_IF_ERROR(AddNewQuadraticConstraints(
2041  model_update.quadratic_constraint_updates().new_constraints()));
2042 
2043  RETURN_IF_ERROR(AddNewSosConstraints(
2044  model_update.sos1_constraint_updates().new_constraints(), GRB_SOS_TYPE1,
2045  sos1_constraints_map_));
2046  RETURN_IF_ERROR(AddNewSosConstraints(
2047  model_update.sos2_constraint_updates().new_constraints(), GRB_SOS_TYPE2,
2048  sos2_constraints_map_));
2049  RETURN_IF_ERROR(AddNewIndicatorConstraints(
2050  model_update.indicator_constraint_updates().new_constraints()));
2051 
2053  ChangeCoefficients(model_update.linear_constraint_matrix_updates()));
2054 
2055  if (model_update.objective_updates().has_direction_update()) {
2056  const int model_sense = model_update.objective_updates().direction_update()
2057  ? GRB_MAXIMIZE
2058  : GRB_MINIMIZE;
2059  RETURN_IF_ERROR(gurobi_->SetIntAttr(GRB_INT_ATTR_MODELSENSE, model_sense));
2060  }
2061 
2062  if (model_update.objective_updates().has_offset_update()) {
2063  RETURN_IF_ERROR(gurobi_->SetDoubleAttr(
2064  GRB_DBL_ATTR_OBJCON, model_update.objective_updates().offset_update()));
2065  }
2066 
2067  RETURN_IF_ERROR(UpdateDoubleListAttribute(
2068  model_update.objective_updates().linear_coefficients(), GRB_DBL_ATTR_OBJ,
2069  variables_map_));
2070 
2071  RETURN_IF_ERROR(UpdateQuadraticObjectiveTerms(
2072  model_update.objective_updates().quadratic_coefficients()));
2073 
2075  UpdateDoubleListAttribute(model_update.variable_updates().lower_bounds(),
2076  GRB_DBL_ATTR_LB, variables_map_));
2077 
2079  UpdateDoubleListAttribute(model_update.variable_updates().upper_bounds(),
2080  GRB_DBL_ATTR_UB, variables_map_));
2081 
2082  if (model_update.variable_updates().has_integers()) {
2083  const SparseBoolVectorProto& update =
2084  model_update.variable_updates().integers();
2085  std::vector<GurobiVariableIndex> index;
2086  index.reserve(update.ids_size());
2087  for (const int64_t id : update.ids()) {
2088  index.push_back(variables_map_.at(id));
2089  }
2090  std::vector<char> value;
2091  value.reserve(update.values_size());
2092  for (const bool val : update.values()) {
2093  value.push_back(val ? GRB_INTEGER : GRB_CONTINUOUS);
2094  }
2096  gurobi_->SetCharAttrList(GRB_CHAR_ATTR_VTYPE, index, value));
2097  }
2098 
2099  // Now we update quadratic_objective_coefficients_, removing any terms where
2100  // either one or both of the involved variables are about to be deleted.
2101  const absl::flat_hash_set<VariableId> variable_ids_to_be_deleted(
2102  model_update.deleted_variable_ids().begin(),
2103  model_update.deleted_variable_ids().end());
2104  // NOTE: Introducing more state and complexity should speed this up, but we
2105  // opt for the simpler approach for now.
2106  for (auto it = quadratic_objective_coefficients_.cbegin();
2107  it != quadratic_objective_coefficients_.cend();
2108  /*incremented in loop*/) {
2109  if (variable_ids_to_be_deleted.contains(it->first.first) ||
2110  variable_ids_to_be_deleted.contains(it->first.second)) {
2111  quadratic_objective_coefficients_.erase(it++);
2112  } else {
2113  ++it;
2114  }
2115  }
2116  // We cache all Gurobi variables and constraint indices that must be deleted,
2117  // and perform deletions at the end of the update call.
2118  DeletedIndices deleted_indices;
2119 
2120  RETURN_IF_ERROR(UpdateLinearConstraints(
2121  model_update.linear_constraint_updates(), deleted_indices.variables));
2122 
2123  for (const VariableId id : model_update.deleted_variable_ids()) {
2124  deleted_indices.variables.emplace_back(variables_map_.at(id));
2125  variables_map_.erase(id);
2126  }
2127 
2128  for (const LinearConstraintId id :
2129  model_update.deleted_linear_constraint_ids()) {
2130  LinearConstraintData& constraint_data = linear_constraints_map_.at(id);
2131  deleted_indices.linear_constraints.push_back(
2132  constraint_data.constraint_index);
2133  if (constraint_data.slack_index != kUnspecifiedIndex) {
2134  deleted_indices.variables.push_back(constraint_data.slack_index);
2135  constraint_data.slack_index = kUnspecifiedIndex;
2136  }
2137  linear_constraints_map_.erase(id);
2138  }
2139 
2140  for (const QuadraticConstraintId id :
2141  model_update.quadratic_constraint_updates().deleted_constraint_ids()) {
2142  const GurobiQuadraticConstraintIndex grb_index =
2143  quadratic_constraints_map_.at(id);
2144  deleted_indices.quadratic_constraints.push_back(grb_index);
2145  quadratic_constraints_map_.erase(id);
2146  }
2147 
2148  const auto sos_updater = [&](const SosConstraintData& sos_constraint) {
2149  deleted_indices.sos_constraints.push_back(sos_constraint.constraint_index);
2150  for (const GurobiVariableIndex index : sos_constraint.slack_variables) {
2151  deleted_indices.variables.push_back(index);
2152  }
2153  for (const GurobiLinearConstraintIndex index :
2154  sos_constraint.slack_constraints) {
2155  deleted_indices.linear_constraints.push_back(index);
2156  }
2157  };
2158  for (const Sos1ConstraintId id :
2159  model_update.sos1_constraint_updates().deleted_constraint_ids()) {
2160  sos_updater(sos1_constraints_map_.at(id));
2161  sos1_constraints_map_.erase(id);
2162  }
2163 
2164  for (const Sos2ConstraintId id :
2165  model_update.sos2_constraint_updates().deleted_constraint_ids()) {
2166  sos_updater(sos2_constraints_map_.at(id));
2167  sos2_constraints_map_.erase(id);
2168  }
2169 
2170  for (const IndicatorConstraintId id :
2171  model_update.indicator_constraint_updates().deleted_constraint_ids()) {
2172  // Otherwise the constraint is not actually registered with Gurobi.
2173  const auto it = indicator_constraints_map_.find(id);
2174  CHECK(it != indicator_constraints_map_.end()) << "id: " << id;
2175  if (it->second.has_value()) {
2176  deleted_indices.general_constraints.push_back(
2177  it->second->constraint_index);
2178  }
2179  indicator_constraints_map_.erase(it);
2180  }
2181 
2182  UpdateGurobiIndices(deleted_indices);
2183 
2184  // If we are removing variables or constraints we remove them after adding
2185  // any variable or constraint. This is to avoid problems with
2186  // the numbering of possibly new variables and constraints.
2187  // After that we must update the model so that sequence of updates don't
2188  // interfere with one-another.
2189  if (!deleted_indices.linear_constraints.empty()) {
2190  RETURN_IF_ERROR(gurobi_->DelConstrs(deleted_indices.linear_constraints));
2191  num_gurobi_lin_cons_ -= deleted_indices.linear_constraints.size();
2192  }
2193 
2194  if (!deleted_indices.quadratic_constraints.empty()) {
2196  gurobi_->DelQConstrs(deleted_indices.quadratic_constraints));
2197  num_gurobi_quad_cons_ -= deleted_indices.quadratic_constraints.size();
2198  }
2199 
2200  if (!deleted_indices.sos_constraints.empty()) {
2201  RETURN_IF_ERROR(gurobi_->DelSos(deleted_indices.sos_constraints));
2202  }
2203 
2204  if (!deleted_indices.general_constraints.empty()) {
2206  gurobi_->DelGenConstrs(deleted_indices.general_constraints));
2207  }
2208 
2209  if (!deleted_indices.variables.empty()) {
2210  RETURN_IF_ERROR(gurobi_->DelVars(deleted_indices.variables));
2211  num_gurobi_variables_ -= deleted_indices.variables.size();
2212  }
2213 
2214  // Synchronize all pending changes.
2215  RETURN_IF_ERROR(gurobi_->UpdateModel());
2216 
2217  return true;
2218 }
2219 
2220 absl::StatusOr<std::unique_ptr<GurobiSolver>> GurobiSolver::New(
2221  const ModelProto& input_model, const SolverInterface::InitArgs& init_args) {
2222  if (!GurobiIsCorrectlyInstalled()) {
2223  return absl::InvalidArgumentError("Gurobi is not correctly installed.");
2224  }
2226  ModelIsSupported(input_model, kGurobiSupportedStructures, "Gurobi"));
2227  ASSIGN_OR_RETURN(std::unique_ptr<Gurobi> gurobi,
2228  GurobiFromInitArgs(init_args));
2229  auto gurobi_solver = absl::WrapUnique(new GurobiSolver(std::move(gurobi)));
2230  RETURN_IF_ERROR(gurobi_solver->LoadModel(input_model));
2231  return gurobi_solver;
2232 }
2233 
2234 absl::StatusOr<std::unique_ptr<GurobiSolver::GurobiCallbackData>>
2235 GurobiSolver::RegisterCallback(const CallbackRegistrationProto& registration,
2236  const Callback cb,
2237  const MessageCallback message_cb,
2238  const absl::Time start,
2239  SolveInterrupter* const local_interrupter) {
2240  const absl::flat_hash_set<CallbackEventProto> events = EventSet(registration);
2241 
2242  // Note that IS_MIP does not necessarily mean the problem has integer
2243  // variables. Please refer to Gurobi's doc for details:
2244  // https://www.gurobi.com/documentation/9.1/refman/ismip.html.
2245  //
2246  // Here we assume that we get MIP related events and use a MIP solving
2247  // stragegy when IS_MIP is true.
2248  ASSIGN_OR_RETURN(const int is_mip, gurobi_->GetIntAttr(GRB_INT_ATTR_IS_MIP));
2249 
2251  registration, is_mip ? SupportedMIPEvents() : SupportedLPEvents()))
2252  << "for a " << (is_mip ? "MIP" : "LP") << " model";
2253 
2254  // Set Gurobi parameters.
2255  if (message_cb != nullptr) {
2256  // Disable logging messages to the console the user wants to handle
2257  // messages.
2258  RETURN_IF_ERROR(gurobi_->SetIntParam(GRB_INT_PAR_LOGTOCONSOLE, 0));
2259  }
2260  if (registration.add_cuts() || registration.add_lazy_constraints()) {
2261  // This is to signal the solver presolve to limit primal transformations
2262  // that precludes crushing cuts to the presolved model.
2263  RETURN_IF_ERROR(gurobi_->SetIntParam(GRB_INT_PAR_PRECRUSH, 1));
2264  }
2265  if (registration.add_lazy_constraints()) {
2266  // This is needed so that the solver knows that some presolve reductions
2267  // can not be performed safely.
2268  RETURN_IF_ERROR(gurobi_->SetIntParam(GRB_INT_PAR_LAZYCONSTRAINTS, 1));
2269  }
2270  return std::make_unique<GurobiCallbackData>(
2271  GurobiCallbackInput{
2272  .user_cb = cb,
2273  .message_cb = message_cb,
2274  .variable_ids = variables_map_,
2275  .num_gurobi_vars = num_gurobi_variables_,
2276  .events = EventToGurobiWhere(events),
2277  .mip_solution_filter = registration.mip_solution_filter(),
2278  .mip_node_filter = registration.mip_node_filter(),
2279  .start = start},
2280  local_interrupter);
2281 }
2282 
2283 absl::StatusOr<InvertedBounds> GurobiSolver::ListInvertedBounds() const {
2284  InvertedBounds inverted_bounds;
2285  {
2287  const std::vector<double> var_lbs,
2288  gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_LB, num_gurobi_variables_));
2290  const std::vector<double> var_ubs,
2291  gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_UB, num_gurobi_variables_));
2292  for (const auto& [id, index] : variables_map_) {
2293  if (var_lbs[index] > var_ubs[index]) {
2294  inverted_bounds.variables.push_back(id);
2295  }
2296  }
2297  }
2298  for (const auto& [id, cstr_data] : linear_constraints_map_) {
2299  if (cstr_data.lower_bound > cstr_data.upper_bound) {
2300  inverted_bounds.linear_constraints.push_back(id);
2301  }
2302  }
2303 
2304  // Above code have inserted ids in non-stable order.
2305  std::sort(inverted_bounds.variables.begin(), inverted_bounds.variables.end());
2306  std::sort(inverted_bounds.linear_constraints.begin(),
2307  inverted_bounds.linear_constraints.end());
2308  return inverted_bounds;
2309 }
2310 
2311 absl::StatusOr<InvalidIndicators> GurobiSolver::ListInvalidIndicators() const {
2312  InvalidIndicators invalid_indicators;
2313  for (const auto& [constraint_id, indicator_data] :
2314  indicator_constraints_map_) {
2315  if (!indicator_data.has_value()) {
2316  continue;
2317  }
2318  const int64_t indicator_id = indicator_data->indicator_variable_id;
2319  const GurobiVariableIndex variable_index = variables_map_.at(indicator_id);
2320  ASSIGN_OR_RETURN(const double var_lb, gurobi_->GetDoubleAttrElement(
2321  GRB_DBL_ATTR_LB, variable_index));
2322  ASSIGN_OR_RETURN(const double var_ub, gurobi_->GetDoubleAttrElement(
2323  GRB_DBL_ATTR_UB, variable_index));
2325  const char var_type,
2326  gurobi_->GetCharAttrElement(GRB_CHAR_ATTR_VTYPE, variable_index));
2327  if (!(var_type == GRB_BINARY ||
2328  (var_type == GRB_INTEGER && var_lb >= 0.0 && var_ub <= 1.0))) {
2329  invalid_indicators.invalid_indicators.push_back(
2330  {.variable = indicator_id, .constraint = constraint_id});
2331  }
2332  }
2333  // Above code may have inserted ids in non-stable order.
2334  invalid_indicators.Sort();
2335  return invalid_indicators;
2336 }
2337 
2338 absl::StatusOr<SolveResultProto> GurobiSolver::Solve(
2339  const SolveParametersProto& parameters,
2340  const ModelSolveParametersProto& model_parameters,
2341  const MessageCallback message_cb,
2342  const CallbackRegistrationProto& callback_registration, const Callback cb,
2343  SolveInterrupter* const interrupter) {
2344  const absl::Time start = absl::Now();
2345  // We must set the parameters before calling RegisterCallback since it changes
2346  // some parameters depending on the callback registration.
2347  RETURN_IF_ERROR(SetParameters(parameters));
2348 
2349  // We use a local interrupter that will triggers the calls to GRBterminate()
2350  // when either the user interrupter is triggered or when a callback returns a
2351  // true `terminate`.
2352  std::unique_ptr<SolveInterrupter> local_interrupter;
2353  if (cb != nullptr || interrupter != nullptr) {
2354  local_interrupter = std::make_unique<SolveInterrupter>();
2355  }
2356  const ScopedSolveInterrupterCallback scoped_terminate_callback(
2357  local_interrupter.get(), [&]() {
2358  // Make an immediate call to GRBterminate() as soon as this interrupter
2359  // is triggered (which may immediately happen in the code below when it
2360  // is chained with the optional user interrupter).
2361  //
2362  // This call may happen too early. This is not an issue since we will
2363  // repeat this call at each call of the Gurobi callback. See the comment
2364  // in GurobiCallbackImpl() for details.
2365  gurobi_->Terminate();
2366  });
2367 
2368  // Chain the user interrupter to the local interrupter. If/when the user
2369  // interrupter is triggered, this triggers the local interrupter. This may
2370  // happen immediately if the user interrupter is already triggered.
2371  //
2372  // The local interrupter can also be triggered by a callback returning a true
2373  // `terminate`.
2374  const ScopedSolveInterrupterCallback scoped_chaining_callback(
2375  interrupter, [&]() { local_interrupter->Interrupt(); });
2376 
2377  // Need to run GRBupdatemodel before registering callbacks (to test if the
2378  // problem is a MIP), setting basis and getting the obj sense.
2379  RETURN_IF_ERROR(gurobi_->UpdateModel());
2380 
2381  if (model_parameters.has_initial_basis()) {
2382  RETURN_IF_ERROR(SetGurobiBasis(model_parameters.initial_basis()));
2383  }
2384  RETURN_IF_ERROR(gurobi_->SetIntAttr(GRB_INT_ATTR_NUMSTART,
2385  model_parameters.solution_hints_size()));
2386  for (int i = 0; i < model_parameters.solution_hints_size(); ++i) {
2387  RETURN_IF_ERROR(gurobi_->SetIntParam(GRB_INT_PAR_STARTNUMBER, i));
2388  RETURN_IF_ERROR(UpdateDoubleListAttribute(
2389  model_parameters.solution_hints(i).variable_values(),
2390  GRB_DBL_ATTR_START, variables_map_));
2391  }
2393  UpdateInt32ListAttribute(model_parameters.branching_priorities(),
2394  GRB_INT_ATTR_BRANCHPRIORITY, variables_map_));
2395 
2396  // Here we register the callback when we either have a user callback or a
2397  // local interrupter. The rationale for doing so when we have only an
2398  // interrupter is explained in GurobiCallbackImpl().
2399  Gurobi::Callback grb_cb = nullptr;
2400  std::unique_ptr<GurobiCallbackData> gurobi_cb_data;
2401  if (cb != nullptr || local_interrupter != nullptr || message_cb != nullptr) {
2402  ASSIGN_OR_RETURN(gurobi_cb_data,
2403  RegisterCallback(callback_registration, cb, message_cb,
2404  start, local_interrupter.get()));
2405  grb_cb = [&gurobi_cb_data](
2406  const Gurobi::CallbackContext& cb_context) -> absl::Status {
2407  return GurobiCallbackImpl(cb_context, gurobi_cb_data->callback_input,
2408  gurobi_cb_data->message_callback_data,
2409  gurobi_cb_data->local_interrupter);
2410  };
2411  }
2412 
2413  // Gurobi returns "infeasible" when bounds are inverted.
2414  {
2415  ASSIGN_OR_RETURN(const InvertedBounds inverted_bounds,
2416  ListInvertedBounds());
2417  RETURN_IF_ERROR(inverted_bounds.ToStatus());
2418  }
2419 
2420  // Gurobi will silently impose that indicator variables are binary even if not
2421  // so specified by the user in the model. We return an error here if this is
2422  // the case to be consistent across solvers.
2423  {
2424  ASSIGN_OR_RETURN(const InvalidIndicators invalid_indicators,
2425  ListInvalidIndicators());
2426  RETURN_IF_ERROR(invalid_indicators.ToStatus());
2427  }
2428 
2429  RETURN_IF_ERROR(gurobi_->Optimize(grb_cb));
2430 
2431  // We flush message callbacks before testing for Gurobi error in case where
2432  // the unfinished line of message would help with the error.
2433  if (gurobi_cb_data != nullptr) {
2434  GurobiCallbackImplFlush(gurobi_cb_data->callback_input,
2435  gurobi_cb_data->message_callback_data);
2436  }
2437 
2438  ASSIGN_OR_RETURN(SolveResultProto solve_result,
2439  ExtractSolveResultProto(start, model_parameters));
2440  // Reset Gurobi parameters.
2441  // TODO(user): ensure that resetting parameters does not degrade
2442  // incrementalism performance.
2443  RETURN_IF_ERROR(gurobi_->ResetParameters());
2444 
2445  return solve_result;
2446 }
2447 
2448 MATH_OPT_REGISTER_SOLVER(SOLVER_TYPE_GUROBI, GurobiSolver::New)
2449 
2450 } // namespace math_opt
2451 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
std::function< absl::Status(const CallbackContext &)> Callback
Definition: g_gurobi.h:228
static absl::StatusOr< std::unique_ptr< Gurobi > > NewWithSharedPrimaryEnv(GRBenv *primary_env)
Definition: g_gurobi.cc:103
static absl::StatusOr< std::unique_ptr< Gurobi > > New(GRBenvUniquePtr primary_env=nullptr)
Definition: g_gurobi.cc:109
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
SatParameters parameters
ModelSharedTimeLimit * time_limit
const std::string name
int64_t value
#define GRB_INT_PAR_BARITERLIMIT
Definition: environment.h:477
#define GRB_SUPERBASIC
Definition: environment.h:476
#define GRB_INT_PAR_LOGTOCONSOLE
Definition: environment.h:607
#define GRB_DBL_ATTR_UB
Definition: environment.h:177
#define GRB_INT_ATTR_BRANCHPRIORITY
Definition: environment.h:182
#define GRB_DBL_ATTR_START
Definition: environment.h:180
#define GRB_DBL_PAR_MIPGAP
Definition: environment.h:491
#define GRB_DBL_PAR_FEASIBILITYTOL
Definition: environment.h:488
#define GRB_SOLUTION_LIMIT
Definition: environment.h:465
#define GRB_MAXIMIZE
Definition: environment.h:110
#define GRB_INT_PAR_SOLUTIONLIMIT
Definition: environment.h:481
#define GRB_NONBASIC_LOWER
Definition: environment.h:474
#define GRB_INT_ATTR_MODELSENSE
Definition: environment.h:163
#define GRB_INT_PAR_CUTS
Definition: environment.h:539
#define GRB_INFINITY
Definition: environment.h:113
#define GRB_INT_ATTR_VBASIS
Definition: environment.h:243
#define GRB_GREATER_EQUAL
Definition: environment.h:102
#define GRB_INT_PAR_SEED
Definition: environment.h:627
#define GRB_DBL_ATTR_NODECOUNT
Definition: environment.h:234
#define GRB_INT_PAR_PRESOLVE
Definition: environment.h:617
#define GRB_DBL_PAR_MIPGAPABS
Definition: environment.h:492
#define GRB_DBL_ATTR_ITERCOUNT
Definition: environment.h:232
#define GRB_INT_PAR_THREADS
Definition: environment.h:629
#define GRB_DBL_ATTR_BOUND_SVIO
Definition: environment.h:252
#define GRB_OPTIMAL
Definition: environment.h:457
#define GRB_DBL_PAR_CUTOFF
Definition: environment.h:478
#define GRB_INTEGER
Definition: environment.h:106
#define GRB_DBL_PAR_ITERATIONLIMIT
Definition: environment.h:479
#define GRB_INT_PAR_METHOD
Definition: environment.h:495
#define GRB_INT_ATTR_IS_QP
Definition: environment.h:166
#define GRB_DBL_ATTR_PI
Definition: environment.h:244
#define GRB_DBL_ATTR_OBJVAL
Definition: environment.h:225
#define GRB_NODE_LIMIT
Definition: environment.h:463
#define GRB_INT_PAR_LAZYCONSTRAINTS
Definition: environment.h:605
#define GRB_DBL_ATTR_XN
Definition: environment.h:239
#define GRB_CONTINUOUS
Definition: environment.h:104
#define GRB_INT_PAR_SCALEFLAG
Definition: environment.h:498
#define GRB_DBL_ATTR_OBJ
Definition: environment.h:178
#define GRB_DBL_PAR_HEURISTICS
Definition: environment.h:516
#define GRB_METHOD_BARRIER
Definition: environment.h:672
#define GRB_INT_ATTR_IS_MIP
Definition: environment.h:165
#define GRB_DBL_ATTR_CONSTR_RESIDUAL
Definition: environment.h:263
#define GRB_SOS_TYPE1
Definition: environment.h:111
#define GRB_INT_PAR_POOLSOLUTIONS
Definition: environment.h:645
#define GRB_CHAR_ATTR_VTYPE
Definition: environment.h:179
#define GRB_INT_ATTR_NUMSTART
Definition: environment.h:337
#define GRB_DBL_ATTR_BOUND_VIO
Definition: environment.h:251
#define GRB_TIME_LIMIT
Definition: environment.h:464
#define GRB_NONBASIC_UPPER
Definition: environment.h:475
#define GRB_DBL_ATTR_OBJCON
Definition: environment.h:164
#define GRB_DBL_PAR_BESTBDSTOP
Definition: environment.h:487
#define GRB_STR_ATTR_MODELNAME
Definition: environment.h:162
#define GRB_DBL_ATTR_CONSTR_VIO
Definition: environment.h:257
#define GRB_DBL_ATTR_RC
Definition: environment.h:241
#define GRB_DBL_ATTR_CONSTR_SRESIDUAL
Definition: environment.h:264
#define GRB_LOADED
Definition: environment.h:456
#define GRB_DBL_ATTR_CONSTR_SVIO
Definition: environment.h:258
#define GRB_INPROGRESS
Definition: environment.h:469
#define GRB_INT_ATTR_IS_QCP
Definition: environment.h:167
#define GRB_DBL_PAR_BESTOBJSTOP
Definition: environment.h:486
#define GRB_INF_OR_UNBD
Definition: environment.h:459
#define GRB_DBL_ATTR_X
Definition: environment.h:238
#define GRB_SUBOPTIMAL
Definition: environment.h:468
#define GRB_INFEASIBLE
Definition: environment.h:458
#define GRB_DBL_ATTR_RHS
Definition: environment.h:191
#define GRB_MAXINT
Definition: environment.h:115
#define GRB_INT_PAR_STARTNUMBER
Definition: environment.h:650
#define GRB_EQUAL
Definition: environment.h:103
#define GRB_SOS_TYPE2
Definition: environment.h:112
#define GRB_CHAR_ATTR_SENSE
Definition: environment.h:193
#define GRB_CUTOFF
Definition: environment.h:461
#define GRB_UNBOUNDED
Definition: environment.h:460
#define GRB_INT_ATTR_CBASIS
Definition: environment.h:249
#define GRB_METHOD_DUAL
Definition: environment.h:671
#define GRB_INT_ATTR_BARITERCOUNT
Definition: environment.h:233
#define GRB_BASIC
Definition: environment.h:473
#define GRB_MINIMIZE
Definition: environment.h:109
#define GRB_INT_ATTR_STATUS
Definition: environment.h:224
#define GRB_DBL_ATTR_FARKASDUAL
Definition: environment.h:312
#define GRB_LESS_EQUAL
Definition: environment.h:101
#define GRB_INTERRUPTED
Definition: environment.h:466
#define GRB_DBL_ATTR_POOLOBJVAL
Definition: environment.h:229
#define GRB_DBL_ATTR_LB
Definition: environment.h:176
#define GRB_INT_PAR_SOLUTIONNUMBER
Definition: environment.h:537
#define GRB_ITERATION_LIMIT
Definition: environment.h:462
#define GRB_INT_ATTR_SOLCOUNT
Definition: environment.h:231
#define GRB_NUMERIC
Definition: environment.h:467
#define GRB_METHOD_PRIMAL
Definition: environment.h:670
#define GRB_DBL_PAR_TIMELIMIT
Definition: environment.h:482
#define GRB_BINARY
Definition: environment.h:105
#define GRB_DBL_PAR_NODELIMIT
Definition: environment.h:480
#define GRB_USER_OBJ_LIMIT
Definition: environment.h:470
#define GRB_DBL_ATTR_UNBDRAY
Definition: environment.h:314
#define GRB_DBL_ATTR_OBJBOUND
Definition: environment.h:226
#define GRB_INT_PAR_PRECRUSH
Definition: environment.h:612
absl::Status status
Definition: g_gurobi.cc:41
Gurobi * gurobi
Definition: g_gurobi.cc:42
int index
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
Definition: map_util.h:154
auto & InsertKeyOrDie(Collection *const collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:173
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto &registration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
void GurobiCallbackImplFlush(const GurobiCallbackInput &callback_input, MessageCallbackData &message_callback_data)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
absl::Status GurobiCallbackImpl(const Gurobi::CallbackContext &context, const GurobiCallbackInput &callback_input, MessageCallbackData &message_callback_data, SolveInterrupter *const local_interrupter)
bool UpdateIsSupported(const ModelUpdateProto &update, const SupportedProblemStructures &support_menu)
TerminationProto TerminateForLimit(const LimitProto limit, const bool feasible, const absl::string_view detail)
absl::StatusOr< GRBenvUniquePtr > NewPrimaryEnvironment(std::optional< GurobiInitializerProto::ISVKey > proto_isv_key)
std::vector< bool > EventToGurobiWhere(const absl::flat_hash_set< CallbackEventProto > &events)
std::function< CallbackResult(const CallbackData &)> Callback
Definition: callback.h:93
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
std::function< void(const std::vector< std::string > &)> MessageCallback
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
std::unique_ptr< GRBenv, GurobiFreeEnv > GRBenvUniquePtr
Definition: g_gurobi.h:69
absl::flat_hash_set< CallbackEventProto > EventSet(const CallbackRegistrationProto &callback_registration)
Collection of objects used to extend the Constraint Solver library.
std::string ProtoEnumToString(ProtoEnumType enum_value)
bool GurobiIsCorrectlyInstalled()
Definition: environment.cc:32
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
Definition: protoutil.h:42
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition: protoutil.h:27
StatusBuilder InvalidArgumentErrorBuilder()
int column
Definition: parse_proto.cc:32
IntVar * upper_bound
Definition: routing.cc:1087
IntVar * lower_bound
Definition: routing.cc:1086
int nodes
std::vector< double > lower_bounds
std::vector< double > upper_bounds
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)
int64_t start
std::string message
Definition: trace.cc:399
double objective_value