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();
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) {
156 const auto lower_bounds =
MakeView(lower_bounds_proto);
157 const auto upper_bounds =
MakeView(upper_bounds_proto);
159 auto current_lower_bound = lower_bounds.begin();
160 auto current_upper_bound = upper_bounds.begin();
163 std::optional<int64_t> next_id;
164 if (current_lower_bound != lower_bounds.end()) {
165 if (!next_id.has_value() || current_lower_bound->first < *next_id) {
166 next_id = current_lower_bound->first;
169 if (current_upper_bound != upper_bounds.end()) {
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,
188 if (current_lower_bound != lower_bounds.end() &&
189 current_lower_bound->first == *next_id) {
190 bounds.lower = current_lower_bound->second;
191 ++current_lower_bound;
193 if (current_upper_bound != upper_bounds.end() &&
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,
202 CHECK(current_lower_bound == lower_bounds.end());
203 CHECK(current_upper_bound == upper_bounds.end());
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());
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;
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;
383 if (parameters.has_threads() && parameters.threads() > 1) {
385 absl::StrCat(
"GLPK only supports parameters.threads = 1; value ",
386 parameters.threads(),
" is not supported"));
388 if (parameters.enable_output() || has_message_callback) {
389 glpk_parameters.msg_lev = GLP_MSG_ALL;
391 glpk_parameters.msg_lev = GLP_MSG_OFF;
393 if (parameters.has_node_limit()) {
394 warnings.push_back(
"parameter node_limit not supported by GLPK");
396 if (parameters.has_objective_limit()) {
397 warnings.push_back(
"parameter objective_limit not supported by GLPK");
399 if (parameters.has_best_bound_limit()) {
400 warnings.push_back(
"parameter best_bound_limit not supported by GLPK");
402 if (parameters.has_cutoff_limit()) {
403 warnings.push_back(
"parameter cutoff_limit not supported by GLPK");
405 if (parameters.has_solution_limit()) {
406 warnings.push_back(
"parameter solution_limit not supported by GLPK");
408 if (parameters.has_random_seed()) {
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) {
433 if (parameters.has_time_limit()) {
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;
446 if (parameters.has_iteration_limit()) {
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;
451 switch (parameters.presolve()) {
452 case EMPHASIS_UNSPECIFIED:
458 glpk_parameters.presolve = GLP_OFF;
461 glpk_parameters.presolve = GLP_ON;
464 switch (parameters.lp_algorithm()) {
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);
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) {
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]) {
572 inverted_bounds.variables.push_back(variable_ids[
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;
601 const double objective_value = glp_mip_obj_val(problem);
602 if (status == GLP_OPT) {
610 is_maximize, LIMIT_UNDETERMINED, objective_value,
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;
636 const double objective_value = glp_ipt_obj_val(problem);
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) {
701 const double objective_value = glp_get_obj_val(problem);
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) {
878double OffsetOnlyObjVal(glp_prob*
const problem) {
879 return glp_get_obj_coef(problem, 0);
885int OptStatus(glp_prob*) {
return GLP_OPT; }
890 const ModelProto& model,
const InitArgs& ) {
892 return absl::WrapUnique(
new GlpkSolver(model));
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] :
910 MakeView(model.objective().linear_coefficients())) {
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()) {
929 LOG(ERROR) << status;
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;
1058 const SolveParametersProto& parameters,
1059 const ModelSolveParametersProto& model_parameters,
1061 const CallbackRegistrationProto& callback_registration,
1064 model_parameters, kGlpkSupportedStructures,
"GLPK"));
1067 const absl::Time start = absl::Now();
1069 const auto set_solve_time =
1070 [&start](SolveResultProto& result) -> absl::Status {
1072 absl::Now() - start,
1073 result.mutable_solve_stats()->mutable_solve_time()))
1074 <<
"failed to set SolveResultProto.solve_stats.solve_time";
1075 return absl::OkStatus();
1082 variables_.unrounded_lower_bounds,
1083 variables_.unrounded_upper_bounds,
1084 linear_constraints_.ids)
1090 std::optional<SolveResultProto> result = EmptyIntegerBoundsResult();
1091 if (result.has_value()) {
1093 return std::move(result).value();
1101 if (term_hook_data.has_user_message_callback()) {
1106 glp_term_hook(TermHook, &term_hook_data);
1111 auto message_cb_cleanup = absl::MakeCleanup([&]() {
1112 if (term_hook_data.has_user_message_callback()) {
1113 glp_term_hook(
nullptr,
nullptr);
1114 term_hook_data.Flush();
1118 SolveResultProto result;
1120 const bool is_mip = IsMip(problem_);
1124 int (*get_prim_stat)(glp_prob*) =
nullptr;
1125 double (*obj_val)(glp_prob*) =
nullptr;
1126 double (*col_val)(glp_prob*, int) =
nullptr;
1128 int (*get_dual_stat)(glp_prob*) =
nullptr;
1129 double (*row_dual)(glp_prob*, int) =
nullptr;
1130 double (*col_dual)(glp_prob*, int) =
nullptr;
1132 const bool maximize = glp_get_obj_dir(problem_) == GLP_MAX;
1133 double best_dual_bound = maximize ?
kInf : -
kInf;
1146 get_prim_stat = glp_mip_status;
1147 obj_val = glp_mip_obj_val;
1148 col_val = glp_mip_col_val;
1150 glp_iocp glpk_parameters;
1151 glp_init_iocp(&glpk_parameters);
1153 parameters, term_hook_data.has_user_message_callback(),
1155 SetTimeLimitParameter(parameters, glpk_parameters);
1160 glpk_parameters.presolve = GLP_ON;
1161 if (parameters.presolve() != EMPHASIS_UNSPECIFIED) {
1163 <<
"parameter presolve not supported by GLPK for MIP";
1165 if (parameters.has_relative_gap_tolerance()) {
1166 glpk_parameters.mip_gap = parameters.relative_gap_tolerance();
1168 if (parameters.has_absolute_gap_tolerance()) {
1170 <<
"parameter absolute_gap_tolerance not supported by GLPK "
1171 "(relative_gap_tolerance is supported)";
1173 if (parameters.has_iteration_limit()) {
1175 <<
"parameter iteration_limit not supported by GLPK for MIP";
1177 if (parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
1179 <<
"parameter lp_algorithm not supported by GLPK for MIP";
1181 MipCallbackData mip_cb_data(interrupter);
1182 glpk_parameters.cb_func = MipCallback;
1183 glpk_parameters.cb_info = &mip_cb_data;
1184 const int rc = glp_intopt(problem_, &glpk_parameters);
1185 const int mip_status = glp_mip_status(problem_);
1186 const bool has_feasible_solution =
1187 mip_status == GLP_OPT || mip_status == GLP_FEAS;
1188 const std::optional<double> feasible_solution_objective_value =
1189 has_feasible_solution ? std::make_optional(glp_mip_obj_val(problem_))
1192 *result.mutable_termination(),
1193 BuildTermination(problem_,
"glp_intopt", rc, MipTerminationOnSuccess,
1194 &mip_cb_data, feasible_solution_objective_value,
1195 glpk_parameters.mip_gap));
1196 if (mip_cb_data.best_bound().has_value()) {
1197 best_dual_bound = *mip_cb_data.best_bound();
1199 *result.mutable_solve_stats()->mutable_problem_status() =
1200 GetMipProblemStatusProto(rc, mip_status,
1201 std::isfinite(best_dual_bound));
1203 if (parameters.lp_algorithm() == LP_ALGORITHM_BARRIER) {
1204 get_prim_stat = glp_ipt_status;
1205 obj_val = glp_ipt_obj_val;
1206 col_val = glp_ipt_col_prim;
1208 get_dual_stat = glp_ipt_status;
1209 row_dual = glp_ipt_row_dual;
1210 col_dual = glp_ipt_col_dual;
1212 glp_iptcp glpk_parameters;
1213 glp_init_iptcp(&glpk_parameters);
1214 if (parameters.has_time_limit()) {
1215 return absl::InvalidArgumentError(
1216 "parameter time_limit not supported by GLPK for interior point "
1220 parameters, term_hook_data.has_user_message_callback(),
1229 if (IsEmpty(problem_)) {
1230 get_prim_stat = OptStatus;
1231 get_dual_stat = OptStatus;
1232 obj_val = OffsetOnlyObjVal;
1233 const double objective_value = OffsetOnlyObjVal(problem_);
1237 "glp_interior() not called since the model is empty");
1238 result.mutable_solve_stats()
1239 ->mutable_problem_status()
1240 ->set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
1241 result.mutable_solve_stats()->mutable_problem_status()->set_dual_status(
1242 FEASIBILITY_STATUS_FEASIBLE);
1246 const int glp_interior_rc = glp_interior(problem_, &glpk_parameters);
1247 const int ipt_status = glp_ipt_status(problem_);
1248 const bool has_feasible_solution = ipt_status == GLP_OPT;
1249 const std::optional<double> feasible_solution_objective_value =
1250 has_feasible_solution
1251 ? std::make_optional(glp_ipt_obj_val(problem_))
1254 *result.mutable_termination(),
1255 BuildTermination(problem_,
"glp_interior", glp_interior_rc,
1256 InteriorTerminationOnSuccess,
1258 feasible_solution_objective_value,
1261 *result.mutable_solve_stats()->mutable_problem_status(),
1262 GetBarrierProblemStatusProto(glp_interior_rc,
1266 get_prim_stat = glp_get_prim_stat;
1267 obj_val = glp_get_obj_val;
1268 col_val = glp_get_col_prim;
1270 get_dual_stat = glp_get_dual_stat;
1271 row_dual = glp_get_row_dual;
1272 col_dual = glp_get_col_dual;
1274 glp_smcp glpk_parameters;
1275 glp_init_smcp(&glpk_parameters);
1277 parameters, term_hook_data.has_user_message_callback(),
1279 SetTimeLimitParameter(parameters, glpk_parameters);
1283 const int glp_simplex_rc = glp_simplex(problem_, &glpk_parameters);
1284 const int prim_stat = glp_get_prim_stat(problem_);
1285 const bool has_feasible_solution = prim_stat == GLP_FEAS;
1286 const std::optional<double> feasible_solution_objective_value =
1287 has_feasible_solution ? std::make_optional(glp_get_obj_val(problem_))
1290 BuildTermination(problem_,
"glp_simplex", glp_simplex_rc,
1291 SimplexTerminationOnSuccess,
1293 feasible_solution_objective_value,
1299 if (glp_get_prim_stat(problem_) == GLP_NOFEAS &&
1300 glp_get_dual_stat(problem_) == GLP_FEAS) {
1301 best_dual_bound = maximize ? -
kInf : +
kInf;
1305 GetSimplexProblemStatusProto(
1308 glp_get_dual_stat(problem_)));
1309 VLOG(1) <<
"glp_get_status: "
1312 <<
" glp_get_dual_stat: "
1318 std::move(message_cb_cleanup).Invoke();
1320 switch (result.termination().reason()) {
1321 case TERMINATION_REASON_OPTIMAL:
1322 case TERMINATION_REASON_FEASIBLE:
1323 result.mutable_solve_stats()->set_best_primal_bound(obj_val(problem_));
1325 case TERMINATION_REASON_UNBOUNDED:
1328 result.mutable_solve_stats()->set_best_primal_bound(maximize ? +
kInf
1332 result.mutable_solve_stats()->set_best_primal_bound(maximize ? -
kInf
1339 result.mutable_solve_stats()->set_best_dual_bound(best_dual_bound);
1341 AddPrimalSolution(get_prim_stat, obj_val, col_val, model_parameters,
1344 AddDualSolution(get_dual_stat, obj_val, row_dual, col_dual,
1349 *result.add_solutions() = std::move(
solution);
1351 if (parameters.glpk().compute_unbound_rays_if_possible()) {
1359void GlpkSolver::AddVariables(
const VariablesProto& new_variables) {
1360 if (new_variables.ids().empty()) {
1365 const int first_new_var_index = variables_.ids.size() + 1;
1367 variables_.ids.insert(variables_.ids.end(), new_variables.ids().begin(),
1368 new_variables.ids().end());
1369 for (
int v = 0; v < new_variables.ids_size(); ++v) {
1370 CHECK(variables_.id_to_index
1371 .try_emplace(new_variables.ids(v), first_new_var_index + v)
1374 glp_add_cols(problem_, new_variables.ids_size());
1375 if (!new_variables.names().empty()) {
1376 for (
int v = 0; v < new_variables.names_size(); ++v) {
1378 problem_, v + first_new_var_index,
1382 CHECK_EQ(new_variables.lower_bounds_size(),
1383 new_variables.upper_bounds_size());
1384 CHECK_EQ(new_variables.lower_bounds_size(), new_variables.ids_size());
1385 variables_.unrounded_lower_bounds.insert(
1386 variables_.unrounded_lower_bounds.end(),
1387 new_variables.lower_bounds().begin(), new_variables.lower_bounds().end());
1388 variables_.unrounded_upper_bounds.insert(
1389 variables_.unrounded_upper_bounds.end(),
1390 new_variables.upper_bounds().begin(), new_variables.upper_bounds().end());
1391 for (
int i = 0;
i < new_variables.lower_bounds_size(); ++
i) {
1398 glp_set_col_kind(problem_, i + first_new_var_index,
1399 new_variables.integers(i) ? GLP_IV : GLP_CV);
1400 SetBounds<Variables>(problem_, i + first_new_var_index,
1401 {.lower = new_variables.lower_bounds(i),
1402 .upper = new_variables.upper_bounds(i)});
1406void GlpkSolver::AddLinearConstraints(
1407 const LinearConstraintsProto& new_linear_constraints) {
1408 if (new_linear_constraints.ids().empty()) {
1413 const int first_new_cstr_index = linear_constraints_.ids.size() + 1;
1415 linear_constraints_.ids.insert(linear_constraints_.ids.end(),
1416 new_linear_constraints.ids().begin(),
1417 new_linear_constraints.ids().end());
1418 for (
int c = 0;
c < new_linear_constraints.ids_size(); ++
c) {
1419 CHECK(linear_constraints_.id_to_index
1420 .try_emplace(new_linear_constraints.ids(
c),
1421 first_new_cstr_index +
c)
1424 glp_add_rows(problem_, new_linear_constraints.ids_size());
1425 if (!new_linear_constraints.names().empty()) {
1426 for (
int c = 0;
c < new_linear_constraints.names_size(); ++
c) {
1428 problem_,
c + first_new_cstr_index,
1432 CHECK_EQ(new_linear_constraints.lower_bounds_size(),
1433 new_linear_constraints.upper_bounds_size());
1434 for (
int i = 0;
i < new_linear_constraints.lower_bounds_size(); ++
i) {
1435 SetBounds<LinearConstraints>(
1436 problem_, i + first_new_cstr_index,
1437 {.lower = new_linear_constraints.lower_bounds(i),
1438 .upper = new_linear_constraints.upper_bounds(i)});
1442void GlpkSolver::UpdateObjectiveCoefficients(
1443 const SparseDoubleVectorProto& coefficients_proto) {
1444 for (
const auto [
id, coeff] :
MakeView(coefficients_proto)) {
1445 const int col_index = variables_.id_to_index.at(
id);
1446 CHECK_EQ(variables_.ids[col_index - 1],
id);
1447 glp_set_obj_coef(problem_, col_index, coeff);
1451void GlpkSolver::UpdateLinearConstraintMatrix(
1452 const SparseDoubleMatrixProto& matrix_updates,
1453 const std::optional<int64_t> first_new_var_id,
1454 const std::optional<int64_t> first_new_cstr_id) {
1488 GlpkSparseVector data(
static_cast<int>(variables_.ids.size()));
1489 for (
const auto& [row_id, row_coefficients] :
1494 first_new_var_id)) {
1497 const int row_index = linear_constraints_.id_to_index.at(row_id);
1498 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1501 data.Load([&](
int*
const indices,
double*
const values) {
1502 return glp_get_mat_row(problem_, row_index, indices, values);
1506 for (
const auto [col_id, coefficient] : row_coefficients) {
1507 const int col_index = variables_.id_to_index.at(col_id);
1508 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1509 data.Set(col_index, coefficient);
1513 glp_set_mat_row(problem_, row_index, data.size(), data.indices(),
1520 if (first_new_var_id.has_value()) {
1521 GlpkSparseVector data(
static_cast<int>(linear_constraints_.ids.size()));
1530 const int col_index = variables_.id_to_index.at(col_id);
1531 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1536 for (
const auto [row_id, coefficient] :
MakeView(col_coefficients)) {
1537 const int row_index = linear_constraints_.id_to_index.at(row_id);
1538 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1539 data.Set(row_index, coefficient);
1543 glp_set_mat_col(problem_, col_index, data.size(), data.indices(),
1549 if (first_new_cstr_id.has_value()) {
1550 GlpkSparseVector data(
static_cast<int>(variables_.ids.size()));
1551 for (
const auto& [row_id, row_coefficients] :
1559 const int row_index = linear_constraints_.id_to_index.at(row_id);
1560 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1565 for (
const auto [col_id, coefficient] : row_coefficients) {
1566 const int col_index = variables_.id_to_index.at(col_id);
1567 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1568 data.Set(col_index, coefficient);
1572 glp_set_mat_row(problem_, row_index, data.size(), data.indices(),
1578void GlpkSolver::AddPrimalSolution(
1579 int (*get_prim_stat)(glp_prob*),
double (*obj_val)(glp_prob*),
1580 double (*col_val)(glp_prob*,
int),
1581 const ModelSolveParametersProto& model_parameters,
1582 SolutionProto& solution_proto) {
1583 const int status = get_prim_stat(problem_);
1584 if (status == GLP_OPT || status == GLP_FEAS) {
1586 *solution_proto.mutable_primal_solution();
1590 FilteredVector(problem_, model_parameters.variable_values_filter(),
1591 variables_.ids, col_val);
1595void GlpkSolver::AddDualSolution(
1596 int (*get_dual_stat)(glp_prob*),
double (*obj_val)(glp_prob*),
1597 double (*row_dual)(glp_prob*,
int),
double (*col_dual)(glp_prob*,
int),
1598 const ModelSolveParametersProto& model_parameters,
1599 SolutionProto& solution_proto) {
1600 const int status = get_dual_stat(problem_);
1601 if (status == GLP_OPT || status == GLP_FEAS) {
1602 DualSolutionProto& dual_solution = *solution_proto.mutable_dual_solution();
1603 dual_solution.set_objective_value(obj_val(problem_));
1604 *dual_solution.mutable_dual_values() =
1605 FilteredVector(problem_, model_parameters.dual_values_filter(),
1606 linear_constraints_.ids, row_dual);
1607 *dual_solution.mutable_reduced_costs() =
1608 FilteredVector(problem_, model_parameters.reduced_costs_filter(),
1609 variables_.ids, col_dual);
1613 dual_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1617absl::Status GlpkSolver::AddPrimalOrDualRay(
1618 const ModelSolveParametersProto& model_parameters,
1619 SolveResultProto& result) {
1622 if (!opt_unbound_ray.has_value()) {
1623 return absl::OkStatus();
1626 const int num_cstrs = linear_constraints_.ids.size();
1627 switch (opt_unbound_ray->type) {
1629 const int num_cstrs =
static_cast<int>(linear_constraints_.ids.size());
1634 std::vector<double> ray_values(variables_.ids.size());
1636 for (
const auto [k, value] : opt_unbound_ray->non_zero_components) {
1637 if (k <= num_cstrs) {
1641 const int var_index = k - num_cstrs;
1642 CHECK_GE(var_index, 1);
1643 ray_values[var_index - 1] = value;
1646 *result.add_primal_rays()->mutable_variable_values() =
1647 FilteredRay(model_parameters.variable_values_filter(), variables_.ids,
1650 return absl::OkStatus();
1659 std::vector<double> ray_reduced_costs(variables_.ids.size());
1660 std::vector<double> ray_dual_values(num_cstrs);
1662 for (
const auto [k, value] : opt_unbound_ray->non_zero_components) {
1663 if (k <= num_cstrs) {
1664 ray_dual_values[k - 1] = value;
1666 const int var_index = k - num_cstrs;
1667 CHECK_GE(var_index, 1);
1668 ray_reduced_costs[var_index - 1] = value;
1672 DualRayProto& dual_ray = *result.add_dual_rays();
1673 *dual_ray.mutable_dual_values() =
1674 FilteredRay(model_parameters.dual_values_filter(),
1675 linear_constraints_.ids, ray_dual_values);
1676 *dual_ray.mutable_reduced_costs() =
1677 FilteredRay(model_parameters.reduced_costs_filter(), variables_.ids,
1680 return absl::OkStatus();
1697 const std::vector<int> sorted_deleted_cols = DeleteRowsOrCols(
1698 problem_, variables_, model_update.deleted_variable_ids());
1699 DeleteRowOrColData(variables_.unrounded_lower_bounds, sorted_deleted_cols);
1700 DeleteRowOrColData(variables_.unrounded_upper_bounds, sorted_deleted_cols);
1701 CHECK_EQ(variables_.unrounded_lower_bounds.size(),
1702 variables_.unrounded_upper_bounds.size());
1703 CHECK_EQ(variables_.unrounded_lower_bounds.size(), variables_.ids.size());
1705 DeleteRowsOrCols(problem_, linear_constraints_,
1706 model_update.deleted_linear_constraint_ids());
1708 for (
const auto [var_id, is_integer] :
1709 MakeView(model_update.variable_updates().integers())) {
1711 const int var_index = variables_.id_to_index.at(var_id);
1712 glp_set_col_kind(problem_, var_index, is_integer ? GLP_IV : GLP_CV);
1719 SetBounds<Variables>(
1720 problem_, var_index,
1721 {.lower = variables_.unrounded_lower_bounds[var_index - 1],
1722 .upper = variables_.unrounded_upper_bounds[var_index - 1]});
1724 for (
const auto [var_id, lower_bound] :
1725 MakeView(model_update.variable_updates().lower_bounds())) {
1726 variables_.unrounded_lower_bounds[variables_.id_to_index.at(var_id) - 1] =
1729 for (
const auto [var_id, upper_bound] :
1730 MakeView(model_update.variable_updates().upper_bounds())) {
1731 variables_.unrounded_upper_bounds[variables_.id_to_index.at(var_id) - 1] =
1735 problem_, variables_,
1736 model_update.variable_updates().lower_bounds(),
1737 model_update.variable_updates().upper_bounds());
1738 UpdateBounds(problem_, linear_constraints_,
1740 model_update.linear_constraint_updates().lower_bounds(),
1742 model_update.linear_constraint_updates().upper_bounds());
1744 AddVariables(model_update.new_variables());
1745 AddLinearConstraints(model_update.new_linear_constraints());
1747 if (model_update.objective_updates().has_direction_update()) {
1748 glp_set_obj_dir(problem_,
1749 model_update.objective_updates().direction_update()
1753 if (model_update.objective_updates().has_offset_update()) {
1755 glp_set_obj_coef(problem_, 0,
1756 model_update.objective_updates().offset_update());
1758 UpdateObjectiveCoefficients(
1759 model_update.objective_updates().linear_coefficients());
1761 UpdateLinearConstraintMatrix(
1762 model_update.linear_constraint_matrix_updates(),
1770absl::Status GlpkSolver::CheckCurrentThread() {
1771 if (std::this_thread::get_id() != thread_id_) {
1772 return absl::InvalidArgumentError(
1773 "GLPK is not thread-safe and thus the solver should only be used on "
1774 "the same thread as it was created");
1776 return absl::OkStatus();
1779std::optional<SolveResultProto> GlpkSolver::EmptyIntegerBoundsResult() {
1780 const int num_cols = glp_get_num_cols(problem_);
1781 for (
int c = 1;
c <= num_cols; ++
c) {
1782 if (!variables_.IsInteger(problem_,
c)) {
1785 const double lb = variables_.unrounded_lower_bounds[
c - 1];
1786 const double ub = variables_.unrounded_upper_bounds[
c - 1];
1793 if (std::ceil(lb) <= std::floor(ub)) {
1800 glp_get_obj_dir(problem_) == GLP_MAX,
1801 variables_.ids[
c - 1],
1805 return std::nullopt;
1808absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
1812 return absl::UnimplementedError(
1813 "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
An object oriented wrapper for quadratic constraints in ModelStorage.
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)
absl::Status ModelSolveParametersAreSupported(const ModelSolveParametersProto &model_parameters, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
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.
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)
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()
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)
Initialization arguments.