Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
rank_one_update.h
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
14#ifndef OR_TOOLS_GLOP_RANK_ONE_UPDATE_H_
15#define OR_TOOLS_GLOP_RANK_ONE_UPDATE_H_
16
17#include <vector>
18
24
25namespace operations_research {
26namespace glop {
27
28// This class holds a matrix of the form T = I + u.Tr(v) where I is the
29// identity matrix and u and v are two column vectors of the same size as I. It
30// allows for efficient left and right solves with T. When T is non-singular,
31// it is easy to show that T^{-1} = I - 1 / mu * u.Tr(v) where
32// mu = 1.0 + Tr(v).u
33//
34// Note that when v is a unit vector, T is a regular Eta matrix and when u
35// is a unit vector, T is a row-wise Eta matrix.
36//
37// This is based on section 3.1 of:
38// Qi Huangfu, J. A. Julian Hall, "Novel update techniques for the revised
39// simplex method", 28 january 2013, Technical Report ERGO-13-0001
41 public:
42 // Rather than copying the vectors u and v, RankOneUpdateElementaryMatrix
43 // takes two columns of a provided CompactSparseMatrix which is used for
44 // storage. This has a couple of advantages, especially in the context of the
45 // RankOneUpdateFactorization below:
46 // - It uses less overall memory (and avoid allocation overhead).
47 // - It has a better cache behavior for the RankOneUpdateFactorization solves.
49 ColIndex u_index, ColIndex v_index,
50 Fractional u_dot_v)
51 : storage_(storage),
52 u_index_(u_index),
53 v_index_(v_index),
54 mu_(1.0 + u_dot_v) {}
55
56 // Returns whether or not this matrix is singular.
57 // Note that the RightSolve() and LeftSolve() function will fail if this is
58 // the case.
59 bool IsSingular() const { return mu_ == 0.0; }
60
61 // Solves T.x = rhs with rhs initialy in x (a column vector).
62 // The non-zeros version keeps track of the new non-zeros.
63 void RightSolve(DenseColumn* x) const {
64 DCHECK(!IsSingular());
65 const Fractional multiplier =
66 -storage_->ColumnScalarProduct(v_index_, Transpose(*x)) / mu_;
67 storage_->ColumnAddMultipleToDenseColumn(u_index_, multiplier, x);
68 }
70 DCHECK(!IsSingular());
71 const Fractional multiplier =
72 -storage_->ColumnScalarProduct(v_index_, Transpose(x->values)) / mu_;
73 if (multiplier != 0.0) {
74 storage_->ColumnAddMultipleToSparseScatteredColumn(u_index_, multiplier,
75 x);
76 }
77 }
78
79 // Solves y.T = rhs with rhs initialy in y (a row vector).
80 // The non-zeros version keeps track of the new non-zeros.
81 void LeftSolve(DenseRow* y) const {
82 DCHECK(!IsSingular());
83 const Fractional multiplier =
84 -storage_->ColumnScalarProduct(u_index_, *y) / mu_;
85 storage_->ColumnAddMultipleToDenseColumn(v_index_, multiplier,
86 reinterpret_cast<DenseColumn*>(y));
87 }
89 DCHECK(!IsSingular());
90 const Fractional multiplier =
91 -storage_->ColumnScalarProduct(u_index_, y->values) / mu_;
92 if (multiplier != 0.0) {
94 v_index_, multiplier, reinterpret_cast<ScatteredColumn*>(y));
95 }
96 }
97
98 // Computes T.x for a given column vector.
100 const Fractional multiplier =
101 storage_->ColumnScalarProduct(v_index_, Transpose(*x));
102 storage_->ColumnAddMultipleToDenseColumn(u_index_, multiplier, x);
103 }
104
105 // Computes y.T for a given row vector.
106 void LeftMultiply(DenseRow* y) const {
107 const Fractional multiplier = storage_->ColumnScalarProduct(u_index_, *y);
108 storage_->ColumnAddMultipleToDenseColumn(v_index_, multiplier,
109 reinterpret_cast<DenseColumn*>(y));
110 }
111
112 EntryIndex num_entries() const {
113 return storage_->column(u_index_).num_entries() +
114 storage_->column(v_index_).num_entries();
115 }
116
117 private:
118 // This is only used in debug mode.
119 Fractional ComputeUScalarV() const {
120 DenseColumn dense_u;
121 storage_->ColumnCopyToDenseColumn(u_index_, &dense_u);
122 return storage_->ColumnScalarProduct(v_index_, Transpose(dense_u));
123 }
124
125 // Note that we allow copy and assignment so we can store a
126 // RankOneUpdateElementaryMatrix in an STL container.
127 const CompactSparseMatrix* storage_;
128 ColIndex u_index_;
129 ColIndex v_index_;
130 Fractional mu_;
131};
132
133// A rank one update factorization corresponds to the product of k rank one
134// update elementary matrices, i.e. T = T_0.T_1. ... .T_{k-1}
136 public:
137 // TODO(user): make the 5% a parameter and share it between all the places
138 // that switch between a sparse/dense version.
139 RankOneUpdateFactorization() : hypersparse_ratio_(0.05) {}
140
141 // This type is neither copyable nor movable.
144 delete;
145
146 // This is currently only visible for testing.
147 void set_hypersparse_ratio(double value) { hypersparse_ratio_ = value; }
148
149 // Deletes all elementary matrices of this factorization.
150 void Clear() {
151 elementary_matrices_.clear();
152 num_entries_ = 0;
153 }
154
155 // Updates the factorization.
156 void Update(const RankOneUpdateElementaryMatrix& update_matrix) {
157 elementary_matrices_.push_back(update_matrix);
158 num_entries_ += update_matrix.num_entries();
159 }
160
161 // Left-solves all systems from right to left, i.e. y_i = y_{i+1}.(T_i)^{-1}
162 void LeftSolve(DenseRow* y) const {
164 for (int i = elementary_matrices_.size() - 1; i >= 0; --i) {
165 elementary_matrices_[i].LeftSolve(y);
166 }
167 dtime_ += DeterministicTimeForFpOperations(num_entries_.value());
168 }
169
170 // Same as LeftSolve(), but if the given non_zeros are not empty, then all
171 // the new non-zeros in the result are appended to it.
174 if (y->non_zeros.empty()) {
175 LeftSolve(&y->values);
176 return;
177 }
178
179 // y->is_non_zero is always all false before and after this code.
180 DCHECK(IsAllFalse(y->is_non_zero));
181 y->RepopulateSparseMask();
182 bool use_dense = y->ShouldUseDenseIteration(hypersparse_ratio_);
183 for (int i = elementary_matrices_.size() - 1; i >= 0; --i) {
184 if (use_dense) {
185 elementary_matrices_[i].LeftSolve(&y->values);
186 } else {
187 elementary_matrices_[i].LeftSolveWithNonZeros(y);
188 use_dense = y->ShouldUseDenseIteration(hypersparse_ratio_);
189 }
190 }
191 y->ClearSparseMask();
192 y->ClearNonZerosIfTooDense(hypersparse_ratio_);
193 dtime_ += DeterministicTimeForFpOperations(num_entries_.value());
194 }
195
196 // Right-solves all systems from left to right, i.e. T_i.d_{i+1} = d_i
197 void RightSolve(DenseColumn* d) const {
199 const size_t end = elementary_matrices_.size();
200 for (int i = 0; i < end; ++i) {
201 elementary_matrices_[i].RightSolve(d);
202 }
203 dtime_ += DeterministicTimeForFpOperations(num_entries_.value());
204 }
205
206 // Same as RightSolve(), but if the given non_zeros are not empty, then all
207 // the new non-zeros in the result are appended to it.
210 if (d->non_zeros.empty()) {
211 RightSolve(&d->values);
212 return;
213 }
214
215 // d->is_non_zero is always all false before and after this code.
216 DCHECK(IsAllFalse(d->is_non_zero));
218 bool use_dense = d->ShouldUseDenseIteration(hypersparse_ratio_);
219 const size_t end = elementary_matrices_.size();
220 for (int i = 0; i < end; ++i) {
221 if (use_dense) {
222 elementary_matrices_[i].RightSolve(&d->values);
223 } else {
224 elementary_matrices_[i].RightSolveWithNonZeros(d);
225 use_dense = d->ShouldUseDenseIteration(hypersparse_ratio_);
226 }
227 }
228 d->ClearSparseMask();
229 d->ClearNonZerosIfTooDense(hypersparse_ratio_);
230 dtime_ += DeterministicTimeForFpOperations(num_entries_.value());
231 }
232
233 EntryIndex num_entries() const { return num_entries_; }
234
235 // Deterministic time spent in all the solves function since last reset.
236 //
237 // TODO(user): This is quite precise. However we overcount a bit, because in
238 // each elementary solves, if the scalar product involved is zero, we skip
239 // some of the operations counted here. Is it worth spending a bit more time
240 // to be more precise here?
241 double DeterministicTimeSinceLastReset() const { return dtime_; }
242 void ResetDeterministicTime() { dtime_ = 0.0; }
243
244 private:
245 mutable double dtime_ = 0.0;
246
247 double hypersparse_ratio_;
248 EntryIndex num_entries_;
249 std::vector<RankOneUpdateElementaryMatrix> elementary_matrices_;
250};
251
252} // namespace glop
253} // namespace operations_research
254
255#endif // OR_TOOLS_GLOP_RANK_ONE_UPDATE_H_
IntegerValue y
void ColumnCopyToDenseColumn(ColIndex col, DenseColumn *dense_column) const
Definition sparse.h:473
void ColumnAddMultipleToDenseColumn(ColIndex col, Fractional multiplier, DenseColumn::View dense_column) const
Definition sparse.h:441
Fractional ColumnScalarProduct(ColIndex col, const DenseRow &vector) const
Definition sparse.h:434
ColumnView column(ColIndex col) const
Definition sparse.h:416
void ColumnAddMultipleToSparseScatteredColumn(ColIndex col, Fractional multiplier, ScatteredColumn *column) const
Definition sparse.h:459
void LeftMultiply(DenseRow *y) const
Computes y.T for a given row vector.
RankOneUpdateElementaryMatrix(const CompactSparseMatrix *storage, ColIndex u_index, ColIndex v_index, Fractional u_dot_v)
void RightMultiply(DenseColumn *x) const
Computes T.x for a given column vector.
RankOneUpdateFactorization & operator=(const RankOneUpdateFactorization &)=delete
void RightSolve(DenseColumn *d) const
Right-solves all systems from left to right, i.e. T_i.d_{i+1} = d_i.
void set_hypersparse_ratio(double value)
This is currently only visible for testing.
void Clear()
Deletes all elementary matrices of this factorization.
void LeftSolve(DenseRow *y) const
Left-solves all systems from right to left, i.e. y_i = y_{i+1}.(T_i)^{-1}.
void Update(const RankOneUpdateElementaryMatrix &update_matrix)
Updates the factorization.
RankOneUpdateFactorization(const RankOneUpdateFactorization &)=delete
This type is neither copyable nor movable.
int64_t value
const DenseRow & Transpose(const DenseColumn &col)
Similar comment as the other Transpose() implementation above.
Definition lp_utils.h:199
bool IsAllFalse(const BoolVector &v)
Returns true if the given vector of bool is all false.
Definition lp_utils.h:238
static double DeterministicTimeForFpOperations(int64_t n)
Definition lp_types.h:435
In SWIG mode, we don't want anything besides these top-level includes.
const Variable x
Definition qp_tests.cc:127
#define RETURN_IF_NULL(x)
std::optional< int64_t > end
void ClearNonZerosIfTooDense(double ratio_for_using_dense_representation)
void ClearSparseMask()
Efficiently clears the is_non_zero vector.
StrictITIVector< Index, Fractional > values
void RepopulateSparseMask()
Update the is_non_zero vector to be consistent with the non_zeros vector.
bool ShouldUseDenseIteration(double ratio_for_using_dense_representation) const
StrictITIVector< Index, bool > is_non_zero