24 #include <string_view>
28 #include "absl/container/flat_hash_map.h"
29 #include "absl/container/flat_hash_set.h"
30 #include "absl/memory/memory.h"
31 #include "absl/status/status.h"
32 #include "absl/status/statusor.h"
33 #include "absl/strings/escaping.h"
34 #include "absl/strings/str_cat.h"
35 #include "absl/strings/str_join.h"
36 #include "absl/strings/string_view.h"
37 #include "absl/time/clock.h"
38 #include "absl/time/time.h"
39 #include "absl/types/span.h"
40 #include "absl/log/check.h"
46 #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"
64 #include "ortools/math_opt/sparse_containers.pb.h"
72 constexpr SupportedProblemStructures kGurobiSupportedStructures = {
80 absl::StatusOr<std::unique_ptr<Gurobi>> GurobiFromInitArgs(
81 const SolverInterface::InitArgs& init_args) {
84 const NonStreamableGurobiInitArguments*
const non_streamable_args =
85 init_args.non_streamable !=
nullptr
86 ? init_args.non_streamable->ToNonStreamableGurobiInitArguments()
88 std::unique_ptr<Gurobi>
gurobi;
89 if (non_streamable_args !=
nullptr &&
90 non_streamable_args->primary_env !=
nullptr) {
92 }
else if (init_args.streamable.has_gurobi() &&
93 init_args.streamable.gurobi().has_isv_key()) {
103 inline BasisStatusProto ConvertVariableStatus(
const int status) {
106 return BASIS_STATUS_BASIC;
108 return BASIS_STATUS_AT_LOWER_BOUND;
110 return BASIS_STATUS_AT_UPPER_BOUND;
112 return BASIS_STATUS_FREE;
114 return BASIS_STATUS_UNSPECIFIED;
118 inline int GrbVariableStatus(
const BasisStatusProto
status) {
120 case BASIS_STATUS_BASIC:
122 case BASIS_STATUS_AT_LOWER_BOUND:
123 case BASIS_STATUS_FIXED_VALUE:
125 case BASIS_STATUS_AT_UPPER_BOUND:
127 case BASIS_STATUS_FREE:
129 case BASIS_STATUS_UNSPECIFIED:
131 LOG(FATAL) <<
"Unexpected invalid initial_basis.";
136 GurobiParametersProto MergeParameters(
137 const SolveParametersProto& solve_parameters) {
138 GurobiParametersProto merged_parameters;
141 GurobiParametersProto::Parameter*
const parameter =
142 merged_parameters.add_parameters();
144 parameter->set_value(solve_parameters.enable_output() ?
"1" :
"0");
147 if (solve_parameters.has_time_limit()) {
148 const double time_limit = absl::ToDoubleSeconds(
150 GurobiParametersProto::Parameter*
const parameter =
151 merged_parameters.add_parameters();
153 parameter->set_value(absl::StrCat(
time_limit));
156 if (solve_parameters.has_node_limit()) {
157 GurobiParametersProto::Parameter*
const parameter =
158 merged_parameters.add_parameters();
160 parameter->set_value(absl::StrCat(solve_parameters.node_limit()));
163 if (solve_parameters.has_threads()) {
164 const int threads = solve_parameters.threads();
165 GurobiParametersProto::Parameter*
const parameter =
166 merged_parameters.add_parameters();
168 parameter->set_value(absl::StrCat(threads));
171 if (solve_parameters.has_absolute_gap_tolerance()) {
172 const double absolute_gap_tolerance =
173 solve_parameters.absolute_gap_tolerance();
174 GurobiParametersProto::Parameter*
const parameter =
175 merged_parameters.add_parameters();
177 parameter->set_value(absl::StrCat(absolute_gap_tolerance));
180 if (solve_parameters.has_relative_gap_tolerance()) {
181 const double relative_gap_tolerance =
182 solve_parameters.relative_gap_tolerance();
183 GurobiParametersProto::Parameter*
const parameter =
184 merged_parameters.add_parameters();
186 parameter->set_value(absl::StrCat(relative_gap_tolerance));
189 if (solve_parameters.has_cutoff_limit()) {
190 GurobiParametersProto::Parameter*
const parameter =
191 merged_parameters.add_parameters();
193 parameter->set_value(absl::StrCat(solve_parameters.cutoff_limit()));
196 if (solve_parameters.has_objective_limit()) {
197 GurobiParametersProto::Parameter*
const parameter =
198 merged_parameters.add_parameters();
200 parameter->set_value(absl::StrCat(solve_parameters.objective_limit()));
203 if (solve_parameters.has_best_bound_limit()) {
204 GurobiParametersProto::Parameter*
const parameter =
205 merged_parameters.add_parameters();
207 parameter->set_value(absl::StrCat(solve_parameters.best_bound_limit()));
210 if (solve_parameters.has_solution_limit()) {
211 GurobiParametersProto::Parameter*
const parameter =
212 merged_parameters.add_parameters();
214 parameter->set_value(absl::StrCat(solve_parameters.solution_limit()));
217 if (solve_parameters.has_random_seed()) {
218 const int random_seed =
220 GurobiParametersProto::Parameter*
const parameter =
221 merged_parameters.add_parameters();
223 parameter->set_value(absl::StrCat(random_seed));
226 if (solve_parameters.has_solution_pool_size()) {
227 GurobiParametersProto::Parameter*
const solution_pool_size =
228 merged_parameters.add_parameters();
230 solution_pool_size->set_value(
231 absl::StrCat(solve_parameters.solution_pool_size()));
234 if (solve_parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
235 GurobiParametersProto::Parameter*
const parameter =
236 merged_parameters.add_parameters();
238 switch (solve_parameters.lp_algorithm()) {
239 case LP_ALGORITHM_PRIMAL_SIMPLEX:
242 case LP_ALGORITHM_DUAL_SIMPLEX:
245 case LP_ALGORITHM_BARRIER:
249 LOG(FATAL) <<
"LPAlgorithm: "
251 <<
" unknown, error setting Gurobi parameters";
255 if (solve_parameters.scaling() != EMPHASIS_UNSPECIFIED) {
256 GurobiParametersProto::Parameter*
const parameter =
257 merged_parameters.add_parameters();
259 switch (solve_parameters.scaling()) {
261 parameter->set_value(absl::StrCat(0));
264 case EMPHASIS_MEDIUM:
265 parameter->set_value(absl::StrCat(1));
268 parameter->set_value(absl::StrCat(2));
270 case EMPHASIS_VERY_HIGH:
271 parameter->set_value(absl::StrCat(3));
274 LOG(FATAL) <<
"Scaling emphasis: "
276 <<
" unknown, error setting Gurobi parameters";
280 if (solve_parameters.cuts() != EMPHASIS_UNSPECIFIED) {
281 GurobiParametersProto::Parameter*
const parameter =
282 merged_parameters.add_parameters();
284 switch (solve_parameters.cuts()) {
286 parameter->set_value(absl::StrCat(0));
289 case EMPHASIS_MEDIUM:
290 parameter->set_value(absl::StrCat(1));
293 parameter->set_value(absl::StrCat(2));
295 case EMPHASIS_VERY_HIGH:
296 parameter->set_value(absl::StrCat(3));
299 LOG(FATAL) <<
"Cuts emphasis: "
301 <<
" unknown, error setting Gurobi parameters";
305 if (solve_parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
306 GurobiParametersProto::Parameter*
const parameter =
307 merged_parameters.add_parameters();
309 switch (solve_parameters.heuristics()) {
311 parameter->set_value(absl::StrCat(0.0));
314 parameter->set_value(absl::StrCat(0.025));
316 case EMPHASIS_MEDIUM:
319 parameter->set_value(absl::StrCat(0.05));
322 parameter->set_value(absl::StrCat(0.1));
324 case EMPHASIS_VERY_HIGH:
325 parameter->set_value(absl::StrCat(0.2));
328 LOG(FATAL) <<
"Heuristics emphasis: "
330 <<
" unknown, error setting Gurobi parameters";
334 if (solve_parameters.presolve() != EMPHASIS_UNSPECIFIED) {
335 GurobiParametersProto::Parameter*
const parameter =
336 merged_parameters.add_parameters();
338 switch (solve_parameters.presolve()) {
340 parameter->set_value(absl::StrCat(0));
343 case EMPHASIS_MEDIUM:
344 parameter->set_value(absl::StrCat(1));
347 case EMPHASIS_VERY_HIGH:
348 parameter->set_value(absl::StrCat(2));
351 LOG(FATAL) <<
"Presolve emphasis: "
353 <<
" unknown, error setting Gurobi parameters";
357 if (solve_parameters.has_iteration_limit()) {
358 GurobiParametersProto::Parameter*
const iterationlimit =
359 merged_parameters.add_parameters();
361 iterationlimit->set_value(absl::StrCat(solve_parameters.iteration_limit()));
362 GurobiParametersProto::Parameter*
const bariterlimit =
363 merged_parameters.add_parameters();
366 solve_parameters.iteration_limit());
367 bariterlimit->set_value(absl::StrCat(val));
370 for (
const GurobiParametersProto::Parameter& parameter :
371 solve_parameters.gurobi().parameters()) {
372 *merged_parameters.add_parameters() = parameter;
375 return merged_parameters;
378 absl::StatusOr<int64_t> SafeInt64FromDouble(
const double d) {
379 const int64_t result =
static_cast<int64_t
>(d);
380 if (
static_cast<double>(result) != d) {
381 return absl::InternalError(
382 absl::StrCat(
"Expected double ", d,
" to contain an int64_t."));
387 const absl::flat_hash_set<CallbackEventProto>& SupportedMIPEvents() {
388 static const auto*
const kEvents =
389 new absl::flat_hash_set<CallbackEventProto>({
390 CALLBACK_EVENT_PRESOLVE, CALLBACK_EVENT_SIMPLEX, CALLBACK_EVENT_MIP,
391 CALLBACK_EVENT_MIP_SOLUTION, CALLBACK_EVENT_MIP_NODE,
400 const absl::flat_hash_set<CallbackEventProto>& SupportedLPEvents() {
401 static const auto*
const kEvents =
402 new absl::flat_hash_set<CallbackEventProto>({
403 CALLBACK_EVENT_PRESOLVE,
404 CALLBACK_EVENT_SIMPLEX,
405 CALLBACK_EVENT_BARRIER,
412 constexpr std::size_t kMaxNameSize = 255;
415 std::string TruncateName(
const std::string_view original_name) {
417 original_name.substr(0,
std::min(kMaxNameSize, original_name.size())));
421 std::vector<std::string> TruncateNames(
422 const google::protobuf::RepeatedPtrField<std::string>& original_names) {
423 std::vector<std::string> result;
424 result.reserve(original_names.size());
425 for (
const std::string& original_name : original_names) {
426 result.push_back(TruncateName(original_name));
431 absl::Status SafeGurobiDouble(
const double d) {
434 <<
"finite value: " << d <<
" will be treated as infinite by Gurobi";
436 return absl::OkStatus();
439 std::string EscapedNameForLogging(
const absl::string_view
name) {
440 return absl::StrCat(
"\"", absl::Utf8SafeCEscape(
name),
"\"");
443 constexpr
int kDeletedIndex = -1;
444 constexpr
int kUnsetIndex = -2;
451 std::vector<int> IndexUpdateMap(
const int size_before_delete,
452 const std::vector<int>& deletes) {
453 std::vector<int> result(size_before_delete, kUnsetIndex);
454 for (
const int del : deletes) {
455 result[del] = kDeletedIndex;
458 for (
int& r : result) {
459 if (r != kDeletedIndex) {
463 CHECK_GT(r, kUnsetIndex);
470 GurobiSolver::GurobiSolver(std::unique_ptr<Gurobi> g_gurobi)
471 : gurobi_(std::move(g_gurobi)) {}
473 absl::StatusOr<TerminationProto> GurobiSolver::ConvertTerminationReason(
474 const int gurobi_status,
const SolutionClaims solution_claims) {
475 switch (gurobi_status) {
481 if (solution_claims.primal_feasible_solution_exists) {
485 "Gurobi status GRB_UNBOUNDED");
488 "Gurobi status GRB_INF_OR_UNBD");
491 false,
"Gurobi status GRB_CUTOFF");
495 solution_claims.primal_feasible_solution_exists);
499 solution_claims.primal_feasible_solution_exists);
503 solution_claims.primal_feasible_solution_exists);
507 solution_claims.primal_feasible_solution_exists);
511 solution_claims.primal_feasible_solution_exists);
523 solution_claims.primal_feasible_solution_exists);
525 return absl::InternalError(
526 "Error creating termination reason, unexpected gurobi status code "
529 return absl::InternalError(
530 "Error creating termination reason, unexpected gurobi status code "
533 return absl::InternalError(absl::StrCat(
534 "Missing Gurobi optimization status code case: ", gurobi_status));
538 absl::StatusOr<bool> GurobiSolver::IsMaximize()
const {
544 absl::StatusOr<bool> GurobiSolver::IsMIP()
const {
546 return static_cast<bool>(is_mip);
550 absl::StatusOr<bool> GurobiSolver::IsQP()
const {
552 return static_cast<bool>(is_qp);
555 absl::StatusOr<bool> GurobiSolver::IsQCP()
const {
557 return static_cast<bool>(is_qcp);
562 template <
typename T>
563 void GurobiSolver::GurobiVectorToSparseDoubleVector(
564 const absl::Span<const double> gurobi_values,
const T& map,
565 SparseDoubleVectorProto& result,
566 const SparseVectorFilterProto& filter)
const {
567 SparseVectorFilterPredicate predicate(filter);
568 for (
auto [
id, gurobi_data] : map) {
569 const double value = gurobi_values[get_model_index(gurobi_data)];
570 if (predicate.AcceptsAndUpdate(
id,
value)) {
572 result.add_values(
value);
577 absl::Status GurobiSolver::SetGurobiBasis(
const BasisProto& basis) {
578 std::vector<int> gurobi_variable_basis_status(num_gurobi_variables_);
579 for (
const auto [
id,
value] :
MakeView(basis.variable_status())) {
580 gurobi_variable_basis_status[variables_map_.at(
id)] =
581 GrbVariableStatus(
static_cast<BasisStatusProto
>(
value));
584 std::vector<int> gurobi_constraint_basis_status;
585 gurobi_constraint_basis_status.reserve(num_gurobi_lin_cons_);
586 for (
const auto [
id,
value] :
MakeView(basis.constraint_status())) {
587 const LinearConstraintData& constraint_data =
588 linear_constraints_map_.at(
id);
590 if (constraint_data.slack_index == kUnspecifiedIndex) {
591 if (
value == BASIS_STATUS_BASIC) {
592 gurobi_constraint_basis_status.push_back(kGrbBasicConstraint);
594 gurobi_constraint_basis_status.push_back(kGrbNonBasicConstraint);
597 }
else if (
value == BASIS_STATUS_BASIC) {
601 gurobi_variable_basis_status[constraint_data.slack_index] =
GRB_BASIC;
602 gurobi_constraint_basis_status.push_back(kGrbNonBasicConstraint);
604 gurobi_variable_basis_status[constraint_data.slack_index] =
605 GrbVariableStatus(
static_cast<BasisStatusProto
>(
value));
606 gurobi_constraint_basis_status.push_back(kGrbNonBasicConstraint);
610 gurobi_variable_basis_status));
612 gurobi_constraint_basis_status));
613 return absl::OkStatus();
616 absl::StatusOr<BasisProto> GurobiSolver::GetGurobiBasis() {
619 const std::vector<int> gurobi_variable_basis_status,
622 for (
auto [variable_id, gurobi_variable_index] : variables_map_) {
623 basis.mutable_variable_status()->add_ids(variable_id);
624 const BasisStatusProto variable_status = ConvertVariableStatus(
625 gurobi_variable_basis_status[gurobi_variable_index]);
626 if (variable_status == BASIS_STATUS_UNSPECIFIED) {
627 return absl::InternalError(
628 absl::StrCat(
"Invalid Gurobi variable basis status: ",
629 gurobi_variable_basis_status[gurobi_variable_index]));
631 basis.mutable_variable_status()->add_values(variable_status);
635 const std::vector<int> gurobi_constraint_basis_status,
637 for (
auto [constraint_id, gurobi_data] : linear_constraints_map_) {
638 basis.mutable_constraint_status()->add_ids(constraint_id);
639 const int gurobi_constraint_status =
640 gurobi_constraint_basis_status[gurobi_data.constraint_index];
641 if ((gurobi_constraint_status != kGrbBasicConstraint) &&
642 (gurobi_constraint_status != kGrbNonBasicConstraint)) {
643 return absl::InternalError(
644 absl::StrCat(
"Invalid Gurobi constraint basis status: ",
645 gurobi_constraint_status));
650 if (gurobi_constraint_status == kGrbBasicConstraint) {
651 basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
653 basis.mutable_constraint_status()->add_values(
654 BASIS_STATUS_AT_UPPER_BOUND);
659 if (gurobi_constraint_status == kGrbBasicConstraint) {
660 basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
662 basis.mutable_constraint_status()->add_values(
663 BASIS_STATUS_AT_LOWER_BOUND);
666 }
else if (gurobi_data.lower_bound == gurobi_data.upper_bound) {
667 if (gurobi_constraint_status == kGrbBasicConstraint) {
668 basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
672 basis.mutable_constraint_status()->add_values(BASIS_STATUS_FIXED_VALUE);
676 const BasisStatusProto slack_status = ConvertVariableStatus(
677 gurobi_variable_basis_status[gurobi_data.slack_index]);
678 if (slack_status == BASIS_STATUS_UNSPECIFIED) {
679 return absl::InternalError(absl::StrCat(
680 "Invalid Gurobi slack variable basis status: ", slack_status));
682 if ((gurobi_constraint_status == kGrbBasicConstraint) ||
683 (slack_status == BASIS_STATUS_BASIC)) {
684 basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
686 basis.mutable_constraint_status()->add_values(slack_status);
695 absl::StatusOr<DualRayProto> GurobiSolver::GetGurobiDualRay(
696 const SparseVectorFilterProto& linear_constraints_filter,
697 const SparseVectorFilterProto& variables_filter,
const bool is_maximize) {
701 num_gurobi_lin_cons_));
703 DualRayProto dual_ray;
707 SparseVectorFilterPredicate predicate(linear_constraints_filter);
708 for (
auto [constraint_id, gurobi_data] : linear_constraints_map_) {
710 const double value = -farkas_dual[gurobi_data.constraint_index];
711 if (predicate.AcceptsAndUpdate(constraint_id,
value)) {
712 dual_ray.mutable_dual_values()->add_ids(constraint_id);
714 dual_ray.mutable_dual_values()->add_values(-
value);
716 dual_ray.mutable_dual_values()->add_values(
value);
724 SparseVectorFilterPredicate predicate(variables_filter);
725 for (
auto [var_id, gurobi_variable_index] : variables_map_) {
728 double reduced_cost_value = 0.0;
730 gurobi_->GetVars(gurobi_variable_index, 1));
731 for (
int i = 0; i <
column.inds.size(); ++i) {
732 reduced_cost_value += farkas_dual[
column.inds[i]] *
column.vals[i];
734 if (predicate.AcceptsAndUpdate(var_id, reduced_cost_value)) {
735 dual_ray.mutable_reduced_costs()->add_ids(var_id);
737 dual_ray.mutable_reduced_costs()->add_values(-reduced_cost_value);
739 dual_ray.mutable_reduced_costs()->add_values(reduced_cost_value);
747 absl::StatusOr<ProblemStatusProto> GurobiSolver::GetProblemStatus(
748 const int grb_termination,
const SolutionClaims solution_claims) {
749 ProblemStatusProto problem_status;
752 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
753 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
756 if (solution_claims.primal_feasible_solution_exists) {
757 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
759 if (solution_claims.dual_feasible_solution_exists) {
760 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
764 switch (grb_termination) {
766 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
767 if (solution_claims.primal_feasible_solution_exists) {
768 return absl::InternalError(
769 "GRB_INT_ATTR_STATUS == GRB_INFEASIBLE, but a primal feasible "
770 "solution was returned.");
776 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
777 if (solution_claims.dual_feasible_solution_exists) {
778 return absl::InternalError(
779 "GRB_INT_ATTR_STATUS == GRB_UNBOUNDED, but a dual feasible "
780 "solution was returned or exists.");
784 problem_status.set_primal_or_dual_infeasible(
true);
785 if (solution_claims.primal_feasible_solution_exists) {
786 return absl::InternalError(
787 "GRB_INT_ATTR_STATUS == GRB_INF_OR_UNBD, but a primal feasible "
788 "solution was returned.");
790 if (solution_claims.dual_feasible_solution_exists) {
791 return absl::InternalError(
792 "GRB_INT_ATTR_STATUS == GRB_INF_OR_UNBD, but a dual feasible "
793 "solution was returned or exists.");
797 return problem_status;
800 absl::StatusOr<SolveResultProto> GurobiSolver::ExtractSolveResultProto(
801 const absl::Time
start,
const ModelSolveParametersProto& model_parameters) {
802 SolveResultProto result;
805 GetSolutions(model_parameters));
810 for (
auto& solution : solutions) {
811 *result.add_solutions() = std::move(solution);
815 GetSolveStats(
start, solution_claims));
820 ConvertTerminationReason(grb_termination, solution_claims));
821 return std::move(result);
824 absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetSolutions(
825 const ModelSolveParametersProto& model_parameters) {
831 return GetMipSolutions(model_parameters);
833 return GetQcpSolution(model_parameters);
835 return GetQpSolution(model_parameters);
837 return GetLpSolution(model_parameters);
841 absl::StatusOr<SolveStatsProto> GurobiSolver::GetSolveStats(
842 const absl::Time
start,
const SolutionClaims solution_claims) {
843 SolveStatsProto solve_stats;
846 solve_stats.mutable_solve_time()));
851 .primal_feasible_solution_exists));
852 solve_stats.set_best_primal_bound(best_primal_bound);
855 solve_stats.set_best_dual_bound(best_dual_bound);
860 GetProblemStatus(grb_termination, solution_claims));
866 SafeInt64FromDouble(simplex_iters_double));
867 solve_stats.set_simplex_iterations(simplex_iters);
873 solve_stats.set_barrier_iterations(barrier_iters);
880 solve_stats.set_node_count(
nodes);
885 absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetMipSolutions(
886 const ModelSolveParametersProto& model_parameters) {
887 int num_solutions = 0;
891 std::vector<SolutionProto> solutions;
892 solutions.reserve(num_solutions);
893 for (
int i = 0; i < num_solutions; ++i) {
896 PrimalSolutionProto primal_solution;
899 primal_solution.set_objective_value(sol_val);
900 primal_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
902 const std::vector<double> grb_var_values,
904 GurobiVectorToSparseDoubleVector(grb_var_values, variables_map_,
905 *primal_solution.mutable_variable_values(),
906 model_parameters.variable_values_filter());
907 *solutions.emplace_back(SolutionProto()).mutable_primal_solution() =
908 std::move(primal_solution);
920 const SolutionClaims solution_claims = {
921 .primal_feasible_solution_exists = num_solutions > 0,
922 .dual_feasible_solution_exists = std::isfinite(best_dual_bound)};
927 if (grb_termination ==
GRB_OPTIMAL && num_solutions == 0) {
928 return absl::InternalError(
929 "GRB_INT_ATTR_STATUS == GRB_OPTIMAL, but solution pool is empty.");
931 if (grb_termination ==
GRB_OPTIMAL && !std::isfinite(best_dual_bound)) {
932 return absl::InternalError(
933 "GRB_INT_ATTR_STATUS == GRB_OPTIMAL, but GRB_DBL_ATTR_OBJBOUND is "
934 "unavailable or infinite.");
937 return SolutionsAndClaims{.solutions = std::move(solutions),
938 .solution_claims = solution_claims};
941 absl::StatusOr<GurobiSolver::SolutionAndClaim<PrimalSolutionProto>>
942 GurobiSolver::GetConvexPrimalSolutionIfAvailable(
943 const ModelSolveParametersProto& model_parameters) {
945 return SolutionAndClaim<PrimalSolutionProto>{
946 .solution = std::nullopt, .feasible_solution_exists =
false};
953 const std::vector<double> grb_var_values,
954 gurobi_->GetDoubleAttrArray(
GRB_DBL_ATTR_X, num_gurobi_variables_));
956 PrimalSolutionProto primal_solution;
966 primal_solution.set_objective_value(sol_val);
970 const std::vector<double> linear_obj_coefs,
972 for (
int i = 0; i < num_gurobi_variables_; ++i) {
978 primal_solution.set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
980 primal_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
982 primal_solution.set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
983 }
else if (PrimalSolutionQualityAvailable()) {
984 ASSIGN_OR_RETURN(
const double solution_quality, GetPrimalSolutionQuality());
987 if (solution_quality <= tolerance) {
988 primal_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
990 primal_solution.set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
994 GurobiVectorToSparseDoubleVector(grb_var_values, variables_map_,
995 *primal_solution.mutable_variable_values(),
996 model_parameters.variable_values_filter());
997 const bool primal_feasible_solution_exists =
998 (primal_solution.feasibility_status() == SOLUTION_STATUS_FEASIBLE);
999 return SolutionAndClaim<PrimalSolutionProto>{
1000 .solution = std::move(primal_solution),
1001 .feasible_solution_exists = primal_feasible_solution_exists};
1004 bool GurobiSolver::PrimalSolutionQualityAvailable()
const {
1013 absl::StatusOr<double> GurobiSolver::GetPrimalSolutionQuality()
const {
1026 return std::max({constraint_residual, constraint_violation, bound_violation,
1027 constraint_scaled_residual, constraint_scaled_violation,
1028 bound_scaled_violation});
1031 absl::StatusOr<double> GurobiSolver::GetBestPrimalBound(
1032 const bool has_primal_feasible_solution) {
1037 if (has_primal_feasible_solution &&
1056 absl::StatusOr<double> GurobiSolver::GetBestDualBound() {
1070 absl::StatusOr<std::optional<BasisProto>> GurobiSolver::GetBasisIfAvailable() {
1076 basis.set_basic_dual_feasibility(SOLUTION_STATUS_UNDETERMINED);
1078 basis.set_basic_dual_feasibility(SOLUTION_STATUS_FEASIBLE);
1080 basis.set_basic_dual_feasibility(SOLUTION_STATUS_INFEASIBLE);
1083 return std::move(basis);
1085 return std::nullopt;
1088 absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetLpSolution(
1089 const ModelSolveParametersProto& model_parameters) {
1091 GetConvexPrimalSolutionIfAvailable(model_parameters));
1093 GetLpDualSolutionIfAvailable(model_parameters));
1095 const SolutionClaims solution_claims = {
1096 .primal_feasible_solution_exists =
1097 primal_solution_and_claim.feasible_solution_exists,
1098 .dual_feasible_solution_exists =
1099 dual_solution_and_claim.feasible_solution_exists};
1101 if (!primal_solution_and_claim.solution.has_value() &&
1102 !dual_solution_and_claim.solution.has_value() && !basis.has_value()) {
1103 return SolutionsAndClaims{.solution_claims = solution_claims};
1105 SolutionsAndClaims solution_and_claims{.solution_claims = solution_claims};
1106 SolutionProto& solution =
1107 solution_and_claims.solutions.emplace_back(SolutionProto());
1108 if (primal_solution_and_claim.solution.has_value()) {
1109 *solution.mutable_primal_solution() =
1110 std::move(*primal_solution_and_claim.solution);
1112 if (dual_solution_and_claim.solution.has_value()) {
1113 *solution.mutable_dual_solution() =
1114 std::move(*dual_solution_and_claim.solution);
1116 if (basis.has_value()) {
1117 *solution.mutable_basis() = std::move(*basis);
1119 return solution_and_claims;
1122 absl::StatusOr<GurobiSolver::SolutionAndClaim<DualSolutionProto>>
1123 GurobiSolver::GetLpDualSolutionIfAvailable(
1124 const ModelSolveParametersProto& model_parameters) {
1127 return SolutionAndClaim<DualSolutionProto>{
1128 .solution = std::nullopt, .feasible_solution_exists =
false};
1134 DualSolutionProto dual_solution;
1135 bool dual_feasible_solution_exists =
false;
1137 const std::vector<double> grb_constraint_duals,
1139 GurobiVectorToSparseDoubleVector(grb_constraint_duals,
1140 linear_constraints_map_,
1141 *dual_solution.mutable_dual_values(),
1142 model_parameters.dual_values_filter());
1145 const std::vector<double> grb_reduced_cost_values,
1147 GurobiVectorToSparseDoubleVector(grb_reduced_cost_values, variables_map_,
1148 *dual_solution.mutable_reduced_costs(),
1149 model_parameters.reduced_costs_filter());
1157 dual_solution.set_objective_value(obj_val);
1163 dual_solution.set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
1165 dual_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1166 dual_feasible_solution_exists =
true;
1168 dual_solution.set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
1189 if (dual_feasible_solution_exists || std::isfinite(best_dual_bound)) {
1190 dual_feasible_solution_exists =
true;
1192 return absl::InternalError(
1193 "GRB_INT_ATTR_STATUS == GRB_OPTIMAL, but GRB_DBL_ATTR_OBJBOUND is "
1194 "unavailable or infinite, and no dual feasible solution is returned");
1196 return SolutionAndClaim<DualSolutionProto>{
1197 .solution = std::move(dual_solution),
1198 .feasible_solution_exists = dual_feasible_solution_exists};
1201 absl::Status GurobiSolver::FillRays(
1202 const ModelSolveParametersProto& model_parameters,
1203 const SolutionClaims solution_claims, SolveResultProto& result) {
1208 if (!solution_claims.dual_feasible_solution_exists &&
1209 num_gurobi_variables_ > 0 &&
1213 num_gurobi_variables_));
1214 PrimalRayProto*
const primal_ray = result.add_primal_rays();
1215 GurobiVectorToSparseDoubleVector(grb_ray_var_values, variables_map_,
1216 *primal_ray->mutable_variable_values(),
1217 model_parameters.variable_values_filter());
1222 if (!solution_claims.primal_feasible_solution_exists &&
1223 num_gurobi_lin_cons_ > 0 &&
1226 DualRayProto dual_ray,
1227 GetGurobiDualRay(model_parameters.dual_values_filter(),
1228 model_parameters.reduced_costs_filter(), is_maximize));
1229 result.mutable_dual_rays()->Add(std::move(dual_ray));
1231 return absl::OkStatus();
1234 absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetQpSolution(
1235 const ModelSolveParametersProto& model_parameters) {
1237 GetConvexPrimalSolutionIfAvailable(model_parameters));
1241 GetLpDualSolutionIfAvailable(model_parameters));
1248 const SolutionClaims solution_claims = {
1249 .primal_feasible_solution_exists = found_primal_feasible_solution,
1250 .dual_feasible_solution_exists = found_dual_feasible_solution};
1252 if (!primal_solution.has_value() && !basis.has_value()) {
1253 return GurobiSolver::SolutionsAndClaims{.solution_claims = solution_claims};
1255 SolutionsAndClaims solution_and_claims{.solution_claims = solution_claims};
1256 SolutionProto& solution =
1257 solution_and_claims.solutions.emplace_back(SolutionProto());
1258 if (primal_solution.has_value()) {
1259 *solution.mutable_primal_solution() = *std::move(primal_solution);
1261 if (dual_solution.has_value()) {
1262 *solution.mutable_dual_solution() = *std::move(dual_solution);
1264 if (basis.has_value()) {
1265 *solution.mutable_basis() = *std::move(basis);
1267 return solution_and_claims;
1270 absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetQcpSolution(
1271 const ModelSolveParametersProto& model_parameters) {
1273 GetConvexPrimalSolutionIfAvailable(model_parameters));
1277 GetLpDualSolutionIfAvailable(model_parameters));
1282 const bool proven_feasible = grb_termination ==
GRB_OPTIMAL;
1283 const SolutionClaims solution_claims = {
1284 .primal_feasible_solution_exists = found_primal_feasible_solution,
1285 .dual_feasible_solution_exists =
1286 found_dual_feasible_solution || proven_feasible};
1288 SolutionsAndClaims solution_and_claims{.solution_claims = solution_claims};
1289 if (primal_solution.has_value()) {
1290 *solution_and_claims.solutions.emplace_back().mutable_primal_solution() =
1291 *std::move(primal_solution);
1293 return solution_and_claims;
1296 absl::Status GurobiSolver::SetParameters(
1298 const GurobiParametersProto gurobi_parameters = MergeParameters(
parameters);
1299 std::vector<std::string> parameter_errors;
1300 for (
const GurobiParametersProto::Parameter& parameter :
1301 gurobi_parameters.parameters()) {
1302 absl::Status param_status =
1303 gurobi_->SetParam(parameter.name().c_str(), parameter.value());
1304 if (!param_status.ok()) {
1305 parameter_errors.emplace_back(std::move(param_status).
message());
1308 if (!parameter_errors.empty()) {
1309 return absl::InvalidArgumentError(absl::StrJoin(parameter_errors,
"; "));
1311 return absl::OkStatus();
1314 absl::Status GurobiSolver::AddNewVariables(
1315 const VariablesProto& new_variables) {
1316 const int num_new_variables = new_variables.lower_bounds().size();
1317 std::vector<char> variable_type(num_new_variables);
1318 for (
int j = 0; j < num_new_variables; ++j) {
1319 const VariableId
id = new_variables.ids(j);
1325 const std::vector<std::string> variable_names =
1326 TruncateNames(new_variables.names());
1329 new_variables.lower_bounds(),
1330 new_variables.upper_bounds(),
1331 variable_type, variable_names));
1332 num_gurobi_variables_ += num_new_variables;
1334 return absl::OkStatus();
1340 absl::Status GurobiSolver::AddNewSlacks(
1341 const std::vector<LinearConstraintData*>& new_slacks) {
1348 const int num_slacks = new_slacks.size();
1349 if (num_slacks == 0) {
1350 return absl::OkStatus();
1353 const std::vector<double> column_non_zeros(num_slacks, -1.0);
1357 std::vector<GurobiLinearConstraintIndex> row_indices;
1358 std::vector<int> column_non_zero_begin;
1359 column_non_zero_begin.reserve(num_slacks);
1360 row_indices.reserve(num_slacks);
1363 for (
int k = 0; k < num_slacks; ++k) {
1364 CHECK_NE(new_slacks[k],
nullptr);
1365 const LinearConstraintData& constraint_data = *new_slacks[k];
1366 row_indices.push_back(constraint_data.constraint_index);
1369 column_non_zero_begin.push_back(k);
1374 column_non_zeros, {},
1377 num_gurobi_variables_ += num_slacks;
1378 return absl::OkStatus();
1381 absl::Status GurobiSolver::AddNewLinearConstraints(
1382 const LinearConstraintsProto& constraints) {
1383 const int num_new_constraints = constraints.lower_bounds().size();
1387 const std::vector<std::string> constraint_names =
1388 TruncateNames(constraints.names());
1398 std::vector<double> constraint_rhs;
1399 std::vector<char> constraint_sense;
1400 std::vector<LinearConstraintData*> new_slacks;
1401 constraint_rhs.reserve(num_new_constraints);
1402 constraint_sense.reserve(num_new_constraints);
1403 new_slacks.reserve(num_new_constraints);
1404 for (
int i = 0; i < num_new_constraints; ++i) {
1405 const int64_t
id = constraints.ids(i);
1406 LinearConstraintData& constraint_data =
1408 const double lb = constraints.lower_bounds(i);
1409 const double ub = constraints.upper_bounds(i);
1411 <<
"lower bound for linear constraint " <<
id <<
": "
1412 << EscapedNameForLogging(
1413 constraints.names().empty() ?
"" : constraints.names(i));
1415 <<
"upper bound for linear constraint " <<
id <<
": "
1416 << EscapedNameForLogging(
1417 constraints.names().empty() ?
"" : constraints.names(i));
1418 constraint_data.lower_bound = lb;
1419 constraint_data.upper_bound = ub;
1420 constraint_data.constraint_index = i + num_gurobi_lin_cons_;
1425 if (lb_is_grb_neg_inf && !ub_is_grb_pos_inf) {
1428 }
else if (!lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
1431 }
else if (lb == ub) {
1437 constraint_data.slack_index = new_slacks.size() + num_gurobi_variables_;
1438 new_slacks.push_back(&constraint_data);
1440 constraint_rhs.emplace_back(rhs);
1441 constraint_sense.emplace_back(sense);
1445 gurobi_->AddConstrs(constraint_sense, constraint_rhs, constraint_names));
1446 num_gurobi_lin_cons_ += num_new_constraints;
1448 if (!new_slacks.empty()) {
1451 return absl::OkStatus();
1454 absl::Status GurobiSolver::AddNewQuadraticConstraints(
1455 const google::protobuf::Map<QuadraticConstraintId,
1456 QuadraticConstraintProto>& constraints) {
1466 for (
const auto& [
id, constraint] : constraints) {
1469 const double lb = constraint.lower_bound();
1470 const double ub = constraint.upper_bound();
1472 <<
"lower bound for quadratic constraint " <<
id <<
": "
1473 << EscapedNameForLogging(constraint.name());
1475 <<
"upper bound for quadratic constraint " <<
id <<
": "
1476 << EscapedNameForLogging(constraint.name());
1479 if (lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
1483 }
else if (lb_is_grb_neg_inf && !ub_is_grb_pos_inf) {
1486 }
else if (!lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
1489 }
else if (lb == ub) {
1495 return absl::UnimplementedError(
1496 "ranged quadratic constraints are not currently supported in Gurobi "
1499 const SparseDoubleVectorProto& linear_coeffs = constraint.linear_terms();
1500 const int num_linear_coeffs = linear_coeffs.ids_size();
1501 std::vector<GurobiVariableIndex> linear_col_index(num_linear_coeffs);
1502 for (
int k = 0; k < num_linear_coeffs; ++k) {
1503 linear_col_index[k] = variables_map_.at(linear_coeffs.ids(k));
1505 const SparseDoubleMatrixProto& quad_coeffs = constraint.quadratic_terms();
1506 const int num_quad_coeffs = quad_coeffs.row_ids_size();
1507 std::vector<GurobiVariableIndex> quad_row_index(num_quad_coeffs);
1508 std::vector<GurobiVariableIndex> quad_col_index(num_quad_coeffs);
1509 for (
int k = 0; k < num_quad_coeffs; ++k) {
1510 quad_row_index[k] = variables_map_.at(quad_coeffs.row_ids(k));
1511 quad_col_index[k] = variables_map_.at(quad_coeffs.column_ids(k));
1514 linear_col_index, linear_coeffs.values(), quad_row_index,
1515 quad_col_index, quad_coeffs.coefficients(), sense, rhs,
1516 TruncateName(constraint.name())));
1518 ++num_gurobi_quad_cons_;
1520 return absl::OkStatus();
1523 absl::Status GurobiSolver::AddNewSosConstraints(
1524 const google::protobuf::Map<AnyConstraintId, SosConstraintProto>&
1527 absl::flat_hash_map<int64_t, SosConstraintData>& constraints_map) {
1528 for (
const auto& [
id, constraint] : constraints) {
1529 SosConstraintData& constraint_data =
1531 constraint_data.constraint_index = num_gurobi_sos_cons_;
1532 std::vector<GurobiVariableIndex> sos_var_indices;
1533 std::vector<double> weights;
1534 for (
int i = 0; i < constraint.expressions_size(); ++i) {
1535 const LinearExpressionProto& expression = constraint.expressions(i);
1536 weights.push_back(constraint.weights().empty() ? i + 1
1537 : constraint.weights(i));
1538 if (expression.offset() == 0 && expression.ids_size() == 1 &&
1539 expression.coefficients(0) == 1) {
1540 const VariableId var_id = expression.ids(0);
1544 sos_var_indices.push_back(variables_map_.at(var_id));
1547 if (sos_type == 2) {
1548 undeletable_variables_.insert(var_id);
1555 sos_var_indices.push_back(num_gurobi_variables_);
1556 constraint_data.slack_variables.push_back(num_gurobi_variables_);
1557 constraint_data.slack_constraints.push_back(num_gurobi_lin_cons_);
1558 std::vector<GurobiVariableIndex> slack_col_indices = {
1559 num_gurobi_variables_};
1560 std::vector<double> slack_coeffs = {-1.0};
1561 for (
int j = 0; j < expression.ids_size(); ++j) {
1562 slack_col_indices.push_back(variables_map_.at(expression.ids(j)));
1563 slack_coeffs.push_back(expression.coefficients(j));
1566 ++num_gurobi_variables_;
1569 ++num_gurobi_lin_cons_;
1571 RETURN_IF_ERROR(gurobi_->AddSos({sos_type}, {0}, sos_var_indices, weights));
1572 ++num_gurobi_sos_cons_;
1574 return absl::OkStatus();
1577 absl::Status GurobiSolver::AddNewIndicatorConstraints(
1578 const google::protobuf::Map<IndicatorConstraintId,
1579 IndicatorConstraintProto>& constraints) {
1580 for (
const auto& [
id, constraint] : constraints) {
1581 if (!constraint.has_indicator_id()) {
1585 const int num_terms = constraint.expression().ids_size();
1586 std::vector<GurobiVariableIndex> grb_ids(num_terms);
1587 for (
int k = 0; k < num_terms; ++k) {
1588 grb_ids[k] = variables_map_.at(constraint.expression().ids(k));
1592 const double lb = constraint.lower_bound();
1593 const double ub = constraint.upper_bound();
1595 <<
"lower bound for indicator constraint " <<
id <<
": "
1596 << EscapedNameForLogging(constraint.name());
1598 <<
"upper bound for indicator constraint " <<
id <<
": "
1599 << EscapedNameForLogging(constraint.name());
1602 if (lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
1605 }
else if (lb_is_grb_neg_inf && !ub_is_grb_pos_inf) {
1608 }
else if (!lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
1611 }
else if (lb == ub) {
1617 return absl::UnimplementedError(
1618 "ranged indicator constraints are not currently supported in Gurobi "
1623 variables_map_.at(constraint.indicator_id()),
1624 constraint.activate_on_zero() ? 0 : 1,
1625 grb_ids, constraint.expression().values(),
1628 IndicatorConstraintData{
1629 .constraint_index = num_gurobi_gen_cons_,
1630 .indicator_variable_id = constraint.indicator_id()});
1631 ++num_gurobi_gen_cons_;
1634 undeletable_variables_.insert(constraint.indicator_id());
1636 return absl::OkStatus();
1639 absl::Status GurobiSolver::ChangeCoefficients(
1640 const SparseDoubleMatrixProto& matrix) {
1641 const int num_coefficients = matrix.row_ids().size();
1642 std::vector<GurobiLinearConstraintIndex> row_index(num_coefficients);
1643 std::vector<GurobiVariableIndex> col_index(num_coefficients);
1644 for (
int k = 0; k < num_coefficients; ++k) {
1646 linear_constraints_map_.at(matrix.row_ids(k)).constraint_index;
1647 col_index[k] = variables_map_.at(matrix.column_ids(k));
1649 return gurobi_->ChgCoeffs(row_index, col_index, matrix.coefficients());
1652 absl::Status GurobiSolver::UpdateDoubleListAttribute(
1653 const SparseDoubleVectorProto& update,
const char* attribute_name,
1654 const IdHashMap& id_hash_map) {
1655 if (update.ids_size() == 0) {
1656 return absl::OkStatus();
1658 std::vector<int>
index;
1659 index.reserve(update.ids_size());
1660 for (
const int64_t
id : update.ids()) {
1661 index.push_back(id_hash_map.at(
id));
1663 return gurobi_->SetDoubleAttrList(attribute_name,
index, update.values());
1666 absl::Status GurobiSolver::UpdateInt32ListAttribute(
1667 const SparseInt32VectorProto& update,
const char* attribute_name,
1668 const IdHashMap& id_hash_map) {
1669 if (update.ids_size() == 0) {
1670 return absl::OkStatus();
1672 std::vector<int>
index;
1673 index.reserve(update.ids_size());
1674 for (
const int64_t
id : update.ids()) {
1675 index.push_back(id_hash_map.at(
id));
1677 return gurobi_->SetIntAttrList(attribute_name,
index, update.values());
1680 absl::Status GurobiSolver::LoadModel(
const ModelProto& input_model) {
1681 CHECK(gurobi_ !=
nullptr);
1683 TruncateName(input_model.name())));
1686 RETURN_IF_ERROR(AddNewLinearConstraints(input_model.linear_constraints()));
1688 AddNewQuadraticConstraints(input_model.quadratic_constraints()));
1695 AddNewIndicatorConstraints(input_model.indicator_constraints()));
1697 RETURN_IF_ERROR(ChangeCoefficients(input_model.linear_constraint_matrix()));
1699 const int model_sense =
1703 input_model.objective().offset()));
1706 UpdateDoubleListAttribute(input_model.objective().linear_coefficients(),
1709 input_model.objective().quadratic_coefficients()));
1710 return absl::OkStatus();
1713 absl::Status GurobiSolver::ResetQuadraticObjectiveTerms(
1714 const SparseDoubleMatrixProto& terms) {
1715 quadratic_objective_coefficients_.clear();
1717 const int num_terms = terms.row_ids().size();
1718 if (num_terms > 0) {
1719 std::vector<GurobiVariableIndex> first_var_index(num_terms);
1720 std::vector<GurobiVariableIndex> second_var_index(num_terms);
1721 for (
int k = 0; k < num_terms; ++k) {
1722 const VariableId row_id = terms.row_ids(k);
1723 const VariableId column_id = terms.column_ids(k);
1724 first_var_index[k] = variables_map_.at(row_id);
1725 second_var_index[k] = variables_map_.at(column_id);
1726 quadratic_objective_coefficients_[{row_id, column_id}] =
1727 terms.coefficients(k);
1729 RETURN_IF_ERROR(gurobi_->AddQpTerms(first_var_index, second_var_index,
1730 terms.coefficients()));
1732 return absl::OkStatus();
1735 absl::Status GurobiSolver::UpdateQuadraticObjectiveTerms(
1736 const SparseDoubleMatrixProto& terms) {
1737 CHECK(gurobi_ !=
nullptr);
1738 const int num_terms = terms.row_ids().size();
1739 if (num_terms > 0) {
1740 std::vector<GurobiVariableIndex> first_var_index(num_terms);
1741 std::vector<GurobiVariableIndex> second_var_index(num_terms);
1742 std::vector<double> coefficient_updates(num_terms);
1743 for (
int k = 0; k < num_terms; ++k) {
1744 const VariableId row_id = terms.row_ids(k);
1745 const VariableId column_id = terms.column_ids(k);
1746 first_var_index[k] = variables_map_.at(row_id);
1747 second_var_index[k] = variables_map_.at(column_id);
1748 const std::pair<VariableId, VariableId> qp_term_key(row_id, column_id);
1749 const double new_coefficient = terms.coefficients(k);
1754 coefficient_updates[k] =
1755 new_coefficient - quadratic_objective_coefficients_[qp_term_key];
1756 quadratic_objective_coefficients_[qp_term_key] = new_coefficient;
1758 RETURN_IF_ERROR(gurobi_->AddQpTerms(first_var_index, second_var_index,
1759 coefficient_updates));
1761 return absl::OkStatus();
1767 absl::Status GurobiSolver::UpdateLinearConstraints(
1768 const LinearConstraintUpdatesProto& constraints_update,
1769 std::vector<GurobiVariableIndex>& deleted_variables_index) {
1770 const SparseDoubleVectorProto& constraint_lower_bounds =
1771 constraints_update.lower_bounds();
1772 const SparseDoubleVectorProto& constraint_upper_bounds =
1773 constraints_update.upper_bounds();
1776 if (constraint_lower_bounds.ids().empty() &&
1777 constraint_upper_bounds.ids().empty()) {
1778 return absl::OkStatus();
1785 struct UpdateConstraintData {
1786 LinearConstraintId constraint_id;
1787 LinearConstraintData& source;
1788 double new_lower_bound;
1789 double new_upper_bound;
1790 UpdateConstraintData(
const LinearConstraintId
id,
1791 LinearConstraintData& reference)
1792 : constraint_id(id),
1797 const int upper_bounds_size = constraint_upper_bounds.ids().size();
1798 const int lower_bounds_size = constraint_lower_bounds.ids().size();
1799 std::vector<UpdateConstraintData> update_vector;
1800 update_vector.reserve(upper_bounds_size + lower_bounds_size);
1803 for (
int lower_index = 0, upper_index = 0;
1804 lower_index < lower_bounds_size || upper_index < upper_bounds_size;) {
1806 if (lower_index < lower_bounds_size) {
1807 lower_id = constraint_lower_bounds.ids(lower_index);
1810 if (upper_index < upper_bounds_size) {
1811 upper_id = constraint_upper_bounds.ids(upper_index);
1813 const VariableId
id =
std::min(lower_id, upper_id);
1815 UpdateConstraintData update(
id, linear_constraints_map_.at(
id));
1816 if (lower_id == upper_id) {
1817 update.new_lower_bound = constraint_lower_bounds.values(lower_index++);
1818 update.new_upper_bound = constraint_upper_bounds.values(upper_index++);
1819 }
else if (lower_id < upper_id) {
1820 update.new_lower_bound = constraint_lower_bounds.values(lower_index++);
1822 update.new_upper_bound = constraint_upper_bounds.values(upper_index++);
1824 update_vector.emplace_back(update);
1831 std::vector<char> sense_data;
1832 std::vector<double> rhs_data;
1833 std::vector<GurobiLinearConstraintIndex> rhs_index;
1835 std::vector<double> lower_bound_data;
1836 std::vector<double> upper_bound_data;
1837 std::vector<GurobiVariableIndex> bound_index;
1839 std::vector<LinearConstraintData*> new_slacks;
1841 for (UpdateConstraintData& update_data : update_vector) {
1842 const bool same_lower_bound =
1843 (update_data.source.lower_bound == update_data.new_lower_bound) ||
1846 const bool same_upper_bound =
1847 (update_data.source.upper_bound == update_data.new_upper_bound) ||
1850 if (same_upper_bound && same_lower_bound)
continue;
1853 update_data.source.lower_bound = update_data.new_lower_bound;
1854 update_data.source.upper_bound = update_data.new_upper_bound;
1855 bool delete_slack =
false;
1859 delete_slack =
true;
1860 rhs_index.emplace_back(update_data.source.constraint_index);
1861 rhs_data.emplace_back(update_data.new_upper_bound);
1863 }
else if (update_data.new_lower_bound > -
GRB_INFINITY &&
1865 delete_slack =
true;
1866 rhs_index.emplace_back(update_data.source.constraint_index);
1867 rhs_data.emplace_back(update_data.new_lower_bound);
1869 }
else if (update_data.new_lower_bound == update_data.new_upper_bound) {
1870 delete_slack =
true;
1871 rhs_index.emplace_back(update_data.source.constraint_index);
1872 rhs_data.emplace_back(update_data.new_lower_bound);
1878 if (update_data.source.slack_index != kUnspecifiedIndex) {
1879 bound_index.emplace_back(update_data.source.slack_index);
1880 lower_bound_data.emplace_back(update_data.new_lower_bound);
1881 upper_bound_data.emplace_back(update_data.new_upper_bound);
1885 rhs_index.emplace_back(update_data.source.constraint_index);
1886 rhs_data.emplace_back(0.0);
1889 update_data.source.slack_index =
1890 new_slacks.size() + num_gurobi_variables_;
1892 new_slacks.push_back(&update_data.source);
1899 if (delete_slack && update_data.source.slack_index != kUnspecifiedIndex) {
1900 deleted_variables_index.emplace_back(update_data.source.slack_index);
1901 update_data.source.slack_index = kUnspecifiedIndex;
1906 if (!rhs_index.empty()) {
1912 if (!bound_index.empty()) {
1919 if (!new_slacks.empty()) {
1922 return absl::OkStatus();
1930 void GurobiSolver::UpdateGurobiIndices(
const DeletedIndices& deleted_indices) {
1932 if (!deleted_indices.variables.empty()) {
1933 const std::vector<GurobiVariableIndex> old_to_new =
1934 IndexUpdateMap(num_gurobi_variables_, deleted_indices.variables);
1935 for (
auto& [_, grb_index] : variables_map_) {
1936 grb_index = old_to_new[grb_index];
1937 CHECK_NE(grb_index, kDeletedIndex);
1939 for (
auto& [_, lin_con_data] : linear_constraints_map_) {
1940 if (lin_con_data.slack_index != kUnspecifiedIndex) {
1941 lin_con_data.slack_index = old_to_new[lin_con_data.slack_index];
1942 CHECK_NE(lin_con_data.slack_index, kDeletedIndex);
1945 for (
auto& [_, sos1_con_data] : sos1_constraints_map_) {
1946 for (GurobiVariableIndex&
index : sos1_con_data.slack_variables) {
1948 CHECK_NE(
index, kDeletedIndex);
1951 for (
auto& [_, sos2_con_data] : sos2_constraints_map_) {
1952 for (GurobiVariableIndex&
index : sos2_con_data.slack_variables) {
1954 CHECK_NE(
index, kDeletedIndex);
1959 if (!deleted_indices.linear_constraints.empty()) {
1960 const std::vector<GurobiLinearConstraintIndex> old_to_new = IndexUpdateMap(
1961 num_gurobi_lin_cons_, deleted_indices.linear_constraints);
1962 for (
auto& [_, lin_con_data] : linear_constraints_map_) {
1963 lin_con_data.constraint_index = old_to_new[lin_con_data.constraint_index];
1964 CHECK_NE(lin_con_data.constraint_index, kDeletedIndex);
1966 for (
auto& [_, sos1_con_data] : sos1_constraints_map_) {
1967 for (GurobiLinearConstraintIndex&
index :
1968 sos1_con_data.slack_constraints) {
1970 CHECK_NE(
index, kDeletedIndex);
1973 for (
auto& [_, sos2_con_data] : sos2_constraints_map_) {
1974 for (GurobiLinearConstraintIndex&
index :
1975 sos2_con_data.slack_constraints) {
1977 CHECK_NE(
index, kDeletedIndex);
1982 if (!deleted_indices.quadratic_constraints.empty()) {
1983 const std::vector<GurobiQuadraticConstraintIndex> old_to_new =
1984 IndexUpdateMap(num_gurobi_quad_cons_,
1985 deleted_indices.quadratic_constraints);
1986 for (
auto& [_, grb_index] : quadratic_constraints_map_) {
1987 grb_index = old_to_new[grb_index];
1988 CHECK_NE(grb_index, kDeletedIndex);
1992 if (!deleted_indices.sos_constraints.empty()) {
1993 const std::vector<GurobiSosConstraintIndex> old_to_new =
1994 IndexUpdateMap(num_gurobi_sos_cons_, deleted_indices.sos_constraints);
1995 for (
auto& [_, sos1_data] : sos1_constraints_map_) {
1996 GurobiSosConstraintIndex& grb_index = sos1_data.constraint_index;
1997 grb_index = old_to_new[grb_index];
1998 CHECK_NE(grb_index, kDeletedIndex);
2000 for (
auto& [_, sos2_data] : sos2_constraints_map_) {
2001 GurobiSosConstraintIndex& grb_index = sos2_data.constraint_index;
2002 grb_index = old_to_new[grb_index];
2003 CHECK_NE(grb_index, kDeletedIndex);
2007 if (!deleted_indices.general_constraints.empty()) {
2008 const std::vector<GurobiGeneralConstraintIndex> old_to_new = IndexUpdateMap(
2009 num_gurobi_gen_cons_, deleted_indices.general_constraints);
2010 for (
auto& [_, indicator_data] : indicator_constraints_map_) {
2011 if (!indicator_data.has_value()) {
2014 GurobiGeneralConstraintIndex& grb_index =
2015 indicator_data->constraint_index;
2016 grb_index = old_to_new[grb_index];
2017 CHECK_NE(grb_index, kDeletedIndex);
2022 absl::StatusOr<bool> GurobiSolver::Update(
2023 const ModelUpdateProto& model_update) {
2024 if (!undeletable_variables_.empty()) {
2025 for (
const VariableId
id : model_update.deleted_variable_ids()) {
2026 if (undeletable_variables_.contains(
id)) {
2038 AddNewLinearConstraints(model_update.new_linear_constraints()));
2041 model_update.quadratic_constraint_updates().new_constraints()));
2044 model_update.sos1_constraint_updates().new_constraints(),
GRB_SOS_TYPE1,
2045 sos1_constraints_map_));
2047 model_update.sos2_constraint_updates().new_constraints(),
GRB_SOS_TYPE2,
2048 sos2_constraints_map_));
2050 model_update.indicator_constraint_updates().new_constraints()));
2053 ChangeCoefficients(model_update.linear_constraint_matrix_updates()));
2055 if (model_update.objective_updates().has_direction_update()) {
2056 const int model_sense = model_update.objective_updates().direction_update()
2062 if (model_update.objective_updates().has_offset_update()) {
2072 model_update.objective_updates().quadratic_coefficients()));
2075 UpdateDoubleListAttribute(model_update.variable_updates().lower_bounds(),
2079 UpdateDoubleListAttribute(model_update.variable_updates().upper_bounds(),
2082 if (model_update.variable_updates().has_integers()) {
2083 const SparseBoolVectorProto& update =
2084 model_update.variable_updates().integers();
2085 std::vector<GurobiVariableIndex>
index;
2086 index.reserve(update.ids_size());
2087 for (
const int64_t
id : update.ids()) {
2088 index.push_back(variables_map_.at(
id));
2090 std::vector<char>
value;
2091 value.reserve(update.values_size());
2092 for (
const bool val : update.values()) {
2101 const absl::flat_hash_set<VariableId> variable_ids_to_be_deleted(
2102 model_update.deleted_variable_ids().begin(),
2103 model_update.deleted_variable_ids().end());
2106 for (
auto it = quadratic_objective_coefficients_.cbegin();
2107 it != quadratic_objective_coefficients_.cend();
2109 if (variable_ids_to_be_deleted.contains(it->first.first) ||
2110 variable_ids_to_be_deleted.contains(it->first.second)) {
2111 quadratic_objective_coefficients_.erase(it++);
2118 DeletedIndices deleted_indices;
2121 model_update.linear_constraint_updates(), deleted_indices.variables));
2123 for (
const VariableId
id : model_update.deleted_variable_ids()) {
2124 deleted_indices.variables.emplace_back(variables_map_.at(
id));
2125 variables_map_.erase(
id);
2128 for (
const LinearConstraintId
id :
2129 model_update.deleted_linear_constraint_ids()) {
2130 LinearConstraintData& constraint_data = linear_constraints_map_.at(
id);
2131 deleted_indices.linear_constraints.push_back(
2132 constraint_data.constraint_index);
2133 if (constraint_data.slack_index != kUnspecifiedIndex) {
2134 deleted_indices.variables.push_back(constraint_data.slack_index);
2135 constraint_data.slack_index = kUnspecifiedIndex;
2137 linear_constraints_map_.erase(
id);
2140 for (
const QuadraticConstraintId
id :
2141 model_update.quadratic_constraint_updates().deleted_constraint_ids()) {
2142 const GurobiQuadraticConstraintIndex grb_index =
2143 quadratic_constraints_map_.at(
id);
2144 deleted_indices.quadratic_constraints.push_back(grb_index);
2145 quadratic_constraints_map_.erase(
id);
2148 const auto sos_updater = [&](
const SosConstraintData& sos_constraint) {
2149 deleted_indices.sos_constraints.push_back(sos_constraint.constraint_index);
2150 for (
const GurobiVariableIndex
index : sos_constraint.slack_variables) {
2151 deleted_indices.variables.push_back(
index);
2153 for (
const GurobiLinearConstraintIndex
index :
2154 sos_constraint.slack_constraints) {
2155 deleted_indices.linear_constraints.push_back(
index);
2158 for (
const Sos1ConstraintId
id :
2159 model_update.sos1_constraint_updates().deleted_constraint_ids()) {
2160 sos_updater(sos1_constraints_map_.at(
id));
2161 sos1_constraints_map_.erase(
id);
2164 for (
const Sos2ConstraintId
id :
2165 model_update.sos2_constraint_updates().deleted_constraint_ids()) {
2166 sos_updater(sos2_constraints_map_.at(
id));
2167 sos2_constraints_map_.erase(
id);
2170 for (
const IndicatorConstraintId
id :
2171 model_update.indicator_constraint_updates().deleted_constraint_ids()) {
2173 const auto it = indicator_constraints_map_.find(
id);
2174 CHECK(it != indicator_constraints_map_.end()) <<
"id: " << id;
2175 if (it->second.has_value()) {
2176 deleted_indices.general_constraints.push_back(
2177 it->second->constraint_index);
2179 indicator_constraints_map_.erase(it);
2182 UpdateGurobiIndices(deleted_indices);
2189 if (!deleted_indices.linear_constraints.empty()) {
2190 RETURN_IF_ERROR(gurobi_->DelConstrs(deleted_indices.linear_constraints));
2191 num_gurobi_lin_cons_ -= deleted_indices.linear_constraints.size();
2194 if (!deleted_indices.quadratic_constraints.empty()) {
2196 gurobi_->DelQConstrs(deleted_indices.quadratic_constraints));
2197 num_gurobi_quad_cons_ -= deleted_indices.quadratic_constraints.size();
2200 if (!deleted_indices.sos_constraints.empty()) {
2204 if (!deleted_indices.general_constraints.empty()) {
2206 gurobi_->DelGenConstrs(deleted_indices.general_constraints));
2209 if (!deleted_indices.variables.empty()) {
2211 num_gurobi_variables_ -= deleted_indices.variables.size();
2220 absl::StatusOr<std::unique_ptr<GurobiSolver>> GurobiSolver::New(
2223 return absl::InvalidArgumentError(
"Gurobi is not correctly installed.");
2228 GurobiFromInitArgs(init_args));
2231 return gurobi_solver;
2234 absl::StatusOr<std::unique_ptr<GurobiSolver::GurobiCallbackData>>
2235 GurobiSolver::RegisterCallback(
const CallbackRegistrationProto& registration,
2238 const absl::Time
start,
2240 const absl::flat_hash_set<CallbackEventProto> events =
EventSet(registration);
2251 registration, is_mip ? SupportedMIPEvents() : SupportedLPEvents()))
2252 <<
"for a " << (is_mip ?
"MIP" :
"LP") <<
" model";
2255 if (message_cb !=
nullptr) {
2260 if (registration.add_cuts() || registration.add_lazy_constraints()) {
2265 if (registration.add_lazy_constraints()) {
2270 return std::make_unique<GurobiCallbackData>(
2271 GurobiCallbackInput{
2273 .message_cb = message_cb,
2274 .variable_ids = variables_map_,
2275 .num_gurobi_vars = num_gurobi_variables_,
2277 .mip_solution_filter = registration.mip_solution_filter(),
2278 .mip_node_filter = registration.mip_node_filter(),
2283 absl::StatusOr<InvertedBounds> GurobiSolver::ListInvertedBounds()
const {
2284 InvertedBounds inverted_bounds;
2287 const std::vector<double> var_lbs,
2290 const std::vector<double> var_ubs,
2292 for (
const auto& [
id,
index] : variables_map_) {
2294 inverted_bounds.variables.push_back(
id);
2298 for (
const auto& [
id, cstr_data] : linear_constraints_map_) {
2299 if (cstr_data.lower_bound > cstr_data.upper_bound) {
2300 inverted_bounds.linear_constraints.push_back(
id);
2305 std::sort(inverted_bounds.variables.begin(), inverted_bounds.variables.end());
2306 std::sort(inverted_bounds.linear_constraints.begin(),
2307 inverted_bounds.linear_constraints.end());
2308 return inverted_bounds;
2311 absl::StatusOr<InvalidIndicators> GurobiSolver::ListInvalidIndicators()
const {
2312 InvalidIndicators invalid_indicators;
2313 for (
const auto& [constraint_id, indicator_data] :
2314 indicator_constraints_map_) {
2315 if (!indicator_data.has_value()) {
2318 const int64_t indicator_id = indicator_data->indicator_variable_id;
2319 const GurobiVariableIndex variable_index = variables_map_.at(indicator_id);
2325 const char var_type,
2328 (var_type ==
GRB_INTEGER && var_lb >= 0.0 && var_ub <= 1.0))) {
2329 invalid_indicators.invalid_indicators.push_back(
2330 {.variable = indicator_id, .constraint = constraint_id});
2334 invalid_indicators.Sort();
2335 return invalid_indicators;
2340 const ModelSolveParametersProto& model_parameters,
2342 const CallbackRegistrationProto& callback_registration,
const Callback cb,
2344 const absl::Time
start = absl::Now();
2352 std::unique_ptr<SolveInterrupter> local_interrupter;
2353 if (cb !=
nullptr || interrupter !=
nullptr) {
2354 local_interrupter = std::make_unique<SolveInterrupter>();
2357 local_interrupter.get(), [&]() {
2365 gurobi_->Terminate();
2375 interrupter, [&]() { local_interrupter->
Interrupt(); });
2381 if (model_parameters.has_initial_basis()) {
2385 model_parameters.solution_hints_size()));
2386 for (
int i = 0; i < model_parameters.solution_hints_size(); ++i) {
2389 model_parameters.solution_hints(i).variable_values(),
2393 UpdateInt32ListAttribute(model_parameters.branching_priorities(),
2400 std::unique_ptr<GurobiCallbackData> gurobi_cb_data;
2401 if (cb !=
nullptr || local_interrupter !=
nullptr || message_cb !=
nullptr) {
2403 RegisterCallback(callback_registration, cb, message_cb,
2404 start, local_interrupter.get()));
2405 grb_cb = [&gurobi_cb_data](
2408 gurobi_cb_data->message_callback_data,
2409 gurobi_cb_data->local_interrupter);
2416 ListInvertedBounds());
2425 ListInvalidIndicators());
2433 if (gurobi_cb_data !=
nullptr) {
2435 gurobi_cb_data->message_callback_data);
2439 ExtractSolveResultProto(
start, model_parameters));
2445 return solve_result;
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
std::function< absl::Status(const CallbackContext &)> Callback
static absl::StatusOr< std::unique_ptr< Gurobi > > NewWithSharedPrimaryEnv(GRBenv *primary_env)
static absl::StatusOr< std::unique_ptr< Gurobi > > New(GRBenvUniquePtr primary_env=nullptr)
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
ModelSharedTimeLimit * time_limit
#define GRB_INT_PAR_BARITERLIMIT
#define GRB_INT_PAR_LOGTOCONSOLE
#define GRB_INT_ATTR_BRANCHPRIORITY
#define GRB_DBL_ATTR_START
#define GRB_DBL_PAR_MIPGAP
#define GRB_DBL_PAR_FEASIBILITYTOL
#define GRB_SOLUTION_LIMIT
#define GRB_INT_PAR_SOLUTIONLIMIT
#define GRB_NONBASIC_LOWER
#define GRB_INT_ATTR_MODELSENSE
#define GRB_INT_ATTR_VBASIS
#define GRB_GREATER_EQUAL
#define GRB_DBL_ATTR_NODECOUNT
#define GRB_INT_PAR_PRESOLVE
#define GRB_DBL_PAR_MIPGAPABS
#define GRB_DBL_ATTR_ITERCOUNT
#define GRB_INT_PAR_THREADS
#define GRB_DBL_ATTR_BOUND_SVIO
#define GRB_DBL_PAR_CUTOFF
#define GRB_DBL_PAR_ITERATIONLIMIT
#define GRB_INT_PAR_METHOD
#define GRB_INT_ATTR_IS_QP
#define GRB_DBL_ATTR_OBJVAL
#define GRB_INT_PAR_LAZYCONSTRAINTS
#define GRB_INT_PAR_SCALEFLAG
#define GRB_DBL_PAR_HEURISTICS
#define GRB_METHOD_BARRIER
#define GRB_INT_ATTR_IS_MIP
#define GRB_DBL_ATTR_CONSTR_RESIDUAL
#define GRB_INT_PAR_POOLSOLUTIONS
#define GRB_CHAR_ATTR_VTYPE
#define GRB_INT_ATTR_NUMSTART
#define GRB_DBL_ATTR_BOUND_VIO
#define GRB_NONBASIC_UPPER
#define GRB_DBL_ATTR_OBJCON
#define GRB_DBL_PAR_BESTBDSTOP
#define GRB_STR_ATTR_MODELNAME
#define GRB_DBL_ATTR_CONSTR_VIO
#define GRB_DBL_ATTR_CONSTR_SRESIDUAL
#define GRB_DBL_ATTR_CONSTR_SVIO
#define GRB_INT_ATTR_IS_QCP
#define GRB_DBL_PAR_BESTOBJSTOP
#define GRB_INT_PAR_STARTNUMBER
#define GRB_CHAR_ATTR_SENSE
#define GRB_INT_ATTR_CBASIS
#define GRB_INT_ATTR_BARITERCOUNT
#define GRB_INT_ATTR_STATUS
#define GRB_DBL_ATTR_FARKASDUAL
#define GRB_DBL_ATTR_POOLOBJVAL
#define GRB_INT_PAR_SOLUTIONNUMBER
#define GRB_ITERATION_LIMIT
#define GRB_INT_ATTR_SOLCOUNT
#define GRB_METHOD_PRIMAL
#define GRB_DBL_PAR_TIMELIMIT
#define GRB_DBL_PAR_NODELIMIT
#define GRB_USER_OBJ_LIMIT
#define GRB_DBL_ATTR_UNBDRAY
#define GRB_DBL_ATTR_OBJBOUND
#define GRB_INT_PAR_PRECRUSH
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
auto & InsertKeyOrDie(Collection *const collection, const typename Collection::value_type::first_type &key)
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto ®istration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
void GurobiCallbackImplFlush(const GurobiCallbackInput &callback_input, MessageCallbackData &message_callback_data)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
absl::Status GurobiCallbackImpl(const Gurobi::CallbackContext &context, const GurobiCallbackInput &callback_input, MessageCallbackData &message_callback_data, SolveInterrupter *const local_interrupter)
bool UpdateIsSupported(const ModelUpdateProto &update, const SupportedProblemStructures &support_menu)
TerminationProto TerminateForLimit(const LimitProto limit, const bool feasible, const absl::string_view detail)
absl::StatusOr< GRBenvUniquePtr > NewPrimaryEnvironment(std::optional< GurobiInitializerProto::ISVKey > proto_isv_key)
std::vector< bool > EventToGurobiWhere(const absl::flat_hash_set< CallbackEventProto > &events)
std::function< CallbackResult(const CallbackData &)> Callback
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
std::function< void(const std::vector< std::string > &)> MessageCallback
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
std::unique_ptr< GRBenv, GurobiFreeEnv > GRBenvUniquePtr
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()
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
StatusBuilder InvalidArgumentErrorBuilder()
std::vector< double > lower_bounds
std::vector< double > upper_bounds
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)
absl::Status ToStatus() const
absl::Status ToStatus() const