Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
glpk_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <atomic>
18#include <cmath>
19#include <cstddef>
20#include <cstdint>
21#include <functional>
22#include <limits>
23#include <memory>
24#include <optional>
25#include <string>
26#include <thread> // IWYU pragma: keep
27#include <utility>
28#include <vector>
29
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"
70
71namespace operations_research {
72namespace math_opt {
73
74namespace {
75
76constexpr double kInf = std::numeric_limits<double>::infinity();
77constexpr double kNaN = std::numeric_limits<double>::quiet_NaN();
78
79constexpr SupportedProblemStructures kGlpkSupportedStructures = {
80 .integer_variables = SupportType::kSupported};
81
82// Bounds of rows or columns.
83struct Bounds {
84 double lower = -kInf;
85 double upper = kInf;
86};
87
88// Sets either a row or a column bounds. The index k is the one-based index of
89// the row or the column.
90//
91// The Dimension type should be either GlpkSolver::Variable or
92// GlpkSolver::LinearConstraints.
93//
94// When Dimension::IsInteger() returns true, the bounds are rounded before being
95// applied which is mandatory for integer variables (solvers fail if a model
96// contains non-integer bounds for integer variables). Thus the integrality of
97// variables must be set/updated before calling this function.
98template <typename Dimension>
99void SetBounds(glp_prob* const problem, const int k, const Bounds& bounds) {
100 // GLPK wants integer bounds for integer variables.
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;
104 int type = GLP_FR;
105 if (std::isinf(lb) && std::isinf(ub)) {
106 type = GLP_FR;
107 } else if (std::isinf(lb)) {
108 type = GLP_UP;
109 } else if (std::isinf(ub)) {
110 type = GLP_LO;
111 } else if (lb == ub) {
112 type = GLP_FX;
113 } else { // Bounds not inf and not equal.
114 type = GLP_DB;
115 }
116 Dimension::kSetBounds(problem, k, type, lb, ub);
117}
118
119// Gets either a row or a column bounds. The index k is the one-based index of
120// the row or the column.
121//
122// The Dimension type should be either GlpkSolver::Variable or
123// GlpkSolver::LinearConstraints.
124template <typename Dimension>
125Bounds GetBounds(glp_prob* const problem, const int k) {
126 const int type = Dimension::kGetType(problem, k);
127 switch (type) {
128 case GLP_FR:
129 return {};
130 case GLP_LO:
131 return {.lower = Dimension::kGetLb(problem, k)};
132 case GLP_UP:
133 return {.upper = Dimension::kGetUb(problem, k)};
134 case GLP_DB:
135 case GLP_FX:
136 return {.lower = Dimension::kGetLb(problem, k),
137 .upper = Dimension::kGetUb(problem, k)};
138 default:
139 LOG(FATAL) << type;
140 }
141}
142
143// Updates the bounds of either rows or columns.
144//
145// The Dimension type should be either GlpkSolver::Variable or
146// GlpkSolver::LinearConstraints.
147//
148// When Dimension::IsInteger() returns true, the bounds are rounded before being
149// applied which is mandatory for integer variables (solvers fail if a model
150// contains non-integer bounds for integer variables). Thus the integrality of
151// variables must be updated before calling this function.
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);
158
159 auto current_lower_bound = lower_bounds.begin();
160 auto current_upper_bound = upper_bounds.begin();
161 for (;;) {
162 // Get the smallest unvisited id from either sparse container.
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;
167 }
168 }
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;
172 }
173 }
174
175 if (!next_id.has_value()) {
176 // We exhausted all collections.
177 break;
178 }
179
180 // Find the corresponding row or column.
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);
183
184 // Get the updated values for bounds and move the iterator for consumed
185 // updates.
186 Bounds bounds = GetBounds<Dimension>(problem,
187 /*k=*/row_or_col_index);
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;
192 }
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;
197 }
198 SetBounds<Dimension>(problem, /*k=*/row_or_col_index,
199 /*bounds=*/bounds);
200 }
201
202 CHECK(current_lower_bound == lower_bounds.end());
203 CHECK(current_upper_bound == upper_bounds.end());
204}
205
206// Deletes in-place the data corresponding to the indices of rows/cols.
207//
208// The vector of one-based indices sorted_deleted_rows_or_cols is expected to be
209// sorted and its first element of index 0 is ignored (this is the GLPK
210// convention).
211template <typename V>
212void DeleteRowOrColData(std::vector<V>& data,
213 absl::Span<const int> sorted_deleted_rows_or_cols) {
214 if (sorted_deleted_rows_or_cols.empty()) {
215 // Avoid looping when not necessary.
216 return;
217 }
218
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];
227 }
228 // Skip the deleted row/col.
229 ++current_row_or_col;
230 }
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];
234 }
235 data.resize(next_insertion_point);
236}
237
238// Deletes the row or cols of the GLPK problem and returns their indices. As a
239// side effect it updates dimension.ids and dimension.id_to_index.
240//
241// The Dimension type should be either GlpkSolver::Variable or
242// GlpkSolver::LinearConstraints.
243//
244// The returned vector is sorted and the first element (index 0) must be ignored
245// (this is the GLPK convention). It can be used with DeleteRowOrColData().
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()) {
251 // This is not only an optimization. Functions glp_del_rows() and
252 // glp_del_cols() fails if the number of deletion is 0.
253 return {};
254 }
255
256 // Delete GLPK rows or columns.
257 std::vector<int> deleted_rows_or_cols;
258 // Functions glp_del_rows() and glp_del_cols() only use values in ranges
259 // [1,n]. The first element is not used.
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));
264 }
265 Dimension::kDelElts(problem, deleted_rows_or_cols.size() - 1,
266 deleted_rows_or_cols.data());
267
268 // Since deleted_ids are in strictly increasing order and we allocate
269 // rows/cols in orders of MathOpt ids; deleted_rows_or_cols should also be
270 // sorted.
271 CHECK(
272 std::is_sorted(deleted_rows_or_cols.begin(), deleted_rows_or_cols.end()));
273
274 // Update the ids vector.
275 DeleteRowOrColData(dimension.ids, deleted_rows_or_cols);
276
277 // Update the id_to_index map.
278 for (const int64_t deleted_id : deleted_ids) {
279 CHECK(dimension.id_to_index.erase(deleted_id));
280 }
281 for (int i = 0; i < dimension.ids.size(); ++i) {
282 dimension.id_to_index.at(dimension.ids[i]) = i + 1;
283 }
284
285 return deleted_rows_or_cols;
286}
287
288// Translates the input MathOpt indices in row/column GLPK indices to use with
289// glp_load_matrix(). The returned vector first element is always 0 and unused
290// as it is required by GLPK (which uses one-based indices for arrays as well).
291//
292// The id_to_index is supposed to contain GLPK's one-based indices for rows and
293// columns.
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);
299 // First item (index 0) is not used by GLPK.
300 ids.push_back(0);
301 for (const int64_t proto_id : proto_ids) {
302 ids.push_back(id_to_index.at(proto_id));
303 }
304 return ids;
305}
306
307// Returns a vector of coefficients starting at index 1 (as used by GLPK) to use
308// with glp_load_matrix(). The returned vector first element is always 0 and it
309// is ignored by GLPK.
310std::vector<double> MatrixCoefficients(
311 const google::protobuf::RepeatedField<double>& proto_coeffs) {
312 std::vector<double> coeffs(proto_coeffs.size() + 1);
313 // First item (index 0) is not used by GLPK.
314 coeffs[0] = 0.0;
315 std::copy(proto_coeffs.begin(), proto_coeffs.end(), coeffs.begin() + 1);
316 return coeffs;
317}
318
319// Returns true if the input GLPK problem contains integer variables.
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) {
324 return true;
325 }
326 }
327 return false;
328}
329
330// Returns true if the input GLPK problem has no rows and no cols.
331bool IsEmpty(glp_prob* const problem) {
332 return glp_get_num_cols(problem) == 0 && glp_get_num_rows(problem) == 0;
333}
334
335// Returns a sparse vector with the values returned by the getter for the input
336// ids and taking into account the provided filter.
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());
344
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)) {
349 vec.add_ids(ids[i]);
350 vec.add_values(value);
351 }
352 }
353 return vec;
354}
355
356// Returns the ray data the corresponds to element id having the given value and
357// all other elements of ids having 0.
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])) {
366 vec.add_ids(ids[i]);
367 vec.add_values(values[i]);
368 }
369 }
370 return vec;
371}
372
373// Sets the parameters shared between MIP and LP and returns warnings for bad
374// parameters.
375//
376// The input Parameters type should be glp_smcp (for LP), glp_iptcp (for LP with
377// interior point) or glp_iocp (for MIP).
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) {
384 warnings.push_back(
385 absl::StrCat("GLPK only supports parameters.threads = 1; value ",
386 parameters.threads(), " is not supported"));
387 }
388 if (parameters.enable_output() || has_message_callback) {
389 glpk_parameters.msg_lev = GLP_MSG_ALL;
390 } else {
391 glpk_parameters.msg_lev = GLP_MSG_OFF;
392 }
393 if (parameters.has_node_limit()) {
394 warnings.push_back("parameter node_limit not supported by GLPK");
395 }
396 if (parameters.has_objective_limit()) {
397 warnings.push_back("parameter objective_limit not supported by GLPK");
398 }
399 if (parameters.has_best_bound_limit()) {
400 warnings.push_back("parameter best_bound_limit not supported by GLPK");
401 }
402 if (parameters.has_cutoff_limit()) {
403 warnings.push_back("parameter cutoff_limit not supported by GLPK");
404 }
405 if (parameters.has_solution_limit()) {
406 warnings.push_back("parameter solution_limit not supported by GLPK");
407 }
408 if (parameters.has_random_seed()) {
409 warnings.push_back("parameter random_seed not supported by GLPK");
410 }
411 if (parameters.cuts() != EMPHASIS_UNSPECIFIED) {
412 warnings.push_back("parameter cuts not supported by GLPK");
413 }
414 if (parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
415 warnings.push_back("parameter heuristics not supported by GLPK");
416 }
417 if (parameters.scaling() != EMPHASIS_UNSPECIFIED) {
418 warnings.push_back("parameter scaling not supported by GLPK");
419 }
420 if (!warnings.empty()) {
421 return absl::InvalidArgumentError(absl::StrJoin(warnings, "; "));
422 }
423 return absl::OkStatus();
424}
425
426// Sets the time limit parameter which is only supported by some LP algorithm
427// and MIP, but not by interior point.
428//
429// The input Parameters type should be glp_smcp (for LP), or glp_iocp (for MIP).
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(
435 util_time::DecodeGoogleApiProto(parameters.time_limit()).value());
436 glpk_parameters.tm_lim = static_cast<int>(std::min(
437 static_cast<int64_t>(std::numeric_limits<int>::max()), time_limit_ms));
438 }
439}
440
441// Sets the LP specific parameters and returns an InvalidArgumentError for
442// invalid parameters or parameter values.
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;
450 }
451 switch (parameters.presolve()) {
452 case EMPHASIS_UNSPECIFIED:
453 // Keep the default.
454 //
455 // TODO(b/187027049): default is off, which may be surprising for users.
456 break;
457 case EMPHASIS_OFF:
458 glpk_parameters.presolve = GLP_OFF;
459 break;
460 default:
461 glpk_parameters.presolve = GLP_ON;
462 break;
463 }
464 switch (parameters.lp_algorithm()) {
465 case LP_ALGORITHM_UNSPECIFIED:
466 break;
467 case LP_ALGORITHM_PRIMAL_SIMPLEX:
468 glpk_parameters.meth = GLP_PRIMAL;
469 break;
470 case LP_ALGORITHM_DUAL_SIMPLEX:
471 // Use GLP_DUALP to switch back to primal simplex if the dual simplex
472 // fails.
473 //
474 // TODO(b/187027049): GLPK also supports GLP_DUAL to only try dual
475 // simplex. We should have an option to support it.
476 glpk_parameters.meth = GLP_DUALP;
477 break;
478 default:
479 warnings.push_back(absl::StrCat(
480 "GLPK does not support ",
482 " for parameters.lp_algorithm"));
483 break;
484 }
485 if (!warnings.empty()) {
486 return absl::InvalidArgumentError(absl::StrJoin(warnings, "; "));
487 }
488 return absl::OkStatus();
489}
490
491class MipCallbackData {
492 public:
493 explicit MipCallbackData(const SolveInterrupter* const interrupter)
494 : interrupter_(interrupter) {}
495
496 void Callback(glp_tree* const tree) {
497 // We only update the best bound on some specific events since it makes a
498 // traversal of all active nodes.
499 switch (glp_ios_reason(tree)) {
500 case GLP_ISELECT:
501 // The ISELECT call is the first one that happens after a node has been
502 // split on two sub-nodes (IBRANCH) with updated `bound`s based on the
503 // integer value of the branched variable.
504 case GLP_IBINGO:
505 // We found a new integer solution, the `bound` has been updated.
506 case GLP_IROWGEN:
507 // The IROWGEN call is the first one that happens on a current node
508 // after the relaxed problem has been solved and the `bound` field
509 // updated.
510 //
511 // Note that the model/cut pool changes done in IROWGEN and ICUTGEN have
512 // no influence on the `bound` and IROWGEN is the first call to happen.
513 if (const int best_node = glp_ios_best_node(tree); best_node != 0) {
514 best_bound_ = glp_ios_node_bound(tree, best_node);
515 }
516 break;
517 default:
518 // We can ignore:
519 // - IPREPRO: since the `bound` of the current node has not been
520 // computed yet.
521 // - IHEUR: since we have IBINGO if the integer solution is better.
522 // - ICUTGEN: since the `bound` is not updated with the rows added at
523 // IROWGEN so we would get the same best bound.
524 // - IBRANCH: since the sub-nodes will be created after that and their
525 // `bound`s taken into account at ISELECT.
526 break;
527 }
528 if (interrupter_ != nullptr && interrupter_->IsInterrupted()) {
529 glp_ios_terminate(tree);
530 interrupted_by_interrupter_ = true;
531 return;
532 }
533 }
534
535 bool HasBeenInterruptedByInterrupter() const {
536 return interrupted_by_interrupter_.load();
537 }
538
539 std::optional<double> best_bound() const { return best_bound_; }
540
541 private:
542 // Optional interrupter.
543 const SolveInterrupter* const interrupter_;
544
545 // Set to true if glp_ios_terminate() has been called due to the interrupter.
546 std::atomic<bool> interrupted_by_interrupter_ = false;
547
548 // Set on each callback that may update the best bound.
549 std::optional<double> best_bound_;
550};
551
552void MipCallback(glp_tree* const tree, void* const info) {
553 static_cast<MipCallbackData*>(info)->Callback(tree);
554}
555
556// Returns the MathOpt ids of the rows/columns with lower_bound > upper_bound.
557//
558// For variables we use the unrounded bounds as we don't want to return a
559// failing status when rounded bounds of integer variables cross due to the
560// rounding. See EmptyIntegerBoundsResult() for dealing with this case.
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;
567
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]);
573 }
574 }
575
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]);
581 }
582 }
583
584 return inverted_bounds;
585}
586
587// Returns the termination reason based on the current MIP data of the problem
588// assuming that the last call to glp_intopt() returned 0 and that the model has
589// not been modified since.
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");
595 }
596 const int status = glp_mip_status(problem);
597 const bool is_maximize = glp_get_obj_dir(problem) == GLP_MAX;
598 switch (status) {
599 case GLP_OPT:
600 case GLP_FEAS: {
601 const double objective_value = glp_mip_obj_val(problem);
602 if (status == GLP_OPT) {
603 // Note that here we don't use MipCallbackData->best_bound(), even if
604 // set, as if the Gap was used to interrupt the solve GLPK is supposed
605 // to return GLP_EMIPGAP and not 0. And thus we should not go through
606 // this code path if the Gap limit is used.
608 }
610 is_maximize, LIMIT_UNDETERMINED, objective_value,
611 mip_cb_data->best_bound(), "glp_mip_status() returned GLP_FEAS");
612 }
613 case GLP_NOFEAS:
614 // According to InfeasibleTerminationProto()'s documentation: "the
615 // convention for infeasible MIPs is that dual_feasibility_status is
616 // feasible".
618 is_maximize, /*dual_feasibility_status=*/FEASIBILITY_STATUS_FEASIBLE);
619 default:
620 return absl::InternalError(
621 absl::StrCat("glp_intopt() returned 0 but glp_mip_status()"
622 "returned the unexpected value ",
624 }
625}
626
627// Returns the termination reason based on the current interior point data of
628// the problem assuming that the last call to glp_interior() returned 0 and that
629// the model has not been modified since.
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;
634 switch (status) {
635 case GLP_OPT: {
636 const double objective_value = glp_ipt_obj_val(problem);
637 // TODO(b/290359402): here we assume that the objective value of the dual
638 // is exactly the same as the one of the primal. This may not be true as
639 // some tolerance may apply.
641 }
642 case GLP_INFEAS:
644 is_maximize, LIMIT_UNDETERMINED,
645 /*optional_dual_objective=*/std::nullopt,
646 "glp_ipt_status() returned GLP_INFEAS");
647 case GLP_NOFEAS:
648 // Documentation in glpapi08.c for glp_ipt_status says this status means
649 // "no feasible solution exists", but the Reference Manual for GLPK
650 // Version 5.0 clarifies that it means "no feasible primal-dual solution
651 // exists." (See also the comment in glpipm.c when ipm_solve returns 1).
652 // Hence, GLP_NOFEAS corresponds to the solver claiming that either the
653 // primal problem, the dual problem (or both) are infeasible. Under this
654 // condition if the primal is feasible, then the dual must be infeasible
655 // and hence the primal is unbounded.
657 is_maximize,
658 /*dual_feasibility_status=*/FEASIBILITY_STATUS_UNDETERMINED);
659 default:
660 return absl::InternalError(
661 absl::StrCat("glp_interior() returned 0 but glp_ipt_status()"
662 "returned the unexpected value ",
664 }
665}
666
667// Returns the termination reason based on the current interior point data of
668// the problem assuming that the last call to glp_simplex() returned 0 and that
669// the model has not been modified since.
670absl::StatusOr<TerminationProto> SimplexTerminationOnSuccess(
671 glp_prob* const problem, MipCallbackData*) {
672 // Here we don't use glp_get_status() since it is biased towards the primal
673 // simplex algorithm. For example if the dual simplex returns GLP_NOFEAS for
674 // the dual and GLP_INFEAS for the primal then glp_get_status() returns
675 // GLP_INFEAS. This is misleading since the dual successfully determined that
676 // the problem was dual infeasible. So here we use the two statuses of the
677 // primal and the dual to get a better result (the glp_get_status() only
678 // combines them anyway, it does not have any other benefit).
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;
682
683 // Returns a status error indicating that glp_get_dual_stat() returned an
684 // unexpected value.
685 const auto unexpected_dual_stat = [&]() -> absl::Status {
687 << "glp_simplex() returned 0 but glp_get_dual_stat() returned the "
688 "unexpected value "
689 << SolutionStatusString(dual_status)
690 << " while glp_get_prim_stat() returned "
691 << SolutionStatusString(prim_status);
692 };
693
694 switch (prim_status) {
695 case GLP_FEAS:
696 switch (dual_status) {
697 case GLP_FEAS: {
698 // Dual feasibility here means that the solution is dual feasible
699 // (correct signs of the residual costs) and that the complementary
700 // slackness condition are respected. Hence the solution is optimal.
701 const double objective_value = glp_get_obj_val(problem);
703 }
704 case GLP_NOFEAS:
705 return UnboundedTerminationProto(is_maximize);
706 case GLP_INFEAS:
707 default:
708 return unexpected_dual_stat();
709 }
710 case GLP_INFEAS:
711 switch (dual_status) {
712 case GLP_NOFEAS:
714 is_maximize,
715 /*dual_feasibility_status=*/FEASIBILITY_STATUS_INFEASIBLE);
716 case GLP_FEAS:
717 case GLP_INFEAS:
718 default:
719 return unexpected_dual_stat();
720 }
721 case GLP_NOFEAS:
722 switch (dual_status) {
723 case GLP_FEAS:
724 // Dual being feasible (GLP_FEAS) here would lead to dual unbounded;
725 // but this does not exist as a reason.
727 is_maximize,
728 /*dual_feasibility_status=*/FEASIBILITY_STATUS_FEASIBLE);
729 case GLP_INFEAS:
731 is_maximize,
732 /*dual_feasibility_status=*/FEASIBILITY_STATUS_UNDETERMINED);
733 return unexpected_dual_stat();
734 case GLP_NOFEAS:
735 // If both the primal and dual are proven infeasible (GLP_NOFEAS), the
736 // primal wins. Maybe GLPK does never return that though since it
737 // implements either primal or dual simplex algorithm but does not
738 // combine both of them.
740 is_maximize,
741 /*dual_feasibility_status=*/FEASIBILITY_STATUS_INFEASIBLE);
742 default:
743 return unexpected_dual_stat();
744 }
745 default:
746 return absl::InternalError(
747 absl::StrCat("glp_simplex() returned 0 but glp_get_prim_stat() "
748 "returned the unexpected value ",
749 SolutionStatusString(prim_status)));
750 }
751}
752
753// Function called by BuildTermination() when the return code of the solve
754// function is 0.
755//
756// Parameter mip_cb_data is not nullptr iff glp_intopt() was used.
757using TerminationOnSuccessFn = std::function<absl::StatusOr<TerminationProto>(
758 glp_prob* problem, MipCallbackData* const mip_cb_data)>;
759
760// Returns the termination reason based on the return code rc of calling fn_name
761// function which is glp_simplex(), glp_interior() or glp_intopt().
762//
763// For return code 0 which means successful solve, the function
764// termination_on_success is called to build the termination. Other return
765// values (errors) are dealt with specifically.
766//
767// For glp_intopt(), the optional mip_cb_data is used to test for interruption
768// and the LIMIT_INTERRUPTED is set if the interrupter has been triggered (even
769// if the return code is 0). It is also used to get the dual bound. The
770// gap_limit must also be set to the gap limit parameter.
771//
772// When a primal feasible solution exists, feasible_solution_objective_value
773// must be set with its objective. When this variable is unset this function
774// assumes no such solution exists.
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()) {
784 return LimitTerminationProto(is_maximize, LIMIT_INTERRUPTED,
785 feasible_solution_objective_value);
786 }
787
788 // TODO(b/187027049): see if GLP_EOBJLL and GLP_EOBJUL should be handled with
789 // dual simplex.
790 switch (rc) {
791 case 0:
792 return termination_on_success(problem, mip_cb_data);
793 case GLP_EBOUND: {
794 // GLP_EBOUND is returned when a variable or a constraint has the GLP_DB
795 // bounds type and lower_bound >= upper_bound. The code in this file makes
796 // sure we don't use GLP_DB but GLP_FX when lower_bound == upper_bound
797 // thus we expect GLP_EBOUND only when lower_bound > upper_bound. This
798 // should never happen as we call ListInvertedBounds() and
799 // EmptyIntegerBoundsResult() before we call GLPK. Thus we don't expect
800 // GLP_EBOUND to happen.
802 << fn_name << "() returned `" << ReturnCodeString(rc)
803 << "` but the model does not contain variables with inverted "
804 "bounds";
805 }
806 case GLP_EITLIM:
807 return LimitTerminationProto(is_maximize, LIMIT_ITERATION,
808 feasible_solution_objective_value);
809 case GLP_ETMLIM:
810 return LimitTerminationProto(is_maximize, LIMIT_TIME,
811 feasible_solution_objective_value);
812 case GLP_EMIPGAP: {
813 if (!feasible_solution_objective_value.has_value()) {
815 << fn_name << "() returned `" << ReturnCodeString(rc)
816 << "` but glp_mip_status() returned "
817 << SolutionStatusString(glp_mip_status(problem));
818 }
819 if (!mip_cb_data) {
821 << fn_name << "() returned `" << ReturnCodeString(rc)
822 << "` but there is no MipCallbackData";
823 }
824 const double objective_value = feasible_solution_objective_value.value();
825 // Here we expect mip_cb_data->best_bound() to always be set. If this is
826 // not the case we use a worst estimation of the dual bound.
829 mip_cb_data->best_bound().value_or(
830 WorstGLPKDualBound(is_maximize, objective_value, gap_limit)));
831 }
832 case GLP_ESTOP:
833 return LimitTerminationProto(is_maximize, LIMIT_INTERRUPTED,
834 feasible_solution_objective_value);
835 case GLP_ENOPFS:
836 // With presolve on, this error is returned if the LP has no feasible
837 // solution.
839 is_maximize,
840 /*dual_feasibility_status=*/FEASIBILITY_STATUS_UNDETERMINED);
841 case GLP_ENODFS:
842 // With presolve on, this error is returned if the LP has no dual
843 // feasible solution.
845 is_maximize,
846 /*dual_feasibility_status=*/FEASIBILITY_STATUS_INFEASIBLE);
847 case GLP_ENOCVG:
848 // Very slow convergence/divergence (for glp_interior).
849 return LimitTerminationProto(is_maximize, LIMIT_SLOW_PROGRESS,
850 feasible_solution_objective_value);
851 case GLP_EINSTAB:
852 // Numeric stability solving Newtonian system (for glp_interior).
853 return TerminateForReason(
854 is_maximize, TERMINATION_REASON_NUMERICAL_ERROR,
855 absl::StrCat(fn_name, "() returned ", ReturnCodeString(rc),
856 " which means that there is a numeric stability issue "
857 "solving Newtonian system"));
858 default:
860 << fn_name
861 << "() returned unexpected value: " << ReturnCodeString(rc);
862 }
863}
864
865// Callback for glp_term_hook().
866//
867// It expects `info` to be a pointer on a TermHookData.
868int TermHook(void* const info, const char* const message) {
869 static_cast<BufferedMessageCallback*>(info)->OnMessage(message);
870
871 // Returns non-zero to remove any terminal output.
872 return 1;
873}
874
875// Returns the objective offset. This is used as a placeholder for function
876// returning the objective value for solve method not supporting solving empty
877// models (glp_exact() and glp_interior()).
878double OffsetOnlyObjVal(glp_prob* const problem) {
879 return glp_get_obj_coef(problem, 0);
880}
881
882// Returns GLP_OPT. This is used as a placeholder for function returning the
883// status for solve method not supporting solving empty models (glp_exact() and
884// glp_interior()).
885int OptStatus(glp_prob*) { return GLP_OPT; }
886
887} // namespace
888
889absl::StatusOr<std::unique_ptr<SolverInterface>> GlpkSolver::New(
890 const ModelProto& model, const InitArgs& /*init_args*/) {
891 RETURN_IF_ERROR(ModelIsSupported(model, kGlpkSupportedStructures, "GLPK"));
892 return absl::WrapUnique(new GlpkSolver(model));
893}
894
895GlpkSolver::GlpkSolver(const ModelProto& model)
896 : thread_id_(std::this_thread::get_id()), problem_(glp_create_prob()) {
897 // Make sure glp_free_env() is called at the exit of the current thread.
899
900 glp_set_prob_name(problem_, TruncateAndQuoteGLPKName(model.name()).c_str());
901
902 AddVariables(model.variables());
903
904 AddLinearConstraints(model.linear_constraints());
905
906 glp_set_obj_dir(problem_, model.objective().maximize() ? GLP_MAX : GLP_MIN);
907 // Glpk uses index 0 for the "shift" of the objective.
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);
914 }
915
916 const SparseDoubleMatrixProto& proto_matrix =
917 model.linear_constraint_matrix();
918 glp_load_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());
923}
924
926 // Here we simply log an error but glp_delete_prob() should crash with an
927 // error like: `glp_free: memory allocation error`.
928 if (const absl::Status status = CheckCurrentThread(); !status.ok()) {
929 LOG(ERROR) << status;
930 }
931 glp_delete_prob(problem_);
932}
933
934namespace {
935
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);
941
942 switch (rc) {
943 case GLP_ENOPFS:
944 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
945 return problem_status;
946 case GLP_ENODFS:
947 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
948 return problem_status;
949 }
950
951 switch (mip_status) {
952 case GLP_OPT:
953 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
954 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
955 return problem_status;
956 case GLP_FEAS:
957 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
958 break;
959 case GLP_NOFEAS:
960 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
961 break;
962 }
963
964 if (has_finite_dual_bound) {
965 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
966 }
967 return problem_status;
968}
969
970absl::StatusOr<FeasibilityStatusProto> TranslateProblemStatus(
971 const int glpk_status, const absl::string_view fn_name) {
972 switch (glpk_status) {
973 case GLP_FEAS:
974 return FEASIBILITY_STATUS_FEASIBLE;
975 case GLP_NOFEAS:
976 return FEASIBILITY_STATUS_INFEASIBLE;
977 case GLP_INFEAS:
978 case GLP_UNDEF:
979 return FEASIBILITY_STATUS_UNDETERMINED;
980 default:
981 return absl::InternalError(
982 absl::StrCat(fn_name, " returned the unexpected value ",
983 SolutionStatusString(glpk_status)));
984 }
985}
986
987// Builds problem status from:
988// * glp_simplex_rc: code returned by glp_simplex.
989// * glpk_primal_status: primal status returned by glp_get_prim_stat.
990// * glpk_dual_status: dual status returned by glp_get_dual_stat.
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);
997
998 switch (glp_simplex_rc) {
999 case GLP_ENOPFS:
1000 // LP presolver concluded primal infeasibility.
1001 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
1002 return problem_status;
1003 case GLP_ENODFS:
1004 // LP presolver concluded dual infeasibility.
1005 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
1006 return problem_status;
1007 default: {
1008 // Get primal status from basic solution.
1010 const FeasibilityStatusProto primal_status,
1011 TranslateProblemStatus(glpk_primal_status, "glp_get_prim_stat"));
1012 problem_status.set_primal_status(primal_status);
1013
1014 // Get dual status from basic solution.
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;
1020 }
1021 }
1022}
1023
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);
1029
1030 switch (glp_interior_rc) {
1031 case 0:
1032 // We only use the glp_ipt_status() result when glp_interior() returned 0.
1033 switch (ipt_status) {
1034 case GLP_OPT:
1035 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
1036 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
1037 return problem_status;
1038 case GLP_INFEAS:
1039 return problem_status;
1040 case GLP_NOFEAS:
1041 problem_status.set_primal_or_dual_infeasible(true);
1042 return problem_status;
1043 case GLP_UNDEF:
1044 return problem_status;
1045 default:
1046 return absl::InternalError(
1047 absl::StrCat("glp_ipt_status returned the unexpected value ",
1048 SolutionStatusString(ipt_status)));
1049 }
1050 default:
1051 return problem_status;
1052 }
1053}
1054
1055} // namespace
1056
1057absl::StatusOr<SolveResultProto> GlpkSolver::Solve(
1058 const SolveParametersProto& parameters,
1059 const ModelSolveParametersProto& model_parameters,
1060 MessageCallback message_cb,
1061 const CallbackRegistrationProto& callback_registration,
1062 const Callback /*cb*/, const SolveInterrupter* const interrupter) {
1063 RETURN_IF_ERROR(CheckCurrentThread());
1064
1065 const absl::Time start = absl::Now();
1066
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();
1074 };
1075
1077 ListInvertedBounds(
1078 problem_,
1079 /*variable_ids=*/variables_.ids,
1080 /*unrounded_variable_lower_bounds=*/variables_.unrounded_lower_bounds,
1081 /*unrounded_variable_upper_bounds=*/variables_.unrounded_upper_bounds,
1082 /*linear_constraint_ids=*/linear_constraints_.ids)
1083 .ToStatus());
1084
1085 // Deal with empty integer bounds that result in inverted bounds due to bounds
1086 // rounding.
1087 { // Limit scope of `result`.
1088 std::optional<SolveResultProto> result = EmptyIntegerBoundsResult();
1089 if (result.has_value()) {
1090 RETURN_IF_ERROR(set_solve_time(result.value()));
1091 return std::move(result).value();
1092 }
1093 }
1094
1095 RETURN_IF_ERROR(CheckRegisteredCallbackEvents(callback_registration,
1096 /*supported_events=*/{}));
1097
1098 BufferedMessageCallback term_hook_data(std::move(message_cb));
1099 if (term_hook_data.has_user_message_callback()) {
1100 // Note that glp_term_hook() uses get_env_ptr() that relies on thread local
1101 // storage to have a different environment per thread. Thus using
1102 // glp_term_hook() is thread-safe.
1103 //
1104 glp_term_hook(TermHook, &term_hook_data);
1105 }
1106
1107 // We must reset the term hook when before exiting or before flushing the last
1108 // unfinished line.
1109 auto message_cb_cleanup = absl::MakeCleanup([&]() {
1110 if (term_hook_data.has_user_message_callback()) {
1111 glp_term_hook(/*func=*/nullptr, /*info=*/nullptr);
1112 term_hook_data.Flush();
1113 }
1114 });
1115
1116 SolveResultProto result;
1117
1118 const bool is_mip = IsMip(problem_);
1119
1120 // We need to use different functions depending on the solve function we used
1121 // (or placeholders if no solve function was called in case of empty models).
1122 int (*get_prim_stat)(glp_prob*) = nullptr;
1123 double (*obj_val)(glp_prob*) = nullptr;
1124 double (*col_val)(glp_prob*, int) = nullptr;
1125
1126 int (*get_dual_stat)(glp_prob*) = nullptr;
1127 double (*row_dual)(glp_prob*, int) = nullptr;
1128 double (*col_dual)(glp_prob*, int) = nullptr;
1129
1130 const bool maximize = glp_get_obj_dir(problem_) == GLP_MAX;
1131 double best_dual_bound = maximize ? kInf : -kInf;
1132
1133 // Here we use different solve algorithms depending on the type of problem:
1134 // * For MIPs: glp_intopt()
1135 // * For LPs:
1136 // * glp_interior() when using BARRIER LP algorithm
1137 // * glp_simplex() for other LP algorithms.
1138 //
1139 // These solve algorithms have dedicated data segments in glp_prob which use
1140 // different access functions to get the solution; hence each branch will set
1141 // the corresponding function pointers accordingly. They also use a custom
1142 // struct for parameters that will be initialized and passed to the algorithm.
1143 if (is_mip) {
1144 get_prim_stat = glp_mip_status;
1145 obj_val = glp_mip_obj_val;
1146 col_val = glp_mip_col_val;
1147
1148 glp_iocp glpk_parameters;
1149 glp_init_iocp(&glpk_parameters);
1150 RETURN_IF_ERROR(SetSharedParameters(
1151 parameters, term_hook_data.has_user_message_callback(),
1152 glpk_parameters));
1153 SetTimeLimitParameter(parameters, glpk_parameters);
1154 // TODO(b/187027049): glp_intopt with presolve off requires an optional
1155 // solution of the relaxed problem. Here we simply always enable pre-solve
1156 // but we should support disabling the presolve and call glp_simplex() in
1157 // that case.
1158 glpk_parameters.presolve = GLP_ON;
1159 if (parameters.presolve() != EMPHASIS_UNSPECIFIED) {
1161 << "parameter presolve not supported by GLPK for MIP";
1162 }
1163 if (parameters.has_relative_gap_tolerance()) {
1164 glpk_parameters.mip_gap = parameters.relative_gap_tolerance();
1165 }
1166 if (parameters.has_absolute_gap_tolerance()) {
1168 << "parameter absolute_gap_tolerance not supported by GLPK "
1169 "(relative_gap_tolerance is supported)";
1170 }
1171 if (parameters.has_iteration_limit()) {
1173 << "parameter iteration_limit not supported by GLPK for MIP";
1174 }
1175 if (parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
1177 << "parameter lp_algorithm not supported by GLPK for MIP";
1178 }
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_))
1188 : std::nullopt;
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();
1196 }
1197 *result.mutable_solve_stats()->mutable_problem_status() =
1198 GetMipProblemStatusProto(rc, mip_status,
1199 std::isfinite(best_dual_bound));
1200 } else {
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;
1205
1206 get_dual_stat = glp_ipt_status;
1207 row_dual = glp_ipt_row_dual;
1208 col_dual = glp_ipt_col_dual;
1209
1210 glp_iptcp glpk_parameters;
1211 glp_init_iptcp(&glpk_parameters);
1212 if (parameters.has_time_limit()) {
1213 return absl::InvalidArgumentError(
1214 "parameter time_limit not supported by GLPK for interior point "
1215 "algorithm");
1216 }
1217 RETURN_IF_ERROR(SetSharedParameters(
1218 parameters, term_hook_data.has_user_message_callback(),
1219 glpk_parameters));
1220
1221 // glp_interior() does not support being called with an empty model and
1222 // returns GLP_EFAIL. Thus we use placeholders in that case.
1223 //
1224 // TODO(b/259557110): the emptiness is tested by glp_interior() *after*
1225 // some pre-processing (including removing fixed variables). The current
1226 // IsEmpty() is thus not good enough to deal with all cases.
1227 if (IsEmpty(problem_)) {
1228 get_prim_stat = OptStatus;
1229 get_dual_stat = OptStatus;
1230 obj_val = OffsetOnlyObjVal;
1231 const double objective_value = OffsetOnlyObjVal(problem_);
1232 *result.mutable_termination() = OptimalTerminationProto(
1233 /*finite_primal_objective=*/objective_value,
1234 /*dual_objective=*/objective_value,
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);
1241 } else {
1242 // TODO(b/187027049): add solver specific parameters for
1243 // glp_iptcp.ord_alg.
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_))
1250 : std::nullopt;
1252 *result.mutable_termination(),
1253 BuildTermination(problem_, "glp_interior", glp_interior_rc,
1254 InteriorTerminationOnSuccess,
1255 /*mip_cb_data=*/nullptr,
1256 feasible_solution_objective_value,
1257 /*gap_limit=*/kNaN));
1259 *result.mutable_solve_stats()->mutable_problem_status(),
1260 GetBarrierProblemStatusProto(/*glp_interior_rc=*/glp_interior_rc,
1261 /*ipt_status=*/ipt_status));
1262 }
1263 } else {
1264 get_prim_stat = glp_get_prim_stat;
1265 obj_val = glp_get_obj_val;
1266 col_val = glp_get_col_prim;
1267
1268 get_dual_stat = glp_get_dual_stat;
1269 row_dual = glp_get_row_dual;
1270 col_dual = glp_get_col_dual;
1271
1272 glp_smcp glpk_parameters;
1273 glp_init_smcp(&glpk_parameters);
1274 RETURN_IF_ERROR(SetSharedParameters(
1275 parameters, term_hook_data.has_user_message_callback(),
1276 glpk_parameters));
1277 SetTimeLimitParameter(parameters, glpk_parameters);
1278 RETURN_IF_ERROR(SetLPParameters(parameters, glpk_parameters));
1279
1280 // TODO(b/187027049): add option to use glp_exact().
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_))
1286 : std::nullopt;
1287 ASSIGN_OR_RETURN(*result.mutable_termination(),
1288 BuildTermination(problem_, "glp_simplex", glp_simplex_rc,
1289 SimplexTerminationOnSuccess,
1290 /*mip_cb_data=*/nullptr,
1291 feasible_solution_objective_value,
1292 /*gap_limit=*/kNaN));
1293
1294 // If the primal is proven infeasible and the dual is feasible, the dual
1295 // is unbounded. Thus we can compute a better dual bound rather than the
1296 // default 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;
1300 }
1301
1302 ASSIGN_OR_RETURN(*result.mutable_solve_stats()->mutable_problem_status(),
1303 GetSimplexProblemStatusProto(
1304 /*glp_simplex_rc=*/glp_simplex_rc,
1305 /*glpk_primal_status=*/prim_stat,
1306 /*glpk_dual_status=*/glp_get_dual_stat(problem_)));
1307 VLOG(1) << "glp_get_status: "
1308 << SolutionStatusString(glp_get_status(problem_))
1309 << " glp_get_prim_stat: " << SolutionStatusString(prim_stat)
1310 << " glp_get_dual_stat: "
1311 << SolutionStatusString(glp_get_dual_stat(problem_));
1312 }
1313 }
1314
1315 // Unregister the callback and flush the potential last unfinished line.
1316 std::move(message_cb_cleanup).Invoke();
1317
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_));
1322 break;
1323 case TERMINATION_REASON_UNBOUNDED:
1324 // Here we can't use obj_val(problem_) as it would be a finite value of
1325 // the feasible solution found.
1326 result.mutable_solve_stats()->set_best_primal_bound(maximize ? +kInf
1327 : -kInf);
1328 break;
1329 default:
1330 result.mutable_solve_stats()->set_best_primal_bound(maximize ? -kInf
1331 : kInf);
1332 break;
1333 }
1334 // TODO(b/187027049): compute the dual value when the dual is feasible (or
1335 // problem optimal for interior point) based on the bounds and the dual values
1336 // for LPs.
1337 result.mutable_solve_stats()->set_best_dual_bound(best_dual_bound);
1338 SolutionProto solution;
1339 AddPrimalSolution(get_prim_stat, obj_val, col_val, model_parameters,
1340 solution);
1341 if (!is_mip) {
1342 AddDualSolution(get_dual_stat, obj_val, row_dual, col_dual,
1343 model_parameters, solution);
1344 }
1345 if (solution.has_primal_solution() || solution.has_dual_solution() ||
1346 solution.has_basis()) {
1347 *result.add_solutions() = std::move(solution);
1348 }
1349 if (parameters.glpk().compute_unbound_rays_if_possible()) {
1350 RETURN_IF_ERROR(AddPrimalOrDualRay(model_parameters, result));
1351 }
1352
1353 RETURN_IF_ERROR(set_solve_time(result));
1354 return result;
1355}
1356
1357void GlpkSolver::AddVariables(const VariablesProto& new_variables) {
1358 if (new_variables.ids().empty()) {
1359 return;
1360 }
1361
1362 // Indices in GLPK are one-based.
1363 const int first_new_var_index = variables_.ids.size() + 1;
1364
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)
1370 .second);
1371 }
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) {
1375 glp_set_col_name(
1376 problem_, v + first_new_var_index,
1377 TruncateAndQuoteGLPKName(new_variables.names(v)).c_str());
1378 }
1379 }
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) {
1390 // Here we don't use the boolean "kind" GLP_BV since it does not exist. It
1391 // is an artifact of glp_(get|set)_col_kind() functions. When
1392 // glp_set_col_kind() is called with GLP_BV, in addition to setting the kind
1393 // to GLP_IV (integer) it also sets the bounds to [0,1]. Symmetrically
1394 // glp_get_col_kind() returns GLP_BV when the kind is GLP_IV and the bounds
1395 // are [0,1].
1396 glp_set_col_kind(problem_, i + first_new_var_index,
1397 new_variables.integers(i) ? GLP_IV : GLP_CV);
1398 SetBounds<Variables>(problem_, /*k=*/i + first_new_var_index,
1399 {.lower = new_variables.lower_bounds(i),
1400 .upper = new_variables.upper_bounds(i)});
1401 }
1402}
1403
1404void GlpkSolver::AddLinearConstraints(
1405 const LinearConstraintsProto& new_linear_constraints) {
1406 if (new_linear_constraints.ids().empty()) {
1407 return;
1408 }
1409
1410 // Indices in GLPK are one-based.
1411 const int first_new_cstr_index = linear_constraints_.ids.size() + 1;
1412
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)
1420 .second);
1421 }
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) {
1425 glp_set_row_name(
1426 problem_, c + first_new_cstr_index,
1427 TruncateAndQuoteGLPKName(new_linear_constraints.names(c)).c_str());
1428 }
1429 }
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_, /*k=*/i + first_new_cstr_index,
1435 {.lower = new_linear_constraints.lower_bounds(i),
1436 .upper = new_linear_constraints.upper_bounds(i)});
1437 }
1438}
1439
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);
1446 }
1447}
1448
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) {
1453 // GLPK's does not have an API to set matrix elements one by one. Instead it
1454 // can either update an entire row or update an entire column or load the
1455 // entire matrix. On top of that there is no API to get the entire matrix at
1456 // once.
1457 //
1458 // Hence to update existing coefficients we have to read rows (or columns)
1459 // coefficients, update existing non-zero that have been changed and add new
1460 // values and write back the result. For new rows and columns we can be more
1461 // efficient since we don't have to read the existing values back.
1462 //
1463 // The strategy used below is to split the matrix in three regions:
1464 //
1465 // existing new
1466 // columns columns
1467 // / | \
1468 // existing | 1 | 2 |
1469 // rows | | |
1470 // |---------+---------|
1471 // new | |
1472 // rows | 3 |
1473 // \ /
1474 //
1475 // We start by updating the region 1 of existing rows and columns to limit the
1476 // number of reads of existing coefficients. Then we update region 2 with all
1477 // new columns but we only existing rows. Finally we update region 3 with all
1478 // new rows and include new columns. Doing updates this way remove the need to
1479 // read existing coefficients for the updates 2 & 3 since by construction
1480 // those values are 0.
1481
1482 // Updating existing rows (constraints), ignoring the new columns.
1483 {
1484 // We reuse the same vectors for all calls to GLPK's API to limit
1485 // reallocations of these temporary buffers.
1486 GlpkSparseVector data(static_cast<int>(variables_.ids.size()));
1487 for (const auto& [row_id, row_coefficients] :
1488 SparseSubmatrixByRows(matrix_updates,
1489 /*start_row_id=*/0,
1490 /*end_row_id=*/first_new_cstr_id,
1491 /*start_col_id=*/0,
1492 /*end_col_id=*/first_new_var_id)) {
1493 // Find the index of the row in GLPK corresponding to the MathOpt's row
1494 // 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);
1497
1498 // Read the current row coefficients.
1499 data.Load([&](int* const indices, double* const values) {
1500 return glp_get_mat_row(problem_, row_index, indices, values);
1501 });
1502
1503 // Update the row data.
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);
1507 data.Set(col_index, coefficient);
1508 }
1509
1510 // Change the row values.
1511 glp_set_mat_row(problem_, row_index, data.size(), data.indices(),
1512 data.values());
1513 }
1514 }
1515
1516 // Add new columns's coefficients of existing rows. The coefficients of new
1517 // columns in new rows will be added when adding new rows below.
1518 if (first_new_var_id.has_value()) {
1519 GlpkSparseVector data(static_cast<int>(linear_constraints_.ids.size()));
1520 for (const auto& [col_id, col_coefficients] : TransposeSparseSubmatrix(
1521 SparseSubmatrixByRows(matrix_updates,
1522 /*start_row_id=*/0,
1523 /*end_row_id=*/first_new_cstr_id,
1524 /*start_col_id=*/*first_new_var_id,
1525 /*end_col_id=*/std::nullopt))) {
1526 // Find the index of the column in GLPK corresponding to the MathOpt's
1527 // column id.
1528 const int col_index = variables_.id_to_index.at(col_id);
1529 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1530
1531 // Prepare the column data replacing MathOpt ids by GLPK one-based row
1532 // indices.
1533 data.Clear();
1534 for (const auto [row_id, coefficient] : MakeView(col_coefficients)) {
1535 const int row_index = linear_constraints_.id_to_index.at(row_id);
1536 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1537 data.Set(row_index, coefficient);
1538 }
1539
1540 // Change the column values.
1541 glp_set_mat_col(problem_, col_index, data.size(), data.indices(),
1542 data.values());
1543 }
1544 }
1545
1546 // Add new rows, including the new columns' coefficients.
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] :
1550 SparseSubmatrixByRows(matrix_updates,
1551 /*start_row_id=*/*first_new_cstr_id,
1552 /*end_row_id=*/std::nullopt,
1553 /*start_col_id=*/0,
1554 /*end_col_id=*/std::nullopt)) {
1555 // Find the index of the row in GLPK corresponding to the MathOpt's row
1556 // id.
1557 const int row_index = linear_constraints_.id_to_index.at(row_id);
1558 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1559
1560 // Prepare the row data replacing MathOpt ids by GLPK one-based column
1561 // indices.
1562 data.Clear();
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);
1566 data.Set(col_index, coefficient);
1567 }
1568
1569 // Change the row values.
1570 glp_set_mat_row(problem_, row_index, data.size(), data.indices(),
1571 data.values());
1572 }
1573 }
1574}
1575
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_);
1582 if (status == GLP_OPT || status == GLP_FEAS) {
1583 PrimalSolutionProto& primal_solution =
1584 *solution_proto.mutable_primal_solution();
1585 primal_solution.set_objective_value(obj_val(problem_));
1586 primal_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1587 *primal_solution.mutable_variable_values() =
1588 FilteredVector(problem_, model_parameters.variable_values_filter(),
1589 variables_.ids, col_val);
1590 }
1591}
1592
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_);
1599 if (status == GLP_OPT || status == GLP_FEAS) {
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);
1608 // TODO(b/197867442): Check that `status == GLP_FEAS` implies dual feasible
1609 // solution on early termination with barrier (where both `get_dual_stat`
1610 // and `get_prim_stat` are equal to `glp_ipt_status`).
1611 dual_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1612 }
1613}
1614
1615absl::Status GlpkSolver::AddPrimalOrDualRay(
1616 const ModelSolveParametersProto& model_parameters,
1617 SolveResultProto& result) {
1618 ASSIGN_OR_RETURN(const std::optional<GlpkRay> opt_unbound_ray,
1619 GlpkComputeUnboundRay(problem_));
1620 if (!opt_unbound_ray.has_value()) {
1621 return absl::OkStatus();
1622 }
1623
1624 const int num_cstrs = linear_constraints_.ids.size();
1625 switch (opt_unbound_ray->type) {
1626 case GlpkRayType::kPrimal: {
1627 const int num_cstrs = static_cast<int>(linear_constraints_.ids.size());
1628 // Note that GlpkComputeUnboundRay() returned ray considers the variables
1629 // of the computational form. Thus it contains both structural and
1630 // auxiliary variables. In the MathOpt's primal ray we only consider
1631 // structural variables though.
1632 std::vector<double> ray_values(variables_.ids.size());
1633
1634 for (const auto [k, value] : opt_unbound_ray->non_zero_components) {
1635 if (k <= num_cstrs) {
1636 // Ignore auxiliary variables.
1637 continue;
1638 }
1639 const int var_index = k - num_cstrs;
1640 CHECK_GE(var_index, 1);
1641 ray_values[var_index - 1] = value;
1642 }
1643
1644 *result.add_primal_rays()->mutable_variable_values() =
1645 FilteredRay(model_parameters.variable_values_filter(), variables_.ids,
1646 ray_values);
1647
1648 return absl::OkStatus();
1649 }
1650 case GlpkRayType::kDual: {
1651 // Note that GlpkComputeUnboundRay() returned ray considers the variables
1652 // of the computational form. Thus it contains reduced costs of both
1653 // structural and auxiliary variables. In the MathOpt's dual ray we split
1654 // the reduced costs. The ones of auxiliary variables (variables of
1655 // constraints) are called "dual values" and the ones of structural
1656 // variables are called "reduced costs".
1657 std::vector<double> ray_reduced_costs(variables_.ids.size());
1658 std::vector<double> ray_dual_values(num_cstrs);
1659
1660 for (const auto [k, value] : opt_unbound_ray->non_zero_components) {
1661 if (k <= num_cstrs) {
1662 ray_dual_values[k - 1] = value;
1663 } else {
1664 const int var_index = k - num_cstrs;
1665 CHECK_GE(var_index, 1);
1666 ray_reduced_costs[var_index - 1] = value;
1667 }
1668 }
1669
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,
1676 ray_reduced_costs);
1677
1678 return absl::OkStatus();
1679 }
1680 }
1681}
1682
1683absl::StatusOr<bool> GlpkSolver::Update(const ModelUpdateProto& model_update) {
1684 RETURN_IF_ERROR(CheckCurrentThread());
1685
1686 // We must do that *after* testing current thread since the Solver class won't
1687 // destroy this instance from another thread when the update is not supported
1688 // (the Solver class destroy the SolverInterface only when an Update() returns
1689 // false).
1690 if (!UpdateIsSupported(model_update, kGlpkSupportedStructures)) {
1691 return false;
1692 }
1693
1694 {
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());
1702 }
1703 DeleteRowsOrCols(problem_, linear_constraints_,
1704 model_update.deleted_linear_constraint_ids());
1705
1706 for (const auto [var_id, is_integer] :
1707 MakeView(model_update.variable_updates().integers())) {
1708 // See comment in AddVariables() to see why we don't use GLP_BV here.
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);
1711
1712 // Either restore the fractional bounds if the variable was integer and is
1713 // now integer, or rounds the existing bounds if the variable was fractional
1714 // and is now integer. Here we use the old bounds; they will get updated
1715 // below by the call to UpdateBounds() if they are also changed by this
1716 // update.
1717 SetBounds<Variables>(
1718 problem_, var_index,
1719 {.lower = variables_.unrounded_lower_bounds[var_index - 1],
1720 .upper = variables_.unrounded_upper_bounds[var_index - 1]});
1721 }
1722 for (const auto [var_id, lower_bound] :
1723 MakeView(model_update.variable_updates().lower_bounds())) {
1724 variables_.unrounded_lower_bounds[variables_.id_to_index.at(var_id) - 1] =
1726 }
1727 for (const auto [var_id, upper_bound] :
1728 MakeView(model_update.variable_updates().upper_bounds())) {
1729 variables_.unrounded_upper_bounds[variables_.id_to_index.at(var_id) - 1] =
1731 }
1732 UpdateBounds(
1733 problem_, variables_,
1734 /*lower_bounds_proto=*/model_update.variable_updates().lower_bounds(),
1735 /*upper_bounds_proto=*/model_update.variable_updates().upper_bounds());
1736 UpdateBounds(problem_, linear_constraints_,
1737 /*lower_bounds_proto=*/
1738 model_update.linear_constraint_updates().lower_bounds(),
1739 /*upper_bounds_proto=*/
1740 model_update.linear_constraint_updates().upper_bounds());
1741
1742 AddVariables(model_update.new_variables());
1743 AddLinearConstraints(model_update.new_linear_constraints());
1744
1745 if (model_update.objective_updates().has_direction_update()) {
1746 glp_set_obj_dir(problem_,
1747 model_update.objective_updates().direction_update()
1748 ? GLP_MAX
1749 : GLP_MIN);
1750 }
1751 if (model_update.objective_updates().has_offset_update()) {
1752 // Glpk uses index 0 for the "shift" of the objective.
1753 glp_set_obj_coef(problem_, 0,
1754 model_update.objective_updates().offset_update());
1755 }
1756 UpdateObjectiveCoefficients(
1757 model_update.objective_updates().linear_coefficients());
1758
1759 UpdateLinearConstraintMatrix(
1760 model_update.linear_constraint_matrix_updates(),
1761 /*first_new_var_id=*/FirstVariableId(model_update.new_variables()),
1762 /*first_new_cstr_id=*/
1763 FirstLinearConstraintId(model_update.new_linear_constraints()));
1764
1765 return true;
1766}
1767
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");
1773 }
1774 return absl::OkStatus();
1775}
1776
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)) {
1781 continue;
1782 }
1783 const double lb = variables_.unrounded_lower_bounds[c - 1];
1784 const double ub = variables_.unrounded_upper_bounds[c - 1];
1785 if (lb > ub) {
1786 // Unrounded bounds are inverted; this case is covered by
1787 // ListInvertedBounds(). We don't want to depend on the order of calls of
1788 // the two functions here so we exclude this case.
1789 continue;
1790 }
1791 if (std::ceil(lb) <= std::floor(ub)) {
1792 continue;
1793 }
1794
1795 // We found a variable with empty integer bounds (that is lb <= ub but
1796 // ceil(lb) > floor(ub)).
1798 /*is_maximize=*/glp_get_obj_dir(problem_) == GLP_MAX,
1799 /*bad_variable_id=*/variables_.ids[c - 1],
1800 /*lb=*/lb, /*ub=*/ub);
1801 }
1802
1803 return std::nullopt;
1804}
1805
1806absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
1808 const SolveParametersProto& parameters, MessageCallback message_cb,
1809 const SolveInterrupter* const interrupter) {
1810 return absl::UnimplementedError(
1811 "GLPK does not provide a method to compute an infeasible subsystem");
1812}
1813
1815
1816} // namespace math_opt
1817} // namespace operations_research
#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 &parameters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, const SolveInterrupter *interrupter) override
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto &parameters, 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
SatParameters parameters
int64_t value
absl::Status status
Definition g_gurobi.cc:44
double lower
double upper
double upper_bound
double lower_bound
absl::Span< const int64_t > variable_ids
GRBmodel * model
double solution
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
Definition callback.h:93
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 &registration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
double WorstGLPKDualBound(const bool is_maximize, const double objective_value, const double relative_gap_limit)
Definition gap.cc:23
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)
Definition rays.cc:352
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)
Definition proto_utils.h:50
std::string TruncateAndQuoteGLPKName(const std::string_view original_name)
std::string ReturnCodeString(const int rc)
std::string SolutionStatusString(const int status)
Formats a solution status (GLP_OPT,...).
STL namespace.
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
Definition protoutil.h:42
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition protoutil.h:27
StatusBuilder InternalErrorBuilder()
StatusBuilder InvalidArgumentErrorBuilder()
int64_t coefficient
std::vector< double > lower_bounds
Definition lp_utils.cc:746
std::vector< double > upper_bounds
Definition lp_utils.cc:747
int var_index
Definition search.cc:3268
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)
int64_t start
std::string message
Definition trace.cc:397
double objective_value
The value objective_vector^T * (solution - center_point).