30#include "absl/cleanup/cleanup.h"
31#include "absl/container/flat_hash_map.h"
32#include "absl/log/check.h"
33#include "absl/memory/memory.h"
34#include "absl/status/status.h"
35#include "absl/status/statusor.h"
36#include "absl/strings/str_cat.h"
37#include "absl/strings/str_join.h"
38#include "absl/strings/string_view.h"
39#include "absl/time/clock.h"
40#include "absl/time/time.h"
41#include "absl/types/span.h"
47#include "ortools/math_opt/callback.pb.h"
54#include "ortools/math_opt/infeasible_subsystem.pb.h"
55#include "ortools/math_opt/model.pb.h"
56#include "ortools/math_opt/model_parameters.pb.h"
57#include "ortools/math_opt/model_update.pb.h"
58#include "ortools/math_opt/parameters.pb.h"
59#include "ortools/math_opt/result.pb.h"
60#include "ortools/math_opt/solution.pb.h"
61#include "ortools/math_opt/solvers/glpk.pb.h"
66#include "ortools/math_opt/sparse_containers.pb.h"
76constexpr double kInf = std::numeric_limits<double>::infinity();
77constexpr double kNaN = std::numeric_limits<double>::quiet_NaN();
79constexpr SupportedProblemStructures kGlpkSupportedStructures = {
98template <
typename Dimension>
99void SetBounds(glp_prob*
const problem,
const int k,
const Bounds& bounds) {
101 const bool is_integer = Dimension::IsInteger(problem, k);
102 const double lb = is_integer ? std::ceil(bounds.lower) : bounds.lower;
103 const double ub = is_integer ? std::floor(bounds.upper) : bounds.upper;
105 if (std::isinf(lb) && std::isinf(ub)) {
107 }
else if (std::isinf(lb)) {
109 }
else if (std::isinf(ub)) {
111 }
else if (lb == ub) {
116 Dimension::kSetBounds(problem, k, type, lb, ub);
124template <
typename Dimension>
125Bounds GetBounds(glp_prob*
const problem,
const int k) {
126 const int type = Dimension::kGetType(problem, k);
131 return {.lower = Dimension::kGetLb(problem, k)};
133 return {.upper = Dimension::kGetUb(problem, k)};
136 return {.lower = Dimension::kGetLb(problem, k),
137 .upper = Dimension::kGetUb(problem, k)};
152template <
typename Dimension>
153void UpdateBounds(glp_prob*
const problem,
const Dimension& dimension,
154 const SparseDoubleVectorProto& lower_bounds_proto,
155 const SparseDoubleVectorProto& upper_bounds_proto) {
163 std::optional<int64_t> next_id;
165 if (!next_id.has_value() || current_lower_bound->first < *next_id) {
166 next_id = current_lower_bound->first;
170 if (!next_id.has_value() || current_upper_bound->first < *next_id) {
171 next_id = current_upper_bound->first;
175 if (!next_id.has_value()) {
181 const int row_or_col_index = dimension.id_to_index.at(*next_id);
182 CHECK_EQ(dimension.ids[row_or_col_index - 1], *next_id);
186 Bounds bounds = GetBounds<Dimension>(problem,
189 current_lower_bound->first == *next_id) {
190 bounds.lower = current_lower_bound->second;
191 ++current_lower_bound;
194 current_upper_bound->first == *next_id) {
195 bounds.upper = current_upper_bound->second;
196 ++current_upper_bound;
198 SetBounds<Dimension>(problem, row_or_col_index,
212void DeleteRowOrColData(std::vector<V>& data,
213 absl::Span<const int> sorted_deleted_rows_or_cols) {
214 if (sorted_deleted_rows_or_cols.empty()) {
219 std::size_t next_insertion_point = 0;
220 std::size_t current_row_or_col = 0;
221 for (std::size_t i = 1;
i < sorted_deleted_rows_or_cols.size(); ++
i) {
222 const int deleted_row_or_col = sorted_deleted_rows_or_cols[
i];
223 for (; current_row_or_col + 1 < deleted_row_or_col;
224 ++current_row_or_col, ++next_insertion_point) {
225 DCHECK_LT(current_row_or_col, data.size());
226 data[next_insertion_point] = data[current_row_or_col];
229 ++current_row_or_col;
231 for (; current_row_or_col < data.size();
232 ++current_row_or_col, ++next_insertion_point) {
233 data[next_insertion_point] = data[current_row_or_col];
235 data.resize(next_insertion_point);
246template <
typename Dimension>
247std::vector<int> DeleteRowsOrCols(
248 glp_prob*
const problem, Dimension& dimension,
249 const google::protobuf::RepeatedField<int64_t>& deleted_ids) {
250 if (deleted_ids.empty()) {
257 std::vector<int> deleted_rows_or_cols;
260 deleted_rows_or_cols.reserve(deleted_ids.size() + 1);
261 deleted_rows_or_cols.push_back(-1);
262 for (
const int64_t deleted_id : deleted_ids) {
263 deleted_rows_or_cols.push_back(dimension.id_to_index.at(deleted_id));
265 Dimension::kDelElts(problem, deleted_rows_or_cols.size() - 1,
266 deleted_rows_or_cols.data());
272 std::is_sorted(deleted_rows_or_cols.begin(), deleted_rows_or_cols.end()));
275 DeleteRowOrColData(dimension.ids, deleted_rows_or_cols);
278 for (
const int64_t deleted_id : deleted_ids) {
279 CHECK(dimension.id_to_index.erase(deleted_id));
281 for (
int i = 0;
i < dimension.ids.size(); ++
i) {
282 dimension.id_to_index.at(dimension.ids[i]) =
i + 1;
285 return deleted_rows_or_cols;
294std::vector<int> MatrixIds(
295 const google::protobuf::RepeatedField<int64_t>& proto_ids,
296 const absl::flat_hash_map<int64_t, int>& id_to_index) {
297 std::vector<int> ids;
298 ids.reserve(proto_ids.size() + 1);
301 for (
const int64_t proto_id : proto_ids) {
302 ids.push_back(id_to_index.at(proto_id));
310std::vector<double> MatrixCoefficients(
311 const google::protobuf::RepeatedField<double>& proto_coeffs) {
312 std::vector<double> coeffs(proto_coeffs.size() + 1);
315 std::copy(proto_coeffs.begin(), proto_coeffs.end(), coeffs.begin() + 1);
320bool IsMip(glp_prob*
const problem) {
321 const int num_vars = glp_get_num_cols(problem);
322 for (
int v = 1; v <= num_vars; ++v) {
323 if (glp_get_col_kind(problem, v) != GLP_CV) {
331bool IsEmpty(glp_prob*
const problem) {
332 return glp_get_num_cols(problem) == 0 && glp_get_num_rows(problem) == 0;
337SparseDoubleVectorProto FilteredVector(glp_prob*
const problem,
338 const SparseVectorFilterProto& filter,
339 absl::Span<const int64_t> ids,
340 double (*
const getter)(glp_prob*,
int)) {
341 SparseDoubleVectorProto vec;
342 vec.mutable_ids()->Reserve(ids.size());
343 vec.mutable_values()->Reserve(ids.size());
345 SparseVectorFilterPredicate predicate(filter);
346 for (
int i = 0;
i < ids.size(); ++
i) {
347 const double value = getter(problem, i + 1);
348 if (predicate.AcceptsAndUpdate(ids[i],
value)) {
350 vec.add_values(
value);
358SparseDoubleVectorProto FilteredRay(
const SparseVectorFilterProto& filter,
359 absl::Span<const int64_t> ids,
360 absl::Span<const double> values) {
361 CHECK_EQ(ids.size(), values.size());
362 SparseDoubleVectorProto vec;
363 SparseVectorFilterPredicate predicate(filter);
364 for (
int i = 0;
i < ids.size(); ++
i) {
365 if (predicate.AcceptsAndUpdate(ids[i], values[i])) {
367 vec.add_values(values[i]);
378template <
typename Parameters>
379absl::Status SetSharedParameters(
const SolveParametersProto&
parameters,
380 const bool has_message_callback,
381 Parameters& glpk_parameters) {
382 std::vector<std::string> warnings;
385 absl::StrCat(
"GLPK only supports parameters.threads = 1; value ",
388 if (
parameters.enable_output() || has_message_callback) {
389 glpk_parameters.msg_lev = GLP_MSG_ALL;
391 glpk_parameters.msg_lev = GLP_MSG_OFF;
394 warnings.push_back(
"parameter node_limit not supported by GLPK");
397 warnings.push_back(
"parameter objective_limit not supported by GLPK");
400 warnings.push_back(
"parameter best_bound_limit not supported by GLPK");
403 warnings.push_back(
"parameter cutoff_limit not supported by GLPK");
406 warnings.push_back(
"parameter solution_limit not supported by GLPK");
409 warnings.push_back(
"parameter random_seed not supported by GLPK");
411 if (
parameters.cuts() != EMPHASIS_UNSPECIFIED) {
412 warnings.push_back(
"parameter cuts not supported by GLPK");
414 if (
parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
415 warnings.push_back(
"parameter heuristics not supported by GLPK");
417 if (
parameters.scaling() != EMPHASIS_UNSPECIFIED) {
418 warnings.push_back(
"parameter scaling not supported by GLPK");
420 if (!warnings.empty()) {
421 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
423 return absl::OkStatus();
430template <
typename Parameters>
431void SetTimeLimitParameter(
const SolveParametersProto&
parameters,
432 Parameters& glpk_parameters) {
434 const int64_t time_limit_ms = absl::ToInt64Milliseconds(
436 glpk_parameters.tm_lim =
static_cast<int>(std::min(
437 static_cast<int64_t
>(std::numeric_limits<int>::max()), time_limit_ms));
443absl::Status SetLPParameters(
const SolveParametersProto&
parameters,
444 glp_smcp& glpk_parameters) {
445 std::vector<std::string> warnings;
447 int limit =
static_cast<int>(std::min<int64_t>(
448 std::numeric_limits<int>::max(),
parameters.iteration_limit()));
449 glpk_parameters.it_lim = limit;
452 case EMPHASIS_UNSPECIFIED:
458 glpk_parameters.presolve = GLP_OFF;
461 glpk_parameters.presolve = GLP_ON;
465 case LP_ALGORITHM_UNSPECIFIED:
467 case LP_ALGORITHM_PRIMAL_SIMPLEX:
468 glpk_parameters.meth = GLP_PRIMAL;
470 case LP_ALGORITHM_DUAL_SIMPLEX:
476 glpk_parameters.meth = GLP_DUALP;
479 warnings.push_back(absl::StrCat(
480 "GLPK does not support ",
482 " for parameters.lp_algorithm"));
485 if (!warnings.empty()) {
486 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
488 return absl::OkStatus();
491class MipCallbackData {
493 explicit MipCallbackData(
const SolveInterrupter*
const interrupter)
494 : interrupter_(interrupter) {}
496 void Callback(glp_tree*
const tree) {
499 switch (glp_ios_reason(tree)) {
513 if (
const int best_node = glp_ios_best_node(tree); best_node != 0) {
514 best_bound_ = glp_ios_node_bound(tree, best_node);
528 if (interrupter_ !=
nullptr && interrupter_->IsInterrupted()) {
529 glp_ios_terminate(tree);
530 interrupted_by_interrupter_ =
true;
535 bool HasBeenInterruptedByInterrupter()
const {
536 return interrupted_by_interrupter_.load();
539 std::optional<double> best_bound()
const {
return best_bound_; }
543 const SolveInterrupter*
const interrupter_;
546 std::atomic<bool> interrupted_by_interrupter_ =
false;
549 std::optional<double> best_bound_;
552void MipCallback(glp_tree*
const tree,
void*
const info) {
553 static_cast<MipCallbackData*
>(info)->
Callback(tree);
561InvertedBounds ListInvertedBounds(
562 glp_prob*
const problem, absl::Span<const int64_t>
variable_ids,
563 absl::Span<const double> unrounded_variable_lower_bounds,
564 absl::Span<const double> unrounded_variable_upper_bounds,
565 absl::Span<const int64_t> linear_constraint_ids) {
566 InvertedBounds inverted_bounds;
568 const int num_cols = glp_get_num_cols(problem);
569 for (
int c = 1;
c <= num_cols; ++
c) {
570 if (unrounded_variable_lower_bounds[
c - 1] >
571 unrounded_variable_upper_bounds[
c - 1]) {
576 const int num_rows = glp_get_num_rows(problem);
577 for (
int r = 1; r <= num_rows; ++r) {
578 if (glp_get_row_lb(problem, r) > glp_get_row_ub(problem, r)) {
579 inverted_bounds.linear_constraints.push_back(
580 linear_constraint_ids[r - 1]);
584 return inverted_bounds;
590absl::StatusOr<TerminationProto> MipTerminationOnSuccess(
591 glp_prob*
const problem, MipCallbackData*
const mip_cb_data) {
592 if (mip_cb_data ==
nullptr) {
593 return absl::InternalError(
594 "MipTerminationOnSuccess() called with nullptr mip_cb_data");
596 const int status = glp_mip_status(problem);
597 const bool is_maximize = glp_get_obj_dir(problem) == GLP_MAX;
611 mip_cb_data->best_bound(),
"glp_mip_status() returned GLP_FEAS");
618 is_maximize, FEASIBILITY_STATUS_FEASIBLE);
620 return absl::InternalError(
621 absl::StrCat(
"glp_intopt() returned 0 but glp_mip_status()"
622 "returned the unexpected value ",
630absl::StatusOr<TerminationProto> InteriorTerminationOnSuccess(
631 glp_prob*
const problem, MipCallbackData*) {
632 const int status = glp_ipt_status(problem);
633 const bool is_maximize = glp_get_obj_dir(problem) == GLP_MAX;
644 is_maximize, LIMIT_UNDETERMINED,
646 "glp_ipt_status() returned GLP_INFEAS");
658 FEASIBILITY_STATUS_UNDETERMINED);
660 return absl::InternalError(
661 absl::StrCat(
"glp_interior() returned 0 but glp_ipt_status()"
662 "returned the unexpected value ",
670absl::StatusOr<TerminationProto> SimplexTerminationOnSuccess(
671 glp_prob*
const problem, MipCallbackData*) {
679 const int prim_status = glp_get_prim_stat(problem);
680 const int dual_status = glp_get_dual_stat(problem);
681 const bool is_maximize = glp_get_obj_dir(problem) == GLP_MAX;
685 const auto unexpected_dual_stat = [&]() -> absl::Status {
687 <<
"glp_simplex() returned 0 but glp_get_dual_stat() returned the "
690 <<
" while glp_get_prim_stat() returned "
694 switch (prim_status) {
696 switch (dual_status) {
708 return unexpected_dual_stat();
711 switch (dual_status) {
715 FEASIBILITY_STATUS_INFEASIBLE);
719 return unexpected_dual_stat();
722 switch (dual_status) {
728 FEASIBILITY_STATUS_FEASIBLE);
732 FEASIBILITY_STATUS_UNDETERMINED);
733 return unexpected_dual_stat();
741 FEASIBILITY_STATUS_INFEASIBLE);
743 return unexpected_dual_stat();
746 return absl::InternalError(
747 absl::StrCat(
"glp_simplex() returned 0 but glp_get_prim_stat() "
748 "returned the unexpected value ",
757using TerminationOnSuccessFn = std::function<absl::StatusOr<TerminationProto>(
758 glp_prob* problem, MipCallbackData*
const mip_cb_data)>;
775absl::StatusOr<TerminationProto> BuildTermination(
776 glp_prob*
const problem,
const absl::string_view fn_name,
const int rc,
777 const TerminationOnSuccessFn termination_on_success,
778 MipCallbackData*
const mip_cb_data,
779 const std::optional<double> feasible_solution_objective_value,
780 const double gap_limit) {
781 const bool is_maximize = glp_get_obj_dir(problem) == GLP_MAX;
782 if (mip_cb_data !=
nullptr &&
783 mip_cb_data->HasBeenInterruptedByInterrupter()) {
785 feasible_solution_objective_value);
792 return termination_on_success(problem, mip_cb_data);
803 <<
"` but the model does not contain variables with inverted "
808 feasible_solution_objective_value);
811 feasible_solution_objective_value);
813 if (!feasible_solution_objective_value.has_value()) {
816 <<
"` but glp_mip_status() returned "
822 <<
"` but there is no MipCallbackData";
824 const double objective_value = feasible_solution_objective_value.value();
829 mip_cb_data->best_bound().value_or(
834 feasible_solution_objective_value);
840 FEASIBILITY_STATUS_UNDETERMINED);
846 FEASIBILITY_STATUS_INFEASIBLE);
850 feasible_solution_objective_value);
854 is_maximize, TERMINATION_REASON_NUMERICAL_ERROR,
856 " which means that there is a numeric stability issue "
857 "solving Newtonian system"));
868int TermHook(
void*
const info,
const char*
const message) {
869 static_cast<BufferedMessageCallback*
>(info)->OnMessage(
message);
878double OffsetOnlyObjVal(glp_prob*
const problem) {
879 return glp_get_obj_coef(problem, 0);
885int OptStatus(glp_prob*) {
return GLP_OPT; }
895GlpkSolver::GlpkSolver(
const ModelProto&
model)
896 : thread_id_(
std::this_thread::get_id()), problem_(glp_create_prob()) {
902 AddVariables(
model.variables());
904 AddLinearConstraints(
model.linear_constraints());
906 glp_set_obj_dir(problem_,
model.objective().maximize() ? GLP_MAX : GLP_MIN);
908 glp_set_obj_coef(problem_, 0,
model.objective().offset());
909 for (
const auto [v, coeff] :
911 const int col_index = variables_.id_to_index.at(v);
912 CHECK_EQ(variables_.ids[col_index - 1], v);
913 glp_set_obj_coef(problem_, col_index, coeff);
916 const SparseDoubleMatrixProto& proto_matrix =
917 model.linear_constraint_matrix();
919 problem_, proto_matrix.row_ids_size(),
920 MatrixIds(proto_matrix.row_ids(), linear_constraints_.id_to_index).data(),
921 MatrixIds(proto_matrix.column_ids(), variables_.id_to_index).data(),
922 MatrixCoefficients(proto_matrix.coefficients()).data());
928 if (
const absl::Status
status = CheckCurrentThread(); !
status.ok()) {
931 glp_delete_prob(problem_);
936ProblemStatusProto GetMipProblemStatusProto(
const int rc,
const int mip_status,
937 const bool has_finite_dual_bound) {
938 ProblemStatusProto problem_status;
939 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
940 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
944 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
945 return problem_status;
947 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
948 return problem_status;
951 switch (mip_status) {
953 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
954 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
955 return problem_status;
957 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
960 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
964 if (has_finite_dual_bound) {
965 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
967 return problem_status;
970absl::StatusOr<FeasibilityStatusProto> TranslateProblemStatus(
971 const int glpk_status,
const absl::string_view fn_name) {
972 switch (glpk_status) {
974 return FEASIBILITY_STATUS_FEASIBLE;
976 return FEASIBILITY_STATUS_INFEASIBLE;
979 return FEASIBILITY_STATUS_UNDETERMINED;
981 return absl::InternalError(
982 absl::StrCat(fn_name,
" returned the unexpected value ",
991absl::StatusOr<ProblemStatusProto> GetSimplexProblemStatusProto(
992 const int glp_simplex_rc,
const int glpk_primal_status,
993 const int glpk_dual_status) {
994 ProblemStatusProto problem_status;
995 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
996 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
998 switch (glp_simplex_rc) {
1001 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
1002 return problem_status;
1005 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
1006 return problem_status;
1010 const FeasibilityStatusProto primal_status,
1011 TranslateProblemStatus(glpk_primal_status,
"glp_get_prim_stat"));
1012 problem_status.set_primal_status(primal_status);
1016 const FeasibilityStatusProto dual_status,
1017 TranslateProblemStatus(glpk_dual_status,
"glp_get_dual_stat"));
1018 problem_status.set_dual_status(dual_status);
1019 return problem_status;
1024absl::StatusOr<ProblemStatusProto> GetBarrierProblemStatusProto(
1025 const int glp_interior_rc,
const int ipt_status) {
1026 ProblemStatusProto problem_status;
1027 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
1028 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
1030 switch (glp_interior_rc) {
1033 switch (ipt_status) {
1035 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
1036 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
1037 return problem_status;
1039 return problem_status;
1041 problem_status.set_primal_or_dual_infeasible(
true);
1042 return problem_status;
1044 return problem_status;
1046 return absl::InternalError(
1047 absl::StrCat(
"glp_ipt_status returned the unexpected value ",
1051 return problem_status;
1059 const ModelSolveParametersProto& model_parameters,
1061 const CallbackRegistrationProto& callback_registration,
1065 const absl::Time
start = absl::Now();
1067 const auto set_solve_time =
1068 [&
start](SolveResultProto& result) -> absl::Status {
1070 absl::Now() -
start,
1071 result.mutable_solve_stats()->mutable_solve_time()))
1072 <<
"failed to set SolveResultProto.solve_stats.solve_time";
1073 return absl::OkStatus();
1080 variables_.unrounded_lower_bounds,
1081 variables_.unrounded_upper_bounds,
1082 linear_constraints_.ids)
1088 std::optional<SolveResultProto> result = EmptyIntegerBoundsResult();
1089 if (result.has_value()) {
1091 return std::move(result).value();
1099 if (term_hook_data.has_user_message_callback()) {
1104 glp_term_hook(TermHook, &term_hook_data);
1109 auto message_cb_cleanup = absl::MakeCleanup([&]() {
1110 if (term_hook_data.has_user_message_callback()) {
1111 glp_term_hook(
nullptr,
nullptr);
1112 term_hook_data.Flush();
1116 SolveResultProto result;
1118 const bool is_mip = IsMip(problem_);
1122 int (*get_prim_stat)(glp_prob*) =
nullptr;
1123 double (*obj_val)(glp_prob*) =
nullptr;
1124 double (*col_val)(glp_prob*, int) =
nullptr;
1126 int (*get_dual_stat)(glp_prob*) =
nullptr;
1127 double (*row_dual)(glp_prob*, int) =
nullptr;
1128 double (*col_dual)(glp_prob*, int) =
nullptr;
1130 const bool maximize = glp_get_obj_dir(problem_) == GLP_MAX;
1131 double best_dual_bound = maximize ?
kInf : -
kInf;
1144 get_prim_stat = glp_mip_status;
1145 obj_val = glp_mip_obj_val;
1146 col_val = glp_mip_col_val;
1148 glp_iocp glpk_parameters;
1149 glp_init_iocp(&glpk_parameters);
1151 parameters, term_hook_data.has_user_message_callback(),
1153 SetTimeLimitParameter(
parameters, glpk_parameters);
1158 glpk_parameters.presolve = GLP_ON;
1159 if (
parameters.presolve() != EMPHASIS_UNSPECIFIED) {
1161 <<
"parameter presolve not supported by GLPK for MIP";
1163 if (
parameters.has_relative_gap_tolerance()) {
1164 glpk_parameters.mip_gap =
parameters.relative_gap_tolerance();
1166 if (
parameters.has_absolute_gap_tolerance()) {
1168 <<
"parameter absolute_gap_tolerance not supported by GLPK "
1169 "(relative_gap_tolerance is supported)";
1173 <<
"parameter iteration_limit not supported by GLPK for MIP";
1175 if (
parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
1177 <<
"parameter lp_algorithm not supported by GLPK for MIP";
1179 MipCallbackData mip_cb_data(interrupter);
1180 glpk_parameters.cb_func = MipCallback;
1181 glpk_parameters.cb_info = &mip_cb_data;
1182 const int rc = glp_intopt(problem_, &glpk_parameters);
1183 const int mip_status = glp_mip_status(problem_);
1184 const bool has_feasible_solution =
1185 mip_status == GLP_OPT || mip_status == GLP_FEAS;
1186 const std::optional<double> feasible_solution_objective_value =
1187 has_feasible_solution ? std::make_optional(glp_mip_obj_val(problem_))
1190 *result.mutable_termination(),
1191 BuildTermination(problem_,
"glp_intopt", rc, MipTerminationOnSuccess,
1192 &mip_cb_data, feasible_solution_objective_value,
1193 glpk_parameters.mip_gap));
1194 if (mip_cb_data.best_bound().has_value()) {
1195 best_dual_bound = *mip_cb_data.best_bound();
1197 *result.mutable_solve_stats()->mutable_problem_status() =
1198 GetMipProblemStatusProto(rc, mip_status,
1199 std::isfinite(best_dual_bound));
1201 if (
parameters.lp_algorithm() == LP_ALGORITHM_BARRIER) {
1202 get_prim_stat = glp_ipt_status;
1203 obj_val = glp_ipt_obj_val;
1204 col_val = glp_ipt_col_prim;
1206 get_dual_stat = glp_ipt_status;
1207 row_dual = glp_ipt_row_dual;
1208 col_dual = glp_ipt_col_dual;
1210 glp_iptcp glpk_parameters;
1211 glp_init_iptcp(&glpk_parameters);
1213 return absl::InvalidArgumentError(
1214 "parameter time_limit not supported by GLPK for interior point "
1218 parameters, term_hook_data.has_user_message_callback(),
1227 if (IsEmpty(problem_)) {
1228 get_prim_stat = OptStatus;
1229 get_dual_stat = OptStatus;
1230 obj_val = OffsetOnlyObjVal;
1235 "glp_interior() not called since the model is empty");
1236 result.mutable_solve_stats()
1237 ->mutable_problem_status()
1238 ->set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
1239 result.mutable_solve_stats()->mutable_problem_status()->set_dual_status(
1240 FEASIBILITY_STATUS_FEASIBLE);
1244 const int glp_interior_rc = glp_interior(problem_, &glpk_parameters);
1245 const int ipt_status = glp_ipt_status(problem_);
1246 const bool has_feasible_solution = ipt_status == GLP_OPT;
1247 const std::optional<double> feasible_solution_objective_value =
1248 has_feasible_solution
1249 ? std::make_optional(glp_ipt_obj_val(problem_))
1252 *result.mutable_termination(),
1253 BuildTermination(problem_,
"glp_interior", glp_interior_rc,
1254 InteriorTerminationOnSuccess,
1256 feasible_solution_objective_value,
1259 *result.mutable_solve_stats()->mutable_problem_status(),
1260 GetBarrierProblemStatusProto(glp_interior_rc,
1264 get_prim_stat = glp_get_prim_stat;
1265 obj_val = glp_get_obj_val;
1266 col_val = glp_get_col_prim;
1268 get_dual_stat = glp_get_dual_stat;
1269 row_dual = glp_get_row_dual;
1270 col_dual = glp_get_col_dual;
1272 glp_smcp glpk_parameters;
1273 glp_init_smcp(&glpk_parameters);
1275 parameters, term_hook_data.has_user_message_callback(),
1277 SetTimeLimitParameter(
parameters, glpk_parameters);
1281 const int glp_simplex_rc = glp_simplex(problem_, &glpk_parameters);
1282 const int prim_stat = glp_get_prim_stat(problem_);
1283 const bool has_feasible_solution = prim_stat == GLP_FEAS;
1284 const std::optional<double> feasible_solution_objective_value =
1285 has_feasible_solution ? std::make_optional(glp_get_obj_val(problem_))
1288 BuildTermination(problem_,
"glp_simplex", glp_simplex_rc,
1289 SimplexTerminationOnSuccess,
1291 feasible_solution_objective_value,
1297 if (glp_get_prim_stat(problem_) == GLP_NOFEAS &&
1298 glp_get_dual_stat(problem_) == GLP_FEAS) {
1299 best_dual_bound = maximize ? -
kInf : +
kInf;
1303 GetSimplexProblemStatusProto(
1306 glp_get_dual_stat(problem_)));
1307 VLOG(1) <<
"glp_get_status: "
1310 <<
" glp_get_dual_stat: "
1316 std::move(message_cb_cleanup).Invoke();
1318 switch (result.termination().reason()) {
1319 case TERMINATION_REASON_OPTIMAL:
1320 case TERMINATION_REASON_FEASIBLE:
1321 result.mutable_solve_stats()->set_best_primal_bound(obj_val(problem_));
1323 case TERMINATION_REASON_UNBOUNDED:
1326 result.mutable_solve_stats()->set_best_primal_bound(maximize ? +
kInf
1330 result.mutable_solve_stats()->set_best_primal_bound(maximize ? -
kInf
1337 result.mutable_solve_stats()->set_best_dual_bound(best_dual_bound);
1339 AddPrimalSolution(get_prim_stat, obj_val, col_val, model_parameters,
1342 AddDualSolution(get_dual_stat, obj_val, row_dual, col_dual,
1347 *result.add_solutions() = std::move(
solution);
1349 if (
parameters.glpk().compute_unbound_rays_if_possible()) {
1357void GlpkSolver::AddVariables(
const VariablesProto& new_variables) {
1358 if (new_variables.ids().empty()) {
1363 const int first_new_var_index = variables_.ids.size() + 1;
1365 variables_.ids.insert(variables_.ids.end(), new_variables.ids().begin(),
1366 new_variables.ids().end());
1367 for (
int v = 0; v < new_variables.ids_size(); ++v) {
1368 CHECK(variables_.id_to_index
1369 .try_emplace(new_variables.ids(v), first_new_var_index + v)
1372 glp_add_cols(problem_, new_variables.ids_size());
1373 if (!new_variables.names().empty()) {
1374 for (
int v = 0; v < new_variables.names_size(); ++v) {
1376 problem_, v + first_new_var_index,
1380 CHECK_EQ(new_variables.lower_bounds_size(),
1381 new_variables.upper_bounds_size());
1382 CHECK_EQ(new_variables.lower_bounds_size(), new_variables.ids_size());
1383 variables_.unrounded_lower_bounds.insert(
1384 variables_.unrounded_lower_bounds.end(),
1385 new_variables.lower_bounds().begin(), new_variables.lower_bounds().end());
1386 variables_.unrounded_upper_bounds.insert(
1387 variables_.unrounded_upper_bounds.end(),
1388 new_variables.upper_bounds().begin(), new_variables.upper_bounds().end());
1389 for (
int i = 0;
i < new_variables.lower_bounds_size(); ++
i) {
1396 glp_set_col_kind(problem_, i + first_new_var_index,
1397 new_variables.integers(i) ? GLP_IV : GLP_CV);
1398 SetBounds<Variables>(problem_, i + first_new_var_index,
1399 {.lower = new_variables.lower_bounds(i),
1400 .upper = new_variables.upper_bounds(i)});
1404void GlpkSolver::AddLinearConstraints(
1405 const LinearConstraintsProto& new_linear_constraints) {
1406 if (new_linear_constraints.ids().empty()) {
1411 const int first_new_cstr_index = linear_constraints_.ids.size() + 1;
1413 linear_constraints_.ids.insert(linear_constraints_.ids.end(),
1414 new_linear_constraints.ids().begin(),
1415 new_linear_constraints.ids().end());
1416 for (
int c = 0;
c < new_linear_constraints.ids_size(); ++
c) {
1417 CHECK(linear_constraints_.id_to_index
1418 .try_emplace(new_linear_constraints.ids(
c),
1419 first_new_cstr_index +
c)
1422 glp_add_rows(problem_, new_linear_constraints.ids_size());
1423 if (!new_linear_constraints.names().empty()) {
1424 for (
int c = 0;
c < new_linear_constraints.names_size(); ++
c) {
1426 problem_,
c + first_new_cstr_index,
1430 CHECK_EQ(new_linear_constraints.lower_bounds_size(),
1431 new_linear_constraints.upper_bounds_size());
1432 for (
int i = 0;
i < new_linear_constraints.lower_bounds_size(); ++
i) {
1433 SetBounds<LinearConstraints>(
1434 problem_, i + first_new_cstr_index,
1435 {.lower = new_linear_constraints.lower_bounds(i),
1436 .upper = new_linear_constraints.upper_bounds(i)});
1440void GlpkSolver::UpdateObjectiveCoefficients(
1441 const SparseDoubleVectorProto& coefficients_proto) {
1442 for (
const auto [
id, coeff] :
MakeView(coefficients_proto)) {
1443 const int col_index = variables_.id_to_index.at(
id);
1444 CHECK_EQ(variables_.ids[col_index - 1],
id);
1445 glp_set_obj_coef(problem_, col_index, coeff);
1449void GlpkSolver::UpdateLinearConstraintMatrix(
1450 const SparseDoubleMatrixProto& matrix_updates,
1451 const std::optional<int64_t> first_new_var_id,
1452 const std::optional<int64_t> first_new_cstr_id) {
1486 GlpkSparseVector data(
static_cast<int>(variables_.ids.size()));
1487 for (
const auto& [row_id, row_coefficients] :
1492 first_new_var_id)) {
1495 const int row_index = linear_constraints_.id_to_index.at(row_id);
1496 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1499 data.Load([&](
int*
const indices,
double*
const values) {
1500 return glp_get_mat_row(problem_, row_index, indices, values);
1504 for (
const auto [col_id,
coefficient] : row_coefficients) {
1505 const int col_index = variables_.id_to_index.at(col_id);
1506 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1511 glp_set_mat_row(problem_, row_index, data.size(), data.indices(),
1518 if (first_new_var_id.has_value()) {
1519 GlpkSparseVector data(
static_cast<int>(linear_constraints_.ids.size()));
1528 const int col_index = variables_.id_to_index.at(col_id);
1529 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1535 const int row_index = linear_constraints_.id_to_index.at(row_id);
1536 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1541 glp_set_mat_col(problem_, col_index, data.size(), data.indices(),
1547 if (first_new_cstr_id.has_value()) {
1548 GlpkSparseVector data(
static_cast<int>(variables_.ids.size()));
1549 for (
const auto& [row_id, row_coefficients] :
1557 const int row_index = linear_constraints_.id_to_index.at(row_id);
1558 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1563 for (
const auto [col_id,
coefficient] : row_coefficients) {
1564 const int col_index = variables_.id_to_index.at(col_id);
1565 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1570 glp_set_mat_row(problem_, row_index, data.size(), data.indices(),
1576void GlpkSolver::AddPrimalSolution(
1577 int (*get_prim_stat)(glp_prob*),
double (*obj_val)(glp_prob*),
1578 double (*col_val)(glp_prob*,
int),
1579 const ModelSolveParametersProto& model_parameters,
1580 SolutionProto& solution_proto) {
1581 const int status = get_prim_stat(problem_);
1584 *solution_proto.mutable_primal_solution();
1588 FilteredVector(problem_, model_parameters.variable_values_filter(),
1589 variables_.ids, col_val);
1593void GlpkSolver::AddDualSolution(
1594 int (*get_dual_stat)(glp_prob*),
double (*obj_val)(glp_prob*),
1595 double (*row_dual)(glp_prob*,
int),
double (*col_dual)(glp_prob*,
int),
1596 const ModelSolveParametersProto& model_parameters,
1597 SolutionProto& solution_proto) {
1598 const int status = get_dual_stat(problem_);
1600 DualSolutionProto& dual_solution = *solution_proto.mutable_dual_solution();
1601 dual_solution.set_objective_value(obj_val(problem_));
1602 *dual_solution.mutable_dual_values() =
1603 FilteredVector(problem_, model_parameters.dual_values_filter(),
1604 linear_constraints_.ids, row_dual);
1605 *dual_solution.mutable_reduced_costs() =
1606 FilteredVector(problem_, model_parameters.reduced_costs_filter(),
1607 variables_.ids, col_dual);
1611 dual_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1615absl::Status GlpkSolver::AddPrimalOrDualRay(
1616 const ModelSolveParametersProto& model_parameters,
1617 SolveResultProto& result) {
1620 if (!opt_unbound_ray.has_value()) {
1621 return absl::OkStatus();
1624 const int num_cstrs = linear_constraints_.ids.size();
1625 switch (opt_unbound_ray->type) {
1627 const int num_cstrs =
static_cast<int>(linear_constraints_.ids.size());
1632 std::vector<double> ray_values(variables_.ids.size());
1634 for (
const auto [k,
value] : opt_unbound_ray->non_zero_components) {
1635 if (k <= num_cstrs) {
1644 *result.add_primal_rays()->mutable_variable_values() =
1645 FilteredRay(model_parameters.variable_values_filter(), variables_.ids,
1648 return absl::OkStatus();
1657 std::vector<double> ray_reduced_costs(variables_.ids.size());
1658 std::vector<double> ray_dual_values(num_cstrs);
1660 for (
const auto [k,
value] : opt_unbound_ray->non_zero_components) {
1661 if (k <= num_cstrs) {
1662 ray_dual_values[k - 1] =
value;
1670 DualRayProto& dual_ray = *result.add_dual_rays();
1671 *dual_ray.mutable_dual_values() =
1672 FilteredRay(model_parameters.dual_values_filter(),
1673 linear_constraints_.ids, ray_dual_values);
1674 *dual_ray.mutable_reduced_costs() =
1675 FilteredRay(model_parameters.reduced_costs_filter(), variables_.ids,
1678 return absl::OkStatus();
1695 const std::vector<int> sorted_deleted_cols = DeleteRowsOrCols(
1696 problem_, variables_, model_update.deleted_variable_ids());
1697 DeleteRowOrColData(variables_.unrounded_lower_bounds, sorted_deleted_cols);
1698 DeleteRowOrColData(variables_.unrounded_upper_bounds, sorted_deleted_cols);
1699 CHECK_EQ(variables_.unrounded_lower_bounds.size(),
1700 variables_.unrounded_upper_bounds.size());
1701 CHECK_EQ(variables_.unrounded_lower_bounds.size(), variables_.ids.size());
1703 DeleteRowsOrCols(problem_, linear_constraints_,
1704 model_update.deleted_linear_constraint_ids());
1706 for (
const auto [var_id, is_integer] :
1707 MakeView(model_update.variable_updates().integers())) {
1709 const int var_index = variables_.id_to_index.at(var_id);
1710 glp_set_col_kind(problem_,
var_index, is_integer ? GLP_IV : GLP_CV);
1717 SetBounds<Variables>(
1719 {.lower = variables_.unrounded_lower_bounds[
var_index - 1],
1720 .upper = variables_.unrounded_upper_bounds[
var_index - 1]});
1723 MakeView(model_update.variable_updates().lower_bounds())) {
1724 variables_.unrounded_lower_bounds[variables_.id_to_index.at(var_id) - 1] =
1728 MakeView(model_update.variable_updates().upper_bounds())) {
1729 variables_.unrounded_upper_bounds[variables_.id_to_index.at(var_id) - 1] =
1733 problem_, variables_,
1734 model_update.variable_updates().lower_bounds(),
1735 model_update.variable_updates().upper_bounds());
1736 UpdateBounds(problem_, linear_constraints_,
1738 model_update.linear_constraint_updates().lower_bounds(),
1740 model_update.linear_constraint_updates().upper_bounds());
1742 AddVariables(model_update.new_variables());
1743 AddLinearConstraints(model_update.new_linear_constraints());
1745 if (model_update.objective_updates().has_direction_update()) {
1746 glp_set_obj_dir(problem_,
1747 model_update.objective_updates().direction_update()
1751 if (model_update.objective_updates().has_offset_update()) {
1753 glp_set_obj_coef(problem_, 0,
1754 model_update.objective_updates().offset_update());
1756 UpdateObjectiveCoefficients(
1757 model_update.objective_updates().linear_coefficients());
1759 UpdateLinearConstraintMatrix(
1760 model_update.linear_constraint_matrix_updates(),
1768absl::Status GlpkSolver::CheckCurrentThread() {
1769 if (std::this_thread::get_id() != thread_id_) {
1770 return absl::InvalidArgumentError(
1771 "GLPK is not thread-safe and thus the solver should only be used on "
1772 "the same thread as it was created");
1774 return absl::OkStatus();
1777std::optional<SolveResultProto> GlpkSolver::EmptyIntegerBoundsResult() {
1778 const int num_cols = glp_get_num_cols(problem_);
1779 for (
int c = 1;
c <= num_cols; ++
c) {
1780 if (!variables_.IsInteger(problem_,
c)) {
1783 const double lb = variables_.unrounded_lower_bounds[
c - 1];
1784 const double ub = variables_.unrounded_upper_bounds[
c - 1];
1791 if (std::ceil(lb) <= std::floor(ub)) {
1798 glp_get_obj_dir(problem_) == GLP_MAX,
1799 variables_.ids[
c - 1],
1803 return std::nullopt;
1806absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
1810 return absl::UnimplementedError(
1811 "GLPK does not provide a method to compute an infeasible subsystem");
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
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, const SolveInterrupter *interrupter) override
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto ¶meters, MessageCallback message_cb, const SolveInterrupter *interrupter) override
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >( const CallbackDataProto &)> Callback
absl::Span< const int64_t > variable_ids
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
std::optional< int64_t > FirstLinearConstraintId(const LinearConstraintsProto &linear_constraints)
std::function< CallbackResult(const CallbackData &)> Callback
TerminationProto OptimalTerminationProto(const double finite_primal_objective, const double dual_objective, const absl::string_view detail)
TerminationProto LimitTerminationProto(const bool is_maximize, const LimitProto limit, const std::optional< double > optional_finite_primal_objective, const std::optional< double > optional_dual_objective, const absl::string_view detail)
TerminationProto InfeasibleOrUnboundedTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
bool UpdateIsSupported(const ModelUpdateProto &update, const SupportedProblemStructures &support_menu)
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)
In SWIG mode, we don't want anything besides these top-level includes.
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)
Formats a solution status (GLP_OPT,...).
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()
std::vector< double > lower_bounds
std::vector< double > upper_bounds
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)
Initialization arguments.
double objective_value
The value objective_vector^T * (solution - center_point).