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