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