OR-Tools  9.6
pdlp_bridge.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 <cstdint>
17 #include <optional>
18 #include <string>
19 #include <vector>
20 
21 #include "Eigen/Core"
22 #include "Eigen/SparseCore"
23 #include "absl/container/flat_hash_map.h"
24 #include "absl/status/status.h"
25 #include "absl/status/statusor.h"
26 #include "absl/strings/str_cat.h"
31 #include "ortools/math_opt/model.pb.h"
32 #include "ortools/math_opt/solution.pb.h"
33 #include "ortools/math_opt/sparse_containers.pb.h"
35 
36 namespace operations_research {
37 namespace math_opt {
38 namespace {
39 
40 constexpr SupportedProblemStructures kPdlpSupportedStructures = {
41  .quadratic_objectives = SupportType::kSupported};
42 
43 absl::StatusOr<SparseDoubleVectorProto> ExtractSolution(
44  const Eigen::VectorXd& values, const std::vector<int64_t>& pdlp_index_to_id,
45  const SparseVectorFilterProto& filter, const double scale) {
46  if (values.size() != pdlp_index_to_id.size()) {
47  return absl::InternalError(
48  absl::StrCat("Expected solution vector with ", pdlp_index_to_id.size(),
49  " elements, found: ", values.size()));
50  }
51  SparseVectorFilterPredicate predicate(filter);
52  SparseDoubleVectorProto result;
53  for (int i = 0; i < pdlp_index_to_id.size(); ++i) {
54  const double value = scale * values[i];
55  const int64_t id = pdlp_index_to_id[i];
56  if (predicate.AcceptsAndUpdate(id, value)) {
57  result.add_ids(id);
58  result.add_values(value);
59  }
60  }
61  return result;
62 }
63 
64 // We are implicitly assuming that all missing IDs have correspoding value 0.
65 Eigen::VectorXd EncodeSolution(
66  const SparseDoubleVectorProto& values,
67  const absl::flat_hash_map<int64_t, int64_t>& id_to_pdlp_index,
68  const double scale) {
69  Eigen::VectorXd pdlp_vector(Eigen::VectorXd::Zero(id_to_pdlp_index.size()));
70  const int num_values = values.values_size();
71  for (int k = 0; k < num_values; ++k) {
72  const int64_t index = id_to_pdlp_index.at(values.ids(k));
73  pdlp_vector[index] = values.values(k) / scale;
74  }
75  return pdlp_vector;
76 }
77 
78 } // namespace
79 
80 absl::StatusOr<PdlpBridge> PdlpBridge::FromProto(
81  const ModelProto& model_proto) {
83  ModelIsSupported(model_proto, kPdlpSupportedStructures, "PDLP"));
84  PdlpBridge result;
85  pdlp::QuadraticProgram& pdlp_lp = result.pdlp_lp_;
86  const VariablesProto& variables = model_proto.variables();
87  const LinearConstraintsProto& linear_constraints =
88  model_proto.linear_constraints();
89  pdlp_lp.ResizeAndInitialize(variables.ids_size(),
90  linear_constraints.ids_size());
91  if (!model_proto.name().empty()) {
93  }
94  if (variables.names_size() > 0) {
95  pdlp_lp.variable_names = {variables.names().begin(),
96  variables.names().end()};
97  }
98  if (linear_constraints.names_size() > 0) {
99  pdlp_lp.constraint_names = {linear_constraints.names().begin(),
100  linear_constraints.names().end()};
101  }
102  for (int i = 0; i < variables.ids_size(); ++i) {
103  result.var_id_to_pdlp_index_[variables.ids(i)] = i;
104  result.pdlp_index_to_var_id_.push_back(variables.ids(i));
105  pdlp_lp.variable_lower_bounds[i] = variables.lower_bounds(i);
106  pdlp_lp.variable_upper_bounds[i] = variables.upper_bounds(i);
107  }
108  for (int i = 0; i < linear_constraints.ids_size(); ++i) {
109  result.lin_con_id_to_pdlp_index_[linear_constraints.ids(i)] = i;
110  result.pdlp_index_to_lin_con_id_.push_back(linear_constraints.ids(i));
111  pdlp_lp.constraint_lower_bounds[i] = linear_constraints.lower_bounds(i);
112  pdlp_lp.constraint_upper_bounds[i] = linear_constraints.upper_bounds(i);
113  }
114  const bool is_maximize = model_proto.objective().maximize();
115  const double obj_scale = is_maximize ? -1.0 : 1.0;
116  pdlp_lp.objective_offset = obj_scale * model_proto.objective().offset();
117  for (const auto [var_id, coef] :
118  MakeView(model_proto.objective().linear_coefficients())) {
119  pdlp_lp.objective_vector[result.var_id_to_pdlp_index_.at(var_id)] =
120  obj_scale * coef;
121  }
122  const SparseDoubleMatrixProto& quadratic_objective =
123  model_proto.objective().quadratic_coefficients();
124  const int obj_nnz = quadratic_objective.row_ids().size();
125  if (obj_nnz > 0) {
126  pdlp_lp.objective_matrix.emplace();
127  pdlp_lp.objective_matrix->setZero(variables.ids_size());
128  }
129  for (int i = 0; i < obj_nnz; ++i) {
130  const int64_t row_index =
131  result.var_id_to_pdlp_index_.at(quadratic_objective.row_ids(i));
132  const int64_t column_index =
133  result.var_id_to_pdlp_index_.at(quadratic_objective.column_ids(i));
134  const double value = obj_scale * quadratic_objective.coefficients(i);
135  if (row_index != column_index) {
136  return absl::InvalidArgumentError(
137  "PDLP cannot solve problems with non-diagonal objective matrices");
138  }
139  // MathOpt represents quadratic objectives in "terms" form, i.e. as a sum
140  // of double * Variable * Variable terms. They are stored in upper
141  // triangular form with row_index <= column_index. In contrast, PDLP
142  // represents quadratic objectives in "matrix" form as 1/2 x'Qx, where Q is
143  // diagonal. To get to the right format, we simply double each diagonal
144  // entry.
145  pdlp_lp.objective_matrix->diagonal()[row_index] = 2 * value;
146  }
147  pdlp_lp.objective_scaling_factor = obj_scale;
148  // Note: MathOpt stores the constraint data in row major order, but PDLP
149  // wants the data in column major order. There is probably a more efficient
150  // method to do this transformation.
151  std::vector<Eigen::Triplet<double, int64_t>> mat_triplets;
152  const int nnz = model_proto.linear_constraint_matrix().row_ids_size();
153  mat_triplets.reserve(nnz);
154  const SparseDoubleMatrixProto& proto_mat =
155  model_proto.linear_constraint_matrix();
156  for (int i = 0; i < nnz; ++i) {
157  const int64_t row_index =
158  result.lin_con_id_to_pdlp_index_.at(proto_mat.row_ids(i));
159  const int64_t column_index =
160  result.var_id_to_pdlp_index_.at(proto_mat.column_ids(i));
161  const double value = proto_mat.coefficients(i);
162  mat_triplets.emplace_back(row_index, column_index, value);
163  }
164  pdlp_lp.constraint_matrix.setFromTriplets(mat_triplets.begin(),
165  mat_triplets.end());
166  return result;
167 }
168 
170  InvertedBounds inverted_bounds;
171  for (int64_t var_index = 0; var_index < pdlp_index_to_var_id_.size();
172  ++var_index) {
173  if (pdlp_lp_.variable_lower_bounds[var_index] >
174  pdlp_lp_.variable_upper_bounds[var_index]) {
175  inverted_bounds.variables.push_back(pdlp_index_to_var_id_[var_index]);
176  }
177  }
178  for (int64_t lin_con_index = 0;
179  lin_con_index < pdlp_index_to_lin_con_id_.size(); ++lin_con_index) {
180  if (pdlp_lp_.constraint_lower_bounds[lin_con_index] >
181  pdlp_lp_.constraint_upper_bounds[lin_con_index]) {
182  inverted_bounds.linear_constraints.push_back(
183  pdlp_index_to_lin_con_id_[lin_con_index]);
184  }
185  }
186  return inverted_bounds;
187 }
188 
189 absl::StatusOr<SparseDoubleVectorProto> PdlpBridge::PrimalVariablesToProto(
190  const Eigen::VectorXd& primal_values,
191  const SparseVectorFilterProto& variable_filter) const {
192  return ExtractSolution(primal_values, pdlp_index_to_var_id_, variable_filter,
193  /*scale=*/1.0);
194 }
195 absl::StatusOr<SparseDoubleVectorProto> PdlpBridge::DualVariablesToProto(
196  const Eigen::VectorXd& dual_values,
197  const SparseVectorFilterProto& linear_constraint_filter) const {
198  return ExtractSolution(dual_values, pdlp_index_to_lin_con_id_,
199  linear_constraint_filter,
200  /*scale=*/pdlp_lp_.objective_scaling_factor);
201 }
202 absl::StatusOr<SparseDoubleVectorProto> PdlpBridge::ReducedCostsToProto(
203  const Eigen::VectorXd& reduced_costs,
204  const SparseVectorFilterProto& variable_filter) const {
205  return ExtractSolution(reduced_costs, pdlp_index_to_var_id_, variable_filter,
206  /*scale=*/pdlp_lp_.objective_scaling_factor);
207 }
208 
210  const SolutionHintProto& solution_hint) const {
211  // We are implicitly assuming that all missing IDs have correspoding value 0.
213  result.primal_solution = EncodeSolution(solution_hint.variable_values(),
214  var_id_to_pdlp_index_, /*scale=*/1.0);
215  result.dual_solution =
216  EncodeSolution(solution_hint.dual_values(), lin_con_id_to_pdlp_index_,
217  /*scale=*/pdlp_lp_.objective_scaling_factor);
218  return result;
219 }
220 
221 } // namespace math_opt
222 } // namespace operations_research
#define RETURN_IF_ERROR(expr)
absl::StatusOr< SparseDoubleVectorProto > DualVariablesToProto(const Eigen::VectorXd &dual_values, const SparseVectorFilterProto &linear_constraint_filter) const
Definition: pdlp_bridge.cc:195
const pdlp::QuadraticProgram & pdlp_lp() const
Definition: pdlp_bridge.h:53
static absl::StatusOr< PdlpBridge > FromProto(const ModelProto &model_proto)
Definition: pdlp_bridge.cc:80
InvertedBounds ListInvertedBounds() const
Definition: pdlp_bridge.cc:169
pdlp::PrimalAndDualSolution SolutionHintToWarmStart(const SolutionHintProto &solution_hint) const
Definition: pdlp_bridge.cc:209
absl::StatusOr< SparseDoubleVectorProto > PrimalVariablesToProto(const Eigen::VectorXd &primal_values, const SparseVectorFilterProto &variable_filter) const
Definition: pdlp_bridge.cc:189
absl::StatusOr< SparseDoubleVectorProto > ReducedCostsToProto(const Eigen::VectorXd &reduced_costs, const SparseVectorFilterProto &variable_filter) const
Definition: pdlp_bridge.cc:202
CpModelProto const * model_proto
int64_t value
int64_t coef
Definition: expr_array.cc:1875
int index
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
Collection of objects used to extend the Constraint Solver library.
int64_t Zero()
NOLINT.
std::optional< std::vector< std::string > > constraint_names
std::optional< std::vector< std::string > > variable_names
void ResizeAndInitialize(int64_t num_variables, int64_t num_constraints)
Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > constraint_matrix
std::optional< Eigen::DiagonalMatrix< double, Eigen::Dynamic > > objective_matrix