OR-Tools  9.2
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 "absl/container/flat_hash_map.h"
22#include "absl/status/status.h"
23#include "absl/status/statusor.h"
24#include "absl/strings/str_cat.h"
25#include "Eigen/Core"
26#include "Eigen/SparseCore"
27#include "ortools/pdlp/quadratic_program.h"
30#include "ortools/math_opt/model.pb.h"
31#include "ortools/math_opt/sparse_containers.pb.h"
32
33namespace operations_research {
34namespace math_opt {
35namespace {
36
37absl::StatusOr<SparseDoubleVectorProto> ExtractSolution(
38 const Eigen::VectorXd& values, const std::vector<int64_t>& pdlp_index_to_id,
39 const SparseVectorFilterProto& filter, const double scale) {
40 if (values.size() != pdlp_index_to_id.size()) {
41 return absl::InternalError(
42 absl::StrCat("Expected solution vector with ", pdlp_index_to_id.size(),
43 " elements, found: ", values.size()));
44 }
45 SparseVectorFilterPredicate predicate(filter);
46 SparseDoubleVectorProto result;
47 for (int i = 0; i < pdlp_index_to_id.size(); ++i) {
48 const double value = scale * values[i];
49 const int64_t id = pdlp_index_to_id[i];
50 if (predicate.AcceptsAndUpdate(id, value)) {
51 result.add_ids(id);
52 result.add_values(value);
53 }
54 }
55 return result;
56}
57
58} // namespace
59
60absl::StatusOr<PdlpBridge> PdlpBridge::FromProto(
61 const ModelProto& model_proto) {
62 PdlpBridge result;
63 pdlp::QuadraticProgram& pdlp_lp = result.pdlp_lp_;
64 const VariablesProto& variables = model_proto.variables();
65 const LinearConstraintsProto& linear_constraints =
66 model_proto.linear_constraints();
67 pdlp_lp.ResizeAndInitialize(variables.ids_size(),
68 linear_constraints.ids_size());
69 if (!model_proto.name().empty()) {
70 pdlp_lp.problem_name = model_proto.name();
71 }
72 if (variables.names_size() > 0) {
73 pdlp_lp.variable_names = {variables.names().begin(),
74 variables.names().end()};
75 }
76 if (linear_constraints.names_size() > 0) {
77 pdlp_lp.constraint_names = {linear_constraints.names().begin(),
78 linear_constraints.names().end()};
79 }
80 for (int i = 0; i < variables.ids_size(); ++i) {
81 result.var_id_to_pdlp_index_[variables.ids(i)] = i;
82 result.pdlp_index_to_var_id_.push_back(variables.ids(i));
83 pdlp_lp.variable_lower_bounds[i] = variables.lower_bounds(i);
84 pdlp_lp.variable_upper_bounds[i] = variables.upper_bounds(i);
85 if (variables.integers(i)) {
86 return absl::InvalidArgumentError(
87 "PDLP cannot solve problems with integer variables");
88 }
89 }
90 for (int i = 0; i < linear_constraints.ids_size(); ++i) {
91 result.lin_con_id_to_pdlp_index_[linear_constraints.ids(i)] = i;
92 result.pdlp_index_to_lin_con_id_.push_back(linear_constraints.ids(i));
93 pdlp_lp.constraint_lower_bounds[i] = linear_constraints.lower_bounds(i);
94 pdlp_lp.constraint_upper_bounds[i] = linear_constraints.upper_bounds(i);
95 }
96 const bool is_maximize = model_proto.objective().maximize();
97 const double obj_scale = is_maximize ? -1.0 : 1.0;
98 pdlp_lp.objective_offset = obj_scale * model_proto.objective().offset();
99 for (const auto [var_id, coef] :
100 MakeView(model_proto.objective().linear_coefficients())) {
101 pdlp_lp.objective_vector[result.var_id_to_pdlp_index_.at(var_id)] =
102 obj_scale * coef;
103 }
104 const SparseDoubleMatrixProto& quadratic_objective =
105 model_proto.objective().quadratic_coefficients();
106 std::vector<Eigen::Triplet<double, int64_t>> obj_triplets;
107 const int obj_nnz = quadratic_objective.row_ids().size();
108 for (int i = 0; i < obj_nnz; ++i) {
109 const int64_t row_index =
110 result.var_id_to_pdlp_index_.at(quadratic_objective.row_ids(i));
111 const int64_t column_index =
112 result.var_id_to_pdlp_index_.at(quadratic_objective.column_ids(i));
113 const double value = obj_scale * quadratic_objective.coefficients(i);
114 // MathOpt represents quadratic objectives in "terms" form, i.e. as a sum
115 // of double * Variable * Variable terms. They are stored in upper
116 // triangular form with row_index <= column_index. In contrast, PDLP
117 // represents quadratic objectives in "matrix" form as 1/2 x'Qx; it wants
118 // the symmetric matrix Q. To get to the right format, we simply add each
119 // term and its transposed entry, and defer to Eigen to sum duplicate
120 // entries along the diagonal.
121 obj_triplets.push_back({row_index, column_index, value});
122 obj_triplets.push_back({column_index, row_index, value});
123 }
124 pdlp_lp.objective_matrix.setFromTriplets(obj_triplets.begin(),
125 obj_triplets.end());
126 pdlp_lp.objective_scaling_factor = obj_scale;
127 // Note: MathOpt stores the constraint data in row major order, but PDLP
128 // wants the data in column major order. There is probably a more efficient
129 // method to do this transformation.
130 std::vector<Eigen::Triplet<double, int64_t>> mat_triplets;
131 const int nnz = model_proto.linear_constraint_matrix().row_ids_size();
132 mat_triplets.reserve(nnz);
133 const SparseDoubleMatrixProto& proto_mat =
134 model_proto.linear_constraint_matrix();
135 for (int i = 0; i < nnz; ++i) {
136 const int64_t row_index =
137 result.lin_con_id_to_pdlp_index_.at(proto_mat.row_ids(i));
138 const int64_t column_index =
139 result.var_id_to_pdlp_index_.at(proto_mat.column_ids(i));
140 const double value = proto_mat.coefficients(i);
141 mat_triplets.emplace_back(row_index, column_index, value);
142 }
143 pdlp_lp.constraint_matrix.setFromTriplets(mat_triplets.begin(),
144 mat_triplets.end());
145 return result;
146}
147
148absl::StatusOr<SparseDoubleVectorProto> PdlpBridge::PrimalVariablesToProto(
149 const Eigen::VectorXd& primal_values,
150 const SparseVectorFilterProto& variable_filter) const {
151 return ExtractSolution(primal_values, pdlp_index_to_var_id_, variable_filter,
152 /*scale=*/1.0);
153}
154absl::StatusOr<SparseDoubleVectorProto> PdlpBridge::DualVariablesToProto(
155 const Eigen::VectorXd& dual_values,
156 const SparseVectorFilterProto& linear_constraint_filter) const {
157 return ExtractSolution(dual_values, pdlp_index_to_lin_con_id_,
158 linear_constraint_filter,
159 /*scale=*/pdlp_lp_.objective_scaling_factor);
160}
161absl::StatusOr<SparseDoubleVectorProto> PdlpBridge::ReducedCostsToProto(
162 const Eigen::VectorXd& reduced_costs,
163 const SparseVectorFilterProto& variable_filter) const {
164 return ExtractSolution(reduced_costs, pdlp_index_to_var_id_, variable_filter,
165 /*scale=*/pdlp_lp_.objective_scaling_factor);
166}
167
168} // namespace math_opt
169} // namespace operations_research
const pdlp::QuadraticProgram & pdlp_lp() const
Definition: pdlp_bridge.h:49
absl::StatusOr< SparseDoubleVectorProto > DualVariablesToProto(const Eigen::VectorXd &dual_values, const SparseVectorFilterProto &linear_constraint_filter) const
Definition: pdlp_bridge.cc:154
static absl::StatusOr< PdlpBridge > FromProto(const ModelProto &model_proto)
Definition: pdlp_bridge.cc:60
absl::StatusOr< SparseDoubleVectorProto > PrimalVariablesToProto(const Eigen::VectorXd &primal_values, const SparseVectorFilterProto &variable_filter) const
Definition: pdlp_bridge.cc:148
absl::StatusOr< SparseDoubleVectorProto > ReducedCostsToProto(const Eigen::VectorXd &reduced_costs, const SparseVectorFilterProto &variable_filter) const
Definition: pdlp_bridge.cc:161
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.