PersistentLaplacians
PersistentLaplacian.hpp
Go to the documentation of this file.
1 #ifndef PL_H
2 #define PL_H
3 
4 #include <vector>
5 
6 #include "../typedefs.hpp"
7 #include "../PersistentLaplacians.hpp"
8 #include "../core/up_algorithms.hpp"
9 #include "../eigenvalues/eigs_algorithms.hpp"
11 
12 #include <unsupported/Eigen/SparseExtra>
13 
14 
15 #include <chrono>
16 #include <numeric> //std::iota
17 #include <map>
18 #include <set>
19 
20 #include <cassert>
21 
22 namespace PersistentLaplacians{
29 template <typename eigs_Algorithm = PersistentLaplacians::selfadjoint, typename up_Algorithm = PersistentLaplacians::schur >
31 
32  public:
33  /********************/
34  /* Member variables */
35  /********************/
36 
37  int top_dim;
38  std::vector<FilteredBoundaryMatrix<storage>> filtered_boundaries;
39  bool verbose;
40  bool use_flipped;
42 
43  /****************/
44  /* Constructors */
45  /****************/
46 
51  filtered_boundaries = std::vector<FilteredBoundaryMatrix<storage>>();
52  top_dim = 0;
53  verbose = false;
54  use_flipped=false;
55  filtered_boundaries.push_back(dummy_d0());
56  }
57 
67  PersistentLaplacian(std::vector<SparseMatrixInt> boundaries,
68  std::vector<std::vector<filtration_type>> filtrations){
69  filtered_boundaries = std::vector<FilteredBoundaryMatrix<storage>>();
70  verbose = false;
71  use_flipped=false;
72  this->set_boundaries_filtrations(boundaries, filtrations);
73  }
74 
75  /****************** */
76  /* Empty destructor */
77  /****************** */
78 
80 
81  /***********************/
82  /* Getters and setters */
83  /***********************/
84 
85 
95  void set_boundaries_filtrations(std::vector<SparseMatrixInt> boundaries,
96  std::vector<std::vector<filtration_type>> filtrations){
97  // Primary setter function called by constructors
98  // Input:
99  // boundaries: vector of Eigen::SparseMatrix of type int.
100  // boundaries must be sorted in order of dimension
101  // filtrations: vector of vector of filtrations.
102  // filtrations[dim] is a list of all filtrations of
103  // simplices in dimension dim.
104  // filtrations must be sorted in order of dimension,
105  // filtrations[i] must be sorted in order of filtration
106  // Important Assumptions:
107  // 1) Boundary matrix has real coefficients stored as integers (but not mod 2!)
108  // 2) Boundary matrix dimensions agree with filtrations sizes
109  // 3) Length(filtrations) = Length(boundaries) + 1
110 
111  filtered_boundaries.clear();
112  top_dim = boundaries.size();
113  filtered_boundaries.reserve(top_dim+1);
114 
115  filtered_boundaries.push_back(dummy_d0());
116 
117  // Stitch together boundary matrices with filtrations for domain and range
118  // Check for consistent sizing
119  for (unsigned long int dim = 1; dim <= (unsigned long int) top_dim; dim++){
120  if(boundaries[dim-1].cols() != (long int) filtrations[dim].size()){
121  std::cout << "boundaries[" << dim -1 << "].cols()=" << boundaries[dim-1].cols() << " != filtrations[" << dim << "].size()=" << filtrations[dim].size() << std::endl;
122  }
123  if (boundaries[dim-1].rows() != (long int) filtrations[dim-1].size()){
124  std::cout << "boundaries[" << dim -1 << "].rows()=" << boundaries[dim-1].rows() << " != filtrations[" << dim-1 << "].size()=" << filtrations[dim-1].size() << std::endl;
125  }
126  filtered_boundaries.push_back(FilteredBoundaryMatrix<storage>(boundaries[dim-1].template cast<storage>(),filtrations[dim],filtrations[dim-1]));
127  }
128  }
129 
130 
135  void set_verbose(bool verbose){this->verbose = verbose;}
136 
141  void set_flipped(bool use_flipped){this->use_flipped = use_flipped;}
142 
143  /***********************************/
144  /* Primary Mathematical Operations */
145  /***********************************/
146 
147 
156  // Get the Persistent Laplacian Matrix in dimension dim from filtration a to filtration b
157  // Inputs: integer dimension, start filtration level, end filtration level
158  // Output (by reference to avoid a large copy): L of type Eigen::MatrixXf
159 
160  // L_0 = L_up
161  if (dim == 0){
162  this->profile.start_L_up();
163  up_Algorithm up_alg; // see up_algorithms.hpp
164  up_alg(&filtered_boundaries[dim+1],a,b, L);
165 
166  // Time monitoring
167  this->profile.stop_L_up();
168  profile.durations_sum_up_down.push_back(0);
169  profile.durations_L_down.push_back(0);
170 
171  return;
172  } else if (dim == top_dim){
173  // L_{top_dim} = L_down
174  this->profile.start_L_down();
175  SparseMatrix_storage down(L.rows(),L.rows());
176  get_down(dim, a, down);
177  // Down produces a sparse matrix so we convert to dense
178  L = DenseMatrix_PL(down.cast<coefficient_type>());
179 
180  // Time monitoring
181  this->profile.stop_L_down();
182  profile.durations_sum_up_down.push_back(0);
183  profile.durations_L_up.push_back(0);
184 
185  return;
186  } else if (dim > top_dim){
187  // L = 0
188  profile.durations_sum_up_down.push_back(0);
189  profile.durations_L_up.push_back(0);
190  profile.durations_L_down.push_back(0);
191  L.setZero(0,0);
192  return;
193  }
194  // Else L = L_up + L_down
195 
196  // Set L = L_up then add L_down later
197  this->profile.start_L_up();
198  get_up(dim, a, b, L);
199  this->profile.stop_L_up();
200 
201  this->profile.start_L_down();
202 
203  // Get L_down
204  SparseMatrix_storage down(L.rows(), L.rows());
205  get_down(dim,a, down);
206  this->profile.stop_L_down();
207 
208  if (L.size() == 0){// L_up is empty, use L = L_down
209  profile.durations_sum_up_down.push_back(0);
210  L = DenseMatrix_PL(down.cast<coefficient_type>());
211  return;
212  } else if (down.size() == 0){// L_down is empty, use L = L_up (already done)
213  profile.durations_sum_up_down.push_back(0);
214  return;
215  }
216  assert (L.cols() == down.cols() && L.rows() == down.rows() && "up and down Laplacians must have same dimensions");
217 
218  // L = L_up + L_down,
219  // but L = L_up already, so just add L_down.
220  // https://libeigen.gitlab.io/docs/group__TutorialSparse.html
221  // this is faster than just returning up+down
222  this->profile.start_sum_up_down();
223  L+= down.cast<coefficient_type>();
224  this->profile.stop_sum_up_down();
225 
226  return;
227  // L is passed by reference to avoid a copy
228  }
229 
242  DenseMatrix_PL L;
243  get_L(dim, a, b, L);
244  return L;
245  }
246 
254  filtered_boundaries[top_dim].submatrix_at_filtration(a, B);
255  // The nonzero eigenvalues of (B * B^T) are the same as (B^T * B), and its possible BB^T is faster to compute the eigenvalues of.
256  L = (B*B.transpose()).cast<coefficient_type>();
257  }
258 
269  SparseMatrix_PL L;
270  get_L_top_dim_flipped(a, L);
271  return L;
272  }
273  // void get_up_standard(int up_dim, filtration_type a, filtration_type b, DenseMatrix_PL &L_up);
274 
275 
285  up_Algorithm up_alg; // see up_algorithms.hpp
286  up_alg(&filtered_boundaries[dim+1],a,b, L_up);
287  }
288 
289 
299  DenseMatrix_PL L_up;
300  get_up(dim, a, b, L_up);
301  return L_up;
302  }
303 
310  void get_down(int dim, filtration_type a, SparseMatrix_storage &L_down){
312  filtered_boundaries[dim].submatrix_at_filtration(a, B);
313  L_down = B.transpose()*B;
314 
315  // Every test of the self adjoint view rank update has been slower for down Laplacian than just B.transpose()*B.
316  // L_down.selfadjointView<Eigen::Lower>().rankUpdate(B.transpose());
317  return;
318  }
319 
331  SparseMatrix_storage L_down;
332  get_down(dim, a, L_down);
333  return L_down;
334  }
335 
345  std::vector<spectra_type> nonzero_spectra(int dim, filtration_type a, filtration_type b, SparseMatrixFloat PH_basis, bool use_dummy_harmonic_basis){
346  spectra_vec eigenvalues;
347  this->profile.start_L();
348  // no 1-simplices, L_0 has no nonzero spectra
349  if (dim == 0 && filtered_boundaries.size() == 1){
350  int betti0 = filtered_boundaries[0].index_of_filtration(true, a)+1;
351  profile.L_rows.push_back(betti0);
352  return std::vector<spectra_type>(); // all spectra zero -> empty vector
353  }
354 
355  // Get number of rows in Laplacian
356  int L_rows;
357  if (dim == 0){
358  L_rows = filtered_boundaries[1].index_of_filtration(false,a)+1;
359  } else {
360  L_rows = filtered_boundaries[dim].index_of_filtration(true,a) + 1;
361  }
362  profile.L_rows.push_back(L_rows);
363 
364  // Note: PH Reduction solves a linear system, producing a dense matrix. We no longer have the a sparse matrix in top dimension, so no need
365  // to have a separate case for the top dimension.
366 
367  // Get Laplacian matrix
368  DenseMatrix_PL L(L_rows, L_rows);
369  get_L(dim,a,b, L);
370 
371  this->profile.stop_L();
372  // L = 0 return trivial
373  if (L.size()==0){
374  eigenvalues.setZero(0);
375  profile.durations_eigs.push_back(0);
376  return std::vector<spectra_type>();
377  }
378 
379  int m = L.rows();
380  int k;
381  DenseMatrix_PL change_of_basis(m,m);
382  // Get size of projection matrix
383  if (use_dummy_harmonic_basis){
384  // compute null space inefficiently for testing
385  // use essentially this answer from StackOverflow: https://stackoverflow.com/a/53598471/3727807
386  std::cout << "using inneficient harmonic basis" << std::endl;
387  Eigen::CompleteOrthogonalDecomposition<DenseMatrix_PL> cod;
388  cod.compute(L);
389  DenseMatrix_PL V = cod.matrixZ().transpose();
390  DenseMatrix_PL Null_space = V.block(0, cod.rank(),V.rows(), V.cols() - cod.rank());
391  DenseMatrix_PL P = cod.colsPermutation();
392  DenseMatrix_PL PH_basis_dense = P * Null_space; // Unpermute the columns
393  std::cout << "PH_basis_dense computed";
394  // print_full_matrix_precise(PH_basis_dense);
395  // here PH_basis is dense
396  int n = PH_basis_dense.cols();
397  k = m - n;
398  change_of_basis.rightCols(PH_basis_dense.cols()) = PH_basis_dense;
399  } else{
400  // Use actual PH_basis
401  // here PH_basis is sparse
402  int n = PH_basis.cols();
403  k = m - n;
404  change_of_basis.rightCols(PH_basis.cols()) = PH_basis;
405  }
406 
407  // A random matrix will be linearly independent with probability 1
408  // A more intelligent basis could be used.
409  DenseMatrix_PL nonharmonic_basis = Eigen::MatrixXf::Random(m,k);
410  change_of_basis.leftCols(nonharmonic_basis.cols()) = nonharmonic_basis;
411 
412  // Do the change of basis
413  DenseMatrix_PL temp = change_of_basis.inverse()*L*change_of_basis; // TODO: optimize this
414  DenseMatrix_PL Schur = temp.topLeftCorner(k,k);
415 
416  // Compute Eigenvalues of the smaller matrix
417  // TODO: convert to parameterized eigenvalue algorithm
418  this->profile.start_eigs();
419  Eigen::BDCSVD<DenseMatrix_PL> bdcsvd(Schur);
420  eigenvalues = bdcsvd.singularValues();
421  this->profile.stop_eigs();
422 
423  // Copy to std::vector and sort
424  std::vector<spectra_type> std_eigenvalues(eigenvalues.data(), eigenvalues.data() + eigenvalues.size());
425  std::sort(std_eigenvalues.begin(),std_eigenvalues.end()); // Standard EigenSolver can return in any order; we want sorted.
426 
427  return std_eigenvalues;
428  }
429 
430 
431  /**********************************************/
432  /* Driver functions to get PL and eigenvalues */
433  /**********************************************/
434 
442  std::vector<spectra_type> spectra(int dim, filtration_type a, filtration_type b){
443  spectra_vec eigenvalues;
444  this->profile.start_L();
445  // no 1-simplices, return vector of zeros
446  if (dim == 0 && filtered_boundaries.size() == 1){
447  int betti0 = filtered_boundaries[0].index_of_filtration(true, a)+1;
448  // for (int i = 0; i < betti0; i++){
449  // eigenvalues.push_back(0.0);
450  // }
451  this->profile.stop_L();
452  profile.L_rows.push_back(betti0);
453  return std::vector<spectra_type>(betti0,0.0);
454  }
455 
456  // Get number of rows in Laplacian
457  int L_rows;
458  if (dim == 0){
459  L_rows = filtered_boundaries[1].index_of_filtration(false,a)+1;
460  } else {
461  L_rows = filtered_boundaries[dim].index_of_filtration(true,a) + 1;
462  }
463  profile.L_rows.push_back(L_rows);
464 
465  // Top dimension might use the "flipped" technique
466  // This must be called from the spectra function so
467  // that the eigenvalues vector can be 0-padded correctly
468  if (dim == top_dim && use_flipped){
469  int L_rows_flipped = filtered_boundaries[dim-1].index_of_filtration(true,a) + 1;
470  int zero_pad_length = L_rows - L_rows_flipped;
471 
472  // Only use flipped version if it will be smaller
473  if (L_rows_flipped < L_rows){
474  this->profile.start_L_down();
475  SparseMatrix_PL L(L_rows_flipped, L_rows_flipped);
476  get_L_top_dim_flipped(a, L);
477  this->profile.stop_L_down();
478  this->profile.stop_L();
479 
480  // L=0 record trivial stats
481  if (L.size()==0){
482  eigenvalues.setZero(0);
483  profile.durations_eigs.push_back(0);
484  profile.durations_sum_up_down.push_back(0);
485  profile.durations_L_up.push_back(0);
486  return std::vector<spectra_type>();
487  }
488 
489  // Get eigenvalues of smaller L
490  // TODO: use parameterized eigenvalue solver
491  this->profile.start_eigs();
492  Eigen::SelfAdjointEigenSolver<SparseMatrix_spectra_PL> es(L, Eigen::EigenvaluesOnly);
493  spectra_vec eigs = es.eigenvalues();
494  std::vector<spectra_type> std_eigs_flipped(eigs.data(), eigs.data() + eigs.size());
495  if (zero_pad_length <= 0){
496  std::cout << "zero_pag_length <= 0 (should not happen)"<< std::endl;
497  this->profile.stop_eigs();
498 
499  profile.durations_sum_up_down.push_back(0);
500  profile.durations_L_up.push_back(0);
501  return std_eigs_flipped;
502  }
503 
504  // Pad the eigenvalues with 0s
505  std::vector<spectra_type> zero_pad(zero_pad_length, 0.0);
506  std::move(std_eigs_flipped.begin(), std_eigs_flipped.end(), std::back_inserter(zero_pad));
507 
508  // Record timing
509  this->profile.stop_eigs();
510  profile.durations_sum_up_down.push_back(0);
511  profile.durations_L_up.push_back(0);
512  return zero_pad;
513  }
514  }
515 
516  // Get L
517  DenseMatrix_PL L(L_rows, L_rows);
518  get_L(dim,a,b, L);
519  this->profile.stop_L();
520 
521  // L = 0, recover trivial stats
522  if (L.size()==0){
523  eigenvalues.setZero(0);
524  profile.durations_eigs.push_back(0);
525  return std::vector<spectra_type>();
526  }
527 
528  // Compute Eigenvalues
529  this->profile.start_eigs();
530  eigs_Algorithm es;
531  eigenvalues = es.eigenvalues(L);
532  this->profile.stop_eigs();
533  std::vector<spectra_type> std_eigenvalues(eigenvalues.data(), eigenvalues.data() + eigenvalues.size());
534  return std_eigenvalues;
535  }
536 
541  std::vector<std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>>> spectra(){
542  // Output:
543  // A vector of tuples (dim, a, b, eigenvalues), where the eigenvalues is a sorted vector of real numbers
544 
545 
546  // Create a vector listing all dimensions 0, 1, ..., top_dim
547  std::vector<int> dims(top_dim+1);
548  std::iota (std::begin(dims), std::end(dims), 0);
549 
550  // Get all filtration values that occur in the complex
551  std::vector<filtration_type> all_filtrations = get_all_filtrations();
552 
553  // Convert the vector of dimensions and filtration values into triples
554  // (dim, a, b) to get L_{dim}^{a,b}
555  std::vector<std::tuple<int, filtration_type, filtration_type>> requests = filtration_list_to_spectra_request(all_filtrations,dims);
556 
557  // Compute the PL matrices and eigenvalues
558  std::vector<std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>>> requested_spectra = spectra(requests);
559 
560  // Record results for the profiler, not necessary for the user outside of benchmarking
561  for (int i = 0; i < (int) requested_spectra.size(); i++){
562  std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>> current = requested_spectra[i];
563  profile.dims.push_back(std::get<0>(current));
564  profile.filtration_a.push_back(std::get<1>(current));
565  profile.filtration_b.push_back(std::get<2>(current));
566  std::pair<int,spectra_type> summary = eigenvalues_summarize(std::get<3>(current));
567  profile.bettis.push_back(summary.first);
568  profile.lambdas.push_back(summary.second);
569  }
570  // Output profiler to csv
571  if (filtered_boundaries.size() > 1)// dont report profile when no 1-simplices
572  profile.to_csv("./profile.csv");
573  return requested_spectra;
574  }
575 
581  std::vector<std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>>> spectra(std::vector<std::tuple<int,filtration_type,filtration_type>> spectra_request_list){
582  // Declare variables and vectors
583  int dim;
584  filtration_type a;
585  filtration_type b;
586  std::tuple<int,filtration_type,filtration_type> spectra_request;
587  std::vector<spectra_vec> spectra_list;
588  std::vector<std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>>> requested_spectra;
589  requested_spectra.reserve(spectra_request_list.size());
590 
591  // Loop through list of tuples (dim, a, b)
592  for (unsigned long int i = 0; i < spectra_request_list.size(); i++){
593  // Unpack the tuples
594  spectra_request = spectra_request_list[i];
595  dim = std::get<0>(spectra_request);
596  a = std::get<1>(spectra_request);
597  b = std::get<2>(spectra_request);
598 
599  // Get eigenvalues and re-pack as (dim, a, b, eigenvalues)
600  this->profile.start_all();
601  requested_spectra.push_back(std::make_tuple(dim, a, b, spectra(dim, a, b)));
602  this->profile.stop_all();
603  if (verbose)
604  std::cout << "duration spectra for dim=" << dim << ", a= " << a << ", b=" << b << ": " << this->profile.all.duration << std::endl;
605  }
606  return requested_spectra;
607  }
608 
609  /***************************************************************/
610  /* Driver functions to get PL and eigenvalues and eigenvectors */
611  /***************************************************************/
612 
613 
621  std::pair<std::vector<spectra_type>,DenseMatrix_PL> eigenpairs(int dim, filtration_type a, filtration_type b){
622  spectra_vec eigenvalues;
623  DenseMatrix_PL eigenvectors;
624  this->profile.start_L();
625  // no 1-simplices, return all eigenvalues zero
626  // each eigenvector is a unit vector in only one component, so the matrix of all eigenvectors is just the identity matrux
627  if (dim == 0 && filtered_boundaries.size() == 1){
628  int betti0 = filtered_boundaries[0].index_of_filtration(true, a)+1;
629  // // for (int i = 0; i < betti0; i++){
630  // // eigenvalues.push_back(0.0);
631  // // }
632  this->profile.stop_L();
633  profile.L_rows.push_back(betti0);
634  std::vector<spectra_type> std_eigs = std::vector<spectra_type>(betti0,0.0);
635  eigenvectors.setIdentity(betti0,betti0);
636  return std::pair<std::vector<spectra_type>,DenseMatrix_PL>(std_eigs, eigenvectors);
637  }
638 
639  // Get number of rows in Laplacian
640  int L_rows;
641  if (dim == 0){
642  L_rows = filtered_boundaries[1].index_of_filtration(false,a)+1;
643  } else {
644  L_rows = filtered_boundaries[dim].index_of_filtration(true,a) + 1;
645  }
646  profile.L_rows.push_back(L_rows);
647 
648  // Get L
649  DenseMatrix_PL L(L_rows, L_rows);
650  get_L(dim,a,b, L);
651  this->profile.stop_L();
652 
653  // L = 0, recover trivial stats
654  if (L.size()==0){
655  eigenvalues.setZero(0);
656  profile.durations_eigs.push_back(0);
657  return std::pair<std::vector<spectra_type>,DenseMatrix_PL>(std::vector<spectra_type>(),DenseMatrix_PL());
658  }
659 
660  // Compute Eigenvalues
661  this->profile.start_eigs();
662  eigs_Algorithm es;
663  std::pair<spectra_vec,DenseMatrix_spectra_PL> eigenpairs = es.eigenpairs(L);
664  eigenvalues = eigenpairs.first;
665  this->profile.stop_eigs();
666  std::vector<spectra_type> std_eigenvalues(eigenvalues.data(), eigenvalues.data() + eigenvalues.size());
667  return std::pair<std::vector<spectra_type>,DenseMatrix_PL>(std_eigenvalues,eigenpairs.second);
668  }
669 
674  std::vector<std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>,DenseMatrix_PL>> eigenpairs(){
675 
676  // Create a vector listing all dimensions 0, 1, ..., top_dim
677  std::vector<int> dims(top_dim+1);
678  std::iota (std::begin(dims), std::end(dims), 0);
679 
680  // Get all filtration values that occur in the complex
681  std::vector<filtration_type> all_filtrations = get_all_filtrations();
682 
683  // Convert the vector of dimensions and filtration values into triples
684  // (dim, a, b) to get L_{dim}^{a,b}
685  std::vector<std::tuple<int, filtration_type, filtration_type>> requests = filtration_list_to_spectra_request(all_filtrations,dims);
686 
687  // Compute the PL matrices and eigenvalues
688  std::vector<std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>,DenseMatrix_PL>> requested_spectra = eigenpairs(requests);
689 
690  // Record results for the profiler, not necessary for the user outside of benchmarking
691  for (int i = 0; i < (int) requested_spectra.size(); i++){
692  std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>,DenseMatrix_PL> current = requested_spectra[i];
693  profile.dims.push_back(std::get<0>(current));
694  profile.filtration_a.push_back(std::get<1>(current));
695  profile.filtration_b.push_back(std::get<2>(current));
696  std::pair<int,spectra_type> summary = eigenvalues_summarize(std::get<3>(current));
697  profile.bettis.push_back(summary.first);
698  profile.lambdas.push_back(summary.second);
699  }
700  // Output profiler to csv
701  if (filtered_boundaries.size() > 1)// dont report profile when no 1-simplices
702  profile.to_csv("./profile.csv");
703  return requested_spectra;
704  }
705 
711  std::vector<std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>, DenseMatrix_PL>> eigenpairs(std::vector<std::tuple<int,filtration_type,filtration_type>> spectra_request_list){
712  // Declare variables and vectors
713  int dim;
714  filtration_type a;
715  filtration_type b;
716  std::tuple<int,filtration_type,filtration_type> spectra_request;
717  std::vector<spectra_vec> spectra_list;
718  std::vector<std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>, DenseMatrix_PL>> requested_spectra;
719  requested_spectra.reserve(spectra_request_list.size());
720 
721  // Loop through list of tuples (dim, a, b)
722  for (unsigned long int i = 0; i < spectra_request_list.size(); i++){
723  // Unpack the tuples
724  spectra_request = spectra_request_list[i];
725  dim = std::get<0>(spectra_request);
726  a = std::get<1>(spectra_request);
727  b = std::get<2>(spectra_request);
728 
729  // Get eigenvalues and eigenvectors and re-pack as (dim, a, b, eigenvalues, eigenvectors)
730  this->profile.start_all();
731  std::pair<std::vector<spectra_type>,DenseMatrix_PL> eigenpairs = this->eigenpairs(dim, a, b);
732  std::vector<spectra_type> eigenvalues = eigenpairs.first;
733  DenseMatrix_PL eigenvectors = eigenpairs.second;
734  requested_spectra.push_back(std::make_tuple(dim, a, b, eigenvalues, eigenvectors));
735  this->profile.stop_all();
736  if (verbose)
737  std::cout << "duration spectra for dim=" << dim << ", a= " << a << ", b=" << b << ": " << this->profile.all.duration << std::endl;
738  }
739  return requested_spectra;
740  }
741 
742 
748  std::pair<int, spectra_type> eigenvalues_summarize(std::vector<spectra_type> eigenvalues){
749  // Input: vector of eigenvalues
750  // Output: betti number and least nonzero eigenvalue (tolerance 1e-4)
751  int current_betti = 0;
752  spectra_type tol = 1e-4;
753  for (int k = 0; k < (int) eigenvalues.size(); k++){
754  if (eigenvalues[k] > tol){ // reached a nonzero eigenvalue
755  return std::make_pair(current_betti, eigenvalues[k]);
756  }
757  current_betti++;
758  }
759  // if we reach the end of the loop, then there were no nonzero eigenvalues
760  if (current_betti == 0){
761  if ((int) eigenvalues.size() > 0){
762  return std::make_pair(0,eigenvalues[0]);
763  }
764  return std::make_pair(0,0.0);
765  }
766  // else never encountered a nonzero eigenvalue
767  return std::make_pair(current_betti,0.0);
768  }
769 
770  /**********************************/
771  /* Helpful input/output functions */
772  /**********************************/
773 
774 
782  void store_L(int dim, filtration_type a, filtration_type b, std::string filename){
783  int L_rows = filtered_boundaries[dim].index_of_filtration(true,a) + 1;
784  DenseMatrix_PL L(L_rows, L_rows);
785 
786  get_L(dim,a,b, L);
787  SparseMatrix_PL L_sparse = L.sparseView();
788  bool success = Eigen::saveMarket(L_sparse, filename); // conversion to sparse is probably extremely expensive, but not all versions of eigen have saveMarketDense
789  if (success)
790  std::cout << "saved matrix to file " << filename << std::endl;
791  else
792  std::cout << "failed saving matrix to file " << filename << std::endl;
793  return;
794  }
795 
800  for (int i = 0; i <= top_dim; i++){
801  filtered_boundaries[i].print();
802  filtered_boundaries[i].print_range_filtration();
803  filtered_boundaries[i].print_domain_filtration();
804  }
805  }
806 
817  void store_spectra(std::vector<std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>>> spectra, std::string out_prefix){
818  // Open the files
819  std::vector<std::ofstream> out_streams(top_dim+1);
820  for (int i = 0; i <= top_dim; i++){
821  out_streams[i] = std::ofstream("./" + out_prefix + "_spectra_" + std::to_string(i) + ".txt");
822  }
823 
824  // Write the eigenvalues (not assuming any ordering on the dimensions)
825  for (int i = 0; i < (int) spectra.size(); i++){
826  std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>> spectrum_record = spectra[i];
827  int dim = std::get<0>(spectrum_record);
828  std::vector<spectra_type> eigs = std::get<3>(spectrum_record);
829  for (int j = 0; j < (int) eigs.size(); j++){
830  out_streams[dim] << eigs[j] << " ";
831  }
832  out_streams[dim] << std::endl;
833  }
834  // Close the files
835  for (int i = 0; i <= top_dim; i++){
836  out_streams[i].close();
837  }
838  }
839 
840 
848  void store_spectra_summary(std::vector<std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>>> spectra, std::string out_prefix){
849  std::set<std::pair<filtration_type, filtration_type>> filtration_values_set;
850 
851  // get all unique (a,b) filtration value pairs
852  for (int i = 0 ; i < (int) spectra.size(); i++){
853  filtration_type a = std::get<1>(spectra[i]);
854  filtration_type b = std::get<2>(spectra[i]);
855  filtration_values_set.insert(
856  std::make_pair(a,b));
857  }
858  int num_filtrations = filtration_values_set.size();
859  std::vector<std::pair<filtration_type,filtration_type>> filtration_values_vec(filtration_values_set.begin(), filtration_values_set.end());
860 
861  // create a mapping for their index
862  // unordered_map may be much faster, but cannot hash a std::pair
863  std::map<std::pair<filtration_type,filtration_type>,int> filtration_index_map;
864  std::vector<std::vector<spectra_type>> output_lines(num_filtrations);
865  int items_per_line = 2 + 2*(top_dim+1);
866  for (int i = 0; i < num_filtrations; i++){
867  filtration_index_map[filtration_values_vec[i]] = i;
868 
869  std::vector<spectra_type> temp_line(items_per_line);
870  temp_line[0] = (spectra_type) filtration_values_vec[i].first;
871  temp_line[1] = (spectra_type) filtration_values_vec[i].second;
872  output_lines[i] = temp_line;
873  }
874 
875  for (int i = 0; i < (int) spectra.size(); i++){
876  // Unpack each tuple
877  std::tuple<int, filtration_type, filtration_type, std::vector<spectra_type>> spectrum_record = spectra[i];
878  int dim = std::get<0>(spectrum_record);
879  filtration_type a = std::get<1>(spectrum_record);
880  filtration_type b = std::get<2>(spectrum_record);
881 
882  // compute eigenvalue summary
883  std::pair<filtration_type, filtration_type> filtration_pair = std::make_pair(a,b);
884  std::vector<spectra_type> eigs = std::get<3>(spectrum_record);
885  std::pair<int, spectra_type> temp_pair = eigenvalues_summarize(eigs);
886  spectra_type betti = (spectra_type) temp_pair.first;
887  spectra_type lambda = (spectra_type) temp_pair.second;
888 
889  int line_index = filtration_index_map[filtration_pair];
890  // a, b, betti_0, betti_1, betti_2, lambda_0, lambda_1, lambda_2
891  // 0, 1, 2, 3, 4, 5, 6, 7
892  output_lines[line_index][2+dim] = betti;
893  output_lines[line_index][3+top_dim+dim] = lambda;
894  }
895 
896  // open file and write headers
897  std::ofstream outstream("./" + out_prefix + "_spectra_summary.txt");
898  outstream << "a\tb";
899  for (int i = 0; i <= top_dim; i++){
900  outstream <<"\tbetti_" << i;
901  }
902  for (int i = 0; i <= top_dim; i++){
903  outstream <<"\tlambda_" << i;
904  }
905  outstream << std::endl;
906 
907  // write the data to the file
908  for (int i = 0; i < (int) num_filtrations; i++){
909  outstream << output_lines[i][0];
910  for (int j = 1; j < items_per_line; j++){
911  outstream << "\t" << output_lines[i][j];
912  }
913  outstream << std::endl;
914  }
915  outstream.close();
916  }
917 
918 
925  std::vector<std::tuple<int, filtration_type, filtration_type>> filtration_list_to_spectra_request(std::vector<filtration_type> filtrations, std::vector<int> dims){
926  // Declare variables
927  filtration_type a;
928  filtration_type b;
929  int dim;
930  std::vector<std::tuple<int, filtration_type, filtration_type>> requests;
931  std::tuple<int, filtration_type, filtration_type> spectra_request;
932 
933  // create the pairs in order (dim=0, a=0, b=1), (dim=1, a=0, b=1), ..., (dim=top_dim, a=0, b=1), (dim=0, a=1, b=2), ...
934  for(unsigned long int filtration_index = 0; filtration_index < filtrations.size()-1; filtration_index++){
935  a = filtrations[filtration_index];
936  b = filtrations[filtration_index+1];
937  for (unsigned long int dim_index = 0; dim_index < dims.size(); dim_index++){
938  dim = dims[dim_index];
939  spectra_request = std::make_tuple(dim, a, b);
940  requests.push_back(spectra_request);
941  }
942  }
943  // add the [a,infinity) case as (dim=0, a, a), (dim=1, a, a), ...
944  a = filtrations[filtrations.size()-1];
945  b = a;
946  for (unsigned long int dim_index = 0; dim_index < dims.size(); dim_index++){
947  dim = dims[dim_index];
948  spectra_request = std::make_tuple(dim, a, b);
949  requests.push_back(spectra_request);
950  }
951  return requests;
952  }
953 
957  std::vector<filtration_type> get_all_filtrations(){
958  std::set<filtration_type> all_filtrations;
959 
960  //add zero-th dimensional
961  std::vector<filtration_type> c0 = filtered_boundaries[0].get_domain_filtrations();
962  for (unsigned long int i = 0; i < c0.size(); i++){
963  all_filtrations.insert(c0[i]);
964  }
965  //add other dimensions, which correspond to domain filtrations
966  std::vector<filtration_type> temp;
967  for (unsigned long int dim = 1; dim <= (unsigned long int) top_dim; dim++){
968  temp = filtered_boundaries[dim].get_domain_filtrations();
969  for (unsigned long int i = 0; i < temp.size(); i++){
970  all_filtrations.insert(temp[i]);
971  }
972  };
973  std::vector<filtration_type> all_filtrations_vector(all_filtrations.begin(), all_filtrations.end());
974  return all_filtrations_vector;
975  }
976 
977  protected:
978 
980  // Construct a placeholder matrix for d0 so that filtered_boundaries[dim] actually gives d_{dim}
981  SparseMatrix_storage dummy_d0_matrix;
982  std::vector<filtration_type> domain_filtrations = {0.0};
983  std::vector<filtration_type> range_filtrations = {0.0};
984  PersistentLaplacians::FilteredBoundaryMatrix<storage> dummy_d0_fbm(dummy_d0_matrix,domain_filtrations,range_filtrations);
985  return dummy_d0_fbm;
986  }
987 };
988 }
989 #endif
Definition: FilteredBoundaryMatrix.hpp:16
Definition: PersistentLaplacian.hpp:30
void store_spectra_summary(std::vector< std::tuple< int, filtration_type, filtration_type, std::vector< spectra_type >>> spectra, std::string out_prefix)
Definition: PersistentLaplacian.hpp:848
void store_spectra(std::vector< std::tuple< int, filtration_type, filtration_type, std::vector< spectra_type >>> spectra, std::string out_prefix)
Definition: PersistentLaplacian.hpp:817
void get_L(int dim, filtration_type a, filtration_type b, DenseMatrix_PL &L)
Definition: PersistentLaplacian.hpp:155
void store_L(int dim, filtration_type a, filtration_type b, std::string filename)
Definition: PersistentLaplacian.hpp:782
std::vector< spectra_type > nonzero_spectra(int dim, filtration_type a, filtration_type b, SparseMatrixFloat PH_basis, bool use_dummy_harmonic_basis)
Definition: PersistentLaplacian.hpp:345
void get_L_top_dim_flipped(filtration_type a, SparseMatrix_PL &L)
Definition: PersistentLaplacian.hpp:252
std::pair< std::vector< spectra_type >, DenseMatrix_PL > eigenpairs(int dim, filtration_type a, filtration_type b)
Definition: PersistentLaplacian.hpp:621
std::vector< std::tuple< int, filtration_type, filtration_type, std::vector< spectra_type >, DenseMatrix_PL > > eigenpairs(std::vector< std::tuple< int, filtration_type, filtration_type >> spectra_request_list)
Definition: PersistentLaplacian.hpp:711
DenseMatrix_PL get_L(int dim, filtration_type a, filtration_type b)
Definition: PersistentLaplacian.hpp:241
DenseMatrix_PL get_up(int dim, filtration_type a, filtration_type b)
Definition: PersistentLaplacian.hpp:298
std::vector< filtration_type > get_all_filtrations()
Definition: PersistentLaplacian.hpp:957
PersistentLaplacian()
Definition: PersistentLaplacian.hpp:50
std::vector< std::tuple< int, filtration_type, filtration_type, std::vector< spectra_type > > > spectra(std::vector< std::tuple< int, filtration_type, filtration_type >> spectra_request_list)
Definition: PersistentLaplacian.hpp:581
PersistentLaplacian(std::vector< SparseMatrixInt > boundaries, std::vector< std::vector< filtration_type >> filtrations)
Definition: PersistentLaplacian.hpp:67
std::vector< std::tuple< int, filtration_type, filtration_type, std::vector< spectra_type > > > spectra()
Definition: PersistentLaplacian.hpp:541
std::vector< FilteredBoundaryMatrix< storage > > filtered_boundaries
Boundary matrix assuming real (or integer) coefficients.
Definition: PersistentLaplacian.hpp:38
bool verbose
Print progress if spectra() is called.
Definition: PersistentLaplacian.hpp:39
int top_dim
Top dimension of the complex.
Definition: PersistentLaplacian.hpp:37
Profile profile
Profiler to track time usage of various steps.
Definition: PersistentLaplacian.hpp:41
std::vector< spectra_type > spectra(int dim, filtration_type a, filtration_type b)
Definition: PersistentLaplacian.hpp:442
void set_verbose(bool verbose)
Definition: PersistentLaplacian.hpp:135
void get_down(int dim, filtration_type a, SparseMatrix_storage &L_down)
Definition: PersistentLaplacian.hpp:310
~PersistentLaplacian()
Definition: PersistentLaplacian.hpp:79
std::pair< int, spectra_type > eigenvalues_summarize(std::vector< spectra_type > eigenvalues)
Definition: PersistentLaplacian.hpp:748
void get_up(int dim, filtration_type a, filtration_type b, DenseMatrix_PL &L_up)
Definition: PersistentLaplacian.hpp:284
void set_flipped(bool use_flipped)
Definition: PersistentLaplacian.hpp:141
PersistentLaplacians::FilteredBoundaryMatrix< storage > dummy_d0()
Definition: PersistentLaplacian.hpp:979
SparseMatrix_PL get_L_top_dim_flipped(filtration_type a)
Definition: PersistentLaplacian.hpp:268
std::vector< std::tuple< int, filtration_type, filtration_type > > filtration_list_to_spectra_request(std::vector< filtration_type > filtrations, std::vector< int > dims)
Definition: PersistentLaplacian.hpp:925
std::vector< std::tuple< int, filtration_type, filtration_type, std::vector< spectra_type >, DenseMatrix_PL > > eigenpairs()
Definition: PersistentLaplacian.hpp:674
SparseMatrix_storage get_down(int dim, filtration_type a)
Definition: PersistentLaplacian.hpp:330
void set_boundaries_filtrations(std::vector< SparseMatrixInt > boundaries, std::vector< std::vector< filtration_type >> filtrations)
Definition: PersistentLaplacian.hpp:95
void print_boundaries()
Definition: PersistentLaplacian.hpp:799
bool use_flipped
Compute the top-dimensional Laplacian's eigenvalues in spectra function via the eigenvalues of the sm...
Definition: PersistentLaplacian.hpp:40
Definition: eigs_algorithms.hpp:101
Definition: PersistentLaplacians.cpp:7
Definition: PersistentLaplacians.hpp:43
void stop_sum_up_down()
Definition: PersistentLaplacians.hpp:81
std::vector< filtration_type > filtration_b
Definition: PersistentLaplacians.hpp:47
void start_L_down()
Definition: PersistentLaplacians.hpp:73
std::vector< int > durations_L_up
Definition: PersistentLaplacians.hpp:57
std::vector< int > durations_eigs
Definition: PersistentLaplacians.hpp:55
void start_L()
Definition: PersistentLaplacians.hpp:71
std::vector< int > durations_L_down
Definition: PersistentLaplacians.hpp:58
void start_L_up()
Definition: PersistentLaplacians.hpp:72
std::vector< spectra_type > lambdas
Definition: PersistentLaplacians.hpp:63
void stop_L()
Definition: PersistentLaplacians.hpp:78
void to_csv(std::string filename)
Definition: PersistentLaplacians.cpp:174
std::vector< filtration_type > filtration_a
Definition: PersistentLaplacians.hpp:46
std::vector< int > bettis
Definition: PersistentLaplacians.hpp:62
void start_all()
Definition: PersistentLaplacians.hpp:69
timer all
Definition: PersistentLaplacians.hpp:48
void start_eigs()
Definition: PersistentLaplacians.hpp:70
void stop_L_down()
Definition: PersistentLaplacians.hpp:80
void start_sum_up_down()
Definition: PersistentLaplacians.hpp:74
std::vector< int > L_rows
Definition: PersistentLaplacians.hpp:61
void stop_all()
Definition: PersistentLaplacians.hpp:76
std::vector< int > dims
Definition: PersistentLaplacians.hpp:45
std::vector< int > durations_sum_up_down
Definition: PersistentLaplacians.hpp:59
void stop_L_up()
Definition: PersistentLaplacians.hpp:79
void stop_eigs()
Definition: PersistentLaplacians.hpp:77
int duration
Definition: PersistentLaplacians.hpp:32
float coefficient_type
Definition: typedefs.hpp:13
Eigen::Matrix< coefficient_type, Eigen::Dynamic, Eigen::Dynamic > DenseMatrix_PL
Definition: typedefs.hpp:24
Eigen::Matrix< spectra_type, Eigen::Dynamic, 1 > spectra_vec
Definition: typedefs.hpp:27
Eigen::SparseMatrix< float, Eigen::ColMajor > SparseMatrixFloat
Definition: typedefs.hpp:31
Eigen::SparseMatrix< storage, Eigen::ColMajor > SparseMatrix_storage
Definition: typedefs.hpp:21
Eigen::SparseMatrix< coefficient_type, Eigen::ColMajor > SparseMatrix_PL
Definition: typedefs.hpp:23
float spectra_type
Definition: typedefs.hpp:14
double filtration_type
Definition: typedefs.hpp:34