Google OR-Tools v9.11
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-2024 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
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
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(const SolveParametersProto& parameters);
264 absl::Status AddNewLinearConstraints(
265 const LinearConstraintsProto& constraints);
266 absl::Status AddNewQuadraticConstraints(
267 const google::protobuf::Map<QuadraticConstraintId,
268 QuadraticConstraintProto>& constraints);
269 absl::Status AddNewSecondOrderConeConstraints(
270 const google::protobuf::Map<SecondOrderConeConstraintId,
271 SecondOrderConeConstraintProto>& constraints);
272 absl::Status AddNewSosConstraints(
273 const google::protobuf::Map<AnyConstraintId, SosConstraintProto>&
274 constraints,
275 int sos_type,
276 absl::flat_hash_map<int64_t, SosConstraintData>& constraints_map);
277 absl::Status AddNewIndicatorConstraints(
278 const google::protobuf::Map<IndicatorConstraintId,
279 IndicatorConstraintProto>& constraints);
280 absl::Status AddNewVariables(const VariablesProto& new_variables);
281 absl::Status AddSingleObjective(const ObjectiveProto& objective);
282 absl::Status AddMultiObjectives(
283 const ObjectiveProto& primary_objective,
284 const google::protobuf::Map<int64_t, ObjectiveProto>&
285 auxiliary_objectives);
286 // * `objective_id` is the corresponding key into `multi_objectives_map_`; see
287 // that field for further comment.
288 // * `is_maximize` is true if the entire Gurobi model is the maximization
289 // sense (only one sense is permitted per model to be shared by all
290 // objectives). Note that this need not agree with `objective.maximize`.
291 absl::Status AddNewMultiObjective(
292 const ObjectiveProto& objective,
293 std::optional<AuxiliaryObjectiveId> objective_id, bool is_maximize);
294 absl::Status AddNewSlacks(
295 const std::vector<LinearConstraintData*>& new_slacks);
296 absl::Status ChangeCoefficients(const SparseDoubleMatrixProto& matrix);
297 // NOTE: Clears any existing quadratic objective terms.
298 absl::Status ResetQuadraticObjectiveTerms(
299 const SparseDoubleMatrixProto& terms);
300 // Updates objective so that it is the sum of everything in terms, plus all
301 // other terms prexisting in the objective that are not overwritten by terms.
302 absl::Status UpdateQuadraticObjectiveTerms(
303 const SparseDoubleMatrixProto& terms);
304 absl::Status LoadModel(const ModelProto& input_model);
305
306 absl::Status UpdateDoubleListAttribute(const SparseDoubleVectorProto& update,
307 const char* attribute_name,
308 const IdHashMap& id_hash_map);
309 absl::Status UpdateInt32ListAttribute(const SparseInt32VectorProto& update,
310 const char* attribute_name,
311 const IdHashMap& id_hash_map);
312
313 struct DeletedIndices {
314 std::vector<GurobiVariableIndex> variables;
315 std::vector<GurobiLinearConstraintIndex> linear_constraints;
316 std::vector<GurobiQuadraticConstraintIndex> quadratic_constraints;
317 std::vector<GurobiSosConstraintIndex> sos_constraints;
318 std::vector<GurobiGeneralConstraintIndex> general_constraints;
319 };
320
321 void UpdateGurobiIndices(const DeletedIndices& deleted_indices);
322 absl::Status UpdateLinearConstraints(
323 const LinearConstraintUpdatesProto& update,
324 std::vector<GurobiVariableIndex>& deleted_variables_index);
325
326 int get_model_index(GurobiVariableIndex index) const { return index; }
327 int get_model_index(const LinearConstraintData& index) const {
328 return index.constraint_index;
329 }
330
331 // Fills in result with the values in gurobi_values aided by the index
332 // conversion from map which should be either variables_map_ or
333 // linear_constraints_map_ as appropriate. Only key/value pairs that passes
334 // the filter predicate are added.
335 template <typename T>
336 void GurobiVectorToSparseDoubleVector(
337 absl::Span<const double> gurobi_values, const T& map,
338 SparseDoubleVectorProto& result,
339 const SparseVectorFilterProto& filter) const;
340 absl::StatusOr<BasisProto> GetGurobiBasis();
341 absl::Status SetGurobiBasis(const BasisProto& basis);
342 absl::StatusOr<DualRayProto> GetGurobiDualRay(
343 const SparseVectorFilterProto& linear_constraints_filter,
344 const SparseVectorFilterProto& variables_filter, bool is_maximize);
345 // Returns true if the problem has any integrality constraints.
346 absl::StatusOr<bool> IsMIP() const;
347 // Returns true if the problem has a quadratic objective.
348 absl::StatusOr<bool> IsQP() const;
349 // Returns true if the problem has any quadratic constraints.
350 absl::StatusOr<bool> IsQCP() const;
351
352 absl::StatusOr<std::unique_ptr<GurobiCallbackData>> RegisterCallback(
353 const CallbackRegistrationProto& registration, Callback cb,
354 MessageCallback message_cb, absl::Time start,
355 SolveInterrupter* local_interrupter);
356
357 // Returns the ids of variables and linear constraints with inverted bounds.
358 absl::StatusOr<InvertedBounds> ListInvertedBounds() const;
359
360 // True if the model is in "multi-objective" mode: That is, at some point it
361 // has been modified via the multi-objective API.
362 bool is_multi_objective_mode() const;
363
364 // Returns the ids of indicator constraint/variables that are invalid because
365 // the indicator is not a binary variable.
366 absl::StatusOr<InvalidIndicators> ListInvalidIndicators() const;
367
368 struct VariableEqualToExpression {
369 GurobiVariableIndex variable_index;
370 GurobiLinearConstraintIndex constraint_index;
371 };
372
373 // If `expression` is equivalent to a single variable in the model, returns
374 // the corresponding MathOpt variable ID. Otherwise, returns nullopt.
375 std::optional<VariableId> TryExtractVariable(
376 const LinearExpressionProto& expression);
377 // Returns a Gurobi variable that is constrained to be equal to `expression`
378 // in `.variable_index`. The variable is newly added to the model and
379 // `.constraint_index` is set and refers to the Gurobi linear constraint index
380 // for a slack constraint just added to the model such that:
381 // `expression` == `.variable_index`.
382 // TODO(b/267310257): Use this for linear constraint slacks, and maybe move it
383 // up the stack to a bridge.
384 absl::StatusOr<VariableEqualToExpression>
385 CreateSlackVariableEqualToExpression(const LinearExpressionProto& expression);
386
387 absl::Status SetMultiObjectiveTolerances(
388 const ModelSolveParametersProto& model_parameters);
389
390 absl::Status ResetModelParameters(
391 const ModelSolveParametersProto& model_parameters);
392
393 const std::unique_ptr<Gurobi> gurobi_;
394
395 // Note that we use linked_hash_map for the indices of the gurobi_model_
396 // variables and linear constraints to ensure that iteration over the map
397 // maintains their insertion order (and, thus, the order in which they appear
398 // in the model). As of 2022-06-28 this property is necessary to ensure that
399 // duals and bases are deterministically ordered.
400
401 // Internal correspondence from variable proto IDs to Gurobi-numbered
402 // variables.
404 // An unset key corresponds to the `ModelProto.objective` field; all other
405 // entries come from `ModelProto.auxiliary_objectives`.
406 absl::flat_hash_map<std::optional<AuxiliaryObjectiveId>,
407 GurobiMultiObjectiveIndex>
408 multi_objectives_map_;
409 // Internal correspondence from linear constraint proto IDs to
410 // Gurobi-numbered linear constraint and extra information.
412 linear_constraints_map_;
413 // Internal correspondence from quadratic constraint proto IDs to
414 // Gurobi-numbered quadratic constraint.
415 absl::flat_hash_map<QuadraticConstraintId, GurobiQuadraticConstraintIndex>
416 quadratic_constraints_map_;
417 // Internal correspondence from second-order cone constraint proto IDs to
418 // corresponding Gurobi information.
419 absl::flat_hash_map<SecondOrderConeConstraintId,
420 SecondOrderConeConstraintData>
421 soc_constraints_map_;
422 // Internal correspondence from SOS1 constraint proto IDs to Gurobi-numbered
423 // SOS constraint (Gurobi ids are shared between SOS1 and SOS2).
424 absl::flat_hash_map<Sos1ConstraintId, SosConstraintData>
425 sos1_constraints_map_;
426 // Internal correspondence from SOS2 constraint proto IDs to Gurobi-numbered
427 // SOS constraint (Gurobi ids are shared between SOS1 and SOS2).
428 absl::flat_hash_map<Sos2ConstraintId, SosConstraintData>
429 sos2_constraints_map_;
430 // Internal correspondence from indicator constraint proto IDs to indicator
431 // constraint data. If unset, the values indicate that the indicator variable
432 // is unset; since Gurobi does not support this, we simply do not add the
433 // constraint to the model.
434 absl::flat_hash_map<IndicatorConstraintId,
435 std::optional<IndicatorConstraintData>>
436 indicator_constraints_map_;
437
438 // Fields to track the number of Gurobi variables and constraints. These
439 // quantities are updated immediately after adding or removing to the model,
440 // so it is correct even if GRBUpdate has not yet been called.
441
442 // Number of Gurobi variables.
443 int num_gurobi_variables_ = 0;
444 // Number of Gurobi linear constraints.
445 int num_gurobi_lin_cons_ = 0;
446 // Number of Gurobi quadratic constraints.
447 int num_gurobi_quad_cons_ = 0;
448 // Number of Gurobi SOS constraints.
449 int num_gurobi_sos_cons_ = 0;
450 // Number of Gurobi general constraints.
451 int num_gurobi_gen_cons_ = 0;
452
453 // Gurobi does not expose a way to query quadratic objective terms from the
454 // model, so we track them. Notes:
455 // * Keys are in upper triangular order (.first <= .second)
456 // * Terms not in the map have zero coefficients
457 // Note also that the map may also have entries with zero coefficient value.
458 absl::flat_hash_map<std::pair<VariableId, VariableId>, double>
459 quadratic_objective_coefficients_;
460
461 // Some MathOpt variables cannot be deleted without rendering the rest of the
462 // model invalid. We flag these variables to check in CanUpdate(). As of
463 // 2022-07-01 elements are not erased from this set, and so it may be overly
464 // conservative in rejecting updates.
465 absl::flat_hash_set<VariableId> undeletable_variables_;
466
467 static constexpr int kGrbBasicConstraint = 0;
468 static constexpr int kGrbNonBasicConstraint = -1;
469};
470
471} // namespace math_opt
472} // namespace operations_research
473
474#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
SatParameters parameters
int index
In SWIG mode, we don't want anything besides these top-level includes.
int64_t start