28#include "absl/algorithm/container.h"
29#include "absl/base/nullability.h"
30#include "absl/container/flat_hash_map.h"
31#include "absl/container/flat_hash_set.h"
32#include "absl/log/check.h"
33#include "absl/log/log.h"
34#include "absl/memory/memory.h"
35#include "absl/status/status.h"
36#include "absl/status/statusor.h"
37#include "absl/strings/escaping.h"
38#include "absl/strings/str_cat.h"
39#include "absl/strings/str_join.h"
40#include "absl/strings/string_view.h"
41#include "absl/time/clock.h"
42#include "absl/time/time.h"
43#include "absl/types/span.h"
44#include "google/protobuf/repeated_ptr_field.h"
88absl::StatusOr<std::unique_ptr<Gurobi>> GurobiFromInitArgs(
91 return absl::FailedPreconditionError(
92 "the Gurobi library is not compatible with any sanitizer (MSAN, ASAN "
99 init_args.non_streamable !=
nullptr
102 std::unique_ptr<Gurobi> gurobi;
103 if (non_streamable_args !=
nullptr &&
104 non_streamable_args->primary_env !=
nullptr) {
106 }
else if (init_args.streamable.has_gurobi() &&
107 init_args.streamable.gurobi().has_isv_key()) {
145 LOG(FATAL) <<
"Unexpected invalid initial_basis.";
152absl::StatusOr<GurobiParametersProto> MergeParameters(
155 const bool is_multi_objective_mode) {
160 merged_parameters.add_parameters();
162 parameter->set_value(solve_parameters.enable_output() ?
"1" :
"0");
166 absl::Duration time_limit = absl::InfiniteDuration();
167 if (solve_parameters.has_time_limit()) {
169 solve_parameters.time_limit()));
174 if (!is_multi_objective_mode &&
175 model_parameters.primary_objective_parameters().has_time_limit()) {
177 const absl::Duration primary_objective_time_limit,
179 model_parameters.primary_objective_parameters().time_limit()));
180 time_limit = std::min(time_limit, primary_objective_time_limit);
182 if (time_limit < absl::InfiniteDuration()) {
184 merged_parameters.add_parameters();
186 parameter->set_value(absl::StrCat(absl::ToDoubleSeconds(time_limit)));
190 if (solve_parameters.has_node_limit()) {
192 merged_parameters.add_parameters();
194 parameter->set_value(absl::StrCat(solve_parameters.node_limit()));
197 if (solve_parameters.has_threads()) {
198 const int threads = solve_parameters.threads();
200 merged_parameters.add_parameters();
202 parameter->set_value(absl::StrCat(threads));
205 if (solve_parameters.has_absolute_gap_tolerance()) {
206 const double absolute_gap_tolerance =
207 solve_parameters.absolute_gap_tolerance();
209 merged_parameters.add_parameters();
211 parameter->set_value(absl::StrCat(absolute_gap_tolerance));
214 if (solve_parameters.has_relative_gap_tolerance()) {
215 const double relative_gap_tolerance =
216 solve_parameters.relative_gap_tolerance();
218 merged_parameters.add_parameters();
220 parameter->set_value(absl::StrCat(relative_gap_tolerance));
223 if (solve_parameters.has_cutoff_limit()) {
225 merged_parameters.add_parameters();
227 parameter->set_value(absl::StrCat(solve_parameters.cutoff_limit()));
230 if (solve_parameters.has_objective_limit()) {
232 return absl::InvalidArgumentError(
233 "objective_limit is only supported for Gurobi on MIP models");
236 merged_parameters.add_parameters();
238 parameter->set_value(absl::StrCat(solve_parameters.objective_limit()));
241 if (solve_parameters.has_best_bound_limit()) {
243 return absl::InvalidArgumentError(
244 "best_bound_limit is only supported for Gurobi on MIP models");
247 merged_parameters.add_parameters();
249 parameter->set_value(absl::StrCat(solve_parameters.best_bound_limit()));
252 if (solve_parameters.has_solution_limit()) {
254 merged_parameters.add_parameters();
256 parameter->set_value(absl::StrCat(solve_parameters.solution_limit()));
259 if (solve_parameters.has_random_seed()) {
260 const int random_seed =
261 std::min(
GRB_MAXINT, std::max(solve_parameters.random_seed(), 0));
263 merged_parameters.add_parameters();
265 parameter->set_value(absl::StrCat(random_seed));
268 if (solve_parameters.has_solution_pool_size()) {
270 merged_parameters.add_parameters();
272 solution_pool_size->set_value(
273 absl::StrCat(solve_parameters.solution_pool_size()));
278 merged_parameters.add_parameters();
280 switch (solve_parameters.lp_algorithm()) {
291 return absl::InvalidArgumentError(
292 "lp_algorithm FIRST_ORDER is not supported for gurobi");
294 LOG(FATAL) <<
"LPAlgorithm: "
296 <<
" unknown, error setting Gurobi parameters";
302 merged_parameters.add_parameters();
304 switch (solve_parameters.scaling()) {
306 parameter->set_value(absl::StrCat(0));
310 parameter->set_value(absl::StrCat(1));
313 parameter->set_value(absl::StrCat(2));
316 parameter->set_value(absl::StrCat(3));
319 LOG(FATAL) <<
"Scaling emphasis: "
321 <<
" unknown, error setting Gurobi parameters";
327 merged_parameters.add_parameters();
329 switch (solve_parameters.cuts()) {
331 parameter->set_value(absl::StrCat(0));
335 parameter->set_value(absl::StrCat(1));
338 parameter->set_value(absl::StrCat(2));
341 parameter->set_value(absl::StrCat(3));
344 LOG(FATAL) <<
"Cuts emphasis: "
346 <<
" unknown, error setting Gurobi parameters";
352 merged_parameters.add_parameters();
354 switch (solve_parameters.heuristics()) {
356 parameter->set_value(absl::StrCat(0.0));
359 parameter->set_value(absl::StrCat(0.025));
364 parameter->set_value(absl::StrCat(0.05));
367 parameter->set_value(absl::StrCat(0.1));
370 parameter->set_value(absl::StrCat(0.2));
373 LOG(FATAL) <<
"Heuristics emphasis: "
375 <<
" unknown, error setting Gurobi parameters";
381 merged_parameters.add_parameters();
383 switch (solve_parameters.presolve()) {
385 parameter->set_value(absl::StrCat(0));
389 parameter->set_value(absl::StrCat(1));
393 parameter->set_value(absl::StrCat(2));
396 LOG(FATAL) <<
"Presolve emphasis: "
398 <<
" unknown, error setting Gurobi parameters";
402 if (solve_parameters.has_iteration_limit()) {
404 merged_parameters.add_parameters();
406 iterationlimit->set_value(absl::StrCat(solve_parameters.iteration_limit()));
408 merged_parameters.add_parameters();
410 double val = std::min<double>(std::numeric_limits<int32_t>::max(),
411 solve_parameters.iteration_limit());
412 bariterlimit->set_value(absl::StrCat(val));
416 solve_parameters.gurobi().parameters()) {
417 *merged_parameters.add_parameters() = parameter;
420 return merged_parameters;
423absl::StatusOr<int64_t> SafeInt64FromDouble(
const double d) {
424 const int64_t result =
static_cast<int64_t
>(d);
425 if (
static_cast<double>(result) != d) {
426 return absl::InternalError(
427 absl::StrCat(
"Expected double ", d,
" to contain an int64_t."));
432const absl::flat_hash_set<CallbackEventProto>& SupportedMIPEvents() {
433 static const auto*
const kEvents =
434 new absl::flat_hash_set<CallbackEventProto>({
448const absl::flat_hash_set<CallbackEventProto>& SupportedLPEvents() {
449 static const auto*
const kEvents =
450 new absl::flat_hash_set<CallbackEventProto>({
460constexpr std::size_t kMaxNameSize = 255;
463std::string TruncateName(
const std::string_view original_name) {
465 original_name.substr(0, std::min(kMaxNameSize, original_name.size())));
469std::vector<std::string> TruncateNames(
470 const google::protobuf::RepeatedPtrField<std::string>& original_names) {
471 std::vector<std::string> result;
472 result.reserve(original_names.size());
473 for (
const std::string& original_name : original_names) {
474 result.push_back(TruncateName(original_name));
479absl::Status SafeGurobiDouble(
const double d) {
482 <<
"finite value: " << d <<
" will be treated as infinite by Gurobi";
484 return absl::OkStatus();
487std::string EscapedNameForLogging(
const absl::string_view name) {
488 return absl::StrCat(
"\"", absl::Utf8SafeCEscape(name),
"\"");
491constexpr int kDeletedIndex = -1;
492constexpr int kUnsetIndex = -2;
499std::vector<int> IndexUpdateMap(
const int size_before_delete,
500 absl::Span<const int> deletes) {
501 std::vector<int> result(size_before_delete, kUnsetIndex);
502 for (
const int del : deletes) {
503 result[del] = kDeletedIndex;
506 for (
int& r : result) {
507 if (r != kDeletedIndex) {
511 CHECK_GT(r, kUnsetIndex);
516absl::StatusOr<bool> GetIntAttrElementAsBool(
Gurobi& gurobi,
517 const char*
const name,
520 const bool cast_value(value);
521 if (
static_cast<int>(cast_value) != value) {
523 <<
"Gurobi unexpectedly returned non-boolean value for " << name
531 OrAccumulator() =
default;
534 absl::Status Or(absl::StatusOr<bool> update) {
537 return absl::OkStatus();
539 bool value()
const {
return value_; }
547absl::Status AddMapEntryIfPresent(
548 const int64_t map_id,
549 absl::StatusOr<std::optional<ModelSubsetProto::Bounds>> maybe_value,
550 google::protobuf::Map<int64_t, ModelSubsetProto::Bounds>& target) {
552 if (maybe_value->has_value()) {
553 target[map_id] = **std::move(maybe_value);
555 return absl::OkStatus();
560absl::Status AppendEntryIfTrue(
561 const int64_t
id, absl::StatusOr<bool> should_append,
562 google::protobuf::RepeatedField<int64_t>& target) {
564 if (*should_append) {
567 return absl::OkStatus();
572GurobiSolver::GurobiSolver(std::unique_ptr<Gurobi> g_gurobi)
573 : gurobi_(std::move(g_gurobi)) {}
575GurobiSolver::GurobiModelElements
576GurobiSolver::LinearConstraintData::DependentElements()
const {
577 GurobiModelElements elements;
578 CHECK_NE(constraint_index, kUnspecifiedConstraint);
579 elements.linear_constraints.push_back(constraint_index);
580 if (slack_index != kUnspecifiedIndex) {
581 elements.variables.push_back(slack_index);
586GurobiSolver::GurobiModelElements
587GurobiSolver::SecondOrderConeConstraintData::DependentElements()
const {
588 const auto index_is_valid = [](
const auto index) {
return index >= 0; };
589 CHECK(absl::c_all_of(slack_variables, index_is_valid));
590 CHECK(absl::c_all_of(slack_constraints, index_is_valid));
591 CHECK_NE(constraint_index, kUnspecifiedConstraint);
592 GurobiModelElements elements{.variables = slack_variables,
593 .linear_constraints = slack_constraints};
594 elements.quadratic_constraints.push_back(constraint_index);
598GurobiSolver::GurobiModelElements
599GurobiSolver::SosConstraintData::DependentElements()
const {
600 const auto index_is_valid = [](
const auto index) {
return index >= 0; };
601 CHECK(absl::c_all_of(slack_variables, index_is_valid));
602 CHECK(absl::c_all_of(slack_constraints, index_is_valid));
603 CHECK_NE(constraint_index, kUnspecifiedConstraint);
604 GurobiModelElements elements{.variables = slack_variables,
605 .linear_constraints = slack_constraints};
606 elements.sos_constraints.push_back(constraint_index);
610absl::StatusOr<TerminationProto> GurobiSolver::ConvertTerminationReason(
611 const int gurobi_status,
const SolutionClaims solution_claims,
612 const double best_primal_bound,
const double best_dual_bound) {
614 switch (gurobi_status) {
623 is_maximize, solution_claims.dual_feasible_solution_exists
624 ? FEASIBILITY_STATUS_FEASIBLE
625 : FEASIBILITY_STATUS_UNDETERMINED);
629 if (solution_claims.primal_feasible_solution_exists) {
634 FEASIBILITY_STATUS_INFEASIBLE,
635 "Gurobi status GRB_UNBOUNDED");
639 FEASIBILITY_STATUS_UNDETERMINED,
640 "Gurobi status GRB_UNBOUNDED");
645 LIMIT_ITERATION, best_primal_bound, best_dual_bound,
646 solution_claims.dual_feasible_solution_exists);
649 LIMIT_NODE, best_primal_bound, best_dual_bound,
650 solution_claims.dual_feasible_solution_exists);
653 LIMIT_TIME, best_primal_bound, best_dual_bound,
654 solution_claims.dual_feasible_solution_exists);
657 LIMIT_SOLUTION, best_primal_bound, best_dual_bound,
658 solution_claims.dual_feasible_solution_exists);
661 LIMIT_INTERRUPTED, best_primal_bound, best_dual_bound,
662 solution_claims.dual_feasible_solution_exists);
665 TERMINATION_REASON_NUMERICAL_ERROR);
667 if (is_multi_objective_mode()) {
672 LIMIT_TIME, best_primal_bound, best_dual_bound,
673 solution_claims.dual_feasible_solution_exists,
674 "Gurobi returned GRB_SUBOPTIMAL for a multi-objective model, which "
675 "indicates that one or more objectives hit their per-objective "
686 LIMIT_OBJECTIVE, best_primal_bound, best_dual_bound,
687 solution_claims.dual_feasible_solution_exists);
689 return absl::InternalError(
690 "Error creating termination reason, unexpected gurobi status code "
693 return absl::InternalError(
694 "Error creating termination reason, unexpected gurobi status code "
697 return absl::InternalError(absl::StrCat(
698 "Missing Gurobi optimization status code case: ", gurobi_status));
702absl::StatusOr<bool> GurobiSolver::IsMaximize()
const {
708absl::StatusOr<bool> GurobiSolver::IsMIP()
const {
710 return static_cast<bool>(is_mip);
714absl::StatusOr<bool> GurobiSolver::IsQP()
const {
716 return static_cast<bool>(is_qp);
719absl::StatusOr<bool> GurobiSolver::IsQCP()
const {
721 return static_cast<bool>(is_qcp);
727void GurobiSolver::GurobiVectorToSparseDoubleVector(
728 const absl::Span<const double> gurobi_values,
const T& map,
729 SparseDoubleVectorProto& result,
730 const SparseVectorFilterProto& filter)
const {
731 SparseVectorFilterPredicate predicate(filter);
732 for (
auto [
id, gurobi_data] : map) {
733 const double value = gurobi_values[get_model_index(gurobi_data)];
734 if (predicate.AcceptsAndUpdate(
id, value)) {
736 result.add_values(value);
741absl::Status GurobiSolver::SetGurobiBasis(
const BasisProto& basis) {
742 std::vector<int> gurobi_variable_basis_status(num_gurobi_variables_);
743 for (
const auto [
id, value] :
MakeView(basis.variable_status())) {
744 gurobi_variable_basis_status[variables_map_.at(
id)] =
748 std::vector<int> gurobi_constraint_basis_status;
749 gurobi_constraint_basis_status.reserve(num_gurobi_lin_cons_);
750 for (
const auto [
id, value] :
MakeView(basis.constraint_status())) {
751 const LinearConstraintData& constraint_data =
752 linear_constraints_map_.at(
id);
754 if (constraint_data.slack_index == kUnspecifiedIndex) {
755 if (value == BASIS_STATUS_BASIC) {
756 gurobi_constraint_basis_status.push_back(kGrbBasicConstraint);
758 gurobi_constraint_basis_status.push_back(kGrbNonBasicConstraint);
761 }
else if (value == BASIS_STATUS_BASIC) {
765 gurobi_variable_basis_status[constraint_data.slack_index] =
GRB_BASIC;
766 gurobi_constraint_basis_status.push_back(kGrbNonBasicConstraint);
768 gurobi_variable_basis_status[constraint_data.slack_index] =
770 gurobi_constraint_basis_status.push_back(kGrbNonBasicConstraint);
774 gurobi_variable_basis_status));
776 gurobi_constraint_basis_status));
777 return absl::OkStatus();
780absl::StatusOr<BasisProto> GurobiSolver::GetGurobiBasis() {
783 const std::vector<int> gurobi_variable_basis_status,
786 for (
auto [variable_id, gurobi_variable_index] : variables_map_) {
787 basis.mutable_variable_status()->add_ids(variable_id);
789 gurobi_variable_basis_status[gurobi_variable_index]);
790 if (variable_status == BASIS_STATUS_UNSPECIFIED) {
791 return absl::InternalError(
792 absl::StrCat(
"Invalid Gurobi variable basis status: ",
793 gurobi_variable_basis_status[gurobi_variable_index]));
795 basis.mutable_variable_status()->add_values(variable_status);
799 const std::vector<int> gurobi_constraint_basis_status,
801 for (
auto [constraint_id, gurobi_data] : linear_constraints_map_) {
802 basis.mutable_constraint_status()->add_ids(constraint_id);
803 const int gurobi_constraint_status =
804 gurobi_constraint_basis_status[gurobi_data.constraint_index];
805 if ((gurobi_constraint_status != kGrbBasicConstraint) &&
806 (gurobi_constraint_status != kGrbNonBasicConstraint)) {
807 return absl::InternalError(
808 absl::StrCat(
"Invalid Gurobi constraint basis status: ",
809 gurobi_constraint_status));
814 if (gurobi_constraint_status == kGrbBasicConstraint) {
815 basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
817 basis.mutable_constraint_status()->add_values(
818 BASIS_STATUS_AT_UPPER_BOUND);
823 if (gurobi_constraint_status == kGrbBasicConstraint) {
824 basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
826 basis.mutable_constraint_status()->add_values(
827 BASIS_STATUS_AT_LOWER_BOUND);
830 }
else if (gurobi_data.lower_bound == gurobi_data.upper_bound) {
831 if (gurobi_constraint_status == kGrbBasicConstraint) {
832 basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
836 basis.mutable_constraint_status()->add_values(BASIS_STATUS_FIXED_VALUE);
841 gurobi_variable_basis_status[gurobi_data.slack_index]);
842 if (slack_status == BASIS_STATUS_UNSPECIFIED) {
843 return absl::InternalError(absl::StrCat(
844 "Invalid Gurobi slack variable basis status: ", slack_status));
846 if ((gurobi_constraint_status == kGrbBasicConstraint) ||
847 (slack_status == BASIS_STATUS_BASIC)) {
848 basis.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
850 basis.mutable_constraint_status()->add_values(slack_status);
857absl::StatusOr<DualRayProto> GurobiSolver::GetGurobiDualRay(
858 const SparseVectorFilterProto& linear_constraints_filter,
859 const SparseVectorFilterProto& variables_filter,
const bool is_maximize) {
863 num_gurobi_lin_cons_));
865 DualRayProto dual_ray;
869 SparseVectorFilterPredicate predicate(linear_constraints_filter);
870 for (
auto [constraint_id, gurobi_data] : linear_constraints_map_) {
872 const double value = -farkas_dual[gurobi_data.constraint_index];
873 if (predicate.AcceptsAndUpdate(constraint_id, value)) {
874 dual_ray.mutable_dual_values()->add_ids(constraint_id);
876 dual_ray.mutable_dual_values()->add_values(-value);
878 dual_ray.mutable_dual_values()->add_values(value);
886 SparseVectorFilterPredicate predicate(variables_filter);
887 for (
auto [var_id, gurobi_variable_index] : variables_map_) {
890 double reduced_cost_value = 0.0;
892 gurobi_->GetVars(gurobi_variable_index, 1));
893 for (
int i = 0;
i < column.inds.size(); ++
i) {
894 reduced_cost_value += farkas_dual[column.inds[
i]] * column.vals[
i];
896 if (predicate.AcceptsAndUpdate(var_id, reduced_cost_value)) {
897 dual_ray.mutable_reduced_costs()->add_ids(var_id);
899 dual_ray.mutable_reduced_costs()->add_values(-reduced_cost_value);
901 dual_ray.mutable_reduced_costs()->add_values(reduced_cost_value);
909absl::StatusOr<SolveResultProto> GurobiSolver::ExtractSolveResultProto(
910 const absl::Time start,
const ModelSolveParametersProto& model_parameters) {
911 SolveResultProto result;
914 SolutionClaims solution_claims;
916 GetSolutions(model_parameters));
924 solution_and_claims.solutions.clear();
925 solution_and_claims.solution_claims.primal_feasible_solution_exists =
false;
926 solution_and_claims.solution_claims.dual_feasible_solution_exists =
true;
929 GetBestPrimalBound(solution_and_claims.solutions));
931 GetBestDualBound(solution_and_claims.solutions));
932 solution_claims = solution_and_claims.solution_claims;
937 for (SolutionProto& solution : solution_and_claims.solutions) {
938 *result.add_solutions() = std::move(solution);
942 *result.mutable_termination(),
943 ConvertTerminationReason(grb_termination, solution_claims,
944 best_primal_bound, best_dual_bound));
950absl::StatusOr<bool> GurobiSolver::AnyElementInIIS(
951 const GurobiModelElements& grb_elements) {
952 OrAccumulator any_in_iis;
953 for (
const GurobiVariableIndex grb_index : grb_elements.variables) {
956 for (
const GurobiLinearConstraintIndex grb_index :
957 grb_elements.linear_constraints) {
961 for (
const GurobiQuadraticConstraintIndex grb_index :
962 grb_elements.quadratic_constraints) {
966 for (
const GurobiSosConstraintIndex grb_index :
967 grb_elements.sos_constraints) {
971 for (
const GurobiGeneralConstraintIndex grb_index :
972 grb_elements.general_constraints) {
976 return any_in_iis.value();
981absl::StatusOr<std::optional<ModelSubsetProto::Bounds>>
982GurobiSolver::VariableBoundsInIIS(
const GurobiVariableIndex grb_index) {
984 const bool var_lb_is_in_iis,
987 const bool var_ub_is_in_iis,
989 if (!var_lb_is_in_iis && !var_ub_is_in_iis) {
992 ModelSubsetProto::Bounds bounds;
993 bounds.set_lower(var_lb_is_in_iis);
994 bounds.set_upper(var_ub_is_in_iis);
998absl::StatusOr<bool> GurobiSolver::VariableInIIS(
999 const GurobiVariableIndex grb_index) {
1001 VariableBoundsInIIS(grb_index));
1002 return bounds.has_value();
1005absl::StatusOr<std::optional<ModelSubsetProto::Bounds>>
1006GurobiSolver::LinearConstraintInIIS(
const LinearConstraintData& grb_data) {
1013 AnyElementInIIS(grb_data.DependentElements()));
1014 if (!constr_in_iis) {
1015 return std::nullopt;
1017 ModelSubsetProto::Bounds result;
1018 result.set_lower(grb_data.lower_bound != -kInf);
1019 result.set_upper(grb_data.upper_bound != kInf);
1023absl::StatusOr<std::optional<ModelSubsetProto::Bounds>>
1024GurobiSolver::QuadraticConstraintInIIS(
1025 const GurobiQuadraticConstraintIndex grb_index) {
1027 const bool constr_in_iis,
1029 if (!constr_in_iis) {
1030 return std::nullopt;
1033 const char constr_sense,
1035 ModelSubsetProto::Bounds result;
1036 result.set_lower(constr_sense ==
GRB_EQUAL ||
1042absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
1043GurobiSolver::ExtractComputeInfeasibleSubsystemResultProto(
1044 const bool proven_infeasible) {
1045 ComputeInfeasibleSubsystemResultProto result;
1046 if (!proven_infeasible) {
1047 result.set_feasibility(FEASIBILITY_STATUS_UNDETERMINED);
1050 result.set_feasibility(FEASIBILITY_STATUS_INFEASIBLE);
1054 result.set_is_minimal(is_minimal);
1056 for (
const auto [
id, grb_index] : variables_map_) {
1058 id, VariableBoundsInIIS(grb_index),
1059 *result.mutable_infeasible_subsystem()->mutable_variable_bounds()));
1064 const char var_type,
1067 result.mutable_infeasible_subsystem()->add_variable_integrality(
1073 for (
const auto [
id, grb_data] : linear_constraints_map_) {
1075 id, LinearConstraintInIIS(grb_data),
1076 *result.mutable_infeasible_subsystem()->mutable_linear_constraints()));
1079 for (
const auto [
id, grb_index] : quadratic_constraints_map_) {
1081 id, QuadraticConstraintInIIS(grb_index),
1082 *result.mutable_infeasible_subsystem()
1083 ->mutable_quadratic_constraints()));
1086 for (
const auto& [
id, grb_data] : soc_constraints_map_) {
1088 id, AnyElementInIIS(grb_data.DependentElements()),
1089 *result.mutable_infeasible_subsystem()
1090 ->mutable_second_order_cone_constraints()));
1093 for (
const auto& [
id, grb_data] : sos1_constraints_map_) {
1095 id, AnyElementInIIS(grb_data.DependentElements()),
1096 *result.mutable_infeasible_subsystem()->mutable_sos1_constraints()));
1099 for (
const auto& [
id, grb_data] : sos2_constraints_map_) {
1101 id, AnyElementInIIS(grb_data.DependentElements()),
1102 *result.mutable_infeasible_subsystem()->mutable_sos2_constraints()));
1105 for (
const auto& [
id, maybe_grb_data] : indicator_constraints_map_) {
1106 if (!maybe_grb_data.has_value()) {
1113 maybe_grb_data->constraint_index),
1114 *result.mutable_infeasible_subsystem()
1115 ->mutable_indicator_constraints()));
1123absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetSolutions(
1124 const ModelSolveParametersProto& model_parameters) {
1131 return GetMipSolutions(model_parameters);
1132 }
else if (is_qcp) {
1133 return GetQcpSolution(model_parameters);
1135 return GetQpSolution(model_parameters);
1137 return GetLpSolution(model_parameters);
1143absl::StatusOr<SolveStatsProto> GurobiSolver::GetSolveStats(
1144 const absl::Time start)
const {
1145 SolveStatsProto solve_stats;
1148 solve_stats.mutable_solve_time()));
1154 SafeInt64FromDouble(simplex_iters_double));
1155 LOG_IF(ERROR, simplex_iters < 0)
1156 <<
"Expected GRB_DBL_ATTR_ITERCOUNT to be non-negative, got: "
1157 << simplex_iters <<
"; clamping to 0";
1158 solve_stats.set_simplex_iterations(std::max(int64_t{0}, simplex_iters));
1164 LOG_IF(ERROR, barrier_iters < 0)
1165 <<
"Expected GRB_INT_ATTR_BARITERCOUNT to be non-negative, got: "
1166 << barrier_iters <<
"; clamping to 0";
1167 solve_stats.set_barrier_iterations(std::max(0, barrier_iters));
1174 LOG_IF(ERROR, nodes < 0)
1175 <<
"Expected GRB_DBL_ATTR_NODECOUNT to be non-negative, got: " << nodes
1176 <<
"; clamping to 0";
1177 solve_stats.set_node_count(std::max(int64_t{0}, nodes));
1182absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetMipSolutions(
1183 const ModelSolveParametersProto& model_parameters) {
1184 int num_solutions = 0;
1188 std::vector<SolutionProto> solutions;
1189 solutions.reserve(num_solutions);
1191 for (
int i = 0;
i < num_solutions; ++
i) {
1198 if (is_multi_objective_mode()) {
1199 for (
const auto [
id, grb_index] : multi_objectives_map_) {
1205 if (
id.has_value()) {
1216 i == 0 ? SOLUTION_STATUS_FEASIBLE : SOLUTION_STATUS_UNDETERMINED);
1218 const std::vector<double> grb_var_values,
1220 GurobiVectorToSparseDoubleVector(grb_var_values, variables_map_,
1222 model_parameters.variable_values_filter());
1223 *solutions.emplace_back(SolutionProto()).mutable_primal_solution() =
1224 std::move(primal_solution);
1242 const SolutionClaims solution_claims = {
1243 .primal_feasible_solution_exists = num_solutions > 0,
1244 .dual_feasible_solution_exists =
1245 std::isfinite(best_dual_bound) || grb_termination ==
GRB_INFEASIBLE ||
1246 (is_multi_objective_mode() && grb_termination ==
GRB_OPTIMAL)};
1249 if (grb_termination ==
GRB_OPTIMAL && num_solutions == 0) {
1250 return absl::InternalError(
1251 "GRB_INT_ATTR_STATUS == GRB_OPTIMAL, but solution pool is empty.");
1255 if (!is_multi_objective_mode() && grb_termination ==
GRB_OPTIMAL &&
1256 !std::isfinite(best_dual_bound)) {
1257 return absl::InternalError(
1258 "GRB_INT_ATTR_STATUS == GRB_OPTIMAL, but GRB_DBL_ATTR_OBJBOUND is "
1259 "unavailable or infinite.");
1262 return SolutionsAndClaims{.solutions = std::move(solutions),
1263 .solution_claims = solution_claims};
1266absl::StatusOr<GurobiSolver::SolutionAndClaim<PrimalSolutionProto>>
1267GurobiSolver::GetConvexPrimalSolutionIfAvailable(
1268 const ModelSolveParametersProto& model_parameters) {
1270 return SolutionAndClaim<PrimalSolutionProto>{
1271 .solution = std::nullopt, .feasible_solution_exists =
false};
1278 const std::vector<double> grb_var_values,
1279 gurobi_->GetDoubleAttrArray(
GRB_DBL_ATTR_X, num_gurobi_variables_));
1293 double objective_value = 0.0;
1295 const std::vector<double> linear_obj_coefs,
1297 for (
int i = 0;
i < num_gurobi_variables_; ++
i) {
1298 objective_value += linear_obj_coefs[
i] * grb_var_values[
i];
1308 }
else if (PrimalSolutionQualityAvailable()) {
1309 ASSIGN_OR_RETURN(
const double solution_quality, GetPrimalSolutionQuality());
1312 if (solution_quality <= tolerance) {
1319 GurobiVectorToSparseDoubleVector(grb_var_values, variables_map_,
1321 model_parameters.variable_values_filter());
1322 const bool primal_feasible_solution_exists =
1324 return SolutionAndClaim<PrimalSolutionProto>{
1325 .solution = std::move(primal_solution),
1326 .feasible_solution_exists = primal_feasible_solution_exists};
1329bool GurobiSolver::PrimalSolutionQualityAvailable()
const {
1338absl::StatusOr<double> GurobiSolver::GetPrimalSolutionQuality()
const {
1351 return std::max({constraint_residual, constraint_violation, bound_violation,
1352 constraint_scaled_residual, constraint_scaled_violation,
1353 bound_scaled_violation});
1356absl::StatusOr<double> GurobiSolver::GetBestPrimalBound(
1357 absl::Span<const SolutionProto> solutions)
const {
1364 double best_objective_value = is_maximize ? -
kInf :
kInf;
1365 for (
const SolutionProto& solution : solutions) {
1366 if (
solution.has_primal_solution() &&
1367 solution.primal_solution().feasibility_status() ==
1368 SOLUTION_STATUS_FEASIBLE) {
1369 const double sol_val =
solution.primal_solution().objective_value();
1370 best_objective_value = is_maximize
1371 ? std::max(best_objective_value, sol_val)
1372 :
std::min(best_objective_value, sol_val);
1375 return best_objective_value;
1378absl::StatusOr<double> GurobiSolver::GetBestDualBound(
1379 absl::Span<const SolutionProto> solutions)
const {
1385 for (
const SolutionProto& solution : solutions) {
1386 if (
solution.has_dual_solution() &&
1387 solution.dual_solution().feasibility_status() ==
1388 SOLUTION_STATUS_FEASIBLE &&
1389 solution.dual_solution().has_objective_value()) {
1390 const double sol_val =
solution.dual_solution().objective_value();
1391 best_dual_bound = is_maximize ? std::min(best_dual_bound, sol_val)
1392 :
std::max(best_dual_bound, sol_val);
1395 return best_dual_bound;
1398absl::StatusOr<double> GurobiSolver::GetGurobiBestDualBound()
const {
1403 !is_multi_objective_mode()) {
1416absl::StatusOr<std::optional<BasisProto>> GurobiSolver::GetBasisIfAvailable() {
1422 basis.set_basic_dual_feasibility(SOLUTION_STATUS_UNDETERMINED);
1424 basis.set_basic_dual_feasibility(SOLUTION_STATUS_FEASIBLE);
1426 basis.set_basic_dual_feasibility(SOLUTION_STATUS_INFEASIBLE);
1429 return std::move(basis);
1431 return std::nullopt;
1434absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetLpSolution(
1435 const ModelSolveParametersProto& model_parameters) {
1437 GetConvexPrimalSolutionIfAvailable(model_parameters));
1439 GetConvexDualSolutionIfAvailable(model_parameters));
1441 const SolutionClaims solution_claims = {
1442 .primal_feasible_solution_exists =
1443 primal_solution_and_claim.feasible_solution_exists,
1444 .dual_feasible_solution_exists =
1445 dual_solution_and_claim.feasible_solution_exists};
1447 if (!primal_solution_and_claim.solution.has_value() &&
1448 !dual_solution_and_claim.solution.has_value() && !basis.has_value()) {
1449 return SolutionsAndClaims{.solution_claims = solution_claims};
1451 SolutionsAndClaims solution_and_claims{.solution_claims = solution_claims};
1453 solution_and_claims.solutions.emplace_back(SolutionProto());
1454 if (primal_solution_and_claim.solution.has_value()) {
1455 *
solution.mutable_primal_solution() =
1456 std::move(*primal_solution_and_claim.solution);
1458 if (dual_solution_and_claim.solution.has_value()) {
1459 *
solution.mutable_dual_solution() =
1460 std::move(*dual_solution_and_claim.solution);
1462 if (basis.has_value()) {
1463 *
solution.mutable_basis() = std::move(*basis);
1465 return solution_and_claims;
1468absl::StatusOr<GurobiSolver::SolutionAndClaim<DualSolutionProto>>
1469GurobiSolver::GetConvexDualSolutionIfAvailable(
1470 const ModelSolveParametersProto& model_parameters) {
1473 return SolutionAndClaim<DualSolutionProto>{
1474 .solution = std::nullopt, .feasible_solution_exists =
false};
1479 DualSolutionProto dual_solution;
1480 bool dual_feasible_solution_exists =
false;
1482 const std::vector<double> grb_constraint_duals,
1484 GurobiVectorToSparseDoubleVector(grb_constraint_duals,
1485 linear_constraints_map_,
1486 *dual_solution.mutable_dual_values(),
1487 model_parameters.dual_values_filter());
1490 const std::vector<double> grb_reduced_cost_values,
1492 GurobiVectorToSparseDoubleVector(grb_reduced_cost_values, variables_map_,
1493 *dual_solution.mutable_reduced_costs(),
1494 model_parameters.reduced_costs_filter());
1496 if (!quadratic_constraints_map_.empty() &&
1499 const std::vector<double> grb_quad_constraint_duals,
1501 GurobiVectorToSparseDoubleVector(
1502 grb_quad_constraint_duals, quadratic_constraints_map_,
1503 *dual_solution.mutable_quadratic_dual_values(),
1504 model_parameters.quadratic_dual_values_filter());
1513 dual_solution.set_objective_value(obj_val);
1516 dual_solution.set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
1518 dual_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1519 dual_feasible_solution_exists =
true;
1521 dual_solution.set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
1537 if (dual_feasible_solution_exists || std::isfinite(best_dual_bound)) {
1538 dual_feasible_solution_exists =
true;
1540 return absl::InternalError(
1541 "GRB_INT_ATTR_STATUS == GRB_OPTIMAL, but GRB_DBL_ATTR_OBJBOUND is "
1542 "unavailable or infinite, and no dual feasible solution is returned");
1544 return SolutionAndClaim<DualSolutionProto>{
1545 .solution = std::move(dual_solution),
1546 .feasible_solution_exists = dual_feasible_solution_exists};
1549absl::Status GurobiSolver::FillRays(
1550 const ModelSolveParametersProto& model_parameters,
1551 const SolutionClaims solution_claims, SolveResultProto& result) {
1556 if (!solution_claims.dual_feasible_solution_exists &&
1557 num_gurobi_variables_ > 0 &&
1561 num_gurobi_variables_));
1562 PrimalRayProto*
const primal_ray = result.add_primal_rays();
1563 GurobiVectorToSparseDoubleVector(grb_ray_var_values, variables_map_,
1564 *primal_ray->mutable_variable_values(),
1565 model_parameters.variable_values_filter());
1570 if (!solution_claims.primal_feasible_solution_exists &&
1571 num_gurobi_lin_cons_ > 0 &&
1574 DualRayProto dual_ray,
1575 GetGurobiDualRay(model_parameters.dual_values_filter(),
1576 model_parameters.reduced_costs_filter(), is_maximize));
1577 result.mutable_dual_rays()->Add(std::move(dual_ray));
1579 return absl::OkStatus();
1582absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetQpSolution(
1583 const ModelSolveParametersProto& model_parameters) {
1585 GetConvexPrimalSolutionIfAvailable(model_parameters));
1589 GetConvexDualSolutionIfAvailable(model_parameters));
1593 const SolutionClaims solution_claims = {
1594 .primal_feasible_solution_exists = found_primal_feasible_solution,
1595 .dual_feasible_solution_exists = found_dual_feasible_solution};
1598 return GurobiSolver::SolutionsAndClaims{.solution_claims = solution_claims};
1600 SolutionsAndClaims solution_and_claims{.solution_claims = solution_claims};
1602 solution_and_claims.solutions.emplace_back(SolutionProto());
1604 *
solution.mutable_primal_solution() = *std::move(primal_solution);
1606 if (dual_solution.has_value()) {
1607 *
solution.mutable_dual_solution() = *std::move(dual_solution);
1609 return solution_and_claims;
1612absl::StatusOr<GurobiSolver::SolutionsAndClaims> GurobiSolver::GetQcpSolution(
1613 const ModelSolveParametersProto& model_parameters) {
1615 GetConvexPrimalSolutionIfAvailable(model_parameters));
1617 GetConvexDualSolutionIfAvailable(model_parameters));
1621 const bool proven_feasible = grb_termination ==
GRB_OPTIMAL;
1622 const SolutionClaims solution_claims = {
1623 .primal_feasible_solution_exists = found_primal_feasible_solution,
1624 .dual_feasible_solution_exists =
1625 found_dual_feasible_solution || proven_feasible};
1627 SolutionsAndClaims solution_and_claims{.solution_claims = solution_claims};
1630 solution_and_claims.solutions.emplace_back(SolutionProto());
1632 *
solution.mutable_primal_solution() = *std::move(primal_solution);
1634 if (dual_solution.has_value()) {
1635 *
solution.mutable_dual_solution() = *std::move(dual_solution);
1638 return solution_and_claims;
1641absl::Status GurobiSolver::SetParameters(
1642 const SolveParametersProto& parameters,
1643 const ModelSolveParametersProto& model_parameters) {
1646 MergeParameters(parameters, model_parameters, is_mip,
1647 is_multi_objective_mode()));
1648 std::vector<std::string> parameter_errors;
1649 for (
const GurobiParametersProto::Parameter& parameter :
1650 gurobi_parameters.parameters()) {
1651 absl::Status param_status =
1652 gurobi_->SetParam(parameter.name().c_str(), parameter.value());
1653 if (!param_status.ok()) {
1654 parameter_errors.emplace_back(std::move(param_status).message());
1657 if (!parameter_errors.empty()) {
1658 return absl::InvalidArgumentError(absl::StrJoin(parameter_errors,
"; "));
1660 return absl::OkStatus();
1663absl::Status GurobiSolver::AddNewVariables(
1664 const VariablesProto& new_variables) {
1665 const int num_new_variables = new_variables.lower_bounds().size();
1666 std::vector<char> variable_type(num_new_variables);
1667 for (
int j = 0; j < num_new_variables; ++j) {
1674 const std::vector<std::string> variable_names =
1675 TruncateNames(new_variables.names());
1678 new_variables.lower_bounds(),
1679 new_variables.upper_bounds(),
1680 variable_type, variable_names));
1681 num_gurobi_variables_ += num_new_variables;
1683 return absl::OkStatus();
1686absl::Status GurobiSolver::AddSingleObjective(
const ObjectiveProto& objective) {
1691 RETURN_IF_ERROR(UpdateDoubleListAttribute(objective.linear_coefficients(),
1694 ResetQuadraticObjectiveTerms(objective.quadratic_coefficients()));
1695 return absl::OkStatus();
1698absl::Status GurobiSolver::AddMultiObjectives(
1699 const ObjectiveProto& primary_objective,
1700 const google::protobuf::Map<int64_t, ObjectiveProto>&
1701 auxiliary_objectives) {
1702 absl::flat_hash_set<int64_t> priorities = {primary_objective.priority()};
1703 for (
const auto& [
id, objective] : auxiliary_objectives) {
1704 const int64_t priority = objective.priority();
1705 if (!priorities.insert(priority).second) {
1707 <<
"repeated objective priority: " << priority;
1710 const bool is_maximize = primary_objective.maximize();
1714 primary_objective, std::nullopt, is_maximize));
1715 for (
const int64_t
id :
SortedMapKeys(auxiliary_objectives)) {
1717 AddNewMultiObjective(auxiliary_objectives.at(
id),
id, is_maximize));
1719 return absl::OkStatus();
1722absl::Status GurobiSolver::AddNewMultiObjective(
1723 const ObjectiveProto& objective,
1724 const std::optional<AuxiliaryObjectiveId> objective_id,
1725 const bool is_maximize) {
1726 std::vector<GurobiVariableIndex> var_indices;
1727 var_indices.reserve(objective.linear_coefficients().ids_size());
1728 for (
const int64_t var_id : objective.linear_coefficients().ids()) {
1729 var_indices.push_back(variables_map_.at(var_id));
1731 const GurobiMultiObjectiveIndex grb_index =
1732 static_cast<int>(multi_objectives_map_.size());
1742 grb_index,
static_cast<int>(-objective.priority()),
1743 objective.maximize() == is_maximize ? +1.0 : -1.0,
1745 0.0, objective.name(),
1746 objective.offset(), var_indices,
1747 objective.linear_coefficients().values()));
1748 multi_objectives_map_.insert({objective_id, grb_index});
1749 return absl::OkStatus();
1755absl::Status GurobiSolver::AddNewSlacks(
1756 const std::vector<LinearConstraintData*>& new_slacks) {
1763 const int num_slacks = new_slacks.size();
1764 if (num_slacks == 0) {
1765 return absl::OkStatus();
1768 const std::vector<double> column_non_zeros(num_slacks, -1.0);
1769 std::vector<double> lower_bounds;
1770 std::vector<double> upper_bounds;
1772 std::vector<GurobiLinearConstraintIndex> row_indices;
1773 std::vector<int> column_non_zero_begin;
1774 column_non_zero_begin.reserve(num_slacks);
1775 row_indices.reserve(num_slacks);
1776 lower_bounds.reserve(num_slacks);
1777 upper_bounds.reserve(num_slacks);
1778 for (
int k = 0; k < num_slacks; ++k) {
1779 CHECK_NE(new_slacks[k],
nullptr);
1780 const LinearConstraintData& constraint_data = *new_slacks[k];
1781 row_indices.push_back(constraint_data.constraint_index);
1782 lower_bounds.push_back(constraint_data.lower_bound);
1783 upper_bounds.push_back(constraint_data.upper_bound);
1784 column_non_zero_begin.push_back(k);
1789 column_non_zeros, {},
1790 lower_bounds, upper_bounds,
1792 num_gurobi_variables_ += num_slacks;
1793 return absl::OkStatus();
1796absl::Status GurobiSolver::AddNewLinearConstraints(
1797 const LinearConstraintsProto& constraints) {
1798 const int num_new_constraints = constraints.lower_bounds().size();
1802 const std::vector<std::string> constraint_names =
1803 TruncateNames(constraints.names());
1813 std::vector<double> constraint_rhs;
1814 std::vector<char> constraint_sense;
1815 std::vector<LinearConstraintData*> new_slacks;
1816 constraint_rhs.reserve(num_new_constraints);
1817 constraint_sense.reserve(num_new_constraints);
1818 new_slacks.reserve(num_new_constraints);
1819 for (
int i = 0;
i < num_new_constraints; ++
i) {
1820 const int64_t
id = constraints.ids(i);
1821 LinearConstraintData& constraint_data =
1823 const double lb = constraints.lower_bounds(i);
1824 const double ub = constraints.upper_bounds(i);
1826 <<
"lower bound for linear constraint " <<
id <<
": "
1827 << EscapedNameForLogging(
1828 constraints.names().empty() ?
"" : constraints.names(i));
1830 <<
"upper bound for linear constraint " <<
id <<
": "
1831 << EscapedNameForLogging(
1832 constraints.names().empty() ?
"" : constraints.names(i));
1833 constraint_data.lower_bound = lb;
1834 constraint_data.upper_bound = ub;
1835 constraint_data.constraint_index =
i + num_gurobi_lin_cons_;
1840 if (lb_is_grb_neg_inf && !ub_is_grb_pos_inf) {
1843 }
else if (!lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
1846 }
else if (lb == ub) {
1852 constraint_data.slack_index = new_slacks.size() + num_gurobi_variables_;
1853 new_slacks.push_back(&constraint_data);
1855 constraint_rhs.emplace_back(rhs);
1856 constraint_sense.emplace_back(sense);
1860 gurobi_->AddConstrs(constraint_sense, constraint_rhs, constraint_names));
1861 num_gurobi_lin_cons_ += num_new_constraints;
1863 if (!new_slacks.empty()) {
1866 return absl::OkStatus();
1869absl::Status GurobiSolver::AddNewQuadraticConstraints(
1870 const google::protobuf::Map<QuadraticConstraintId,
1871 QuadraticConstraintProto>& constraints) {
1881 for (
const auto& [
id, constraint] : constraints) {
1884 const double lb = constraint.lower_bound();
1885 const double ub = constraint.upper_bound();
1887 <<
"lower bound for quadratic constraint " <<
id <<
": "
1888 << EscapedNameForLogging(constraint.name());
1890 <<
"upper bound for quadratic constraint " <<
id <<
": "
1891 << EscapedNameForLogging(constraint.name());
1894 if (lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
1898 }
else if (lb_is_grb_neg_inf && !ub_is_grb_pos_inf) {
1901 }
else if (!lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
1904 }
else if (lb == ub) {
1910 return absl::UnimplementedError(
1911 "ranged quadratic constraints are not currently supported in Gurobi "
1914 const SparseDoubleVectorProto& linear_coeffs = constraint.linear_terms();
1915 const int num_linear_coeffs = linear_coeffs.ids_size();
1916 std::vector<GurobiVariableIndex> linear_col_index(num_linear_coeffs);
1917 for (
int k = 0; k < num_linear_coeffs; ++k) {
1918 linear_col_index[k] = variables_map_.at(linear_coeffs.ids(k));
1920 const SparseDoubleMatrixProto& quad_coeffs = constraint.quadratic_terms();
1921 const int num_quad_coeffs = quad_coeffs.row_ids_size();
1922 std::vector<GurobiVariableIndex> quad_row_index(num_quad_coeffs);
1923 std::vector<GurobiVariableIndex> quad_col_index(num_quad_coeffs);
1924 for (
int k = 0; k < num_quad_coeffs; ++k) {
1925 quad_row_index[k] = variables_map_.at(quad_coeffs.row_ids(k));
1926 quad_col_index[k] = variables_map_.at(quad_coeffs.column_ids(k));
1929 linear_col_index, linear_coeffs.values(), quad_row_index,
1930 quad_col_index, quad_coeffs.coefficients(), sense, rhs,
1931 TruncateName(constraint.name())));
1933 ++num_gurobi_quad_cons_;
1935 return absl::OkStatus();
1938std::optional<GurobiSolver::VariableId> GurobiSolver::TryExtractVariable(
1939 const LinearExpressionProto& expression) {
1940 if (expression.offset() == 0 && expression.ids_size() == 1 &&
1941 expression.coefficients(0) == 1) {
1942 return expression.ids(0);
1944 return std::nullopt;
1947absl::StatusOr<GurobiSolver::VariableEqualToExpression>
1948GurobiSolver::CreateSlackVariableEqualToExpression(
1949 const LinearExpressionProto& expression) {
1950 const GurobiVariableIndex slack_variable = num_gurobi_variables_;
1951 std::vector<GurobiVariableIndex> slack_col_indices = {slack_variable};
1952 std::vector<double> slack_coeffs = {-1.0};
1953 for (
int j = 0; j < expression.ids_size(); ++j) {
1954 slack_col_indices.push_back(variables_map_.at(expression.ids(j)));
1955 slack_coeffs.push_back(expression.coefficients(j));
1959 -expression.offset(),
""));
1960 return VariableEqualToExpression{.variable_index = num_gurobi_variables_++,
1961 .constraint_index = num_gurobi_lin_cons_++};
1964absl::Status GurobiSolver::AddNewSecondOrderConeConstraints(
1965 const google::protobuf::Map<SecondOrderConeConstraintId,
1966 SecondOrderConeConstraintProto>& constraints) {
1967 for (
const auto& [
id, constraint] : constraints) {
1980 SecondOrderConeConstraintData& constraint_data =
1982 constraint_data.constraint_index = num_gurobi_quad_cons_;
1987 (
const auto [ub_var, ub_cons]),
1988 CreateSlackVariableEqualToExpression(constraint.upper_bound()));
1991 constraint_data.slack_variables.push_back(ub_var);
1992 constraint_data.slack_constraints.push_back(ub_cons);
1993 std::vector<GurobiVariableIndex> quad_var_indices = {ub_var};
1994 std::vector<double> quad_coeffs = {-1.0};
1995 for (
const LinearExpressionProto& expression :
1996 constraint.arguments_to_norm()) {
1997 quad_coeffs.push_back(1.0);
1998 if (
const std::optional<VariableId> maybe_variable =
1999 TryExtractVariable(expression);
2000 maybe_variable.has_value()) {
2001 quad_var_indices.push_back(variables_map_.at(*maybe_variable));
2005 CreateSlackVariableEqualToExpression(expression));
2006 quad_var_indices.push_back(arg_var);
2007 constraint_data.slack_variables.push_back(arg_var);
2008 constraint_data.slack_constraints.push_back(arg_cons);
2011 quad_var_indices, quad_coeffs,
2013 ++num_gurobi_quad_cons_;
2015 return absl::OkStatus();
2018absl::Status GurobiSolver::AddNewSosConstraints(
2019 const google::protobuf::Map<AnyConstraintId, SosConstraintProto>&
2022 absl::flat_hash_map<int64_t, SosConstraintData>& constraints_map) {
2023 for (
const auto& [
id, constraint] : constraints) {
2024 SosConstraintData& constraint_data =
2026 constraint_data.constraint_index = num_gurobi_sos_cons_;
2027 std::vector<GurobiVariableIndex> sos_var_indices;
2028 std::vector<double> weights;
2034 absl::flat_hash_set<VariableId> reused_variables;
2035 for (
int i = 0;
i < constraint.expressions_size(); ++
i) {
2036 weights.push_back(constraint.weights().empty() ? i + 1
2037 : constraint.weights(i));
2038 if (
const std::optional<VariableId> maybe_variable =
2039 TryExtractVariable(constraint.expressions(i));
2040 maybe_variable.has_value() &&
2041 !reused_variables.contains(*maybe_variable)) {
2042 sos_var_indices.push_back(variables_map_.at(*maybe_variable));
2043 reused_variables.insert(*maybe_variable);
2044 if (sos_type == 2) {
2048 undeletable_variables_.insert(*maybe_variable);
2053 (
const auto [var_index, cons_index]),
2054 CreateSlackVariableEqualToExpression(constraint.expressions(i)));
2055 sos_var_indices.push_back(var_index);
2056 constraint_data.slack_variables.push_back(var_index);
2057 constraint_data.slack_constraints.push_back(cons_index);
2059 RETURN_IF_ERROR(gurobi_->AddSos({sos_type}, {0}, sos_var_indices, weights));
2060 ++num_gurobi_sos_cons_;
2062 return absl::OkStatus();
2065absl::Status GurobiSolver::AddNewIndicatorConstraints(
2066 const google::protobuf::Map<IndicatorConstraintId,
2067 IndicatorConstraintProto>& constraints) {
2068 for (
const auto& [
id, constraint] : constraints) {
2069 if (!constraint.has_indicator_id()) {
2070 if (constraint.activate_on_zero()) {
2072 <<
"MathOpt does not currently support Gurobi models with "
2073 "indicator constraints that activate on zero with unset "
2074 "indicator variables; encountered one at id: "
2080 const int num_terms = constraint.expression().ids_size();
2081 std::vector<GurobiVariableIndex> grb_ids(num_terms);
2082 for (
int k = 0; k < num_terms; ++k) {
2083 grb_ids[k] = variables_map_.at(constraint.expression().ids(k));
2087 const double lb = constraint.lower_bound();
2088 const double ub = constraint.upper_bound();
2090 <<
"lower bound for indicator constraint " <<
id <<
": "
2091 << EscapedNameForLogging(constraint.name());
2093 <<
"upper bound for indicator constraint " <<
id <<
": "
2094 << EscapedNameForLogging(constraint.name());
2097 if (lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
2100 }
else if (lb_is_grb_neg_inf && !ub_is_grb_pos_inf) {
2103 }
else if (!lb_is_grb_neg_inf && ub_is_grb_pos_inf) {
2106 }
else if (lb == ub) {
2112 return absl::UnimplementedError(
2113 "ranged indicator constraints are not currently supported in "
2119 variables_map_.at(constraint.indicator_id()),
2120 constraint.activate_on_zero() ? 0 : 1,
2121 grb_ids, constraint.expression().values(),
2124 IndicatorConstraintData{
2125 .constraint_index = num_gurobi_gen_cons_,
2126 .indicator_variable_id = constraint.indicator_id()});
2127 ++num_gurobi_gen_cons_;
2130 undeletable_variables_.insert(constraint.indicator_id());
2132 return absl::OkStatus();
2135absl::Status GurobiSolver::ChangeCoefficients(
2136 const SparseDoubleMatrixProto& matrix) {
2137 const int num_coefficients = matrix.row_ids().size();
2138 std::vector<GurobiLinearConstraintIndex> row_index(num_coefficients);
2139 std::vector<GurobiVariableIndex> col_index(num_coefficients);
2140 for (
int k = 0; k < num_coefficients; ++k) {
2142 linear_constraints_map_.at(matrix.row_ids(k)).constraint_index;
2143 col_index[k] = variables_map_.at(matrix.column_ids(k));
2145 return gurobi_->ChgCoeffs(row_index, col_index, matrix.coefficients());
2148absl::Status GurobiSolver::UpdateDoubleListAttribute(
2149 const SparseDoubleVectorProto& update,
2150 const char* absl_nonnull attribute_name,
const IdHashMap& id_hash_map) {
2151 if (update.ids_size() == 0) {
2152 return absl::OkStatus();
2154 std::vector<int> index;
2155 index.reserve(update.ids_size());
2156 for (
const int64_t
id : update.ids()) {
2157 index.push_back(id_hash_map.at(
id));
2159 return gurobi_->SetDoubleAttrList(attribute_name, index, update.values());
2162absl::Status GurobiSolver::UpdateInt32ListAttribute(
2163 const SparseInt32VectorProto& update,
2164 const char* absl_nonnull attribute_name,
const IdHashMap& id_hash_map) {
2165 if (update.ids_size() == 0) {
2166 return absl::OkStatus();
2168 std::vector<int> index;
2169 index.reserve(update.ids_size());
2170 for (
const int64_t
id : update.ids()) {
2171 index.push_back(id_hash_map.at(
id));
2173 return gurobi_->SetIntAttrList(attribute_name, index, update.values());
2176absl::Status GurobiSolver::LoadModel(
const ModelProto& input_model) {
2177 CHECK(gurobi_ !=
nullptr);
2179 TruncateName(input_model.name())));
2182 RETURN_IF_ERROR(AddNewLinearConstraints(input_model.linear_constraints()));
2184 AddNewQuadraticConstraints(input_model.quadratic_constraints()));
2186 input_model.second_order_cone_constraints()));
2192 AddNewIndicatorConstraints(input_model.indicator_constraints()));
2194 RETURN_IF_ERROR(ChangeCoefficients(input_model.linear_constraint_matrix()));
2196 if (input_model.auxiliary_objectives().empty()) {
2200 input_model.auxiliary_objectives()));
2202 return absl::OkStatus();
2205absl::Status GurobiSolver::ResetQuadraticObjectiveTerms(
2206 const SparseDoubleMatrixProto& terms) {
2207 quadratic_objective_coefficients_.clear();
2209 const int num_terms = terms.row_ids().size();
2210 if (num_terms > 0) {
2211 std::vector<GurobiVariableIndex> first_var_index(num_terms);
2212 std::vector<GurobiVariableIndex> second_var_index(num_terms);
2213 for (
int k = 0; k < num_terms; ++k) {
2215 const VariableId column_id = terms.column_ids(k);
2216 first_var_index[k] = variables_map_.at(row_id);
2217 second_var_index[k] = variables_map_.at(column_id);
2218 quadratic_objective_coefficients_[{row_id, column_id}] =
2219 terms.coefficients(k);
2221 RETURN_IF_ERROR(gurobi_->AddQpTerms(first_var_index, second_var_index,
2222 terms.coefficients()));
2224 return absl::OkStatus();
2227absl::Status GurobiSolver::UpdateQuadraticObjectiveTerms(
2228 const SparseDoubleMatrixProto& terms) {
2229 CHECK(gurobi_ !=
nullptr);
2230 const int num_terms = terms.row_ids().size();
2231 if (num_terms > 0) {
2232 std::vector<GurobiVariableIndex> first_var_index(num_terms);
2233 std::vector<GurobiVariableIndex> second_var_index(num_terms);
2234 std::vector<double> coefficient_updates(num_terms);
2235 for (
int k = 0; k < num_terms; ++k) {
2237 const VariableId column_id = terms.column_ids(k);
2238 first_var_index[k] = variables_map_.at(row_id);
2239 second_var_index[k] = variables_map_.at(column_id);
2240 const std::pair<VariableId, VariableId> qp_term_key(row_id, column_id);
2241 const double new_coefficient = terms.coefficients(k);
2246 coefficient_updates[k] =
2247 new_coefficient - quadratic_objective_coefficients_[qp_term_key];
2248 quadratic_objective_coefficients_[qp_term_key] = new_coefficient;
2250 RETURN_IF_ERROR(gurobi_->AddQpTerms(first_var_index, second_var_index,
2251 coefficient_updates));
2253 return absl::OkStatus();
2259absl::Status GurobiSolver::UpdateLinearConstraints(
2260 const LinearConstraintUpdatesProto& constraints_update,
2261 std::vector<GurobiVariableIndex>& deleted_variables_index) {
2262 const SparseDoubleVectorProto& constraint_lower_bounds =
2263 constraints_update.lower_bounds();
2264 const SparseDoubleVectorProto& constraint_upper_bounds =
2265 constraints_update.upper_bounds();
2268 if (constraint_lower_bounds.ids().empty() &&
2269 constraint_upper_bounds.ids().empty()) {
2270 return absl::OkStatus();
2277 struct UpdateConstraintData {
2279 LinearConstraintData& source;
2280 double new_lower_bound;
2281 double new_upper_bound;
2282 UpdateConstraintData(
const LinearConstraintId
id,
2283 LinearConstraintData& reference)
2284 : constraint_id(id),
2286 new_lower_bound(reference.lower_bound),
2287 new_upper_bound(reference.upper_bound) {}
2289 const int upper_bounds_size = constraint_upper_bounds.ids().size();
2290 const int lower_bounds_size = constraint_lower_bounds.ids().size();
2291 std::vector<UpdateConstraintData> update_vector;
2292 update_vector.reserve(upper_bounds_size + lower_bounds_size);
2295 for (
int lower_index = 0, upper_index = 0;
2296 lower_index < lower_bounds_size || upper_index < upper_bounds_size;) {
2297 VariableId lower_id = std::numeric_limits<int64_t>::max();
2298 if (lower_index < lower_bounds_size) {
2299 lower_id = constraint_lower_bounds.ids(lower_index);
2301 VariableId upper_id = std::numeric_limits<int64_t>::max();
2302 if (upper_index < upper_bounds_size) {
2303 upper_id = constraint_upper_bounds.ids(upper_index);
2305 const VariableId id = std::min(lower_id, upper_id);
2306 DCHECK(
id < std::numeric_limits<int64_t>::max());
2307 UpdateConstraintData update(
id, linear_constraints_map_.at(
id));
2308 if (lower_id == upper_id) {
2309 update.new_lower_bound = constraint_lower_bounds.values(lower_index++);
2310 update.new_upper_bound = constraint_upper_bounds.values(upper_index++);
2311 }
else if (lower_id < upper_id) {
2312 update.new_lower_bound = constraint_lower_bounds.values(lower_index++);
2314 update.new_upper_bound = constraint_upper_bounds.values(upper_index++);
2316 update_vector.emplace_back(update);
2323 std::vector<char> sense_data;
2324 std::vector<double> rhs_data;
2325 std::vector<GurobiLinearConstraintIndex> rhs_index;
2327 std::vector<double> lower_bound_data;
2328 std::vector<double> upper_bound_data;
2329 std::vector<GurobiVariableIndex> bound_index;
2331 std::vector<LinearConstraintData*> new_slacks;
2333 for (UpdateConstraintData& update_data : update_vector) {
2334 const bool same_lower_bound =
2335 (update_data.source.lower_bound == update_data.new_lower_bound) ||
2338 const bool same_upper_bound =
2339 (update_data.source.upper_bound == update_data.new_upper_bound) ||
2342 if (same_upper_bound && same_lower_bound)
continue;
2345 update_data.source.lower_bound = update_data.new_lower_bound;
2346 update_data.source.upper_bound = update_data.new_upper_bound;
2347 bool delete_slack =
false;
2351 delete_slack =
true;
2352 rhs_index.emplace_back(update_data.source.constraint_index);
2353 rhs_data.emplace_back(update_data.new_upper_bound);
2355 }
else if (update_data.new_lower_bound > -
GRB_INFINITY &&
2357 delete_slack =
true;
2358 rhs_index.emplace_back(update_data.source.constraint_index);
2359 rhs_data.emplace_back(update_data.new_lower_bound);
2361 }
else if (update_data.new_lower_bound == update_data.new_upper_bound) {
2362 delete_slack =
true;
2363 rhs_index.emplace_back(update_data.source.constraint_index);
2364 rhs_data.emplace_back(update_data.new_lower_bound);
2370 if (update_data.source.slack_index != kUnspecifiedIndex) {
2371 bound_index.emplace_back(update_data.source.slack_index);
2372 lower_bound_data.emplace_back(update_data.new_lower_bound);
2373 upper_bound_data.emplace_back(update_data.new_upper_bound);
2377 rhs_index.emplace_back(update_data.source.constraint_index);
2378 rhs_data.emplace_back(0.0);
2381 update_data.source.slack_index =
2382 new_slacks.size() + num_gurobi_variables_;
2384 new_slacks.push_back(&update_data.source);
2391 if (delete_slack && update_data.source.slack_index != kUnspecifiedIndex) {
2392 deleted_variables_index.emplace_back(update_data.source.slack_index);
2393 update_data.source.slack_index = kUnspecifiedIndex;
2398 if (!rhs_index.empty()) {
2400 gurobi_->SetDoubleAttrList(GRB_DBL_ATTR_RHS, rhs_index, rhs_data));
2402 gurobi_->SetCharAttrList(GRB_CHAR_ATTR_SENSE, rhs_index, sense_data));
2404 if (!bound_index.empty()) {
2405 RETURN_IF_ERROR(gurobi_->SetDoubleAttrList(GRB_DBL_ATTR_LB, bound_index,
2407 RETURN_IF_ERROR(gurobi_->SetDoubleAttrList(GRB_DBL_ATTR_UB, bound_index,
2411 if (!new_slacks.empty()) {
2412 RETURN_IF_ERROR(AddNewSlacks(new_slacks));
2414 return absl::OkStatus();
2422void GurobiSolver::UpdateGurobiIndices(
const DeletedIndices& deleted_indices) {
2424 if (!deleted_indices.variables.empty()) {
2425 const std::vector<GurobiVariableIndex> old_to_new =
2426 IndexUpdateMap(num_gurobi_variables_, deleted_indices.variables);
2427 for (
auto& [_, grb_index] : variables_map_) {
2428 grb_index = old_to_new[grb_index];
2429 CHECK_NE(grb_index, kDeletedIndex);
2431 for (
auto& [_, lin_con_data] : linear_constraints_map_) {
2432 if (lin_con_data.slack_index != kUnspecifiedIndex) {
2433 lin_con_data.slack_index = old_to_new[lin_con_data.slack_index];
2434 CHECK_NE(lin_con_data.slack_index, kDeletedIndex);
2437 for (
auto& [_, soc_con_data] : soc_constraints_map_) {
2438 for (GurobiVariableIndex& index : soc_con_data.slack_variables) {
2439 index = old_to_new[index];
2440 CHECK_NE(index, kDeletedIndex);
2443 for (
auto& [_, sos1_con_data] : sos1_constraints_map_) {
2444 for (GurobiVariableIndex& index : sos1_con_data.slack_variables) {
2445 index = old_to_new[index];
2446 CHECK_NE(index, kDeletedIndex);
2449 for (
auto& [_, sos2_con_data] : sos2_constraints_map_) {
2450 for (GurobiVariableIndex& index : sos2_con_data.slack_variables) {
2451 index = old_to_new[index];
2452 CHECK_NE(index, kDeletedIndex);
2457 if (!deleted_indices.linear_constraints.empty()) {
2458 const std::vector<GurobiLinearConstraintIndex> old_to_new = IndexUpdateMap(
2459 num_gurobi_lin_cons_, deleted_indices.linear_constraints);
2460 for (
auto& [_, lin_con_data] : linear_constraints_map_) {
2461 lin_con_data.constraint_index = old_to_new[lin_con_data.constraint_index];
2462 CHECK_NE(lin_con_data.constraint_index, kDeletedIndex);
2464 for (
auto& [_, soc_con_data] : soc_constraints_map_) {
2465 for (GurobiLinearConstraintIndex& index :
2466 soc_con_data.slack_constraints) {
2467 index = old_to_new[index];
2468 CHECK_NE(index, kDeletedIndex);
2471 for (
auto& [_, sos1_con_data] : sos1_constraints_map_) {
2472 for (GurobiLinearConstraintIndex& index :
2473 sos1_con_data.slack_constraints) {
2474 index = old_to_new[index];
2475 CHECK_NE(index, kDeletedIndex);
2478 for (
auto& [_, sos2_con_data] : sos2_constraints_map_) {
2479 for (GurobiLinearConstraintIndex& index :
2480 sos2_con_data.slack_constraints) {
2481 index = old_to_new[index];
2482 CHECK_NE(index, kDeletedIndex);
2487 if (!deleted_indices.quadratic_constraints.empty()) {
2488 const std::vector<GurobiQuadraticConstraintIndex> old_to_new =
2489 IndexUpdateMap(num_gurobi_quad_cons_,
2490 deleted_indices.quadratic_constraints);
2491 for (
auto& [_, grb_index] : quadratic_constraints_map_) {
2492 grb_index = old_to_new[grb_index];
2493 CHECK_NE(grb_index, kDeletedIndex);
2495 for (
auto& [_, soc_con_data] : soc_constraints_map_) {
2496 GurobiQuadraticConstraintIndex& grb_index = soc_con_data.constraint_index;
2497 grb_index = old_to_new[soc_con_data.constraint_index];
2498 CHECK_NE(grb_index, kDeletedIndex);
2502 if (!deleted_indices.sos_constraints.empty()) {
2503 const std::vector<GurobiSosConstraintIndex> old_to_new =
2504 IndexUpdateMap(num_gurobi_sos_cons_, deleted_indices.sos_constraints);
2505 for (
auto& [_, sos1_data] : sos1_constraints_map_) {
2506 GurobiSosConstraintIndex& grb_index = sos1_data.constraint_index;
2507 grb_index = old_to_new[grb_index];
2508 CHECK_NE(grb_index, kDeletedIndex);
2510 for (
auto& [_, sos2_data] : sos2_constraints_map_) {
2511 GurobiSosConstraintIndex& grb_index = sos2_data.constraint_index;
2512 grb_index = old_to_new[grb_index];
2513 CHECK_NE(grb_index, kDeletedIndex);
2517 if (!deleted_indices.general_constraints.empty()) {
2518 const std::vector<GurobiGeneralConstraintIndex> old_to_new = IndexUpdateMap(
2519 num_gurobi_gen_cons_, deleted_indices.general_constraints);
2520 for (
auto& [_, indicator_data] : indicator_constraints_map_) {
2521 if (!indicator_data.has_value()) {
2524 GurobiGeneralConstraintIndex& grb_index =
2525 indicator_data->constraint_index;
2526 grb_index = old_to_new[grb_index];
2527 CHECK_NE(grb_index, kDeletedIndex);
2534 if (!undeletable_variables_.empty()) {
2536 if (undeletable_variables_.contains(
id)) {
2549 !objs_update.new_objectives().empty() ||
2550 !objs_update.objective_updates().empty()) {
2569 sos1_constraints_map_));
2572 sos2_constraints_map_));
2609 std::vector<GurobiVariableIndex> index;
2611 for (
const int64_t
id : update.
ids()) {
2612 index.push_back(variables_map_.at(
id));
2614 std::vector<char> value;
2616 for (
const bool val : update.
values()) {
2625 const absl::flat_hash_set<VariableId> variable_ids_to_be_deleted(
2630 for (
auto it = quadratic_objective_coefficients_.cbegin();
2631 it != quadratic_objective_coefficients_.cend();
2633 if (variable_ids_to_be_deleted.contains(it->first.first) ||
2634 variable_ids_to_be_deleted.contains(it->first.second)) {
2635 quadratic_objective_coefficients_.erase(it++);
2642 DeletedIndices deleted_indices;
2648 deleted_indices.variables.emplace_back(variables_map_.at(
id));
2649 variables_map_.erase(
id);
2652 for (
const LinearConstraintId
id :
2654 LinearConstraintData& constraint_data = linear_constraints_map_.at(
id);
2655 deleted_indices.linear_constraints.push_back(
2656 constraint_data.constraint_index);
2657 if (constraint_data.slack_index != kUnspecifiedIndex) {
2658 deleted_indices.variables.push_back(constraint_data.slack_index);
2659 constraint_data.slack_index = kUnspecifiedIndex;
2661 linear_constraints_map_.erase(
id);
2664 for (
const QuadraticConstraintId
id :
2666 const GurobiQuadraticConstraintIndex grb_index =
2667 quadratic_constraints_map_.at(
id);
2668 deleted_indices.quadratic_constraints.push_back(grb_index);
2669 quadratic_constraints_map_.erase(
id);
2672 for (
const SecondOrderConeConstraintId
id :
2675 deleted_indices.quadratic_constraints.push_back(
2676 soc_constraints_map_.at(
id).constraint_index);
2677 for (
const GurobiVariableIndex index :
2678 soc_constraints_map_.at(
id).slack_variables) {
2679 deleted_indices.variables.push_back(index);
2681 for (
const GurobiLinearConstraintIndex index :
2682 soc_constraints_map_.at(
id).slack_constraints) {
2683 deleted_indices.linear_constraints.push_back(index);
2685 soc_constraints_map_.erase(
id);
2688 const auto sos_updater = [&](
const SosConstraintData& sos_constraint) {
2689 deleted_indices.sos_constraints.push_back(sos_constraint.constraint_index);
2690 for (
const GurobiVariableIndex index : sos_constraint.slack_variables) {
2691 deleted_indices.variables.push_back(index);
2693 for (
const GurobiLinearConstraintIndex index :
2694 sos_constraint.slack_constraints) {
2695 deleted_indices.linear_constraints.push_back(index);
2698 for (
const Sos1ConstraintId
id :
2700 sos_updater(sos1_constraints_map_.at(
id));
2701 sos1_constraints_map_.erase(
id);
2704 for (
const Sos2ConstraintId
id :
2706 sos_updater(sos2_constraints_map_.at(
id));
2707 sos2_constraints_map_.erase(
id);
2710 for (
const IndicatorConstraintId
id :
2713 const auto it = indicator_constraints_map_.find(
id);
2714 CHECK(it != indicator_constraints_map_.end()) <<
"id: " << id;
2715 if (it->second.has_value()) {
2716 deleted_indices.general_constraints.push_back(
2717 it->second->constraint_index);
2719 indicator_constraints_map_.erase(it);
2722 UpdateGurobiIndices(deleted_indices);
2729 if (!deleted_indices.linear_constraints.empty()) {
2730 RETURN_IF_ERROR(gurobi_->DelConstrs(deleted_indices.linear_constraints));
2731 num_gurobi_lin_cons_ -= deleted_indices.linear_constraints.size();
2734 if (!deleted_indices.quadratic_constraints.empty()) {
2736 gurobi_->DelQConstrs(deleted_indices.quadratic_constraints));
2737 num_gurobi_quad_cons_ -= deleted_indices.quadratic_constraints.size();
2740 if (!deleted_indices.sos_constraints.empty()) {
2744 if (!deleted_indices.general_constraints.empty()) {
2746 gurobi_->DelGenConstrs(deleted_indices.general_constraints));
2749 if (!deleted_indices.variables.empty()) {
2751 num_gurobi_variables_ -= deleted_indices.variables.size();
2768 <<
"Gurobi does not support multiple objective models with "
2769 "quadratic objectives";
2772 if (!obj.quadratic_coefficients().row_ids().empty()) {
2774 <<
"Gurobi does not support multiple objective models with "
2775 "quadratic objectives";
2779 GurobiFromInitArgs(init_args));
2780 auto gurobi_solver = absl::WrapUnique(
new GurobiSolver(std::move(gurobi)));
2782 return gurobi_solver;
2785absl::StatusOr<std::unique_ptr<GurobiSolver::GurobiCallbackData>>
2786GurobiSolver::RegisterCallback(
2790 const absl::flat_hash_set<CallbackEventProto> events =
EventSet(registration);
2801 registration, is_mip ? SupportedMIPEvents() : SupportedLPEvents()))
2802 <<
"for a " << (is_mip ?
"MIP" :
"LP") <<
" model";
2805 if (message_cb !=
nullptr) {
2820 return std::make_unique<GurobiCallbackData>(
2821 GurobiCallbackInput{
2823 .message_cb = message_cb,
2824 .variable_ids = variables_map_,
2825 .num_gurobi_vars = num_gurobi_variables_,
2833absl::StatusOr<InvertedBounds> GurobiSolver::ListInvertedBounds()
const {
2834 InvertedBounds inverted_bounds;
2837 const std::vector<double> var_lbs,
2840 const std::vector<double> var_ubs,
2842 for (
const auto& [
id, index] : variables_map_) {
2843 if (var_lbs[index] > var_ubs[index]) {
2844 inverted_bounds.variables.push_back(
id);
2848 for (
const auto& [
id, cstr_data] : linear_constraints_map_) {
2849 if (cstr_data.lower_bound > cstr_data.upper_bound) {
2850 inverted_bounds.linear_constraints.push_back(
id);
2855 std::sort(inverted_bounds.variables.begin(), inverted_bounds.variables.end());
2856 std::sort(inverted_bounds.linear_constraints.begin(),
2857 inverted_bounds.linear_constraints.end());
2858 return inverted_bounds;
2861absl::StatusOr<InvalidIndicators> GurobiSolver::ListInvalidIndicators()
const {
2862 InvalidIndicators invalid_indicators;
2863 for (
const auto& [constraint_id, indicator_data] :
2864 indicator_constraints_map_) {
2865 if (!indicator_data.has_value()) {
2868 const int64_t indicator_id = indicator_data->indicator_variable_id;
2869 const GurobiVariableIndex variable_index = variables_map_.at(indicator_id);
2875 const char var_type,
2878 (var_type ==
GRB_INTEGER && var_lb >= 0.0 && var_ub <= 1.0))) {
2879 invalid_indicators.invalid_indicators.push_back(
2880 {.variable = indicator_id, .constraint = constraint_id});
2884 invalid_indicators.Sort();
2885 return invalid_indicators;
2888bool GurobiSolver::is_multi_objective_mode()
const {
2889 return !multi_objectives_map_.empty();
2892absl::Status GurobiSolver::SetMultiObjectiveParameters(
2893 const ModelSolveParametersProto& model_parameters) {
2894 const auto set_tolerances =
2895 [&](
const GurobiMultiObjectiveIndex index,
2896 const ObjectiveParametersProto& objective_parameters)
2899 if (objective_parameters.has_objective_degradation_absolute_tolerance()) {
2902 objective_parameters.objective_degradation_absolute_tolerance()));
2904 if (objective_parameters.has_objective_degradation_relative_tolerance()) {
2907 objective_parameters.objective_degradation_relative_tolerance()));
2909 return absl::OkStatus();
2911 const auto set_time_limit =
2912 [&](
const GurobiMultiObjectiveIndex index,
2913 const ObjectiveParametersProto& objective_parameters)
2915 if (!objective_parameters.has_time_limit()) {
2917 return absl::OkStatus();
2920 const absl::Duration time_limit,
2922 return gurobi_->SetMultiObjectiveDoubleParam(
2925 if (model_parameters.has_primary_objective_parameters()) {
2926 const GurobiMultiObjectiveIndex obj_index =
2927 multi_objectives_map_.at(std::nullopt);
2929 obj_index, model_parameters.primary_objective_parameters()))
2930 <<
" for primary objective";
2932 obj_index, model_parameters.primary_objective_parameters()))
2933 <<
" for primary objective";
2935 for (
const auto& [
id, objective_parameters] :
2936 model_parameters.auxiliary_objective_parameters()) {
2937 const GurobiMultiObjectiveIndex obj_index = multi_objectives_map_.at(
id);
2939 <<
" for auxiliary objective " << id;
2941 <<
" for auxiliary objective " << id;
2943 return absl::OkStatus();
2946absl::Status GurobiSolver::ResetModelParameters(
2947 const ModelSolveParametersProto& model_parameters) {
2948 for (
int i = 0;
i < model_parameters.branching_priorities().ids_size(); ++
i) {
2949 const int64_t var_id = model_parameters.branching_priorities().ids(i);
2950 const GurobiVariableIndex grb_index = variables_map_.at(var_id);
2953 <<
"failed to reset branching priority for variable ID " << var_id
2954 <<
" (Gurobi index = " << grb_index <<
")";
2956 for (
const int64_t lazy_constraint_id :
2957 model_parameters.lazy_linear_constraint_ids()) {
2958 const GurobiLinearConstraintIndex lazy_constraint_index =
2959 linear_constraints_map_.at(lazy_constraint_id).constraint_index;
2962 <<
"failed to reset lazy constraint for lazy constraint ID "
2963 << lazy_constraint_id <<
" (Gurobi index = " << lazy_constraint_index
2966 return absl::OkStatus();
2976 model_parameters, kGurobiSupportedStructures,
"Gurobi"));
2977 const absl::Time start = absl::Now();
2989 ListInvertedBounds());
2998 ListInvalidIndicators());
3009 std::unique_ptr<SolveInterrupter> local_interrupter;
3010 if (cb !=
nullptr || interrupter !=
nullptr) {
3011 local_interrupter = std::make_unique<SolveInterrupter>();
3014 local_interrupter.get(), [&]() {
3022 gurobi_->Terminate();
3032 interrupter, [&]() { local_interrupter->Interrupt(); });
3048 if (is_multi_objective_mode()) {
3051 for (
const int64_t lazy_constraint_id :
3053 const GurobiLinearConstraintIndex lazy_constraint_index =
3054 linear_constraints_map_.at(lazy_constraint_id).constraint_index;
3061 lazy_constraint_index, 1));
3068 std::unique_ptr<GurobiCallbackData> gurobi_cb_data;
3069 if (cb !=
nullptr || local_interrupter !=
nullptr || message_cb !=
nullptr) {
3071 RegisterCallback(callback_registration, cb, message_cb,
3072 start, local_interrupter.get()));
3073 grb_cb = [&gurobi_cb_data](
3076 gurobi_cb_data->message_callback_data,
3077 gurobi_cb_data->local_interrupter);
3085 if (gurobi_cb_data !=
nullptr) {
3087 gurobi_cb_data->message_callback_data);
3091 ExtractSolveResultProto(start, model_parameters));
3098 return solve_result;
3102absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
3106 const absl::Time start = absl::Now();
3118 ListInvalidIndicators());
3129 std::unique_ptr<SolveInterrupter> local_interrupter;
3130 if (interrupter !=
nullptr) {
3131 local_interrupter = std::make_unique<SolveInterrupter>();
3134 local_interrupter.get(), [&]() {
3143 gurobi_->Terminate();
3153 interrupter, [&]() { local_interrupter->Interrupt(); });
3159 std::unique_ptr<GurobiCallbackData> gurobi_cb_data;
3160 if (local_interrupter !=
nullptr || message_cb !=
nullptr) {
3162 RegisterCallback({},
nullptr, message_cb, start,
3163 local_interrupter.get()));
3164 grb_cb = [&gurobi_cb_data](
3167 gurobi_cb_data->message_callback_data,
3168 gurobi_cb_data->local_interrupter);
3172 ASSIGN_OR_RETURN(
const bool proven_infeasible, gurobi_->ComputeIIS(grb_cb));
3176 if (gurobi_cb_data !=
nullptr) {
3178 gurobi_cb_data->message_callback_data);
3183 ExtractComputeInfeasibleSubsystemResultProto(proven_infeasible));
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
::int64_t deleted_objective_ids(int index) const
const ::operations_research::math_opt::SparseVectorFilterProto & mip_solution_filter() const
bool add_lazy_constraints() const
const ::operations_research::math_opt::SparseVectorFilterProto & mip_node_filter() const
GurobiParametersProto_Parameter Parameter
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto ¶meters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, const SolveInterrupter *absl_nullable interrupter) override
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto ¶meters, MessageCallback message_cb, const SolveInterrupter *absl_nullable interrupter) override
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
static absl::StatusOr< std::unique_ptr< GurobiSolver > > New(const ModelProto &input_model, const SolverInterface::InitArgs &init_args)
static absl::StatusOr< std::unique_ptr< Gurobi > > NewWithSharedPrimaryEnv(GRBenv *primary_env)
std::function< absl::Status(const CallbackContext &)> Callback
static absl::StatusOr< std::unique_ptr< Gurobi > > New(GRBenvUniquePtr primary_env=nullptr)
::int64_t deleted_constraint_ids(int index) const
const ::google::protobuf::Map<::int64_t, ::operations_research::math_opt::IndicatorConstraintProto > & new_constraints() const
const ::operations_research::math_opt::ObjectiveProto & objective() const
const ::google::protobuf::Map<::int64_t, ::operations_research::math_opt::ObjectiveProto > & auxiliary_objectives() const
const ::operations_research::math_opt::SparseInt32VectorProto & branching_priorities() const
const ::operations_research::math_opt::SolutionHintProto & solution_hints(int index) const
::int64_t lazy_linear_constraint_ids(int index) const
const ::operations_research::math_opt::BasisProto & initial_basis() const
bool has_initial_basis() const
int solution_hints_size() const
const ::operations_research::math_opt::SosConstraintUpdatesProto & sos1_constraint_updates() const
::int64_t deleted_linear_constraint_ids(int index) const
const ::operations_research::math_opt::SparseDoubleMatrixProto & linear_constraint_matrix_updates() const
const ::operations_research::math_opt::LinearConstraintsProto & new_linear_constraints() const
const ::operations_research::math_opt::AuxiliaryObjectivesUpdatesProto & auxiliary_objectives_updates() const
const ::operations_research::math_opt::VariableUpdatesProto & variable_updates() const
const ::operations_research::math_opt::QuadraticConstraintUpdatesProto & quadratic_constraint_updates() const
const ::operations_research::math_opt::SosConstraintUpdatesProto & sos2_constraint_updates() const
const ::operations_research::math_opt::IndicatorConstraintUpdatesProto & indicator_constraint_updates() const
const ::operations_research::math_opt::ObjectiveUpdatesProto & objective_updates() const
const ::operations_research::math_opt::SecondOrderConeConstraintUpdatesProto & second_order_cone_constraint_updates() const
bool has_objective_updates() const
const ::operations_research::math_opt::LinearConstraintUpdatesProto & linear_constraint_updates() const
::int64_t deleted_variable_ids(int index) const
const ::operations_research::math_opt::VariablesProto & new_variables() const
const ::operations_research::math_opt::SparseDoubleMatrixProto & quadratic_coefficients() const
double offset_update() const
const ::operations_research::math_opt::SparseDoubleMatrixProto & quadratic_coefficients() const
const ::operations_research::math_opt::SparseDoubleVectorProto & linear_coefficients() const
bool direction_update() const
bool has_direction_update() const
bool has_offset_update() const
const ::google::protobuf::Map<::int64_t, ::operations_research::math_opt::QuadraticConstraintProto > & new_constraints() const
::int64_t deleted_constraint_ids(int index) const
const ::google::protobuf::Map<::int64_t, ::operations_research::math_opt::SecondOrderConeConstraintProto > & new_constraints() const
::int64_t deleted_constraint_ids(int index) const
const ::operations_research::math_opt::SparseDoubleVectorProto & variable_values() const
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >( const CallbackDataProto &)> Callback
const ::google::protobuf::Map<::int64_t, ::operations_research::math_opt::SosConstraintProto > & new_constraints() const
::int64_t deleted_constraint_ids(int index) const
bool values(int index) const
::int64_t ids(int index) const
::int64_t row_ids(int index) const
const ::operations_research::math_opt::SparseDoubleVectorProto & upper_bounds() const
bool has_integers() const
const ::operations_research::math_opt::SparseDoubleVectorProto & lower_bounds() const
const ::operations_research::math_opt::SparseBoolVectorProto & integers() const
#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_INT_ATTR_IIS_QCONSTR
#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_OBJNUMBER
#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_DBL_ATTR_OBJNVAL
#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_INT_ATTR_IIS_UB
#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_ATTR_IIS_GENCONSTR
#define GRB_INT_ATTR_IIS_MINIMAL
#define GRB_INT_PAR_STARTNUMBER
#define GRB_CHAR_ATTR_QCSENSE
#define GRB_INT_ATTR_IIS_SOS
#define GRB_DBL_ATTR_QCPI
#define GRB_INT_ATTR_CBASIS
#define GRB_INT_ATTR_IIS_CONSTR
#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_INT_ATTR_IIS_LB
#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
#define GRB_INT_ATTR_LAZY
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
auto & InsertKeyOrDie(Collection *const collection, const typename Collection::value_type::first_type &key)
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
std::vector< bool > EventToGurobiWhere(const absl::flat_hash_set< CallbackEventProto > &events)
absl::Status ModelSolveParametersAreSupported(const ModelSolveParametersProto &model_parameters, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
absl::StatusOr< GRBenvUniquePtr > NewPrimaryEnvironment(std::optional< GurobiInitializerProto::ISVKey > proto_isv_key)
std::function< CallbackResult(const CallbackData &)> Callback
@ LP_ALGORITHM_DUAL_SIMPLEX
@ LP_ALGORITHM_FIRST_ORDER
@ LP_ALGORITHM_UNSPECIFIED
@ LP_ALGORITHM_PRIMAL_SIMPLEX
absl::flat_hash_set< CallbackEventProto > EventSet(const CallbackRegistrationProto &callback_registration)
TerminationProto OptimalTerminationProto(const double finite_primal_objective, const double dual_objective, const absl::string_view detail)
TerminationProto LimitTerminationProto(const bool is_maximize, const LimitProto limit, const std::optional< double > optional_finite_primal_objective, const std::optional< double > optional_dual_objective, const absl::string_view detail)
TerminationProto InfeasibleOrUnboundedTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
bool UpdateIsSupported(const ModelUpdateProto &update, const SupportedProblemStructures &support_menu)
std::function< void(const std::vector< std::string > &)> MessageCallback
ElementId< ElementType::kVariable > VariableId
@ CALLBACK_EVENT_PRESOLVE
@ CALLBACK_EVENT_MIP_NODE
@ CALLBACK_EVENT_MIP_SOLUTION
void GurobiCallbackImplFlush(const GurobiCallbackInput &callback_input, MessageCallbackData &message_callback_data)
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto ®istration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
ElementId< ElementType::kLinearConstraint > LinearConstraintId
@ BASIS_STATUS_AT_UPPER_BOUND
@ BASIS_STATUS_FIXED_VALUE
@ BASIS_STATUS_UNSPECIFIED
@ BASIS_STATUS_AT_LOWER_BOUND
absl::Status GurobiCallbackImpl(const Gurobi::CallbackContext &context, const GurobiCallbackInput &callback_input, MessageCallbackData &message_callback_data, SolveInterrupter *const local_interrupter)
std::unique_ptr< GRBenv, GurobiFreeEnv > GRBenvUniquePtr
TerminationProto InfeasibleTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
TerminationProto CutoffTerminationProto(bool is_maximize, const absl::string_view detail)
TerminationProto UnboundedTerminationProto(const bool is_maximize, const absl::string_view detail)
std::vector< K > SortedMapKeys(const absl::flat_hash_map< K, V > &in_map)
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
std::string ProtoEnumToString(ProtoEnumType enum_value)
constexpr bool kAnyXsanEnabled
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
StatusBuilder InternalErrorBuilder()
StatusBuilder UnimplementedErrorBuilder()
StatusBuilder InvalidArgumentErrorBuilder()
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)
absl::Status ToStatus() const
absl::Status ToStatus() const
const NonStreamableGurobiInitArguments * ToNonStreamableGurobiInitArguments() const override