Google OR-Tools v9.15
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-2025 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/base/nullability.h"
31#include "absl/cleanup/cleanup.h"
32#include "absl/container/flat_hash_map.h"
33#include "absl/log/check.h"
34#include "absl/log/log.h"
35#include "absl/memory/memory.h"
36#include "absl/status/status.h"
37#include "absl/status/statusor.h"
38#include "absl/strings/str_cat.h"
39#include "absl/strings/str_join.h"
40#include "absl/strings/string_view.h"
41#include "absl/time/clock.h"
42#include "absl/time/time.h"
43#include "absl/types/span.h"
71
72namespace operations_research {
73namespace math_opt {
74
75namespace {
76
77constexpr double kInf = std::numeric_limits<double>::infinity();
78constexpr double kNaN = std::numeric_limits<double>::quiet_NaN();
79
80constexpr SupportedProblemStructures kGlpkSupportedStructures = {
81 .integer_variables = SupportType::kSupported};
82
83// Bounds of rows or columns.
84struct Bounds {
85 double lower = -kInf;
86 double upper = kInf;
87};
88
89// Sets either a row or a column bounds. The index k is the one-based index of
90// the row or the column.
91//
92// The Dimension type should be either GlpkSolver::Variable or
93// GlpkSolver::LinearConstraints.
94//
95// When Dimension::IsInteger() returns true, the bounds are rounded before being
96// applied which is mandatory for integer variables (solvers fail if a model
97// contains non-integer bounds for integer variables). Thus the integrality of
98// variables must be set/updated before calling this function.
99template <typename Dimension>
100void SetBounds(glp_prob* const problem, const int k, const Bounds& bounds) {
101 // GLPK wants integer bounds for integer variables.
102 const bool is_integer = Dimension::IsInteger(problem, k);
103 const double lb = is_integer ? std::ceil(bounds.lower) : bounds.lower;
104 const double ub = is_integer ? std::floor(bounds.upper) : bounds.upper;
105 int type = GLP_FR;
106 if (std::isinf(lb) && std::isinf(ub)) {
107 type = GLP_FR;
108 } else if (std::isinf(lb)) {
109 type = GLP_UP;
110 } else if (std::isinf(ub)) {
111 type = GLP_LO;
112 } else if (lb == ub) {
113 type = GLP_FX;
114 } else { // Bounds not inf and not equal.
115 type = GLP_DB;
116 }
117 Dimension::kSetBounds(problem, k, type, lb, ub);
118}
119
120// Gets either a row or a column bounds. The index k is the one-based index of
121// the row or the column.
122//
123// The Dimension type should be either GlpkSolver::Variable or
124// GlpkSolver::LinearConstraints.
125template <typename Dimension>
126Bounds GetBounds(glp_prob* const problem, const int k) {
127 const int type = Dimension::kGetType(problem, k);
128 switch (type) {
129 case GLP_FR:
130 return {};
131 case GLP_LO:
132 return {.lower = Dimension::kGetLb(problem, k)};
133 case GLP_UP:
134 return {.upper = Dimension::kGetUb(problem, k)};
135 case GLP_DB:
136 case GLP_FX:
137 return {.lower = Dimension::kGetLb(problem, k),
138 .upper = Dimension::kGetUb(problem, k)};
139 default:
140 LOG(FATAL) << type;
141 }
142}
143
144// Updates the bounds of either rows or columns.
145//
146// The Dimension type should be either GlpkSolver::Variable or
147// GlpkSolver::LinearConstraints.
148//
149// When Dimension::IsInteger() returns true, the bounds are rounded before being
150// applied which is mandatory for integer variables (solvers fail if a model
151// contains non-integer bounds for integer variables). Thus the integrality of
152// variables must be updated before calling this function.
153template <typename Dimension>
154void UpdateBounds(glp_prob* const problem, const Dimension& dimension,
155 const SparseDoubleVectorProto& lower_bounds_proto,
156 const SparseDoubleVectorProto& upper_bounds_proto) {
157 const auto lower_bounds = MakeView(lower_bounds_proto);
158 const auto upper_bounds = MakeView(upper_bounds_proto);
159
160 auto current_lower_bound = lower_bounds.begin();
161 auto current_upper_bound = upper_bounds.begin();
162 for (;;) {
163 // Get the smallest unvisited id from either sparse container.
164 std::optional<int64_t> next_id;
165 if (current_lower_bound != lower_bounds.end()) {
166 if (!next_id.has_value() || current_lower_bound->first < *next_id) {
167 next_id = current_lower_bound->first;
168 }
169 }
170 if (current_upper_bound != upper_bounds.end()) {
171 if (!next_id.has_value() || current_upper_bound->first < *next_id) {
172 next_id = current_upper_bound->first;
173 }
174 }
175
176 if (!next_id.has_value()) {
177 // We exhausted all collections.
178 break;
179 }
180
181 // Find the corresponding row or column.
182 const int row_or_col_index = dimension.id_to_index.at(*next_id);
183 CHECK_EQ(dimension.ids[row_or_col_index - 1], *next_id);
184
185 // Get the updated values for bounds and move the iterator for consumed
186 // updates.
187 Bounds bounds = GetBounds<Dimension>(problem,
188 /*k=*/row_or_col_index);
189 if (current_lower_bound != lower_bounds.end() &&
190 current_lower_bound->first == *next_id) {
191 bounds.lower = current_lower_bound->second;
192 ++current_lower_bound;
193 }
194 if (current_upper_bound != upper_bounds.end() &&
195 current_upper_bound->first == *next_id) {
196 bounds.upper = current_upper_bound->second;
197 ++current_upper_bound;
198 }
199 SetBounds<Dimension>(problem, /*k=*/row_or_col_index,
200 /*bounds=*/bounds);
201 }
202
203 CHECK(current_lower_bound == lower_bounds.end());
204 CHECK(current_upper_bound == upper_bounds.end());
205}
206
207// Deletes in-place the data corresponding to the indices of rows/cols.
208//
209// The vector of one-based indices sorted_deleted_rows_or_cols is expected to be
210// sorted and its first element of index 0 is ignored (this is the GLPK
211// convention).
212template <typename V>
213void DeleteRowOrColData(std::vector<V>& data,
214 absl::Span<const int> sorted_deleted_rows_or_cols) {
215 if (sorted_deleted_rows_or_cols.empty()) {
216 // Avoid looping when not necessary.
217 return;
218 }
219
220 std::size_t next_insertion_point = 0;
221 std::size_t current_row_or_col = 0;
222 for (std::size_t i = 1; i < sorted_deleted_rows_or_cols.size(); ++i) {
223 const int deleted_row_or_col = sorted_deleted_rows_or_cols[i];
224 for (; current_row_or_col + 1 < deleted_row_or_col;
225 ++current_row_or_col, ++next_insertion_point) {
226 DCHECK_LT(current_row_or_col, data.size());
227 data[next_insertion_point] = data[current_row_or_col];
228 }
229 // Skip the deleted row/col.
230 ++current_row_or_col;
231 }
232 for (; current_row_or_col < data.size();
233 ++current_row_or_col, ++next_insertion_point) {
234 data[next_insertion_point] = data[current_row_or_col];
235 }
236 data.resize(next_insertion_point);
237}
238
239// Deletes the row or cols of the GLPK problem and returns their indices. As a
240// side effect it updates dimension.ids and dimension.id_to_index.
241//
242// The Dimension type should be either GlpkSolver::Variable or
243// GlpkSolver::LinearConstraints.
244//
245// The returned vector is sorted and the first element (index 0) must be ignored
246// (this is the GLPK convention). It can be used with DeleteRowOrColData().
247template <typename Dimension>
248std::vector<int> DeleteRowsOrCols(
249 glp_prob* const problem, Dimension& dimension,
250 const google::protobuf::RepeatedField<int64_t>& deleted_ids) {
251 if (deleted_ids.empty()) {
252 // This is not only an optimization. Functions glp_del_rows() and
253 // glp_del_cols() fails if the number of deletion is 0.
254 return {};
255 }
256
257 // Delete GLPK rows or columns.
258 std::vector<int> deleted_rows_or_cols;
259 // Functions glp_del_rows() and glp_del_cols() only use values in ranges
260 // [1,n]. The first element is not used.
261 deleted_rows_or_cols.reserve(deleted_ids.size() + 1);
262 deleted_rows_or_cols.push_back(-1);
263 for (const int64_t deleted_id : deleted_ids) {
264 deleted_rows_or_cols.push_back(dimension.id_to_index.at(deleted_id));
265 }
266 Dimension::kDelElts(problem, deleted_rows_or_cols.size() - 1,
267 deleted_rows_or_cols.data());
268
269 // Since deleted_ids are in strictly increasing order and we allocate
270 // rows/cols in orders of MathOpt ids; deleted_rows_or_cols should also be
271 // sorted.
272 CHECK(
273 std::is_sorted(deleted_rows_or_cols.begin(), deleted_rows_or_cols.end()));
274
275 // Update the ids vector.
276 DeleteRowOrColData(dimension.ids, deleted_rows_or_cols);
277
278 // Update the id_to_index map.
279 for (const int64_t deleted_id : deleted_ids) {
280 CHECK(dimension.id_to_index.erase(deleted_id));
281 }
282 for (int i = 0; i < dimension.ids.size(); ++i) {
283 dimension.id_to_index.at(dimension.ids[i]) = i + 1;
284 }
285
286 return deleted_rows_or_cols;
287}
288
289// Translates the input MathOpt indices in row/column GLPK indices to use with
290// glp_load_matrix(). The returned vector first element is always 0 and unused
291// as it is required by GLPK (which uses one-based indices for arrays as well).
292//
293// The id_to_index is supposed to contain GLPK's one-based indices for rows and
294// columns.
295std::vector<int> MatrixIds(
296 const google::protobuf::RepeatedField<int64_t>& proto_ids,
297 const absl::flat_hash_map<int64_t, int>& id_to_index) {
298 std::vector<int> ids;
299 ids.reserve(proto_ids.size() + 1);
300 // First item (index 0) is not used by GLPK.
301 ids.push_back(0);
302 for (const int64_t proto_id : proto_ids) {
303 ids.push_back(id_to_index.at(proto_id));
304 }
305 return ids;
306}
307
308// Returns a vector of coefficients starting at index 1 (as used by GLPK) to use
309// with glp_load_matrix(). The returned vector first element is always 0 and it
310// is ignored by GLPK.
311std::vector<double> MatrixCoefficients(
312 const google::protobuf::RepeatedField<double>& proto_coeffs) {
313 std::vector<double> coeffs(proto_coeffs.size() + 1);
314 // First item (index 0) is not used by GLPK.
315 coeffs[0] = 0.0;
316 std::copy(proto_coeffs.begin(), proto_coeffs.end(), coeffs.begin() + 1);
317 return coeffs;
318}
319
320// Returns true if the input GLPK problem contains integer variables.
321bool IsMip(glp_prob* const problem) {
322 const int num_vars = glp_get_num_cols(problem);
323 for (int v = 1; v <= num_vars; ++v) {
324 if (glp_get_col_kind(problem, v) != GLP_CV) {
325 return true;
326 }
327 }
328 return false;
329}
330
331// Returns true if the input GLPK problem has no rows and no cols.
332bool IsEmpty(glp_prob* const problem) {
333 return glp_get_num_cols(problem) == 0 && glp_get_num_rows(problem) == 0;
334}
335
336// Returns a sparse vector with the values returned by the getter for the input
337// ids and taking into account the provided filter.
338SparseDoubleVectorProto FilteredVector(glp_prob* const problem,
339 const SparseVectorFilterProto& filter,
340 absl::Span<const int64_t> ids,
341 double (*const getter)(glp_prob*, int)) {
343 vec.mutable_ids()->Reserve(ids.size());
344 vec.mutable_values()->Reserve(ids.size());
345
346 SparseVectorFilterPredicate predicate(filter);
347 for (int i = 0; i < ids.size(); ++i) {
348 const double value = getter(problem, i + 1);
349 if (predicate.AcceptsAndUpdate(ids[i], value)) {
350 vec.add_ids(ids[i]);
351 vec.add_values(value);
352 }
353 }
354 return vec;
355}
356
357// Returns the ray data the corresponds to element id having the given value and
358// all other elements of ids having 0.
359SparseDoubleVectorProto FilteredRay(const SparseVectorFilterProto& filter,
360 absl::Span<const int64_t> ids,
361 absl::Span<const double> values) {
362 CHECK_EQ(ids.size(), values.size());
364 SparseVectorFilterPredicate predicate(filter);
365 for (int i = 0; i < ids.size(); ++i) {
366 if (predicate.AcceptsAndUpdate(ids[i], values[i])) {
367 vec.add_ids(ids[i]);
368 vec.add_values(values[i]);
369 }
370 }
371 return vec;
372}
373
374// Sets the parameters shared between MIP and LP and returns warnings for bad
375// parameters.
376//
377// The input Parameters type should be glp_smcp (for LP), glp_iptcp (for LP with
378// interior point) or glp_iocp (for MIP).
379template <typename Parameters>
380absl::Status SetSharedParameters(const SolveParametersProto& parameters,
381 const bool has_message_callback,
382 Parameters& glpk_parameters) {
383 std::vector<std::string> warnings;
384 if (parameters.has_threads() && parameters.threads() > 1) {
385 warnings.push_back(
386 absl::StrCat("GLPK only supports parameters.threads = 1; value ",
387 parameters.threads(), " is not supported"));
388 }
389 if (parameters.enable_output() || has_message_callback) {
390 glpk_parameters.msg_lev = GLP_MSG_ALL;
391 } else {
392 glpk_parameters.msg_lev = GLP_MSG_OFF;
393 }
394 if (parameters.has_node_limit()) {
395 warnings.push_back("parameter node_limit not supported by GLPK");
396 }
397 if (parameters.has_objective_limit()) {
398 warnings.push_back("parameter objective_limit not supported by GLPK");
399 }
400 if (parameters.has_best_bound_limit()) {
401 warnings.push_back("parameter best_bound_limit not supported by GLPK");
402 }
403 if (parameters.has_cutoff_limit()) {
404 warnings.push_back("parameter cutoff_limit not supported by GLPK");
405 }
406 if (parameters.has_solution_limit()) {
407 warnings.push_back("parameter solution_limit not supported by GLPK");
408 }
409 if (parameters.has_random_seed()) {
410 warnings.push_back("parameter random_seed not supported by GLPK");
411 }
412 if (parameters.cuts() != EMPHASIS_UNSPECIFIED) {
413 warnings.push_back("parameter cuts not supported by GLPK");
414 }
415 if (parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
416 warnings.push_back("parameter heuristics not supported by GLPK");
417 }
418 if (parameters.scaling() != EMPHASIS_UNSPECIFIED) {
419 warnings.push_back("parameter scaling not supported by GLPK");
420 }
421 if (!warnings.empty()) {
422 return absl::InvalidArgumentError(absl::StrJoin(warnings, "; "));
423 }
424 return absl::OkStatus();
425}
426
427// Sets the time limit parameter which is only supported by some LP algorithm
428// and MIP, but not by interior point.
429//
430// The input Parameters type should be glp_smcp (for LP), or glp_iocp (for MIP).
431template <typename Parameters>
432void SetTimeLimitParameter(const SolveParametersProto& parameters,
433 Parameters& glpk_parameters) {
434 if (parameters.has_time_limit()) {
435 const int64_t time_limit_ms = absl::ToInt64Milliseconds(
436 util_time::DecodeGoogleApiProto(parameters.time_limit()).value());
437 glpk_parameters.tm_lim = static_cast<int>(std::min(
438 static_cast<int64_t>(std::numeric_limits<int>::max()), time_limit_ms));
439 }
440}
441
442// Sets the LP specific parameters and returns an InvalidArgumentError for
443// invalid parameters or parameter values.
444absl::Status SetLPParameters(const SolveParametersProto& parameters,
445 glp_smcp& glpk_parameters) {
446 std::vector<std::string> warnings;
447 if (parameters.has_iteration_limit()) {
448 int limit = static_cast<int>(std::min<int64_t>(
449 std::numeric_limits<int>::max(), parameters.iteration_limit()));
450 glpk_parameters.it_lim = limit;
451 }
452 switch (parameters.presolve()) {
454 // Keep the default.
455 //
456 // TODO(b/187027049): default is off, which may be surprising for users.
457 break;
458 case EMPHASIS_OFF:
459 glpk_parameters.presolve = GLP_OFF;
460 break;
461 default:
462 glpk_parameters.presolve = GLP_ON;
463 break;
464 }
465 switch (parameters.lp_algorithm()) {
467 break;
469 glpk_parameters.meth = GLP_PRIMAL;
470 break;
472 // Use GLP_DUALP to switch back to primal simplex if the dual simplex
473 // fails.
474 //
475 // TODO(b/187027049): GLPK also supports GLP_DUAL to only try dual
476 // simplex. We should have an option to support it.
477 glpk_parameters.meth = GLP_DUALP;
478 break;
479 default:
480 warnings.push_back(absl::StrCat(
481 "GLPK does not support ",
482 operations_research::ProtoEnumToString(parameters.lp_algorithm()),
483 " for parameters.lp_algorithm"));
484 break;
485 }
486 if (!warnings.empty()) {
487 return absl::InvalidArgumentError(absl::StrJoin(warnings, "; "));
488 }
489 return absl::OkStatus();
490}
491
492class MipCallbackData {
493 public:
494 explicit MipCallbackData(
495 const SolveInterrupter* absl_nullable const interrupter)
496 : interrupter_(interrupter) {}
497
498 void Callback(glp_tree* const tree) {
499 // We only update the best bound on some specific events since it makes a
500 // traversal of all active nodes.
501 switch (glp_ios_reason(tree)) {
502 case GLP_ISELECT:
503 // The ISELECT call is the first one that happens after a node has been
504 // split on two sub-nodes (IBRANCH) with updated `bound`s based on the
505 // integer value of the branched variable.
506 case GLP_IBINGO:
507 // We found a new integer solution, the `bound` has been updated.
508 case GLP_IROWGEN:
509 // The IROWGEN call is the first one that happens on a current node
510 // after the relaxed problem has been solved and the `bound` field
511 // updated.
512 //
513 // Note that the model/cut pool changes done in IROWGEN and ICUTGEN have
514 // no influence on the `bound` and IROWGEN is the first call to happen.
515 if (const int best_node = glp_ios_best_node(tree); best_node != 0) {
516 best_bound_ = glp_ios_node_bound(tree, best_node);
517 }
518 break;
519 default:
520 // We can ignore:
521 // - IPREPRO: since the `bound` of the current node has not been
522 // computed yet.
523 // - IHEUR: since we have IBINGO if the integer solution is better.
524 // - ICUTGEN: since the `bound` is not updated with the rows added at
525 // IROWGEN so we would get the same best bound.
526 // - IBRANCH: since the sub-nodes will be created after that and their
527 // `bound`s taken into account at ISELECT.
528 break;
529 }
530 if (interrupter_ != nullptr && interrupter_->IsInterrupted()) {
531 glp_ios_terminate(tree);
532 interrupted_by_interrupter_ = true;
533 return;
534 }
535 }
536
537 bool HasBeenInterruptedByInterrupter() const {
538 return interrupted_by_interrupter_.load();
539 }
540
541 std::optional<double> best_bound() const { return best_bound_; }
542
543 private:
544 // Optional interrupter.
545 const SolveInterrupter* absl_nullable const interrupter_;
546
547 // Set to true if glp_ios_terminate() has been called due to the interrupter.
548 std::atomic<bool> interrupted_by_interrupter_ = false;
549
550 // Set on each callback that may update the best bound.
551 std::optional<double> best_bound_;
552};
553
554void MipCallback(glp_tree* const tree, void* const info) {
555 static_cast<MipCallbackData*>(info)->Callback(tree);
556}
557
558// Returns the MathOpt ids of the rows/columns with lower_bound > upper_bound.
559//
560// For variables we use the unrounded bounds as we don't want to return a
561// failing status when rounded bounds of integer variables cross due to the
562// rounding. See EmptyIntegerBoundsResult() for dealing with this case.
563InvertedBounds ListInvertedBounds(
564 glp_prob* const problem, absl::Span<const int64_t> variable_ids,
565 absl::Span<const double> unrounded_variable_lower_bounds,
566 absl::Span<const double> unrounded_variable_upper_bounds,
567 absl::Span<const int64_t> linear_constraint_ids) {
568 InvertedBounds inverted_bounds;
569
570 const int num_cols = glp_get_num_cols(problem);
571 for (int c = 1; c <= num_cols; ++c) {
572 if (unrounded_variable_lower_bounds[c - 1] >
573 unrounded_variable_upper_bounds[c - 1]) {
574 inverted_bounds.variables.push_back(variable_ids[c - 1]);
575 }
576 }
577
578 const int num_rows = glp_get_num_rows(problem);
579 for (int r = 1; r <= num_rows; ++r) {
580 if (glp_get_row_lb(problem, r) > glp_get_row_ub(problem, r)) {
581 inverted_bounds.linear_constraints.push_back(
582 linear_constraint_ids[r - 1]);
583 }
584 }
585
586 return inverted_bounds;
587}
588
589// Returns the termination reason based on the current MIP data of the problem
590// assuming that the last call to glp_intopt() returned 0 and that the model has
591// not been modified since.
592absl::StatusOr<TerminationProto> MipTerminationOnSuccess(
593 glp_prob* const problem, MipCallbackData* const mip_cb_data) {
594 if (mip_cb_data == nullptr) {
595 return absl::InternalError(
596 "MipTerminationOnSuccess() called with nullptr mip_cb_data");
597 }
598 const int status = glp_mip_status(problem);
599 const bool is_maximize = glp_get_obj_dir(problem) == GLP_MAX;
600 switch (status) {
601 case GLP_OPT:
602 case GLP_FEAS: {
603 const double objective_value = glp_mip_obj_val(problem);
604 if (status == GLP_OPT) {
605 // Note that here we don't use MipCallbackData->best_bound(), even if
606 // set, as if the Gap was used to interrupt the solve GLPK is supposed
607 // to return GLP_EMIPGAP and not 0. And thus we should not go through
608 // this code path if the Gap limit is used.
609 return OptimalTerminationProto(objective_value, objective_value);
610 }
612 is_maximize, LIMIT_UNDETERMINED, objective_value,
613 mip_cb_data->best_bound(), "glp_mip_status() returned GLP_FEAS");
614 }
615 case GLP_NOFEAS:
616 // According to InfeasibleTerminationProto()'s documentation: "the
617 // convention for infeasible MIPs is that dual_feasibility_status is
618 // feasible".
620 is_maximize, /*dual_feasibility_status=*/FEASIBILITY_STATUS_FEASIBLE);
621 default:
622 return absl::InternalError(
623 absl::StrCat("glp_intopt() returned 0 but glp_mip_status()"
624 "returned the unexpected value ",
625 SolutionStatusString(status)));
626 }
627}
628
629// Returns the termination reason based on the current interior point data of
630// the problem assuming that the last call to glp_interior() returned 0 and that
631// the model has not been modified since.
632absl::StatusOr<TerminationProto> InteriorTerminationOnSuccess(
633 glp_prob* const problem, MipCallbackData*) {
634 const int status = glp_ipt_status(problem);
635 const bool is_maximize = glp_get_obj_dir(problem) == GLP_MAX;
636 switch (status) {
637 case GLP_OPT: {
638 const double objective_value = glp_ipt_obj_val(problem);
639 // TODO(b/290359402): here we assume that the objective value of the dual
640 // is exactly the same as the one of the primal. This may not be true as
641 // some tolerance may apply.
642 return OptimalTerminationProto(objective_value, objective_value);
643 }
644 case GLP_INFEAS:
646 is_maximize, LIMIT_UNDETERMINED,
647 /*optional_dual_objective=*/std::nullopt,
648 "glp_ipt_status() returned GLP_INFEAS");
649 case GLP_NOFEAS:
650 // Documentation in glpapi08.c for glp_ipt_status says this status means
651 // "no feasible solution exists", but the Reference Manual for GLPK
652 // Version 5.0 clarifies that it means "no feasible primal-dual solution
653 // exists." (See also the comment in glpipm.c when ipm_solve returns 1).
654 // Hence, GLP_NOFEAS corresponds to the solver claiming that either the
655 // primal problem, the dual problem (or both) are infeasible. Under this
656 // condition if the primal is feasible, then the dual must be infeasible
657 // and hence the primal is unbounded.
659 is_maximize,
660 /*dual_feasibility_status=*/FEASIBILITY_STATUS_UNDETERMINED);
661 default:
662 return absl::InternalError(
663 absl::StrCat("glp_interior() returned 0 but glp_ipt_status()"
664 "returned the unexpected value ",
665 SolutionStatusString(status)));
666 }
667}
668
669// Returns the termination reason based on the current interior point data of
670// the problem assuming that the last call to glp_simplex() returned 0 and that
671// the model has not been modified since.
672absl::StatusOr<TerminationProto> SimplexTerminationOnSuccess(
673 glp_prob* const problem, MipCallbackData*) {
674 // Here we don't use glp_get_status() since it is biased towards the primal
675 // simplex algorithm. For example if the dual simplex returns GLP_NOFEAS for
676 // the dual and GLP_INFEAS for the primal then glp_get_status() returns
677 // GLP_INFEAS. This is misleading since the dual successfully determined that
678 // the problem was dual infeasible. So here we use the two statuses of the
679 // primal and the dual to get a better result (the glp_get_status() only
680 // combines them anyway, it does not have any other benefit).
681 const int prim_status = glp_get_prim_stat(problem);
682 const int dual_status = glp_get_dual_stat(problem);
683 const bool is_maximize = glp_get_obj_dir(problem) == GLP_MAX;
684
685 // Returns a status error indicating that glp_get_dual_stat() returned an
686 // unexpected value.
687 const auto unexpected_dual_stat = [&]() -> absl::Status {
689 << "glp_simplex() returned 0 but glp_get_dual_stat() returned the "
690 "unexpected value "
691 << SolutionStatusString(dual_status)
692 << " while glp_get_prim_stat() returned "
693 << SolutionStatusString(prim_status);
694 };
695
696 switch (prim_status) {
697 case GLP_FEAS:
698 switch (dual_status) {
699 case GLP_FEAS: {
700 // Dual feasibility here means that the solution is dual feasible
701 // (correct signs of the residual costs) and that the complementary
702 // slackness condition are respected. Hence the solution is optimal.
703 const double objective_value = glp_get_obj_val(problem);
704 return OptimalTerminationProto(objective_value, objective_value);
705 }
706 case GLP_NOFEAS:
707 return UnboundedTerminationProto(is_maximize);
708 case GLP_INFEAS:
709 default:
710 return unexpected_dual_stat();
711 }
712 case GLP_INFEAS:
713 switch (dual_status) {
714 case GLP_NOFEAS:
716 is_maximize,
717 /*dual_feasibility_status=*/FEASIBILITY_STATUS_INFEASIBLE);
718 case GLP_FEAS:
719 case GLP_INFEAS:
720 default:
721 return unexpected_dual_stat();
722 }
723 case GLP_NOFEAS:
724 switch (dual_status) {
725 case GLP_FEAS:
726 // Dual being feasible (GLP_FEAS) here would lead to dual unbounded;
727 // but this does not exist as a reason.
729 is_maximize,
730 /*dual_feasibility_status=*/FEASIBILITY_STATUS_FEASIBLE);
731 case GLP_INFEAS:
733 is_maximize,
734 /*dual_feasibility_status=*/FEASIBILITY_STATUS_UNDETERMINED);
735 return unexpected_dual_stat();
736 case GLP_NOFEAS:
737 // If both the primal and dual are proven infeasible (GLP_NOFEAS), the
738 // primal wins. Maybe GLPK does never return that though since it
739 // implements either primal or dual simplex algorithm but does not
740 // combine both of them.
742 is_maximize,
743 /*dual_feasibility_status=*/FEASIBILITY_STATUS_INFEASIBLE);
744 default:
745 return unexpected_dual_stat();
746 }
747 default:
748 return absl::InternalError(
749 absl::StrCat("glp_simplex() returned 0 but glp_get_prim_stat() "
750 "returned the unexpected value ",
751 SolutionStatusString(prim_status)));
752 }
753}
754
755// Function called by BuildTermination() when the return code of the solve
756// function is 0.
757//
758// Parameter mip_cb_data is not nullptr iff glp_intopt() was used.
759using TerminationOnSuccessFn = std::function<absl::StatusOr<TerminationProto>(
760 glp_prob* problem, MipCallbackData* const mip_cb_data)>;
761
762// Returns the termination reason based on the return code rc of calling fn_name
763// function which is glp_simplex(), glp_interior() or glp_intopt().
764//
765// For return code 0 which means successful solve, the function
766// termination_on_success is called to build the termination. Other return
767// values (errors) are dealt with specifically.
768//
769// For glp_intopt(), the optional mip_cb_data is used to test for interruption
770// and the LIMIT_INTERRUPTED is set if the interrupter has been triggered (even
771// if the return code is 0). It is also used to get the dual bound. The
772// gap_limit must also be set to the gap limit parameter.
773//
774// When a primal feasible solution exists, feasible_solution_objective_value
775// must be set with its objective. When this variable is unset this function
776// assumes no such solution exists.
777absl::StatusOr<TerminationProto> BuildTermination(
778 glp_prob* const problem, const absl::string_view fn_name, const int rc,
779 const TerminationOnSuccessFn termination_on_success,
780 MipCallbackData* const mip_cb_data,
781 const std::optional<double> feasible_solution_objective_value,
782 const double gap_limit) {
783 const bool is_maximize = glp_get_obj_dir(problem) == GLP_MAX;
784 if (mip_cb_data != nullptr &&
785 mip_cb_data->HasBeenInterruptedByInterrupter()) {
786 return LimitTerminationProto(is_maximize, LIMIT_INTERRUPTED,
787 feasible_solution_objective_value);
788 }
789
790 // TODO(b/187027049): see if GLP_EOBJLL and GLP_EOBJUL should be handled with
791 // dual simplex.
792 switch (rc) {
793 case 0:
794 return termination_on_success(problem, mip_cb_data);
795 case GLP_EBOUND: {
796 // GLP_EBOUND is returned when a variable or a constraint has the GLP_DB
797 // bounds type and lower_bound >= upper_bound. The code in this file makes
798 // sure we don't use GLP_DB but GLP_FX when lower_bound == upper_bound
799 // thus we expect GLP_EBOUND only when lower_bound > upper_bound. This
800 // should never happen as we call ListInvertedBounds() and
801 // EmptyIntegerBoundsResult() before we call GLPK. Thus we don't expect
802 // GLP_EBOUND to happen.
804 << fn_name << "() returned `" << ReturnCodeString(rc)
805 << "` but the model does not contain variables with inverted "
806 "bounds";
807 }
808 case GLP_EITLIM:
809 return LimitTerminationProto(is_maximize, LIMIT_ITERATION,
810 feasible_solution_objective_value);
811 case GLP_ETMLIM:
812 return LimitTerminationProto(is_maximize, LIMIT_TIME,
813 feasible_solution_objective_value);
814 case GLP_EMIPGAP: {
815 if (!feasible_solution_objective_value.has_value()) {
817 << fn_name << "() returned `" << ReturnCodeString(rc)
818 << "` but glp_mip_status() returned "
819 << SolutionStatusString(glp_mip_status(problem));
820 }
821 if (!mip_cb_data) {
823 << fn_name << "() returned `" << ReturnCodeString(rc)
824 << "` but there is no MipCallbackData";
825 }
826 const double objective_value = feasible_solution_objective_value.value();
827 // Here we expect mip_cb_data->best_bound() to always be set. If this is
828 // not the case we use a worst estimation of the dual bound.
830 objective_value,
831 mip_cb_data->best_bound().value_or(
832 WorstGLPKDualBound(is_maximize, objective_value, gap_limit)));
833 }
834 case GLP_ESTOP:
835 return LimitTerminationProto(is_maximize, LIMIT_INTERRUPTED,
836 feasible_solution_objective_value);
837 case GLP_ENOPFS:
838 // With presolve on, this error is returned if the LP has no feasible
839 // solution.
841 is_maximize,
842 /*dual_feasibility_status=*/FEASIBILITY_STATUS_UNDETERMINED);
843 case GLP_ENODFS:
844 // With presolve on, this error is returned if the LP has no dual
845 // feasible solution.
847 is_maximize,
848 /*dual_feasibility_status=*/FEASIBILITY_STATUS_INFEASIBLE);
849 case GLP_ENOCVG:
850 // Very slow convergence/divergence (for glp_interior).
851 return LimitTerminationProto(is_maximize, LIMIT_SLOW_PROGRESS,
852 feasible_solution_objective_value);
853 case GLP_EINSTAB:
854 // Numeric stability solving Newtonian system (for glp_interior).
855 return TerminateForReason(
857 absl::StrCat(fn_name, "() returned ", ReturnCodeString(rc),
858 " which means that there is a numeric stability issue "
859 "solving Newtonian system"));
860 default:
862 << fn_name
863 << "() returned unexpected value: " << ReturnCodeString(rc);
864 }
865}
866
867// Callback for glp_term_hook().
868//
869// It expects `info` to be a pointer on a TermHookData.
870int TermHook(void* const info, const char* const message) {
871 static_cast<BufferedMessageCallback*>(info)->OnMessage(message);
872
873 // Returns non-zero to remove any terminal output.
874 return 1;
875}
876
877// Returns the objective offset. This is used as a placeholder for function
878// returning the objective value for solve method not supporting solving empty
879// models (glp_exact() and glp_interior()).
880double OffsetOnlyObjVal(glp_prob* const problem) {
881 return glp_get_obj_coef(problem, 0);
882}
883
884// Returns GLP_OPT. This is used as a placeholder for function returning the
885// status for solve method not supporting solving empty models (glp_exact() and
886// glp_interior()).
887int OptStatus(glp_prob*) { return GLP_OPT; }
888
889} // namespace
890
891absl::StatusOr<std::unique_ptr<SolverInterface>> GlpkSolver::New(
892 const ModelProto& model, const InitArgs& /*init_args*/) {
893 RETURN_IF_ERROR(ModelIsSupported(model, kGlpkSupportedStructures, "GLPK"));
894 return absl::WrapUnique(new GlpkSolver(model));
895}
896
897GlpkSolver::GlpkSolver(const ModelProto& model)
898 : thread_id_(std::this_thread::get_id()), problem_(glp_create_prob()) {
899 // Make sure glp_free_env() is called at the exit of the current thread.
901
902 glp_set_prob_name(problem_, TruncateAndQuoteGLPKName(model.name()).c_str());
903
904 AddVariables(model.variables());
905
906 AddLinearConstraints(model.linear_constraints());
907
908 glp_set_obj_dir(problem_, model.objective().maximize() ? GLP_MAX : GLP_MIN);
909 // Glpk uses index 0 for the "shift" of the objective.
910 glp_set_obj_coef(problem_, 0, model.objective().offset());
911 for (const auto [v, coeff] :
913 const int col_index = variables_.id_to_index.at(v);
914 CHECK_EQ(variables_.ids[col_index - 1], v);
915 glp_set_obj_coef(problem_, col_index, coeff);
916 }
917
918 const SparseDoubleMatrixProto& proto_matrix =
920 glp_load_matrix(
921 problem_, proto_matrix.row_ids_size(),
922 MatrixIds(proto_matrix.row_ids(), linear_constraints_.id_to_index).data(),
923 MatrixIds(proto_matrix.column_ids(), variables_.id_to_index).data(),
924 MatrixCoefficients(proto_matrix.coefficients()).data());
925}
926
928 // Here we simply log an error but glp_delete_prob() should crash with an
929 // error like: `glp_free: memory allocation error`.
930 if (const absl::Status status = CheckCurrentThread(); !status.ok()) {
931 LOG(ERROR) << status;
932 }
933 glp_delete_prob(problem_);
934}
935
936namespace {
937
938ProblemStatusProto GetMipProblemStatusProto(const int rc, const int mip_status,
939 const bool has_finite_dual_bound) {
940 ProblemStatusProto problem_status;
943
944 switch (rc) {
945 case GLP_ENOPFS:
947 return problem_status;
948 case GLP_ENODFS:
950 return problem_status;
951 }
952
953 switch (mip_status) {
954 case GLP_OPT:
957 return problem_status;
958 case GLP_FEAS:
960 break;
961 case GLP_NOFEAS:
963 break;
964 }
965
966 if (has_finite_dual_bound) {
968 }
969 return problem_status;
970}
971
972absl::StatusOr<FeasibilityStatusProto> TranslateProblemStatus(
973 const int glpk_status, const absl::string_view fn_name) {
974 switch (glpk_status) {
975 case GLP_FEAS:
977 case GLP_NOFEAS:
979 case GLP_INFEAS:
980 case GLP_UNDEF:
982 default:
983 return absl::InternalError(
984 absl::StrCat(fn_name, " returned the unexpected value ",
985 SolutionStatusString(glpk_status)));
986 }
987}
988
989// Builds problem status from:
990// * glp_simplex_rc: code returned by glp_simplex.
991// * glpk_primal_status: primal status returned by glp_get_prim_stat.
992// * glpk_dual_status: dual status returned by glp_get_dual_stat.
993absl::StatusOr<ProblemStatusProto> GetSimplexProblemStatusProto(
994 const int glp_simplex_rc, const int glpk_primal_status,
995 const int glpk_dual_status) {
996 ProblemStatusProto problem_status;
998 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
999
1000 switch (glp_simplex_rc) {
1001 case GLP_ENOPFS:
1002 // LP presolver concluded primal infeasibility.
1003 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
1004 return problem_status;
1005 case GLP_ENODFS:
1006 // LP presolver concluded dual infeasibility.
1007 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
1008 return problem_status;
1009 default: {
1010 // Get primal status from basic solution.
1012 const FeasibilityStatusProto primal_status,
1013 TranslateProblemStatus(glpk_primal_status, "glp_get_prim_stat"));
1014 problem_status.set_primal_status(primal_status);
1015
1016 // Get dual status from basic solution.
1018 const FeasibilityStatusProto dual_status,
1019 TranslateProblemStatus(glpk_dual_status, "glp_get_dual_stat"));
1020 problem_status.set_dual_status(dual_status);
1021 return problem_status;
1022 }
1023 }
1024}
1025
1026absl::StatusOr<ProblemStatusProto> GetBarrierProblemStatusProto(
1027 const int glp_interior_rc, const int ipt_status) {
1028 ProblemStatusProto problem_status;
1030 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
1031
1032 switch (glp_interior_rc) {
1033 case 0:
1034 // We only use the glp_ipt_status() result when glp_interior() returned 0.
1035 switch (ipt_status) {
1036 case GLP_OPT:
1037 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
1038 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
1039 return problem_status;
1040 case GLP_INFEAS:
1041 return problem_status;
1042 case GLP_NOFEAS:
1043 problem_status.set_primal_or_dual_infeasible(true);
1044 return problem_status;
1045 case GLP_UNDEF:
1046 return problem_status;
1047 default:
1048 return absl::InternalError(
1049 absl::StrCat("glp_ipt_status returned the unexpected value ",
1050 SolutionStatusString(ipt_status)));
1051 }
1052 default:
1053 return problem_status;
1054 }
1055}
1056
1057} // namespace
1058
1059absl::StatusOr<SolveResultProto> GlpkSolver::Solve(
1060 const SolveParametersProto& parameters,
1061 const ModelSolveParametersProto& model_parameters,
1062 MessageCallback message_cb,
1063 const CallbackRegistrationProto& callback_registration,
1064 const Callback /*cb*/,
1065 const SolveInterrupter* absl_nullable const interrupter) {
1067 model_parameters, kGlpkSupportedStructures, "GLPK"));
1068 RETURN_IF_ERROR(CheckCurrentThread());
1069
1070 const absl::Time start = absl::Now();
1071
1072 const auto set_solve_time =
1073 [&start](SolveResultProto& result) -> absl::Status {
1075 absl::Now() - start,
1076 result.mutable_solve_stats()->mutable_solve_time()))
1077 << "failed to set SolveResultProto.solve_stats.solve_time";
1078 return absl::OkStatus();
1079 };
1080
1082 ListInvertedBounds(
1083 problem_,
1084 /*variable_ids=*/variables_.ids,
1085 /*unrounded_variable_lower_bounds=*/variables_.unrounded_lower_bounds,
1086 /*unrounded_variable_upper_bounds=*/variables_.unrounded_upper_bounds,
1087 /*linear_constraint_ids=*/linear_constraints_.ids)
1088 .ToStatus());
1089
1090 // Deal with empty integer bounds that result in inverted bounds due to bounds
1091 // rounding.
1092 { // Limit scope of `result`.
1093 std::optional<SolveResultProto> result = EmptyIntegerBoundsResult();
1094 if (result.has_value()) {
1095 RETURN_IF_ERROR(set_solve_time(result.value()));
1096 return std::move(result).value();
1097 }
1098 }
1099
1100 RETURN_IF_ERROR(CheckRegisteredCallbackEvents(callback_registration,
1101 /*supported_events=*/{}));
1102
1103 BufferedMessageCallback term_hook_data(std::move(message_cb));
1104 if (term_hook_data.has_user_message_callback()) {
1105 // Note that glp_term_hook() uses get_env_ptr() that relies on thread local
1106 // storage to have a different environment per thread. Thus using
1107 // glp_term_hook() is thread-safe.
1108 //
1109 glp_term_hook(TermHook, &term_hook_data);
1110 }
1111
1112 // We must reset the term hook when before exiting or before flushing the last
1113 // unfinished line.
1114 auto message_cb_cleanup = absl::MakeCleanup([&]() {
1115 if (term_hook_data.has_user_message_callback()) {
1116 glp_term_hook(/*func=*/nullptr, /*info=*/nullptr);
1117 term_hook_data.Flush();
1118 }
1119 });
1120
1121 SolveResultProto result;
1122
1123 const bool is_mip = IsMip(problem_);
1124
1125 // We need to use different functions depending on the solve function we used
1126 // (or placeholders if no solve function was called in case of empty models).
1127 int (*get_prim_stat)(glp_prob*) = nullptr;
1128 double (*obj_val)(glp_prob*) = nullptr;
1129 double (*col_val)(glp_prob*, int) = nullptr;
1130
1131 int (*get_dual_stat)(glp_prob*) = nullptr;
1132 double (*row_dual)(glp_prob*, int) = nullptr;
1133 double (*col_dual)(glp_prob*, int) = nullptr;
1134
1135 const bool maximize = glp_get_obj_dir(problem_) == GLP_MAX;
1136 double best_dual_bound = maximize ? kInf : -kInf;
1137
1138 // Here we use different solve algorithms depending on the type of problem:
1139 // * For MIPs: glp_intopt()
1140 // * For LPs:
1141 // * glp_interior() when using BARRIER LP algorithm
1142 // * glp_simplex() for other LP algorithms.
1143 //
1144 // These solve algorithms have dedicated data segments in glp_prob which use
1145 // different access functions to get the solution; hence each branch will set
1146 // the corresponding function pointers accordingly. They also use a custom
1147 // struct for parameters that will be initialized and passed to the algorithm.
1148 if (is_mip) {
1149 get_prim_stat = glp_mip_status;
1150 obj_val = glp_mip_obj_val;
1151 col_val = glp_mip_col_val;
1152
1153 glp_iocp glpk_parameters;
1154 glp_init_iocp(&glpk_parameters);
1155 RETURN_IF_ERROR(SetSharedParameters(
1156 parameters, term_hook_data.has_user_message_callback(),
1157 glpk_parameters));
1158 SetTimeLimitParameter(parameters, glpk_parameters);
1159 // TODO(b/187027049): glp_intopt with presolve off requires an optional
1160 // solution of the relaxed problem. Here we simply always enable pre-solve
1161 // but we should support disabling the presolve and call glp_simplex() in
1162 // that case.
1163 glpk_parameters.presolve = GLP_ON;
1164 if (parameters.presolve() != EMPHASIS_UNSPECIFIED) {
1166 << "parameter presolve not supported by GLPK for MIP";
1167 }
1168 if (parameters.has_relative_gap_tolerance()) {
1169 glpk_parameters.mip_gap = parameters.relative_gap_tolerance();
1170 }
1171 if (parameters.has_absolute_gap_tolerance()) {
1173 << "parameter absolute_gap_tolerance not supported by GLPK "
1174 "(relative_gap_tolerance is supported)";
1175 }
1176 if (parameters.has_iteration_limit()) {
1178 << "parameter iteration_limit not supported by GLPK for MIP";
1179 }
1180 if (parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
1182 << "parameter lp_algorithm not supported by GLPK for MIP";
1183 }
1184 MipCallbackData mip_cb_data(interrupter);
1185 glpk_parameters.cb_func = MipCallback;
1186 glpk_parameters.cb_info = &mip_cb_data;
1187 const int rc = glp_intopt(problem_, &glpk_parameters);
1188 const int mip_status = glp_mip_status(problem_);
1189 const bool has_feasible_solution =
1190 mip_status == GLP_OPT || mip_status == GLP_FEAS;
1191 const std::optional<double> feasible_solution_objective_value =
1192 has_feasible_solution ? std::make_optional(glp_mip_obj_val(problem_))
1193 : std::nullopt;
1195 *result.mutable_termination(),
1196 BuildTermination(problem_, "glp_intopt", rc, MipTerminationOnSuccess,
1197 &mip_cb_data, feasible_solution_objective_value,
1198 glpk_parameters.mip_gap));
1199 if (mip_cb_data.best_bound().has_value()) {
1200 best_dual_bound = *mip_cb_data.best_bound();
1201 }
1202 *result.mutable_solve_stats()->mutable_problem_status() =
1203 GetMipProblemStatusProto(rc, mip_status,
1204 std::isfinite(best_dual_bound));
1205 } else {
1206 if (parameters.lp_algorithm() == LP_ALGORITHM_BARRIER) {
1207 get_prim_stat = glp_ipt_status;
1208 obj_val = glp_ipt_obj_val;
1209 col_val = glp_ipt_col_prim;
1210
1211 get_dual_stat = glp_ipt_status;
1212 row_dual = glp_ipt_row_dual;
1213 col_dual = glp_ipt_col_dual;
1214
1215 glp_iptcp glpk_parameters;
1216 glp_init_iptcp(&glpk_parameters);
1217 if (parameters.has_time_limit()) {
1218 return absl::InvalidArgumentError(
1219 "parameter time_limit not supported by GLPK for interior point "
1220 "algorithm");
1221 }
1222 RETURN_IF_ERROR(SetSharedParameters(
1223 parameters, term_hook_data.has_user_message_callback(),
1224 glpk_parameters));
1225
1226 // glp_interior() does not support being called with an empty model and
1227 // returns GLP_EFAIL. Thus we use placeholders in that case.
1228 //
1229 // TODO(b/259557110): the emptiness is tested by glp_interior() *after*
1230 // some pre-processing (including removing fixed variables). The current
1231 // IsEmpty() is thus not good enough to deal with all cases.
1232 if (IsEmpty(problem_)) {
1233 get_prim_stat = OptStatus;
1234 get_dual_stat = OptStatus;
1235 obj_val = OffsetOnlyObjVal;
1236 const double objective_value = OffsetOnlyObjVal(problem_);
1237 *result.mutable_termination() = OptimalTerminationProto(
1238 /*finite_primal_objective=*/objective_value,
1239 /*dual_objective=*/objective_value,
1240 "glp_interior() not called since the model is empty");
1241 result.mutable_solve_stats()
1242 ->mutable_problem_status()
1243 ->set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
1244 result.mutable_solve_stats()->mutable_problem_status()->set_dual_status(
1246 } else {
1247 // TODO(b/187027049): add solver specific parameters for
1248 // glp_iptcp.ord_alg.
1249 const int glp_interior_rc = glp_interior(problem_, &glpk_parameters);
1250 const int ipt_status = glp_ipt_status(problem_);
1251 const bool has_feasible_solution = ipt_status == GLP_OPT;
1252 const std::optional<double> feasible_solution_objective_value =
1253 has_feasible_solution
1254 ? std::make_optional(glp_ipt_obj_val(problem_))
1255 : std::nullopt;
1257 *result.mutable_termination(),
1258 BuildTermination(problem_, "glp_interior", glp_interior_rc,
1259 InteriorTerminationOnSuccess,
1260 /*mip_cb_data=*/nullptr,
1261 feasible_solution_objective_value,
1262 /*gap_limit=*/kNaN));
1264 *result.mutable_solve_stats()->mutable_problem_status(),
1265 GetBarrierProblemStatusProto(/*glp_interior_rc=*/glp_interior_rc,
1266 /*ipt_status=*/ipt_status));
1267 }
1268 } else {
1269 get_prim_stat = glp_get_prim_stat;
1270 obj_val = glp_get_obj_val;
1271 col_val = glp_get_col_prim;
1272
1273 get_dual_stat = glp_get_dual_stat;
1274 row_dual = glp_get_row_dual;
1275 col_dual = glp_get_col_dual;
1276
1277 glp_smcp glpk_parameters;
1278 glp_init_smcp(&glpk_parameters);
1279 RETURN_IF_ERROR(SetSharedParameters(
1280 parameters, term_hook_data.has_user_message_callback(),
1281 glpk_parameters));
1282 SetTimeLimitParameter(parameters, glpk_parameters);
1283 RETURN_IF_ERROR(SetLPParameters(parameters, glpk_parameters));
1284
1285 // TODO(b/187027049): add option to use glp_exact().
1286 const int glp_simplex_rc = glp_simplex(problem_, &glpk_parameters);
1287 const int prim_stat = glp_get_prim_stat(problem_);
1288 const bool has_feasible_solution = prim_stat == GLP_FEAS;
1289 const std::optional<double> feasible_solution_objective_value =
1290 has_feasible_solution ? std::make_optional(glp_get_obj_val(problem_))
1291 : std::nullopt;
1292 ASSIGN_OR_RETURN(*result.mutable_termination(),
1293 BuildTermination(problem_, "glp_simplex", glp_simplex_rc,
1294 SimplexTerminationOnSuccess,
1295 /*mip_cb_data=*/nullptr,
1296 feasible_solution_objective_value,
1297 /*gap_limit=*/kNaN));
1298
1299 // If the primal is proven infeasible and the dual is feasible, the dual
1300 // is unbounded. Thus we can compute a better dual bound rather than the
1301 // default value.
1302 if (glp_get_prim_stat(problem_) == GLP_NOFEAS &&
1303 glp_get_dual_stat(problem_) == GLP_FEAS) {
1304 best_dual_bound = maximize ? -kInf : +kInf;
1305 }
1306
1307 ASSIGN_OR_RETURN(*result.mutable_solve_stats()->mutable_problem_status(),
1308 GetSimplexProblemStatusProto(
1309 /*glp_simplex_rc=*/glp_simplex_rc,
1310 /*glpk_primal_status=*/prim_stat,
1311 /*glpk_dual_status=*/glp_get_dual_stat(problem_)));
1312 VLOG(1) << "glp_get_status: "
1313 << SolutionStatusString(glp_get_status(problem_))
1314 << " glp_get_prim_stat: " << SolutionStatusString(prim_stat)
1315 << " glp_get_dual_stat: "
1316 << SolutionStatusString(glp_get_dual_stat(problem_));
1317 }
1318 }
1319
1320 // Unregister the callback and flush the potential last unfinished line.
1321 std::move(message_cb_cleanup).Invoke();
1322
1323 switch (result.termination().reason()) {
1326 result.mutable_solve_stats()->set_best_primal_bound(obj_val(problem_));
1327 break;
1329 // Here we can't use obj_val(problem_) as it would be a finite value of
1330 // the feasible solution found.
1331 result.mutable_solve_stats()->set_best_primal_bound(maximize ? +kInf
1332 : -kInf);
1333 break;
1334 default:
1335 result.mutable_solve_stats()->set_best_primal_bound(maximize ? -kInf
1336 : kInf);
1337 break;
1338 }
1339 // TODO(b/187027049): compute the dual value when the dual is feasible (or
1340 // problem optimal for interior point) based on the bounds and the dual values
1341 // for LPs.
1342 result.mutable_solve_stats()->set_best_dual_bound(best_dual_bound);
1344 AddPrimalSolution(get_prim_stat, obj_val, col_val, model_parameters,
1345 solution);
1346 if (!is_mip) {
1347 AddDualSolution(get_dual_stat, obj_val, row_dual, col_dual,
1348 model_parameters, solution);
1349 }
1350 if (solution.has_primal_solution() || solution.has_dual_solution() ||
1351 solution.has_basis()) {
1352 *result.add_solutions() = std::move(solution);
1353 }
1354 if (parameters.glpk().compute_unbound_rays_if_possible()) {
1355 RETURN_IF_ERROR(AddPrimalOrDualRay(model_parameters, result));
1356 }
1357
1358 RETURN_IF_ERROR(set_solve_time(result));
1359 return result;
1360}
1361
1362void GlpkSolver::AddVariables(const VariablesProto& new_variables) {
1363 if (new_variables.ids().empty()) {
1364 return;
1365 }
1366
1367 // Indices in GLPK are one-based.
1368 const int first_new_var_index = variables_.ids.size() + 1;
1369
1370 variables_.ids.insert(variables_.ids.end(), new_variables.ids().begin(),
1371 new_variables.ids().end());
1372 for (int v = 0; v < new_variables.ids_size(); ++v) {
1373 CHECK(variables_.id_to_index
1374 .try_emplace(new_variables.ids(v), first_new_var_index + v)
1375 .second);
1376 }
1377 glp_add_cols(problem_, new_variables.ids_size());
1378 if (!new_variables.names().empty()) {
1379 for (int v = 0; v < new_variables.names_size(); ++v) {
1380 glp_set_col_name(
1381 problem_, v + first_new_var_index,
1382 TruncateAndQuoteGLPKName(new_variables.names(v)).c_str());
1383 }
1384 }
1385 CHECK_EQ(new_variables.lower_bounds_size(),
1386 new_variables.upper_bounds_size());
1387 CHECK_EQ(new_variables.lower_bounds_size(), new_variables.ids_size());
1388 variables_.unrounded_lower_bounds.insert(
1389 variables_.unrounded_lower_bounds.end(),
1390 new_variables.lower_bounds().begin(), new_variables.lower_bounds().end());
1391 variables_.unrounded_upper_bounds.insert(
1392 variables_.unrounded_upper_bounds.end(),
1393 new_variables.upper_bounds().begin(), new_variables.upper_bounds().end());
1394 for (int i = 0; i < new_variables.lower_bounds_size(); ++i) {
1395 // Here we don't use the boolean "kind" GLP_BV since it does not exist. It
1396 // is an artifact of glp_(get|set)_col_kind() functions. When
1397 // glp_set_col_kind() is called with GLP_BV, in addition to setting the kind
1398 // to GLP_IV (integer) it also sets the bounds to [0,1]. Symmetrically
1399 // glp_get_col_kind() returns GLP_BV when the kind is GLP_IV and the bounds
1400 // are [0,1].
1401 glp_set_col_kind(problem_, i + first_new_var_index,
1402 new_variables.integers(i) ? GLP_IV : GLP_CV);
1403 SetBounds<Variables>(problem_, /*k=*/i + first_new_var_index,
1404 {.lower = new_variables.lower_bounds(i),
1405 .upper = new_variables.upper_bounds(i)});
1406 }
1407}
1408
1409void GlpkSolver::AddLinearConstraints(
1410 const LinearConstraintsProto& new_linear_constraints) {
1411 if (new_linear_constraints.ids().empty()) {
1412 return;
1413 }
1414
1415 // Indices in GLPK are one-based.
1416 const int first_new_cstr_index = linear_constraints_.ids.size() + 1;
1417
1418 linear_constraints_.ids.insert(linear_constraints_.ids.end(),
1419 new_linear_constraints.ids().begin(),
1420 new_linear_constraints.ids().end());
1421 for (int c = 0; c < new_linear_constraints.ids_size(); ++c) {
1422 CHECK(linear_constraints_.id_to_index
1423 .try_emplace(new_linear_constraints.ids(c),
1424 first_new_cstr_index + c)
1425 .second);
1426 }
1427 glp_add_rows(problem_, new_linear_constraints.ids_size());
1428 if (!new_linear_constraints.names().empty()) {
1429 for (int c = 0; c < new_linear_constraints.names_size(); ++c) {
1430 glp_set_row_name(
1431 problem_, c + first_new_cstr_index,
1432 TruncateAndQuoteGLPKName(new_linear_constraints.names(c)).c_str());
1433 }
1434 }
1435 CHECK_EQ(new_linear_constraints.lower_bounds_size(),
1436 new_linear_constraints.upper_bounds_size());
1437 for (int i = 0; i < new_linear_constraints.lower_bounds_size(); ++i) {
1438 SetBounds<LinearConstraints>(
1439 problem_, /*k=*/i + first_new_cstr_index,
1440 {.lower = new_linear_constraints.lower_bounds(i),
1441 .upper = new_linear_constraints.upper_bounds(i)});
1442 }
1443}
1444
1445void GlpkSolver::UpdateObjectiveCoefficients(
1446 const SparseDoubleVectorProto& coefficients_proto) {
1447 for (const auto [id, coeff] : MakeView(coefficients_proto)) {
1448 const int col_index = variables_.id_to_index.at(id);
1449 CHECK_EQ(variables_.ids[col_index - 1], id);
1450 glp_set_obj_coef(problem_, col_index, coeff);
1451 }
1452}
1453
1454void GlpkSolver::UpdateLinearConstraintMatrix(
1455 const SparseDoubleMatrixProto& matrix_updates,
1456 const std::optional<int64_t> first_new_var_id,
1457 const std::optional<int64_t> first_new_cstr_id) {
1458 // GLPK's does not have an API to set matrix elements one by one. Instead it
1459 // can either update an entire row or update an entire column or load the
1460 // entire matrix. On top of that there is no API to get the entire matrix at
1461 // once.
1462 //
1463 // Hence to update existing coefficients we have to read rows (or columns)
1464 // coefficients, update existing non-zero that have been changed and add new
1465 // values and write back the result. For new rows and columns we can be more
1466 // efficient since we don't have to read the existing values back.
1467 //
1468 // The strategy used below is to split the matrix in three regions:
1469 //
1470 // existing new
1471 // columns columns
1472 // / | \
1473 // existing | 1 | 2 |
1474 // rows | | |
1475 // |---------+---------|
1476 // new | |
1477 // rows | 3 |
1478 // \ /
1479 //
1480 // We start by updating the region 1 of existing rows and columns to limit the
1481 // number of reads of existing coefficients. Then we update region 2 with all
1482 // new columns but we only existing rows. Finally we update region 3 with all
1483 // new rows and include new columns. Doing updates this way remove the need to
1484 // read existing coefficients for the updates 2 & 3 since by construction
1485 // those values are 0.
1486
1487 // Updating existing rows (constraints), ignoring the new columns.
1488 {
1489 // We reuse the same vectors for all calls to GLPK's API to limit
1490 // reallocations of these temporary buffers.
1491 GlpkSparseVector data(static_cast<int>(variables_.ids.size()));
1492 for (const auto& [row_id, row_coefficients] :
1493 SparseSubmatrixByRows(matrix_updates,
1494 /*start_row_id=*/0,
1495 /*end_row_id=*/first_new_cstr_id,
1496 /*start_col_id=*/0,
1497 /*end_col_id=*/first_new_var_id)) {
1498 // Find the index of the row in GLPK corresponding to the MathOpt's row
1499 // id.
1500 const int row_index = linear_constraints_.id_to_index.at(row_id);
1501 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1502
1503 // Read the current row coefficients.
1504 data.Load([&](int* const indices, double* const values) {
1505 return glp_get_mat_row(problem_, row_index, indices, values);
1506 });
1507
1508 // Update the row data.
1509 for (const auto [col_id, coefficient] : row_coefficients) {
1510 const int col_index = variables_.id_to_index.at(col_id);
1511 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1512 data.Set(col_index, coefficient);
1513 }
1514
1515 // Change the row values.
1516 glp_set_mat_row(problem_, row_index, data.size(), data.indices(),
1517 data.values());
1518 }
1519 }
1520
1521 // Add new columns's coefficients of existing rows. The coefficients of new
1522 // columns in new rows will be added when adding new rows below.
1523 if (first_new_var_id.has_value()) {
1524 GlpkSparseVector data(static_cast<int>(linear_constraints_.ids.size()));
1525 for (const auto& [col_id, col_coefficients] : TransposeSparseSubmatrix(
1526 SparseSubmatrixByRows(matrix_updates,
1527 /*start_row_id=*/0,
1528 /*end_row_id=*/first_new_cstr_id,
1529 /*start_col_id=*/*first_new_var_id,
1530 /*end_col_id=*/std::nullopt))) {
1531 // Find the index of the column in GLPK corresponding to the MathOpt's
1532 // column id.
1533 const int col_index = variables_.id_to_index.at(col_id);
1534 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1535
1536 // Prepare the column data replacing MathOpt ids by GLPK one-based row
1537 // indices.
1538 data.Clear();
1539 for (const auto [row_id, coefficient] : MakeView(col_coefficients)) {
1540 const int row_index = linear_constraints_.id_to_index.at(row_id);
1541 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1542 data.Set(row_index, coefficient);
1543 }
1544
1545 // Change the column values.
1546 glp_set_mat_col(problem_, col_index, data.size(), data.indices(),
1547 data.values());
1548 }
1549 }
1550
1551 // Add new rows, including the new columns' coefficients.
1552 if (first_new_cstr_id.has_value()) {
1553 GlpkSparseVector data(static_cast<int>(variables_.ids.size()));
1554 for (const auto& [row_id, row_coefficients] :
1555 SparseSubmatrixByRows(matrix_updates,
1556 /*start_row_id=*/*first_new_cstr_id,
1557 /*end_row_id=*/std::nullopt,
1558 /*start_col_id=*/0,
1559 /*end_col_id=*/std::nullopt)) {
1560 // Find the index of the row in GLPK corresponding to the MathOpt's row
1561 // id.
1562 const int row_index = linear_constraints_.id_to_index.at(row_id);
1563 CHECK_EQ(linear_constraints_.ids[row_index - 1], row_id);
1564
1565 // Prepare the row data replacing MathOpt ids by GLPK one-based column
1566 // indices.
1567 data.Clear();
1568 for (const auto [col_id, coefficient] : row_coefficients) {
1569 const int col_index = variables_.id_to_index.at(col_id);
1570 CHECK_EQ(variables_.ids[col_index - 1], col_id);
1571 data.Set(col_index, coefficient);
1572 }
1573
1574 // Change the row values.
1575 glp_set_mat_row(problem_, row_index, data.size(), data.indices(),
1576 data.values());
1577 }
1578 }
1579}
1580
1581void GlpkSolver::AddPrimalSolution(
1582 int (*get_prim_stat)(glp_prob*), double (*obj_val)(glp_prob*),
1583 double (*col_val)(glp_prob*, int),
1584 const ModelSolveParametersProto& model_parameters,
1585 SolutionProto& solution_proto) {
1586 const int status = get_prim_stat(problem_);
1587 if (status == GLP_OPT || status == GLP_FEAS) {
1588 PrimalSolutionProto& primal_solution =
1589 *solution_proto.mutable_primal_solution();
1590 primal_solution.set_objective_value(obj_val(problem_));
1591 primal_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1592 *primal_solution.mutable_variable_values() =
1593 FilteredVector(problem_, model_parameters.variable_values_filter(),
1594 variables_.ids, col_val);
1595 }
1596}
1597
1598void GlpkSolver::AddDualSolution(
1599 int (*get_dual_stat)(glp_prob*), double (*obj_val)(glp_prob*),
1600 double (*row_dual)(glp_prob*, int), double (*col_dual)(glp_prob*, int),
1601 const ModelSolveParametersProto& model_parameters,
1602 SolutionProto& solution_proto) {
1603 const int status = get_dual_stat(problem_);
1604 if (status == GLP_OPT || status == GLP_FEAS) {
1605 DualSolutionProto& dual_solution = *solution_proto.mutable_dual_solution();
1606 dual_solution.set_objective_value(obj_val(problem_));
1607 *dual_solution.mutable_dual_values() =
1608 FilteredVector(problem_, model_parameters.dual_values_filter(),
1609 linear_constraints_.ids, row_dual);
1610 *dual_solution.mutable_reduced_costs() =
1611 FilteredVector(problem_, model_parameters.reduced_costs_filter(),
1612 variables_.ids, col_dual);
1613 // TODO(b/197867442): Check that `status == GLP_FEAS` implies dual feasible
1614 // solution on early termination with barrier (where both `get_dual_stat`
1615 // and `get_prim_stat` are equal to `glp_ipt_status`).
1616 dual_solution.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
1617 }
1618}
1619
1620absl::Status GlpkSolver::AddPrimalOrDualRay(
1621 const ModelSolveParametersProto& model_parameters,
1622 SolveResultProto& result) {
1623 ASSIGN_OR_RETURN(const std::optional<GlpkRay> opt_unbound_ray,
1624 GlpkComputeUnboundRay(problem_));
1625 if (!opt_unbound_ray.has_value()) {
1626 return absl::OkStatus();
1627 }
1628
1629 const int num_cstrs = linear_constraints_.ids.size();
1630 switch (opt_unbound_ray->type) {
1631 case GlpkRayType::kPrimal: {
1632 const int num_cstrs = static_cast<int>(linear_constraints_.ids.size());
1633 // Note that GlpkComputeUnboundRay() returned ray considers the variables
1634 // of the computational form. Thus it contains both structural and
1635 // auxiliary variables. In the MathOpt's primal ray we only consider
1636 // structural variables though.
1637 std::vector<double> ray_values(variables_.ids.size());
1638
1639 for (const auto [k, value] : opt_unbound_ray->non_zero_components) {
1640 if (k <= num_cstrs) {
1641 // Ignore auxiliary variables.
1642 continue;
1643 }
1644 const int var_index = k - num_cstrs;
1645 CHECK_GE(var_index, 1);
1646 ray_values[var_index - 1] = value;
1647 }
1648
1649 *result.add_primal_rays()->mutable_variable_values() =
1650 FilteredRay(model_parameters.variable_values_filter(), variables_.ids,
1651 ray_values);
1652
1653 return absl::OkStatus();
1654 }
1655 case GlpkRayType::kDual: {
1656 // Note that GlpkComputeUnboundRay() returned ray considers the variables
1657 // of the computational form. Thus it contains reduced costs of both
1658 // structural and auxiliary variables. In the MathOpt's dual ray we split
1659 // the reduced costs. The ones of auxiliary variables (variables of
1660 // constraints) are called "dual values" and the ones of structural
1661 // variables are called "reduced costs".
1662 std::vector<double> ray_reduced_costs(variables_.ids.size());
1663 std::vector<double> ray_dual_values(num_cstrs);
1664
1665 for (const auto [k, value] : opt_unbound_ray->non_zero_components) {
1666 if (k <= num_cstrs) {
1667 ray_dual_values[k - 1] = value;
1668 } else {
1669 const int var_index = k - num_cstrs;
1670 CHECK_GE(var_index, 1);
1671 ray_reduced_costs[var_index - 1] = value;
1672 }
1673 }
1674
1675 DualRayProto& dual_ray = *result.add_dual_rays();
1676 *dual_ray.mutable_dual_values() =
1677 FilteredRay(model_parameters.dual_values_filter(),
1678 linear_constraints_.ids, ray_dual_values);
1679 *dual_ray.mutable_reduced_costs() =
1680 FilteredRay(model_parameters.reduced_costs_filter(), variables_.ids,
1681 ray_reduced_costs);
1682
1683 return absl::OkStatus();
1684 }
1685 }
1686}
1687
1688absl::StatusOr<bool> GlpkSolver::Update(const ModelUpdateProto& model_update) {
1689 RETURN_IF_ERROR(CheckCurrentThread());
1690
1691 // We must do that *after* testing current thread since the Solver class won't
1692 // destroy this instance from another thread when the update is not supported
1693 // (the Solver class destroy the SolverInterface only when an Update() returns
1694 // false).
1695 if (!UpdateIsSupported(model_update, kGlpkSupportedStructures)) {
1696 return false;
1697 }
1698
1699 {
1700 const std::vector<int> sorted_deleted_cols = DeleteRowsOrCols(
1701 problem_, variables_, model_update.deleted_variable_ids());
1702 DeleteRowOrColData(variables_.unrounded_lower_bounds, sorted_deleted_cols);
1703 DeleteRowOrColData(variables_.unrounded_upper_bounds, sorted_deleted_cols);
1704 CHECK_EQ(variables_.unrounded_lower_bounds.size(),
1705 variables_.unrounded_upper_bounds.size());
1706 CHECK_EQ(variables_.unrounded_lower_bounds.size(), variables_.ids.size());
1707 }
1708 DeleteRowsOrCols(problem_, linear_constraints_,
1709 model_update.deleted_linear_constraint_ids());
1710
1711 for (const auto [var_id, is_integer] :
1712 MakeView(model_update.variable_updates().integers())) {
1713 // See comment in AddVariables() to see why we don't use GLP_BV here.
1714 const int var_index = variables_.id_to_index.at(var_id);
1715 glp_set_col_kind(problem_, var_index, is_integer ? GLP_IV : GLP_CV);
1716
1717 // Either restore the fractional bounds if the variable was integer and is
1718 // now integer, or rounds the existing bounds if the variable was fractional
1719 // and is now integer. Here we use the old bounds; they will get updated
1720 // below by the call to UpdateBounds() if they are also changed by this
1721 // update.
1722 SetBounds<Variables>(
1723 problem_, var_index,
1724 {.lower = variables_.unrounded_lower_bounds[var_index - 1],
1725 .upper = variables_.unrounded_upper_bounds[var_index - 1]});
1726 }
1727 for (const auto [var_id, lower_bound] :
1728 MakeView(model_update.variable_updates().lower_bounds())) {
1729 variables_.unrounded_lower_bounds[variables_.id_to_index.at(var_id) - 1] =
1730 lower_bound;
1731 }
1732 for (const auto [var_id, upper_bound] :
1733 MakeView(model_update.variable_updates().upper_bounds())) {
1734 variables_.unrounded_upper_bounds[variables_.id_to_index.at(var_id) - 1] =
1735 upper_bound;
1736 }
1737 UpdateBounds(
1738 problem_, variables_,
1739 /*lower_bounds_proto=*/model_update.variable_updates().lower_bounds(),
1740 /*upper_bounds_proto=*/model_update.variable_updates().upper_bounds());
1741 UpdateBounds(problem_, linear_constraints_,
1742 /*lower_bounds_proto=*/
1743 model_update.linear_constraint_updates().lower_bounds(),
1744 /*upper_bounds_proto=*/
1745 model_update.linear_constraint_updates().upper_bounds());
1746
1747 AddVariables(model_update.new_variables());
1748 AddLinearConstraints(model_update.new_linear_constraints());
1749
1750 if (model_update.objective_updates().has_direction_update()) {
1751 glp_set_obj_dir(problem_,
1752 model_update.objective_updates().direction_update()
1753 ? GLP_MAX
1754 : GLP_MIN);
1755 }
1756 if (model_update.objective_updates().has_offset_update()) {
1757 // Glpk uses index 0 for the "shift" of the objective.
1758 glp_set_obj_coef(problem_, 0,
1759 model_update.objective_updates().offset_update());
1760 }
1761 UpdateObjectiveCoefficients(
1762 model_update.objective_updates().linear_coefficients());
1763
1764 UpdateLinearConstraintMatrix(
1765 model_update.linear_constraint_matrix_updates(),
1766 /*first_new_var_id=*/FirstVariableId(model_update.new_variables()),
1767 /*first_new_cstr_id=*/
1769
1770 return true;
1771}
1772
1773absl::Status GlpkSolver::CheckCurrentThread() {
1774 if (std::this_thread::get_id() != thread_id_) {
1775 return absl::InvalidArgumentError(
1776 "GLPK is not thread-safe and thus the solver should only be used on "
1777 "the same thread as it was created");
1778 }
1779 return absl::OkStatus();
1780}
1781
1782std::optional<SolveResultProto> GlpkSolver::EmptyIntegerBoundsResult() {
1783 const int num_cols = glp_get_num_cols(problem_);
1784 for (int c = 1; c <= num_cols; ++c) {
1785 if (!variables_.IsInteger(problem_, c)) {
1786 continue;
1787 }
1788 const double lb = variables_.unrounded_lower_bounds[c - 1];
1789 const double ub = variables_.unrounded_upper_bounds[c - 1];
1790 if (lb > ub) {
1791 // Unrounded bounds are inverted; this case is covered by
1792 // ListInvertedBounds(). We don't want to depend on the order of calls of
1793 // the two functions here so we exclude this case.
1794 continue;
1795 }
1796 if (std::ceil(lb) <= std::floor(ub)) {
1797 continue;
1798 }
1799
1800 // We found a variable with empty integer bounds (that is lb <= ub but
1801 // ceil(lb) > floor(ub)).
1803 /*is_maximize=*/glp_get_obj_dir(problem_) == GLP_MAX,
1804 /*bad_variable_id=*/variables_.ids[c - 1],
1805 /*lb=*/lb, /*ub=*/ub);
1806 }
1807
1808 return std::nullopt;
1809}
1810
1811absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
1813 const SolveParametersProto& parameters, MessageCallback message_cb,
1814 const SolveInterrupter* absl_nullable const interrupter) {
1815 return absl::UnimplementedError(
1816 "GLPK does not provide a method to compute an infeasible subsystem");
1817}
1818
1820
1821} // namespace math_opt
1822} // namespace operations_research
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto &parameters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, const SolveInterrupter *absl_nullable interrupter) override
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const InitArgs &init_args)
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto &parameters, MessageCallback message_cb, const SolveInterrupter *absl_nullable interrupter) override
const ::operations_research::math_opt::SparseDoubleVectorProto & upper_bounds() const
const ::operations_research::math_opt::SparseDoubleVectorProto & lower_bounds() const
const ::operations_research::math_opt::VariablesProto & variables() const
Definition model.pb.h:4464
const ::operations_research::math_opt::LinearConstraintsProto & linear_constraints() const
Definition model.pb.h:4694
const ::std::string & name() const
Definition model.pb.h:4389
const ::operations_research::math_opt::ObjectiveProto & objective() const
Definition model.pb.h:4563
const ::operations_research::math_opt::SparseDoubleMatrixProto & linear_constraint_matrix() const
Definition model.pb.h:4787
::int64_t deleted_linear_constraint_ids(int index) const
const ::operations_research::math_opt::SparseDoubleMatrixProto & linear_constraint_matrix_updates() const
const ::operations_research::math_opt::LinearConstraintsProto & new_linear_constraints() const
const ::operations_research::math_opt::VariableUpdatesProto & variable_updates() const
const ::operations_research::math_opt::ObjectiveUpdatesProto & objective_updates() const
const ::operations_research::math_opt::LinearConstraintUpdatesProto & linear_constraint_updates() const
const ::operations_research::math_opt::VariablesProto & new_variables() const
const ::operations_research::math_opt::SparseDoubleVectorProto & linear_coefficients() const
Definition model.pb.h:2911
const ::operations_research::math_opt::SparseDoubleVectorProto & linear_coefficients() const
void set_primal_status(::operations_research::math_opt::FeasibilityStatusProto value)
Definition result.pb.h:1781
void set_dual_status(::operations_research::math_opt::FeasibilityStatusProto value)
Definition result.pb.h:1806
::operations_research::math_opt::EmphasisProto presolve() const
const ::operations_research::math_opt::GlpkParametersProto & glpk() const
::operations_research::math_opt::LPAlgorithmProto lp_algorithm() const
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >( const CallbackDataProto &)> Callback
::google::protobuf::RepeatedField<::int64_t > *PROTOBUF_NONNULL mutable_ids()
const ::operations_research::math_opt::SparseDoubleVectorProto & upper_bounds() const
const ::operations_research::math_opt::SparseDoubleVectorProto & lower_bounds() const
const ::operations_research::math_opt::SparseBoolVectorProto & integers() const
const ::std::string & names(int index) const
Definition model.pb.h:2796
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
Definition callback.h:94
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:351
SolveResultProto ResultForIntegerInfeasible(const bool is_maximize, const int64_t bad_variable_id, const double lb, const double ub)
OR-Tools root namespace.
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)
Definition proto_utils.h:63
std::string TruncateAndQuoteGLPKName(const std::string_view original_name)
std::string ReturnCodeString(const int rc)
std::string SolutionStatusString(const int status)
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()
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)