30#include "absl/base/nullability.h"
31#include "absl/cleanup/cleanup.h"
32#include "absl/container/flat_hash_map.h"
33#include "absl/log/check.h"
34#include "absl/log/log.h"
35#include "absl/memory/memory.h"
36#include "absl/status/status.h"
37#include "absl/status/statusor.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"
77constexpr double kInf = std::numeric_limits<double>::infinity();
78constexpr double kNaN = std::numeric_limits<double>::quiet_NaN();
99template <
typename Dimension>
100void SetBounds(glp_prob*
const problem,
const int k,
const Bounds& bounds) {
102 const bool is_integer = Dimension::IsInteger(problem, k);
103 const double lb = is_integer ? std::ceil(bounds.lower) : bounds.lower;
104 const double ub = is_integer ? std::floor(bounds.upper) : bounds.upper;
106 if (std::isinf(lb) && std::isinf(ub)) {
108 }
else if (std::isinf(lb)) {
110 }
else if (std::isinf(ub)) {
112 }
else if (lb == ub) {
117 Dimension::kSetBounds(problem, k, type, lb, ub);
125template <
typename Dimension>
126Bounds GetBounds(glp_prob*
const problem,
const int k) {
127 const int type = Dimension::kGetType(problem, k);
132 return {.lower = Dimension::kGetLb(problem, k)};
134 return {.upper = Dimension::kGetUb(problem, k)};
137 return {.lower = Dimension::kGetLb(problem, k),
138 .upper = Dimension::kGetUb(problem, k)};
153template <
typename Dimension>
154void UpdateBounds(glp_prob*
const problem,
const Dimension& dimension,
157 const auto lower_bounds =
MakeView(lower_bounds_proto);
158 const auto upper_bounds =
MakeView(upper_bounds_proto);
160 auto current_lower_bound = lower_bounds.begin();
161 auto current_upper_bound = upper_bounds.begin();
164 std::optional<int64_t> next_id;
165 if (current_lower_bound != lower_bounds.end()) {
166 if (!next_id.has_value() || current_lower_bound->first < *next_id) {
167 next_id = current_lower_bound->first;
170 if (current_upper_bound != upper_bounds.end()) {
171 if (!next_id.has_value() || current_upper_bound->first < *next_id) {
172 next_id = current_upper_bound->first;
176 if (!next_id.has_value()) {
182 const int row_or_col_index = dimension.id_to_index.at(*next_id);
183 CHECK_EQ(dimension.ids[row_or_col_index - 1], *next_id);
187 Bounds bounds = GetBounds<Dimension>(problem,
189 if (current_lower_bound != lower_bounds.end() &&
190 current_lower_bound->first == *next_id) {
191 bounds.lower = current_lower_bound->second;
192 ++current_lower_bound;
194 if (current_upper_bound != upper_bounds.end() &&
195 current_upper_bound->first == *next_id) {
196 bounds.upper = current_upper_bound->second;
197 ++current_upper_bound;
199 SetBounds<Dimension>(problem, row_or_col_index,
203 CHECK(current_lower_bound == lower_bounds.end());
204 CHECK(current_upper_bound == upper_bounds.end());
213void DeleteRowOrColData(std::vector<V>& data,
214 absl::Span<const int> sorted_deleted_rows_or_cols) {
215 if (sorted_deleted_rows_or_cols.empty()) {
220 std::size_t next_insertion_point = 0;
221 std::size_t current_row_or_col = 0;
222 for (std::size_t i = 1;
i < sorted_deleted_rows_or_cols.size(); ++
i) {
223 const int deleted_row_or_col = sorted_deleted_rows_or_cols[
i];
224 for (; current_row_or_col + 1 < deleted_row_or_col;
225 ++current_row_or_col, ++next_insertion_point) {
226 DCHECK_LT(current_row_or_col, data.size());
227 data[next_insertion_point] = data[current_row_or_col];
230 ++current_row_or_col;
232 for (; current_row_or_col < data.size();
233 ++current_row_or_col, ++next_insertion_point) {
234 data[next_insertion_point] = data[current_row_or_col];
236 data.resize(next_insertion_point);
247template <
typename Dimension>
248std::vector<int> DeleteRowsOrCols(
249 glp_prob*
const problem, Dimension& dimension,
250 const google::protobuf::RepeatedField<int64_t>& deleted_ids) {
251 if (deleted_ids.empty()) {
258 std::vector<int> deleted_rows_or_cols;
261 deleted_rows_or_cols.reserve(deleted_ids.size() + 1);
262 deleted_rows_or_cols.push_back(-1);
263 for (
const int64_t deleted_id : deleted_ids) {
264 deleted_rows_or_cols.push_back(dimension.id_to_index.at(deleted_id));
266 Dimension::kDelElts(problem, deleted_rows_or_cols.size() - 1,
267 deleted_rows_or_cols.data());
273 std::is_sorted(deleted_rows_or_cols.begin(), deleted_rows_or_cols.end()));
276 DeleteRowOrColData(dimension.ids, deleted_rows_or_cols);
279 for (
const int64_t deleted_id : deleted_ids) {
280 CHECK(dimension.id_to_index.erase(deleted_id));
282 for (
int i = 0;
i < dimension.ids.size(); ++
i) {
283 dimension.id_to_index.at(dimension.ids[i]) =
i + 1;
286 return deleted_rows_or_cols;
295std::vector<int> MatrixIds(
296 const google::protobuf::RepeatedField<int64_t>& proto_ids,
297 const absl::flat_hash_map<int64_t, int>& id_to_index) {
298 std::vector<int> ids;
299 ids.reserve(proto_ids.size() + 1);
302 for (
const int64_t proto_id : proto_ids) {
303 ids.push_back(id_to_index.at(proto_id));
311std::vector<double> MatrixCoefficients(
312 const google::protobuf::RepeatedField<double>& proto_coeffs) {
313 std::vector<double> coeffs(proto_coeffs.size() + 1);
316 std::copy(proto_coeffs.begin(), proto_coeffs.end(), coeffs.begin() + 1);
321bool IsMip(glp_prob*
const problem) {
322 const int num_vars = glp_get_num_cols(problem);
323 for (
int v = 1; v <= num_vars; ++v) {
324 if (glp_get_col_kind(problem, v) != GLP_CV) {
332bool IsEmpty(glp_prob*
const problem) {
333 return glp_get_num_cols(problem) == 0 && glp_get_num_rows(problem) == 0;
340 absl::Span<const int64_t> ids,
341 double (*
const getter)(glp_prob*,
int)) {
344 vec.mutable_values()->Reserve(ids.size());
347 for (
int i = 0;
i < ids.size(); ++
i) {
348 const double value = getter(problem, i + 1);
349 if (predicate.AcceptsAndUpdate(ids[i], value)) {
351 vec.add_values(value);
360 absl::Span<const int64_t> ids,
361 absl::Span<const double> values) {
362 CHECK_EQ(ids.size(), values.size());
365 for (
int i = 0;
i < ids.size(); ++
i) {
366 if (predicate.AcceptsAndUpdate(ids[i], values[i])) {
368 vec.add_values(values[i]);
379template <
typename Parameters>
381 const bool has_message_callback,
382 Parameters& glpk_parameters) {
383 std::vector<std::string> warnings;
384 if (parameters.has_threads() && parameters.threads() > 1) {
386 absl::StrCat(
"GLPK only supports parameters.threads = 1; value ",
387 parameters.threads(),
" is not supported"));
389 if (parameters.enable_output() || has_message_callback) {
390 glpk_parameters.msg_lev = GLP_MSG_ALL;
392 glpk_parameters.msg_lev = GLP_MSG_OFF;
394 if (parameters.has_node_limit()) {
395 warnings.push_back(
"parameter node_limit not supported by GLPK");
397 if (parameters.has_objective_limit()) {
398 warnings.push_back(
"parameter objective_limit not supported by GLPK");
400 if (parameters.has_best_bound_limit()) {
401 warnings.push_back(
"parameter best_bound_limit not supported by GLPK");
403 if (parameters.has_cutoff_limit()) {
404 warnings.push_back(
"parameter cutoff_limit not supported by GLPK");
406 if (parameters.has_solution_limit()) {
407 warnings.push_back(
"parameter solution_limit not supported by GLPK");
409 if (parameters.has_random_seed()) {
410 warnings.push_back(
"parameter random_seed not supported by GLPK");
413 warnings.push_back(
"parameter cuts not supported by GLPK");
416 warnings.push_back(
"parameter heuristics not supported by GLPK");
419 warnings.push_back(
"parameter scaling not supported by GLPK");
421 if (!warnings.empty()) {
422 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
424 return absl::OkStatus();
431template <
typename Parameters>
433 Parameters& glpk_parameters) {
434 if (parameters.has_time_limit()) {
435 const int64_t time_limit_ms = absl::ToInt64Milliseconds(
437 glpk_parameters.tm_lim =
static_cast<int>(std::min(
438 static_cast<int64_t
>(std::numeric_limits<int>::max()), time_limit_ms));
445 glp_smcp& glpk_parameters) {
446 std::vector<std::string> warnings;
447 if (parameters.has_iteration_limit()) {
448 int limit =
static_cast<int>(std::min<int64_t>(
449 std::numeric_limits<int>::max(), parameters.iteration_limit()));
450 glpk_parameters.it_lim = limit;
452 switch (parameters.presolve()) {
459 glpk_parameters.presolve = GLP_OFF;
462 glpk_parameters.presolve = GLP_ON;
465 switch (parameters.lp_algorithm()) {
469 glpk_parameters.meth = GLP_PRIMAL;
477 glpk_parameters.meth = GLP_DUALP;
480 warnings.push_back(absl::StrCat(
481 "GLPK does not support ",
483 " for parameters.lp_algorithm"));
486 if (!warnings.empty()) {
487 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
489 return absl::OkStatus();
492class MipCallbackData {
494 explicit MipCallbackData(
495 const SolveInterrupter* absl_nullable
const interrupter)
496 : interrupter_(interrupter) {}
498 void Callback(glp_tree*
const tree) {
501 switch (glp_ios_reason(tree)) {
515 if (
const int best_node = glp_ios_best_node(tree); best_node != 0) {
516 best_bound_ = glp_ios_node_bound(tree, best_node);
530 if (interrupter_ !=
nullptr && interrupter_->IsInterrupted()) {
531 glp_ios_terminate(tree);
532 interrupted_by_interrupter_ =
true;
537 bool HasBeenInterruptedByInterrupter()
const {
538 return interrupted_by_interrupter_.load();
541 std::optional<double> best_bound()
const {
return best_bound_; }
545 const SolveInterrupter* absl_nullable
const interrupter_;
548 std::atomic<bool> interrupted_by_interrupter_ =
false;
551 std::optional<double> best_bound_;
554void MipCallback(glp_tree*
const tree,
void*
const info) {
555 static_cast<MipCallbackData*
>(info)->
Callback(tree);
564 glp_prob*
const problem, absl::Span<const int64_t> variable_ids,
565 absl::Span<const double> unrounded_variable_lower_bounds,
566 absl::Span<const double> unrounded_variable_upper_bounds,
567 absl::Span<const int64_t> linear_constraint_ids) {
570 const int num_cols = glp_get_num_cols(problem);
571 for (
int c = 1;
c <= num_cols; ++
c) {
572 if (unrounded_variable_lower_bounds[
c - 1] >
573 unrounded_variable_upper_bounds[
c - 1]) {
574 inverted_bounds.variables.push_back(variable_ids[
c - 1]);
578 const int num_rows = glp_get_num_rows(problem);
579 for (
int r = 1; r <= num_rows; ++r) {
580 if (glp_get_row_lb(problem, r) > glp_get_row_ub(problem, r)) {
581 inverted_bounds.linear_constraints.push_back(
582 linear_constraint_ids[r - 1]);
586 return inverted_bounds;
592absl::StatusOr<TerminationProto> MipTerminationOnSuccess(
593 glp_prob*
const problem, MipCallbackData*
const mip_cb_data) {
594 if (mip_cb_data ==
nullptr) {
595 return absl::InternalError(
596 "MipTerminationOnSuccess() called with nullptr mip_cb_data");
598 const int status = glp_mip_status(problem);
599 const bool is_maximize = glp_get_obj_dir(problem) == GLP_MAX;
603 const double objective_value = glp_mip_obj_val(problem);
604 if (status == GLP_OPT) {
613 mip_cb_data->best_bound(),
"glp_mip_status() returned GLP_FEAS");
622 return absl::InternalError(
623 absl::StrCat(
"glp_intopt() returned 0 but glp_mip_status()"
624 "returned the unexpected value ",
632absl::StatusOr<TerminationProto> InteriorTerminationOnSuccess(
633 glp_prob*
const problem, MipCallbackData*) {
634 const int status = glp_ipt_status(problem);
635 const bool is_maximize = glp_get_obj_dir(problem) == GLP_MAX;
638 const double objective_value = glp_ipt_obj_val(problem);
648 "glp_ipt_status() returned GLP_INFEAS");
662 return absl::InternalError(
663 absl::StrCat(
"glp_interior() returned 0 but glp_ipt_status()"
664 "returned the unexpected value ",
672absl::StatusOr<TerminationProto> SimplexTerminationOnSuccess(
673 glp_prob*
const problem, MipCallbackData*) {
681 const int prim_status = glp_get_prim_stat(problem);
682 const int dual_status = glp_get_dual_stat(problem);
683 const bool is_maximize = glp_get_obj_dir(problem) == GLP_MAX;
687 const auto unexpected_dual_stat = [&]() -> absl::Status {
689 <<
"glp_simplex() returned 0 but glp_get_dual_stat() returned the "
692 <<
" while glp_get_prim_stat() returned "
696 switch (prim_status) {
698 switch (dual_status) {
703 const double objective_value = glp_get_obj_val(problem);
710 return unexpected_dual_stat();
713 switch (dual_status) {
721 return unexpected_dual_stat();
724 switch (dual_status) {
735 return unexpected_dual_stat();
745 return unexpected_dual_stat();
748 return absl::InternalError(
749 absl::StrCat(
"glp_simplex() returned 0 but glp_get_prim_stat() "
750 "returned the unexpected value ",
759using TerminationOnSuccessFn = std::function<absl::StatusOr<TerminationProto>(
760 glp_prob* problem, MipCallbackData*
const mip_cb_data)>;
777absl::StatusOr<TerminationProto> BuildTermination(
778 glp_prob*
const problem,
const absl::string_view fn_name,
const int rc,
779 const TerminationOnSuccessFn termination_on_success,
780 MipCallbackData*
const mip_cb_data,
781 const std::optional<double> feasible_solution_objective_value,
782 const double gap_limit) {
783 const bool is_maximize = glp_get_obj_dir(problem) == GLP_MAX;
784 if (mip_cb_data !=
nullptr &&
785 mip_cb_data->HasBeenInterruptedByInterrupter()) {
787 feasible_solution_objective_value);
794 return termination_on_success(problem, mip_cb_data);
805 <<
"` but the model does not contain variables with inverted "
810 feasible_solution_objective_value);
813 feasible_solution_objective_value);
815 if (!feasible_solution_objective_value.has_value()) {
818 <<
"` but glp_mip_status() returned "
824 <<
"` but there is no MipCallbackData";
826 const double objective_value = feasible_solution_objective_value.value();
831 mip_cb_data->best_bound().value_or(
836 feasible_solution_objective_value);
852 feasible_solution_objective_value);
858 " which means that there is a numeric stability issue "
859 "solving Newtonian system"));
870int TermHook(
void*
const info,
const char*
const message) {
880double OffsetOnlyObjVal(glp_prob*
const problem) {
881 return glp_get_obj_coef(problem, 0);
887int OptStatus(glp_prob*) {
return GLP_OPT; }
894 return absl::WrapUnique(
new GlpkSolver(model));
897GlpkSolver::GlpkSolver(
const ModelProto& model)
898 : thread_id_(
std::this_thread::get_id()), problem_(glp_create_prob()) {
911 for (
const auto [v, coeff] :
913 const int col_index = variables_.id_to_index.at(v);
914 CHECK_EQ(variables_.ids[col_index - 1], v);
915 glp_set_obj_coef(problem_, col_index, coeff);
918 const SparseDoubleMatrixProto& proto_matrix =
921 problem_, proto_matrix.row_ids_size(),
922 MatrixIds(proto_matrix.row_ids(), linear_constraints_.id_to_index).data(),
923 MatrixIds(proto_matrix.column_ids(), variables_.id_to_index).data(),
924 MatrixCoefficients(proto_matrix.coefficients()).data());
930 if (
const absl::Status status = CheckCurrentThread(); !status.ok()) {
931 LOG(ERROR) << status;
933 glp_delete_prob(problem_);
939 const bool has_finite_dual_bound) {
947 return problem_status;
950 return problem_status;
953 switch (mip_status) {
957 return problem_status;
966 if (has_finite_dual_bound) {
969 return problem_status;
972absl::StatusOr<FeasibilityStatusProto> TranslateProblemStatus(
973 const int glpk_status,
const absl::string_view fn_name) {
974 switch (glpk_status) {
983 return absl::InternalError(
984 absl::StrCat(fn_name,
" returned the unexpected value ",
993absl::StatusOr<ProblemStatusProto> GetSimplexProblemStatusProto(
994 const int glp_simplex_rc,
const int glpk_primal_status,
995 const int glpk_dual_status) {
1000 switch (glp_simplex_rc) {
1004 return problem_status;
1008 return problem_status;
1013 TranslateProblemStatus(glpk_primal_status,
"glp_get_prim_stat"));
1014 problem_status.set_primal_status(primal_status);
1019 TranslateProblemStatus(glpk_dual_status,
"glp_get_dual_stat"));
1020 problem_status.set_dual_status(dual_status);
1021 return problem_status;
1026absl::StatusOr<ProblemStatusProto> GetBarrierProblemStatusProto(
1027 const int glp_interior_rc,
const int ipt_status) {
1032 switch (glp_interior_rc) {
1035 switch (ipt_status) {
1039 return problem_status;
1041 return problem_status;
1043 problem_status.set_primal_or_dual_infeasible(
true);
1044 return problem_status;
1046 return problem_status;
1048 return absl::InternalError(
1049 absl::StrCat(
"glp_ipt_status returned the unexpected value ",
1053 return problem_status;
1067 model_parameters, kGlpkSupportedStructures,
"GLPK"));
1070 const absl::Time start = absl::Now();
1072 const auto set_solve_time =
1075 absl::Now() - start,
1076 result.mutable_solve_stats()->mutable_solve_time()))
1077 <<
"failed to set SolveResultProto.solve_stats.solve_time";
1078 return absl::OkStatus();
1085 variables_.unrounded_lower_bounds,
1086 variables_.unrounded_upper_bounds,
1087 linear_constraints_.ids)
1093 std::optional<SolveResultProto> result = EmptyIntegerBoundsResult();
1094 if (result.has_value()) {
1096 return std::move(result).value();
1104 if (term_hook_data.has_user_message_callback()) {
1109 glp_term_hook(TermHook, &term_hook_data);
1114 auto message_cb_cleanup = absl::MakeCleanup([&]() {
1115 if (term_hook_data.has_user_message_callback()) {
1116 glp_term_hook(
nullptr,
nullptr);
1117 term_hook_data.Flush();
1123 const bool is_mip = IsMip(problem_);
1127 int (*get_prim_stat)(glp_prob*) =
nullptr;
1128 double (*obj_val)(glp_prob*) =
nullptr;
1129 double (*col_val)(glp_prob*, int) =
nullptr;
1131 int (*get_dual_stat)(glp_prob*) =
nullptr;
1132 double (*row_dual)(glp_prob*, int) =
nullptr;
1133 double (*col_dual)(glp_prob*, int) =
nullptr;
1135 const bool maximize = glp_get_obj_dir(problem_) == GLP_MAX;
1136 double best_dual_bound = maximize ?
kInf : -
kInf;
1149 get_prim_stat = glp_mip_status;
1150 obj_val = glp_mip_obj_val;
1151 col_val = glp_mip_col_val;
1153 glp_iocp glpk_parameters;
1154 glp_init_iocp(&glpk_parameters);
1156 parameters, term_hook_data.has_user_message_callback(),
1158 SetTimeLimitParameter(parameters, glpk_parameters);
1163 glpk_parameters.presolve = GLP_ON;
1166 <<
"parameter presolve not supported by GLPK for MIP";
1173 <<
"parameter absolute_gap_tolerance not supported by GLPK "
1174 "(relative_gap_tolerance is supported)";
1178 <<
"parameter iteration_limit not supported by GLPK for MIP";
1182 <<
"parameter lp_algorithm not supported by GLPK for MIP";
1184 MipCallbackData mip_cb_data(interrupter);
1185 glpk_parameters.cb_func = MipCallback;
1186 glpk_parameters.cb_info = &mip_cb_data;
1187 const int rc = glp_intopt(problem_, &glpk_parameters);
1188 const int mip_status = glp_mip_status(problem_);
1189 const bool has_feasible_solution =
1190 mip_status == GLP_OPT || mip_status == GLP_FEAS;
1191 const std::optional<double> feasible_solution_objective_value =
1192 has_feasible_solution ? std::make_optional(glp_mip_obj_val(problem_))
1195 *result.mutable_termination(),
1196 BuildTermination(problem_,
"glp_intopt", rc, MipTerminationOnSuccess,
1197 &mip_cb_data, feasible_solution_objective_value,
1198 glpk_parameters.mip_gap));
1199 if (mip_cb_data.best_bound().has_value()) {
1200 best_dual_bound = *mip_cb_data.best_bound();
1202 *result.mutable_solve_stats()->mutable_problem_status() =
1203 GetMipProblemStatusProto(rc, mip_status,
1204 std::isfinite(best_dual_bound));
1207 get_prim_stat = glp_ipt_status;
1208 obj_val = glp_ipt_obj_val;
1209 col_val = glp_ipt_col_prim;
1211 get_dual_stat = glp_ipt_status;
1212 row_dual = glp_ipt_row_dual;
1213 col_dual = glp_ipt_col_dual;
1215 glp_iptcp glpk_parameters;
1216 glp_init_iptcp(&glpk_parameters);
1218 return absl::InvalidArgumentError(
1219 "parameter time_limit not supported by GLPK for interior point "
1223 parameters, term_hook_data.has_user_message_callback(),
1232 if (IsEmpty(problem_)) {
1233 get_prim_stat = OptStatus;
1234 get_dual_stat = OptStatus;
1235 obj_val = OffsetOnlyObjVal;
1236 const double objective_value = OffsetOnlyObjVal(problem_);
1240 "glp_interior() not called since the model is empty");
1241 result.mutable_solve_stats()
1242 ->mutable_problem_status()
1244 result.mutable_solve_stats()->mutable_problem_status()->set_dual_status(
1249 const int glp_interior_rc = glp_interior(problem_, &glpk_parameters);
1250 const int ipt_status = glp_ipt_status(problem_);
1251 const bool has_feasible_solution = ipt_status == GLP_OPT;
1252 const std::optional<double> feasible_solution_objective_value =
1253 has_feasible_solution
1254 ? std::make_optional(glp_ipt_obj_val(problem_))
1257 *result.mutable_termination(),
1258 BuildTermination(problem_,
"glp_interior", glp_interior_rc,
1259 InteriorTerminationOnSuccess,
1261 feasible_solution_objective_value,
1264 *result.mutable_solve_stats()->mutable_problem_status(),
1265 GetBarrierProblemStatusProto(glp_interior_rc,
1269 get_prim_stat = glp_get_prim_stat;
1270 obj_val = glp_get_obj_val;
1271 col_val = glp_get_col_prim;
1273 get_dual_stat = glp_get_dual_stat;
1274 row_dual = glp_get_row_dual;
1275 col_dual = glp_get_col_dual;
1277 glp_smcp glpk_parameters;
1278 glp_init_smcp(&glpk_parameters);
1280 parameters, term_hook_data.has_user_message_callback(),
1282 SetTimeLimitParameter(parameters, glpk_parameters);
1286 const int glp_simplex_rc = glp_simplex(problem_, &glpk_parameters);
1287 const int prim_stat = glp_get_prim_stat(problem_);
1288 const bool has_feasible_solution = prim_stat == GLP_FEAS;
1289 const std::optional<double> feasible_solution_objective_value =
1290 has_feasible_solution ? std::make_optional(glp_get_obj_val(problem_))
1293 BuildTermination(problem_,
"glp_simplex", glp_simplex_rc,
1294 SimplexTerminationOnSuccess,
1296 feasible_solution_objective_value,
1302 if (glp_get_prim_stat(problem_) == GLP_NOFEAS &&
1303 glp_get_dual_stat(problem_) == GLP_FEAS) {
1304 best_dual_bound = maximize ? -
kInf : +
kInf;
1308 GetSimplexProblemStatusProto(
1311 glp_get_dual_stat(problem_)));
1312 VLOG(1) <<
"glp_get_status: "
1315 <<
" glp_get_dual_stat: "
1321 std::move(message_cb_cleanup).Invoke();
1323 switch (result.termination().reason()) {
1326 result.mutable_solve_stats()->set_best_primal_bound(obj_val(problem_));
1331 result.mutable_solve_stats()->set_best_primal_bound(maximize ? +
kInf
1335 result.mutable_solve_stats()->set_best_primal_bound(maximize ? -
kInf
1342 result.mutable_solve_stats()->set_best_dual_bound(best_dual_bound);
1344 AddPrimalSolution(get_prim_stat, obj_val, col_val, model_parameters,
1347 AddDualSolution(get_dual_stat, obj_val, row_dual, col_dual,
1352 *result.add_solutions() = std::move(
solution);
1362void GlpkSolver::AddVariables(
const VariablesProto& new_variables) {
1363 if (new_variables.
ids().empty()) {
1368 const int first_new_var_index = variables_.ids.size() + 1;
1370 variables_.ids.insert(variables_.ids.end(), new_variables.
ids().begin(),
1371 new_variables.
ids().end());
1372 for (
int v = 0; v < new_variables.
ids_size(); ++v) {
1373 CHECK(variables_.id_to_index
1374 .try_emplace(new_variables.
ids(v), first_new_var_index + v)
1377 glp_add_cols(problem_, new_variables.
ids_size());
1378 if (!new_variables.
names().empty()) {
1379 for (
int v = 0; v < new_variables.
names_size(); ++v) {
1381 problem_, v + first_new_var_index,
1388 variables_.unrounded_lower_bounds.insert(
1389 variables_.unrounded_lower_bounds.end(),
1391 variables_.unrounded_upper_bounds.insert(
1392 variables_.unrounded_upper_bounds.end(),
1401 glp_set_col_kind(problem_, i + first_new_var_index,
1402 new_variables.
integers(i) ? GLP_IV : GLP_CV);
1403 SetBounds<Variables>(problem_, i + first_new_var_index,
1409void GlpkSolver::AddLinearConstraints(
1411 if (new_linear_constraints.ids().empty()) {
1416 const int first_new_cstr_index = linear_constraints_.ids.size() + 1;
1418 linear_constraints_.ids.insert(linear_constraints_.ids.end(),
1419 new_linear_constraints.ids().begin(),
1420 new_linear_constraints.ids().end());
1421 for (
int c = 0;
c < new_linear_constraints.ids_size(); ++
c) {
1422 CHECK(linear_constraints_.id_to_index
1423 .try_emplace(new_linear_constraints.ids(
c),
1424 first_new_cstr_index +
c)
1427 glp_add_rows(problem_, new_linear_constraints.ids_size());
1428 if (!new_linear_constraints.names().empty()) {
1429 for (
int c = 0;
c < new_linear_constraints.names_size(); ++
c) {
1431 problem_,
c + first_new_cstr_index,
1435 CHECK_EQ(new_linear_constraints.lower_bounds_size(),
1436 new_linear_constraints.upper_bounds_size());
1437 for (
int i = 0;
i < new_linear_constraints.lower_bounds_size(); ++
i) {
1438 SetBounds<LinearConstraints>(
1439 problem_, i + first_new_cstr_index,
1440 {.lower = new_linear_constraints.lower_bounds(i),
1441 .upper = new_linear_constraints.upper_bounds(i)});
1445void GlpkSolver::UpdateObjectiveCoefficients(
1447 for (
const auto [
id, coeff] :
MakeView(coefficients_proto)) {
1448 const int col_index = variables_.id_to_index.at(
id);
1449 CHECK_EQ(variables_.ids[col_index - 1],
id);
1450 glp_set_obj_coef(problem_, col_index, coeff);
1454void GlpkSolver::UpdateLinearConstraintMatrix(
1456 const std::optional<int64_t> first_new_var_id,
1457 const std::optional<int64_t> first_new_cstr_id) {
1491 GlpkSparseVector data(
static_cast<int>(variables_.ids.size()));
1492 for (
const auto& [row_id, row_coefficients] :
1497 first_new_var_id)) {
1500 const int row_index = linear_constraints_.id_to_index.at(row_id);
1501 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1504 data.Load([&](
int*
const indices,
double*
const values) {
1505 return glp_get_mat_row(problem_, row_index, indices, values);
1509 for (
const auto [col_id, coefficient] : row_coefficients) {
1510 const int col_index = variables_.id_to_index.at(col_id);
1511 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1512 data.Set(col_index, coefficient);
1516 glp_set_mat_row(problem_, row_index, data.size(), data.indices(),
1523 if (first_new_var_id.has_value()) {
1524 GlpkSparseVector data(
static_cast<int>(linear_constraints_.ids.size()));
1533 const int col_index = variables_.id_to_index.at(col_id);
1534 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1539 for (
const auto [row_id, coefficient] :
MakeView(col_coefficients)) {
1540 const int row_index = linear_constraints_.id_to_index.at(row_id);
1541 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1542 data.Set(row_index, coefficient);
1546 glp_set_mat_col(problem_, col_index, data.size(), data.indices(),
1552 if (first_new_cstr_id.has_value()) {
1553 GlpkSparseVector data(
static_cast<int>(variables_.ids.size()));
1554 for (
const auto& [row_id, row_coefficients] :
1562 const int row_index = linear_constraints_.id_to_index.at(row_id);
1563 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1568 for (
const auto [col_id, coefficient] : row_coefficients) {
1569 const int col_index = variables_.id_to_index.at(col_id);
1570 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1571 data.Set(col_index, coefficient);
1575 glp_set_mat_row(problem_, row_index, data.size(), data.indices(),
1581void GlpkSolver::AddPrimalSolution(
1582 int (*get_prim_stat)(glp_prob*),
double (*obj_val)(glp_prob*),
1583 double (*col_val)(glp_prob*,
int),
1586 const int status = get_prim_stat(problem_);
1587 if (status == GLP_OPT || status == GLP_FEAS) {
1589 *solution_proto.mutable_primal_solution();
1593 FilteredVector(problem_, model_parameters.variable_values_filter(),
1594 variables_.ids, col_val);
1598void GlpkSolver::AddDualSolution(
1599 int (*get_dual_stat)(glp_prob*),
double (*obj_val)(glp_prob*),
1600 double (*row_dual)(glp_prob*,
int),
double (*col_dual)(glp_prob*,
int),
1603 const int status = get_dual_stat(problem_);
1604 if (status == GLP_OPT || status == GLP_FEAS) {
1605 DualSolutionProto& dual_solution = *solution_proto.mutable_dual_solution();
1606 dual_solution.set_objective_value(obj_val(problem_));
1607 *dual_solution.mutable_dual_values() =
1608 FilteredVector(problem_, model_parameters.dual_values_filter(),
1609 linear_constraints_.ids, row_dual);
1610 *dual_solution.mutable_reduced_costs() =
1611 FilteredVector(problem_, model_parameters.reduced_costs_filter(),
1612 variables_.ids, col_dual);
1620absl::Status GlpkSolver::AddPrimalOrDualRay(
1625 if (!opt_unbound_ray.has_value()) {
1626 return absl::OkStatus();
1629 const int num_cstrs = linear_constraints_.ids.size();
1630 switch (opt_unbound_ray->type) {
1632 const int num_cstrs =
static_cast<int>(linear_constraints_.ids.size());
1637 std::vector<double> ray_values(variables_.ids.size());
1639 for (
const auto [k, value] : opt_unbound_ray->non_zero_components) {
1640 if (k <= num_cstrs) {
1644 const int var_index = k - num_cstrs;
1645 CHECK_GE(var_index, 1);
1646 ray_values[var_index - 1] = value;
1649 *result.add_primal_rays()->mutable_variable_values() =
1650 FilteredRay(model_parameters.variable_values_filter(), variables_.ids,
1653 return absl::OkStatus();
1662 std::vector<double> ray_reduced_costs(variables_.ids.size());
1663 std::vector<double> ray_dual_values(num_cstrs);
1665 for (
const auto [k, value] : opt_unbound_ray->non_zero_components) {
1666 if (k <= num_cstrs) {
1667 ray_dual_values[k - 1] = value;
1669 const int var_index = k - num_cstrs;
1670 CHECK_GE(var_index, 1);
1671 ray_reduced_costs[var_index - 1] = value;
1675 DualRayProto& dual_ray = *result.add_dual_rays();
1676 *dual_ray.mutable_dual_values() =
1677 FilteredRay(model_parameters.dual_values_filter(),
1678 linear_constraints_.ids, ray_dual_values);
1679 *dual_ray.mutable_reduced_costs() =
1680 FilteredRay(model_parameters.reduced_costs_filter(), variables_.ids,
1683 return absl::OkStatus();
1700 const std::vector<int> sorted_deleted_cols = DeleteRowsOrCols(
1702 DeleteRowOrColData(variables_.unrounded_lower_bounds, sorted_deleted_cols);
1703 DeleteRowOrColData(variables_.unrounded_upper_bounds, sorted_deleted_cols);
1704 CHECK_EQ(variables_.unrounded_lower_bounds.size(),
1705 variables_.unrounded_upper_bounds.size());
1706 CHECK_EQ(variables_.unrounded_lower_bounds.size(), variables_.ids.size());
1708 DeleteRowsOrCols(problem_, linear_constraints_,
1711 for (
const auto [var_id, is_integer] :
1714 const int var_index = variables_.id_to_index.at(var_id);
1715 glp_set_col_kind(problem_, var_index, is_integer ? GLP_IV : GLP_CV);
1722 SetBounds<Variables>(
1723 problem_, var_index,
1724 {.lower = variables_.unrounded_lower_bounds[var_index - 1],
1725 .upper = variables_.unrounded_upper_bounds[var_index - 1]});
1727 for (
const auto [var_id, lower_bound] :
1729 variables_.unrounded_lower_bounds[variables_.id_to_index.at(var_id) - 1] =
1732 for (
const auto [var_id, upper_bound] :
1734 variables_.unrounded_upper_bounds[variables_.id_to_index.at(var_id) - 1] =
1738 problem_, variables_,
1741 UpdateBounds(problem_, linear_constraints_,
1751 glp_set_obj_dir(problem_,
1758 glp_set_obj_coef(problem_, 0,
1761 UpdateObjectiveCoefficients(
1764 UpdateLinearConstraintMatrix(
1773absl::Status GlpkSolver::CheckCurrentThread() {
1774 if (std::this_thread::get_id() != thread_id_) {
1775 return absl::InvalidArgumentError(
1776 "GLPK is not thread-safe and thus the solver should only be used on "
1777 "the same thread as it was created");
1779 return absl::OkStatus();
1782std::optional<SolveResultProto> GlpkSolver::EmptyIntegerBoundsResult() {
1783 const int num_cols = glp_get_num_cols(problem_);
1784 for (
int c = 1;
c <= num_cols; ++
c) {
1785 if (!variables_.IsInteger(problem_,
c)) {
1788 const double lb = variables_.unrounded_lower_bounds[
c - 1];
1789 const double ub = variables_.unrounded_upper_bounds[
c - 1];
1796 if (std::ceil(lb) <= std::floor(ub)) {
1803 glp_get_obj_dir(problem_) == GLP_MAX,
1804 variables_.ids[
c - 1],
1808 return std::nullopt;
1811absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
1815 return absl::UnimplementedError(
1816 "GLPK does not provide a method to compute an infeasible subsystem");
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
bool compute_unbound_rays_if_possible() const
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
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const InitArgs &init_args)
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto ¶meters, MessageCallback message_cb, const SolveInterrupter *absl_nullable interrupter) override
const ::operations_research::math_opt::SparseDoubleVectorProto & upper_bounds() const
const ::operations_research::math_opt::SparseDoubleVectorProto & lower_bounds() const
const ::operations_research::math_opt::VariablesProto & variables() const
const ::operations_research::math_opt::LinearConstraintsProto & linear_constraints() const
const ::std::string & name() const
const ::operations_research::math_opt::ObjectiveProto & objective() const
const ::operations_research::math_opt::SparseDoubleMatrixProto & linear_constraint_matrix() 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::VariableUpdatesProto & variable_updates() const
const ::operations_research::math_opt::ObjectiveUpdatesProto & 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::SparseDoubleVectorProto & linear_coefficients() const
double offset_update() const
const ::operations_research::math_opt::SparseDoubleVectorProto & linear_coefficients() const
bool direction_update() const
bool has_direction_update() const
bool has_offset_update() const
void set_primal_status(::operations_research::math_opt::FeasibilityStatusProto value)
void set_dual_status(::operations_research::math_opt::FeasibilityStatusProto value)
::operations_research::math_opt::EmphasisProto presolve() const
const ::operations_research::math_opt::GlpkParametersProto & glpk() const
bool has_absolute_gap_tolerance() const
::operations_research::math_opt::LPAlgorithmProto lp_algorithm() const
double relative_gap_tolerance() const
bool has_relative_gap_tolerance() const
bool has_time_limit() const
bool has_iteration_limit() const
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >( const CallbackDataProto &)> Callback
::google::protobuf::RepeatedField<::int64_t > *PROTOBUF_NONNULL mutable_ids()
const ::operations_research::math_opt::SparseDoubleVectorProto & upper_bounds() const
const ::operations_research::math_opt::SparseDoubleVectorProto & lower_bounds() const
const ::operations_research::math_opt::SparseBoolVectorProto & integers() const
const ::std::string & names(int index) const
double lower_bounds(int index) const
bool integers(int index) const
int upper_bounds_size() const
int lower_bounds_size() const
double upper_bounds(int index) const
::int64_t ids(int index) const
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)
@ TERMINATION_REASON_OPTIMAL
@ TERMINATION_REASON_FEASIBLE
@ TERMINATION_REASON_NUMERICAL_ERROR
@ TERMINATION_REASON_UNBOUNDED
std::optional< int64_t > FirstLinearConstraintId(const LinearConstraintsProto &linear_constraints)
absl::Status ModelSolveParametersAreSupported(const ModelSolveParametersProto &model_parameters, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
@ SOLUTION_STATUS_FEASIBLE
std::function< CallbackResult(const CallbackData &)> Callback
@ LP_ALGORITHM_DUAL_SIMPLEX
@ LP_ALGORITHM_UNSPECIFIED
@ LP_ALGORITHM_PRIMAL_SIMPLEX
TerminationProto OptimalTerminationProto(const double finite_primal_objective, const double dual_objective, const absl::string_view detail)
@ FEASIBILITY_STATUS_FEASIBLE
@ FEASIBILITY_STATUS_UNDETERMINED
@ FEASIBILITY_STATUS_INFEASIBLE
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)
TerminationProto FeasibleTerminationProto(const bool is_maximize, const LimitProto limit, const double primal_objective, const std::optional< double > optional_dual_objective, const absl::string_view detail)
TerminationProto NoSolutionFoundTerminationProto(const bool is_maximize, const LimitProto limit, const std::optional< double > optional_dual_objective, const absl::string_view detail)
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto ®istration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
double WorstGLPKDualBound(const bool is_maximize, const double objective_value, const double relative_gap_limit)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
std::vector< std::pair< int64_t, SparseVector< double > > > TransposeSparseSubmatrix(const SparseSubmatrixRowsView &submatrix_by_rows)
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)
std::optional< int64_t > FirstVariableId(const VariablesProto &variables)
TerminationProto InfeasibleTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
TerminationProto UnboundedTerminationProto(const bool is_maximize, const absl::string_view detail)
absl::StatusOr< std::optional< GlpkRay > > GlpkComputeUnboundRay(glp_prob *const problem)
SolveResultProto ResultForIntegerInfeasible(const bool is_maximize, const int64_t bad_variable_id, const double lb, const double ub)
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)
std::string TruncateAndQuoteGLPKName(const std::string_view original_name)
void SetupGlpkEnvAutomaticDeletion()
std::string ReturnCodeString(const int rc)
std::string SolutionStatusString(const int status)
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
StatusBuilder InternalErrorBuilder()
StatusBuilder InvalidArgumentErrorBuilder()
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)