Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
sharded_quadratic_program.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 <memory>
18#include <optional>
19#include <utility>
20
21#include "Eigen/Core"
22#include "Eigen/SparseCore"
23#include "absl/log/check.h"
24#include "absl/strings/string_view.h"
30
32
33namespace {
34
35// Logs a warning if `matrix` has more than `density_limit` non-zeros in
36// a single column.
37void WarnIfMatrixUnbalanced(
38 const Eigen::SparseMatrix<double, Eigen::ColMajor, int64_t>& matrix,
39 absl::string_view matrix_name, int64_t density_limit,
41 if (matrix.cols() == 0) return;
42 int64_t worst_column = 0;
43 for (int64_t col = 0; col < matrix.cols(); ++col) {
44 if (matrix.col(col).nonZeros() > matrix.col(worst_column).nonZeros()) {
45 worst_column = col;
46 }
47 }
48 if (matrix.col(worst_column).nonZeros() > density_limit) {
49 // TODO(user): fix this automatically in presolve instead of asking the
50 // user to do it.
51 if (logger) {
53 logger, "WARNING: The ", matrix_name, " has ",
54 matrix.col(worst_column).nonZeros(), " non-zeros in its ",
55 worst_column,
56 "th column. For best parallelization all rows and columns should "
57 "have at most order ",
58 density_limit,
59 " non-zeros. Consider rewriting the QP to split the corresponding "
60 "variable or constraint.");
61 } else {
62 LOG(WARNING)
63 << "The " << matrix_name << " has "
64 << matrix.col(worst_column).nonZeros() << " non-zeros in its "
65 << worst_column
66 << "th column. For best parallelization all rows and columns should "
67 "have at most order "
68 << density_limit
69 << " non-zeros. Consider rewriting the QP to split the corresponding "
70 "variable or constraint.";
71 }
72 }
73}
74
75} // namespace
76
78 QuadraticProgram qp, const int num_threads, const int num_shards,
80 : qp_(std::move(qp)),
81 transposed_constraint_matrix_(qp_.constraint_matrix.transpose()),
82 thread_pool_(num_threads == 1
83 ? nullptr
84 : std::make_unique<ThreadPool>("PDLP", num_threads)),
85 constraint_matrix_sharder_(qp_.constraint_matrix, num_shards,
86 thread_pool_.get()),
87 transposed_constraint_matrix_sharder_(transposed_constraint_matrix_,
88 num_shards, thread_pool_.get()),
89 primal_sharder_(qp_.variable_lower_bounds.size(), num_shards,
90 thread_pool_.get()),
91 dual_sharder_(qp_.constraint_lower_bounds.size(), num_shards,
92 thread_pool_.get()) {
93 CHECK_GE(num_threads, 1);
94 CHECK_GE(num_shards, num_threads);
95 if (num_threads > 1) {
96 thread_pool_->StartWorkers();
97 const int64_t work_per_iteration = qp_.constraint_matrix.nonZeros() +
98 qp_.variable_lower_bounds.size() +
99 qp_.constraint_lower_bounds.size();
100 const int64_t column_density_limit = work_per_iteration / num_threads;
101 WarnIfMatrixUnbalanced(qp_.constraint_matrix, "constraint matrix",
102 column_density_limit, logger);
103 WarnIfMatrixUnbalanced(transposed_constraint_matrix_,
104 "transposed constraint matrix", column_density_limit,
105 logger);
106 }
107}
108
109namespace {
110
111// Multiply each entry of `matrix` by the corresponding element of
112// `row_scaling_vec` and `col_scaling_vec`, i.e.,
113// `matrix[i,j] *= row_scaling_vec[i] * col_scaling_vec[j]`.
114void ScaleMatrix(
115 const Eigen::VectorXd& col_scaling_vec,
116 const Eigen::VectorXd& row_scaling_vec, const Sharder& sharder,
117 Eigen::SparseMatrix<double, Eigen::ColMajor, int64_t>& matrix) {
118 CHECK_EQ(matrix.cols(), col_scaling_vec.size());
119 CHECK_EQ(matrix.rows(), row_scaling_vec.size());
120 sharder.ParallelForEachShard([&](const Sharder::Shard& shard) {
121 auto matrix_shard = shard(matrix);
122 auto col_scaling_vec_shard = shard(col_scaling_vec);
123 for (int64_t col_num = 0; col_num < shard(matrix).outerSize(); ++col_num) {
124 for (decltype(matrix_shard)::InnerIterator it(matrix_shard, col_num); it;
125 ++it) {
126 it.valueRef() *=
127 row_scaling_vec[it.row()] * col_scaling_vec_shard[it.col()];
128 }
129 }
130 });
131}
132
133void ReplaceLargeValuesWithInfinity(const double threshold,
134 const Sharder& sharder,
135 Eigen::VectorXd& vector) {
136 constexpr double kInfinity = std::numeric_limits<double>::infinity();
137 sharder.ParallelForEachShard([&](const Sharder::Shard& shard) {
138 auto vector_shard = shard(vector);
139 for (int64_t i = 0; i < vector_shard.size(); ++i) {
140 if (vector_shard[i] <= -threshold) vector_shard[i] = -kInfinity;
141 if (vector_shard[i] >= threshold) vector_shard[i] = kInfinity;
142 }
143 });
144}
145
146} // namespace
147
149 const Eigen::VectorXd& col_scaling_vec,
150 const Eigen::VectorXd& row_scaling_vec) {
151 CHECK_EQ(PrimalSize(), col_scaling_vec.size());
152 CHECK_EQ(DualSize(), row_scaling_vec.size());
153 primal_sharder_.ParallelForEachShard([&](const Sharder::Shard& shard) {
154 CHECK((shard(col_scaling_vec).array() > 0.0).all());
155 shard(qp_.objective_vector) =
156 shard(qp_.objective_vector).cwiseProduct(shard(col_scaling_vec));
157 shard(qp_.variable_lower_bounds) =
158 shard(qp_.variable_lower_bounds).cwiseQuotient(shard(col_scaling_vec));
159 shard(qp_.variable_upper_bounds) =
160 shard(qp_.variable_upper_bounds).cwiseQuotient(shard(col_scaling_vec));
161 if (!IsLinearProgram(qp_)) {
162 shard(qp_.objective_matrix->diagonal()) =
163 shard(qp_.objective_matrix->diagonal())
164 .cwiseProduct(
165 shard(col_scaling_vec).cwiseProduct(shard(col_scaling_vec)));
166 }
167 });
168 dual_sharder_.ParallelForEachShard([&](const Sharder::Shard& shard) {
169 CHECK((shard(row_scaling_vec).array() > 0.0).all());
170 shard(qp_.constraint_lower_bounds) =
171 shard(qp_.constraint_lower_bounds).cwiseProduct(shard(row_scaling_vec));
172 shard(qp_.constraint_upper_bounds) =
173 shard(qp_.constraint_upper_bounds).cwiseProduct(shard(row_scaling_vec));
174 });
175
176 ScaleMatrix(col_scaling_vec, row_scaling_vec, constraint_matrix_sharder_,
178 ScaleMatrix(row_scaling_vec, col_scaling_vec,
179 transposed_constraint_matrix_sharder_,
180 transposed_constraint_matrix_);
181}
182
184 const double threshold) {
185 ReplaceLargeValuesWithInfinity(threshold, DualSharder(),
187 ReplaceLargeValuesWithInfinity(threshold, DualSharder(),
189}
190
192 int64_t constraint_index, std::optional<double> lower_bound,
193 std::optional<double> upper_bound) {
194 CHECK_LT(constraint_index, DualSize());
195 CHECK_GE(constraint_index, 0);
196 if (lower_bound.has_value()) {
198 }
199 if (upper_bound.has_value()) {
201 }
202}
203
204} // namespace operations_research::pdlp
IntegerValue size
void SetConstraintBounds(int64_t constraint_index, std::optional< double > lower_bound, std::optional< double > upper_bound)
ShardedQuadraticProgram(QuadraticProgram qp, int num_threads, int num_shards, operations_research::SolverLogger *logger=nullptr)
const Sharder & DualSharder() const
Returns a Sharder intended for dual vectors.
void RescaleQuadraticProgram(const Eigen::VectorXd &col_scaling_vec, const Eigen::VectorXd &row_scaling_vec)
void ParallelForEachShard(const std::function< void(const Shard &)> &func) const
Runs func on each of the shards.
Definition sharder.cc:109
double upper_bound
double lower_bound
int constraint_index
ColIndex col
Definition markowitz.cc:187
Validation utilities for solvers.proto.
bool IsLinearProgram(const QuadraticProgram &qp)
STL namespace.
std::optional< Eigen::DiagonalMatrix< double, Eigen::Dynamic > > objective_matrix
Eigen::SparseMatrix< double, Eigen::ColMajor, int64_t > constraint_matrix
#define SOLVER_LOG(logger,...)
Definition logging.h:109