OR-Tools  9.6
glop_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 <cstdint>
19 #include <functional>
20 #include <limits>
21 #include <memory>
22 #include <string>
23 #include <utility>
24 #include <vector>
25 
26 #include "absl/container/flat_hash_map.h"
27 #include "absl/memory/memory.h"
28 #include "absl/status/status.h"
29 #include "absl/status/statusor.h"
30 #include "absl/strings/str_cat.h"
31 #include "absl/strings/str_join.h"
32 #include "absl/strings/str_split.h"
33 #include "absl/strings/string_view.h"
34 #include "absl/time/clock.h"
35 #include "absl/time/time.h"
36 #include "absl/types/span.h"
37 #include "ortools/base/cleanup.h"
38 #include "ortools/base/int_type.h"
40 #include "ortools/base/logging.h"
41 #include "ortools/base/map_util.h"
42 #include "ortools/base/protoutil.h"
45 #include "ortools/glop/lp_solver.h"
46 #include "ortools/glop/parameters.pb.h"
50 #include "ortools/math_opt/callback.pb.h"
55 #include "ortools/math_opt/model.pb.h"
56 #include "ortools/math_opt/model_parameters.pb.h"
57 #include "ortools/math_opt/model_update.pb.h"
58 #include "ortools/math_opt/parameters.pb.h"
59 #include "ortools/math_opt/result.pb.h"
60 #include "ortools/math_opt/solution.pb.h"
61 #include "ortools/math_opt/sparse_containers.pb.h"
65 
66 namespace operations_research {
67 namespace math_opt {
68 
69 namespace {
70 
71 constexpr double kInf = std::numeric_limits<double>::infinity();
72 
73 constexpr SupportedProblemStructures kGlopSupportedStructures = {};
74 
75 absl::string_view SafeName(const VariablesProto& variables, int index) {
76  if (variables.names().empty()) {
77  return {};
78  }
79  return variables.names(index);
80 }
81 
82 absl::string_view SafeName(const LinearConstraintsProto& linear_constraints,
83  int index) {
84  if (linear_constraints.names().empty()) {
85  return {};
86  }
87  return linear_constraints.names(index);
88 }
89 
90 absl::StatusOr<TerminationProto> BuildTermination(
92  const SolveInterrupter* const interrupter) {
93  switch (status) {
95  return TerminateForReason(TERMINATION_REASON_OPTIMAL);
98  return TerminateForReason(TERMINATION_REASON_INFEASIBLE);
100  return TerminateForReason(TERMINATION_REASON_UNBOUNDED);
103  return TerminateForReason(TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED);
106  // Glop may flip the `interrupt_solve` atomic when it is terminated for a
107  // reason other than interruption so we should ignore its value. Instead
108  // we use the interrupter.
109  // A primal feasible solution is only returned for PRIMAL_FEASIBLE (see
110  // comments in FillSolution).
111  return NoSolutionFoundTermination(interrupter != nullptr &&
112  interrupter->IsInterrupted()
113  ? LIMIT_INTERRUPTED
114  : LIMIT_UNDETERMINED);
116  // Glop may flip the `interrupt_solve` atomic when it is terminated for a
117  // reason other than interruption so we should ignore its value. Instead
118  // we use the interrupter.
119  // A primal feasible solution is only returned for PRIMAL_FEASIBLE (see
120  // comments in FillSolution).
121  return FeasibleTermination(interrupter != nullptr &&
122  interrupter->IsInterrupted()
123  ? LIMIT_INTERRUPTED
124  : LIMIT_UNDETERMINED);
126  return TerminateForReason(TERMINATION_REASON_IMPRECISE);
129  return absl::InternalError(
130  absl::StrCat("Unexpected GLOP termination reason: ",
132  }
133  LOG(FATAL) << "Unimplemented GLOP termination reason: "
135 }
136 
137 // Returns an InvalidArgumentError if the provided parameters are invalid.
138 absl::Status ValidateGlopParameters(const glop::GlopParameters& parameters) {
139  const std::string error = glop::ValidateParameters(parameters);
140  if (!error.empty()) {
142  << "invalid GlopParameters: " << error;
143  }
144  return absl::OkStatus();
145 }
146 
147 } // namespace
148 
149 GlopSolver::GlopSolver() : linear_program_(), lp_solver_() {}
150 
151 void GlopSolver::AddVariables(const VariablesProto& variables) {
152  for (int i = 0; i < NumVariables(variables); ++i) {
153  const glop::ColIndex col_index = linear_program_.CreateNewVariable();
154  linear_program_.SetVariableBounds(col_index, variables.lower_bounds(i),
155  variables.upper_bounds(i));
156  linear_program_.SetVariableName(col_index, SafeName(variables, i));
157  gtl::InsertOrDie(&variables_, variables.ids(i), col_index);
158  }
159 }
160 
161 // Note that this relies on the fact that when variable/constraint
162 // are deleted, Glop re-index everything by compacting the
163 // index domain in a stable way.
164 template <typename IndexType>
166 
167  IndexType num_indices,
168  absl::flat_hash_map<int64_t, IndexType>& id_index_map) {
169  absl::StrongVector<IndexType, IndexType> new_indices(num_indices.value(),
170  IndexType(0));
171  IndexType new_index(0);
172  for (IndexType index(0); index < num_indices; ++index) {
173  if (indices_to_delete[index]) {
174  // Mark deleted index
175  new_indices[index] = IndexType(-1);
176  } else {
177  new_indices[index] = new_index;
178  ++new_index;
179  }
180  }
181  for (auto it = id_index_map.begin(); it != id_index_map.end();) {
182  IndexType index = it->second;
183  if (indices_to_delete[index]) {
184  // This safely deletes the entry and moves the iterator one step ahead.
185  id_index_map.erase(it++);
186  } else {
187  it->second = new_indices[index];
188  ++it;
189  }
190  }
191 }
192 
193 void GlopSolver::DeleteVariables(absl::Span<const int64_t> ids_to_delete) {
194  const glop::ColIndex num_cols = linear_program_.num_variables();
195  glop::StrictITIVector<glop::ColIndex, bool> columns_to_delete(num_cols,
196  false);
197  for (const int64_t deleted_variable_id : ids_to_delete) {
198  columns_to_delete[variables_.at(deleted_variable_id)] = true;
199  }
200  linear_program_.DeleteColumns(columns_to_delete);
201  UpdateIdIndexMap<glop::ColIndex>(columns_to_delete, num_cols, variables_);
202 }
203 
204 void GlopSolver::DeleteLinearConstraints(
205  absl::Span<const int64_t> ids_to_delete) {
206  const glop::RowIndex num_rows = linear_program_.num_constraints();
207  glop::DenseBooleanColumn rows_to_delete(num_rows, false);
208  for (const int64_t deleted_constraint_id : ids_to_delete) {
209  rows_to_delete[linear_constraints_.at(deleted_constraint_id)] = true;
210  }
211  linear_program_.DeleteRows(rows_to_delete);
212  UpdateIdIndexMap<glop::RowIndex>(rows_to_delete, num_rows,
213  linear_constraints_);
214 }
215 
216 void GlopSolver::AddLinearConstraints(
217  const LinearConstraintsProto& linear_constraints) {
218  for (int i = 0; i < NumConstraints(linear_constraints); ++i) {
219  const glop::RowIndex row_index = linear_program_.CreateNewConstraint();
220  linear_program_.SetConstraintBounds(row_index,
221  linear_constraints.lower_bounds(i),
222  linear_constraints.upper_bounds(i));
223  linear_program_.SetConstraintName(row_index,
224  SafeName(linear_constraints, i));
225  gtl::InsertOrDie(&linear_constraints_, linear_constraints.ids(i),
226  row_index);
227  }
228 }
229 
230 void GlopSolver::SetOrUpdateObjectiveCoefficients(
231  const SparseDoubleVectorProto& linear_objective_coefficients) {
232  for (int i = 0; i < linear_objective_coefficients.ids_size(); ++i) {
233  const glop::ColIndex col_index =
234  variables_.at(linear_objective_coefficients.ids(i));
235  linear_program_.SetObjectiveCoefficient(
236  col_index, linear_objective_coefficients.values(i));
237  }
238 }
239 
240 void GlopSolver::SetOrUpdateConstraintMatrix(
241  const SparseDoubleMatrixProto& linear_constraint_matrix) {
242  for (int j = 0; j < NumMatrixNonzeros(linear_constraint_matrix); ++j) {
243  const glop::ColIndex col_index =
244  variables_.at(linear_constraint_matrix.column_ids(j));
245  const glop::RowIndex row_index =
246  linear_constraints_.at(linear_constraint_matrix.row_ids(j));
247  const double coefficient = linear_constraint_matrix.coefficients(j);
248  linear_program_.SetCoefficient(row_index, col_index, coefficient);
249  }
250 }
251 
252 void GlopSolver::UpdateVariableBounds(
253  const VariableUpdatesProto& variable_updates) {
254  for (const auto [id, lb] : MakeView(variable_updates.lower_bounds())) {
255  const auto col_index = variables_.at(id);
256  linear_program_.SetVariableBounds(
257  col_index, lb, linear_program_.variable_upper_bounds()[col_index]);
258  }
259  for (const auto [id, ub] : MakeView(variable_updates.upper_bounds())) {
260  const auto col_index = variables_.at(id);
261  linear_program_.SetVariableBounds(
262  col_index, linear_program_.variable_lower_bounds()[col_index], ub);
263  }
264 }
265 
266 void GlopSolver::UpdateLinearConstraintBounds(
267  const LinearConstraintUpdatesProto& linear_constraint_updates) {
268  for (const auto [id, lb] :
269  MakeView(linear_constraint_updates.lower_bounds())) {
270  const auto row_index = linear_constraints_.at(id);
271  linear_program_.SetConstraintBounds(
272  row_index, lb, linear_program_.constraint_upper_bounds()[row_index]);
273  }
274  for (const auto [id, ub] :
275  MakeView(linear_constraint_updates.upper_bounds())) {
276  const auto row_index = linear_constraints_.at(id);
277  linear_program_.SetConstraintBounds(
278  row_index, linear_program_.constraint_lower_bounds()[row_index], ub);
279  }
280 }
281 
282 absl::StatusOr<glop::GlopParameters> GlopSolver::MergeSolveParameters(
283  const SolveParametersProto& solve_parameters,
284  const bool setting_initial_basis, const bool has_message_callback) {
285  // Validate first the user specific Glop parameters.
286  RETURN_IF_ERROR(ValidateGlopParameters(solve_parameters.glop()))
287  << "invalid SolveParametersProto.glop value";
288 
289  glop::GlopParameters result = solve_parameters.glop();
290  std::vector<std::string> warnings;
291  if (!result.has_max_time_in_seconds() && solve_parameters.has_time_limit()) {
292  const absl::Duration time_limit =
293  util_time::DecodeGoogleApiProto(solve_parameters.time_limit()).value();
294  result.set_max_time_in_seconds(absl::ToDoubleSeconds(time_limit));
295  }
296  if (has_message_callback) {
297  // If we have a message callback, we must set log_search_progress to get any
298  // logs. We ignore the user's input on specific solver parameters here since
299  // it would be confusing to accept a callback but never call it.
300  result.set_log_search_progress(true);
301 
302  // We don't want the logs to be also printed to stdout when we have a
303  // message callback. Here we ignore the user input since message callback
304  // can be used in the context of a server and printing to stdout could be a
305  // problem.
306  result.set_log_to_stdout(false);
307  } else if (!result.has_log_search_progress()) {
308  result.set_log_search_progress(solve_parameters.enable_output());
309  }
310  if (!result.has_num_omp_threads() && solve_parameters.has_threads()) {
311  result.set_num_omp_threads(solve_parameters.threads());
312  }
313  if (!result.has_random_seed() && solve_parameters.has_random_seed()) {
314  const int random_seed = std::max(0, solve_parameters.random_seed());
315  result.set_random_seed(random_seed);
316  }
317  if (!result.has_max_number_of_iterations() &&
318  solve_parameters.iteration_limit()) {
319  result.set_max_number_of_iterations(solve_parameters.iteration_limit());
320  }
321  if (solve_parameters.has_node_limit()) {
322  warnings.emplace_back("GLOP does snot support 'node_limit' parameter");
323  }
324  if (!result.has_use_dual_simplex() &&
325  solve_parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
326  switch (solve_parameters.lp_algorithm()) {
327  case LP_ALGORITHM_PRIMAL_SIMPLEX:
328  result.set_use_dual_simplex(false);
329  break;
330  case LP_ALGORITHM_DUAL_SIMPLEX:
331  result.set_use_dual_simplex(true);
332  break;
333  case LP_ALGORITHM_BARRIER:
334  warnings.emplace_back(
335  "GLOP does not support 'LP_ALGORITHM_BARRIER' value for "
336  "'lp_algorithm' parameter.");
337  break;
338  default:
339  LOG(FATAL) << "LPAlgorithm: "
340  << ProtoEnumToString(solve_parameters.lp_algorithm())
341  << " unknown, error setting GLOP parameters";
342  }
343  }
344  if (!result.has_use_scaling() && !result.has_scaling_method() &&
345  solve_parameters.scaling() != EMPHASIS_UNSPECIFIED) {
346  switch (solve_parameters.scaling()) {
347  case EMPHASIS_OFF:
348  result.set_use_scaling(false);
349  break;
350  case EMPHASIS_LOW:
351  case EMPHASIS_MEDIUM:
352  case EMPHASIS_HIGH:
353  case EMPHASIS_VERY_HIGH:
354  result.set_use_scaling(true);
355  result.set_scaling_method(glop::GlopParameters::EQUILIBRATION);
356  break;
357  default:
358  LOG(FATAL) << "Scaling emphasis: "
359  << ProtoEnumToString(solve_parameters.scaling())
360  << " unknown, error setting GLOP parameters";
361  }
362  }
363  if (setting_initial_basis) {
364  result.set_use_preprocessing(false);
365  } else if (!result.has_use_preprocessing() &&
366  solve_parameters.presolve() != EMPHASIS_UNSPECIFIED) {
367  switch (solve_parameters.presolve()) {
368  case EMPHASIS_OFF:
369  result.set_use_preprocessing(false);
370  break;
371  case EMPHASIS_LOW:
372  case EMPHASIS_MEDIUM:
373  case EMPHASIS_HIGH:
374  case EMPHASIS_VERY_HIGH:
375  result.set_use_preprocessing(true);
376  break;
377  default:
378  LOG(FATAL) << "Presolve emphasis: "
379  << ProtoEnumToString(solve_parameters.presolve())
380  << " unknown, error setting GLOP parameters";
381  }
382  }
383  if (solve_parameters.cuts() != EMPHASIS_UNSPECIFIED) {
384  warnings.push_back(absl::StrCat(
385  "GLOP does not support 'cuts' parameters, but cuts was set to: ",
386  ProtoEnumToString(solve_parameters.cuts())));
387  }
388  if (solve_parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
389  warnings.push_back(
390  absl::StrCat("GLOP does not support 'heuristics' parameter, but "
391  "heuristics was set to: ",
392  ProtoEnumToString(solve_parameters.heuristics())));
393  }
394  if (solve_parameters.has_cutoff_limit()) {
395  warnings.push_back("GLOP does not support 'cutoff_limit' parameter");
396  }
397  if (solve_parameters.has_objective_limit()) {
398  warnings.push_back("GLOP does not support 'objective_limit' parameter");
399  }
400  if (solve_parameters.has_best_bound_limit()) {
401  warnings.push_back("GLOP does not support 'best_bound_limit' parameter");
402  }
403  if (solve_parameters.has_solution_limit()) {
404  warnings.push_back("GLOP does not support 'solution_limit' parameter");
405  }
406  if (!warnings.empty()) {
407  return absl::InvalidArgumentError(absl::StrJoin(warnings, "; "));
408  }
409 
410  // Validate the result of the merge. If the parameters are not valid, this is
411  // an internal error from MathOpt as user specified Glop parameters have been
412  // validated at the beginning of this function. Thus the invalid values are
413  // values translated from solve_parameters and this code should not produce
414  // invalid parameters.
415  RETURN_IF_ERROR(ValidateGlopParameters(result))
416  << "invalid GlopParameters generated from SolveParametersProto";
417 
418  return result;
419 }
420 
421 template <typename IndexType>
422 SparseDoubleVectorProto FillSparseDoubleVector(
423  const std::vector<int64_t>& ids_in_order,
424  const absl::flat_hash_map<int64_t, IndexType>& id_map,
426  const SparseVectorFilterProto& filter) {
427  SparseVectorFilterPredicate predicate(filter);
428  SparseDoubleVectorProto result;
429  for (const int64_t variable_id : ids_in_order) {
430  const double value = values[id_map.at(variable_id)];
431  if (predicate.AcceptsAndUpdate(variable_id, value)) {
432  result.add_ids(variable_id);
433  result.add_values(value);
434  }
435  }
436  return result;
437 }
438 
439 // ValueType should be glop's VariableStatus or ConstraintStatus.
440 template <typename ValueType>
441 BasisStatusProto FromGlopBasisStatus(const ValueType glop_basis_status) {
442  switch (glop_basis_status) {
443  case ValueType::BASIC:
444  return BasisStatusProto::BASIS_STATUS_BASIC;
445  case ValueType::FIXED_VALUE:
446  return BasisStatusProto::BASIS_STATUS_FIXED_VALUE;
447  case ValueType::AT_LOWER_BOUND:
448  return BasisStatusProto::BASIS_STATUS_AT_LOWER_BOUND;
449  case ValueType::AT_UPPER_BOUND:
450  return BasisStatusProto::BASIS_STATUS_AT_UPPER_BOUND;
451  case ValueType::FREE:
452  return BasisStatusProto::BASIS_STATUS_FREE;
453  }
454  return BasisStatusProto::BASIS_STATUS_UNSPECIFIED;
455 }
456 
457 template <typename IndexType, typename ValueType>
458 SparseBasisStatusVector FillSparseBasisStatusVector(
459  const std::vector<int64_t>& ids_in_order,
460  const absl::flat_hash_map<int64_t, IndexType>& id_map,
462  SparseBasisStatusVector result;
463  for (const int64_t variable_id : ids_in_order) {
464  const ValueType value = values[id_map.at(variable_id)];
465  result.add_ids(variable_id);
466  result.add_values(FromGlopBasisStatus(value));
467  }
468  return result;
469 }
470 
471 // ValueType should be glop's VariableStatus or ConstraintStatus.
472 template <typename ValueType>
473 ValueType ToGlopBasisStatus(const BasisStatusProto basis_status) {
474  switch (basis_status) {
475  case BASIS_STATUS_BASIC:
476  return ValueType::BASIC;
477  case BASIS_STATUS_FIXED_VALUE:
478  return ValueType::FIXED_VALUE;
479  case BASIS_STATUS_AT_LOWER_BOUND:
480  return ValueType::AT_LOWER_BOUND;
481  case BASIS_STATUS_AT_UPPER_BOUND:
482  return ValueType::AT_UPPER_BOUND;
483  case BASIS_STATUS_FREE:
484  return ValueType::FREE;
485  default:
486  LOG(FATAL) << "Unexpected invalid initial_basis.";
487  return ValueType::FREE;
488  }
489 }
490 
491 template <typename T>
492 std::vector<int64_t> GetSortedIs(
493  const absl::flat_hash_map<int64_t, T>& id_map) {
494  std::vector<int64_t> sorted;
495  sorted.reserve(id_map.size());
496  for (const auto& entry : id_map) {
497  sorted.emplace_back(entry.first);
498  }
499  std::sort(sorted.begin(), sorted.end());
500  return sorted;
501 }
502 
503 // Returns a vector of containing the MathOpt id of each row or column. Here T
504 // is either (Col|Row)Index and id_map is expected to be
505 // GlopSolver::(linear_constraints_|variables_).
506 template <typename T>
508  const absl::flat_hash_map<int64_t, T>& id_map) {
509  // Guard value used to identify not-yet-set elements of index_to_id.
510  constexpr int64_t kEmptyId = -1;
511  glop::StrictITIVector<T, int64_t> index_to_id(T(id_map.size()), kEmptyId);
512  for (const auto& [id, index] : id_map) {
513  CHECK(index >= 0 && index < index_to_id.size()) << index;
514  CHECK_EQ(index_to_id[index], kEmptyId);
515  index_to_id[index] = id;
516  }
517 
518  // At this point, index_to_id can't contain any kEmptyId values since
519  // index_to_id.size() == id_map.size() and we modified id_map.size() elements
520  // in the loop, after checking that the modified element was changed by a
521  // previous iteration.
522  return index_to_id;
523 }
524 
525 InvertedBounds GlopSolver::ListInvertedBounds() const {
526  // Identify rows and columns by index first.
527  std::vector<glop::ColIndex> inverted_columns;
528  const glop::ColIndex num_cols = linear_program_.num_variables();
529  for (glop::ColIndex col(0); col < num_cols; ++col) {
530  if (linear_program_.variable_lower_bounds()[col] >
531  linear_program_.variable_upper_bounds()[col]) {
532  inverted_columns.push_back(col);
533  }
534  }
535  std::vector<glop::RowIndex> inverted_rows;
536  const glop::RowIndex num_rows = linear_program_.num_constraints();
537  for (glop::RowIndex row(0); row < num_rows; ++row) {
538  if (linear_program_.constraint_lower_bounds()[row] >
539  linear_program_.constraint_upper_bounds()[row]) {
540  inverted_rows.push_back(row);
541  }
542  }
543 
544  // Convert column/row indices into MathOpt ids. We avoid calling the expensive
545  // IndexToId() when not necessary.
546  InvertedBounds inverted_bounds;
547  if (!inverted_columns.empty()) {
548  const glop::StrictITIVector<glop::ColIndex, int64_t> ids =
549  IndexToId(variables_);
550  CHECK_EQ(ids.size(), num_cols);
551  inverted_bounds.variables.reserve(inverted_columns.size());
552  for (const glop::ColIndex col : inverted_columns) {
553  inverted_bounds.variables.push_back(ids[col]);
554  }
555  }
556  if (!inverted_rows.empty()) {
557  const glop::StrictITIVector<glop::RowIndex, int64_t> ids =
558  IndexToId(linear_constraints_);
559  CHECK_EQ(ids.size(), num_rows);
560  inverted_bounds.linear_constraints.reserve(inverted_rows.size());
561  for (const glop::RowIndex row : inverted_rows) {
562  inverted_bounds.linear_constraints.push_back(ids[row]);
563  }
564  }
565 
566  return inverted_bounds;
567 }
568 
569 void GlopSolver::FillSolution(const glop::ProblemStatus status,
570  const ModelSolveParametersProto& model_parameters,
571  SolveResultProto& solve_result) {
572  // Meaningfull solutions are available if optimality is proven in
573  // preprocessing or after 1 simplex iteration.
574  // TODO(b/195295177): Discuss what to do with glop::ProblemStatus::IMPRECISE
575  // looks like it may be set also when rays are imprecise.
576  const bool phase_I_solution_available =
577  (status == glop::ProblemStatus::INIT) &&
578  (lp_solver_.GetNumberOfSimplexIterations() > 0);
580  status != glop::ProblemStatus::PRIMAL_FEASIBLE &&
581  status != glop::ProblemStatus::DUAL_FEASIBLE &&
582  status != glop::ProblemStatus::PRIMAL_UNBOUNDED &&
583  status != glop::ProblemStatus::DUAL_UNBOUNDED &&
584  !phase_I_solution_available) {
585  return;
586  }
587  auto sorted_variables = GetSortedIs(variables_);
588  auto sorted_constraints = GetSortedIs(linear_constraints_);
589  SolutionProto* const solution = solve_result.add_solutions();
590  BasisProto* const basis = solution->mutable_basis();
591  PrimalSolutionProto* const primal_solution =
592  solution->mutable_primal_solution();
593  DualSolutionProto* const dual_solution = solution->mutable_dual_solution();
594 
595  // Fill in feasibility statuses
596  // Note: if we reach here and status != OPTIMAL, then at least 1 simplex
597  // iteration has been executed.
599  primal_solution->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
600  basis->set_basic_dual_feasibility(SOLUTION_STATUS_FEASIBLE);
601  dual_solution->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
602  } else if (status == glop::ProblemStatus::PRIMAL_FEASIBLE) {
603  // Solve reached phase II of primal simplex and current basis is not
604  // optimal. Hence basis is primal feasible, but cannot be dual feasible.
605  // Dual solution could still be feasible as noted in
606  // go/mathopt-basis-advanced#dualfeasibility
607  primal_solution->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
608  dual_solution->set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
609  basis->set_basic_dual_feasibility(SOLUTION_STATUS_INFEASIBLE);
610  } else if (status == glop::ProblemStatus::DUAL_FEASIBLE) {
611  // Solve reached phase II of dual simplex and current basis is not optimal.
612  // Hence basis is dual feasible, but cannot be primal feasible. In addition,
613  // glop applies dual feasibility correction in dual simplex so feasibility
614  // of the dual solution matches dual feasibility of the basis (i.e the issue
615  // described in go/mathopt-basis-advanced#dualfeasibility cannot happen).
616  // TODO(b/195295177): confirm with fdid
617  primal_solution->set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
618  dual_solution->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
619  basis->set_basic_dual_feasibility(SOLUTION_STATUS_FEASIBLE);
620  } else { // status == INIT
621  // Phase I of primal or dual simplex ran for at least one iteration
622  if (lp_solver_.GetParameters().use_dual_simplex()) {
623  // Phase I did not finish so basis is not dual feasible. In addition,
624  // glop applies dual feasibility correction so feasibility of the dual
625  // solution matches dual feasibility of the basis (i.e the issue described
626  // in go/mathopt-basis-advanced#dualfeasibility cannot happen).
627  // TODO(b/195295177): confirm with fdid
628  primal_solution->set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
629  dual_solution->set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
630  basis->set_basic_dual_feasibility(SOLUTION_STATUS_INFEASIBLE);
631  } else {
632  // Phase I did not finish so basis is not primal feasible.
633  primal_solution->set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
634  dual_solution->set_feasibility_status(SOLUTION_STATUS_UNDETERMINED);
635  basis->set_basic_dual_feasibility(SOLUTION_STATUS_UNDETERMINED);
636  }
637  }
638 
639  // Fill in objective values
640  primal_solution->set_objective_value(lp_solver_.GetObjectiveValue());
641  if (basis->basic_dual_feasibility() == SOLUTION_STATUS_FEASIBLE) {
642  // Primal and dual objectives are the same for a dual feasible basis
643  // see go/mathopt-basis-advanced#cs-obj-dual-feasible-dual-feasible-basis
644  dual_solution->set_objective_value(primal_solution->objective_value());
645  }
646 
647  // Fill solution and basis
648  *basis->mutable_constraint_status() = *basis->mutable_variable_status() =
649  FillSparseBasisStatusVector(sorted_variables, variables_,
650  lp_solver_.variable_statuses());
651  *basis->mutable_constraint_status() =
652  FillSparseBasisStatusVector(sorted_constraints, linear_constraints_,
653  lp_solver_.constraint_statuses());
654 
655  *primal_solution->mutable_variable_values() = FillSparseDoubleVector(
656  sorted_variables, variables_, lp_solver_.variable_values(),
657  model_parameters.variable_values_filter());
658 
659  *dual_solution->mutable_dual_values() = FillSparseDoubleVector(
660  sorted_constraints, linear_constraints_, lp_solver_.dual_values(),
661  model_parameters.dual_values_filter());
662  *dual_solution->mutable_reduced_costs() = FillSparseDoubleVector(
663  sorted_variables, variables_, lp_solver_.reduced_costs(),
664  model_parameters.reduced_costs_filter());
665 
666  if (!lp_solver_.primal_ray().empty()) {
667  PrimalRayProto* const primal_ray = solve_result.add_primal_rays();
668 
669  *primal_ray->mutable_variable_values() = FillSparseDoubleVector(
670  sorted_variables, variables_, lp_solver_.primal_ray(),
671  model_parameters.variable_values_filter());
672  }
673  if (!lp_solver_.constraints_dual_ray().empty() &&
674  !lp_solver_.variable_bounds_dual_ray().empty()) {
675  DualRayProto* const dual_ray = solve_result.add_dual_rays();
676  *dual_ray->mutable_dual_values() =
677  FillSparseDoubleVector(sorted_constraints, linear_constraints_,
678  lp_solver_.constraints_dual_ray(),
679  model_parameters.dual_values_filter());
680  *dual_ray->mutable_reduced_costs() = FillSparseDoubleVector(
681  sorted_variables, variables_, lp_solver_.variable_bounds_dual_ray(),
682  model_parameters.reduced_costs_filter());
683  }
684 }
685 
686 absl::Status GlopSolver::FillSolveStats(const glop::ProblemStatus status,
687  const absl::Duration solve_time,
688  SolveStatsProto& solve_stats) {
689  const bool is_maximize = linear_program_.IsMaximizationProblem();
690 
691  // Set default status and bounds.
692  solve_stats.mutable_problem_status()->set_primal_status(
693  FEASIBILITY_STATUS_UNDETERMINED);
694  solve_stats.set_best_primal_bound(is_maximize ? -kInf : kInf);
695  solve_stats.mutable_problem_status()->set_dual_status(
696  FEASIBILITY_STATUS_UNDETERMINED);
697  solve_stats.set_best_dual_bound(is_maximize ? kInf : -kInf);
698 
699  // Update status and bounds as appropriate.
700  switch (status) {
702  solve_stats.mutable_problem_status()->set_primal_status(
703  FEASIBILITY_STATUS_FEASIBLE);
704  solve_stats.mutable_problem_status()->set_dual_status(
705  FEASIBILITY_STATUS_FEASIBLE);
706  solve_stats.set_best_primal_bound(lp_solver_.GetObjectiveValue());
707  solve_stats.set_best_dual_bound(lp_solver_.GetObjectiveValue());
708  break;
709  case glop::ProblemStatus::PRIMAL_INFEASIBLE:
710  solve_stats.mutable_problem_status()->set_primal_status(
711  FEASIBILITY_STATUS_INFEASIBLE);
712  break;
713  case glop::ProblemStatus::DUAL_UNBOUNDED:
714  solve_stats.mutable_problem_status()->set_primal_status(
715  FEASIBILITY_STATUS_INFEASIBLE);
716  solve_stats.mutable_problem_status()->set_dual_status(
717  FEASIBILITY_STATUS_FEASIBLE);
718  solve_stats.set_best_dual_bound(is_maximize ? -kInf : kInf);
719  break;
720  case glop::ProblemStatus::PRIMAL_UNBOUNDED:
721  solve_stats.mutable_problem_status()->set_primal_status(
722  FEASIBILITY_STATUS_FEASIBLE);
723  solve_stats.mutable_problem_status()->set_dual_status(
724  FEASIBILITY_STATUS_INFEASIBLE);
725  solve_stats.set_best_primal_bound(is_maximize ? kInf : -kInf);
726  break;
727  case glop::ProblemStatus::DUAL_INFEASIBLE:
728  solve_stats.mutable_problem_status()->set_dual_status(
729  FEASIBILITY_STATUS_INFEASIBLE);
730  break;
731  case glop::ProblemStatus::INFEASIBLE_OR_UNBOUNDED:
732  solve_stats.mutable_problem_status()->set_primal_or_dual_infeasible(true);
733  break;
734  case glop::ProblemStatus::PRIMAL_FEASIBLE:
735  solve_stats.mutable_problem_status()->set_primal_status(
736  FEASIBILITY_STATUS_FEASIBLE);
737  solve_stats.set_best_primal_bound(lp_solver_.GetObjectiveValue());
738  break;
739  case glop::ProblemStatus::DUAL_FEASIBLE:
740  solve_stats.mutable_problem_status()->set_dual_status(
741  FEASIBILITY_STATUS_FEASIBLE);
742  solve_stats.set_best_dual_bound(lp_solver_.GetObjectiveValue());
743  break;
744  case glop::ProblemStatus::INIT:
745  case glop::ProblemStatus::IMPRECISE:
746  // TODO(b/195295177): Discuss what to do with
747  // glop::ProblemStatus::IMPRECISE
748  break;
750  case glop::ProblemStatus::INVALID_PROBLEM:
751  return absl::InternalError(
752  absl::StrCat("Unexpected GLOP termination reason: ",
754  }
755 
756  // Fill remaining stats
757  solve_stats.set_simplex_iterations(lp_solver_.GetNumberOfSimplexIterations());
759  solve_time, solve_stats.mutable_solve_time()));
760 
761  return absl::OkStatus();
762 }
763 
764 absl::StatusOr<SolveResultProto> GlopSolver::MakeSolveResult(
766  const ModelSolveParametersProto& model_parameters,
767  const SolveInterrupter* const interrupter,
768  const absl::Duration solve_time) {
769  SolveResultProto solve_result;
770  ASSIGN_OR_RETURN(*solve_result.mutable_termination(),
771  BuildTermination(status, interrupter));
772  FillSolution(status, model_parameters, solve_result);
774  FillSolveStats(status, solve_time, *solve_result.mutable_solve_stats()));
775  return solve_result;
776 }
777 
778 void GlopSolver::SetGlopBasis(const BasisProto& basis) {
779  glop::VariableStatusRow variable_statuses(linear_program_.num_variables());
780  for (const auto [id, value] : MakeView(basis.variable_status())) {
781  variable_statuses[variables_.at(id)] =
782  ToGlopBasisStatus<glop::VariableStatus>(
783  static_cast<BasisStatusProto>(value));
784  }
785  glop::ConstraintStatusColumn constraint_statuses(
786  linear_program_.num_constraints());
787  for (const auto [id, value] : MakeView(basis.constraint_status())) {
788  constraint_statuses[linear_constraints_.at(id)] =
789  ToGlopBasisStatus<glop::ConstraintStatus>(
790  static_cast<BasisStatusProto>(value));
791  }
792  lp_solver_.SetInitialBasis(variable_statuses, constraint_statuses);
793 }
794 
795 absl::StatusOr<SolveResultProto> GlopSolver::Solve(
796  const SolveParametersProto& parameters,
797  const ModelSolveParametersProto& model_parameters,
798  const MessageCallback message_cb,
799  const CallbackRegistrationProto& callback_registration, const Callback cb,
800  SolveInterrupter* const interrupter) {
801  RETURN_IF_ERROR(CheckRegisteredCallbackEvents(callback_registration,
802  /*supported_events=*/{}));
803 
804  const absl::Time start = absl::Now();
806  const glop::GlopParameters glop_parameters,
807  MergeSolveParameters(
808  parameters,
809  /*setting_initial_basis=*/model_parameters.has_initial_basis(),
810  /*has_message_callback=*/message_cb != nullptr));
811  lp_solver_.SetParameters(glop_parameters);
812 
813  if (model_parameters.has_initial_basis()) {
814  SetGlopBasis(model_parameters.initial_basis());
815  }
816 
817  std::atomic<bool> interrupt_solve = false;
818  const std::unique_ptr<TimeLimit> time_limit =
819  TimeLimit::FromParameters(lp_solver_.GetParameters());
820  time_limit->RegisterExternalBooleanAsLimit(&interrupt_solve);
821 
822  const ScopedSolveInterrupterCallback scoped_interrupt_cb(interrupter, [&]() {
823  CHECK_NE(interrupter, nullptr);
824  interrupt_solve = true;
825  });
826 
827  if (message_cb != nullptr) {
828  // Please note that the logging is enabled in MergeSolveParameters() where
829  // we also disable logging to stdout. We can't modify the SolverLogger here
830  // since the values are overwritten from the parameters at the beginning of
831  // the solve.
832  //
833  // Here we test that there are no other callbacks since we will clear them
834  // all in the cleanup below.
835  CHECK_EQ(lp_solver_.GetSolverLogger().NumInfoLoggingCallbacks(), 0);
836  lp_solver_.GetSolverLogger().AddInfoLoggingCallback(
837  [&](const std::string& message) {
838  message_cb(absl::StrSplit(message, '\n'));
839  });
840  }
841  const auto message_cb_cleanup = absl::MakeCleanup([&]() {
842  if (message_cb != nullptr) {
843  // Check that no other callbacks have been added to the logger.
844  CHECK_EQ(lp_solver_.GetSolverLogger().NumInfoLoggingCallbacks(), 1);
845  lp_solver_.GetSolverLogger().ClearInfoLoggingCallbacks();
846  }
847  });
848 
849  // Glop returns an error when bounds are inverted and does not list the
850  // offending variables/constraints. Here we want to return a more detailed
851  // status.
852  RETURN_IF_ERROR(ListInvertedBounds().ToStatus());
853 
855  lp_solver_.SolveWithTimeLimit(linear_program_, time_limit.get());
856  const absl::Duration solve_time = absl::Now() - start;
857  return MakeSolveResult(status, model_parameters, interrupter, solve_time);
858 }
859 
860 absl::StatusOr<std::unique_ptr<SolverInterface>> GlopSolver::New(
861  const ModelProto& model, const InitArgs& init_args) {
862  RETURN_IF_ERROR(ModelIsSupported(model, kGlopSupportedStructures, "Glop"));
863  auto solver = absl::WrapUnique(new GlopSolver);
864  // By default Glop CHECKs that bounds are always consistent (lb < ub); thus it
865  // would fail if the initial model or later updates temporarily set inverted
866  // bounds.
867  solver->linear_program_.SetDcheckBounds(false);
868 
869  solver->linear_program_.SetName(model.name());
870  solver->linear_program_.SetMaximizationProblem(model.objective().maximize());
871  solver->linear_program_.SetObjectiveOffset(model.objective().offset());
872 
873  solver->AddVariables(model.variables());
874  solver->SetOrUpdateObjectiveCoefficients(
875  model.objective().linear_coefficients());
876 
877  solver->AddLinearConstraints(model.linear_constraints());
878  solver->SetOrUpdateConstraintMatrix(model.linear_constraint_matrix());
879  solver->linear_program_.CleanUp();
880  return solver;
881 }
882 
883 absl::StatusOr<bool> GlopSolver::Update(const ModelUpdateProto& model_update) {
884  if (!UpdateIsSupported(model_update, kGlopSupportedStructures)) {
885  return false;
886  }
887 
888  if (model_update.objective_updates().has_direction_update()) {
889  linear_program_.SetMaximizationProblem(
890  model_update.objective_updates().direction_update());
891  }
892  if (model_update.objective_updates().has_offset_update()) {
893  linear_program_.SetObjectiveOffset(
894  model_update.objective_updates().offset_update());
895  }
896 
897  DeleteVariables(model_update.deleted_variable_ids());
898  AddVariables(model_update.new_variables());
899 
900  SetOrUpdateObjectiveCoefficients(
901  model_update.objective_updates().linear_coefficients());
902  UpdateVariableBounds(model_update.variable_updates());
903 
904  DeleteLinearConstraints(model_update.deleted_linear_constraint_ids());
905  AddLinearConstraints(model_update.new_linear_constraints());
906  UpdateLinearConstraintBounds(model_update.linear_constraint_updates());
907 
908  SetOrUpdateConstraintMatrix(model_update.linear_constraint_matrix_updates());
909 
910  linear_program_.CleanUp();
911 
912  return true;
913 }
914 
915 MATH_OPT_REGISTER_SOLVER(SOLVER_TYPE_GLOP, GlopSolver::New)
916 
917 } // namespace math_opt
918 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
void push_back(const value_type &x)
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
bool AcceptsAndUpdate(const int64_t id, const Value &value)
SatParameters parameters
ModelSharedTimeLimit * time_limit
int64_t value
absl::Status status
Definition: g_gurobi.cc:41
GRBmodel * model
int index
ColIndex col
Definition: markowitz.cc:186
RowIndex row
Definition: markowitz.cc:185
absl::Cleanup< absl::decay_t< Callback > > MakeCleanup(Callback &&callback)
Definition: cleanup.h:125
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
Definition: map_util.h:154
std::string ValidateParameters(const GlopParameters &params)
std::string GetProblemStatusString(ProblemStatus problem_status)
Definition: lp_types.cc:21
StrictITIVector< ColIndex, VariableStatus > VariableStatusRow
Definition: lp_types.h:362
StrictITIVector< RowIndex, ConstraintStatus > ConstraintStatusColumn
Definition: lp_types.h:387
StrictITIVector< RowIndex, bool > DenseBooleanColumn
Definition: lp_types.h:373
TerminationProto FeasibleTermination(const LimitProto limit, const absl::string_view detail)
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto &registration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
int NumMatrixNonzeros(const SparseDoubleMatrixProto &matrix)
void UpdateIdIndexMap(glop::StrictITIVector< IndexType, bool > indices_to_delete, IndexType num_indices, absl::flat_hash_map< int64_t, IndexType > &id_index_map)
Definition: glop_solver.cc:165
int NumVariables(const VariablesProto &variables)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
SparseDoubleVectorProto FillSparseDoubleVector(const std::vector< int64_t > &ids_in_order, const absl::flat_hash_map< int64_t, IndexType > &id_map, const glop::StrictITIVector< IndexType, glop::Fractional > &values, const SparseVectorFilterProto &filter)
Definition: glop_solver.cc:422
BasisStatusProto FromGlopBasisStatus(const ValueType glop_basis_status)
Definition: glop_solver.cc:441
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
ValueType ToGlopBasisStatus(const BasisStatusProto basis_status)
Definition: glop_solver.cc:473
bool UpdateIsSupported(const ModelUpdateProto &update, const SupportedProblemStructures &support_menu)
std::vector< int64_t > GetSortedIs(const absl::flat_hash_map< int64_t, T > &id_map)
Definition: glop_solver.cc:492
TerminationProto NoSolutionFoundTermination(const LimitProto limit, const absl::string_view detail)
int NumConstraints(const LinearConstraintsProto &linear_constraints)
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
glop::StrictITIVector< T, int64_t > IndexToId(const absl::flat_hash_map< int64_t, T > &id_map)
Definition: glop_solver.cc:507
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
SparseBasisStatusVector FillSparseBasisStatusVector(const std::vector< int64_t > &ids_in_order, const absl::flat_hash_map< int64_t, IndexType > &id_map, const glop::StrictITIVector< IndexType, ValueType > &values)
Definition: glop_solver.cc:458
Collection of objects used to extend the Constraint Solver library.
std::string ProtoEnumToString(ProtoEnumType enum_value)
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 InvalidArgumentErrorBuilder()
int64_t coefficient
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)
int64_t start
std::string message
Definition: trace.cc:399