1#ifndef IRLBA_COMPUTE_HPP
2#define IRLBA_COMPUTE_HPP
15#include "sanisizer/sanisizer.hpp"
28template<
class Matrix_,
class EigenMatrix_,
class EigenVector_>
29void exact(
const Matrix_& matrix,
const Eigen::Index requested_number, EigenMatrix_& outU, EigenMatrix_& outV, EigenVector_& outD) {
30 Eigen::BDCSVD<EigenMatrix_, Eigen::ComputeThinU | Eigen::ComputeThinV> svd(matrix.rows(), matrix.cols());
32 auto realizer = matrix.new_known_realize_workspace();
34 svd.compute(realizer->realize(buffer));
36 outD.resize(requested_number);
37 outD = svd.singularValues().head(requested_number);
39 outU.resize(matrix.rows(), requested_number);
40 outU = svd.matrixU().leftCols(requested_number);
42 outV.resize(matrix.cols(), requested_number);
43 outV = svd.matrixV().leftCols(requested_number);
47inline Eigen::Index choose_extra_work(
const Eigen::Index requested,
const std::optional<Eigen::Index>& extra_work) {
48 if (extra_work.has_value()) {
51 return std::max(requested,
static_cast<Eigen::Index
>(7));
56inline bool requested_greater_than_or_equal_to_half_smaller(
const Eigen::Index requested,
const Eigen::Index smaller) {
57 const Eigen::Index half_smaller = smaller / 2;
58 if (requested == half_smaller) {
59 return smaller % 2 == 0;
61 return requested > half_smaller;
66inline Eigen::Index choose_requested_plus_extra_work_or_smaller(
const Eigen::Index requested_number,
const Eigen::Index extra_work,
const Eigen::Index smaller) {
67 if (requested_number >= smaller) {
72 return requested_number + sanisizer::min(smaller - requested_number, extra_work);
85inline Eigen::Index update_k(Eigen::Index k,
const Eigen::Index requested_number,
const Eigen::Index n_converged,
const Eigen::Index work) {
101 const Eigen::Index limit = work - 3;
103 const auto less_than_requested_plus_converged = [&](
const Eigen::Index val) ->
bool {
104 return val < requested_number || static_cast<Eigen::Index>(val - requested_number) < n_converged;
107 if (less_than_requested_plus_converged(k)) {
108 if (less_than_requested_plus_converged(limit)) {
111 const Eigen::Index output = requested_number + n_converged;
112 return std::max(output,
static_cast<Eigen::Index
>(1));
115 const Eigen::Index output = std::min(k, limit);
116 return std::max(output,
static_cast<Eigen::Index
>(1));
168template<
class Matrix_,
class EigenMatrix_,
class EigenVector_>
170 const Matrix_& matrix,
171 const Eigen::Index number,
177 const Eigen::Index smaller = std::min(matrix.rows(), matrix.cols());
178 Eigen::Index requested_number = sanisizer::cast<Eigen::Index>(number);
179 if (requested_number > smaller) {
181 requested_number = smaller;
183 throw std::runtime_error(
"requested number of singular values cannot be greater than the smaller matrix dimension");
186 throw std::runtime_error(
"requested number of singular values must be less than the smaller matrix dimension for IRLBA iterations");
191 if (requested_number == 0) {
192 outD.resize(requested_number);
193 outU.resize(matrix.rows(), requested_number);
194 outV.resize(matrix.cols(), requested_number);
204 (options.
exact_for_large_number && requested_greater_than_or_equal_to_half_smaller(requested_number, smaller))
206 exact(matrix, requested_number, outU, outV, outD);
212 const Eigen::Index extra_work = choose_extra_work(requested_number, options.
extra_work);
216 const Eigen::Index work = choose_requested_plus_extra_work_or_smaller(requested_number, extra_work, smaller);
220 EigenMatrix_ V(matrix.cols(), work);
221 std::mt19937_64 eng(options.
seed);
222 if (options.
initial.has_value()) {
223 const auto& init = *(options.
initial);
224 if (init.size() != V.rows()) {
225 throw std::runtime_error(
"initialization vector does not have expected number of rows");
229 fill_with_random_normals(V, 0, eng);
231 V.col(0) /= V.col(0).norm();
233 bool converged =
false;
234 int iter = 0, mult = 0;
238 Eigen::BDCSVD<EigenMatrix_, Eigen::NoQRPreconditioner | Eigen::ComputeFullU | Eigen::ComputeFullV> svd(work, work);
240 LanczosWorkspace<EigenVector_, Matrix_> lpwork(matrix);
242 EigenMatrix_ W(matrix.rows(), work);
243 EigenMatrix_ Wtmp(matrix.rows(), work);
244 EigenMatrix_ Vtmp(matrix.cols(), work);
246 EigenMatrix_ B(work, work);
247 B.setZero(work, work);
248 EigenVector_ res(work);
249 EigenVector_ F(matrix.cols());
251 EigenVector_ prevS(work);
253 const typename EigenMatrix_::Scalar svtol_actual = choose_singular_value_tolerance(options);
254 const auto inv_eps = choose_invariant_tolerance<typename EigenMatrix_::Scalar>(options);
260 mult += run_lanczos_bidiagonalization(lpwork, W, V, B, eng, k, inv_eps);
269 const auto& BS = svd.singularValues();
270 const auto& BU = svd.matrixU();
271 const auto& BV = svd.matrixV();
274 if (B.coeff(work - 1, work - 1) == 0) {
279 const auto& F = lpwork.F;
287 res = R_F * BU.row(work - 1);
289 Eigen::Index n_converged = 0;
291 const auto Smax = *std::max_element(BS.begin(), BS.end());
292 const auto threshold = Smax * tol;
294 for (Eigen::Index j = 0; j < work; ++j) {
295 if (std::abs(res[j]) <= threshold) {
296 const auto ratio = std::abs(BS[j] - prevS[j]) / BS[j];
297 if (ratio <= svtol_actual) {
303 if (n_converged >= requested_number) {
310 k = update_k(k, requested_number, n_converged, work);
311 Vtmp.leftCols(k).noalias() = V * BV.leftCols(k);
322 Wtmp.leftCols(k).noalias() = W * BU.leftCols(k);
325 B.setZero(work, work);
326 for (I<
decltype(k)> l = 0; l < k; ++l) {
327 B.coeffRef(l, l) = BS[l];
335 B.coeffRef(l, k) = res[l];
341 outD.resize(requested_number);
342 outD = svd.singularValues().head(requested_number);
344 outU.resize(matrix.rows(), requested_number);
345 outU.noalias() = W * svd.matrixU().leftCols(requested_number);
347 outV.resize(matrix.cols(), requested_number);
348 outV.noalias() = V * svd.matrixV().leftCols(requested_number);
353 met.
iterations = (converged ? iter + 1 : iter);
380template<
class InputEigenMatrix_,
class OutputEigenMatrix_,
class EigenVector_>
382 const InputEigenMatrix_& matrix,
384 OutputEigenMatrix_& outU,
385 OutputEigenMatrix_& outV,
396 return compute<Interface>(wrapped, number, outU, outV, outD, options);
404template<
class EigenMatrix_,
class EigenVector_>
445template<
class EigenMatrix_ = Eigen::MatrixXd,
class EigenVector_ = Eigen::VectorXd,
class Matrix_>
466template<
class OutputEigenMatrix_ = Eigen::MatrixXd,
class EigenVector_ = Eigen::VectorXd,
class InputEigenMatrix_>
Interface for a matrix to use in compute().
Definition interface.hpp:142
A Matrix-compatible wrapper around a "simple" matrix.
Definition simple.hpp:89
Metrics compute(const Matrix_ &matrix, const Eigen::Index number, EigenMatrix_ &outU, EigenMatrix_ &outV, EigenVector_ &outD, const Options< EigenVector_ > &options)
Definition compute.hpp:169
Metrics compute_simple(const InputEigenMatrix_ &matrix, Eigen::Index number, OutputEigenMatrix_ &outU, OutputEigenMatrix_ &outV, EigenVector_ &outD, const Options< EigenVector_ > &options)
Definition compute.hpp:381
Simple wrapper around an Eigen matrix.
Metrics for IRLBA progress from compute().
Definition compute.hpp:126
int iterations
Definition compute.hpp:130
int multiplications
Definition compute.hpp:135
bool converged
Definition compute.hpp:140
Options for running IRLBA in compute() and pca().
Definition Options.hpp:21
double convergence_tolerance
Definition Options.hpp:39
bool exact_for_large_number
Definition Options.hpp:74
int max_iterations
Definition Options.hpp:62
std::optional< EigenVector_ > initial
Definition Options.hpp:91
bool exact_for_small_matrix
Definition Options.hpp:68
bool cap_number
Definition Options.hpp:80
std::optional< Eigen::Index > extra_work
Definition Options.hpp:56
std::mt19937_64::result_type seed
Definition Options.hpp:85
Results of the IRLBA-based SVD by compute().
Definition compute.hpp:405
EigenVector_ D
Definition compute.hpp:424
Metrics metrics
Definition compute.hpp:429
EigenMatrix_ U
Definition compute.hpp:411
EigenMatrix_ V
Definition compute.hpp:418