OR-Tools  9.3
gurobi_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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 <list>
22#include <memory>
23#include <optional>
24#include <ostream>
25#include <string>
26#include <string_view>
27#include <tuple>
28#include <utility>
29#include <vector>
30
31#include "absl/container/flat_hash_set.h"
32#include "absl/memory/memory.h"
33#include "absl/status/status.h"
34#include "absl/status/statusor.h"
35#include "absl/strings/str_cat.h"
36#include "absl/strings/str_format.h"
37#include "absl/strings/str_join.h"
38#include "absl/strings/string_view.h"
39#include "absl/time/clock.h"
40#include "absl/time/time.h"
41#include "absl/types/span.h"
48#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"
63#include "ortools/math_opt/sparse_containers.pb.h"
66
67namespace operations_research {
68namespace math_opt {
69namespace {
70
71absl::StatusOr<std::unique_ptr<Gurobi>> GurobiFromInitArgs(
72 const SolverInterface::InitArgs& init_args) {
73 // We don't test or return an error for incorrect non streamable arguments
74 // type since it is already tested by the Solver class.
75 const NonStreamableGurobiInitArguments* const non_streamable_args =
76 init_args.non_streamable != nullptr
77 ? init_args.non_streamable->ToNonStreamableGurobiInitArguments()
78 : nullptr;
79 std::unique_ptr<Gurobi> gurobi;
80 if (non_streamable_args != nullptr &&
81 non_streamable_args->master_env != nullptr) {
82 return Gurobi::NewWithSharedMasterEnv(non_streamable_args->master_env);
83 } else if (init_args.streamable.has_gurobi() &&
84 init_args.streamable.gurobi().has_isv_key()) {
87 NewMasterEnvironment(init_args.streamable.gurobi().isv_key()));
88 return Gurobi::New(std::move(env));
89 } else {
90 return Gurobi::New();
91 }
92}
93
94inline BasisStatusProto ConvertVariableStatus(const int status) {
95 switch (status) {
96 case GRB_BASIC:
97 return BASIS_STATUS_BASIC;
99 return BASIS_STATUS_AT_LOWER_BOUND;
101 return BASIS_STATUS_AT_UPPER_BOUND;
102 case GRB_SUPERBASIC:
103 return BASIS_STATUS_FREE;
104 default:
105 return BASIS_STATUS_UNSPECIFIED;
106 }
107}
108
109inline int GrbVariableStatus(const BasisStatusProto status) {
110 switch (status) {
111 case BASIS_STATUS_BASIC:
112 return GRB_BASIC;
113 case BASIS_STATUS_AT_LOWER_BOUND:
114 case BASIS_STATUS_FIXED_VALUE:
115 return GRB_NONBASIC_LOWER;
116 case BASIS_STATUS_AT_UPPER_BOUND:
117 return GRB_NONBASIC_UPPER;
118 case BASIS_STATUS_FREE:
119 return GRB_SUPERBASIC;
120 case BASIS_STATUS_UNSPECIFIED:
121 default:
122 LOG(FATAL) << "Unexpected invalid initial_basis.";
123 return 0;
124 }
125}
126
127GurobiParametersProto MergeParameters(
128 const SolveParametersProto& solve_parameters) {
129 GurobiParametersProto merged_parameters;
130
131 {
132 GurobiParametersProto::Parameter* const parameter =
133 merged_parameters.add_parameters();
134 parameter->set_name(GRB_INT_PAR_LOGTOCONSOLE);
135 parameter->set_value(solve_parameters.enable_output() ? "1" : "0");
136 }
137
138 if (solve_parameters.has_time_limit()) {
139 const double time_limit = absl::ToDoubleSeconds(
140 util_time::DecodeGoogleApiProto(solve_parameters.time_limit()).value());
141 GurobiParametersProto::Parameter* const parameter =
142 merged_parameters.add_parameters();
143 parameter->set_name(GRB_DBL_PAR_TIMELIMIT);
144 parameter->set_value(absl::StrCat(time_limit));
145 }
146
147 if (solve_parameters.has_node_limit()) {
148 GurobiParametersProto::Parameter* const parameter =
149 merged_parameters.add_parameters();
150 parameter->set_name(GRB_DBL_PAR_NODELIMIT);
151 parameter->set_value(absl::StrCat(solve_parameters.node_limit()));
152 }
153
154 if (solve_parameters.has_threads()) {
155 const int threads = solve_parameters.threads();
156 GurobiParametersProto::Parameter* const parameter =
157 merged_parameters.add_parameters();
158 parameter->set_name(GRB_INT_PAR_THREADS);
159 parameter->set_value(absl::StrCat(threads));
160 }
161
162 if (solve_parameters.has_absolute_gap_tolerance()) {
163 const double absolute_gap_tolerance =
164 solve_parameters.absolute_gap_tolerance();
165 GurobiParametersProto::Parameter* const parameter =
166 merged_parameters.add_parameters();
167 parameter->set_name(GRB_DBL_PAR_MIPGAPABS);
168 parameter->set_value(absl::StrCat(absolute_gap_tolerance));
169 }
170
171 if (solve_parameters.has_relative_gap_tolerance()) {
172 const double relative_gap_tolerance =
173 solve_parameters.relative_gap_tolerance();
174 GurobiParametersProto::Parameter* const parameter =
175 merged_parameters.add_parameters();
176 parameter->set_name(GRB_DBL_PAR_MIPGAP);
177 parameter->set_value(absl::StrCat(relative_gap_tolerance));
178 }
179
180 if (solve_parameters.has_cutoff_limit()) {
181 GurobiParametersProto::Parameter* const parameter =
182 merged_parameters.add_parameters();
183 parameter->set_name(GRB_DBL_PAR_CUTOFF);
184 parameter->set_value(absl::StrCat(solve_parameters.cutoff_limit()));
185 }
186
187 if (solve_parameters.has_objective_limit()) {
188 GurobiParametersProto::Parameter* const parameter =
189 merged_parameters.add_parameters();
190 parameter->set_name(GRB_DBL_PAR_BESTOBJSTOP);
191 parameter->set_value(absl::StrCat(solve_parameters.objective_limit()));
192 }
193
194 if (solve_parameters.has_best_bound_limit()) {
195 GurobiParametersProto::Parameter* const parameter =
196 merged_parameters.add_parameters();
197 parameter->set_name(GRB_DBL_PAR_BESTBDSTOP);
198 parameter->set_value(absl::StrCat(solve_parameters.best_bound_limit()));
199 }
200
201 if (solve_parameters.has_solution_limit()) {
202 GurobiParametersProto::Parameter* const parameter =
203 merged_parameters.add_parameters();
204 parameter->set_name(GRB_INT_PAR_SOLUTIONLIMIT);
205 parameter->set_value(absl::StrCat(solve_parameters.solution_limit()));
206 }
207
208 if (solve_parameters.has_random_seed()) {
209 const int random_seed =
210 std::min(GRB_MAXINT, std::max(solve_parameters.random_seed(), 0));
211 GurobiParametersProto::Parameter* const parameter =
212 merged_parameters.add_parameters();
213 parameter->set_name(GRB_INT_PAR_SEED);
214 parameter->set_value(absl::StrCat(random_seed));
215 }
216
217 if (solve_parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
218 GurobiParametersProto::Parameter* const parameter =
219 merged_parameters.add_parameters();
220 parameter->set_name(GRB_INT_PAR_METHOD);
221 switch (solve_parameters.lp_algorithm()) {
222 case LP_ALGORITHM_PRIMAL_SIMPLEX:
223 parameter->set_value(absl::StrCat(GRB_METHOD_PRIMAL));
224 break;
225 case LP_ALGORITHM_DUAL_SIMPLEX:
226 parameter->set_value(absl::StrCat(GRB_METHOD_DUAL));
227 break;
228 case LP_ALGORITHM_BARRIER:
229 parameter->set_value(absl::StrCat(GRB_METHOD_BARRIER));
230 break;
231 default:
232 LOG(FATAL) << "LPAlgorithm: "
233 << ProtoEnumToString(solve_parameters.lp_algorithm())
234 << " unknown, error setting Gurobi parameters";
235 }
236 }
237
238 if (solve_parameters.scaling() != EMPHASIS_UNSPECIFIED) {
239 GurobiParametersProto::Parameter* const parameter =
240 merged_parameters.add_parameters();
241 parameter->set_name(GRB_INT_PAR_SCALEFLAG);
242 switch (solve_parameters.scaling()) {
243 case EMPHASIS_OFF:
244 parameter->set_value(absl::StrCat(0));
245 break;
246 case EMPHASIS_LOW:
247 case EMPHASIS_MEDIUM:
248 parameter->set_value(absl::StrCat(1));
249 break;
250 case EMPHASIS_HIGH:
251 parameter->set_value(absl::StrCat(2));
252 break;
253 case EMPHASIS_VERY_HIGH:
254 parameter->set_value(absl::StrCat(3));
255 break;
256 default:
257 LOG(FATAL) << "Scaling emphasis: "
258 << ProtoEnumToString(solve_parameters.scaling())
259 << " unknown, error setting Gurobi parameters";
260 }
261 }
262
263 if (solve_parameters.cuts() != EMPHASIS_UNSPECIFIED) {
264 GurobiParametersProto::Parameter* const parameter =
265 merged_parameters.add_parameters();
266 parameter->set_name(GRB_INT_PAR_CUTS);
267 switch (solve_parameters.cuts()) {
268 case EMPHASIS_OFF:
269 parameter->set_value(absl::StrCat(0));
270 break;
271 case EMPHASIS_LOW:
272 case EMPHASIS_MEDIUM:
273 parameter->set_value(absl::StrCat(1));
274 break;
275 case EMPHASIS_HIGH:
276 parameter->set_value(absl::StrCat(2));
277 break;
278 case EMPHASIS_VERY_HIGH:
279 parameter->set_value(absl::StrCat(3));
280 break;
281 default:
282 LOG(FATAL) << "Cuts emphasis: "
283 << ProtoEnumToString(solve_parameters.cuts())
284 << " unknown, error setting Gurobi parameters";
285 }
286 }
287
288 if (solve_parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
289 GurobiParametersProto::Parameter* const parameter =
290 merged_parameters.add_parameters();
291 parameter->set_name(GRB_DBL_PAR_HEURISTICS);
292 switch (solve_parameters.heuristics()) {
293 case EMPHASIS_OFF:
294 parameter->set_value(absl::StrCat(0.0));
295 break;
296 case EMPHASIS_LOW:
297 parameter->set_value(absl::StrCat(0.025));
298 break;
299 case EMPHASIS_MEDIUM:
300 // As of Gurobi 9.1 this is the default value.
301 // https://www.gurobi.com/documentation/9.1/refman/heuristics.html
302 parameter->set_value(absl::StrCat(0.05));
303 break;
304 case EMPHASIS_HIGH:
305 parameter->set_value(absl::StrCat(0.1));
306 break;
307 case EMPHASIS_VERY_HIGH:
308 parameter->set_value(absl::StrCat(0.2));
309 break;
310 default:
311 LOG(FATAL) << "Heuristics emphasis: "
312 << ProtoEnumToString(solve_parameters.heuristics())
313 << " unknown, error setting Gurobi parameters";
314 }
315 }
316
317 if (solve_parameters.presolve() != EMPHASIS_UNSPECIFIED) {
318 GurobiParametersProto::Parameter* const parameter =
319 merged_parameters.add_parameters();
320 parameter->set_name(GRB_INT_PAR_PRESOLVE);
321 switch (solve_parameters.presolve()) {
322 case EMPHASIS_OFF:
323 parameter->set_value(absl::StrCat(0));
324 break;
325 case EMPHASIS_LOW:
326 case EMPHASIS_MEDIUM:
327 parameter->set_value(absl::StrCat(1));
328 break;
329 case EMPHASIS_HIGH:
330 case EMPHASIS_VERY_HIGH:
331 parameter->set_value(absl::StrCat(2));
332 break;
333 default:
334 LOG(FATAL) << "Presolve emphasis: "
335 << ProtoEnumToString(solve_parameters.presolve())
336 << " unknown, error setting Gurobi parameters";
337 }
338 }
339
340 if (solve_parameters.has_iteration_limit()) {
341 GurobiParametersProto::Parameter* const iterationlimit =
342 merged_parameters.add_parameters();
343 iterationlimit->set_name(GRB_DBL_PAR_ITERATIONLIMIT);
344 iterationlimit->set_value(absl::StrCat(solve_parameters.iteration_limit()));
345 GurobiParametersProto::Parameter* const bariterlimit =
346 merged_parameters.add_parameters();
347 bariterlimit->set_name(GRB_INT_PAR_BARITERLIMIT);
348 double val = std::min<double>(std::numeric_limits<int32_t>::max(),
349 solve_parameters.iteration_limit());
350 bariterlimit->set_value(absl::StrCat(val));
351 }
352
353 for (const GurobiParametersProto::Parameter& parameter :
354 solve_parameters.gurobi().parameters()) {
355 *merged_parameters.add_parameters() = parameter;
356 }
357
358 return merged_parameters;
359}
360
361absl::StatusOr<int64_t> SafeInt64FromDouble(const double d) {
362 const int64_t result = static_cast<int64_t>(d);
363 if (static_cast<double>(result) != d) {
364 return absl::InternalError(
365 absl::StrCat("Expected double ", d, " to contain an int64_t."));
366 }
367 return result;
368}
369
370const absl::flat_hash_set<CallbackEventProto>& SupportedMIPEvents() {
371 static const auto* const kEvents =
372 new absl::flat_hash_set<CallbackEventProto>({
373 CALLBACK_EVENT_PRESOLVE, CALLBACK_EVENT_SIMPLEX, CALLBACK_EVENT_MIP,
374 CALLBACK_EVENT_MIP_SOLUTION, CALLBACK_EVENT_MIP_NODE,
375 // CALLBACK_EVENT_BARRIER is not supported when solving MIPs; it turns
376 // out that Gurobi uses a barrier algorithm to solve the root node
377 // relaxation (from the traces) but does not call the associated
378 // callback.
379 });
380 return *kEvents;
381}
382
383const absl::flat_hash_set<CallbackEventProto>& SupportedLPEvents() {
384 static const auto* const kEvents =
385 new absl::flat_hash_set<CallbackEventProto>({
386 CALLBACK_EVENT_PRESOLVE,
387 CALLBACK_EVENT_SIMPLEX,
388 CALLBACK_EVENT_BARRIER,
389 });
390 return *kEvents;
391}
392
393// Gurobi names (model, variables and constraints) must be no longer than 255
394// characters; or Gurobi fails with an error.
395constexpr std::size_t kMaxNameSize = 255;
396
397// Returns a string of at most kMaxNameSize max size.
398std::string TruncateName(const std::string_view original_name) {
399 return std::string(
400 original_name.substr(0, std::min(kMaxNameSize, original_name.size())));
401}
402
403// Truncate the names of variables and constraints.
404std::vector<std::string> TruncateNames(
405 const google::protobuf::RepeatedPtrField<std::string>& original_names) {
406 std::vector<std::string> result;
407 result.reserve(original_names.size());
408 for (const std::string& original_name : original_names) {
409 result.push_back(TruncateName(original_name));
410 }
411 return result;
412}
413
414} // namespace
415
416GurobiSolver::GurobiSolver(std::unique_ptr<Gurobi> g_gurobi)
417 : gurobi_(std::move(g_gurobi)) {}
418
419int GurobiSolver::num_gurobi_constraints() const {
420 return linear_constraints_map_.size();
421}
422
423absl::StatusOr<TerminationProto> GurobiSolver::ConvertTerminationReason(
424 const int gurobi_status, const SolutionClaims solution_claims) {
425 switch (gurobi_status) {
426 case GRB_OPTIMAL:
427 return TerminateForReason(TERMINATION_REASON_OPTIMAL);
428 case GRB_INFEASIBLE:
429 return TerminateForReason(TERMINATION_REASON_INFEASIBLE);
430 case GRB_UNBOUNDED:
431 if (solution_claims.primal_feasible_solution_exists) {
432 return TerminateForReason(TERMINATION_REASON_UNBOUNDED);
433 }
434 return TerminateForReason(TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
435 "Gurobi status GRB_UNBOUNDED");
436 case GRB_INF_OR_UNBD:
437 return TerminateForReason(TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
438 "Gurobi status GRB_INF_OR_UNBD");
439 case GRB_CUTOFF:
440 return TerminateForLimit(LIMIT_CUTOFF,
441 /*feasible=*/false, "Gurobi status GRB_CUTOFF");
443 return TerminateForLimit(
444 LIMIT_ITERATION,
445 /*feasible=*/solution_claims.primal_feasible_solution_exists);
446 case GRB_NODE_LIMIT:
447 return TerminateForLimit(
448 LIMIT_NODE,
449 /*feasible=*/solution_claims.primal_feasible_solution_exists);
450 case GRB_TIME_LIMIT:
451 return TerminateForLimit(
452 LIMIT_TIME,
453 /*feasible=*/solution_claims.primal_feasible_solution_exists);
455 return TerminateForLimit(
456 LIMIT_SOLUTION,
457 /*feasible=*/solution_claims.primal_feasible_solution_exists);
458 case GRB_INTERRUPTED:
459 return TerminateForLimit(
460 LIMIT_INTERRUPTED,
461 /*feasible=*/solution_claims.primal_feasible_solution_exists);
462 case GRB_NUMERIC:
463 return TerminateForReason(TERMINATION_REASON_NUMERICAL_ERROR);
464 case GRB_SUBOPTIMAL:
465 return TerminateForReason(TERMINATION_REASON_IMPRECISE);
467 // TODO(b/214567536): maybe we should override
468 // solution_claims.primal_feasible_solution_exists to true or false
469 // depending on whether objective_limit and best_bound_limit triggered
470 // this. Not sure if it's possible to detect this though.
471 return TerminateForLimit(
472 LIMIT_OBJECTIVE,
473 /*feasible=*/solution_claims.primal_feasible_solution_exists);
474 case GRB_LOADED:
475 return absl::InternalError(
476 "Error creating termination reason, unexpected gurobi status code "
477 "GRB_LOADED.");
478 case GRB_INPROGRESS:
479 return absl::InternalError(
480 "Error creating termination reason, unexpected gurobi status code "
481 "GRB_INPROGRESS.");
482 default:
483 return absl::InternalError(absl::StrCat(
484 "Missing Gurobi optimization status code case: ", gurobi_status));
485 }
486}
487
488absl::StatusOr<bool> GurobiSolver::IsMaximize() const {
489 ASSIGN_OR_RETURN(const int obj_sense,
490 gurobi_->GetIntAttr(GRB_INT_ATTR_MODELSENSE));
491 return obj_sense == GRB_MAXIMIZE;
492}
493
494absl::StatusOr<bool> GurobiSolver::IsLP() const {
495 ASSIGN_OR_RETURN(const int is_mip, gurobi_->GetIntAttr(GRB_INT_ATTR_IS_MIP));
496 ASSIGN_OR_RETURN(const int is_qp, gurobi_->GetIntAttr(GRB_INT_ATTR_IS_QP));
497 ASSIGN_OR_RETURN(const int is_qcp, gurobi_->GetIntAttr(GRB_INT_ATTR_IS_QCP));
498 return !static_cast<bool>(is_mip) && !static_cast<bool>(is_qp) &&
499 !static_cast<bool>(is_qcp);
500}
501
502// TODO(b/204595455): Revisit logic when nonconvex QP support is decided upon
503absl::StatusOr<bool> GurobiSolver::IsQP() const {
504 ASSIGN_OR_RETURN(const int is_mip, gurobi_->GetIntAttr(GRB_INT_ATTR_IS_MIP));
505 ASSIGN_OR_RETURN(const int is_qp, gurobi_->GetIntAttr(GRB_INT_ATTR_IS_QP));
506 ASSIGN_OR_RETURN(const int is_qcp, gurobi_->GetIntAttr(GRB_INT_ATTR_IS_QCP));
507 return !static_cast<bool>(is_mip) && static_cast<bool>(is_qp) &&
508 !static_cast<bool>(is_qcp);
509}
510
511// TODO(user): switch the use of this function to something closer to
512// GetGurobiDualRay()
513template <typename T>
514void GurobiSolver::GurobiVectorToSparseDoubleVector(
515 const absl::Span<const double> gurobi_values, const T& map,
516 SparseDoubleVectorProto& result,
517 const SparseVectorFilterProto& filter) const {
518 SparseVectorFilterPredicate predicate(filter);
519 for (auto [id, gurobi_data] : map) {
520 const double value = gurobi_values[get_model_index(gurobi_data)];
521 if (predicate.AcceptsAndUpdate(id, value)) {
522 result.add_ids(id);
523 result.add_values(value);
524 }
525 }
526}
527
528absl::Status GurobiSolver::SetGurobiBasis(const BasisProto& basis) {
529 std::vector<int> gurobi_variable_basis_status(num_gurobi_variables_);
530 for (const auto [id, value] : MakeView(basis.variable_status())) {
531 gurobi_variable_basis_status[variables_map_.at(id)] =
532 GrbVariableStatus(static_cast<BasisStatusProto>(value));
533 }
534
535 std::vector<int> gurobi_constraint_basis_status;
536 gurobi_constraint_basis_status.reserve(num_gurobi_constraints());
537 for (const auto [id, value] : MakeView(basis.constraint_status())) {
538 const ConstraintData& constraint_data = linear_constraints_map_.at(id);
539 // Non-ranged constraints
540 if (constraint_data.slack_index == kUnspecifiedIndex) {
541 if (value == BASIS_STATUS_BASIC) {
542 gurobi_constraint_basis_status.push_back(kGrbBasicConstraint);
543 } else {
544 gurobi_constraint_basis_status.push_back(kGrbNonBasicConstraint);
545 }
546 // Ranged constraints
547 } else if (value == BASIS_STATUS_BASIC) {
548 // Either constraint or MathOpt slack is basic, but not both (because
549 // columns for MathOpt slack and internal Gurobi slack are linearly
550 // dependent). We choose the MathOpt slack to be basic.
551 gurobi_variable_basis_status[constraint_data.slack_index] = GRB_BASIC;
552 gurobi_constraint_basis_status.push_back(kGrbNonBasicConstraint);
553 } else {
554 gurobi_variable_basis_status[constraint_data.slack_index] =
555 GrbVariableStatus(static_cast<BasisStatusProto>(value));
556 gurobi_constraint_basis_status.push_back(kGrbNonBasicConstraint);
557 }
558 }
559 RETURN_IF_ERROR(gurobi_->SetIntAttrArray(GRB_INT_ATTR_VBASIS,
560 gurobi_variable_basis_status));
561 RETURN_IF_ERROR(gurobi_->SetIntAttrArray(GRB_INT_ATTR_CBASIS,
562 gurobi_constraint_basis_status));
563 return absl::OkStatus();
564}
565
566absl::StatusOr<BasisProto> GurobiSolver::GetGurobiBasis() {
567 BasisProto basis;
569 const std::vector<int> gurobi_variable_basis_status,
570 gurobi_->GetIntAttrArray(GRB_INT_ATTR_VBASIS, num_gurobi_variables_));
571
572 for (auto [variable_id, gurobi_variable_index] : variables_map_) {
573 basis.mutable_variable_status()->add_ids(variable_id);
574 const BasisStatusProto variable_status = ConvertVariableStatus(
575 gurobi_variable_basis_status[gurobi_variable_index]);
576 if (variable_status == BASIS_STATUS_UNSPECIFIED) {
577 return absl::InternalError(
578 absl::StrCat("Invalid Gurobi variable basis status: ",
579 gurobi_variable_basis_status[gurobi_variable_index]));
580 }
581 basis.mutable_variable_status()->add_values(variable_status);
582 }
583
585 const std::vector<int> gurobi_constraint_basis_status,
586 gurobi_->GetIntAttrArray(GRB_INT_ATTR_CBASIS, num_gurobi_constraints()));
587 for (auto [constraint_id, gurobi_data] : linear_constraints_map_) {
588 basis.mutable_constraint_status()->add_ids(constraint_id);
589 const int gurobi_constraint_status =
590 gurobi_constraint_basis_status[gurobi_data.constraint_index];
591 if ((gurobi_constraint_status != kGrbBasicConstraint) &&
592 (gurobi_constraint_status != kGrbNonBasicConstraint)) {
593 return absl::InternalError(
594 absl::StrCat("Invalid Gurobi constraint basis status: ",
595 gurobi_constraint_status));
596 }
597 // linear_terms <= upper_bound
598 if (gurobi_data.lower_bound <= -GRB_INFINITY &&
599 gurobi_data.upper_bound < GRB_INFINITY) {
600 if (gurobi_constraint_status == kGrbBasicConstraint) {
601 basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
602 } else {
603 basis.mutable_constraint_status()->add_values(
604 BASIS_STATUS_AT_UPPER_BOUND);
605 }
606 // linear_terms >= lower_bound
607 } else if (gurobi_data.lower_bound > -GRB_INFINITY &&
608 gurobi_data.upper_bound >= GRB_INFINITY) {
609 if (gurobi_constraint_status == kGrbBasicConstraint) {
610 basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
611 } else {
612 basis.mutable_constraint_status()->add_values(
613 BASIS_STATUS_AT_LOWER_BOUND);
614 }
615 // linear_terms == xxxxx_bound
616 } else if (gurobi_data.lower_bound == gurobi_data.upper_bound) {
617 if (gurobi_constraint_status == kGrbBasicConstraint) {
618 basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
619 } else {
620 // TODO(user): consider refining this to
621 // AT_LOWER_BOUND/AT_UPPER_BOUND using the sign of the dual variable.
622 basis.mutable_constraint_status()->add_values(BASIS_STATUS_FIXED_VALUE);
623 }
624 // linear_term - slack == 0 (ranged constraint)
625 } else {
626 const BasisStatusProto slack_status = ConvertVariableStatus(
627 gurobi_variable_basis_status[gurobi_data.slack_index]);
628 if (slack_status == BASIS_STATUS_UNSPECIFIED) {
629 return absl::InternalError(absl::StrCat(
630 "Invalid Gurobi slack variable basis status: ", slack_status));
631 }
632 if ((gurobi_constraint_status == kGrbBasicConstraint) ||
633 (slack_status == BASIS_STATUS_BASIC)) {
634 basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
635 } else {
636 basis.mutable_constraint_status()->add_values(slack_status);
637 }
638 }
639 }
640 return basis;
641}
642
643// See go/mathopt-dev-transformations#gurobi-inf for details of this
644// transformation, comments inside the function refer to the notation there.
645absl::StatusOr<DualRayProto> GurobiSolver::GetGurobiDualRay(
646 const SparseVectorFilterProto& linear_constraints_filter,
647 const SparseVectorFilterProto& variables_filter, const bool is_maximize) {
648 // farkas_dual = lambda
649 ASSIGN_OR_RETURN(const std::vector<double> farkas_dual,
650 gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_FARKASDUAL,
651 num_gurobi_constraints()));
652
653 DualRayProto dual_ray;
654
655 // Compute y = -lambda
656 {
657 SparseVectorFilterPredicate predicate(linear_constraints_filter);
658 for (auto [constraint_id, gurobi_data] : linear_constraints_map_) {
659 // constraint_dual_value = y[constraint_id]
660 const double value = -farkas_dual[gurobi_data.constraint_index];
661 if (predicate.AcceptsAndUpdate(constraint_id, value)) {
662 dual_ray.mutable_dual_values()->add_ids(constraint_id);
663 if (is_maximize) {
664 dual_ray.mutable_dual_values()->add_values(-value);
665 } else {
666 dual_ray.mutable_dual_values()->add_values(value);
667 }
668 }
669 }
670 }
671
672 // Compute r = \bar{a} = A^T lambda
673 {
674 SparseVectorFilterPredicate predicate(variables_filter);
675 for (auto [var_id, gurobi_variable_index] : variables_map_) {
676 // reduced_cost_value = r[gurobi_variable_index]
677 // = \bar{a}[gurobi_variable_index]
678 double reduced_cost_value = 0.0;
679 ASSIGN_OR_RETURN(Gurobi::SparseMat column,
680 gurobi_->GetVars(gurobi_variable_index, 1));
681 for (int i = 0; i < column.inds.size(); ++i) {
682 reduced_cost_value += farkas_dual[column.inds[i]] * column.vals[i];
683 }
684 if (predicate.AcceptsAndUpdate(var_id, reduced_cost_value)) {
685 dual_ray.mutable_reduced_costs()->add_ids(var_id);
686 if (is_maximize) {
687 dual_ray.mutable_reduced_costs()->add_values(-reduced_cost_value);
688 } else {
689 dual_ray.mutable_reduced_costs()->add_values(reduced_cost_value);
690 }
691 }
692 }
693 }
694 return dual_ray;
695}
696
697absl::StatusOr<ProblemStatusProto> GurobiSolver::GetProblemStatus(
698 const int grb_termination, const SolutionClaims solution_claims) {
699 ProblemStatusProto problem_status;
700
701 // Set default statuses
702 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
703 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
704
705 // Set feasibility statuses
706 if (solution_claims.primal_feasible_solution_exists) {
707 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
708 }
709 if (solution_claims.dual_feasible_solution_exists) {
710 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
711 }
712
713 // Process infeasible conclusions from grb_termination.
714 switch (grb_termination) {
715 case GRB_INFEASIBLE:
716 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
717 if (solution_claims.primal_feasible_solution_exists) {
718 return absl::InternalError(
719 "GRB_INT_ATTR_STATUS == GRB_INFEASIBLE, but a primal feasible "
720 "solution was returned.");
721 }
722 break;
723 case GRB_UNBOUNDED:
724 // GRB_UNBOUNDED does necessarily imply the primal is feasible
725 // https://www.gurobi.com/documentation/9.1/refman/optimization_status_codes.html
726 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
727 if (solution_claims.dual_feasible_solution_exists) {
728 return absl::InternalError(
729 "GRB_INT_ATTR_STATUS == GRB_UNBOUNDED, but a dual feasible "
730 "solution was returned or exists.");
731 }
732 break;
733 case GRB_INF_OR_UNBD:
734 problem_status.set_primal_or_dual_infeasible(true);
735 if (solution_claims.primal_feasible_solution_exists) {
736 return absl::InternalError(
737 "GRB_INT_ATTR_STATUS == GRB_INF_OR_UNBD, but a primal feasible "
738 "solution was returned.");
739 }
740 if (solution_claims.dual_feasible_solution_exists) {
741 return absl::InternalError(
742 "GRB_INT_ATTR_STATUS == GRB_INF_OR_UNBD, but a dual feasible "
743 "solution was returned or exists.");
744 }
745 break;
746 }
747 return problem_status;
748}
749
750absl::StatusOr<SolveResultProto> GurobiSolver::ExtractSolveResultProto(
751 const absl::Time start, const ModelSolveParametersProto& model_parameters) {
752 SolveResultProto result;
753
754 // TODO(b/195295177): Add tests for rays in unbounded MIPs
755 RETURN_IF_ERROR(FillRays(model_parameters, result));
756
757 ASSIGN_OR_RETURN((auto [solutions, solution_claims]),
758 GetSolutions(model_parameters));
759
760 for (auto& solution : solutions) {
761 *result.add_solutions() = std::move(solution);
762 }
763
764 ASSIGN_OR_RETURN(*result.mutable_solve_stats(),
765 GetSolveStats(start, solution_claims));
766
767 ASSIGN_OR_RETURN(const int grb_termination,
768 gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
769 ASSIGN_OR_RETURN(*result.mutable_termination(),
770 ConvertTerminationReason(grb_termination, solution_claims));
771 return std::move(result);
772}
773
774absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetSolutions(
775 const ModelSolveParametersProto& model_parameters) {
776 ASSIGN_OR_RETURN(const bool is_lp, IsLP());
777 ASSIGN_OR_RETURN(const bool is_qp, IsQP());
778
779 if (is_lp) {
780 return GetLpSolution(model_parameters);
781 } else if (is_qp) {
782 return GetQpSolution(model_parameters);
783 } else {
784 return GetMipSolutions(model_parameters);
785 }
786}
787
788absl::StatusOr<SolveStatsProto> GurobiSolver::GetSolveStats(
789 const absl::Time start, const SolutionClaims solution_claims) {
790 SolveStatsProto solve_stats;
791
793 solve_stats.mutable_solve_time()));
794
795 ASSIGN_OR_RETURN(const double best_primal_bound,
796 GetBestPrimalBound(
797 /*has_primal_feasible_solution=*/solution_claims
798 .primal_feasible_solution_exists));
799 solve_stats.set_best_primal_bound(best_primal_bound);
800
801 ASSIGN_OR_RETURN(double best_dual_bound, GetBestDualBound());
802 solve_stats.set_best_dual_bound(best_dual_bound);
803
804 ASSIGN_OR_RETURN(const int grb_termination,
805 gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
806 ASSIGN_OR_RETURN((*solve_stats.mutable_problem_status()),
807 GetProblemStatus(grb_termination, solution_claims));
808
809 if (gurobi_->IsAttrAvailable(GRB_DBL_ATTR_ITERCOUNT)) {
810 ASSIGN_OR_RETURN(const double simplex_iters_double,
811 gurobi_->GetDoubleAttr(GRB_DBL_ATTR_ITERCOUNT));
812 ASSIGN_OR_RETURN(const int64_t simplex_iters,
813 SafeInt64FromDouble(simplex_iters_double));
814 solve_stats.set_simplex_iterations(simplex_iters);
815 }
816
817 if (gurobi_->IsAttrAvailable(GRB_INT_ATTR_BARITERCOUNT)) {
818 ASSIGN_OR_RETURN(const int barrier_iters,
819 gurobi_->GetIntAttr(GRB_INT_ATTR_BARITERCOUNT));
820 solve_stats.set_barrier_iterations(barrier_iters);
821 }
822
823 if (gurobi_->IsAttrAvailable(GRB_DBL_ATTR_NODECOUNT)) {
824 ASSIGN_OR_RETURN(const double nodes_double,
825 gurobi_->GetDoubleAttr(GRB_DBL_ATTR_NODECOUNT));
826 ASSIGN_OR_RETURN(const int64_t nodes, SafeInt64FromDouble(nodes_double));
827 solve_stats.set_node_count(nodes);
828 }
829 return solve_stats;
830}
831
832absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetMipSolutions(
833 const ModelSolveParametersProto& model_parameters) {
834 int num_solutions = 0;
835 if (gurobi_->IsAttrAvailable(GRB_INT_ATTR_SOLCOUNT)) {
836 ASSIGN_OR_RETURN(num_solutions, gurobi_->GetIntAttr(GRB_INT_ATTR_SOLCOUNT));
837 }
838 std::vector<SolutionProto> solutions;
839 solutions.reserve(num_solutions);
840 for (int i = 0; i < num_solutions; ++i) {
841 RETURN_IF_ERROR(gurobi_->SetIntParam(GRB_INT_PAR_SOLUTIONNUMBER, i));
842
843 PrimalSolutionProto primal_solution;
844 ASSIGN_OR_RETURN(const double sol_val,
845 gurobi_->GetDoubleAttr(GRB_DBL_ATTR_POOLOBJVAL));
846 primal_solution.set_objective_value(sol_val);
847 primal_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
849 const std::vector<double> grb_var_values,
850 gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_XN, num_gurobi_variables_));
851 GurobiVectorToSparseDoubleVector(grb_var_values, variables_map_,
852 *primal_solution.mutable_variable_values(),
853 model_parameters.variable_values_filter());
854 *solutions.emplace_back(SolutionProto()).mutable_primal_solution() =
855 std::move(primal_solution);
856 }
857
858 // Set solution claims
859 ASSIGN_OR_RETURN(const double best_dual_bound, GetBestDualBound());
860 // Note: here the existence of a dual solution refers to a dual solution to
861 // some convex relaxation of the MIP. This convex relaxation can likely be
862 // interpreted as an LP between the LP relaxation of the MIP and the convex
863 // hull of feasible solutions of the MIP. However, here we only use the fact
864 // that best_dual_bound being finite implies the existence of the trivial
865 // convex relaxation given by (assuming a minimization problem with objective
866 // function c^T x): min{c^T x : c^T x >= best_dual_bound}.
867 const SolutionClaims solution_claims = {
868 .primal_feasible_solution_exists = num_solutions > 0,
869 .dual_feasible_solution_exists = std::isfinite(best_dual_bound)};
870
871 // Check consistency of solutions, bounds and statuses.
872 ASSIGN_OR_RETURN(const int grb_termination,
873 gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
874 if (grb_termination == GRB_OPTIMAL && num_solutions == 0) {
875 return absl::InternalError(
876 "GRB_INT_ATTR_STATUS == GRB_OPTIMAL, but solution pool is empty.");
877 }
878 if (grb_termination == GRB_OPTIMAL && !std::isfinite(best_dual_bound)) {
879 return absl::InternalError(
880 "GRB_INT_ATTR_STATUS == GRB_OPTIMAL, but GRB_DBL_ATTR_OBJBOUND is "
881 "unavailable or infinite.");
882 }
883
884 return SolutionsAndClaims{.solutions = std::move(solutions),
885 .solution_claims = solution_claims};
886}
887
888absl::StatusOr<GurobiSolver::SolutionAndClaim<PrimalSolutionProto>>
889GurobiSolver::GetConvexPrimalSolutionIfAvailable(
890 const ModelSolveParametersProto& model_parameters) {
891 if (!gurobi_->IsAttrAvailable(GRB_DBL_ATTR_X)) {
892 return SolutionAndClaim<PrimalSolutionProto>{
893 .solution = std::nullopt, .feasible_solution_exists = false};
894 }
895 ASSIGN_OR_RETURN(const int grb_termination,
896 gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
897
898 // Get primal solutions if available.
900 const std::vector<double> grb_var_values,
901 gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_X, num_gurobi_variables_));
902
903 PrimalSolutionProto primal_solution;
904 // As noted in go/gurobi-objval-bug the objective value may be missing for
905 // primal feasible solutions for unbounded problems.
906 // TODO(b/195295177): for GRB_ITERATION_LIMIT an objective value of 0.0 is
907 // returned which breaks LpIncompleteSolveTest.PrimalSimplexAlgorithm. Explore
908 // more and make simple example to file a bug.
909 if (gurobi_->IsAttrAvailable(GRB_DBL_ATTR_OBJVAL) &&
910 grb_termination != GRB_ITERATION_LIMIT) {
911 ASSIGN_OR_RETURN(const double sol_val,
912 gurobi_->GetDoubleAttr(GRB_DBL_ATTR_OBJVAL));
913 primal_solution.set_objective_value(sol_val);
914 } else {
915 double objective_value = 0.0;
917 const std::vector<double> linear_obj_coefs,
918 gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_OBJ, num_gurobi_variables_));
919 for (int i = 0; i < num_gurobi_variables_; ++i) {
920 objective_value += linear_obj_coefs[i] * grb_var_values[i];
921 }
922 primal_solution.set_objective_value(objective_value);
923 }
924
925 primal_solution.set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
926 if (grb_termination == GRB_OPTIMAL) {
927 primal_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
928 } else if (grb_termination == GRB_INFEASIBLE) {
929 primal_solution.set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
930 } else if (PrimalSolutionQualityAvailable()) {
931 ASSIGN_OR_RETURN(const double solution_quality, GetPrimalSolutionQuality());
932 ASSIGN_OR_RETURN(const double tolerance,
933 gurobi_->GetDoubleParam(GRB_DBL_PAR_FEASIBILITYTOL));
934 if (solution_quality <= tolerance) {
935 primal_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
936 } else {
937 primal_solution.set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
938 }
939 }
940
941 GurobiVectorToSparseDoubleVector(grb_var_values, variables_map_,
942 *primal_solution.mutable_variable_values(),
943 model_parameters.variable_values_filter());
944 const bool primal_feasible_solution_exists =
945 (primal_solution.feasibility_status() == SOLUTION_STATUS_FEASIBLE);
946 return SolutionAndClaim<PrimalSolutionProto>{
947 .solution = std::move(primal_solution),
948 .feasible_solution_exists = primal_feasible_solution_exists};
949}
950
951bool GurobiSolver::PrimalSolutionQualityAvailable() const {
952 return gurobi_->IsAttrAvailable(GRB_DBL_ATTR_CONSTR_RESIDUAL) &&
953 gurobi_->IsAttrAvailable(GRB_DBL_ATTR_CONSTR_VIO) &&
954 gurobi_->IsAttrAvailable(GRB_DBL_ATTR_BOUND_VIO) &&
955 gurobi_->IsAttrAvailable(GRB_DBL_ATTR_CONSTR_SRESIDUAL) &&
956 gurobi_->IsAttrAvailable(GRB_DBL_ATTR_CONSTR_SVIO) &&
957 gurobi_->IsAttrAvailable(GRB_DBL_ATTR_BOUND_SVIO);
958}
959
960absl::StatusOr<double> GurobiSolver::GetPrimalSolutionQuality() const {
961 ASSIGN_OR_RETURN(const double constraint_residual,
962 gurobi_->GetDoubleAttr(GRB_DBL_ATTR_CONSTR_RESIDUAL));
963 ASSIGN_OR_RETURN(const double constraint_violation,
964 gurobi_->GetDoubleAttr(GRB_DBL_ATTR_CONSTR_VIO));
965 ASSIGN_OR_RETURN(const double bound_violation,
966 gurobi_->GetDoubleAttr(GRB_DBL_ATTR_BOUND_VIO));
967 ASSIGN_OR_RETURN(const double constraint_scaled_residual,
968 gurobi_->GetDoubleAttr(GRB_DBL_ATTR_CONSTR_SRESIDUAL));
969 ASSIGN_OR_RETURN(const double constraint_scaled_violation,
970 gurobi_->GetDoubleAttr(GRB_DBL_ATTR_CONSTR_SVIO));
971 ASSIGN_OR_RETURN(const double bound_scaled_violation,
972 gurobi_->GetDoubleAttr(GRB_DBL_ATTR_BOUND_SVIO));
973 return std::max({constraint_residual, constraint_violation, bound_violation,
974 constraint_scaled_residual, constraint_scaled_violation,
975 bound_scaled_violation});
976}
977
978absl::StatusOr<double> GurobiSolver::GetBestPrimalBound(
979 const bool has_primal_feasible_solution) {
980 ASSIGN_OR_RETURN(const bool is_maximize, IsMaximize());
981 // We need has_primal_feasible_solution because, as noted in
982 // go/gurobi-objval-bug, GRB_DBL_ATTR_OBJVAL may be available and finite for
983 // primal infeasible solutions.
984 if (has_primal_feasible_solution &&
985 gurobi_->IsAttrAvailable(GRB_DBL_ATTR_OBJVAL)) {
986 // TODO(b/195295177): Discuss if this should be removed. Unlike the dual
987 // case below, it appears infesible models do not return GRB_DBL_ATTR_OBJVAL
988 // equal to GRB_INFINITY (GRB_DBL_ATTR_OBJVAL is just unavailable). Hence,
989 // this may not be needed and may not be consistent (e.g. we should explore
990 // whether GRB_DBL_ATTR_OBJVAL = GRB_INFINITY may happen for a primal
991 // feasible solution, in which the conversion of +/-GRB_INFINITY to +/-kInf
992 // would not be consistent). Note that unlike the dual case removing this
993 // does not break any test.
994 ASSIGN_OR_RETURN(const double obj_val,
995 gurobi_->GetDoubleAttr(GRB_DBL_ATTR_OBJVAL));
996 if (std::abs(obj_val) < GRB_INFINITY) {
997 return obj_val;
998 }
999 }
1000 return is_maximize ? -kInf : kInf;
1001}
1002
1003absl::StatusOr<double> GurobiSolver::GetBestDualBound() {
1004 if (gurobi_->IsAttrAvailable(GRB_DBL_ATTR_OBJBOUND)) {
1005 ASSIGN_OR_RETURN(const double obj_bound,
1006 gurobi_->GetDoubleAttr(GRB_DBL_ATTR_OBJBOUND));
1007 // Note: Unbounded models return GRB_DBL_ATTR_OBJBOUND = GRB_INFINITY so
1008 // the conversion of +/-GRB_INFINITY to +/-kInf is needed and consistent.
1009 if (std::abs(obj_bound) < GRB_INFINITY) {
1010 return obj_bound;
1011 }
1012 }
1013 ASSIGN_OR_RETURN(const bool is_maximize, IsMaximize());
1014 return is_maximize ? kInf : -kInf;
1015}
1016
1017absl::StatusOr<std::optional<BasisProto>> GurobiSolver::GetBasisIfAvailable() {
1018 if (gurobi_->IsAttrAvailable(GRB_INT_ATTR_VBASIS) &&
1019 gurobi_->IsAttrAvailable(GRB_INT_ATTR_CBASIS)) {
1020 ASSIGN_OR_RETURN(BasisProto basis, GetGurobiBasis());
1021 ASSIGN_OR_RETURN(const int grb_termination,
1022 gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
1023 basis.set_basic_dual_feasibility(SOLUTION_STATUS_UNDETERMINED);
1024 if (grb_termination == GRB_OPTIMAL) {
1025 basis.set_basic_dual_feasibility(SOLUTION_STATUS_FEASIBLE);
1026 } else if (grb_termination == GRB_UNBOUNDED) {
1027 basis.set_basic_dual_feasibility(SOLUTION_STATUS_INFEASIBLE);
1028 }
1029 // TODO(b/195295177): double check if the move is needed
1030 return std::move(basis);
1031 }
1032 return std::nullopt;
1033}
1034
1035absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetLpSolution(
1036 const ModelSolveParametersProto& model_parameters) {
1037 ASSIGN_OR_RETURN(auto primal_solution_and_claim,
1038 GetConvexPrimalSolutionIfAvailable(model_parameters));
1039 ASSIGN_OR_RETURN(auto dual_solution_and_claim,
1040 GetLpDualSolutionIfAvailable(model_parameters));
1041 ASSIGN_OR_RETURN(auto basis, GetBasisIfAvailable());
1042 const SolutionClaims solution_claims = {
1043 .primal_feasible_solution_exists =
1044 primal_solution_and_claim.feasible_solution_exists,
1045 .dual_feasible_solution_exists =
1046 dual_solution_and_claim.feasible_solution_exists};
1047
1048 if (!primal_solution_and_claim.solution.has_value() &&
1049 !dual_solution_and_claim.solution.has_value() && !basis.has_value()) {
1050 return SolutionsAndClaims{.solution_claims = solution_claims};
1051 }
1052 SolutionsAndClaims solution_and_claims{.solution_claims = solution_claims};
1053 SolutionProto& solution =
1054 solution_and_claims.solutions.emplace_back(SolutionProto());
1055 if (primal_solution_and_claim.solution.has_value()) {
1056 *solution.mutable_primal_solution() =
1057 std::move(*primal_solution_and_claim.solution);
1058 }
1059 if (dual_solution_and_claim.solution.has_value()) {
1060 *solution.mutable_dual_solution() =
1061 std::move(*dual_solution_and_claim.solution);
1062 }
1063 if (basis.has_value()) {
1064 *solution.mutable_basis() = std::move(*basis);
1065 }
1066 return solution_and_claims;
1067}
1068
1069absl::StatusOr<GurobiSolver::SolutionAndClaim<DualSolutionProto>>
1070GurobiSolver::GetLpDualSolutionIfAvailable(
1071 const ModelSolveParametersProto& model_parameters) {
1072 if (!gurobi_->IsAttrAvailable(GRB_DBL_ATTR_PI) ||
1073 !gurobi_->IsAttrAvailable(GRB_DBL_ATTR_RC)) {
1074 return SolutionAndClaim<DualSolutionProto>{
1075 .solution = std::nullopt, .feasible_solution_exists = false};
1076 }
1077
1078 // Note that we can ignore the reduced costs of the slack variables for
1079 // ranged constraints because of
1080 // go/mathopt-dev-transformations#slack-var-range-constraint
1081 DualSolutionProto dual_solution;
1082 bool dual_feasible_solution_exists = false;
1084 const std::vector<double> grb_constraint_duals,
1085 gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_PI, num_gurobi_constraints()));
1086 GurobiVectorToSparseDoubleVector(grb_constraint_duals,
1087 linear_constraints_map_,
1088 *dual_solution.mutable_dual_values(),
1089 model_parameters.dual_values_filter());
1090
1092 const std::vector<double> grb_reduced_cost_values,
1093 gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_RC, num_gurobi_variables_));
1094 GurobiVectorToSparseDoubleVector(grb_reduced_cost_values, variables_map_,
1095 *dual_solution.mutable_reduced_costs(),
1096 model_parameters.reduced_costs_filter());
1097
1098 ASSIGN_OR_RETURN(const int grb_termination,
1099 gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
1100 if (grb_termination == GRB_OPTIMAL &&
1101 gurobi_->IsAttrAvailable(GRB_DBL_ATTR_OBJVAL)) {
1102 ASSIGN_OR_RETURN(const double obj_val,
1103 gurobi_->GetDoubleAttr(GRB_DBL_ATTR_OBJVAL));
1104 dual_solution.set_objective_value(obj_val);
1105 }
1106 // TODO(b/195295177): explore using GRB_DBL_ATTR_OBJBOUND to set the dual
1107 // objective. As described in go/gurobi-objval-bug, this could provide the
1108 // dual objective in some cases.
1109
1110 dual_solution.set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
1111 if (grb_termination == GRB_OPTIMAL) {
1112 dual_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1113 dual_feasible_solution_exists = true;
1114 } else if (grb_termination == GRB_UNBOUNDED) {
1115 dual_solution.set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
1116 }
1117 // TODO(b/195295177): We could use gurobi's dual solution quality measures
1118 // for further upgrade the dual feasibility but it likely is only useful
1119 // for phase II of dual simplex because:
1120 // * the quality measures seem to evaluate if the basis is dual feasible
1121 // so for primal simplex we would not improve over checking
1122 // GRB_OPTIMAL.
1123 // * for phase I dual simplex we cannot rely on the quality measures
1124 // because of go/gurobi-solution-quality-bug.
1125 // We could also use finiteness of GRB_DBL_ATTR_OBJBOUND to deduce dual
1126 // feasibility as described in go/gurobi-objval-bug.
1127
1128 // Note: as shown in go/gurobi-objval-bug, GRB_DBL_ATTR_OBJBOUND can
1129 // sometimes provide the objective value of a sub-optimal dual feasible
1130 // solution. Here we only use it to possibly update
1131 // dual_feasible_solution_exists (Otherwise
1132 // StatusTest.PrimalInfeasibleAndDualFeasible for pure dual simplex would
1133 // fail because go/gurobi-solution-quality-bug prevents us from certifying
1134 // feasibility of the dual solution found in this case).
1135 ASSIGN_OR_RETURN(const double best_dual_bound, GetBestDualBound());
1136 if (dual_feasible_solution_exists || std::isfinite(best_dual_bound)) {
1137 dual_feasible_solution_exists = true;
1138 } else if (grb_termination == GRB_OPTIMAL) {
1139 return absl::InternalError(
1140 "GRB_INT_ATTR_STATUS == GRB_OPTIMAL, but GRB_DBL_ATTR_OBJBOUND is "
1141 "unavailable or infinite, and no dual feasible solution is returned");
1142 }
1143 return SolutionAndClaim<DualSolutionProto>{
1144 .solution = std::move(dual_solution),
1145 .feasible_solution_exists = dual_feasible_solution_exists};
1146}
1147
1148absl::Status GurobiSolver::FillRays(
1149 const ModelSolveParametersProto& model_parameters,
1150 SolveResultProto& result) {
1151 ASSIGN_OR_RETURN(const bool is_maximize, IsMaximize());
1152 if (gurobi_->IsAttrAvailable(GRB_DBL_ATTR_UNBDRAY) &&
1153 num_gurobi_variables_ > 0) {
1154 ASSIGN_OR_RETURN(const std::vector<double> grb_ray_var_values,
1155 gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_UNBDRAY,
1156 num_gurobi_variables_));
1157 PrimalRayProto* const primal_ray = result.add_primal_rays();
1158 GurobiVectorToSparseDoubleVector(grb_ray_var_values, variables_map_,
1159 *primal_ray->mutable_variable_values(),
1160 model_parameters.variable_values_filter());
1161 }
1162 if (gurobi_->IsAttrAvailable(GRB_DBL_ATTR_FARKASDUAL) &&
1163 num_gurobi_constraints() + num_gurobi_variables_ > 0) {
1165 DualRayProto dual_ray,
1166 GetGurobiDualRay(model_parameters.dual_values_filter(),
1167 model_parameters.reduced_costs_filter(), is_maximize));
1168 result.mutable_dual_rays()->Add(std::move(dual_ray));
1169 }
1170 return absl::OkStatus();
1171}
1172
1173absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetQpSolution(
1174 const ModelSolveParametersProto& model_parameters) {
1175 ASSIGN_OR_RETURN((auto [primal_solution, found_primal_feasible_solution]),
1176 GetConvexPrimalSolutionIfAvailable(model_parameters));
1177
1178 // TODO(b/195295177): Update, seems GRB_DBL_ATTR_OBJBOUND is unavailable
1179 // even for GRB_OPTIMAL, so the code below will only give a finite bound
1180 // and a dual feasible status for GRB_OPTIMAL.
1181 ASSIGN_OR_RETURN(const int grb_termination,
1182 gurobi_->GetIntAttr(GRB_INT_ATTR_STATUS));
1183 bool dual_feasible_solution_exists = false;
1184 ASSIGN_OR_RETURN(double best_dual_bound, GetBestDualBound());
1185 if (grb_termination == GRB_OPTIMAL || std::isfinite(best_dual_bound)) {
1186 dual_feasible_solution_exists = true;
1187 }
1188 // Basis information is available when Gurobi uses QP simplex. As of v9.1 this
1189 // is not the default [1], so a user will need to explicitly set the Method
1190 // parameter in order for the following call to do anything interesting.
1191 // [1] https://www.gurobi.com/documentation/9.1/refman/method.html
1192 ASSIGN_OR_RETURN(auto basis, GetBasisIfAvailable());
1193
1194 const SolutionClaims solution_claims = {
1195 .primal_feasible_solution_exists = found_primal_feasible_solution,
1196 .dual_feasible_solution_exists = dual_feasible_solution_exists};
1197
1198 if (!primal_solution.has_value() && !basis.has_value()) {
1199 return GurobiSolver::SolutionsAndClaims{.solution_claims = solution_claims};
1200 }
1201 SolutionsAndClaims solution_and_claims{.solution_claims = solution_claims};
1202 SolutionProto& solution =
1203 solution_and_claims.solutions.emplace_back(SolutionProto());
1204 if (primal_solution.has_value()) {
1205 *solution.mutable_primal_solution() = std::move(*primal_solution);
1206 }
1207 if (basis.has_value()) {
1208 *solution.mutable_basis() = std::move(*basis);
1209 }
1210 return solution_and_claims;
1211}
1212
1213absl::Status GurobiSolver::SetParameters(
1214 const SolveParametersProto& parameters) {
1215 const GurobiParametersProto gurobi_parameters = MergeParameters(parameters);
1216 std::vector<std::string> parameter_errors;
1217 for (const GurobiParametersProto::Parameter& parameter :
1218 gurobi_parameters.parameters()) {
1219 absl::Status param_status =
1220 gurobi_->SetParam(parameter.name().c_str(), parameter.value());
1221 if (!param_status.ok()) {
1222 parameter_errors.emplace_back(std::move(param_status).message());
1223 }
1224 }
1225 if (!parameter_errors.empty()) {
1226 return absl::InvalidArgumentError(absl::StrJoin(parameter_errors, "; "));
1227 }
1228 return absl::OkStatus();
1229}
1230
1231absl::Status GurobiSolver::AddNewVariables(
1232 const VariablesProto& new_variables) {
1233 const int num_new_variables = new_variables.lower_bounds().size();
1234 std::vector<char> variable_type(num_new_variables);
1235 for (int j = 0; j < num_new_variables; ++j) {
1236 const VariableId id = new_variables.ids(j);
1237 gtl::InsertOrDie(&variables_map_, id, j + num_gurobi_variables_);
1238 variable_type[j] = new_variables.integers(j) ? GRB_INTEGER : GRB_CONTINUOUS;
1239 }
1240 // We need to copy the names, RepeatedPtrField cannot be converted to
1241 // absl::Span<std::string>.
1242 const std::vector<std::string> variable_names =
1243 TruncateNames(new_variables.names());
1244 RETURN_IF_ERROR(gurobi_->AddVars(
1245 /*obj=*/{},
1246 /*lb=*/new_variables.lower_bounds(),
1247 /*ub=*/new_variables.upper_bounds(),
1248 /*vtype=*/variable_type, variable_names));
1249 num_gurobi_variables_ += num_new_variables;
1250
1251 return absl::OkStatus();
1252}
1253
1254// Given a vector of pairs<LinearConstraintId,ConstraintData&> add a slack
1255// variable for each of the constraints in the underlying `gurobi_`
1256// using the referenced bounds.
1257absl::Status GurobiSolver::AddNewSlacks(
1258 const std::vector<SlackInfo>& new_slacks) {
1259 // Note that we are really adding the sub-matrix
1260 // D * slack
1261 // to the set of linear constraints, and the D matrix is stored in compressed
1262 // sparse column (CSC) format. In our particular case, D is a diagonal matrix
1263 // with -1.0 coefficients for each new slack in the row indicated in the
1264 // row_indices vector.
1265 const int num_slacks = new_slacks.size();
1266 if (num_slacks == 0) {
1267 return absl::OkStatus();
1268 }
1269 // Build the D matrix in CSC format.
1270 const std::vector<double> column_non_zeros(num_slacks, -1.0);
1271 std::vector<double> lower_bounds;
1272 std::vector<double> upper_bounds;
1273 const std::vector<char> vtypes(num_slacks, GRB_CONTINUOUS);
1274 std::vector<GurobiLinearConstraintIndex> row_indices;
1275 std::vector<int> column_non_zero_begin;
1276 column_non_zero_begin.reserve(num_slacks);
1277 row_indices.reserve(num_slacks);
1278 lower_bounds.reserve(num_slacks);
1279 upper_bounds.reserve(num_slacks);
1280 for (int k = 0; k < num_slacks; ++k) {
1281 auto& [id, constraint_data] = new_slacks[k];
1282 gtl::InsertOrDie(&slack_map_, id, constraint_data);
1283 row_indices.emplace_back(constraint_data.constraint_index);
1284 lower_bounds.emplace_back(constraint_data.lower_bound);
1285 upper_bounds.emplace_back(constraint_data.upper_bound);
1286 column_non_zero_begin.emplace_back(k);
1287 }
1288 // Add variables to the underlying model.
1289 RETURN_IF_ERROR(gurobi_->AddVars(/*vbegin=*/column_non_zero_begin,
1290 /*vind=*/row_indices,
1291 /*vval=*/column_non_zeros, /*obj=*/{},
1292 /*lb=*/lower_bounds, /*ub=*/upper_bounds,
1293 /*vtype=*/vtypes, /*names=*/{}));
1294 num_gurobi_variables_ += num_slacks;
1295 return absl::OkStatus();
1296}
1297
1298absl::Status GurobiSolver::AddNewConstraints(
1299 const LinearConstraintsProto& constraints) {
1300 const int num_model_constraints = num_gurobi_constraints();
1301 const int num_new_constraints = constraints.lower_bounds().size();
1302
1303 // We need to copy the names, RepeatedPtrField cannot be converted to
1304 // absl::Span<std::string>.
1305 const std::vector<std::string> constraint_names =
1306 TruncateNames(constraints.names());
1307 // Constraints are translated into:
1308 // 1. ax <= upper_bound (if lower bound <= -GRB_INFINITY, and upper_bound
1309 // is finite and less than GRB_INFINITY)
1310 // 2. ax >= lower_bound (if upper bound >= GRB_INFINITY, and lower_bound is
1311 // finite and greater than -GRB_INFINITY)
1312 // 3. ax == xxxxx_bound (if both bounds are finite, equal, and their
1313 // absolute values less than GRB_INFINITY)
1314 // 4. ax - slack = 0.0 (otherwise,
1315 // slack bounds == [lower_bound, upper_bound])
1316 std::vector<double> constraint_rhs;
1317 std::vector<char> constraint_sense;
1318 std::vector<SlackInfo> new_slacks;
1319 constraint_rhs.reserve(num_new_constraints);
1320 constraint_sense.reserve(num_new_constraints);
1321 new_slacks.reserve(num_new_constraints);
1322 for (int i = 0; i < num_new_constraints; ++i) {
1323 const int64_t id = constraints.ids(i);
1324 ConstraintData& constraint_data =
1325 gtl::InsertKeyOrDie(&linear_constraints_map_, id);
1326 constraint_data.lower_bound = constraints.lower_bounds(i);
1327 constraint_data.upper_bound = constraints.upper_bounds(i);
1328 constraint_data.constraint_index = i + num_model_constraints;
1329 char sense = GRB_EQUAL;
1330 double rhs = 0.0;
1331 // Detect the type of constraint to add and store RHS and bounds.
1332 if (constraint_data.lower_bound <= -GRB_INFINITY &&
1333 constraint_data.upper_bound < GRB_INFINITY) {
1334 rhs = constraint_data.upper_bound;
1335 sense = GRB_LESS_EQUAL;
1336 } else if (constraint_data.lower_bound > -GRB_INFINITY &&
1337 constraint_data.upper_bound >= GRB_INFINITY) {
1338 rhs = constraint_data.lower_bound;
1339 sense = GRB_GREATER_EQUAL;
1340 } else if (constraint_data.lower_bound == constraint_data.upper_bound) {
1341 rhs = constraint_data.lower_bound;
1342 sense = GRB_EQUAL;
1343 } else {
1344 // Note that constraints where the lower bound and the upper bound are
1345 // -+infinity translate into a range constraint with an unbounded slack.
1346 constraint_data.slack_index = new_slacks.size() + num_gurobi_variables_;
1347 new_slacks.emplace_back(id, constraint_data);
1348 }
1349 constraint_rhs.emplace_back(rhs);
1350 constraint_sense.emplace_back(sense);
1351 }
1352 // Add all constraints in one call.
1354 gurobi_->AddConstrs(constraint_sense, constraint_rhs, constraint_names));
1355 // Add slacks for true ranged constraints (if needed)
1356 if (!new_slacks.empty()) {
1357 RETURN_IF_ERROR(AddNewSlacks(new_slacks));
1358 }
1359 return absl::OkStatus();
1360}
1361
1362absl::Status GurobiSolver::ChangeCoefficients(
1363 const SparseDoubleMatrixProto& matrix) {
1364 const int num_coefficients = matrix.row_ids().size();
1365 std::vector<GurobiLinearConstraintIndex> row_index(num_coefficients);
1366 std::vector<GurobiVariableIndex> col_index(num_coefficients);
1367 for (int k = 0; k < num_coefficients; ++k) {
1368 row_index[k] =
1369 linear_constraints_map_.at(matrix.row_ids(k)).constraint_index;
1370 col_index[k] = variables_map_.at(matrix.column_ids(k));
1371 }
1372 return gurobi_->ChgCoeffs(row_index, col_index, matrix.coefficients());
1373}
1374
1375absl::Status GurobiSolver::UpdateDoubleListAttribute(
1376 const SparseDoubleVectorProto& update, const char* attribute_name,
1377 const IdHashMap& id_hash_map) {
1378 if (update.ids_size() == 0) {
1379 return absl::OkStatus();
1380 }
1381 std::vector<int> index;
1382 index.reserve(update.ids_size());
1383 for (const int64_t id : update.ids()) {
1384 index.push_back(id_hash_map.at(id));
1385 }
1386 return gurobi_->SetDoubleAttrList(attribute_name, index, update.values());
1387}
1388
1389absl::Status GurobiSolver::UpdateInt32ListAttribute(
1390 const SparseInt32VectorProto& update, const char* attribute_name,
1391 const IdHashMap& id_hash_map) {
1392 if (update.ids_size() == 0) {
1393 return absl::OkStatus();
1394 }
1395 std::vector<int> index;
1396 index.reserve(update.ids_size());
1397 for (const int64_t id : update.ids()) {
1398 index.push_back(id_hash_map.at(id));
1399 }
1400 return gurobi_->SetIntAttrList(attribute_name, index, update.values());
1401}
1402
1403absl::Status GurobiSolver::LoadModel(const ModelProto& input_model) {
1404 CHECK(gurobi_ != nullptr);
1405 RETURN_IF_ERROR(gurobi_->SetStringAttr(GRB_STR_ATTR_MODELNAME,
1406 TruncateName(input_model.name())));
1407 RETURN_IF_ERROR(AddNewVariables(input_model.variables()));
1408
1409 RETURN_IF_ERROR(AddNewConstraints(input_model.linear_constraints()));
1410
1411 RETURN_IF_ERROR(ChangeCoefficients(input_model.linear_constraint_matrix()));
1412
1413 const int model_sense =
1414 input_model.objective().maximize() ? GRB_MAXIMIZE : GRB_MINIMIZE;
1415 RETURN_IF_ERROR(gurobi_->SetIntAttr(GRB_INT_ATTR_MODELSENSE, model_sense));
1416 RETURN_IF_ERROR(gurobi_->SetDoubleAttr(GRB_DBL_ATTR_OBJCON,
1417 input_model.objective().offset()));
1418
1420 UpdateDoubleListAttribute(input_model.objective().linear_coefficients(),
1421 GRB_DBL_ATTR_OBJ, variables_map_));
1422 RETURN_IF_ERROR(ResetQuadraticObjectiveTerms(
1423 input_model.objective().quadratic_coefficients()));
1424 return absl::OkStatus();
1425}
1426
1427absl::Status GurobiSolver::ResetQuadraticObjectiveTerms(
1428 const SparseDoubleMatrixProto& terms) {
1429 quadratic_objective_coefficients_.clear();
1430 RETURN_IF_ERROR(gurobi_->DelQ());
1431 const int num_terms = terms.row_ids().size();
1432 if (num_terms > 0) {
1433 std::vector<GurobiVariableIndex> first_var_index(num_terms);
1434 std::vector<GurobiVariableIndex> second_var_index(num_terms);
1435 for (int k = 0; k < num_terms; ++k) {
1436 const VariableId row_id = terms.row_ids(k);
1437 const VariableId column_id = terms.column_ids(k);
1438 first_var_index[k] = variables_map_.at(row_id);
1439 second_var_index[k] = variables_map_.at(column_id);
1440 quadratic_objective_coefficients_[{row_id, column_id}] =
1441 terms.coefficients(k);
1442 }
1443 RETURN_IF_ERROR(gurobi_->AddQpTerms(first_var_index, second_var_index,
1444 terms.coefficients()));
1445 }
1446 return absl::OkStatus();
1447}
1448
1449absl::Status GurobiSolver::UpdateQuadraticObjectiveTerms(
1450 const SparseDoubleMatrixProto& terms) {
1451 CHECK(gurobi_ != nullptr);
1452 const int num_terms = terms.row_ids().size();
1453 if (num_terms > 0) {
1454 std::vector<GurobiVariableIndex> first_var_index(num_terms);
1455 std::vector<GurobiVariableIndex> second_var_index(num_terms);
1456 std::vector<double> coefficient_updates(num_terms);
1457 for (int k = 0; k < num_terms; ++k) {
1458 const VariableId row_id = terms.row_ids(k);
1459 const VariableId column_id = terms.column_ids(k);
1460 first_var_index[k] = variables_map_.at(row_id);
1461 second_var_index[k] = variables_map_.at(column_id);
1462 const std::pair<VariableId, VariableId> qp_term_key(row_id, column_id);
1463 const double new_coefficient = terms.coefficients(k);
1464 // Gurobi will maintain any existing quadratic coefficients unless we
1465 // call GRBdelq (which we don't). So, since stored entries in terms
1466 // specify the target coefficients, we need to compute the difference from
1467 // the existing coefficient with Gurobi, if any.
1468 coefficient_updates[k] =
1469 new_coefficient - quadratic_objective_coefficients_[qp_term_key];
1470 quadratic_objective_coefficients_[qp_term_key] = new_coefficient;
1471 }
1472 RETURN_IF_ERROR(gurobi_->AddQpTerms(first_var_index, second_var_index,
1473 coefficient_updates));
1474 }
1475 return absl::OkStatus();
1476}
1477
1478// Bound changes in constraints can induce new variables, and also remove
1479// some slacks. We first add all new variables, and queue all deletions to be
1480// dealt with later on.
1481absl::Status GurobiSolver::UpdateLinearConstraints(
1482 const LinearConstraintUpdatesProto& constraints_update,
1483 std::vector<GurobiVariableIndex>& deleted_variables_index) {
1484 const SparseDoubleVectorProto& constraint_lower_bounds =
1485 constraints_update.lower_bounds();
1486 const SparseDoubleVectorProto& constraint_upper_bounds =
1487 constraints_update.upper_bounds();
1488
1489 // If no update, just return.
1490 if (constraint_lower_bounds.ids().empty() &&
1491 constraint_upper_bounds.ids().empty()) {
1492 return absl::OkStatus();
1493 }
1494
1495 // We want to avoid changing the right-hand-side, sense, or slacks of each
1496 // constraint more than once. Since we can refer to the same constraint ID
1497 // both in the `constraint_upper_bounds` and `constraint_lower_bounds` sparse
1498 // vectors, we collect all changes into a single structure:
1499 struct UpdateConstraintData {
1500 LinearConstraintId constraint_id;
1501 ConstraintData& source;
1502 double new_lower_bound;
1503 double new_upper_bound;
1504 UpdateConstraintData(const LinearConstraintId id, ConstraintData& reference)
1505 : constraint_id(id),
1506 source(reference),
1507 new_lower_bound(reference.lower_bound),
1508 new_upper_bound(reference.upper_bound) {}
1509 };
1510 const int upper_bounds_size = constraint_upper_bounds.ids().size();
1511 const int lower_bounds_size = constraint_lower_bounds.ids().size();
1512 std::vector<UpdateConstraintData> update_vector;
1513 update_vector.reserve(upper_bounds_size + lower_bounds_size);
1514 // We exploit the fact that IDs are sorted in increasing order to merge
1515 // changes into a vector of aggregated changes.
1516 for (int lower_index = 0, upper_index = 0;
1517 lower_index < lower_bounds_size || upper_index < upper_bounds_size;) {
1518 VariableId lower_id = std::numeric_limits<int64_t>::max();
1519 if (lower_index < lower_bounds_size) {
1520 lower_id = constraint_lower_bounds.ids(lower_index);
1521 }
1522 VariableId upper_id = std::numeric_limits<int64_t>::max();
1523 if (upper_index < upper_bounds_size) {
1524 upper_id = constraint_upper_bounds.ids(upper_index);
1525 }
1526 const VariableId id = std::min(lower_id, upper_id);
1528 UpdateConstraintData update(id, linear_constraints_map_.at(id));
1529 if (lower_id == upper_id) {
1530 update.new_lower_bound = constraint_lower_bounds.values(lower_index++);
1531 update.new_upper_bound = constraint_upper_bounds.values(upper_index++);
1532 } else if (lower_id < upper_id) {
1533 update.new_lower_bound = constraint_lower_bounds.values(lower_index++);
1534 } else { /* upper_id < lower_id */
1535 update.new_upper_bound = constraint_upper_bounds.values(upper_index++);
1536 }
1537 update_vector.emplace_back(update);
1538 }
1539
1540 // We have grouped all changes in update_vector, now generate changes in
1541 // slack bounds, rhs, senses, new slacks, and deleted_slacks (to be dealt
1542 // with later, outside this function).
1543 // These three vectors keep changes to right-hand-side and senses.
1544 std::vector<char> sense_data;
1545 std::vector<double> rhs_data;
1546 std::vector<GurobiLinearConstraintIndex> rhs_index;
1547 // These three vectors keep changes to bounds on existing slack.
1548 std::vector<double> lower_bound_data;
1549 std::vector<double> upper_bound_data;
1550 std::vector<GurobiVariableIndex> bound_index;
1551 // This vector keep newly introduced slacks.
1552 std::vector<SlackInfo> new_slacks;
1553 // Iterate on the changes, and populate the three possible changes.
1554 for (UpdateConstraintData& update_data : update_vector) {
1555 const bool same_lower_bound =
1556 (update_data.source.lower_bound == update_data.new_lower_bound) ||
1557 ((update_data.source.lower_bound <= -GRB_INFINITY) &&
1558 (update_data.new_lower_bound <= -GRB_INFINITY));
1559 const bool same_upper_bound =
1560 (update_data.source.upper_bound == update_data.new_upper_bound) ||
1561 ((update_data.source.upper_bound >= GRB_INFINITY) &&
1562 (update_data.new_upper_bound >= GRB_INFINITY));
1563 if (same_upper_bound && same_lower_bound) continue;
1564 // Save into linear_constraints_map_[id] the new bounds for the linear
1565 // constraint.
1566 update_data.source.lower_bound = update_data.new_lower_bound;
1567 update_data.source.upper_bound = update_data.new_upper_bound;
1568 bool delete_slack = false;
1569 // Detect the type of constraint to add and store RHS and bounds.
1570 if (update_data.new_lower_bound <= -GRB_INFINITY &&
1571 update_data.new_upper_bound < GRB_INFINITY) {
1572 delete_slack = true;
1573 rhs_index.emplace_back(update_data.source.constraint_index);
1574 rhs_data.emplace_back(update_data.new_upper_bound);
1575 sense_data.emplace_back(GRB_LESS_EQUAL);
1576 } else if (update_data.new_lower_bound > -GRB_INFINITY &&
1577 update_data.new_upper_bound >= GRB_INFINITY) {
1578 delete_slack = true;
1579 rhs_index.emplace_back(update_data.source.constraint_index);
1580 rhs_data.emplace_back(update_data.new_lower_bound);
1581 sense_data.emplace_back(GRB_GREATER_EQUAL);
1582 } else if (update_data.new_lower_bound == update_data.new_upper_bound) {
1583 delete_slack = true;
1584 rhs_index.emplace_back(update_data.source.constraint_index);
1585 rhs_data.emplace_back(update_data.new_lower_bound);
1586 sense_data.emplace_back(GRB_EQUAL);
1587 } else {
1588 // Note that constraints where the lower bound and the upper bound are
1589 // -+infinity translated into a range constraint with an unbounded
1590 // slack.
1591 if (update_data.source.slack_index != kUnspecifiedIndex) {
1592 bound_index.emplace_back(update_data.source.slack_index);
1593 lower_bound_data.emplace_back(update_data.new_lower_bound);
1594 upper_bound_data.emplace_back(update_data.new_upper_bound);
1595 } else {
1596 // Note that if we add a new slack, we must both reset the sense and
1597 // right hand side for the inequality.
1598 rhs_index.emplace_back(update_data.source.constraint_index);
1599 rhs_data.emplace_back(0.0);
1600 sense_data.emplace_back(GRB_EQUAL);
1601 // Update the slack_index in the linear_constraints_map_[id]
1602 update_data.source.slack_index =
1603 new_slacks.size() + num_gurobi_variables_;
1604 // Save the data needed to add the new slack.
1605 new_slacks.emplace_back(update_data.constraint_id, update_data.source);
1606 }
1607 }
1608 // If the constraint had a slack, and now is marked for deletion, we reset
1609 // the stored slack_index in linear_constraints_map_[id], save the index
1610 // in the list of variables to be deleted later on and remove the constraint
1611 // from slack_map_.
1612 if (delete_slack && update_data.source.slack_index != kUnspecifiedIndex) {
1613 deleted_variables_index.emplace_back(update_data.source.slack_index);
1614 update_data.source.slack_index = kUnspecifiedIndex;
1615 slack_map_.erase(update_data.constraint_id);
1616 }
1617 }
1618
1619 // Pass down changes to Gurobi.
1620 if (!rhs_index.empty()) {
1622 gurobi_->SetDoubleAttrList(GRB_DBL_ATTR_RHS, rhs_index, rhs_data));
1624 gurobi_->SetCharAttrList(GRB_CHAR_ATTR_SENSE, rhs_index, sense_data));
1625 } // rhs changes
1626 if (!bound_index.empty()) {
1627 RETURN_IF_ERROR(gurobi_->SetDoubleAttrList(GRB_DBL_ATTR_LB, bound_index,
1628 lower_bound_data));
1629 RETURN_IF_ERROR(gurobi_->SetDoubleAttrList(GRB_DBL_ATTR_UB, bound_index,
1630 upper_bound_data));
1631 } // Slack bound changes.
1632
1633 if (!new_slacks.empty()) {
1634 RETURN_IF_ERROR(AddNewSlacks(new_slacks));
1635 }
1636 return absl::OkStatus();
1637}
1638
1639// This function re-assign indices for variables and constraints after
1640// deletion. The updated indices are computed from the previous indices, sorted
1641// in incremental form, but re-assigned so that all indices are contiguous
1642// between [0, num_variables-1] and [0, num_linear_constraints-1].
1643// This implementation exploit the fact that gtl::linked_hash_map preserves the
1644// insertion order of whatever elements remain in the hash tables.
1645absl::Status GurobiSolver::UpdateGurobiIndices() {
1646 { // Recover index of variables.
1647 GurobiVariableIndex next_index = 0;
1648 GurobiVariableIndex prev_index = kUnspecifiedIndex;
1649 auto variable_it = variables_map_.begin();
1650 auto slack_it = slack_map_.begin();
1651 while (variable_it != variables_map_.end() ||
1652 slack_it != slack_map_.end()) {
1653 GurobiVariableIndex variable_index = std::numeric_limits<int32_t>::max();
1654 if (variable_it != variables_map_.end()) {
1655 variable_index = variable_it->second;
1656 }
1657 GurobiVariableIndex slack_index = std::numeric_limits<int32_t>::max();
1658 if (slack_it != slack_map_.end()) {
1659 slack_index = slack_it->second.slack_index;
1660 }
1661 DCHECK_LT(prev_index, variable_index);
1662 DCHECK_LT(prev_index, slack_index);
1663 DCHECK_NE(variable_index, slack_index);
1664 if (slack_index < variable_index) {
1665 prev_index = slack_index;
1666 slack_it->second.slack_index = next_index++;
1667 ++slack_it;
1668 } else {
1669 prev_index = variable_index;
1670 variable_it->second = next_index++;
1671 ++variable_it;
1672 }
1673 }
1674 DCHECK_EQ(next_index, num_gurobi_variables_);
1675 }
1676 { // Recover index of constraints.
1677 GurobiLinearConstraintIndex next_constraint = 0;
1678 GurobiLinearConstraintIndex prev_constraint = kUnspecifiedConstraint;
1679 for (auto& constraint_iterator : linear_constraints_map_) {
1680 DCHECK_LT(prev_constraint, constraint_iterator.second.constraint_index);
1681 prev_constraint = constraint_iterator.second.constraint_index;
1682 constraint_iterator.second.constraint_index = next_constraint++;
1683 }
1684 DCHECK_EQ(next_constraint, num_gurobi_constraints());
1685 }
1686 return absl::OkStatus();
1687}
1688
1689absl::Status GurobiSolver::Update(const ModelUpdateProto& model_update) {
1690 RETURN_IF_ERROR(AddNewVariables(model_update.new_variables()));
1691
1692 RETURN_IF_ERROR(AddNewConstraints(model_update.new_linear_constraints()));
1693
1695 ChangeCoefficients(model_update.linear_constraint_matrix_updates()));
1696
1697 if (model_update.objective_updates().has_direction_update()) {
1698 const int model_sense = model_update.objective_updates().direction_update()
1699 ? GRB_MAXIMIZE
1700 : GRB_MINIMIZE;
1701 RETURN_IF_ERROR(gurobi_->SetIntAttr(GRB_INT_ATTR_MODELSENSE, model_sense));
1702 }
1703
1704 if (model_update.objective_updates().has_offset_update()) {
1705 RETURN_IF_ERROR(gurobi_->SetDoubleAttr(
1706 GRB_DBL_ATTR_OBJCON, model_update.objective_updates().offset_update()));
1707 }
1708
1709 RETURN_IF_ERROR(UpdateDoubleListAttribute(
1710 model_update.objective_updates().linear_coefficients(), GRB_DBL_ATTR_OBJ,
1711 variables_map_));
1712
1713 RETURN_IF_ERROR(UpdateQuadraticObjectiveTerms(
1714 model_update.objective_updates().quadratic_coefficients()));
1715
1717 UpdateDoubleListAttribute(model_update.variable_updates().lower_bounds(),
1718 GRB_DBL_ATTR_LB, variables_map_));
1719
1721 UpdateDoubleListAttribute(model_update.variable_updates().upper_bounds(),
1722 GRB_DBL_ATTR_UB, variables_map_));
1723
1724 if (model_update.variable_updates().has_integers()) {
1725 const SparseBoolVectorProto& update =
1726 model_update.variable_updates().integers();
1727 std::vector<GurobiVariableIndex> index;
1728 index.reserve(update.ids_size());
1729 for (const int64_t id : update.ids()) {
1730 index.push_back(variables_map_.at(id));
1731 }
1732 std::vector<char> value;
1733 value.reserve(update.values_size());
1734 for (const bool val : update.values()) {
1735 value.push_back(val ? GRB_INTEGER : GRB_CONTINUOUS);
1736 }
1738 gurobi_->SetCharAttrList(GRB_CHAR_ATTR_VTYPE, index, value));
1739 }
1740
1741 // Now we update quadratic_objective_coefficients_, removing any terms where
1742 // either or both of the involved variables are about to be deleted.
1743 const absl::flat_hash_set<VariableId> variable_ids_to_be_deleted(
1744 model_update.deleted_variable_ids().begin(),
1745 model_update.deleted_variable_ids().end());
1746 // NOTE: Introducing more state and complexity should speed this up, but we
1747 // opt for the simpler approach for now.
1748 for (auto it = quadratic_objective_coefficients_.cbegin();
1749 it != quadratic_objective_coefficients_.cend();
1750 /*incremented in loop*/) {
1751 if (variable_ids_to_be_deleted.contains(it->first.first) ||
1752 variable_ids_to_be_deleted.contains(it->first.second)) {
1753 quadratic_objective_coefficients_.erase(it++);
1754 } else {
1755 ++it;
1756 }
1757 }
1758 // We cache all Gurobi variables and constraint indices that must be deleted,
1759 // and perform deletions at the end of the update call.
1760 std::vector<GurobiVariableIndex> deleted_variables_index;
1761 std::vector<GurobiLinearConstraintIndex> deleted_constraints_index;
1762
1763 RETURN_IF_ERROR(UpdateLinearConstraints(
1764 model_update.linear_constraint_updates(), deleted_variables_index));
1765
1766 for (const VariableId id : model_update.deleted_variable_ids()) {
1767 deleted_variables_index.emplace_back(variables_map_.at(id));
1768 variables_map_.erase(id);
1769 }
1770
1771 for (const LinearConstraintId id :
1772 model_update.deleted_linear_constraint_ids()) {
1773 ConstraintData& constraint_data = linear_constraints_map_.at(id);
1774 deleted_constraints_index.emplace_back(constraint_data.constraint_index);
1775 if (constraint_data.slack_index != kUnspecifiedIndex) {
1776 deleted_variables_index.emplace_back(constraint_data.slack_index);
1777 constraint_data.slack_index = kUnspecifiedIndex;
1778 slack_map_.erase(id);
1779 }
1780 linear_constraints_map_.erase(id);
1781 }
1782
1783 // If no cached deletions, we are done.
1784 if (deleted_variables_index.empty() && deleted_constraints_index.empty()) {
1785 return absl::OkStatus();
1786 }
1787 // If we are removing variables or constraints we remove them after adding
1788 // any variable or constraint. This is to avoid problems with
1789 // the numbering of possibly new variables and constraints.
1790 // After that we must update the model so that sequence of updates don't
1791 // interfere with one-another.
1792 if (!deleted_constraints_index.empty()) {
1793 RETURN_IF_ERROR(gurobi_->DelConstrs(deleted_constraints_index));
1794 }
1795
1796 if (!deleted_variables_index.empty()) {
1797 RETURN_IF_ERROR(gurobi_->DelVars(deleted_variables_index));
1798 num_gurobi_variables_ -= deleted_variables_index.size();
1799 }
1800
1801 // If we removed variables or constraints we must flush all pending changes
1802 // to synchronize the number of variables and constraints with the Gurobi
1803 // model.
1804 RETURN_IF_ERROR(gurobi_->UpdateModel());
1805 // Regenerate indices.
1806 RETURN_IF_ERROR(UpdateGurobiIndices());
1807
1808 return absl::OkStatus();
1809}
1810
1811absl::StatusOr<std::unique_ptr<GurobiSolver>> GurobiSolver::New(
1812 const ModelProto& input_model, const SolverInterface::InitArgs& init_args) {
1814 return absl::InvalidArgumentError("Gurobi is not correctly installed.");
1815 }
1816 ASSIGN_OR_RETURN(std::unique_ptr<Gurobi> gurobi,
1817 GurobiFromInitArgs(init_args));
1818 auto gurobi_solver = absl::WrapUnique(new GurobiSolver(std::move(gurobi)));
1819 RETURN_IF_ERROR(gurobi_solver->LoadModel(input_model));
1820 return gurobi_solver;
1821}
1822
1823absl::StatusOr<std::unique_ptr<GurobiSolver::GurobiCallbackData>>
1824GurobiSolver::RegisterCallback(const CallbackRegistrationProto& registration,
1825 const Callback cb,
1826 const MessageCallback message_cb,
1827 const absl::Time start,
1828 SolveInterrupter* const local_interrupter) {
1829 const absl::flat_hash_set<CallbackEventProto> events = EventSet(registration);
1830
1831 // Note that IS_MIP does not necessarily mean the problem has integer
1832 // variables. Please refer to Gurobi's doc for details:
1833 // https://www.gurobi.com/documentation/9.1/refman/ismip.html.
1834 //
1835 // Here we assume that we get MIP related events and use a MIP solving
1836 // stragegy when IS_MIP is true.
1837 ASSIGN_OR_RETURN(const int is_mip, gurobi_->GetIntAttr(GRB_INT_ATTR_IS_MIP));
1838
1840 registration, is_mip ? SupportedMIPEvents() : SupportedLPEvents()))
1841 << "for a " << (is_mip ? "MIP" : "LP") << " model";
1842
1843 // Set Gurobi parameters.
1844 if (message_cb != nullptr) {
1845 // Disable logging messages to the console the user wants to handle
1846 // messages.
1847 RETURN_IF_ERROR(gurobi_->SetIntParam(GRB_INT_PAR_LOGTOCONSOLE, 0));
1848 }
1849 if (registration.add_cuts() || registration.add_lazy_constraints()) {
1850 // This is to signal the solver presolve to limit primal transformations
1851 // that precludes crushing cuts to the presolved model.
1852 RETURN_IF_ERROR(gurobi_->SetIntParam(GRB_INT_PAR_PRECRUSH, 1));
1853 }
1854 if (registration.add_lazy_constraints()) {
1855 // This is needed so that the solver knows that some presolve reductions
1856 // can not be performed safely.
1857 RETURN_IF_ERROR(gurobi_->SetIntParam(GRB_INT_PAR_LAZYCONSTRAINTS, 1));
1858 }
1859 return absl::make_unique<GurobiCallbackData>(
1860 GurobiCallbackInput{
1861 .user_cb = cb,
1862 .message_cb = message_cb,
1863 .variable_ids = variables_map_,
1864 .num_gurobi_vars = num_gurobi_variables_,
1865 .events = EventToGurobiWhere(events),
1866 .mip_solution_filter = registration.mip_solution_filter(),
1867 .mip_node_filter = registration.mip_node_filter(),
1868 .start = start},
1869 local_interrupter);
1870}
1871
1872absl::StatusOr<InvertedBounds> GurobiSolver::ListInvertedBounds() const {
1873 InvertedBounds inverted_bounds;
1874 {
1876 const std::vector<double> var_lbs,
1877 gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_LB, num_gurobi_variables_));
1879 const std::vector<double> var_ubs,
1880 gurobi_->GetDoubleAttrArray(GRB_DBL_ATTR_UB, num_gurobi_variables_));
1881 for (const auto& [id, index] : variables_map_) {
1882 if (var_lbs[index] > var_ubs[index]) {
1883 inverted_bounds.variables.push_back(id);
1884 }
1885 }
1886 }
1887 for (const auto& [id, cstr_data] : linear_constraints_map_) {
1888 if (cstr_data.lower_bound > cstr_data.upper_bound) {
1889 inverted_bounds.linear_constraints.push_back(id);
1890 }
1891 }
1892
1893 // Above code have inserted ids in non-stable order.
1894 std::sort(inverted_bounds.variables.begin(), inverted_bounds.variables.end());
1895 std::sort(inverted_bounds.linear_constraints.begin(),
1896 inverted_bounds.linear_constraints.end());
1897 return inverted_bounds;
1898}
1899
1900absl::StatusOr<SolveResultProto> GurobiSolver::Solve(
1901 const SolveParametersProto& parameters,
1902 const ModelSolveParametersProto& model_parameters,
1903 const MessageCallback message_cb,
1904 const CallbackRegistrationProto& callback_registration, const Callback cb,
1905 SolveInterrupter* const interrupter) {
1906 const absl::Time start = absl::Now();
1907 // We must set the parameters before calling RegisterCallback since it changes
1908 // some parameters depending on the callback registration.
1909 RETURN_IF_ERROR(SetParameters(parameters));
1910
1911 // We use a local interrupter that will triggers the calls to GRBterminate()
1912 // when either the user interrupter is triggered or when a callback returns a
1913 // true `terminate`.
1914 std::unique_ptr<SolveInterrupter> local_interrupter;
1915 if (cb != nullptr || interrupter != nullptr) {
1916 local_interrupter = std::make_unique<SolveInterrupter>();
1917 }
1918 const ScopedSolveInterrupterCallback scoped_terminate_callback(
1919 local_interrupter.get(), [&]() {
1920 // Make an immediate call to GRBterminate() as soon as this interrupter
1921 // is triggered (which may immediately happen in the code below when it
1922 // is chained with the optional user interrupter).
1923 //
1924 // This call may happen too early. This is not an issue since we will
1925 // repeat this call at each call of the Gurobi callback. See the comment
1926 // in GurobiCallbackImpl() for details.
1927 gurobi_->Terminate();
1928 });
1929
1930 // Chain the user interrupter to the local interrupter. If/when the user
1931 // interrupter is triggered, this triggers the local interrupter. This may
1932 // happen immediately if the user interrupter is already triggered.
1933 //
1934 // The local interrupter can also be triggered by a callback returning a true
1935 // `terminate`.
1936 const ScopedSolveInterrupterCallback scoped_chaining_callback(
1937 interrupter, [&]() { local_interrupter->Interrupt(); });
1938
1939 // Need to run GRBupdatemodel before registering callbacks (to test if the
1940 // problem is a MIP), setting basis and getting the obj sense.
1941 RETURN_IF_ERROR(gurobi_->UpdateModel());
1942
1943 if (model_parameters.has_initial_basis()) {
1944 RETURN_IF_ERROR(SetGurobiBasis(model_parameters.initial_basis()));
1945 }
1946 RETURN_IF_ERROR(gurobi_->SetIntAttr(GRB_INT_ATTR_NUMSTART,
1947 model_parameters.solution_hints_size()));
1948 for (int i = 0; i < model_parameters.solution_hints_size(); ++i) {
1949 RETURN_IF_ERROR(gurobi_->SetIntParam(GRB_INT_PAR_STARTNUMBER, i));
1950 RETURN_IF_ERROR(UpdateDoubleListAttribute(
1951 model_parameters.solution_hints(i).variable_values(),
1952 GRB_DBL_ATTR_START, variables_map_));
1953 }
1955 UpdateInt32ListAttribute(model_parameters.branching_priorities(),
1956 GRB_INT_ATTR_BRANCHPRIORITY, variables_map_));
1957
1958 // Here we register the callback when we either have a user callback or a
1959 // local interrupter. The rationale for doing so when we have only an
1960 // interrupter is explained in GurobiCallbackImpl().
1961 Gurobi::Callback grb_cb = nullptr;
1962 std::unique_ptr<GurobiCallbackData> gurobi_cb_data;
1963 if (cb != nullptr || local_interrupter != nullptr || message_cb != nullptr) {
1964 ASSIGN_OR_RETURN(gurobi_cb_data,
1965 RegisterCallback(callback_registration, cb, message_cb,
1966 start, local_interrupter.get()));
1967 grb_cb = [&gurobi_cb_data](
1968 const Gurobi::CallbackContext& cb_context) -> absl::Status {
1969 return GurobiCallbackImpl(cb_context, gurobi_cb_data->callback_input,
1970 gurobi_cb_data->message_callback_data,
1971 gurobi_cb_data->local_interrupter);
1972 };
1973 }
1974
1975 // Gurobi returns "infeasible" when bounds are inverted.
1976 {
1977 ASSIGN_OR_RETURN(const InvertedBounds inverted_bounds,
1978 ListInvertedBounds());
1979 RETURN_IF_ERROR(inverted_bounds.ToStatus());
1980 }
1981
1982 RETURN_IF_ERROR(gurobi_->Optimize(grb_cb));
1983
1984 // We flush message callbacks before testing for Gurobi error in case where
1985 // the unfinished line of message would help with the error.
1986 if (gurobi_cb_data != nullptr) {
1987 GurobiCallbackImplFlush(gurobi_cb_data->callback_input,
1988 gurobi_cb_data->message_callback_data);
1989 }
1990
1991 ASSIGN_OR_RETURN(SolveResultProto solve_result,
1992 ExtractSolveResultProto(start, model_parameters));
1993 // Reset Gurobi parameters.
1994 // TODO(user): ensure that resetting parameters does not degrade
1995 // incrementalism performance.
1996 RETURN_IF_ERROR(gurobi_->ResetParameters());
1997
1998 return solve_result;
1999}
2000
2001bool GurobiSolver::CanUpdate(const ModelUpdateProto& model_update) {
2002 return true;
2003}
2004
2005MATH_OPT_REGISTER_SOLVER(SOLVER_TYPE_GUROBI, GurobiSolver::New)
2006
2007} // namespace math_opt
2008} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:495
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:892
#define CHECK_OK(x)
Definition: base/logging.h:44
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:894
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK(condition)
Definition: base/logging.h:890
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:891
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
static absl::StatusOr< std::unique_ptr< Gurobi > > New(GRBenvUniquePtr master_env=nullptr)
Definition: g_gurobi.cc:103
std::function< absl::Status(const CallbackContext &)> Callback
Definition: g_gurobi.h:225
static absl::StatusOr< std::unique_ptr< Gurobi > > NewWithSharedMasterEnv(GRBenv *master_env)
Definition: g_gurobi.cc:97
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
SatParameters parameters
ModelSharedTimeLimit * time_limit
int64_t value
#define GRB_INT_PAR_BARITERLIMIT
Definition: environment.h:476
#define GRB_SUPERBASIC
Definition: environment.h:474
#define GRB_INT_PAR_LOGTOCONSOLE
Definition: environment.h:562
#define GRB_DBL_ATTR_UB
Definition: environment.h:179
#define GRB_INT_ATTR_BRANCHPRIORITY
Definition: environment.h:184
#define GRB_DBL_ATTR_START
Definition: environment.h:182
#define GRB_DBL_PAR_MIPGAP
Definition: environment.h:487
#define GRB_DBL_PAR_FEASIBILITYTOL
Definition: environment.h:484
#define GRB_SOLUTION_LIMIT
Definition: environment.h:465
#define GRB_MAXIMIZE
Definition: environment.h:105
#define GRB_INT_PAR_SOLUTIONLIMIT
Definition: environment.h:480
#define GRB_NONBASIC_LOWER
Definition: environment.h:472
#define GRB_INT_ATTR_MODELSENSE
Definition: environment.h:170
#define GRB_INT_PAR_CUTS
Definition: environment.h:528
#define GRB_INFINITY
Definition: environment.h:108
#define GRB_INT_ATTR_VBASIS
Definition: environment.h:235
#define GRB_GREATER_EQUAL
Definition: environment.h:97
#define GRB_INT_PAR_SEED
Definition: environment.h:579
#define GRB_DBL_ATTR_NODECOUNT
Definition: environment.h:227
#define GRB_INT_PAR_PRESOLVE
Definition: environment.h:571
#define GRB_DBL_PAR_MIPGAPABS
Definition: environment.h:488
#define GRB_DBL_ATTR_ITERCOUNT
Definition: environment.h:225
#define GRB_INT_PAR_THREADS
Definition: environment.h:580
#define GRB_DBL_ATTR_BOUND_SVIO
Definition: environment.h:243
#define GRB_OPTIMAL
Definition: environment.h:457
#define GRB_DBL_PAR_CUTOFF
Definition: environment.h:477
#define GRB_INTEGER
Definition: environment.h:101
#define GRB_DBL_PAR_ITERATIONLIMIT
Definition: environment.h:478
#define GRB_INT_PAR_METHOD
Definition: environment.h:491
#define GRB_INT_ATTR_IS_QP
Definition: environment.h:173
#define GRB_DBL_ATTR_PI
Definition: environment.h:236
#define GRB_DBL_ATTR_OBJVAL
Definition: environment.h:218
#define GRB_NODE_LIMIT
Definition: environment.h:463
#define GRB_INT_PAR_LAZYCONSTRAINTS
Definition: environment.h:560
#define GRB_DBL_ATTR_XN
Definition: environment.h:231
#define GRB_CONTINUOUS
Definition: environment.h:99
#define GRB_INT_PAR_SCALEFLAG
Definition: environment.h:494
#define GRB_DBL_ATTR_OBJ
Definition: environment.h:180
#define GRB_DBL_PAR_HEURISTICS
Definition: environment.h:510
#define GRB_METHOD_BARRIER
Definition: environment.h:609
#define GRB_INT_ATTR_IS_MIP
Definition: environment.h:172
#define GRB_DBL_ATTR_CONSTR_RESIDUAL
Definition: environment.h:254
#define GRB_CHAR_ATTR_VTYPE
Definition: environment.h:181
#define GRB_INT_ATTR_NUMSTART
Definition: environment.h:313
#define GRB_DBL_ATTR_BOUND_VIO
Definition: environment.h:242
#define GRB_TIME_LIMIT
Definition: environment.h:464
#define GRB_NONBASIC_UPPER
Definition: environment.h:473
#define GRB_DBL_ATTR_OBJCON
Definition: environment.h:171
#define GRB_DBL_PAR_BESTBDSTOP
Definition: environment.h:483
#define GRB_STR_ATTR_MODELNAME
Definition: environment.h:169
#define GRB_DBL_ATTR_CONSTR_VIO
Definition: environment.h:248
#define GRB_DBL_ATTR_RC
Definition: environment.h:233
#define GRB_DBL_ATTR_CONSTR_SRESIDUAL
Definition: environment.h:255
#define GRB_LOADED
Definition: environment.h:456
#define GRB_DBL_ATTR_CONSTR_SVIO
Definition: environment.h:249
#define GRB_INPROGRESS
Definition: environment.h:469
#define GRB_INT_ATTR_IS_QCP
Definition: environment.h:174
#define GRB_DBL_PAR_BESTOBJSTOP
Definition: environment.h:482
#define GRB_INF_OR_UNBD
Definition: environment.h:459
#define GRB_DBL_ATTR_X
Definition: environment.h:230
#define GRB_SUBOPTIMAL
Definition: environment.h:468
#define GRB_INFEASIBLE
Definition: environment.h:458
#define GRB_DBL_ATTR_RHS
Definition: environment.h:190
#define GRB_MAXINT
Definition: environment.h:110
#define GRB_INT_PAR_STARTNUMBER
Definition: environment.h:594
#define GRB_EQUAL
Definition: environment.h:98
#define GRB_CHAR_ATTR_SENSE
Definition: environment.h:192
#define GRB_CUTOFF
Definition: environment.h:461
#define GRB_UNBOUNDED
Definition: environment.h:460
#define GRB_INT_ATTR_CBASIS
Definition: environment.h:241
#define GRB_METHOD_DUAL
Definition: environment.h:608
#define GRB_INT_ATTR_BARITERCOUNT
Definition: environment.h:226
#define GRB_BASIC
Definition: environment.h:471
#define GRB_MINIMIZE
Definition: environment.h:104
#define GRB_INT_ATTR_STATUS
Definition: environment.h:217
#define GRB_DBL_ATTR_FARKASDUAL
Definition: environment.h:297
#define GRB_LESS_EQUAL
Definition: environment.h:96
#define GRB_INTERRUPTED
Definition: environment.h:466
#define GRB_DBL_ATTR_POOLOBJVAL
Definition: environment.h:222
#define GRB_DBL_ATTR_LB
Definition: environment.h:178
#define GRB_INT_PAR_SOLUTIONNUMBER
Definition: environment.h:526
#define GRB_ITERATION_LIMIT
Definition: environment.h:462
#define GRB_INT_ATTR_SOLCOUNT
Definition: environment.h:224
#define GRB_NUMERIC
Definition: environment.h:467
#define GRB_METHOD_PRIMAL
Definition: environment.h:607
#define GRB_DBL_PAR_TIMELIMIT
Definition: environment.h:481
#define GRB_DBL_PAR_NODELIMIT
Definition: environment.h:479
#define GRB_USER_OBJ_LIMIT
Definition: environment.h:470
#define GRB_DBL_ATTR_UNBDRAY
Definition: environment.h:299
#define GRB_DBL_ATTR_OBJBOUND
Definition: environment.h:219
#define GRB_INT_PAR_PRECRUSH
Definition: environment.h:566
absl::Status status
Definition: g_gurobi.cc:35
Gurobi * gurobi
Definition: g_gurobi.cc:36
double upper_bound
double lower_bound
int index
const int FATAL
Definition: log_severity.h:32
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::StatusOr< GRBenvUniquePtr > NewMasterEnvironment(std::optional< GurobiInitializerProto::ISVKey > proto_isv_key)
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
Definition: solve.cc:94
absl::Status GurobiCallbackImpl(const Gurobi::CallbackContext &context, const GurobiCallbackInput &callback_input, MessageCallbackData &message_callback_data, SolveInterrupter *const local_interrupter)
TerminationProto TerminateForLimit(const LimitProto limit, const bool feasible, const absl::string_view detail)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
std::vector< bool > EventToGurobiWhere(const absl::flat_hash_set< CallbackEventProto > &events)
std::function< CallbackResult(const CallbackData &)> Callback
Definition: callback.h:89
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
std::function< void(const std::vector< std::string > &)> MessageCallback
std::unique_ptr< GRBenv, GurobiFreeEnv > GRBenvUniquePtr
Definition: g_gurobi.h:67
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:31
STL namespace.
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
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:398
double objective_value