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