Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
gurobi_solver.h
Go to the documentation of this file.
1// Copyright 2010-2025 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 ORTOOLS_MATH_OPT_SOLVERS_GUROBI_SOLVER_H_
15#define ORTOOLS_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/base/nullability.h"
25#include "absl/container/flat_hash_map.h"
26#include "absl/container/flat_hash_set.h"
27#include "absl/status/status.h"
28#include "absl/status/statusor.h"
29#include "absl/time/time.h"
30#include "absl/types/span.h"
49
50namespace operations_research {
51namespace math_opt {
52
53class GurobiSolver : public SolverInterface {
54 public:
55 static absl::StatusOr<std::unique_ptr<GurobiSolver>> New(
56 const ModelProto& input_model,
57 const SolverInterface::InitArgs& init_args);
58
59 absl::StatusOr<SolveResultProto> Solve(
60 const SolveParametersProto& parameters,
61 const ModelSolveParametersProto& model_parameters,
62 MessageCallback message_cb,
63 const CallbackRegistrationProto& callback_registration, Callback cb,
64 const SolveInterrupter* absl_nullable interrupter) override;
65 absl::StatusOr<bool> Update(const ModelUpdateProto& model_update) override;
66 absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
68 const SolveParametersProto& parameters, MessageCallback message_cb,
69 const SolveInterrupter* absl_nullable interrupter) override;
70
71 private:
72 struct GurobiCallbackData {
73 explicit GurobiCallbackData(
74 GurobiCallbackInput callback_input,
75 SolveInterrupter* absl_nullable const local_interrupter)
76 : callback_input(std::move(callback_input)),
77 local_interrupter(local_interrupter) {}
78 const GurobiCallbackInput callback_input;
79
80 // Interrupter triggered when either the user interrupter passed to Solve()
81 // is triggered or after one user callback returned a true `terminate`.
82 //
83 // This is not the user interrupter though so it safe for callbacks to
84 // trigger it.
85 //
86 // It is optional; it is not null when either we have a LP/MIP callback or a
87 // user interrupter. But it can be null if we only have a message callback.
88 SolveInterrupter* absl_nullable const local_interrupter;
89
90 MessageCallbackData message_callback_data;
91
92 absl::Status status = absl::OkStatus();
93 };
94
95 explicit GurobiSolver(std::unique_ptr<Gurobi> g_gurobi);
96
97 // For easing reading the code, we declare these types:
98 using VariableId = int64_t;
99 using AuxiliaryObjectiveId = int64_t;
100 using LinearConstraintId = int64_t;
101 using QuadraticConstraintId = int64_t;
102 using SecondOrderConeConstraintId = int64_t;
103 using Sos1ConstraintId = int64_t;
104 using Sos2ConstraintId = int64_t;
105 using IndicatorConstraintId = int64_t;
106 using AnyConstraintId = int64_t;
107 using GurobiVariableIndex = int;
108 using GurobiMultiObjectiveIndex = int;
109 using GurobiLinearConstraintIndex = int;
110 using GurobiQuadraticConstraintIndex = int;
111 using GurobiSosConstraintIndex = int;
112 // A collection of other constraints (e.g., norm, max, indicator) supported by
113 // Gurobi. All general constraints share the same index set. See for more
114 // detail: https://www.gurobi.com/documentation/9.5/refman/constraints.html.
115 using GurobiGeneralConstraintIndex = int;
116 using GurobiAnyConstraintIndex = int;
117
118 static constexpr GurobiVariableIndex kUnspecifiedIndex = -1;
119 static constexpr GurobiAnyConstraintIndex kUnspecifiedConstraint = -2;
120 static constexpr double kInf = std::numeric_limits<double>::infinity();
121
122 struct GurobiModelElements {
123 std::vector<GurobiVariableIndex> variables;
124 std::vector<GurobiLinearConstraintIndex> linear_constraints;
125 std::vector<GurobiQuadraticConstraintIndex> quadratic_constraints;
126 std::vector<GurobiSosConstraintIndex> sos_constraints;
127 std::vector<GurobiGeneralConstraintIndex> general_constraints;
128 };
129
130 // Data associated with each linear constraint. With it we know if the
131 // underlying representation is either:
132 // linear_terms <= upper_bound (if lower bound <= -GRB_INFINITY)
133 // linear_terms >= lower_bound (if upper bound >= GRB_INFINTY)
134 // linear_terms == xxxxx_bound (if upper_bound == lower_bound)
135 // linear_term - slack == 0 (with slack bounds equal to xxxxx_bound)
136 struct LinearConstraintData {
137 // Returns all Gurobi elements related to this constraint (including the
138 // linear constraint itself). Will CHECK-fail if any element is unspecified.
139 GurobiModelElements DependentElements() const;
140
141 GurobiLinearConstraintIndex constraint_index = kUnspecifiedConstraint;
142 // only valid for true ranged constraints.
143 GurobiVariableIndex slack_index = kUnspecifiedIndex;
144 double lower_bound = -kInf;
145 double upper_bound = kInf;
146 };
147
148 struct SecondOrderConeConstraintData {
149 // Returns all Gurobi elements related to this constraint (including the
150 // linear constraint itself). Will CHECK-fail if any element is unspecified.
151 GurobiModelElements DependentElements() const;
152
153 GurobiQuadraticConstraintIndex constraint_index = kUnspecifiedConstraint;
154 std::vector<GurobiVariableIndex> slack_variables;
155 std::vector<GurobiLinearConstraintIndex> slack_constraints;
156 };
157
158 struct SosConstraintData {
159 // Returns all Gurobi elements related to this constraint (including the
160 // linear constraint itself). Will CHECK-fail if any element is unspecified.
161 GurobiModelElements DependentElements() const;
162
163 GurobiSosConstraintIndex constraint_index = kUnspecifiedConstraint;
164 std::vector<GurobiVariableIndex> slack_variables;
165 std::vector<GurobiLinearConstraintIndex> slack_constraints;
166 };
167
168 struct IndicatorConstraintData {
169 // The Gurobi-numbered general constraint ID (Gurobi ids are shared among
170 // all general constraint types).
171 GurobiGeneralConstraintIndex constraint_index;
172 // The MathOpt-numbered indicator variable ID. Used for reporting invalid
173 // indicator variables.
174 int64_t indicator_variable_id;
175 };
176
177 struct SolutionClaims {
178 bool primal_feasible_solution_exists;
179 bool dual_feasible_solution_exists;
180 };
181
182 struct SolutionsAndClaims {
183 std::vector<SolutionProto> solutions;
184 SolutionClaims solution_claims;
185 };
186
187 template <typename SolutionType>
188 struct SolutionAndClaim {
189 std::optional<SolutionType> solution;
190 bool feasible_solution_exists = false;
191 };
192
193 using IdHashMap = gtl::linked_hash_map<int64_t, int>;
194
195 absl::StatusOr<SolveResultProto> ExtractSolveResultProto(
196 absl::Time start, const ModelSolveParametersProto& model_parameters);
197 absl::Status FillRays(const ModelSolveParametersProto& model_parameters,
198 SolutionClaims solution_claims,
199 SolveResultProto& result);
200 absl::StatusOr<GurobiSolver::SolutionsAndClaims> GetSolutions(
201 const ModelSolveParametersProto& model_parameters);
202 absl::StatusOr<SolveStatsProto> GetSolveStats(absl::Time start) const;
203
204 absl::StatusOr<double> GetGurobiBestDualBound() const;
205 absl::StatusOr<double> GetBestDualBound(
206 absl::Span<const SolutionProto> solutions) const;
207 absl::StatusOr<double> GetBestPrimalBound(
208 absl::Span<const SolutionProto> solutions) const;
209
210 bool PrimalSolutionQualityAvailable() const;
211 absl::StatusOr<double> GetPrimalSolutionQuality() const;
212
213 // Returns true if any entry in `grb_elements` is contained in the IIS.
214 absl::StatusOr<bool> AnyElementInIIS(const GurobiModelElements& grb_elements);
215 // Returns which variable bounds are in the IIS, or unset if neither are.
216 absl::StatusOr<std::optional<ModelSubsetProto::Bounds>> VariableBoundsInIIS(
217 GurobiVariableIndex grb_index);
218 // Returns true if any variable bound is contained in the IIS.
219 absl::StatusOr<bool> VariableInIIS(GurobiVariableIndex grb_index);
220 absl::StatusOr<std::optional<ModelSubsetProto::Bounds>> LinearConstraintInIIS(
221 const LinearConstraintData& grb_data);
222 absl::StatusOr<std::optional<ModelSubsetProto::Bounds>>
223 QuadraticConstraintInIIS(GurobiQuadraticConstraintIndex grb_index);
224 // NOTE: Gurobi may claim that an IIS is minimal when the returned message is
225 // not. This occurs because Gurobi does not return variable integrality IIS
226 // attributes, and because internally we apply model transformations to handle
227 // ranged constraints and linear expressions in nonlinear constraints.
228 absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
229 ExtractComputeInfeasibleSubsystemResultProto(bool proven_infeasible);
230
231 // Warning: is read from gurobi, take care with gurobi update.
232 absl::StatusOr<bool> IsMaximize() const;
233
234 absl::StatusOr<TerminationProto> ConvertTerminationReason(
235 int gurobi_status, SolutionClaims solution_claims,
236 double best_primal_bound, double best_dual_bound);
237
238 // Returns solution information appropriate and available for an LP (linear
239 // constraints + linear objective, only).
240 absl::StatusOr<SolutionsAndClaims> GetLpSolution(
241 const ModelSolveParametersProto& model_parameters);
242 // Returns solution information appropriate and available for a QP (linear
243 // constraints + quadratic objective, only).
244 absl::StatusOr<SolutionsAndClaims> GetQpSolution(
245 const ModelSolveParametersProto& model_parameters);
246 // Returns solution information appropriate and available for a QCP
247 // (linear/quadratic constraints + linear/quadratic objective, only).
248 absl::StatusOr<SolutionsAndClaims> GetQcpSolution(
249 const ModelSolveParametersProto& model_parameters);
250 // Returns solution information appropriate and available for a MIP
251 // (integrality on some/all decision variables). Following Gurobi's API, this
252 // is also used for any multi-objective model.
253 absl::StatusOr<SolutionsAndClaims> GetMipSolutions(
254 const ModelSolveParametersProto& model_parameters);
255
256 // return bool field should be true if a primal solution exists.
257 absl::StatusOr<SolutionAndClaim<PrimalSolutionProto>>
258 GetConvexPrimalSolutionIfAvailable(
259 const ModelSolveParametersProto& model_parameters);
260 absl::StatusOr<SolutionAndClaim<DualSolutionProto>>
261 GetConvexDualSolutionIfAvailable(
262 const ModelSolveParametersProto& model_parameters);
263 absl::StatusOr<std::optional<BasisProto>> GetBasisIfAvailable();
264
265 absl::Status SetParameters(
266 const SolveParametersProto& parameters,
267 const ModelSolveParametersProto& model_parameters = {});
268 absl::Status AddNewLinearConstraints(
269 const LinearConstraintsProto& constraints);
270 absl::Status AddNewQuadraticConstraints(
271 const google::protobuf::Map<QuadraticConstraintId,
272 QuadraticConstraintProto>& constraints);
273 absl::Status AddNewSecondOrderConeConstraints(
274 const google::protobuf::Map<SecondOrderConeConstraintId,
275 SecondOrderConeConstraintProto>& constraints);
276 absl::Status AddNewSosConstraints(
277 const google::protobuf::Map<AnyConstraintId, SosConstraintProto>&
278 constraints,
279 int sos_type,
280 absl::flat_hash_map<int64_t, SosConstraintData>& constraints_map);
281 absl::Status AddNewIndicatorConstraints(
282 const google::protobuf::Map<IndicatorConstraintId,
283 IndicatorConstraintProto>& constraints);
284 absl::Status AddNewVariables(const VariablesProto& new_variables);
285 absl::Status AddSingleObjective(const ObjectiveProto& objective);
286 absl::Status AddMultiObjectives(
287 const ObjectiveProto& primary_objective,
288 const google::protobuf::Map<int64_t, ObjectiveProto>&
289 auxiliary_objectives);
290 // * `objective_id` is the corresponding key into `multi_objectives_map_`; see
291 // that field for further comment.
292 // * `is_maximize` is true if the entire Gurobi model is the maximization
293 // sense (only one sense is permitted per model to be shared by all
294 // objectives). Note that this need not agree with `objective.maximize`.
295 absl::Status AddNewMultiObjective(
296 const ObjectiveProto& objective,
297 std::optional<AuxiliaryObjectiveId> objective_id, bool is_maximize);
298 absl::Status AddNewSlacks(
299 const std::vector<LinearConstraintData*>& new_slacks);
300 absl::Status ChangeCoefficients(const SparseDoubleMatrixProto& matrix);
301 // NOTE: Clears any existing quadratic objective terms.
302 absl::Status ResetQuadraticObjectiveTerms(
303 const SparseDoubleMatrixProto& terms);
304 // Updates objective so that it is the sum of everything in terms, plus all
305 // other terms prexisting in the objective that are not overwritten by terms.
306 absl::Status UpdateQuadraticObjectiveTerms(
307 const SparseDoubleMatrixProto& terms);
308 absl::Status LoadModel(const ModelProto& input_model);
309
310 absl::Status UpdateDoubleListAttribute(
311 const SparseDoubleVectorProto& update,
312 const char* absl_nonnull attribute_name, const IdHashMap& id_hash_map);
313 absl::Status UpdateInt32ListAttribute(const SparseInt32VectorProto& update,
314 const char* absl_nonnull attribute_name,
315 const IdHashMap& id_hash_map);
316
317 struct DeletedIndices {
318 std::vector<GurobiVariableIndex> variables;
319 std::vector<GurobiLinearConstraintIndex> linear_constraints;
320 std::vector<GurobiQuadraticConstraintIndex> quadratic_constraints;
321 std::vector<GurobiSosConstraintIndex> sos_constraints;
322 std::vector<GurobiGeneralConstraintIndex> general_constraints;
323 };
324
325 void UpdateGurobiIndices(const DeletedIndices& deleted_indices);
326 absl::Status UpdateLinearConstraints(
327 const LinearConstraintUpdatesProto& update,
328 std::vector<GurobiVariableIndex>& deleted_variables_index);
329
330 int get_model_index(GurobiVariableIndex index) const { return index; }
331 int get_model_index(const LinearConstraintData& index) const {
332 return index.constraint_index;
333 }
334
335 // Fills in result with the values in gurobi_values aided by the index
336 // conversion from map which should be either variables_map_ or
337 // linear_constraints_map_ as appropriate. Only key/value pairs that passes
338 // the filter predicate are added.
339 template <typename T>
340 void GurobiVectorToSparseDoubleVector(
341 absl::Span<const double> gurobi_values, const T& map,
343 const SparseVectorFilterProto& filter) const;
344 absl::StatusOr<BasisProto> GetGurobiBasis();
345 absl::Status SetGurobiBasis(const BasisProto& basis);
346 absl::StatusOr<DualRayProto> GetGurobiDualRay(
347 const SparseVectorFilterProto& linear_constraints_filter,
348 const SparseVectorFilterProto& variables_filter, bool is_maximize);
349 // Returns true if the problem has any integrality constraints.
350 absl::StatusOr<bool> IsMIP() const;
351 // Returns true if the problem has a quadratic objective.
352 absl::StatusOr<bool> IsQP() const;
353 // Returns true if the problem has any quadratic constraints.
354 absl::StatusOr<bool> IsQCP() const;
355
356 absl::StatusOr<std::unique_ptr<GurobiCallbackData>> RegisterCallback(
357 const CallbackRegistrationProto& registration, Callback cb,
358 MessageCallback message_cb, absl::Time start,
359 SolveInterrupter* absl_nullable local_interrupter);
360
361 // Returns the ids of variables and linear constraints with inverted bounds.
362 absl::StatusOr<InvertedBounds> ListInvertedBounds() const;
363
364 // True if the model is in "multi-objective" mode: That is, at some point it
365 // has been modified via the multi-objective API.
366 bool is_multi_objective_mode() const;
367
368 // Returns the ids of indicator constraint/variables that are invalid because
369 // the indicator is not a binary variable.
370 absl::StatusOr<InvalidIndicators> ListInvalidIndicators() const;
371
372 struct VariableEqualToExpression {
373 GurobiVariableIndex variable_index;
374 GurobiLinearConstraintIndex constraint_index;
375 };
376
377 // If `expression` is equivalent to a single variable in the model, returns
378 // the corresponding MathOpt variable ID. Otherwise, returns nullopt.
379 std::optional<VariableId> TryExtractVariable(
380 const LinearExpressionProto& expression);
381 // Returns a Gurobi variable that is constrained to be equal to `expression`
382 // in `.variable_index`. The variable is newly added to the model and
383 // `.constraint_index` is set and refers to the Gurobi linear constraint index
384 // for a slack constraint just added to the model such that:
385 // `expression` == `.variable_index`.
386 // TODO(b/267310257): Use this for linear constraint slacks, and maybe move it
387 // up the stack to a bridge.
388 absl::StatusOr<VariableEqualToExpression>
389 CreateSlackVariableEqualToExpression(const LinearExpressionProto& expression);
390
391 // May only be called if `is_multi_objective_mode()` is true.
392 absl::Status SetMultiObjectiveParameters(
393 const ModelSolveParametersProto& model_parameters);
394
395 absl::Status ResetModelParameters(
396 const ModelSolveParametersProto& model_parameters);
397
398 const std::unique_ptr<Gurobi> gurobi_;
399
400 // Note that we use linked_hash_map for the indices of the gurobi_model_
401 // variables and linear constraints to ensure that iteration over the map
402 // maintains their insertion order (and, thus, the order in which they appear
403 // in the model). As of 2022-06-28 this property is necessary to ensure that
404 // duals and bases are deterministically ordered.
405
406 // Internal correspondence from variable proto IDs to Gurobi-numbered
407 // variables.
409 // An unset key corresponds to the `ModelProto.objective` field; all other
410 // entries come from `ModelProto.auxiliary_objectives`.
411 absl::flat_hash_map<std::optional<AuxiliaryObjectiveId>,
412 GurobiMultiObjectiveIndex>
413 multi_objectives_map_;
414 // Internal correspondence from linear constraint proto IDs to
415 // Gurobi-numbered linear constraint and extra information.
417 linear_constraints_map_;
418 // Internal correspondence from quadratic constraint proto IDs to
419 // Gurobi-numbered quadratic constraint.
420 absl::flat_hash_map<QuadraticConstraintId, GurobiQuadraticConstraintIndex>
421 quadratic_constraints_map_;
422 // Internal correspondence from second-order cone constraint proto IDs to
423 // corresponding Gurobi information.
424 absl::flat_hash_map<SecondOrderConeConstraintId,
425 SecondOrderConeConstraintData>
426 soc_constraints_map_;
427 // Internal correspondence from SOS1 constraint proto IDs to Gurobi-numbered
428 // SOS constraint (Gurobi ids are shared between SOS1 and SOS2).
429 absl::flat_hash_map<Sos1ConstraintId, SosConstraintData>
430 sos1_constraints_map_;
431 // Internal correspondence from SOS2 constraint proto IDs to Gurobi-numbered
432 // SOS constraint (Gurobi ids are shared between SOS1 and SOS2).
433 absl::flat_hash_map<Sos2ConstraintId, SosConstraintData>
434 sos2_constraints_map_;
435 // Internal correspondence from indicator constraint proto IDs to indicator
436 // constraint data. If unset, the values indicate that the indicator variable
437 // is unset; since Gurobi does not support this, we simply do not add the
438 // constraint to the model.
439 absl::flat_hash_map<IndicatorConstraintId,
440 std::optional<IndicatorConstraintData>>
441 indicator_constraints_map_;
442
443 // Fields to track the number of Gurobi variables and constraints. These
444 // quantities are updated immediately after adding or removing to the model,
445 // so it is correct even if GRBUpdate has not yet been called.
446
447 // Number of Gurobi variables.
448 int num_gurobi_variables_ = 0;
449 // Number of Gurobi linear constraints.
450 int num_gurobi_lin_cons_ = 0;
451 // Number of Gurobi quadratic constraints.
452 int num_gurobi_quad_cons_ = 0;
453 // Number of Gurobi SOS constraints.
454 int num_gurobi_sos_cons_ = 0;
455 // Number of Gurobi general constraints.
456 int num_gurobi_gen_cons_ = 0;
457
458 // Gurobi does not expose a way to query quadratic objective terms from the
459 // model, so we track them. Notes:
460 // * Keys are in upper triangular order (.first <= .second)
461 // * Terms not in the map have zero coefficients
462 // Note also that the map may also have entries with zero coefficient value.
463 absl::flat_hash_map<std::pair<VariableId, VariableId>, double>
464 quadratic_objective_coefficients_;
465
466 // Some MathOpt variables cannot be deleted without rendering the rest of the
467 // model invalid. We flag these variables to check in CanUpdate(). As of
468 // 2022-07-01 elements are not erased from this set, and so it may be overly
469 // conservative in rejecting updates.
470 absl::flat_hash_set<VariableId> undeletable_variables_;
471
472 static constexpr int kGrbBasicConstraint = 0;
473 static constexpr int kGrbNonBasicConstraint = -1;
474};
475
476} // namespace math_opt
477} // namespace operations_research
478
479#endif // ORTOOLS_MATH_OPT_SOLVERS_GUROBI_SOLVER_H_
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto &parameters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, const SolveInterrupter *absl_nullable interrupter) override
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto &parameters, MessageCallback message_cb, const SolveInterrupter *absl_nullable interrupter) override
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)
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >( const CallbackDataProto &)> Callback
OR-Tools root namespace.
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid