-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy patharpaca.hpp
373 lines (315 loc) · 11.9 KB
/
arpaca.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
#ifndef ARPACA_ARPACA_HPP_
#define ARPACA_ARPACA_HPP_
#include <algorithm>
#include <stdexcept>
#include <vector>
#include <eigen3/Eigen/Core>
// ARPACK interface
extern "C" {
// Lanczos method (double precision)
void dsaupd_(int* ido, char* bmat, int* n, char* which, int* nev, double* tol,
double* resid, int* ncv, double* v, int* ldv, int* iparam,
int* ipntr, double* workd, double* workl, int* lworkl, int* info);
// Lanczos method (single precision)
void ssaupd_(int* ido, char* bmat, int* n, char* which, int* nev, float* tol,
float* resid, int* ncv, float* v, int* ldv, int* iparam,
int* ipntr, float* workd, float* workl, int* lworkl, int* info);
// Eigenvalue problem solver for result of dsaupd_
void dseupd_(int* rvec, char* howmny, int* select, double* d, double* z,
int* ldz, double* sigma, char* bmat, int* n, char* which, int* nev,
double* tol, double* resid, int* ncv, double* v, int* ldv,
int* iparam, int* ipntr, double* workd, double* workl, int* lworkl,
int* info);
// Eigenvalue problem solver for result of ssaupd_
void sseupd_(int* rvec, char* howmny, int* select, float* d, float* z, int* ldz,
float* sigma, char* bmat, int* n, char* which, int* nev,
float* tol, float* resid, int* ncv, float* v, int* ldv,
int* iparam, int* ipntr, float* workd, float* workl, int* lworkl,
int* info);
} // extern "C"
namespace arpaca {
// Type of eigenvalue to solve.
// Arpaca::SymmetricEigenSolver computes part of eigenvalues and eigenvectors.
// EigenvalueType indicates which part is to be solved.
enum EigenvalueType {
ALGEBRAIC_LARGEST, // Largest as real numbers
ALGEBRAIC_SMALLEST, // Smallest as real numbers
ALGEBRAIC_BOTH_END, // Both end as real numbers
MAGNITUDE_LARGEST, // Largest as absolute values
MAGNITUDE_SMALLEST, // Smallest as absolute values
};
namespace detail {
inline const char* GetNameOfEigenvalueType(EigenvalueType type)
{
switch (type) {
case ALGEBRAIC_LARGEST: return "LA";
case ALGEBRAIC_SMALLEST: return "SA";
case ALGEBRAIC_BOTH_END: return "BE";
case MAGNITUDE_LARGEST: return "LM";
case MAGNITUDE_SMALLEST: return "SM";
}
throw std::invalid_argument("Invalid eigenvalue type");
}
inline void GetNameOfEigenvalueType(EigenvalueType type, char* to)
{
const char* from = GetNameOfEigenvalueType(type);
std::copy(from, from + 3, to);
}
template<typename Scalar>
void saupd(int* ido, char* bmat, int* n, char* which, int* nev, Scalar* tol,
Scalar* resid, int* ncv, Scalar* v, int* ldv, int* iparam,
int* ipntr, Scalar* workd, Scalar* workl, int* lworkl, int* info)
{
dsaupd_(ido, bmat, n, which, nev, tol, resid, ncv, v, ldv, iparam, ipntr,
workd, workl, lworkl, info);
}
template<>
inline void saupd<float>(int* ido, char* bmat, int* n, char* which, int* nev,
float* tol, float* resid, int* ncv, float* v, int* ldv,
int* iparam, int* ipntr, float* workd, float* workl,
int* lworkl, int* info)
{
ssaupd_(ido, bmat, n, which, nev, tol, resid, ncv, v, ldv, iparam, ipntr,
workd, workl, lworkl, info);
}
template<typename Scalar>
void seupd(int* rvec, char* howmny, int* select, Scalar* d, Scalar* z, int* ldz,
Scalar* sigma, char* bmat, int* n, char* which, int* nev,
Scalar* tol, Scalar* resid, int* ncv, Scalar* v, int* ldv,
int* iparam, int* ipntr, Scalar* workd, Scalar* workl, int* lworkl,
int* info)
{
dseupd_(rvec, howmny, select, d, z, ldz, sigma, bmat, n, which, nev, tol,
resid, ncv, v, ldv, iparam, ipntr, workd, workl, lworkl, info);
}
template<>
inline void seupd<float>(int* rvec, char* howmny, int* select, float* d,
float* z, int* ldz, float* sigma, char* bmat, int* n,
char* which, int* nev, float* tol, float* resid,
int* ncv, float* v, int* ldv, int* iparam, int* ipntr,
float* workd, float* workl, int* lworkl, int* info)
{
sseupd_(rvec, howmny, select, d, z, ldz, sigma, bmat, n, which, nev, tol,
resid, ncv, v, ldv, iparam, ipntr, workd, workl, lworkl, info);
}
} // namespace detail
// Eigenvalue problem solver for symmetric matrix (especially sparse one).
// It partly computes eigenvalues and eigenvectors of a symmetric matrix A using
// ARPACK, which implements Implicitly Restarted Arnoldi (IRA) method.
template<typename Scalar = double>
class SymmetricEigenSolver {
public:
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> Matrix;
typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> Vector;
SymmetricEigenSolver()
: num_lanczos_vectors_(100),
max_iterations_(100),
tolerance_(-1)
{
SetEigenvalueType(MAGNITUDE_LARGEST);
}
// Sets # of Lanczos vectors.
// From two to four times larger than # of objective eigenvalues is
// recommended.
//
// NOTE: IRA approximates subspace containing the objective eigenvectors by
// constructing |num| orthogonal basis vectors in each iteration, which is
// improved for each iteration. Consequently, too small |num| causes slow
// convergence, while too large |num| requires large size of memory and even
// make each iteration too slow.
void SetNumLanczosVectors(int num)
{
num_lanczos_vectors_ = num;
}
// Sets max # of Lanczos iterations.
void SetMaxIterations(int num)
{
if (num <= 0)
throw std::invalid_argument("Given # of iterations is not positive");
max_iterations_ = num;
}
// Sets precision tolerance for computation.
// Negative value means machine precision.
void SetTolerance(Scalar tolerance)
{
tolerance_ = tolerance;
}
// Sets the type of eigenvalues to solve.
// See EigenvalueType for detail.
void SetEigenvalueType(EigenvalueType type)
{
detail::GetNameOfEigenvalueType(type, eigenvalue_type_name_);
}
// Solves the eigenvalue problem of a real symmetrix matrix A.
// The matrix A is not set explicitly. Solve() only needs the operator
// y = A * x.
//
// dimension: Dimension of space (rows or cols of the matrix A).
// num_eigenvalues: # of eigenvalues to compute.
// op: Functor that computes operation y = A * x. Signature is
// op(x, y), where x and y are slices of Eigen::VectorX{d,f},
// i.e. return values of Eigen::VectorX{d,f}::middleRows().
template<typename OperatorATimes>
void Solve(int dimension, int num_eigenvalues, OperatorATimes op)
{
if (dimension <= 0)
throw std::invalid_argument("Given dimension is not positive");
if (num_eigenvalues <= 0)
throw std::invalid_argument("Given num_eigenvalues is not positive");
// Eigenvalues (Lanczos)
int info = 0;
num_eigenvalues = std::max(std::min(num_eigenvalues, dimension), 0);
int num_lanczos_vectors = num_lanczos_vectors_;
if (num_lanczos_vectors <= 0)
num_lanczos_vectors = num_eigenvalues * 3;
else if (num_lanczos_vectors <= num_eigenvalues)
num_lanczos_vectors = num_eigenvalues + 1;
num_lanczos_vectors = std::min(num_lanczos_vectors, dimension);
char bmat[2] = { 'I' };
int leading_dimension = dimension;
int iparam[11] = {}, ipntr[11] = {};
iparam[0] = 1; // Use effective shift
iparam[2] = max_iterations_;
iparam[3] = 1; // block size
iparam[6] = 1; // Solve standard eigenvalue problem
// Calculation workspace
int lworkl = (num_lanczos_vectors + 8) * num_lanczos_vectors;
Vector workd(3 * dimension), workl(lworkl);
std::vector<Scalar> residue(dimension);
Matrix lanczos_vectors(dimension, num_lanczos_vectors);
// Lanczos iterations
for (int mode = 0; mode != 99; ) {
detail::saupd(&mode, bmat, &dimension, eigenvalue_type_name_,
&num_eigenvalues, &tolerance_, &residue[0],
&num_lanczos_vectors, lanczos_vectors.data(),
&leading_dimension, iparam, ipntr, workd.data(),
workl.data(), &lworkl, &info);
switch (mode) {
case -1:
op(workd.middleRows(ipntr[0] - 1, dimension),
workd.middleRows(ipntr[1] - 1, dimension));
break;
case 1:
op(workd.middleRows(ipntr[2] - 1, dimension),
workd.middleRows(ipntr[1] - 1, dimension));
workd.middleRows(ipntr[2] - 1, dimension) =
workd.middleRows(ipntr[0] - 1, dimension);
break;
case 2:
workd.middleRows(ipntr[1] - 1, dimension) =
workd.middleRows(ipntr[0] - 1, dimension);
break;
}
}
info_ = info;
num_actual_iterations_ = iparam[2];
num_converged_eigenvalues_ = iparam[4];
// Eigenvectors
int rvec = 1; // Compute eigenvectors
char howmany[2] = { 'A' };
std::vector<int> select(num_lanczos_vectors);
Scalar sigma;
eigenvectors_.resize(dimension, num_eigenvalues);
eigenvalues_.resize(num_eigenvalues);
detail::seupd(&rvec, howmany, &select[0], eigenvalues_.data(),
eigenvectors_.data(), &dimension, &sigma, bmat, &dimension,
eigenvalue_type_name_, &num_eigenvalues, &tolerance_,
&residue[0], &num_lanczos_vectors, lanczos_vectors.data(),
&leading_dimension, iparam, ipntr, workd.data(), workl.data(),
&lworkl, &info);
}
// Gets the computed eigenvectors.
// Returned matrix contains each eigenvector in each column.
const Matrix& eigenvectors() const
{
return eigenvectors_;
}
// Gets the computed eigenvalues.
const Vector& eigenvalues() const
{
return eigenvalues_;
}
Matrix MoveEigenvectors()
{
Matrix eigenvectors;
eigenvectors.swap(eigenvectors_);
return eigenvectors;
}
Vector MoveEigenvalues()
{
Vector eigenvalues;
eigenvalues.swap(eigenvalues_);
return eigenvalues;
}
int num_actual_iterations() const
{
return num_actual_iterations_;
}
int num_converged_eigenvalues() const
{
return num_converged_eigenvalues_;
}
// Gets information message based on error code of ARPACK.
const char* GetInfo() const
{
switch (info_) {
case 0: return "Normal exit";
case 1: return "Not converged";
case 3: return "num_lanczos_vectors may be too small";
case -8: return "Error on tridiagonal eigenvalue calculation";
case -9999: return "Could not build an Arnoldi factorization";
}
return "Arpaca internal error";
}
private:
Matrix eigenvectors_;
Vector eigenvalues_;
int num_lanczos_vectors_;
int max_iterations_;
Scalar tolerance_;
int info_;
int num_actual_iterations_;
int num_converged_eigenvalues_;
char eigenvalue_type_name_[3];
};
// Default operator for the case the matrix A is explicitly given.
template<typename MatrixType>
class DefaultOperator {
public:
explicit DefaultOperator(MatrixType& m)
: m_(m)
{}
template<typename X, typename Y>
void operator()(X x, Y y) const
{
y = m_ * x;
}
private:
MatrixType& m_;
};
template<typename Matrix>
inline DefaultOperator<Matrix> MakeDefaultOperator(Matrix& A)
{
return DefaultOperator<Matrix>(A);
}
// Solves the eigenvalue problem of a real symmetrix matrix A, where the matrix
// A is explicitly given.
template<typename Matrix>
SymmetricEigenSolver<typename Matrix::Scalar> Solve(
const Matrix& A,
int num_eigenvalues,
EigenvalueType type = MAGNITUDE_LARGEST,
int num_lanczos_vectors = 0,
int max_iterations = 10000,
typename Matrix::Scalar tolerance = -1)
{
SymmetricEigenSolver<typename Matrix::Scalar> solver;
solver.SetEigenvalueType(type);
solver.SetMaxIterations(max_iterations);
solver.SetNumLanczosVectors(num_lanczos_vectors);
solver.SetTolerance(tolerance);
solver.Solve(A.rows(), num_eigenvalues, MakeDefaultOperator(A));
return solver;
}
} // namespace arpaca
#endif // ARPACA_ARPACA_HPP_