1#ifndef IRLBA_COMPUTE_HPP
2#define IRLBA_COMPUTE_HPP
14#include "sanisizer/sanisizer.hpp"
27template<
typename EigenMatrix_>
28using JacobiSVD = Eigen::JacobiSVD<EigenMatrix_, Eigen::ComputeThinU | Eigen::ComputeThinV>;
30template<
class Matrix_,
class EigenMatrix_,
class EigenVector_>
31void exact(
const Matrix_& matrix,
const Eigen::Index requested_number, EigenMatrix_& outU, EigenMatrix_& outV, EigenVector_& outD) {
32 JacobiSVD<EigenMatrix_> svd(matrix.rows(), matrix.cols());
34 auto realizer = matrix.new_known_realize_workspace();
36 svd.compute(realizer->realize(buffer));
38 outD.resize(requested_number);
39 outD = svd.singularValues().head(requested_number);
41 outU.resize(matrix.rows(), requested_number);
42 outU = svd.matrixU().leftCols(requested_number);
44 outV.resize(matrix.cols(), requested_number);
45 outV = svd.matrixV().leftCols(requested_number);
49inline bool requested_greater_than_or_equal_to_half_smaller(
const Eigen::Index requested,
const Eigen::Index smaller) {
50 const Eigen::Index half_smaller = smaller / 2;
51 if (requested == half_smaller) {
52 return smaller % 2 == 0;
54 return requested > half_smaller;
59inline Eigen::Index choose_requested_plus_extra_work_or_smaller(
const Eigen::Index requested_number,
const int extra_work,
const Eigen::Index smaller) {
60 if (requested_number >= smaller) {
65 return requested_number + sanisizer::min(smaller - requested_number, extra_work);
78inline Eigen::Index update_k(Eigen::Index k,
const Eigen::Index requested_number,
const Eigen::Index n_converged,
const Eigen::Index work) {
94 const Eigen::Index limit = work - 3;
96 const auto less_than_requested_plus_converged = [&](
const Eigen::Index val) ->
bool {
97 return val < requested_number || static_cast<Eigen::Index>(val - requested_number) < n_converged;
100 if (less_than_requested_plus_converged(k)) {
101 if (less_than_requested_plus_converged(limit)) {
104 const Eigen::Index output = requested_number + n_converged;
105 return std::max(output,
static_cast<Eigen::Index
>(1));
108 const Eigen::Index output = std::min(k, limit);
109 return std::max(output,
static_cast<Eigen::Index
>(1));
141template<
class Matrix_,
class EigenMatrix_,
class EigenVector_>
143 const Matrix_& matrix,
144 const Eigen::Index number,
150 const Eigen::Index smaller = std::min(matrix.rows(), matrix.cols());
151 Eigen::Index requested_number = sanisizer::cast<Eigen::Index>(number);
152 if (requested_number > smaller) {
154 requested_number = smaller;
156 throw std::runtime_error(
"requested number of singular values cannot be greater than the smaller matrix dimension");
159 throw std::runtime_error(
"requested number of singular values must be less than the smaller matrix dimension for IRLBA iterations");
164 if (requested_number == 0) {
165 outD.resize(requested_number);
166 outU.resize(matrix.rows(), requested_number);
167 outV.resize(matrix.cols(), requested_number);
168 return std::make_pair(
true, 0);
175 (options.
exact_for_large_number && requested_greater_than_or_equal_to_half_smaller(requested_number, smaller))
177 exact(matrix, requested_number, outU, outV, outD);
178 return std::make_pair(
true, 0);
183 const Eigen::Index work = choose_requested_plus_extra_work_or_smaller(requested_number, options.
extra_work, smaller);
187 EigenMatrix_ V(matrix.cols(), work);
188 std::mt19937_64 eng(options.
seed);
189 if (options.
initial.has_value()) {
190 const auto& init = *(options.
initial);
191 if (init.size() != V.rows()) {
192 throw std::runtime_error(
"initialization vector does not have expected number of rows");
196 fill_with_random_normals(V, 0, eng);
198 V.col(0) /= V.col(0).norm();
200 bool converged =
false;
203 JacobiSVD<EigenMatrix_> svd(work, work);
205 LanczosWorkspace<EigenVector_, Matrix_> lpwork(matrix);
207 EigenMatrix_ W(matrix.rows(), work);
208 EigenMatrix_ Wtmp(matrix.rows(), work);
209 EigenMatrix_ Vtmp(matrix.cols(), work);
211 EigenMatrix_ B(work, work);
212 B.setZero(work, work);
213 EigenVector_ res(work);
214 EigenVector_ F(matrix.cols());
216 EigenVector_ prevS(work);
219 typename EigenMatrix_::Scalar svtol_actual = (svtol >= 0 ? svtol : tol);
225 run_lanczos_bidiagonalization(lpwork, W, V, B, eng, k, options);
234 const auto& BS = svd.singularValues();
235 const auto& BU = svd.matrixU();
236 const auto& BV = svd.matrixV();
239 if (B(work - 1, work - 1) == 0) {
244 const auto& F = lpwork.F;
252 res = R_F * BU.row(work - 1);
254 Eigen::Index n_converged = 0;
256 const auto Smax = *std::max_element(BS.begin(), BS.end());
257 const auto threshold = Smax * tol;
259 for (Eigen::Index j = 0; j < work; ++j) {
260 if (std::abs(res[j]) <= threshold) {
261 const auto ratio = std::abs(BS[j] - prevS[j]) / BS[j];
262 if (ratio <= svtol_actual) {
268 if (n_converged >= requested_number) {
275 k = update_k(k, requested_number, n_converged, work);
276 Vtmp.leftCols(k).noalias() = V * BV.leftCols(k);
277 V.leftCols(k) = Vtmp.leftCols(k);
287 Wtmp.leftCols(k).noalias() = W * BU.leftCols(k);
288 W.leftCols(k) = Wtmp.leftCols(k);
290 B.setZero(work, work);
291 for (I<
decltype(k)> l = 0; l < k; ++l) {
306 outD.resize(requested_number);
307 outD = svd.singularValues().head(requested_number);
309 outU.resize(matrix.rows(), requested_number);
310 outU.noalias() = W * svd.matrixU().leftCols(requested_number);
312 outV.resize(matrix.cols(), requested_number);
313 outV.noalias() = V * svd.matrixV().leftCols(requested_number);
315 return std::make_pair(converged, (converged ? iter + 1 : iter));
341template<
class InputEigenMatrix_,
class OutputEigenMatrix_,
class EigenVector_>
343 const InputEigenMatrix_& matrix,
345 OutputEigenMatrix_& outU,
346 OutputEigenMatrix_& outV,
357 return compute<Interface>(wrapped, number, outU, outV, outD, options);
365template<
class EigenMatrix_,
class EigenVector_>
411template<
class EigenMatrix_ = Eigen::MatrixXd,
class EigenVector_ = Eigen::VectorXd,
class Matrix_>
414 const auto stats =
compute(matrix, number, output.
U, output.
V, output.
D, options);
434template<
class OutputEigenMatrix_ = Eigen::MatrixXd,
class EigenVector_ = Eigen::VectorXd,
class InputEigenMatrix_>
437 const auto stats =
compute_simple(matrix, number, output.
U, output.
V, output.
D, options);
Interface for a matrix to use in compute().
Definition interface.hpp:142
A Matrix-compatible wrapper around a "simple" matrix.
Definition simple.hpp:89
std::pair< bool, int > compute(const Matrix_ &matrix, const Eigen::Index number, EigenMatrix_ &outU, EigenMatrix_ &outV, EigenVector_ &outD, const Options< EigenVector_ > &options)
Definition compute.hpp:142
std::pair< bool, int > compute_simple(const InputEigenMatrix_ &matrix, Eigen::Index number, OutputEigenMatrix_ &outU, OutputEigenMatrix_ &outV, EigenVector_ &outD, const Options< EigenVector_ > &options)
Definition compute.hpp:342
Simple wrapper around an Eigen matrix.
Options for running IRLBA in compute() and pca().
Definition Options.hpp:20
double convergence_tolerance
Definition Options.hpp:34
int extra_work
Definition Options.hpp:47
bool exact_for_large_number
Definition Options.hpp:65
int max_iterations
Definition Options.hpp:53
std::optional< EigenVector_ > initial
Definition Options.hpp:82
bool exact_for_small_matrix
Definition Options.hpp:59
bool cap_number
Definition Options.hpp:71
double singular_value_ratio_tolerance
Definition Options.hpp:41
std::mt19937_64::result_type seed
Definition Options.hpp:76
Results of the IRLBA-based SVD by compute().
Definition compute.hpp:366
EigenVector_ D
Definition compute.hpp:385
int iterations
Definition compute.hpp:390
EigenMatrix_ U
Definition compute.hpp:372
EigenMatrix_ V
Definition compute.hpp:379
bool converged
Definition compute.hpp:395