ARPACK-Armadillo
SymEigsSolver_Impl.h
1 // Copyright (C) 2015 Yixuan Qiu
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 // Arnoldi factorization starting from step-k
8 template < typename Scalar,
9  int SelectionRule,
10  typename OpType >
11 inline void SymEigsSolver<Scalar, SelectionRule, OpType>::factorize_from(int from_k, int to_m, const Vector &fk)
12 {
13  if(to_m <= from_k) return;
14 
15  fac_f = fk;
16 
17  Vector v(dim_n), w(dim_n);
18  Scalar beta = 0.0, Hii = 0.0;
19  // Keep the upperleft k x k submatrix of H and set other elements to 0
20  fac_H.tail_cols(ncv - from_k).zeros();
21  fac_H.submat(arma::span(from_k, ncv - 1), arma::span(0, from_k - 1)).zeros();
22  for(int i = from_k; i <= to_m - 1; i++)
23  {
24  beta = arma::norm(fac_f);
25  v = fac_f / beta;
26  fac_V.col(i) = v; // The (i+1)-th column
27  fac_H(i, i - 1) = beta;
28 
29  op->perform_op(v.memptr(), w.memptr());
30  nmatop++;
31 
32  Hii = arma::dot(v, w);
33  fac_H(i - 1, i) = beta;
34  fac_H(i, i) = Hii;
35 
36  fac_f = w - beta * fac_V.col(i - 1) - Hii * v;
37  // Correct f if it is not orthogonal to V
38  // Typically the largest absolute value occurs in
39  // the first element, i.e., <v1, f>, so we use this
40  // to test the orthogonality
41  Scalar v1f = arma::dot(fac_f, fac_V.col(0));
42  if(v1f > prec || v1f < -prec)
43  {
44  Vector Vf(i + 1);
45  Vf.tail(i) = fac_V.cols(1, i).t() * fac_f;
46  Vf[0] = v1f;
47  fac_f -= fac_V.head_cols(i + 1) * Vf;
48  }
49  }
50 }
51 
52 // Implicitly restarted Arnoldi factorization
53 template < typename Scalar,
54  int SelectionRule,
55  typename OpType >
57 {
58  if(k >= ncv)
59  return;
60 
61  TridiagQR<Scalar> decomp;
62  Vector em(ncv, arma::fill::zeros);
63  em[ncv - 1] = 1;
64 
65  for(int i = k; i < ncv; i++)
66  {
67  // QR decomposition of H-mu*I, mu is the shift
68  fac_H.diag() -= ritz_val[i];
69  decomp.compute(fac_H);
70 
71  // V -> VQ
72  decomp.apply_YQ(fac_V);
73  // H -> Q'HQ
74  // Since QR = H - mu * I, we have H = QR + mu * I
75  // and therefore Q'HQ = RQ + mu * I
76  fac_H = decomp.matrix_RQ();
77  fac_H.diag() += ritz_val[i];
78  // em -> Q'em
79  decomp.apply_QtY(em);
80  }
81  Vector fk = fac_f * em[k - 1] + fac_V.col(k) * fac_H(k, k - 1);
82  factorize_from(k, ncv, fk);
83  retrieve_ritzpair();
84 }
85 
86 // Calculate the number of converged Ritz values
87 template < typename Scalar,
88  int SelectionRule,
89  typename OpType >
91 {
92  // thresh = tol * max(prec, abs(theta)), theta for ritz value
93  Vector rv = arma::abs(ritz_val.head(nev));
94  Vector thresh = tol * arma::clamp(rv, prec, std::max(prec, rv.max()));
95  Vector resid = arma::abs(ritz_vec.tail_rows(1).t()) * arma::norm(fac_f);
96  // Converged "wanted" ritz values
97  ritz_conv = (resid < thresh);
98 
99  return arma::sum(ritz_conv);
100 }
101 
102 // Return the adjusted nev for restarting
103 template < typename Scalar,
104  int SelectionRule,
105  typename OpType >
107 {
108  int nev_new = nev;
109 
110  // Adjust nev_new, according to dsaup2.f line 677~684 in ARPACK
111  nev_new = nev + std::min(nconv, (ncv - nev) / 2);
112  if(nev == 1 && ncv >= 6)
113  nev_new = ncv / 2;
114  else if(nev == 1 && ncv > 2)
115  nev_new = 2;
116 
117  return nev_new;
118 }
119 
120 // Retrieve and sort ritz values and ritz vectors
121 template < typename Scalar,
122  int SelectionRule,
123  typename OpType >
125 {
126  /*Vector evals(ncv);
127  Matrix evecs(ncv, ncv);
128  arma::eig_sym(evals, evecs, arma::symmatl(fac_H));*/
129  TridiagEigen<double> decomp(fac_H);
130  Vector evals = decomp.eigenvalues();
131  Matrix evecs = decomp.eigenvectors();
132 
133  SortEigenvalue<Scalar, SelectionRule> sorting(evals.memptr(), evals.n_elem);
134  std::vector<int> ind = sorting.index();
135 
136  // For BOTH_ENDS, the eigenvalues are sorted according
137  // to the LARGEST_ALGE rule, so we need to move those smallest
138  // values to the left
139  // The order would be
140  // Largest => Smallest => 2nd largest => 2nd smallest => ...
141  // We keep this order since the first k values will always be
142  // the wanted collection, no matter k is nev_updated (used in restart())
143  // or is nev (used in sort_ritzpair())
144  if(SelectionRule == BOTH_ENDS)
145  {
146  std::vector<int> ind_copy(ind);
147  for(int i = 0; i < ncv; i++)
148  {
149  // If i is even, pick values from the left (large values)
150  // If i is odd, pick values from the right (small values)
151  if(i % 2 == 0)
152  ind[i] = ind_copy[i / 2];
153  else
154  ind[i] = ind_copy[ncv - 1 - i / 2];
155  }
156  }
157 
158  // Copy the ritz values and vectors to ritz_val and ritz_vec, respectively
159  for(int i = 0; i < ncv; i++)
160  {
161  ritz_val[i] = evals[ind[i]];
162  }
163  for(int i = 0; i < nev; i++)
164  {
165  ritz_vec.col(i) = evecs.col(ind[i]);
166  }
167 }
168 
169 
170 
171 // Sort the first nev Ritz pairs in decreasing magnitude order
172 // This is used to return the final results
173 template < typename Scalar,
174  int SelectionRule,
175  typename OpType >
177 {
178  SortEigenvalue<Scalar, LARGEST_MAGN> sorting(ritz_val.memptr(), nev);
179  std::vector<int> ind = sorting.index();
180 
181  Vector new_ritz_val(ncv);
182  Matrix new_ritz_vec(ncv, nev);
183  BoolVector new_ritz_conv(nev);
184 
185  for(int i = 0; i < nev; i++)
186  {
187  new_ritz_val[i] = ritz_val[ind[i]];
188  new_ritz_vec.col(i) = ritz_vec.col(ind[i]);
189  new_ritz_conv[i] = ritz_conv[ind[i]];
190  }
191 
192  ritz_val.swap(new_ritz_val);
193  ritz_vec.swap(new_ritz_vec);
194  ritz_conv.swap(new_ritz_conv);
195 }
196 
197 
198 
199 // Initialization and clean-up
200 template < typename Scalar,
201  int SelectionRule,
202  typename OpType >
204 {
205  // Reset all matrices/vectors to zero
206  fac_V.zeros(dim_n, ncv);
207  fac_H.zeros(ncv, ncv);
208  fac_f.zeros(dim_n);
209  ritz_val.zeros(ncv);
210  ritz_vec.zeros(ncv, nev);
211  ritz_conv.zeros(nev);
212 
213  nmatop = 0;
214  niter = 0;
215 
216  Vector r(init_resid, dim_n, false);
217  // The first column of fac_V
218  Vector v(fac_V.colptr(0), dim_n, false);
219  Scalar rnorm = arma::norm(r);
220  if(rnorm < prec)
221  throw std::invalid_argument("initial residual vector cannot be zero");
222  v = r / rnorm;
223 
224  Vector w(dim_n);
225  op->perform_op(v.memptr(), w.memptr());
226  nmatop++;
227 
228  fac_H(0, 0) = arma::dot(v, w);
229  fac_f = w - v * fac_H(0, 0);
230 }
231 
232 // Initialization with random initial coefficients
233 template < typename Scalar,
234  int SelectionRule,
235  typename OpType >
237 {
238  Vector init_resid(dim_n, arma::fill::randu);
239  init_resid -= 0.5;
240  init(init_resid.memptr());
241 }
242 
243 // Compute Ritz pairs and return the number of converged eigenvalues
244 template < typename Scalar,
245  int SelectionRule,
246  typename OpType >
248 {
249  // The m-step Arnoldi factorization
250  factorize_from(1, ncv, fac_f);
251  retrieve_ritzpair();
252  // Restarting
253  int i, nconv = 0, nev_adj;
254  for(i = 0; i < maxit; i++)
255  {
256  nconv = num_converged(tol);
257  if(nconv >= nev)
258  break;
259 
260  nev_adj = nev_adjusted(nconv);
261  restart(nev_adj);
262  }
263  // Sorting results
264  sort_ritzpair();
265 
266  niter = i + 1;
267 
268  return std::min(nev, nconv);
269 }
270 
271 // Return converged eigenvalues
272 template < typename Scalar,
273  int SelectionRule,
274  typename OpType >
275 inline typename SymEigsSolver<Scalar, SelectionRule, OpType>::Vector SymEigsSolver<Scalar, SelectionRule, OpType>::eigenvalues()
276 {
277  int nconv = arma::sum(ritz_conv);
278  Vector res(nconv);
279 
280  if(!nconv)
281  return res;
282 
283  int j = 0;
284  for(int i = 0; i < nev; i++)
285  {
286  if(ritz_conv[i])
287  {
288  res[j] = ritz_val[i];
289  j++;
290  }
291  }
292 
293  return res;
294 }
295 
296 // Return converged eigenvectors
297 template < typename Scalar,
298  int SelectionRule,
299  typename OpType >
300 inline typename SymEigsSolver<Scalar, SelectionRule, OpType>::Matrix SymEigsSolver<Scalar, SelectionRule, OpType>::eigenvectors(int nvec)
301 {
302  int nconv = arma::sum(ritz_conv);
303  nvec = std::min(nvec, nconv);
304  Matrix res(dim_n, nvec);
305 
306  if(!nvec)
307  return res;
308 
309  Matrix ritz_vec_conv(ncv, nvec);
310  int j = 0;
311  for(int i = 0; i < nev && j < nvec; i++)
312  {
313  if(ritz_conv[i])
314  {
315  ritz_vec_conv.col(j) = ritz_vec.col(i);
316  j++;
317  }
318  }
319 
320  res = fac_V * ritz_vec_conv;
321 
322  return res;
323 }
int compute(int maxit=1000, Scalar tol=1e-10)
Matrix eigenvectors()
Matrix matrix_RQ()
void apply_YQ(Matrix &Y)
void compute(const Matrix &mat)
void apply_QtY(Vector &Y)