vnl_sparse_symmetric_eigensystem.cxx
Go to the documentation of this file.
1 // This is core/vnl/algo/vnl_sparse_symmetric_eigensystem.cxx
2 //:
3 // \file
4 
5 #include <cstring>
6 #include <cstdlib>
7 #include <iostream>
8 #include <vector>
9 #include <cassert>
11 #include "vnl_sparse_lu.h"
12 #include <vnl/vnl_vector_ref.h>
13 
14 #include <vnl/algo/vnl_netlib.h> // dnlaso_() dseupd_() dsaupd_()
15 
16 static vnl_sparse_symmetric_eigensystem * current_system = nullptr;
17 
18 //------------------------------------------------------------
19 //: Callback for multiplying our matrix by a number of vectors.
20 // The input is p, which is an NxM matrix.
21 // This function returns q = A p, where A is the current sparse matrix.
22 static
23 void sse_op_callback(const long* n,
24  const long* m,
25  const double* p,
26  double* q)
27 {
28  assert(current_system != nullptr);
29 
30  current_system->CalculateProduct(*n,*m,p,q);
31 }
32 
33 //------------------------------------------------------------
34 //: Callback for saving the Lanczos vectors as required by dnlaso.
35 // If k=0, save the m columns of q as the (j-m+1)th through jth
36 // vectors. If k=1 then return the (j-m+1)th through jth vectors in
37 // q.
38 static
39 void sse_iovect_callback(const long* n,
40  const long* m,
41  double* q,
42  const long* j,
43  const long* k)
44 {
45  assert(current_system != nullptr);
46 
47  if (*k==0)
48  current_system->SaveVectors(*n,*m,q,*j-*m);
49  else if (*k==1)
50  current_system->RestoreVectors(*n,*m,q,*j-*m);
51 }
52 
54  : nvalues(0), vectors(nullptr), values(nullptr)
55 {
56 }
57 
59 {
60  delete[] vectors; vectors = nullptr;
61  delete[] values; values = nullptr;
62  for (auto & i : temp_store)
63  delete i;
64  temp_store.clear();
65 }
66 
67 //------------------------------------------------------------
68 //: Here is where the fortran converted code gets called.
69 // The sparse matrix M is assumed to be symmetric. The n smallest
70 // eigenvalues and their corresponding eigenvectors are calculated if
71 // smallest is true (the default). Otherwise the n largest eigenpairs
72 // are found. The accuracy of the eigenvalues is to nfigures decimal
73 // digits. Returns 0 if successful, non-zero otherwise.
75  int n,
76  bool smallest,
77  long nfigures)
78 {
79  mat = &M;
80 
81  // Clear current vectors.
82  if (vectors) {
83  delete[] vectors; vectors = nullptr;
84  delete[] values; values = nullptr;
85  }
86  nvalues = 0;
87 
88  current_system = this;
89 
90  long dim = mat->columns();
91  long nvals = (smallest)?-n:n;
92  long nperm = 0;
93  long nmval = n;
94  long nmvec = dim;
95  std::vector<double> temp_vals(n*4);
96  std::vector<double> temp_vecs(n*dim);
97 
98  // set nblock = std::max(10, dim/6) :
99  long nblock = (dim<60) ? dim/6 : 10;
100 
101  // isn't this rather a lot ? -- fsm
102  long maxop = dim*10; // dim*20;
103 
104  // set maxj = std::max(40, maxop*nblock, 6*nblock+1) :
105  long maxj = maxop*nblock; // 2*n+1;
106  long t1 = 6*nblock+1;
107  if (maxj < t1) maxj = t1;
108  if (maxj < 40) maxj = 40;
109 
110  // Calculate size of workspace needed. These expressions come from
111  // the LASO documentation.
112  int work_size = dim*nblock;
113  int t2 = maxj*(2*nblock+3) + 2*n + 6 + (2*nblock+2)*(nblock+1);
114  if (work_size < t2) work_size = t2;
115  work_size += 2*dim*nblock + maxj*(nblock + n + 2) + 2*nblock*nblock + 3*n;
116  std::vector<double> work(work_size+10);
117 
118  // Set starting vectors to zero.
119  for (int i=0; i<dim*nblock; ++i)
120  work[i] = 0.0;
121 
122  std::vector<long> ind(n);
123 
124  long ierr = 0;
125 
126  v3p_netlib_dnlaso_(sse_op_callback, sse_iovect_callback,
127  &dim, &nvals, &nfigures, &nperm,
128  &nmval, &temp_vals[0],
129  &nmvec, &temp_vecs[0],
130  &nblock,
131  &maxop,
132  &maxj,
133  &work[0],
134  &ind[0],
135  &ierr);
136  if (ierr > 0)
137  {
138  if (ierr & 0x1)
139  std::cerr << "Error: vnl_sparse_symmetric_eigensystem: N < 6*NBLOCK\n";
140  if (ierr & 0x2)
141  std::cerr << "Error: vnl_sparse_symmetric_eigensystem: NFIG < 0\n";
142  if (ierr & 0x4)
143  std::cerr << "Error: vnl_sparse_symmetric_eigensystem: NMVEC < N\n";
144  if (ierr & 0x8)
145  std::cerr << "Error: vnl_sparse_symmetric_eigensystem: NPERM < 0\n";
146  if (ierr & 0x10)
147  std::cerr << "Error: vnl_sparse_symmetric_eigensystem: MAXJ < 6*NBLOCK\n";
148  if (ierr & 0x20)
149  std::cerr << "Error: vnl_sparse_symmetric_eigensystem: NVAL < max(1,NPERM)\n";
150  if (ierr & 0x40)
151  std::cerr << "Error: vnl_sparse_symmetric_eigensystem: NVAL > NMVAL\n";
152  if (ierr & 0x80)
153  std::cerr << "Error: vnl_sparse_symmetric_eigensystem: NVAL > MAXOP\n";
154  if (ierr & 0x100)
155  std::cerr << "Error: vnl_sparse_symmetric_eigensystem: NVAL > MAXJ/2\n";
156  if (ierr & 0x200)
157  std::cerr << "Error: vnl_sparse_symmetric_eigensystem: NBLOCK < 1\n";
158  }
159  else if (ierr < 0)
160  {
161  if (ierr == -1)
162  std::cerr << "Error: vnl_sparse_symmetric_eigensystem:\n"
163  << " poor initial vectors chosen\n";
164  else if (ierr == -2)
165  std::cerr << "Error: vnl_sparse_symmetric_eigensystem:\n"
166  << " reached maximum operations " << maxop
167  << " without finding all eigenvalues,\n"
168  << " found " << nperm << " eigenvalues\n";
169  else if (ierr == -8)
170  std::cerr << "Error: vnl_sparse_symmetric_eigensystem:\n"
171  << " disastrous loss of orthogonality - internal error\n";
172  }
173 
174  // Copy the eigenvalues and vectors.
175  nvalues = n;
176  vectors = new vnl_vector<double>[n];
177  values = new double[n];
178  for (int i=0; i<n; ++i) {
179  values[i] = temp_vals[i];
180  vnl_vector<double> vec(dim,0.0);
181  for (int j=0; j<dim; ++j)
182  vec[j] = temp_vecs[j + dim*i];
183  vectors[i] = vec;
184  }
185 
186  // Delete temporary space.
187  for (auto & i : temp_store)
188  delete [] i;
189  temp_store.clear();
190 
191  return ierr;
192 }
193 
194 
195 //------------------------------------------------------------
196 //: Here is where the fortran converted code gets called.
197 // The sparse matrix A is assumed to be symmetric.
198 // Find n eigenvalue/eigenvectors of the eigenproblem A * x = lambda * B * x.
199 // !smallest and !magnitude - compute the N largest (algebraic) eigenvalues
200 // smallest and !magnitude - compute the N smallest (algebraic) eigenvalues
201 // !smallest and magnitude - compute the N largest (magnitude) eigenvalues
202 // smallest and magnitude - compute the nev smallest (magnitude) eigenvalues
203 // set sigma for shift/invert mode
206  double tolerance, int numberLanczosVecs,
207  bool smallest, bool magnitude,
208  int maxIterations,
209  double sigma)
210 {
211  mat = &A;
212  Bmat = &B;
213 
214  // Clear current vectors.
215  if (vectors) {
216  delete[] vectors; vectors = nullptr;
217  delete[] values; values = nullptr;
218  }
219  nvalues = 0;
220 
221  constexpr long whichLength = 2;
222  char which[whichLength + 1];
223  which[whichLength] = '\0';
224  if (smallest)
225  which[0] = 'S';
226  else
227  which[0] = 'L';
228 
229  if (magnitude)
230  which[1] = 'M';
231  else
232  which[1] = 'A';
233 
234  long matSize = mat->columns(); // Dimension of the eigenproblem.
235  long ido = 0; // ido == 0 means initialization
236 
237  long nconv = 0L; // Number of "converged" Ritz values.
238  long numberLanczosVecsL = numberLanczosVecs; // number of vectors to calc
239  long nEVL = nEV; // long number of EVs to calc
240 
241  auto *resid = new double[matSize];
242  std::memset((void*) resid, 0, sizeof(double)*matSize);
243 
244  if (maxIterations <= 0)
245  maxIterations = nEVL * 100;
246 
247  if (numberLanczosVecsL <= 0)
248  numberLanczosVecsL = 2 * nEVL + 1;
249  numberLanczosVecsL = (numberLanczosVecsL > matSize ? matSize : numberLanczosVecsL);
250  auto *V = new double[matSize * numberLanczosVecsL + 1];
251 
252 #define DONE 99
253  constexpr int genEigProblemLength = 1;
254  char genEigProblem = 'G';
255  long info = 0; // Initialization info (INPUT) and error flag (OUTPUT)
256 
257 #define IPARAMSIZE 12
258  long iParam[IPARAMSIZE];
259  // for the sake of consistency with parameter indices in FORTRAN,
260  // start at index 1...
261  iParam[0] = 0;
262  iParam[1] = 1; // always auto-shift
263  iParam[2] = 0; // no longer referenced
264  iParam[3] = maxIterations;
265  iParam[4] = 1; // NB: blocksize to be used in the recurrence.
266  // The code currently works only for NB = 1.
267 
268  iParam[5] = 0; // output - number of converged Ritz values
269  iParam[6] = 0; // No longer referenced. Implicit restarting is ALWAYS used
270 
271  long mode;
272 
273  // if we have a sigma, it's mode 3, otherwise, mode 2
274  // the mode determines the OP used in the solution
275  // determine OP
277  if (sigma != 0.0)
278  {
279  // K*x = lambda*M*x, K symmetric, M symmetric positive semi-definite
280  // OP = (inv[K - sigma*M])*M and B = M.
281  // Shift-and-Invert mode
282  mode = 3;
283  // determine OP
284 
285  OP = B;
286  OP *= sigma;
287  OP = A - OP;
288 //vsl_print_summary(std::cout, OP);
289  }
290  else
291  {
292  // A*x = lambda*M*x, A symmetric, M symmetric positive definite
293  // OP = inv[M]*A and B = M.
294  mode = 2;
295  OP = B;
296  }
297  // iParam[7] is the mode of the solution
298  iParam[7] = mode;
299 
300  // decompose for using in "multiplying" intermediate results
301  vnl_sparse_lu opLU(OP);
302 
303 //std::cout << opLU << std::endl;
304 
305  iParam[8] = 0; // parameter for user supplied shifts - not used here
306 
307  // iParam 9 - 11 are output
308  iParam[9] = 0; // total number of OP*x operations
309  iParam[10] = 0; // total number of B*x operations if BMAT='G'
310  iParam[11] = 0; // total number of steps of re-orthogonalization
311 
312  // output vector filled with address information for intermediate data used
313  // by the solver
314  // use FORTRAN indexing again...
315  long iPntr[IPARAMSIZE];
316  for (long & clrIx : iPntr)
317  clrIx= 0;
318 
319  // Double precision work array of length 3*N.
320  auto *workd = new double[3 * matSize + 1];
321 
322  // Double precision work array of length 3*N.
323  long lworkl = numberLanczosVecsL * (numberLanczosVecsL+9);
324 
325  // Double precision work array of length at least NCV**2 + 8*NCV
326  auto *workl = new double[lworkl + 1];
327 
328  vnl_vector<double> workVector;
329 
330  while (true)
331  {
332  // Calling arpack routine dsaupd.
333  v3p_netlib_dsaupd_(
334  &ido, &genEigProblem, &matSize, which,
335  &nEVL, &tolerance, resid, &numberLanczosVecsL, &V[1], &matSize,
336  &iParam[1], &iPntr[1], &workd[1], &workl[1], &lworkl, &info,
337  genEigProblemLength, whichLength);
338 
339  // Checking if aupp is done
340  if (ido==DONE)
341  {
342  nconv = iParam[5];
343  break;
344  }
345  else
346  {
347  switch (info) {
348  case -8: // Could not perform LAPACK eigenvalue calculation
349  case -9: // Starting vector is zero
350  case -9999: // Could not build an Arnoldi factorization
351  return info;
352  break;
353  case 0: // success
354  case 1: // hit maxIterations - should be DONE
355  case 3: // No shifts could be applied during a cycle of IRAM iteration
356  break;
357  default : // unknown ARPACK error
358  return info;
359  }
360 
361  // setting z pointer to ( = Bx) into workd
362  if (ido == -1)
363  iPntr[3] = iPntr[2] + matSize;
364 
365  vnl_vector_ref<double> x(matSize, &workd[iPntr[1]]);
366  vnl_vector_ref<double> y(matSize, &workd[iPntr[2]]);
367  vnl_vector_ref<double> z(matSize, &workd[iPntr[3]]); // z = Bx
368 
369  switch (ido)
370  {
371  case -1:
372  // Performing y <- OP*x for the first time when mode != 2.
373  if (mode != 2)
374  B.mult(x, z);
375  // no "break;" - initialization continues below
376  case 1:
377  // Performing y <- OP*w.
378  if (mode != 2)
379  opLU.solve(z, &y);
380  else
381  {
382  A.mult(x, workVector);
383  x.update(workVector);
384  opLU.solve(x, &y);
385  }
386  break;
387  case 2:
388  B.mult(x, y);
389  break;
390  default:
391  break;
392  }
393  }
394  }
395 
396  long rvec = 1; // get the values and vectors
397 
398  // which Ritz vctors do we want?
399  constexpr int howMnyLength = 1;
400  char howMny = 'A'; // all
401 
402  // selection vector for which Ritz vectors to calc.
403  // we want them all, so allocate the space (dseupd uses it)
404  vnl_vector<long> select(numberLanczosVecsL);
405 
406  // allocate eVals and eVecs
407  nvalues = nconv;
408  values = new double[nvalues];
410 
411  // hold the eigenvectors
412  auto *Z = new double[nvalues * matSize];
413 
414  v3p_netlib_dseupd_(&rvec, &howMny, select.data_block(), values, Z, &matSize, &sigma, &genEigProblem,
415  &matSize, which, &nEVL, &tolerance, resid, &numberLanczosVecsL, &V[1], &matSize, &iParam[1],
416  &iPntr[1], &workd[1], &workl[1], &lworkl, &info,
417  howMnyLength, genEigProblemLength, whichLength);
418 
419  // Copy the eigenvectors
420  int evIx;
421  for (evIx = 0; evIx < nvalues; evIx++)
422  {
423  vnl_vector_ref<double> tempEVec(matSize, &Z[evIx * matSize]);
424  vectors[evIx] = tempEVec;
425  }
426 
427  // Delete temporary space.
428  delete[] Z;
429  delete[] resid;
430  delete[] V;
431  delete[] workd;
432  delete[] workl;
433 
434  return info;
435 }
436 
437 
438 //------------------------------------------------------------
439 //: Callback from solver to calculate the product A p.
441  const double* p,
442  double* q)
443 {
444  // Call the special multiply method on the matrix.
445  mat->mult(n,m,p,q);
446 
447  return 0;
448 }
449 
450 //------------------------------------------------------------
451 //: Callback to store vectors for dnlaso.
453  const double* q,
454  int base)
455 {
456  // Store the contents of q. Basically this is a fifo. When a write
457  // with base=0 is called, we start another fifo.
458  if (base == 0) {
459  for (auto & i : temp_store)
460  delete i;
461  temp_store.clear();
462  }
463 
464  auto* temp = new double[n*m];
465  std::memcpy(temp,q,n*m*sizeof(double));
466 #ifdef DEBUG
467  std::cout << "Save vectors " << base << ' ' << temp << '\n';
468 #endif
469 
470  temp_store.push_back(temp);
471 
472  return 0;
473 }
474 
475 //------------------------------------------------------------
476 //: Callback to restore vectors for dnlaso.
478  double* q,
479  int base)
480 {
481  // Store the contents of q. Basically this is a fifo. When a read
482  // with base=0 is called, we start another fifo.
483  static int read_idx = 0;
484  if (base == 0)
485  read_idx = 0;
486 
487  double* temp = temp_store[read_idx];
488  std::memcpy(q,temp,n*m*sizeof(double));
489 #ifdef DEBUG
490  std::cout << "Restore vectors " << base << ' ' << temp << '\n';
491 #endif
492 
493  read_idx++;
494  return 0;
495 }
496 
497 //------------------------------------------------------------
498 //: Return a calculated eigenvector.
500 {
501  assert(i>=0 && i<nvalues);
502  return vectors[i];
503 }
504 
506 {
507  assert(i>=0 && i<nvalues);
508  return values[i];
509 }
unsigned int columns() const
Get the number of columns in the matrix.
Find the eigenvalues of a sparse symmetric matrix.
Solution of the linear system Mx = b for sparse matrices.
vnl_vector< T > & update(vnl_vector< T > const &, size_t start=0)
Replaces elements with index beginning at start, by values of v. O(n).
Definition: vnl_vector.hxx:480
void mult(vnl_vector< T > const &rhs, vnl_vector< T > &result) const
Multiply this*rhs, where rhs is a vector.
vnl_vector using user-supplied storage
vnl_vector using user-supplied storage.
Definition: vnl_fwd.h:17
#define m
Definition: vnl_vector.h:43
T const * data_block() const
Access the contiguous block storing the elements in the vector. O(1).
Definition: vnl_vector.h:230
int CalculateNPairs(vnl_sparse_matrix< double > &M, int n, bool smallest=true, long nfigures=10)
Here is where the fortran converted code gets called.
#define IPARAMSIZE
vnl_vector< double > get_eigenvector(int i) const
Return a calculated eigenvector.
#define DONE
int CalculateProduct(int n, int m, const double *p, double *q)
Callback from solver to calculate the product A p.
Declare in a central place the list of symbols from netlib.
Find the eigenvalues of a sparse symmetric matrix.
Linear system solver for Mx = b using LU decomposition of a sparse matrix.
Definition: vnl_sparse_lu.h:28
vnl_vector< double > solve(vnl_vector< double > const &b)
Solve problem M x = b.
int RestoreVectors(int n, int m, double *q, int base)
Callback to restore vectors for dnlaso.
int SaveVectors(int n, int m, const double *q, int base)
Callback to store vectors for dnlaso.