26 #include <string_view>
28 #include <type_traits>
32 #include "absl/base/thread_annotations.h"
33 #include "absl/container/flat_hash_map.h"
34 #include "absl/memory/memory.h"
35 #include "absl/status/status.h"
36 #include "absl/status/statusor.h"
37 #include "absl/strings/str_cat.h"
38 #include "absl/strings/str_join.h"
39 #include "absl/strings/string_view.h"
40 #include "absl/synchronization/mutex.h"
41 #include "absl/time/clock.h"
42 #include "absl/time/time.h"
49 #include "ortools/math_opt/callback.pb.h"
56 #include "ortools/math_opt/model.pb.h"
57 #include "ortools/math_opt/model_parameters.pb.h"
58 #include "ortools/math_opt/model_update.pb.h"
59 #include "ortools/math_opt/parameters.pb.h"
60 #include "ortools/math_opt/result.pb.h"
61 #include "ortools/math_opt/solution.pb.h"
65 #include "ortools/math_opt/sparse_containers.pb.h"
74 constexpr
double kInf = std::numeric_limits<double>::infinity();
76 constexpr SupportedProblemStructures kGlpkSupportedStructures = {
95 template <
typename Dimension>
96 void SetBounds(glp_prob*
const problem,
const int k,
const Bounds&
bounds) {
98 const bool is_integer = Dimension::IsInteger(problem, k);
99 const double lb = is_integer ? std::ceil(
bounds.lower) :
bounds.lower;
100 const double ub = is_integer ? std::floor(
bounds.upper) :
bounds.upper;
102 if (std::isinf(lb) && std::isinf(ub)) {
104 }
else if (std::isinf(lb)) {
106 }
else if (std::isinf(ub)) {
108 }
else if (lb == ub) {
113 Dimension::kSetBounds(problem, k, type, lb, ub);
121 template <
typename Dimension>
122 Bounds GetBounds(glp_prob*
const problem,
const int k) {
123 const int type = Dimension::kGetType(problem, k);
128 return {.lower = Dimension::kGetLb(problem, k)};
130 return {.upper = Dimension::kGetUb(problem, k)};
133 return {.lower = Dimension::kGetLb(problem, k),
134 .upper = Dimension::kGetUb(problem, k)};
149 template <
typename Dimension>
150 void UpdateBounds(glp_prob*
const problem,
const Dimension& dimension,
151 const SparseDoubleVectorProto& lower_bounds_proto,
152 const SparseDoubleVectorProto& upper_bounds_proto) {
160 std::optional<int64_t> next_id;
162 if (!next_id.has_value() || current_lower_bound->first < *next_id) {
163 next_id = current_lower_bound->first;
167 if (!next_id.has_value() || current_upper_bound->first < *next_id) {
168 next_id = current_upper_bound->first;
172 if (!next_id.has_value()) {
178 const int row_or_col_index = dimension.id_to_index.at(*next_id);
179 CHECK_EQ(dimension.ids[row_or_col_index - 1], *next_id);
183 Bounds
bounds = GetBounds<Dimension>(problem,
186 current_lower_bound->first == *next_id) {
187 bounds.lower = current_lower_bound->second;
188 ++current_lower_bound;
191 current_upper_bound->first == *next_id) {
192 bounds.upper = current_upper_bound->second;
193 ++current_upper_bound;
195 SetBounds<Dimension>(problem, row_or_col_index,
208 template <
typename V>
209 void DeleteRowOrColData(std::vector<V>& data,
210 const std::vector<int>& sorted_deleted_rows_or_cols) {
211 if (sorted_deleted_rows_or_cols.empty()) {
216 std::size_t next_insertion_point = 0;
217 std::size_t current_row_or_col = 0;
218 for (std::size_t i = 1; i < sorted_deleted_rows_or_cols.size(); ++i) {
219 const int deleted_row_or_col = sorted_deleted_rows_or_cols[i];
220 for (; current_row_or_col + 1 < deleted_row_or_col;
221 ++current_row_or_col, ++next_insertion_point) {
222 DCHECK_LT(current_row_or_col, data.size());
223 data[next_insertion_point] = data[current_row_or_col];
226 ++current_row_or_col;
228 for (; current_row_or_col < data.size();
229 ++current_row_or_col, ++next_insertion_point) {
230 data[next_insertion_point] = data[current_row_or_col];
232 data.resize(next_insertion_point);
243 template <
typename Dimension>
244 std::vector<int> DeleteRowsOrCols(
245 glp_prob*
const problem, Dimension& dimension,
246 const google::protobuf::RepeatedField<int64_t>& deleted_ids) {
247 if (deleted_ids.empty()) {
254 std::vector<int> deleted_rows_or_cols;
257 deleted_rows_or_cols.reserve(deleted_ids.size() + 1);
258 deleted_rows_or_cols.push_back(-1);
259 for (
const int64_t deleted_id : deleted_ids) {
260 deleted_rows_or_cols.push_back(dimension.id_to_index.at(deleted_id));
262 Dimension::kDelElts(problem, deleted_rows_or_cols.size() - 1,
263 deleted_rows_or_cols.data());
269 std::is_sorted(deleted_rows_or_cols.begin(), deleted_rows_or_cols.end()));
272 DeleteRowOrColData(dimension.ids, deleted_rows_or_cols);
275 for (
const int64_t deleted_id : deleted_ids) {
276 CHECK(dimension.id_to_index.erase(deleted_id));
278 for (
int i = 0; i < dimension.ids.size(); ++i) {
279 dimension.id_to_index.at(dimension.ids[i]) = i + 1;
282 return deleted_rows_or_cols;
291 std::vector<int> MatrixIds(
292 const google::protobuf::RepeatedField<int64_t>& proto_ids,
293 const absl::flat_hash_map<int64_t, int>& id_to_index) {
294 std::vector<int> ids;
295 ids.reserve(proto_ids.size() + 1);
298 for (
const int64_t proto_id : proto_ids) {
299 ids.push_back(id_to_index.at(proto_id));
307 std::vector<double> MatrixCoefficients(
308 const google::protobuf::RepeatedField<double>& proto_coeffs) {
309 std::vector<double> coeffs(proto_coeffs.size() + 1);
312 std::copy(proto_coeffs.begin(), proto_coeffs.end(), coeffs.begin() + 1);
317 bool IsMip(glp_prob*
const problem) {
318 const int num_vars = glp_get_num_cols(problem);
319 for (
int v = 1; v <= num_vars; ++v) {
320 if (glp_get_col_kind(problem, v) != GLP_CV) {
328 bool IsEmpty(glp_prob*
const problem) {
329 return glp_get_num_cols(problem) == 0 && glp_get_num_rows(problem) == 0;
334 SparseDoubleVectorProto FilteredVector(glp_prob*
const problem,
335 const SparseVectorFilterProto& filter,
336 const std::vector<int64_t>& ids,
337 double (*
const getter)(glp_prob*,
int)) {
338 SparseDoubleVectorProto vec;
339 vec.mutable_ids()->Reserve(ids.size());
340 vec.mutable_values()->Reserve(ids.size());
342 SparseVectorFilterPredicate predicate(filter);
343 for (
int i = 0; i < ids.size(); ++i) {
344 const double value = getter(problem, i + 1);
345 if (predicate.AcceptsAndUpdate(ids[i],
value)) {
347 vec.add_values(
value);
355 SparseDoubleVectorProto FilteredRay(
const SparseVectorFilterProto& filter,
356 const std::vector<int64_t>& ids,
357 const std::vector<double>& values) {
358 CHECK_EQ(ids.size(), values.size());
359 SparseDoubleVectorProto vec;
360 SparseVectorFilterPredicate predicate(filter);
361 for (
int i = 0; i < ids.size(); ++i) {
362 if (predicate.AcceptsAndUpdate(ids[i], values[i])) {
364 vec.add_values(values[i]);
375 template <
typename Parameters>
376 absl::Status SetSharedParameters(
const SolveParametersProto&
parameters,
377 const bool has_message_callback,
378 Parameters& glpk_parameters) {
379 std::vector<std::string> warnings;
382 absl::StrCat(
"GLPK only supports parameters.threads = 1; value ",
385 if (
parameters.enable_output() || has_message_callback) {
386 glpk_parameters.msg_lev = GLP_MSG_ALL;
388 glpk_parameters.msg_lev = GLP_MSG_OFF;
391 warnings.push_back(
"Parameter node_limit not supported by GLPK");
394 warnings.push_back(
"Parameter objective_limit not supported by GLPK");
397 warnings.push_back(
"Parameter best_bound_limit not supported by GLPK");
400 warnings.push_back(
"Parameter cutoff_limit not supported by GLPK");
403 warnings.push_back(
"Parameter solution_limit not supported by GLPK");
405 if (!warnings.empty()) {
406 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
408 return absl::OkStatus();
415 template <
typename Parameters>
416 void SetTimeLimitParameter(
const SolveParametersProto&
parameters,
417 Parameters& glpk_parameters) {
419 const int64_t time_limit_ms = absl::ToInt64Milliseconds(
421 glpk_parameters.tm_lim =
static_cast<int>(
std::min(
428 absl::Status SetLPParameters(
const SolveParametersProto&
parameters,
429 glp_smcp& glpk_parameters) {
430 std::vector<std::string> warnings;
432 case EMPHASIS_UNSPECIFIED:
438 glpk_parameters.presolve = GLP_OFF;
441 glpk_parameters.presolve = GLP_ON;
445 case LP_ALGORITHM_UNSPECIFIED:
447 case LP_ALGORITHM_PRIMAL_SIMPLEX:
448 glpk_parameters.meth = GLP_PRIMAL;
450 case LP_ALGORITHM_DUAL_SIMPLEX:
456 glpk_parameters.meth = GLP_DUALP;
459 warnings.push_back(absl::StrCat(
460 "GLPK does not support ",
462 " for parameters.lp_algorithm"));
465 if (!warnings.empty()) {
466 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
468 return absl::OkStatus();
471 class MipCallbackData {
473 explicit MipCallbackData(SolveInterrupter*
const interrupter)
474 : interrupter_(interrupter) {}
476 void Callback(glp_tree*
const tree) {
479 switch (glp_ios_reason(tree)) {
493 if (
const int best_node = glp_ios_best_node(tree); best_node != 0) {
494 best_bound_ = glp_ios_node_bound(tree, best_node);
508 if (interrupter_ !=
nullptr && interrupter_->IsInterrupted()) {
509 glp_ios_terminate(tree);
510 interrupted_by_interrupter_ =
true;
515 bool HasBeenInterruptedByInterrupter()
const {
516 return interrupted_by_interrupter_.load();
519 std::optional<double> best_bound()
const {
return best_bound_; }
523 SolveInterrupter*
const interrupter_;
526 std::atomic<bool> interrupted_by_interrupter_ =
false;
529 std::optional<double> best_bound_;
532 void MipCallback(glp_tree*
const tree,
void*
const info) {
533 static_cast<MipCallbackData*
>(info)->
Callback(tree);
537 InvertedBounds ListInvertedBounds(
538 glp_prob*
const problem,
const std::vector<int64_t>&
variable_ids,
539 const std::vector<int64_t>& linear_constraint_ids) {
540 InvertedBounds inverted_bounds;
542 const int num_cols = glp_get_num_cols(problem);
543 for (
int c = 1; c <= num_cols; ++c) {
544 if (glp_get_col_lb(problem, c) > glp_get_col_ub(problem, c)) {
545 inverted_bounds.variables.push_back(
variable_ids[c - 1]);
549 const int num_rows = glp_get_num_rows(problem);
550 for (
int r = 1; r <= num_rows; ++r) {
551 if (glp_get_row_lb(problem, r) > glp_get_row_ub(problem, r)) {
552 inverted_bounds.linear_constraints.push_back(
553 linear_constraint_ids[r - 1]);
557 return inverted_bounds;
563 absl::StatusOr<TerminationProto> MipTerminationOnSuccess(
564 glp_prob*
const problem) {
565 const int status = glp_mip_status(problem);
571 "glp_mip_status() returned GLP_FEAS");
575 return absl::InternalError(
576 absl::StrCat(
"glp_intopt() returned 0 but glp_mip_status()"
577 "returned the unexpected value ",
585 absl::StatusOr<TerminationProto> InteriorTerminationOnSuccess(
586 glp_prob*
const problem) {
587 const int status = glp_ipt_status(problem);
593 "glp_ipt_status() returned GLP_INFEAS");
605 return absl::InternalError(
606 absl::StrCat(
"glp_interior() returned 0 but glp_ipt_status()"
607 "returned the unexpected value ",
615 absl::StatusOr<TerminationProto> SimplexTerminationOnSuccess(
616 glp_prob*
const problem) {
624 const int prim_status = glp_get_prim_stat(problem);
625 const int dual_status = glp_get_dual_stat(problem);
628 const auto undetermined_limit = [&]() {
629 const std::string detail = absl::StrCat(
631 " and glp_get_dual_stat() returned ",
633 if (prim_status == GLP_FEAS) {
641 const auto unexpected_dual_stat = [&]() {
642 return absl::InternalError(
643 absl::StrCat(
"glp_simplex() returned 0 but glp_get_dual_stat() "
644 "returned the unexpected value ",
648 switch (prim_status) {
650 switch (dual_status) {
657 return undetermined_limit();
661 return unexpected_dual_stat();
664 switch (dual_status) {
667 return undetermined_limit();
671 return unexpected_dual_stat();
674 switch (dual_status) {
687 return unexpected_dual_stat();
690 return absl::InternalError(
691 absl::StrCat(
"glp_simplex() returned 0 but glp_get_prim_stat() "
692 "returned the unexpected value ",
710 absl::StatusOr<TerminationProto> BuildTermination(
711 glp_prob*
const problem,
const std::string_view fn_name,
const int rc,
712 const std::function<absl::StatusOr<TerminationProto>(glp_prob*)>
713 termination_on_success,
714 MipCallbackData*
const mip_cb_data,
const bool has_feasible_solution,
716 const std::vector<int64_t>& linear_constraint_ids) {
717 if (mip_cb_data !=
nullptr &&
718 mip_cb_data->HasBeenInterruptedByInterrupter()) {
720 has_feasible_solution);
727 return termination_on_success(problem);
734 ListInvertedBounds(problem,
736 linear_constraint_ids)
740 <<
"` but the model does not contain variables with inverted "
745 has_feasible_solution);
750 TERMINATION_REASON_OPTIMAL,
753 absl::StrCat(std::string(fn_name),
"() returned ",
757 has_feasible_solution);
769 has_feasible_solution);
773 TERMINATION_REASON_NUMERICAL_ERROR,
776 absl::StrCat(std::string(fn_name),
"() returned ",
778 " which means that there is a numeric stability issue "
779 "solving Newtonian system"));
792 void Parse(
const std::string_view
message) {
797 const absl::MutexLock lock(&mutex_);
798 std::vector<std::string> new_lines = buffer_.Parse(
message);
799 if (!new_lines.empty()) {
800 callback_(new_lines);
807 const absl::MutexLock lock(&mutex_);
808 std::vector<std::string> new_lines = buffer_.Flush();
809 if (!new_lines.empty()) {
810 callback_(new_lines);
816 MessageCallbackData buffer_ ABSL_GUARDED_BY(mutex_);
823 int TermHook(
void*
const info,
const char*
const message) {
824 static_cast<TermHookData*
>(info)->Parse(
message);
833 double OffsetOnlyObjVal(glp_prob*
const problem) {
834 return glp_get_obj_coef(problem, 0);
840 int OptStatus(glp_prob*) {
return GLP_OPT; }
850 GlpkSolver::GlpkSolver(
const ModelProto&
model)
851 : thread_id_(std::this_thread::get_id()), problem_(glp_create_prob()) {
857 AddVariables(
model.variables());
859 AddLinearConstraints(
model.linear_constraints());
861 glp_set_obj_dir(problem_,
model.objective().maximize() ? GLP_MAX : GLP_MIN);
863 glp_set_obj_coef(problem_, 0,
model.objective().offset());
864 for (
const auto [v, coeff] :
866 const int col_index = variables_.id_to_index.at(v);
867 CHECK_EQ(variables_.ids[col_index - 1], v);
868 glp_set_obj_coef(problem_, col_index, coeff);
871 const SparseDoubleMatrixProto& proto_matrix =
872 model.linear_constraint_matrix();
874 problem_, proto_matrix.row_ids_size(),
875 MatrixIds(proto_matrix.row_ids(), linear_constraints_.id_to_index).data(),
876 MatrixIds(proto_matrix.column_ids(), variables_.id_to_index).data(),
877 MatrixCoefficients(proto_matrix.coefficients()).data());
883 if (
const absl::Status
status = CheckCurrentThread(); !
status.ok()) {
886 glp_delete_prob(problem_);
891 ProblemStatusProto GetMipProblemStatusProto(
const int rc,
const int mip_status,
892 const bool has_finite_dual_bound) {
893 ProblemStatusProto problem_status;
894 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
895 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
899 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
900 return problem_status;
902 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
903 return problem_status;
906 switch (mip_status) {
908 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
909 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
910 return problem_status;
912 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
915 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
919 if (has_finite_dual_bound) {
920 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
922 return problem_status;
925 absl::StatusOr<FeasibilityStatusProto> TranslateProblemStatus(
926 const int glpk_status,
const absl::string_view fn_name) {
927 switch (glpk_status) {
929 return FEASIBILITY_STATUS_FEASIBLE;
931 return FEASIBILITY_STATUS_INFEASIBLE;
934 return FEASIBILITY_STATUS_UNDETERMINED;
936 return absl::InternalError(
937 absl::StrCat(fn_name,
" returned the unexpected value ",
946 absl::StatusOr<ProblemStatusProto> GetSimplexProblemStatusProto(
947 const int glp_simplex_rc,
const int glpk_primal_status,
948 const int glpk_dual_status) {
949 ProblemStatusProto problem_status;
950 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
951 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
953 switch (glp_simplex_rc) {
956 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
957 return problem_status;
960 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
961 return problem_status;
965 const FeasibilityStatusProto primal_status,
966 TranslateProblemStatus(glpk_primal_status,
"glp_get_prim_stat"));
967 problem_status.set_primal_status(primal_status);
971 const FeasibilityStatusProto dual_status,
972 TranslateProblemStatus(glpk_dual_status,
"glp_get_dual_stat"));
973 problem_status.set_dual_status(dual_status);
974 return problem_status;
979 absl::StatusOr<ProblemStatusProto> GetBarrierProblemStatusProto(
980 const int glp_interior_rc,
const int ipt_status) {
981 ProblemStatusProto problem_status;
982 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
983 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
985 switch (glp_interior_rc) {
988 switch (ipt_status) {
990 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
991 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
992 return problem_status;
994 return problem_status;
996 problem_status.set_primal_or_dual_infeasible(
true);
997 return problem_status;
999 return problem_status;
1001 return absl::InternalError(
1002 absl::StrCat(
"glp_ipt_status returned the unexpected value ",
1006 return problem_status;
1014 const ModelSolveParametersProto& model_parameters,
1016 const CallbackRegistrationProto& callback_registration,
1020 const absl::Time
start = absl::Now();
1025 std::unique_ptr<TermHookData> term_hook_data;
1026 if (message_cb !=
nullptr) {
1027 term_hook_data = std::make_unique<TermHookData>(std::move(message_cb));
1033 glp_term_hook(TermHook, term_hook_data.get());
1039 if (term_hook_data !=
nullptr) {
1040 glp_term_hook(
nullptr,
nullptr);
1044 SolveResultProto result;
1046 const bool is_mip = IsMip(problem_);
1050 int (*get_prim_stat)(glp_prob*) =
nullptr;
1051 double (*obj_val)(glp_prob*) =
nullptr;
1052 double (*col_val)(glp_prob*, int) =
nullptr;
1054 int (*get_dual_stat)(glp_prob*) =
nullptr;
1055 double (*row_dual)(glp_prob*, int) =
nullptr;
1056 double (*col_dual)(glp_prob*, int) =
nullptr;
1058 const bool maximize = glp_get_obj_dir(problem_) == GLP_MAX;
1059 double best_dual_bound = maximize ?
kInf : -
kInf;
1072 get_prim_stat = glp_mip_status;
1073 obj_val = glp_mip_obj_val;
1074 col_val = glp_mip_col_val;
1076 glp_iocp glpk_parameters;
1077 glp_init_iocp(&glpk_parameters);
1080 term_hook_data !=
nullptr, glpk_parameters));
1081 SetTimeLimitParameter(
parameters, glpk_parameters);
1086 glpk_parameters.presolve = GLP_ON;
1087 MipCallbackData mip_cb_data(interrupter);
1088 glpk_parameters.cb_func = MipCallback;
1089 glpk_parameters.cb_info = &mip_cb_data;
1090 const int rc = glp_intopt(problem_, &glpk_parameters);
1091 const int mip_status = glp_mip_status(problem_);
1092 const bool has_feasible_solution =
1093 mip_status == GLP_OPT || mip_status == GLP_FEAS;
1095 *result.mutable_termination(),
1096 BuildTermination(problem_,
"glp_intopt", rc, MipTerminationOnSuccess,
1098 has_feasible_solution,
1100 linear_constraints_.ids));
1101 if (mip_cb_data.best_bound().has_value()) {
1102 best_dual_bound = *mip_cb_data.best_bound();
1104 *result.mutable_solve_stats()->mutable_problem_status() =
1105 GetMipProblemStatusProto(rc, mip_status,
1106 std::isfinite(best_dual_bound));
1108 if (
parameters.lp_algorithm() == LP_ALGORITHM_BARRIER) {
1109 get_prim_stat = glp_ipt_status;
1110 obj_val = glp_ipt_obj_val;
1111 col_val = glp_ipt_col_prim;
1113 get_dual_stat = glp_ipt_status;
1114 row_dual = glp_ipt_row_dual;
1115 col_dual = glp_ipt_col_dual;
1117 glp_iptcp glpk_parameters;
1118 glp_init_iptcp(&glpk_parameters);
1120 return absl::InvalidArgumentError(
1121 "Parameter time_limit not supported by GLPK for interior point "
1126 term_hook_data !=
nullptr, glpk_parameters));
1134 if (IsEmpty(problem_)) {
1135 get_prim_stat = OptStatus;
1136 get_dual_stat = OptStatus;
1137 obj_val = OffsetOnlyObjVal;
1139 TERMINATION_REASON_OPTIMAL,
1140 "glp_interior() not called since the model is empty");
1141 result.mutable_solve_stats()
1142 ->mutable_problem_status()
1143 ->set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
1144 result.mutable_solve_stats()->mutable_problem_status()->set_dual_status(
1145 FEASIBILITY_STATUS_FEASIBLE);
1149 const int glp_interior_rc = glp_interior(problem_, &glpk_parameters);
1150 const int ipt_status = glp_ipt_status(problem_);
1151 const bool has_feasible_solution = ipt_status == GLP_OPT;
1153 *result.mutable_termination(),
1155 problem_,
"glp_interior", glp_interior_rc,
1156 InteriorTerminationOnSuccess,
1158 has_feasible_solution,
1160 linear_constraints_.ids));
1162 *result.mutable_solve_stats()->mutable_problem_status(),
1163 GetBarrierProblemStatusProto(glp_interior_rc,
1167 get_prim_stat = glp_get_prim_stat;
1168 obj_val = glp_get_obj_val;
1169 col_val = glp_get_col_prim;
1171 get_dual_stat = glp_get_dual_stat;
1172 row_dual = glp_get_row_dual;
1173 col_dual = glp_get_col_dual;
1175 glp_smcp glpk_parameters;
1176 glp_init_smcp(&glpk_parameters);
1179 term_hook_data !=
nullptr, glpk_parameters));
1180 SetTimeLimitParameter(
parameters, glpk_parameters);
1184 const int glp_simplex_rc = glp_simplex(problem_, &glpk_parameters);
1185 const int prim_stat = glp_get_prim_stat(problem_);
1186 const bool has_feasible_solution = prim_stat == GLP_FEAS;
1188 *result.mutable_termination(),
1189 BuildTermination(problem_,
"glp_simplex", glp_simplex_rc,
1190 SimplexTerminationOnSuccess,
1192 has_feasible_solution,
1194 linear_constraints_.ids));
1197 GetSimplexProblemStatusProto(
1200 glp_get_dual_stat(problem_)));
1201 VLOG(1) <<
"glp_get_status: "
1204 <<
" glp_get_dual_stat: "
1210 if (term_hook_data !=
nullptr) {
1212 std::move(message_cb_cleanup).Invoke();
1213 term_hook_data->Flush();
1214 term_hook_data.reset();
1217 double best_primal_bound = maximize ? -
kInf :
kInf;
1218 switch (get_prim_stat(problem_)) {
1221 best_primal_bound = obj_val(problem_);
1224 result.mutable_solve_stats()->set_best_primal_bound(best_primal_bound);
1228 result.mutable_solve_stats()->set_best_dual_bound(best_dual_bound);
1229 SolutionProto solution;
1230 AddPrimalSolution(get_prim_stat, obj_val, col_val, model_parameters,
1233 AddDualSolution(get_dual_stat, obj_val, row_dual, col_dual,
1234 model_parameters, solution);
1236 if (solution.has_primal_solution() || solution.has_dual_solution() ||
1237 solution.has_basis()) {
1238 *result.add_solutions() = std::move(solution);
1246 absl::Now() -
start, result.mutable_solve_stats()->mutable_solve_time()));
1250 void GlpkSolver::AddVariables(
const VariablesProto& new_variables) {
1251 if (new_variables.ids().empty()) {
1256 const int first_new_var_index = variables_.ids.size() + 1;
1258 variables_.ids.insert(variables_.ids.end(), new_variables.ids().begin(),
1259 new_variables.ids().end());
1260 for (
int v = 0; v < new_variables.ids_size(); ++v) {
1261 CHECK(variables_.id_to_index
1262 .try_emplace(new_variables.ids(v), first_new_var_index + v)
1265 glp_add_cols(problem_, new_variables.ids_size());
1266 if (!new_variables.names().empty()) {
1267 for (
int v = 0; v < new_variables.names_size(); ++v) {
1269 problem_, v + first_new_var_index,
1273 CHECK_EQ(new_variables.lower_bounds_size(),
1274 new_variables.upper_bounds_size());
1275 CHECK_EQ(new_variables.lower_bounds_size(), new_variables.ids_size());
1276 variables_.unrounded_lower_bounds.insert(
1277 variables_.unrounded_lower_bounds.end(),
1278 new_variables.lower_bounds().begin(), new_variables.lower_bounds().end());
1279 variables_.unrounded_upper_bounds.insert(
1280 variables_.unrounded_upper_bounds.end(),
1281 new_variables.upper_bounds().begin(), new_variables.upper_bounds().end());
1282 for (
int i = 0; i < new_variables.lower_bounds_size(); ++i) {
1289 glp_set_col_kind(problem_, i + first_new_var_index,
1290 new_variables.integers(i) ? GLP_IV : GLP_CV);
1291 SetBounds<Variables>(problem_, i + first_new_var_index,
1292 {.lower = new_variables.lower_bounds(i),
1293 .
upper = new_variables.upper_bounds(i)});
1297 void GlpkSolver::AddLinearConstraints(
1298 const LinearConstraintsProto& new_linear_constraints) {
1299 if (new_linear_constraints.ids().empty()) {
1304 const int first_new_cstr_index = linear_constraints_.ids.size() + 1;
1306 linear_constraints_.ids.insert(linear_constraints_.ids.end(),
1307 new_linear_constraints.ids().begin(),
1308 new_linear_constraints.ids().end());
1309 for (
int c = 0; c < new_linear_constraints.ids_size(); ++c) {
1310 CHECK(linear_constraints_.id_to_index
1311 .try_emplace(new_linear_constraints.ids(c),
1312 first_new_cstr_index + c)
1315 glp_add_rows(problem_, new_linear_constraints.ids_size());
1316 if (!new_linear_constraints.names().empty()) {
1317 for (
int c = 0; c < new_linear_constraints.names_size(); ++c) {
1319 problem_, c + first_new_cstr_index,
1323 CHECK_EQ(new_linear_constraints.lower_bounds_size(),
1324 new_linear_constraints.upper_bounds_size());
1325 for (
int i = 0; i < new_linear_constraints.lower_bounds_size(); ++i) {
1326 SetBounds<LinearConstraints>(
1327 problem_, i + first_new_cstr_index,
1328 {.lower = new_linear_constraints.lower_bounds(i),
1329 .
upper = new_linear_constraints.upper_bounds(i)});
1333 void GlpkSolver::UpdateObjectiveCoefficients(
1334 const SparseDoubleVectorProto& coefficients_proto) {
1335 for (
const auto [
id, coeff] :
MakeView(coefficients_proto)) {
1336 const int col_index = variables_.id_to_index.at(
id);
1337 CHECK_EQ(variables_.ids[col_index - 1],
id);
1338 glp_set_obj_coef(problem_, col_index, coeff);
1342 void GlpkSolver::UpdateLinearConstraintMatrix(
1343 const SparseDoubleMatrixProto& matrix_updates,
1344 const std::optional<int64_t> first_new_var_id,
1345 const std::optional<int64_t> first_new_cstr_id) {
1379 GlpkSparseVector data(
static_cast<int>(variables_.ids.size()));
1380 for (
const auto& [row_id, row_coefficients] :
1385 first_new_var_id)) {
1388 const int row_index = linear_constraints_.id_to_index.at(row_id);
1389 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1392 data.Load([&](
int*
const indices,
double*
const values) {
1393 return glp_get_mat_row(problem_, row_index, indices, values);
1397 for (
const auto [col_id,
coefficient] : row_coefficients) {
1398 const int col_index = variables_.id_to_index.at(col_id);
1399 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1404 glp_set_mat_row(problem_, row_index, data.size(), data.indices(),
1411 if (first_new_var_id.has_value()) {
1412 GlpkSparseVector data(
static_cast<int>(linear_constraints_.ids.size()));
1421 const int col_index = variables_.id_to_index.at(col_id);
1422 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1428 const int row_index = linear_constraints_.id_to_index.at(row_id);
1429 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1434 glp_set_mat_col(problem_, col_index, data.size(), data.indices(),
1440 if (first_new_cstr_id.has_value()) {
1441 GlpkSparseVector data(
static_cast<int>(variables_.ids.size()));
1442 for (
const auto& [row_id, row_coefficients] :
1450 const int row_index = linear_constraints_.id_to_index.at(row_id);
1451 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1456 for (
const auto [col_id,
coefficient] : row_coefficients) {
1457 const int col_index = variables_.id_to_index.at(col_id);
1458 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1463 glp_set_mat_row(problem_, row_index, data.size(), data.indices(),
1469 void GlpkSolver::AddPrimalSolution(
1470 int (*get_prim_stat)(glp_prob*),
double (*obj_val)(glp_prob*),
1471 double (*col_val)(glp_prob*,
int),
1472 const ModelSolveParametersProto& model_parameters,
1473 SolutionProto& solution_proto) {
1474 const int status = get_prim_stat(problem_);
1476 PrimalSolutionProto& primal_solution =
1477 *solution_proto.mutable_primal_solution();
1478 primal_solution.set_objective_value(obj_val(problem_));
1479 primal_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1480 *primal_solution.mutable_variable_values() =
1481 FilteredVector(problem_, model_parameters.variable_values_filter(),
1482 variables_.ids, col_val);
1486 void GlpkSolver::AddDualSolution(
1487 int (*get_dual_stat)(glp_prob*),
double (*obj_val)(glp_prob*),
1488 double (*row_dual)(glp_prob*,
int),
double (*col_dual)(glp_prob*,
int),
1489 const ModelSolveParametersProto& model_parameters,
1490 SolutionProto& solution_proto) {
1491 const int status = get_dual_stat(problem_);
1493 DualSolutionProto& dual_solution = *solution_proto.mutable_dual_solution();
1494 dual_solution.set_objective_value(obj_val(problem_));
1495 *dual_solution.mutable_dual_values() =
1496 FilteredVector(problem_, model_parameters.dual_values_filter(),
1497 linear_constraints_.ids, row_dual);
1498 *dual_solution.mutable_reduced_costs() =
1499 FilteredVector(problem_, model_parameters.reduced_costs_filter(),
1500 variables_.ids, col_dual);
1504 dual_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1508 absl::Status GlpkSolver::AddPrimalOrDualRay(
1509 const ModelSolveParametersProto& model_parameters,
1510 SolveResultProto& result) {
1513 if (!opt_unbound_ray.has_value()) {
1514 return absl::OkStatus();
1517 const int num_cstrs = linear_constraints_.ids.size();
1518 switch (opt_unbound_ray->type) {
1520 const int num_cstrs = linear_constraints_.ids.size();
1525 std::vector<double> ray_values(variables_.ids.size());
1527 for (
const auto [k,
value] : opt_unbound_ray->non_zero_components) {
1528 if (k <= num_cstrs) {
1532 const int var_index = k - num_cstrs;
1533 CHECK_GE(var_index, 1);
1534 ray_values[var_index - 1] =
value;
1537 *result.add_primal_rays()->mutable_variable_values() =
1538 FilteredRay(model_parameters.variable_values_filter(), variables_.ids,
1541 return absl::OkStatus();
1550 std::vector<double> ray_reduced_costs(variables_.ids.size());
1551 std::vector<double> ray_dual_values(num_cstrs);
1553 for (
const auto [k,
value] : opt_unbound_ray->non_zero_components) {
1554 if (k <= num_cstrs) {
1555 ray_dual_values[k - 1] =
value;
1557 const int var_index = k - num_cstrs;
1558 CHECK_GE(var_index, 1);
1559 ray_reduced_costs[var_index - 1] =
value;
1563 DualRayProto& dual_ray = *result.add_dual_rays();
1564 *dual_ray.mutable_dual_values() =
1565 FilteredRay(model_parameters.dual_values_filter(),
1566 linear_constraints_.ids, ray_dual_values);
1567 *dual_ray.mutable_reduced_costs() =
1568 FilteredRay(model_parameters.reduced_costs_filter(), variables_.ids,
1571 return absl::OkStatus();
1588 const std::vector<int> sorted_deleted_cols = DeleteRowsOrCols(
1589 problem_, variables_, model_update.deleted_variable_ids());
1590 DeleteRowOrColData(variables_.unrounded_lower_bounds, sorted_deleted_cols);
1591 DeleteRowOrColData(variables_.unrounded_upper_bounds, sorted_deleted_cols);
1592 CHECK_EQ(variables_.unrounded_lower_bounds.size(),
1593 variables_.unrounded_upper_bounds.size());
1594 CHECK_EQ(variables_.unrounded_lower_bounds.size(), variables_.ids.size());
1596 DeleteRowsOrCols(problem_, linear_constraints_,
1597 model_update.deleted_linear_constraint_ids());
1599 for (
const auto [var_id, is_integer] :
1600 MakeView(model_update.variable_updates().integers())) {
1602 const int var_index = variables_.id_to_index.at(var_id);
1603 glp_set_col_kind(problem_, var_index, is_integer ? GLP_IV : GLP_CV);
1610 SetBounds<Variables>(
1611 problem_, var_index,
1612 {.lower = variables_.unrounded_lower_bounds[var_index - 1],
1613 .upper = variables_.unrounded_upper_bounds[var_index - 1]});
1616 MakeView(model_update.variable_updates().lower_bounds())) {
1617 variables_.unrounded_lower_bounds[variables_.id_to_index.at(var_id) - 1] =
1621 MakeView(model_update.variable_updates().upper_bounds())) {
1622 variables_.unrounded_upper_bounds[variables_.id_to_index.at(var_id) - 1] =
1626 problem_, variables_,
1627 model_update.variable_updates().lower_bounds(),
1628 model_update.variable_updates().upper_bounds());
1629 UpdateBounds(problem_, linear_constraints_,
1631 model_update.linear_constraint_updates().lower_bounds(),
1633 model_update.linear_constraint_updates().upper_bounds());
1635 AddVariables(model_update.new_variables());
1636 AddLinearConstraints(model_update.new_linear_constraints());
1638 if (model_update.objective_updates().has_direction_update()) {
1639 glp_set_obj_dir(problem_,
1640 model_update.objective_updates().direction_update()
1644 if (model_update.objective_updates().has_offset_update()) {
1646 glp_set_obj_coef(problem_, 0,
1647 model_update.objective_updates().offset_update());
1649 UpdateObjectiveCoefficients(
1650 model_update.objective_updates().linear_coefficients());
1652 UpdateLinearConstraintMatrix(
1653 model_update.linear_constraint_matrix_updates(),
1661 absl::Status GlpkSolver::CheckCurrentThread() {
1662 if (std::this_thread::get_id() != thread_id_) {
1663 return absl::InvalidArgumentError(
1664 "GLPK is not thread-safe and thus the solver should only be used on "
1665 "the same thread as it was created");
1667 return absl::OkStatus();
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const InitArgs &init_args)
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto ¶meters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, SolveInterrupter *interrupter) override
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
SharedBoundsManager * bounds
absl::Span< const int64_t > variable_ids
absl::Cleanup< absl::decay_t< Callback > > MakeCleanup(Callback &&callback)
TerminationProto FeasibleTermination(const LimitProto limit, const absl::string_view detail)
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto ®istration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
std::vector< std::pair< int64_t, SparseVector< double > > > TransposeSparseSubmatrix(const SparseSubmatrixRowsView &submatrix_by_rows)
MATH_OPT_REGISTER_SOLVER(SOLVER_TYPE_CP_SAT, CpSatSolver::New)
std::optional< int64_t > FirstLinearConstraintId(const LinearConstraintsProto &linear_constraints)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
bool UpdateIsSupported(const ModelUpdateProto &update, const SupportedProblemStructures &support_menu)
TerminationProto TerminateForLimit(const LimitProto limit, const bool feasible, const absl::string_view detail)
SparseSubmatrixRowsView SparseSubmatrixByRows(const SparseDoubleMatrixProto &matrix, const int64_t start_row_id, const std::optional< int64_t > end_row_id, const int64_t start_col_id, const std::optional< int64_t > end_col_id)
TerminationProto NoSolutionFoundTermination(const LimitProto limit, const absl::string_view detail)
std::function< CallbackResult(const CallbackData &)> Callback
absl::StatusOr< std::optional< GlpkRay > > GlpkComputeUnboundRay(glp_prob *const problem)
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
std::optional< int64_t > FirstVariableId(const VariablesProto &variables)
Collection of objects used to extend the Constraint Solver library.
std::string ProtoEnumToString(ProtoEnumType enum_value)
std::string ReturnCodeString(const int rc)
std::string SolutionStatusString(const int status)
std::string TruncateAndQuoteGLPKName(const std::string_view original_name)
void SetupGlpkEnvAutomaticDeletion()
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
StatusBuilder InternalErrorBuilder()
std::vector< double > lower_bounds
std::vector< double > upper_bounds
#define VLOG(verboselevel)