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