OR-Tools  9.3
pdlp_bridge.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <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"
30#include "ortools/math_opt/model.pb.h"
31#include "ortools/math_opt/sparse_containers.pb.h"
33
34namespace operations_research {
35namespace math_opt {
36namespace {
37
38absl::StatusOr<SparseDoubleVectorProto> ExtractSolution(
39 const Eigen::VectorXd& values, const std::vector<int64_t>& pdlp_index_to_id,
40 const SparseVectorFilterProto& filter, const double scale) {
41 if (values.size() != pdlp_index_to_id.size()) {
42 return absl::InternalError(
43 absl::StrCat("Expected solution vector with ", pdlp_index_to_id.size(),
44 " elements, found: ", values.size()));
45 }
46 SparseVectorFilterPredicate predicate(filter);
47 SparseDoubleVectorProto result;
48 for (int i = 0; i < pdlp_index_to_id.size(); ++i) {
49 const double value = scale * values[i];
50 const int64_t id = pdlp_index_to_id[i];
51 if (predicate.AcceptsAndUpdate(id, value)) {
52 result.add_ids(id);
53 result.add_values(value);
54 }
55 }
56 return result;
57}
58
59} // namespace
60
61absl::StatusOr<PdlpBridge> PdlpBridge::FromProto(
62 const ModelProto& model_proto) {
63 PdlpBridge result;
64 pdlp::QuadraticProgram& pdlp_lp = result.pdlp_lp_;
65 const VariablesProto& variables = model_proto.variables();
66 const LinearConstraintsProto& linear_constraints =
67 model_proto.linear_constraints();
68 pdlp_lp.ResizeAndInitialize(variables.ids_size(),
69 linear_constraints.ids_size());
70 if (!model_proto.name().empty()) {
72 }
73 if (variables.names_size() > 0) {
74 pdlp_lp.variable_names = {variables.names().begin(),
75 variables.names().end()};
76 }
77 if (linear_constraints.names_size() > 0) {
78 pdlp_lp.constraint_names = {linear_constraints.names().begin(),
79 linear_constraints.names().end()};
80 }
81 for (int i = 0; i < variables.ids_size(); ++i) {
82 result.var_id_to_pdlp_index_[variables.ids(i)] = i;
83 result.pdlp_index_to_var_id_.push_back(variables.ids(i));
84 pdlp_lp.variable_lower_bounds[i] = variables.lower_bounds(i);
85 pdlp_lp.variable_upper_bounds[i] = variables.upper_bounds(i);
86 if (variables.integers(i)) {
87 return absl::InvalidArgumentError(
88 "PDLP cannot solve problems with integer variables");
89 }
90 }
91 for (int i = 0; i < linear_constraints.ids_size(); ++i) {
92 result.lin_con_id_to_pdlp_index_[linear_constraints.ids(i)] = i;
93 result.pdlp_index_to_lin_con_id_.push_back(linear_constraints.ids(i));
94 pdlp_lp.constraint_lower_bounds[i] = linear_constraints.lower_bounds(i);
95 pdlp_lp.constraint_upper_bounds[i] = linear_constraints.upper_bounds(i);
96 }
97 const bool is_maximize = model_proto.objective().maximize();
98 const double obj_scale = is_maximize ? -1.0 : 1.0;
99 pdlp_lp.objective_offset = obj_scale * model_proto.objective().offset();
100 for (const auto [var_id, coef] :
101 MakeView(model_proto.objective().linear_coefficients())) {
102 pdlp_lp.objective_vector[result.var_id_to_pdlp_index_.at(var_id)] =
103 obj_scale * coef;
104 }
105 const SparseDoubleMatrixProto& quadratic_objective =
106 model_proto.objective().quadratic_coefficients();
107 const int obj_nnz = quadratic_objective.row_ids().size();
108 if (obj_nnz > 0) {
109 pdlp_lp.objective_matrix.emplace();
110 pdlp_lp.objective_matrix->setZero(variables.ids_size());
111 }
112 for (int i = 0; i < obj_nnz; ++i) {
113 const int64_t row_index =
114 result.var_id_to_pdlp_index_.at(quadratic_objective.row_ids(i));
115 const int64_t column_index =
116 result.var_id_to_pdlp_index_.at(quadratic_objective.column_ids(i));
117 const double value = obj_scale * quadratic_objective.coefficients(i);
118 if (row_index != column_index) {
119 return absl::InvalidArgumentError(
120 "PDLP cannot solve problems with non-diagonal objective matrices");
121 }
122 // MathOpt represents quadratic objectives in "terms" form, i.e. as a sum
123 // of double * Variable * Variable terms. They are stored in upper
124 // triangular form with row_index <= column_index. In contrast, PDLP
125 // represents quadratic objectives in "matrix" form as 1/2 x'Qx, where Q is
126 // diagonal. To get to the right format, we simply double each diagonal
127 // entry.
128 pdlp_lp.objective_matrix->diagonal()[row_index] = 2 * value;
129 }
131 // Note: MathOpt stores the constraint data in row major order, but PDLP
132 // wants the data in column major order. There is probably a more efficient
133 // method to do this transformation.
134 std::vector<Eigen::Triplet<double, int64_t>> mat_triplets;
135 const int nnz = model_proto.linear_constraint_matrix().row_ids_size();
136 mat_triplets.reserve(nnz);
137 const SparseDoubleMatrixProto& proto_mat =
138 model_proto.linear_constraint_matrix();
139 for (int i = 0; i < nnz; ++i) {
140 const int64_t row_index =
141 result.lin_con_id_to_pdlp_index_.at(proto_mat.row_ids(i));
142 const int64_t column_index =
143 result.var_id_to_pdlp_index_.at(proto_mat.column_ids(i));
144 const double value = proto_mat.coefficients(i);
145 mat_triplets.emplace_back(row_index, column_index, value);
146 }
147 pdlp_lp.constraint_matrix.setFromTriplets(mat_triplets.begin(),
148 mat_triplets.end());
149 return result;
150}
151
153 InvertedBounds inverted_bounds;
154 for (int64_t var_index = 0; var_index < pdlp_index_to_var_id_.size();
155 ++var_index) {
156 if (pdlp_lp_.variable_lower_bounds[var_index] >
157 pdlp_lp_.variable_upper_bounds[var_index]) {
158 inverted_bounds.variables.push_back(pdlp_index_to_var_id_[var_index]);
159 }
160 }
161 for (int64_t lin_con_index = 0;
162 lin_con_index < pdlp_index_to_lin_con_id_.size(); ++lin_con_index) {
163 if (pdlp_lp_.constraint_lower_bounds[lin_con_index] >
164 pdlp_lp_.constraint_upper_bounds[lin_con_index]) {
165 inverted_bounds.linear_constraints.push_back(
166 pdlp_index_to_lin_con_id_[lin_con_index]);
167 }
168 }
169 return inverted_bounds;
170}
171
172absl::StatusOr<SparseDoubleVectorProto> PdlpBridge::PrimalVariablesToProto(
173 const Eigen::VectorXd& primal_values,
174 const SparseVectorFilterProto& variable_filter) const {
175 return ExtractSolution(primal_values, pdlp_index_to_var_id_, variable_filter,
176 /*scale=*/1.0);
177}
178absl::StatusOr<SparseDoubleVectorProto> PdlpBridge::DualVariablesToProto(
179 const Eigen::VectorXd& dual_values,
180 const SparseVectorFilterProto& linear_constraint_filter) const {
181 return ExtractSolution(dual_values, pdlp_index_to_lin_con_id_,
182 linear_constraint_filter,
183 /*scale=*/pdlp_lp_.objective_scaling_factor);
184}
185absl::StatusOr<SparseDoubleVectorProto> PdlpBridge::ReducedCostsToProto(
186 const Eigen::VectorXd& reduced_costs,
187 const SparseVectorFilterProto& variable_filter) const {
188 return ExtractSolution(reduced_costs, pdlp_index_to_var_id_, variable_filter,
189 /*scale=*/pdlp_lp_.objective_scaling_factor);
190}
191
192} // namespace math_opt
193} // namespace operations_research
const pdlp::QuadraticProgram & pdlp_lp() const
Definition: pdlp_bridge.h:50
absl::StatusOr< SparseDoubleVectorProto > DualVariablesToProto(const Eigen::VectorXd &dual_values, const SparseVectorFilterProto &linear_constraint_filter) const
Definition: pdlp_bridge.cc:178
static absl::StatusOr< PdlpBridge > FromProto(const ModelProto &model_proto)
Definition: pdlp_bridge.cc:61
InvertedBounds ListInvertedBounds() const
Definition: pdlp_bridge.cc:152
absl::StatusOr< SparseDoubleVectorProto > PrimalVariablesToProto(const Eigen::VectorXd &primal_values, const SparseVectorFilterProto &variable_filter) const
Definition: pdlp_bridge.cc:172
absl::StatusOr< SparseDoubleVectorProto > ReducedCostsToProto(const Eigen::VectorXd &reduced_costs, const SparseVectorFilterProto &variable_filter) const
Definition: pdlp_bridge.cc:185
CpModelProto const * model_proto
int64_t value
int64_t coef
Definition: expr_array.cc:1875
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
Collection of objects used to extend the Constraint Solver library.
absl::optional< std::vector< std::string > > constraint_names
absl::optional< std::vector< std::string > > variable_names
absl::optional< std::string > problem_name
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