30#include "absl/base/thread_annotations.h"
31#include "absl/container/flat_hash_map.h"
32#include "absl/memory/memory.h"
33#include "absl/status/status.h"
34#include "absl/status/statusor.h"
35#include "absl/strings/str_cat.h"
36#include "absl/strings/str_join.h"
37#include "absl/strings/string_view.h"
38#include "absl/synchronization/mutex.h"
39#include "absl/time/clock.h"
40#include "absl/time/time.h"
47#include "ortools/math_opt/callback.pb.h"
54#include "ortools/math_opt/model.pb.h"
55#include "ortools/math_opt/model_parameters.pb.h"
56#include "ortools/math_opt/model_update.pb.h"
57#include "ortools/math_opt/parameters.pb.h"
58#include "ortools/math_opt/result.pb.h"
59#include "ortools/math_opt/solution.pb.h"
62#include "ortools/math_opt/sparse_containers.pb.h"
71constexpr double kInf = std::numeric_limits<double>::infinity();
89template <
typename Dimension>
90void SetBounds(glp_prob*
const problem,
const int k,
const Bounds&
bounds) {
92 const bool is_integer = Dimension::IsInteger(problem, k);
93 const double lb = is_integer ? std::ceil(
bounds.lower) :
bounds.lower;
94 const double ub = is_integer ? std::floor(
bounds.upper) :
bounds.upper;
96 if (std::isinf(lb) && std::isinf(ub)) {
98 }
else if (std::isinf(lb)) {
100 }
else if (std::isinf(ub)) {
102 }
else if (lb == ub) {
107 Dimension::kSetBounds(problem, k, type, lb, ub);
115template <
typename Dimension>
116Bounds GetBounds(glp_prob*
const problem,
const int k) {
117 const int type = Dimension::kGetType(problem, k);
122 return {.lower = Dimension::kGetLb(problem, k)};
124 return {.upper = Dimension::kGetUb(problem, k)};
127 return {.lower = Dimension::kGetLb(problem, k),
128 .upper = Dimension::kGetUb(problem, k)};
143template <
typename Dimension>
144void UpdateBounds(glp_prob*
const problem,
const Dimension& dimension,
145 const SparseDoubleVectorProto& lower_bounds_proto,
146 const SparseDoubleVectorProto& upper_bounds_proto) {
154 std::optional<int64_t> next_id;
156 if (!next_id.has_value() || current_lower_bound->first < *next_id) {
157 next_id = current_lower_bound->first;
161 if (!next_id.has_value() || current_upper_bound->first < *next_id) {
162 next_id = current_upper_bound->first;
166 if (!next_id.has_value()) {
172 const int row_or_col_index = dimension.id_to_index.at(*next_id);
173 CHECK_EQ(dimension.ids[row_or_col_index - 1], *next_id);
177 Bounds
bounds = GetBounds<Dimension>(problem,
180 current_lower_bound->first == *next_id) {
181 bounds.lower = current_lower_bound->second;
182 ++current_lower_bound;
185 current_upper_bound->first == *next_id) {
186 bounds.upper = current_upper_bound->second;
187 ++current_upper_bound;
189 SetBounds<Dimension>(problem, row_or_col_index,
203void DeleteRowOrColData(std::vector<V>& data,
204 const std::vector<int>& sorted_deleted_rows_or_cols) {
205 if (sorted_deleted_rows_or_cols.empty()) {
210 std::size_t next_insertion_point = 0;
211 std::size_t current_row_or_col = 0;
212 for (std::size_t i = 1; i < sorted_deleted_rows_or_cols.size(); ++i) {
213 const int deleted_row_or_col = sorted_deleted_rows_or_cols[i];
214 for (; current_row_or_col + 1 < deleted_row_or_col;
215 ++current_row_or_col, ++next_insertion_point) {
216 DCHECK_LT(current_row_or_col, data.size());
217 data[next_insertion_point] = data[current_row_or_col];
220 ++current_row_or_col;
222 for (; current_row_or_col < data.size();
223 ++current_row_or_col, ++next_insertion_point) {
224 data[next_insertion_point] = data[current_row_or_col];
226 data.resize(next_insertion_point);
237template <
typename Dimension>
238std::vector<int> DeleteRowsOrCols(
239 glp_prob*
const problem, Dimension& dimension,
240 const google::protobuf::RepeatedField<int64_t>& deleted_ids) {
241 if (deleted_ids.empty()) {
248 std::vector<int> deleted_rows_or_cols;
251 deleted_rows_or_cols.reserve(deleted_ids.size() + 1);
252 deleted_rows_or_cols.push_back(-1);
253 for (
const int64_t deleted_id : deleted_ids) {
254 deleted_rows_or_cols.push_back(dimension.id_to_index.at(deleted_id));
256 Dimension::kDelElts(problem, deleted_rows_or_cols.size() - 1,
257 deleted_rows_or_cols.data());
263 std::is_sorted(deleted_rows_or_cols.begin(), deleted_rows_or_cols.end()));
266 DeleteRowOrColData(dimension.ids, deleted_rows_or_cols);
269 for (
const int64_t deleted_id : deleted_ids) {
270 CHECK(dimension.id_to_index.erase(deleted_id));
272 for (
int i = 0; i < dimension.ids.size(); ++i) {
273 dimension.id_to_index.at(dimension.ids[i]) = i + 1;
276 return deleted_rows_or_cols;
285std::vector<int> MatrixIds(
286 const google::protobuf::RepeatedField<int64_t>& proto_ids,
287 const absl::flat_hash_map<int64_t, int>& id_to_index) {
288 std::vector<int> ids;
289 ids.reserve(proto_ids.size() + 1);
292 for (
const int64_t proto_id : proto_ids) {
293 ids.push_back(id_to_index.at(proto_id));
301std::vector<double> MatrixCoefficients(
302 const google::protobuf::RepeatedField<double>& proto_coeffs) {
303 std::vector<double> coeffs(proto_coeffs.size() + 1);
306 std::copy(proto_coeffs.begin(), proto_coeffs.end(), coeffs.begin() + 1);
311bool IsMip(glp_prob*
const problem) {
312 const int num_vars = glp_get_num_cols(problem);
313 for (
int v = 1; v <= num_vars; ++v) {
314 if (glp_get_col_kind(problem, v) != GLP_CV) {
322bool IsEmpty(glp_prob*
const problem) {
323 return glp_get_num_cols(problem) == 0 && glp_get_num_rows(problem) == 0;
328SparseDoubleVectorProto FilteredVector(glp_prob*
const problem,
329 const SparseVectorFilterProto& filter,
330 const std::vector<int64_t>& ids,
331 double (*
const getter)(glp_prob*,
int)) {
332 SparseDoubleVectorProto vec;
333 vec.mutable_ids()->Reserve(ids.size());
334 vec.mutable_values()->Reserve(ids.size());
336 SparseVectorFilterPredicate predicate(filter);
337 for (
int i = 0; i < ids.size(); ++i) {
338 const double value = getter(problem, i + 1);
339 if (predicate.AcceptsAndUpdate(ids[i],
value)) {
341 vec.add_values(
value);
349SparseDoubleVectorProto FilteredRay(
const SparseVectorFilterProto& filter,
350 const std::vector<int64_t>& ids,
351 const std::vector<double>& values) {
352 CHECK_EQ(ids.size(), values.size());
353 SparseDoubleVectorProto vec;
354 SparseVectorFilterPredicate predicate(filter);
355 for (
int i = 0; i < ids.size(); ++i) {
356 if (predicate.AcceptsAndUpdate(ids[i], values[i])) {
358 vec.add_values(values[i]);
369template <
typename Parameters>
370absl::Status SetSharedParameters(
const SolveParametersProto&
parameters,
371 const bool has_message_callback,
372 Parameters& glpk_parameters) {
373 std::vector<std::string> warnings;
376 absl::StrCat(
"GLPK only supports parameters.threads = 1; value ",
379 if (
parameters.enable_output() || has_message_callback) {
380 glpk_parameters.msg_lev = GLP_MSG_ALL;
382 glpk_parameters.msg_lev = GLP_MSG_OFF;
385 warnings.push_back(
"Parameter node_limit not supported by GLPK");
388 warnings.push_back(
"Parameter objective_limit not supported by GLPK");
391 warnings.push_back(
"Parameter best_bound_limit not supported by GLPK");
394 warnings.push_back(
"Parameter cutoff_limit not supported by GLPK");
397 warnings.push_back(
"Parameter solution_limit not supported by GLPK");
399 if (!warnings.empty()) {
400 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
402 return absl::OkStatus();
409template <
typename Parameters>
410void SetTimeLimitParameter(
const SolveParametersProto&
parameters,
411 Parameters& glpk_parameters) {
413 const int64_t time_limit_ms = absl::ToInt64Milliseconds(
415 glpk_parameters.tm_lim =
static_cast<int>(
std::min(
422absl::Status SetLPParameters(
const SolveParametersProto&
parameters,
423 glp_smcp& glpk_parameters) {
424 std::vector<std::string> warnings;
426 case EMPHASIS_UNSPECIFIED:
432 glpk_parameters.presolve = GLP_OFF;
435 glpk_parameters.presolve = GLP_ON;
439 case LP_ALGORITHM_UNSPECIFIED:
441 case LP_ALGORITHM_PRIMAL_SIMPLEX:
442 glpk_parameters.meth = GLP_PRIMAL;
444 case LP_ALGORITHM_DUAL_SIMPLEX:
450 glpk_parameters.meth = GLP_DUALP;
453 warnings.push_back(absl::StrCat(
454 "GLPK does not support ",
456 " for parameters.lp_algorithm"));
459 if (!warnings.empty()) {
460 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
462 return absl::OkStatus();
465class MipCallbackData {
467 explicit MipCallbackData(SolveInterrupter*
const interrupter)
468 : interrupter_(interrupter) {}
470 void Callback(glp_tree*
const tree) {
473 switch (glp_ios_reason(tree)) {
487 if (
const int best_node = glp_ios_best_node(tree); best_node != 0) {
488 best_bound_ = glp_ios_node_bound(tree, best_node);
502 if (interrupter_ !=
nullptr && interrupter_->IsInterrupted()) {
503 glp_ios_terminate(tree);
504 interrupted_by_interrupter_ =
true;
509 bool HasBeenInterruptedByInterrupter()
const {
510 return interrupted_by_interrupter_.load();
513 std::optional<double> best_bound()
const {
return best_bound_; }
517 SolveInterrupter*
const interrupter_;
520 std::atomic<bool> interrupted_by_interrupter_ =
false;
523 std::optional<double> best_bound_;
526void MipCallback(glp_tree*
const tree,
void*
const info) {
527 static_cast<MipCallbackData*
>(info)->
Callback(tree);
531InvertedBounds ListInvertedBounds(
532 glp_prob*
const problem,
const std::vector<int64_t>&
variable_ids,
533 const std::vector<int64_t>& linear_constraint_ids) {
534 InvertedBounds inverted_bounds;
536 const int num_cols = glp_get_num_cols(problem);
537 for (
int c = 1; c <= num_cols; ++c) {
538 if (glp_get_col_lb(problem, c) > glp_get_col_ub(problem, c)) {
539 inverted_bounds.variables.push_back(
variable_ids[c - 1]);
543 const int num_rows = glp_get_num_rows(problem);
544 for (
int r = 1; r <= num_rows; ++r) {
545 if (glp_get_row_lb(problem, r) > glp_get_row_ub(problem, r)) {
546 inverted_bounds.linear_constraints.push_back(
547 linear_constraint_ids[r - 1]);
551 return inverted_bounds;
557absl::StatusOr<TerminationProto> MipTerminationOnSuccess(
558 glp_prob*
const problem) {
559 const int status = glp_mip_status(problem);
565 "glp_mip_status() returned GLP_FEAS");
569 return absl::InternalError(
570 absl::StrCat(
"glp_intopt() returned 0 but glp_mip_status()"
571 "returned the unexpected value ",
579absl::StatusOr<TerminationProto> InteriorTerminationOnSuccess(
580 glp_prob*
const problem) {
581 const int status = glp_ipt_status(problem);
587 "glp_ipt_status() returned GLP_INFEAS");
599 return absl::InternalError(
600 absl::StrCat(
"glp_interior() returned 0 but glp_ipt_status()"
601 "returned the unexpected value ",
609absl::StatusOr<TerminationProto> SimplexTerminationOnSuccess(
610 glp_prob*
const problem) {
618 const int prim_status = glp_get_prim_stat(problem);
619 const int dual_status = glp_get_dual_stat(problem);
622 const auto undetermined_limit = [&]() {
623 const std::string detail = absl::StrCat(
625 " and glp_get_dual_stat() returned ",
627 if (prim_status == GLP_FEAS) {
635 const auto unexpected_dual_stat = [&]() {
636 return absl::InternalError(
637 absl::StrCat(
"glp_simplex() returned 0 but glp_get_dual_stat() "
638 "returned the unexpected value ",
642 switch (prim_status) {
644 switch (dual_status) {
651 return undetermined_limit();
655 return unexpected_dual_stat();
658 switch (dual_status) {
661 return undetermined_limit();
665 return unexpected_dual_stat();
668 switch (dual_status) {
681 return unexpected_dual_stat();
684 return absl::InternalError(
685 absl::StrCat(
"glp_simplex() returned 0 but glp_get_prim_stat() "
686 "returned the unexpected value ",
704absl::StatusOr<TerminationProto> BuildTermination(
705 glp_prob*
const problem,
const std::string_view fn_name,
const int rc,
706 const std::function<absl::StatusOr<TerminationProto>(glp_prob*)>
707 termination_on_success,
708 MipCallbackData*
const mip_cb_data,
const bool has_feasible_solution,
710 const std::vector<int64_t>& linear_constraint_ids) {
711 if (mip_cb_data !=
nullptr &&
712 mip_cb_data->HasBeenInterruptedByInterrupter()) {
714 has_feasible_solution);
721 return termination_on_success(problem);
728 ListInvertedBounds(problem,
730 linear_constraint_ids)
734 <<
"` but the model does not contain variables with inverted "
739 has_feasible_solution);
744 TERMINATION_REASON_OPTIMAL,
747 absl::StrCat(std::string(fn_name),
"() returned ",
751 has_feasible_solution);
763 has_feasible_solution);
767 TERMINATION_REASON_NUMERICAL_ERROR,
770 absl::StrCat(std::string(fn_name),
"() returned ",
772 " which means that there is a numeric stability issue "
773 "solving Newtonian system"));
786 void Parse(
const std::string_view
message) {
791 const absl::MutexLock lock(&mutex_);
792 std::vector<std::string> new_lines = buffer_.Parse(
message);
793 if (!new_lines.empty()) {
794 callback_(new_lines);
801 const absl::MutexLock lock(&mutex_);
802 std::vector<std::string> new_lines = buffer_.Flush();
803 if (!new_lines.empty()) {
804 callback_(new_lines);
810 MessageCallbackData buffer_ ABSL_GUARDED_BY(mutex_);
817int TermHook(
void*
const info,
const char*
const message) {
818 static_cast<TermHookData*
>(info)->Parse(
message);
827double OffsetOnlyObjVal(glp_prob*
const problem) {
828 return glp_get_obj_coef(problem, 0);
834int OptStatus(glp_prob*) {
return GLP_OPT; }
837absl::Status QuadraticObjectiveError() {
838 return absl::InvalidArgumentError(
839 "GLPK does not support quadratic objectives");
846 if (!
model.objective().quadratic_coefficients().row_ids().empty()) {
847 return QuadraticObjectiveError();
852GlpkSolver::GlpkSolver(
const ModelProto&
model) : problem_(glp_create_prob()) {
858 AddVariables(
model.variables());
860 AddLinearConstraints(
model.linear_constraints());
862 glp_set_obj_dir(problem_,
model.objective().maximize() ? GLP_MAX : GLP_MIN);
864 glp_set_obj_coef(problem_, 0,
model.objective().offset());
865 for (
const auto [v,
coeff] :
867 const int col_index = variables_.id_to_index.at(v);
868 CHECK_EQ(variables_.ids[col_index - 1], v);
869 glp_set_obj_coef(problem_, col_index,
coeff);
872 const SparseDoubleMatrixProto& proto_matrix =
873 model.linear_constraint_matrix();
875 problem_, proto_matrix.row_ids_size(),
876 MatrixIds(proto_matrix.row_ids(), linear_constraints_.id_to_index).data(),
877 MatrixIds(proto_matrix.column_ids(), variables_.id_to_index).data(),
878 MatrixCoefficients(proto_matrix.coefficients()).data());
885ProblemStatusProto GetMipProblemStatusProto(
const int rc,
const int mip_status,
886 const bool has_finite_dual_bound) {
887 ProblemStatusProto problem_status;
888 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
889 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
893 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
894 return problem_status;
896 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
897 return problem_status;
900 switch (mip_status) {
902 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
903 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
904 return problem_status;
906 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
909 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
913 if (has_finite_dual_bound) {
914 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
916 return problem_status;
919absl::StatusOr<FeasibilityStatusProto> TranslateProblemStatus(
920 const int glpk_status,
const absl::string_view fn_name) {
921 switch (glpk_status) {
923 return FEASIBILITY_STATUS_FEASIBLE;
925 return FEASIBILITY_STATUS_INFEASIBLE;
928 return FEASIBILITY_STATUS_UNDETERMINED;
930 return absl::InternalError(
931 absl::StrCat(fn_name,
" returned the unexpected value ",
940absl::StatusOr<ProblemStatusProto> GetSimplexProblemStatusProto(
941 const int glp_simplex_rc,
const int glpk_primal_status,
942 const int glpk_dual_status) {
943 ProblemStatusProto problem_status;
944 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
945 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
947 switch (glp_simplex_rc) {
950 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
951 return problem_status;
954 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
955 return problem_status;
959 const FeasibilityStatusProto primal_status,
960 TranslateProblemStatus(glpk_primal_status,
"glp_get_prim_stat"));
961 problem_status.set_primal_status(primal_status);
965 const FeasibilityStatusProto dual_status,
966 TranslateProblemStatus(glpk_dual_status,
"glp_get_dual_stat"));
967 problem_status.set_dual_status(dual_status);
968 return problem_status;
973absl::StatusOr<ProblemStatusProto> GetBarrierProblemStatusProto(
974 const int glp_interior_rc,
const int ipt_status) {
975 ProblemStatusProto problem_status;
976 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
977 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
979 switch (glp_interior_rc) {
982 switch (ipt_status) {
984 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
985 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
986 return problem_status;
988 return problem_status;
990 problem_status.set_primal_or_dual_infeasible(
true);
991 return problem_status;
993 return problem_status;
995 return absl::InternalError(
996 absl::StrCat(
"glp_ipt_status returned the unexpected value ",
1000 return problem_status;
1008 const ModelSolveParametersProto& model_parameters,
1010 const CallbackRegistrationProto& callback_registration,
const Callback cb,
1016 const absl::Time
start = absl::Now();
1021 std::unique_ptr<TermHookData> term_hook_data;
1022 if (message_cb !=
nullptr) {
1023 term_hook_data = std::make_unique<TermHookData>(std::move(message_cb));
1029 glp_term_hook(TermHook, term_hook_data.get());
1035 if (term_hook_data !=
nullptr) {
1036 glp_term_hook(
nullptr,
nullptr);
1040 SolveResultProto result;
1042 const bool is_mip = IsMip(problem_);
1046 int (*get_prim_stat)(glp_prob*) =
nullptr;
1047 double (*obj_val)(glp_prob*) =
nullptr;
1048 double (*col_val)(glp_prob*, int) =
nullptr;
1050 int (*get_dual_stat)(glp_prob*) =
nullptr;
1051 double (*row_dual)(glp_prob*, int) =
nullptr;
1052 double (*col_dual)(glp_prob*, int) =
nullptr;
1054 const bool maximize = glp_get_obj_dir(problem_) == GLP_MAX;
1055 double best_dual_bound = maximize ?
kInf : -
kInf;
1068 get_prim_stat = glp_mip_status;
1069 obj_val = glp_mip_obj_val;
1070 col_val = glp_mip_col_val;
1072 glp_iocp glpk_parameters;
1073 glp_init_iocp(&glpk_parameters);
1076 term_hook_data !=
nullptr, glpk_parameters));
1077 SetTimeLimitParameter(
parameters, glpk_parameters);
1082 glpk_parameters.presolve = GLP_ON;
1083 MipCallbackData mip_cb_data(interrupter);
1084 glpk_parameters.cb_func = MipCallback;
1085 glpk_parameters.cb_info = &mip_cb_data;
1086 const int rc = glp_intopt(problem_, &glpk_parameters);
1087 const int mip_status = glp_mip_status(problem_);
1088 const bool has_feasible_solution =
1089 mip_status == GLP_OPT || mip_status == GLP_FEAS;
1091 *result.mutable_termination(),
1092 BuildTermination(problem_,
"glp_intopt", rc, MipTerminationOnSuccess,
1094 has_feasible_solution,
1096 linear_constraints_.ids));
1097 if (mip_cb_data.best_bound().has_value()) {
1098 best_dual_bound = *mip_cb_data.best_bound();
1100 *result.mutable_solve_stats()->mutable_problem_status() =
1101 GetMipProblemStatusProto(rc, mip_status,
1102 std::isfinite(best_dual_bound));
1104 if (
parameters.lp_algorithm() == LP_ALGORITHM_BARRIER) {
1105 get_prim_stat = glp_ipt_status;
1106 obj_val = glp_ipt_obj_val;
1107 col_val = glp_ipt_col_prim;
1109 get_dual_stat = glp_ipt_status;
1110 row_dual = glp_ipt_row_dual;
1111 col_dual = glp_ipt_col_dual;
1113 glp_iptcp glpk_parameters;
1114 glp_init_iptcp(&glpk_parameters);
1116 return absl::InvalidArgumentError(
1117 "Parameter time_limit not supported by GLPK for interior point "
1122 term_hook_data !=
nullptr, glpk_parameters));
1126 if (IsEmpty(problem_)) {
1127 get_prim_stat = OptStatus;
1128 get_dual_stat = OptStatus;
1129 obj_val = OffsetOnlyObjVal;
1131 TERMINATION_REASON_OPTIMAL,
1132 "glp_interior() not called since the model is empty");
1133 result.mutable_solve_stats()
1134 ->mutable_problem_status()
1135 ->set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
1136 result.mutable_solve_stats()->mutable_problem_status()->set_dual_status(
1137 FEASIBILITY_STATUS_FEASIBLE);
1141 const int glp_interior_rc = glp_interior(problem_, &glpk_parameters);
1142 const int ipt_status = glp_ipt_status(problem_);
1143 const bool has_feasible_solution = ipt_status == GLP_OPT;
1145 *result.mutable_termination(),
1147 problem_,
"glp_interior", glp_interior_rc,
1148 InteriorTerminationOnSuccess,
1150 has_feasible_solution,
1152 linear_constraints_.ids));
1154 *result.mutable_solve_stats()->mutable_problem_status(),
1155 GetBarrierProblemStatusProto(glp_interior_rc,
1159 get_prim_stat = glp_get_prim_stat;
1160 obj_val = glp_get_obj_val;
1161 col_val = glp_get_col_prim;
1163 get_dual_stat = glp_get_dual_stat;
1164 row_dual = glp_get_row_dual;
1165 col_dual = glp_get_col_dual;
1167 glp_smcp glpk_parameters;
1168 glp_init_smcp(&glpk_parameters);
1171 term_hook_data !=
nullptr, glpk_parameters));
1172 SetTimeLimitParameter(
parameters, glpk_parameters);
1176 const int glp_simplex_rc = glp_simplex(problem_, &glpk_parameters);
1177 const int prim_stat = glp_get_prim_stat(problem_);
1178 const bool has_feasible_solution = prim_stat == GLP_FEAS;
1180 *result.mutable_termination(),
1181 BuildTermination(problem_,
"glp_simplex", glp_simplex_rc,
1182 SimplexTerminationOnSuccess,
1184 has_feasible_solution,
1186 linear_constraints_.ids));
1189 GetSimplexProblemStatusProto(
1192 glp_get_dual_stat(problem_)));
1193 VLOG(1) <<
"glp_get_status: "
1196 <<
" glp_get_dual_stat: "
1202 if (term_hook_data !=
nullptr) {
1204 std::move(message_cb_cleanup).Invoke();
1205 term_hook_data->Flush();
1206 term_hook_data.reset();
1209 double best_primal_bound = maximize ? -
kInf :
kInf;
1210 switch (get_prim_stat(problem_)) {
1213 best_primal_bound = obj_val(problem_);
1216 result.mutable_solve_stats()->set_best_primal_bound(best_primal_bound);
1220 result.mutable_solve_stats()->set_best_dual_bound(best_dual_bound);
1221 SolutionProto solution;
1222 AddPrimalSolution(get_prim_stat, obj_val, col_val, model_parameters,
1225 AddDualSolution(get_dual_stat, obj_val, row_dual, col_dual,
1226 model_parameters, solution);
1228 if (solution.has_primal_solution() || solution.has_dual_solution() ||
1229 solution.has_basis()) {
1230 *result.add_solutions() = std::move(solution);
1238 absl::Now() -
start, result.mutable_solve_stats()->mutable_solve_time()));
1242void GlpkSolver::AddVariables(
const VariablesProto& new_variables) {
1243 if (new_variables.ids().empty()) {
1248 const int first_new_var_index = variables_.ids.size() + 1;
1250 variables_.ids.insert(variables_.ids.end(), new_variables.ids().begin(),
1251 new_variables.ids().end());
1252 for (
int v = 0; v < new_variables.ids_size(); ++v) {
1253 CHECK(variables_.id_to_index
1254 .try_emplace(new_variables.ids(v), first_new_var_index + v)
1257 glp_add_cols(problem_, new_variables.ids_size());
1258 if (!new_variables.names().empty()) {
1259 for (
int v = 0; v < new_variables.names_size(); ++v) {
1261 problem_, v + first_new_var_index,
1265 CHECK_EQ(new_variables.lower_bounds_size(),
1266 new_variables.upper_bounds_size());
1267 CHECK_EQ(new_variables.lower_bounds_size(), new_variables.ids_size());
1268 variables_.unrounded_lower_bounds.insert(
1269 variables_.unrounded_lower_bounds.end(),
1270 new_variables.lower_bounds().begin(), new_variables.lower_bounds().end());
1271 variables_.unrounded_upper_bounds.insert(
1272 variables_.unrounded_upper_bounds.end(),
1273 new_variables.upper_bounds().begin(), new_variables.upper_bounds().end());
1274 for (
int i = 0; i < new_variables.lower_bounds_size(); ++i) {
1281 glp_set_col_kind(problem_, i + first_new_var_index,
1282 new_variables.integers(i) ? GLP_IV : GLP_CV);
1283 SetBounds<Variables>(problem_, i + first_new_var_index,
1284 {.lower = new_variables.lower_bounds(i),
1285 .
upper = new_variables.upper_bounds(i)});
1289void GlpkSolver::AddLinearConstraints(
1290 const LinearConstraintsProto& new_linear_constraints) {
1291 if (new_linear_constraints.ids().empty()) {
1296 const int first_new_cstr_index = linear_constraints_.ids.size() + 1;
1298 linear_constraints_.ids.insert(linear_constraints_.ids.end(),
1299 new_linear_constraints.ids().begin(),
1300 new_linear_constraints.ids().end());
1301 for (
int c = 0; c < new_linear_constraints.ids_size(); ++c) {
1302 CHECK(linear_constraints_.id_to_index
1303 .try_emplace(new_linear_constraints.ids(c),
1304 first_new_cstr_index + c)
1307 glp_add_rows(problem_, new_linear_constraints.ids_size());
1308 if (!new_linear_constraints.names().empty()) {
1309 for (
int c = 0; c < new_linear_constraints.names_size(); ++c) {
1311 problem_, c + first_new_cstr_index,
1315 CHECK_EQ(new_linear_constraints.lower_bounds_size(),
1316 new_linear_constraints.upper_bounds_size());
1317 for (
int i = 0; i < new_linear_constraints.lower_bounds_size(); ++i) {
1318 SetBounds<LinearConstraints>(
1319 problem_, i + first_new_cstr_index,
1320 {.lower = new_linear_constraints.lower_bounds(i),
1321 .
upper = new_linear_constraints.upper_bounds(i)});
1325void GlpkSolver::UpdateObjectiveCoefficients(
1326 const SparseDoubleVectorProto& coefficients_proto) {
1327 for (
const auto [
id,
coeff] :
MakeView(coefficients_proto)) {
1328 const int col_index = variables_.id_to_index.at(
id);
1329 CHECK_EQ(variables_.ids[col_index - 1],
id);
1330 glp_set_obj_coef(problem_, col_index,
coeff);
1334void GlpkSolver::UpdateLinearConstraintMatrix(
1335 const SparseDoubleMatrixProto& matrix_updates,
1336 const std::optional<int64_t> first_new_var_id,
1337 const std::optional<int64_t> first_new_cstr_id) {
1377 std::vector<int> data_indices(variables_.ids.size() + 1);
1378 std::vector<double> data_values(variables_.ids.size() + 1);
1388 std::vector<int> col_to_data_element(variables_.ids.size() + 1,
1390 for (
const auto& [row_id, row_coefficients] :
1395 first_new_var_id)) {
1398 const int row_index = linear_constraints_.id_to_index.at(row_id);
1399 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1402 const int initial_non_zeros = glp_get_mat_row(
1403 problem_, row_index, data_indices.data(), data_values.data());
1404 CHECK_LE(initial_non_zeros + 1, data_indices.size());
1405 CHECK_LE(initial_non_zeros + 1, data_values.size());
1408 for (
int i = 1; i <= initial_non_zeros; ++i) {
1409 col_to_data_element[data_indices[i]] = i;
1413 int non_zeros = initial_non_zeros;
1414 for (
const auto [col_id,
coefficient] : row_coefficients) {
1415 const int col_index = variables_.id_to_index.at(col_id);
1416 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1422 if (
const int i = col_to_data_element[col_index]; i == kColNotPresent) {
1424 data_indices[non_zeros] = col_index;
1427 CHECK_EQ(data_indices[i], col_index);
1431 CHECK_LE(non_zeros + 1, data_indices.size());
1432 CHECK_LE(non_zeros + 1, data_values.size());
1435 glp_set_mat_row(problem_, row_index, non_zeros, data_indices.data(),
1436 data_values.data());
1440 for (
int i = 1; i <= non_zeros; ++i) {
1441 data_indices[i] = 0;
1448 for (
int i = 1; i <= initial_non_zeros; ++i) {
1449 col_to_data_element[data_indices[i]] = kColNotPresent;
1456 if (first_new_var_id.has_value()) {
1459 std::vector<int> data_indices(linear_constraints_.ids.size() + 1);
1460 std::vector<double> data_values(linear_constraints_.ids.size() + 1);
1469 const int col_index = variables_.id_to_index.at(col_id);
1470 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1476 const int row_index = linear_constraints_.id_to_index.at(row_id);
1477 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1480 data_indices[non_zeros] = row_index;
1483 CHECK_LE(non_zeros + 1, data_indices.size());
1484 CHECK_LE(non_zeros + 1, data_values.size());
1487 glp_set_mat_col(problem_, col_index, non_zeros, data_indices.data(),
1488 data_values.data());
1492 for (
int i = 1; i <= non_zeros; ++i) {
1493 data_indices[i] = 0;
1500 if (first_new_cstr_id.has_value()) {
1503 std::vector<int> data_indices(variables_.ids.size() + 1);
1504 std::vector<double> data_values(variables_.ids.size() + 1);
1505 for (
const auto& [row_id, row_coefficients] :
1513 const int row_index = linear_constraints_.id_to_index.at(row_id);
1514 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1519 for (
const auto [col_id,
coefficient] : row_coefficients) {
1520 const int col_index = variables_.id_to_index.at(col_id);
1521 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1524 data_indices[non_zeros] = col_index;
1527 CHECK_LE(non_zeros + 1, data_indices.size());
1528 CHECK_LE(non_zeros + 1, data_values.size());
1531 glp_set_mat_row(problem_, row_index, non_zeros, data_indices.data(),
1532 data_values.data());
1536 for (
int i = 1; i <= non_zeros; ++i) {
1537 data_indices[i] = 0;
1544void GlpkSolver::AddPrimalSolution(
1545 int (*get_prim_stat)(glp_prob*),
double (*obj_val)(glp_prob*),
1546 double (*col_val)(glp_prob*,
int),
1547 const ModelSolveParametersProto& model_parameters,
1548 SolutionProto& solution_proto) {
1549 const int status = get_prim_stat(problem_);
1551 PrimalSolutionProto& primal_solution =
1552 *solution_proto.mutable_primal_solution();
1553 primal_solution.set_objective_value(obj_val(problem_));
1554 primal_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1555 *primal_solution.mutable_variable_values() =
1556 FilteredVector(problem_, model_parameters.variable_values_filter(),
1557 variables_.ids, col_val);
1561void GlpkSolver::AddDualSolution(
1562 int (*get_dual_stat)(glp_prob*),
double (*obj_val)(glp_prob*),
1563 double (*row_dual)(glp_prob*,
int),
double (*col_dual)(glp_prob*,
int),
1564 const ModelSolveParametersProto& model_parameters,
1565 SolutionProto& solution_proto) {
1566 const int status = get_dual_stat(problem_);
1568 DualSolutionProto& dual_solution = *solution_proto.mutable_dual_solution();
1569 dual_solution.set_objective_value(obj_val(problem_));
1570 *dual_solution.mutable_dual_values() =
1571 FilteredVector(problem_, model_parameters.dual_values_filter(),
1572 linear_constraints_.ids, row_dual);
1573 *dual_solution.mutable_reduced_costs() =
1574 FilteredVector(problem_, model_parameters.reduced_costs_filter(),
1575 variables_.ids, col_dual);
1579 dual_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1583absl::Status GlpkSolver::AddPrimalOrDualRay(
1584 const ModelSolveParametersProto& model_parameters,
1585 SolveResultProto& result) {
1588 if (!opt_unbound_ray.has_value()) {
1589 return absl::OkStatus();
1592 const int num_cstrs = linear_constraints_.ids.size();
1593 switch (opt_unbound_ray->type) {
1595 const int num_cstrs = linear_constraints_.ids.size();
1600 std::vector<double> ray_values(variables_.ids.size());
1602 for (
const auto [k,
value] : opt_unbound_ray->non_zero_components) {
1603 if (k <= num_cstrs) {
1607 const int var_index = k - num_cstrs;
1609 ray_values[var_index - 1] =
value;
1612 *result.add_primal_rays()->mutable_variable_values() =
1613 FilteredRay(model_parameters.variable_values_filter(), variables_.ids,
1616 return absl::OkStatus();
1625 std::vector<double> ray_reduced_costs(variables_.ids.size());
1626 std::vector<double> ray_dual_values(num_cstrs);
1628 for (
const auto [k,
value] : opt_unbound_ray->non_zero_components) {
1629 if (k <= num_cstrs) {
1630 ray_dual_values[k - 1] =
value;
1632 const int var_index = k - num_cstrs;
1634 ray_reduced_costs[var_index - 1] =
value;
1638 DualRayProto& dual_ray = *result.add_dual_rays();
1639 *dual_ray.mutable_dual_values() =
1640 FilteredRay(model_parameters.dual_values_filter(),
1641 linear_constraints_.ids, ray_dual_values);
1642 *dual_ray.mutable_reduced_costs() =
1643 FilteredRay(model_parameters.reduced_costs_filter(), variables_.ids,
1646 return absl::OkStatus();
1652 if (!model_update.objective_updates()
1653 .quadratic_coefficients()
1656 return QuadraticObjectiveError();
1664 const std::vector<int> sorted_deleted_cols = DeleteRowsOrCols(
1665 problem_, variables_, model_update.deleted_variable_ids());
1666 DeleteRowOrColData(variables_.unrounded_lower_bounds, sorted_deleted_cols);
1667 DeleteRowOrColData(variables_.unrounded_upper_bounds, sorted_deleted_cols);
1668 CHECK_EQ(variables_.unrounded_lower_bounds.size(),
1669 variables_.unrounded_upper_bounds.size());
1670 CHECK_EQ(variables_.unrounded_lower_bounds.size(), variables_.ids.size());
1672 DeleteRowsOrCols(problem_, linear_constraints_,
1673 model_update.deleted_linear_constraint_ids());
1675 for (
const auto [var_id, is_integer] :
1676 MakeView(model_update.variable_updates().integers())) {
1678 const int var_index = variables_.id_to_index.at(var_id);
1679 glp_set_col_kind(problem_, var_index, is_integer ? GLP_IV : GLP_CV);
1686 SetBounds<Variables>(
1687 problem_, var_index,
1688 {.lower = variables_.unrounded_lower_bounds[var_index - 1],
1689 .upper = variables_.unrounded_upper_bounds[var_index - 1]});
1692 MakeView(model_update.variable_updates().lower_bounds())) {
1693 variables_.unrounded_lower_bounds[variables_.id_to_index.at(var_id) - 1] =
1697 MakeView(model_update.variable_updates().upper_bounds())) {
1698 variables_.unrounded_upper_bounds[variables_.id_to_index.at(var_id) - 1] =
1702 problem_, variables_,
1703 model_update.variable_updates().lower_bounds(),
1704 model_update.variable_updates().upper_bounds());
1705 UpdateBounds(problem_, linear_constraints_,
1707 model_update.linear_constraint_updates().lower_bounds(),
1709 model_update.linear_constraint_updates().upper_bounds());
1711 AddVariables(model_update.new_variables());
1712 AddLinearConstraints(model_update.new_linear_constraints());
1714 if (model_update.objective_updates().has_direction_update()) {
1715 glp_set_obj_dir(problem_,
1716 model_update.objective_updates().direction_update()
1720 if (model_update.objective_updates().has_offset_update()) {
1722 glp_set_obj_coef(problem_, 0,
1723 model_update.objective_updates().offset_update());
1725 UpdateObjectiveCoefficients(
1726 model_update.objective_updates().linear_coefficients());
1728 UpdateLinearConstraintMatrix(
1729 model_update.linear_constraint_matrix_updates(),
1734 return absl::OkStatus();
#define CHECK_EQ(val1, val2)
#define CHECK_GE(val1, val2)
#define DCHECK_LT(val1, val2)
#define CHECK_LE(val1, val2)
#define VLOG(verboselevel)
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
bool CanUpdate(const ModelUpdateProto &model_update) override
absl::Status 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)
std::optional< int64_t > FirstLinearConstraintId(const LinearConstraintsProto &linear_constraints)
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)
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)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
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)
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