Skip to content

Commit 8235958

Browse files
committed
Switch to modified Gram-Schmidt for orthogonalization.
This is actually faster as we can overwrite 'vec' as we go along, rather than having to store the projections in a separate allocation to avoid mutating it.
1 parent b0da55c commit 8235958

2 files changed

Lines changed: 19 additions & 25 deletions

File tree

include/irlba/lanczos.hpp

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,28 @@
1515
namespace irlba {
1616

1717
template<class EigenMatrix_, class EigenVector_>
18-
void orthogonalize_vector(const EigenMatrix_& mat, EigenVector_& vec, size_t ncols, EigenVector_& tmp) {
19-
tmp.head(ncols).noalias() = mat.leftCols(ncols).adjoint() * vec;
20-
vec.noalias() -= mat.leftCols(ncols) * tmp.head(ncols);
18+
void orthogonalize_vector(const EigenMatrix_& mat, EigenVector_& vec, Eigen::Index ncols) {
19+
// Original package uses classical Gram Schmidt but modified GS is actually faster when we
20+
// sum the timing across all orthogonalization steps in the Lanczos process.
21+
// Possibly because it allows us to modify 'vec' for each of mat's progressive column vectors,
22+
// rather than requiring a temporary space to store the projections from the original 'vec'.
23+
for (Eigen::Index c = 0; c < ncols; ++c) {
24+
// No need to divide by the norm of mat.col(c), as this is always 1.
25+
vec -= mat.col(c) * vec.dot(mat.col(c));
26+
}
2127
}
2228

2329
template<class EigenVector_, class Matrix_>
2430
struct LanczosWorkspace {
2531
LanczosWorkspace(const Matrix_& mat) :
2632
F(mat.cols()),
2733
W_next(mat.rows()),
28-
orthog_tmp(mat.cols()),
2934
work(mat.new_known_workspace()),
3035
awork(mat.new_known_adjoint_workspace())
3136
{}
3237

3338
EigenVector_ F;
3439
EigenVector_ W_next;
35-
EigenVector_ orthog_tmp;
3640

3741
I<decltype(std::declval<Matrix_>().new_known_workspace())> work;
3842
I<decltype(std::declval<Matrix_>().new_known_adjoint_workspace())> awork;
@@ -68,15 +72,14 @@ int run_lanczos_bidiagonalization(
6872
const Eigen::Index work = W.cols();
6973
auto& F = inter.F;
7074
auto& W_next = inter.W_next;
71-
auto& otmp = inter.orthog_tmp;
7275

7376
F = V.col(start);
7477
inter.work->multiply(F, W_next); // i.e., W_next = mat * F;
7578
int mult = 1;
7679

7780
// If start = 0, there's nothing to orthogonalize against.
7881
if (start) {
79-
orthogonalize_vector(W, W_next, start, otmp);
82+
orthogonalize_vector(W, W_next, start);
8083
}
8184

8285
Float S = W_next.norm();
@@ -95,14 +98,14 @@ int run_lanczos_bidiagonalization(
9598
++mult;
9699

97100
F -= S * V.col(j); // equivalent to daxpy.
98-
orthogonalize_vector(V, F, j + 1, otmp);
101+
orthogonalize_vector(V, F, j + 1);
99102

100103
if (j + 1 < work) {
101104
Float R_F = F.norm();
102105

103106
if (R_F < eps) {
104107
fill_with_random_normals(F, eng);
105-
orthogonalize_vector(V, F, j + 1, otmp);
108+
orthogonalize_vector(V, F, j + 1);
106109
R_F = F.norm();
107110
F /= R_F;
108111
R_F = 0;
@@ -121,12 +124,12 @@ int run_lanczos_bidiagonalization(
121124
// Full re-orthogonalization, using the left-most 'j + 1' columns of W.
122125
// Recall that W_next will be the 'j + 2'-th column, i.e., W.col(j + 1) in
123126
// 0-indexed terms, so we want to orthogonalize to all previous columns.
124-
orthogonalize_vector(W, W_next, j + 1, otmp);
127+
orthogonalize_vector(W, W_next, j + 1);
125128

126129
S = W_next.norm();
127130
if (S < eps) {
128131
fill_with_random_normals(W_next, eng);
129-
orthogonalize_vector(W, W_next, j + 1, otmp);
132+
orthogonalize_vector(W, W_next, j + 1);
130133
S = W_next.norm();
131134
W_next /= S;
132135
S = 0;

tests/src/lanczos.cpp

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,37 +17,28 @@ TEST(Lanczos, Orthogonalize) {
1717
-0.1683330, 0.1634341, -0.75523159, -0.5473305105,
1818
0.8749166, -0.2434161, -0.09989224, -0.0004957394;
1919

20-
auto orthogonalize_vector = [](const Eigen::MatrixXd& mat, Eigen::VectorXd& vec, size_t ncols) -> void {
21-
Eigen::VectorXd tmp(mat.cols());
22-
irlba::orthogonalize_vector(mat, vec, ncols, tmp);
23-
};
24-
2520
// Generated by rnorm(5)
2621
Eigen::VectorXd v(5);
2722
v << -0.24054848, -0.04785069, -0.76491749, -0.65634291, 0.62815141;
2823

2924
auto copy = v;
30-
orthogonalize_vector(m, copy, 4);
25+
irlba::orthogonalize_vector(m, copy, 4);
3126

3227
// Checking that the L2 norm is not all-zero.
3328
double l2 = 0;
3429
for (auto x : copy) { l2 += x*x; }
35-
EXPECT_TRUE(l2 > 0.1);
30+
EXPECT_GT(l2, 0.1);
3631

3732
// Checking that we do have orthogonality.
3833
for (size_t i = 0; i < 4; ++i) {
3934
auto col = m.col(i);
40-
auto cit = col.begin();
41-
double sum = 0;
42-
for (auto x : copy) {
43-
sum += x * (*cit);
44-
}
45-
EXPECT_TRUE(sum < 0.0000000001);
35+
double sum = std::inner_product(col.begin(), col.end(), copy.begin(), 0.0);
36+
EXPECT_LT(std::abs(sum), 0.000001);
4637
}
4738

4839
// Checking that the specification of columns has an effect.
4940
auto copy2 = v;
50-
orthogonalize_vector(m, copy, 2);
41+
irlba::orthogonalize_vector(m, copy2, 2);
5142
EXPECT_NE(copy, copy2);
5243
}
5344

0 commit comments

Comments
 (0)