OR-Tools  9.6
rank_one_update.h
Go to the documentation of this file.
1 // Copyright 2010-2022 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 
19 #include "ortools/base/logging.h"
23 #include "ortools/lp_data/sparse.h"
24 
25 namespace operations_research {
26 namespace 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.
99  void RightMultiply(DenseColumn* x) const {
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 is currently only visible for testing.
142  void set_hypersparse_ratio(double value) { hypersparse_ratio_ = value; }
143 
144  // Deletes all elementary matrices of this factorization.
145  void Clear() {
146  elementary_matrices_.clear();
147  num_entries_ = 0;
148  }
149 
150  // Updates the factorization.
151  void Update(const RankOneUpdateElementaryMatrix& update_matrix) {
152  elementary_matrices_.push_back(update_matrix);
153  num_entries_ += update_matrix.num_entries();
154  }
155 
156  // Left-solves all systems from right to left, i.e. y_i = y_{i+1}.(T_i)^{-1}
157  void LeftSolve(DenseRow* y) const {
158  RETURN_IF_NULL(y);
159  for (int i = elementary_matrices_.size() - 1; i >= 0; --i) {
160  elementary_matrices_[i].LeftSolve(y);
161  }
162  dtime_ += DeterministicTimeForFpOperations(num_entries_.value());
163  }
164 
165  // Same as LeftSolve(), but if the given non_zeros are not empty, then all
166  // the new non-zeros in the result are appended to it.
168  RETURN_IF_NULL(y);
169  if (y->non_zeros.empty()) {
170  LeftSolve(&y->values);
171  return;
172  }
173 
174  // y->is_non_zero is always all false before and after this code.
175  DCHECK(IsAllFalse(y->is_non_zero));
177  bool use_dense = y->ShouldUseDenseIteration(hypersparse_ratio_);
178  for (int i = elementary_matrices_.size() - 1; i >= 0; --i) {
179  if (use_dense) {
180  elementary_matrices_[i].LeftSolve(&y->values);
181  } else {
182  elementary_matrices_[i].LeftSolveWithNonZeros(y);
183  use_dense = y->ShouldUseDenseIteration(hypersparse_ratio_);
184  }
185  }
186  y->ClearSparseMask();
187  y->ClearNonZerosIfTooDense(hypersparse_ratio_);
188  dtime_ += DeterministicTimeForFpOperations(num_entries_.value());
189  }
190 
191  // Right-solves all systems from left to right, i.e. T_i.d_{i+1} = d_i
192  void RightSolve(DenseColumn* d) const {
193  RETURN_IF_NULL(d);
194  const size_t end = elementary_matrices_.size();
195  for (int i = 0; i < end; ++i) {
196  elementary_matrices_[i].RightSolve(d);
197  }
198  dtime_ += DeterministicTimeForFpOperations(num_entries_.value());
199  }
200 
201  // Same as RightSolve(), but if the given non_zeros are not empty, then all
202  // the new non-zeros in the result are appended to it.
204  RETURN_IF_NULL(d);
205  if (d->non_zeros.empty()) {
206  RightSolve(&d->values);
207  return;
208  }
209 
210  // d->is_non_zero is always all false before and after this code.
211  DCHECK(IsAllFalse(d->is_non_zero));
213  bool use_dense = d->ShouldUseDenseIteration(hypersparse_ratio_);
214  const size_t end = elementary_matrices_.size();
215  for (int i = 0; i < end; ++i) {
216  if (use_dense) {
217  elementary_matrices_[i].RightSolve(&d->values);
218  } else {
219  elementary_matrices_[i].RightSolveWithNonZeros(d);
220  use_dense = d->ShouldUseDenseIteration(hypersparse_ratio_);
221  }
222  }
223  d->ClearSparseMask();
224  d->ClearNonZerosIfTooDense(hypersparse_ratio_);
225  dtime_ += DeterministicTimeForFpOperations(num_entries_.value());
226  }
227 
228  EntryIndex num_entries() const { return num_entries_; }
229 
230  // Deterministic time spent in all the solves function since last reset.
231  //
232  // TODO(user): This is quite precise. However we overcount a bit, because in
233  // each elementary solves, if the scalar product involved is zero, we skip
234  // some of the operations counted here. Is it worth spending a bit more time
235  // to be more precise here?
236  double DeterministicTimeSinceLastReset() const { return dtime_; }
237  void ResetDeterministicTime() { dtime_ = 0.0; }
238 
239  private:
240  mutable double dtime_ = 0.0;
241 
242  double hypersparse_ratio_;
243  EntryIndex num_entries_;
244  std::vector<RankOneUpdateElementaryMatrix> elementary_matrices_;
245  DISALLOW_COPY_AND_ASSIGN(RankOneUpdateFactorization);
246 };
247 
248 } // namespace glop
249 } // namespace operations_research
250 
251 #endif // OR_TOOLS_GLOP_RANK_ONE_UPDATE_H_
void ColumnCopyToDenseColumn(ColIndex col, DenseColumn *dense_column) const
Definition: sparse.h:456
void ColumnAddMultipleToSparseScatteredColumn(ColIndex col, Fractional multiplier, ScatteredColumn *column) const
Definition: sparse.h:442
Fractional ColumnScalarProduct(ColIndex col, const DenseRow &vector) const
Definition: sparse.h:421
void ColumnAddMultipleToDenseColumn(ColIndex col, Fractional multiplier, DenseColumn *dense_column) const
Definition: sparse.h:428
ColumnView column(ColIndex col) const
Definition: sparse.h:403
RankOneUpdateElementaryMatrix(const CompactSparseMatrix *storage, ColIndex u_index, ColIndex v_index, Fractional u_dot_v)
void Update(const RankOneUpdateElementaryMatrix &update_matrix)
int64_t value
bool IsAllFalse(const BoolVector &v)
const DenseRow & Transpose(const DenseColumn &col)
static double DeterministicTimeForFpOperations(int64_t n)
Definition: lp_types.h:421
Collection of objects used to extend the Constraint Solver library.
#define RETURN_IF_NULL(x)
Definition: return_macros.h:20
std::optional< int64_t > end
bool ShouldUseDenseIteration(double ratio_for_using_dense_representation) const
void ClearNonZerosIfTooDense(double ratio_for_using_dense_representation)
StrictITIVector< Index, bool > is_non_zero
StrictITIVector< Index, Fractional > values