OR-Tools  9.6
gurobi_solver.h
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 
14 #ifndef OR_TOOLS_MATH_OPT_SOLVERS_GUROBI_SOLVER_H_
15 #define OR_TOOLS_MATH_OPT_SOLVERS_GUROBI_SOLVER_H_
16 
17 #include <cstdint>
18 #include <limits>
19 #include <memory>
20 #include <optional>
21 #include <utility>
22 #include <vector>
23 
24 #include "absl/container/flat_hash_map.h"
25 #include "absl/container/flat_hash_set.h"
26 #include "absl/status/status.h"
27 #include "absl/status/statusor.h"
28 #include "absl/time/time.h"
29 #include "absl/types/span.h"
32 #include "ortools/math_opt/callback.pb.h"
37 #include "ortools/math_opt/model.pb.h"
38 #include "ortools/math_opt/model_parameters.pb.h"
39 #include "ortools/math_opt/model_update.pb.h"
40 #include "ortools/math_opt/parameters.pb.h"
41 #include "ortools/math_opt/result.pb.h"
42 #include "ortools/math_opt/solution.pb.h"
46 #include "ortools/math_opt/sparse_containers.pb.h"
47 
48 namespace operations_research {
49 namespace math_opt {
50 
51 class GurobiSolver : public SolverInterface {
52  public:
53  static absl::StatusOr<std::unique_ptr<GurobiSolver>> New(
54  const ModelProto& input_model,
55  const SolverInterface::InitArgs& init_args);
56 
57  absl::StatusOr<SolveResultProto> Solve(
58  const SolveParametersProto& parameters,
59  const ModelSolveParametersProto& model_parameters,
60  MessageCallback message_cb,
61  const CallbackRegistrationProto& callback_registration, Callback cb,
62  SolveInterrupter* interrupter) override;
63  absl::StatusOr<bool> Update(const ModelUpdateProto& model_update) override;
64 
65  private:
66  struct GurobiCallbackData {
67  explicit GurobiCallbackData(GurobiCallbackInput callback_input,
68  SolveInterrupter* const local_interrupter)
69  : callback_input(std::move(callback_input)),
70  local_interrupter(local_interrupter) {}
71  const GurobiCallbackInput callback_input;
72 
73  // Interrupter triggered when either the user interrupter passed to Solve()
74  // is triggered or after one user callback returned a true `terminate`.
75  //
76  // This is not the user interrupter though so it safe for callbacks to
77  // trigger it.
78  //
79  // It is optional; it is not null when either we have a LP/MIP callback or a
80  // user interrupter. But it can be null if we only have a message callback.
81  SolveInterrupter* const local_interrupter;
82 
83  MessageCallbackData message_callback_data;
84 
85  absl::Status status = absl::OkStatus();
86  };
87 
88  explicit GurobiSolver(std::unique_ptr<Gurobi> g_gurobi);
89 
90  // For easing reading the code, we declare these types:
91  using VariableId = int64_t;
92  using LinearConstraintId = int64_t;
93  using QuadraticConstraintId = int64_t;
94  using Sos1ConstraintId = int64_t;
95  using Sos2ConstraintId = int64_t;
96  using IndicatorConstraintId = int64_t;
97  using AnyConstraintId = int64_t;
98  using GurobiVariableIndex = int;
99  using GurobiLinearConstraintIndex = int;
100  using GurobiQuadraticConstraintIndex = int;
101  using GurobiSosConstraintIndex = int;
102  // A collection of other constraints (e.g., norm, max, indicator) supported by
103  // Gurobi. All general constraints share the same index set. See for more
104  // detail: https://www.gurobi.com/documentation/9.5/refman/constraints.html.
105  using GurobiGeneralConstraintIndex = int;
106  using GurobiAnyConstraintIndex = int;
107 
108  static constexpr GurobiVariableIndex kUnspecifiedIndex = -1;
109  static constexpr GurobiAnyConstraintIndex kUnspecifiedConstraint = -2;
110  static constexpr double kInf = std::numeric_limits<double>::infinity();
111 
112  // Data associated with each linear constraint. With it we know if the
113  // underlying representation is either:
114  // linear_terms <= upper_bound (if lower bound <= -GRB_INFINITY)
115  // linear_terms >= lower_bound (if upper bound >= GRB_INFINTY)
116  // linear_terms == xxxxx_bound (if upper_bound == lower_bound)
117  // linear_term - slack == 0 (with slack bounds equal to xxxxx_bound)
118  struct LinearConstraintData {
119  GurobiLinearConstraintIndex constraint_index = kUnspecifiedConstraint;
120  // only valid for true ranged constraints.
121  GurobiVariableIndex slack_index = kUnspecifiedIndex;
122  double lower_bound = -kInf;
123  double upper_bound = kInf;
124  };
125 
126  struct SosConstraintData {
127  GurobiSosConstraintIndex constraint_index = kUnspecifiedConstraint;
128  std::vector<GurobiVariableIndex> slack_variables;
129  std::vector<GurobiLinearConstraintIndex> slack_constraints;
130  };
131 
132  struct IndicatorConstraintData {
133  // The Gurobi-numbered general constraint ID (Gurobi ids are shared among
134  // all general constraint types).
135  GurobiGeneralConstraintIndex constraint_index;
136  // The MathOpt-numbered indicator variable ID. Used for reporting invalid
137  // indicator variables.
138  int64_t indicator_variable_id;
139  };
140 
141  struct SolutionClaims {
142  bool primal_feasible_solution_exists;
143  bool dual_feasible_solution_exists;
144  };
145 
146  struct SolutionsAndClaims {
147  std::vector<SolutionProto> solutions;
148  SolutionClaims solution_claims;
149  };
150 
151  template <typename SolutionType>
152  struct SolutionAndClaim {
153  std::optional<SolutionType> solution;
154  bool feasible_solution_exists = false;
155  };
156 
158 
159  absl::StatusOr<ProblemStatusProto> GetProblemStatus(
160  const int grb_termination, const SolutionClaims solution_claims);
161  absl::StatusOr<SolveResultProto> ExtractSolveResultProto(
162  absl::Time start, const ModelSolveParametersProto& model_parameters);
163  absl::Status FillRays(const ModelSolveParametersProto& model_parameters,
164  const SolutionClaims solution_claims,
165  SolveResultProto& result);
166  absl::StatusOr<GurobiSolver::SolutionsAndClaims> GetSolutions(
167  const ModelSolveParametersProto& model_parameters);
168  absl::StatusOr<SolveStatsProto> GetSolveStats(absl::Time start,
169  SolutionClaims solution_claims);
170 
171  absl::StatusOr<double> GetBestDualBound();
172  absl::StatusOr<double> GetBestPrimalBound(bool has_primal_feasible_solution);
173  bool PrimalSolutionQualityAvailable() const;
174  absl::StatusOr<double> GetPrimalSolutionQuality() const;
175 
176  // Warning: is read from gurobi, take care with gurobi update.
177  absl::StatusOr<bool> IsMaximize() const;
178 
179  static absl::StatusOr<TerminationProto> ConvertTerminationReason(
180  int gurobi_status, SolutionClaims solution_claims);
181 
182  // Returns solution information appropriate and available for an LP (linear
183  // constraints + linear objective, only).
184  absl::StatusOr<SolutionsAndClaims> GetLpSolution(
185  const ModelSolveParametersProto& model_parameters);
186  // Returns solution information appropriate and available for a QP (linear
187  // constraints + quadratic objective, only).
188  absl::StatusOr<SolutionsAndClaims> GetQpSolution(
189  const ModelSolveParametersProto& model_parameters);
190  // Returns solution information appropriate and available for a QCP
191  // (linear/quadratic constraints + linear/quadratic objective, only).
192  absl::StatusOr<SolutionsAndClaims> GetQcpSolution(
193  const ModelSolveParametersProto& model_parameters);
194  // Returns solution information appropriate and available for a MIP
195  // (integrality on some/all decision variables).
196  absl::StatusOr<SolutionsAndClaims> GetMipSolutions(
197  const ModelSolveParametersProto& model_parameters);
198 
199  // return bool field should be true if a primal solution exists.
200  absl::StatusOr<SolutionAndClaim<PrimalSolutionProto>>
201  GetConvexPrimalSolutionIfAvailable(
202  const ModelSolveParametersProto& model_parameters);
203  absl::StatusOr<SolutionAndClaim<DualSolutionProto>>
204  GetLpDualSolutionIfAvailable(
205  const ModelSolveParametersProto& model_parameters);
206  absl::StatusOr<std::optional<BasisProto>> GetBasisIfAvailable();
207 
208  absl::Status SetParameters(const SolveParametersProto& parameters);
209  absl::Status AddNewLinearConstraints(
210  const LinearConstraintsProto& constraints);
211  absl::Status AddNewQuadraticConstraints(
212  const google::protobuf::Map<QuadraticConstraintId,
213  QuadraticConstraintProto>& constraints);
214  absl::Status AddNewSosConstraints(
215  const google::protobuf::Map<AnyConstraintId, SosConstraintProto>&
216  constraints,
217  int sos_type,
218  absl::flat_hash_map<int64_t, SosConstraintData>& constraints_map);
219  absl::Status AddNewIndicatorConstraints(
220  const google::protobuf::Map<IndicatorConstraintId,
221  IndicatorConstraintProto>& constraints);
222  absl::Status AddNewVariables(const VariablesProto& new_variables);
223  absl::Status AddNewSlacks(
224  const std::vector<LinearConstraintData*>& new_slacks);
225  absl::Status ChangeCoefficients(const SparseDoubleMatrixProto& matrix);
226  // NOTE: Clears any existing quadratic objective terms.
227  absl::Status ResetQuadraticObjectiveTerms(
228  const SparseDoubleMatrixProto& terms);
229  // Updates objective so that it is the sum of everything in terms, plus all
230  // other terms prexisting in the objective that are not overwritten by terms.
231  absl::Status UpdateQuadraticObjectiveTerms(
232  const SparseDoubleMatrixProto& terms);
233  absl::Status LoadModel(const ModelProto& input_model);
234 
235  absl::Status UpdateDoubleListAttribute(const SparseDoubleVectorProto& update,
236  const char* attribute_name,
237  const IdHashMap& id_hash_map);
238  absl::Status UpdateInt32ListAttribute(const SparseInt32VectorProto& update,
239  const char* attribute_name,
240  const IdHashMap& id_hash_map);
241 
242  struct DeletedIndices {
243  std::vector<GurobiVariableIndex> variables;
244  std::vector<GurobiLinearConstraintIndex> linear_constraints;
245  std::vector<GurobiQuadraticConstraintIndex> quadratic_constraints;
246  std::vector<GurobiSosConstraintIndex> sos_constraints;
247  std::vector<GurobiGeneralConstraintIndex> general_constraints;
248  };
249 
250  void UpdateGurobiIndices(const DeletedIndices& deleted_indices);
251  absl::Status UpdateLinearConstraints(
252  const LinearConstraintUpdatesProto& update,
253  std::vector<GurobiVariableIndex>& deleted_variables_index);
254 
255  int get_model_index(GurobiVariableIndex index) const { return index; }
256  int get_model_index(const LinearConstraintData& index) const {
257  return index.constraint_index;
258  }
259 
260  // Fills in result with the values in gurobi_values aided by the index
261  // conversion from map which should be either variables_map_ or
262  // linear_constraints_map_ as appropriate. Only key/value pairs that passes
263  // the filter predicate are added.
264  template <typename T>
265  void GurobiVectorToSparseDoubleVector(
266  absl::Span<const double> gurobi_values, const T& map,
267  SparseDoubleVectorProto& result,
268  const SparseVectorFilterProto& filter) const;
269  absl::StatusOr<BasisProto> GetGurobiBasis();
270  absl::Status SetGurobiBasis(const BasisProto& basis);
271  absl::StatusOr<DualRayProto> GetGurobiDualRay(
272  const SparseVectorFilterProto& linear_constraints_filter,
273  const SparseVectorFilterProto& variables_filter, bool is_maximize);
274  // Returns true if the problem has any integrality constraints.
275  absl::StatusOr<bool> IsMIP() const;
276  // Returns true if the problem has a quadratic objective.
277  absl::StatusOr<bool> IsQP() const;
278  // Returns true if the problem has any quadratic constraints.
279  absl::StatusOr<bool> IsQCP() const;
280 
281  absl::StatusOr<std::unique_ptr<GurobiCallbackData>> RegisterCallback(
282  const CallbackRegistrationProto& registration, Callback cb,
283  const MessageCallback message_cb, absl::Time start,
284  SolveInterrupter* interrupter);
285 
286  // Returns the ids of variables and linear constraints with inverted bounds.
287  absl::StatusOr<InvertedBounds> ListInvertedBounds() const;
288 
289  // Returns the ids of indicator constraint/variables that are invalid because
290  // the indicator is not a binary variable.
291  absl::StatusOr<InvalidIndicators> ListInvalidIndicators() const;
292 
293  const std::unique_ptr<Gurobi> gurobi_;
294 
295  // Note that we use linked_hash_map for the indices of the gurobi_model_
296  // variables and linear constraints to ensure that iteration over the map
297  // maintains their insertion order (and, thus, the order in which they appear
298  // in the model). As of 2022-06-28 this property is necessary to ensure that
299  // duals and bases are deterministically ordered.
300 
301  // Internal correspondence from variable proto IDs to Gurobi-numbered
302  // variables.
304  // Internal correspondence from linear constraint proto IDs to
305  // Gurobi-numbered linear constraint and extra information.
307  linear_constraints_map_;
308  // Internal correspondence from quadratic constraint proto IDs to
309  // Gurobi-numbered quadratic constraint.
310  absl::flat_hash_map<QuadraticConstraintId, GurobiQuadraticConstraintIndex>
311  quadratic_constraints_map_;
312  // Internal correspondence from SOS1 constraint proto IDs to Gurobi-numbered
313  // SOS constraint (Gurobi ids are shared between SOS1 and SOS2).
314  absl::flat_hash_map<Sos1ConstraintId, SosConstraintData>
315  sos1_constraints_map_;
316  // Internal correspondence from SOS2 constraint proto IDs to Gurobi-numbered
317  // SOS constraint (Gurobi ids are shared between SOS1 and SOS2).
318  absl::flat_hash_map<Sos2ConstraintId, SosConstraintData>
319  sos2_constraints_map_;
320  // Internal correspondence from indicator constraint proto IDs to indicator
321  // constraint data. If unset, the values indicate that the indicator variable
322  // is unset; since Gurobi does not support this, we simply do not add the
323  // constraint to the model.
324  absl::flat_hash_map<IndicatorConstraintId,
325  std::optional<IndicatorConstraintData>>
326  indicator_constraints_map_;
327 
328  // Fields to track the number of Gurobi variables and constraints. These
329  // quantities are updated immediately after adding or removing to the model,
330  // so it is correct even if GRBUpdate has not yet been called.
331 
332  // Number of Gurobi variables.
333  int num_gurobi_variables_ = 0;
334  // Number of Gurobi linear constraints.
335  int num_gurobi_lin_cons_ = 0;
336  // Number of Gurobi quadratic constraints.
337  int num_gurobi_quad_cons_ = 0;
338  // Number of Gurobi SOS constraints.
339  int num_gurobi_sos_cons_ = 0;
340  // Number of Gurobi general constraints.
341  int num_gurobi_gen_cons_ = 0;
342 
343  // Gurobi does not expose a way to query quadratic objective terms from the
344  // model, so we track them. Notes:
345  // * Keys are in upper triangular order (.first <= .second)
346  // * Terms not in the map have zero coefficients
347  // Note also that the map may also have entries with zero coefficient value.
348  absl::flat_hash_map<std::pair<VariableId, VariableId>, double>
349  quadratic_objective_coefficients_;
350 
351  // Some MathOpt variables cannot be deleted without rendering the rest of the
352  // model invalid. We flag these variables to check in CanUpdate(). As of
353  // 2022-07-01 elements are not erased from this set, and so it may be overly
354  // conservative in rejecting updates.
355  absl::flat_hash_set<VariableId> undeletable_variables_;
356 
357  static constexpr int kGrbBasicConstraint = 0;
358  static constexpr int kGrbNonBasicConstraint = -1;
359 };
360 
361 } // namespace math_opt
362 } // namespace operations_research
363 
364 #endif // OR_TOOLS_MATH_OPT_SOLVERS_GUROBI_SOLVER_H_
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
static absl::StatusOr< std::unique_ptr< GurobiSolver > > New(const ModelProto &input_model, const SolverInterface::InitArgs &init_args)
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto &parameters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, SolveInterrupter *interrupter) override
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
SatParameters parameters
absl::Status status
Definition: g_gurobi.cc:41
int index
Collection of objects used to extend the Constraint Solver library.
IntVar * upper_bound
Definition: routing.cc:1087
IntVar * lower_bound
Definition: routing.cc:1086
int64_t start