libROM  v1.0
Data-driven physical simulation library
RandomizedSVD.cpp
1 
11 // Description: A class implementing interface of SVD for the Randomized SVD
12 // algorithm.
13 
14 #include "RandomizedSVD.h"
15 
16 #include "mpi.h"
17 #include "linalg/scalapack_wrapper.h"
18 #include "utils/mpi_utils.h"
19 
20 #include <limits.h>
21 #include <algorithm>
22 #include <random>
23 
24 #include <stdio.h>
25 #include <string.h>
26 
27 namespace CAROM {
28 
29 RandomizedSVD::RandomizedSVD(
30  Options options) :
31  StaticSVD(options),
32  d_subspace_dim(options.randomized_subspace_dim) {
33  srand(options.random_seed);
34 }
35 
36 void
37 RandomizedSVD::computeSVD()
38 {
39  d_samples->n = d_num_samples;
40  delete_factorizer();
41 
42  const int num_rows = d_total_dim;
43  const int num_cols = d_num_samples;
44  if (d_subspace_dim < 1 || d_subspace_dim > std::min(num_rows, num_cols)) {
45  d_subspace_dim = std::min(num_rows, num_cols);
46  }
47 
48  // Get snapshot matrix in distributed format.
49  // If there are less dimensions than samples, use the transpose instead.
50  Matrix* snapshot_matrix;
51  if (num_rows > num_cols) {
52  snapshot_matrix = new Matrix(d_dims[static_cast<unsigned>(d_rank)],
53  num_cols, true);
54  for (int rank = 0; rank < d_num_procs; ++rank) {
55  // gather_transposed_block does the same as gather_block, but transposes
56  // it; here, it is used to go from column-major to row-major order.
57  gather_transposed_block(&snapshot_matrix->item(0, 0), d_samples.get(),
58  d_istarts[static_cast<unsigned>(rank)] + 1, 1,
59  d_dims[static_cast<unsigned>(rank)], num_cols,
60  rank);
61  }
62  }
63  else {
64  std::vector<int> snapshot_transpose_row_offset;
65  const int num_transposed_rows = split_dimension(num_cols, MPI_COMM_WORLD);
66  const int num_cols_check = get_global_offsets(num_transposed_rows,
67  snapshot_transpose_row_offset,
68  MPI_COMM_WORLD);
69  CAROM_VERIFY(num_cols == num_cols_check);
70 
71  snapshot_matrix = new Matrix(num_transposed_rows,
72  num_rows, true);
73 
74  for (int rank = 0; rank < d_num_procs; ++rank) {
75  gather_block(&snapshot_matrix->item(0, 0), d_samples.get(),
76  1, snapshot_transpose_row_offset[rank] + 1,
77  num_rows, snapshot_transpose_row_offset[rank + 1] -
78  snapshot_transpose_row_offset[rank],
79  rank);
80  }
81  }
82 
83  int snapshot_matrix_distributed_rows = std::max(num_rows, num_cols);
84 
85  // Create a random matrix of smaller dimension to project the snapshot matrix
86  // If debug mode is turned on, just set rand_mat as an identity matrix of smaller size
87  // for reproducibility
88  Matrix* rand_mat;
89  if (d_debug_algorithm) {
90  rand_mat = new Matrix(snapshot_matrix->numColumns(), d_subspace_dim, false);
91  for (int i = 0; i < snapshot_matrix->numColumns(); i++) {
92  for (int j = 0; j < d_subspace_dim; j++) {
93  rand_mat->item(i, j) = (i == j);
94  }
95  }
96  }
97  else {
98  rand_mat = new Matrix(snapshot_matrix->numColumns(), d_subspace_dim, false,
99  true);
100  }
101 
102  // Project snapshot matrix onto random subspace
103  std::unique_ptr<Matrix> rand_proj = snapshot_matrix->mult(*rand_mat);
104  int rand_proj_rows = rand_proj->numRows();
105 
106  std::unique_ptr<Matrix> Q = rand_proj->qr_factorize();
107 
108  // Project d_samples onto Q
109  std::unique_ptr<Matrix> svd_input_mat = Q->transposeMult(*snapshot_matrix);
110  int svd_input_mat_distributed_rows = svd_input_mat->numDistributedRows();
111 
112  SLPK_Matrix svd_input;
113  initialize_matrix(&svd_input, svd_input_mat->numColumns(),
114  svd_input_mat->numDistributedRows(),
115  d_npcol, d_nprow, d_blocksize, d_blocksize);
116  scatter_block(&svd_input, 1, 1,
117  svd_input_mat->getData(),
118  svd_input_mat->numColumns(), svd_input_mat->numDistributedRows(),0);
119  delete snapshot_matrix;
120 
121  // This block does the actual ScaLAPACK call to do the factorization.
122  svd_init(d_factorizer.get(), &svd_input);
123 
124  d_factorizer->dov = 1;
125 
126  factorize(d_factorizer.get());
127  free_matrix_data(&svd_input);
128 
129  // Compute how many basis vectors we will actually return.
130  int sigma_cutoff = 0, hard_cutoff = num_cols;
131  if (d_singular_value_tol == 0) {
132  sigma_cutoff = std::numeric_limits<int>::max();
133  } else {
134  for (int i = 0; i < num_cols; ++i) {
135  if (d_factorizer->S[i] / d_factorizer->S[0] > d_singular_value_tol) {
136  sigma_cutoff += 1;
137  } else {
138  break;
139  }
140  }
141  }
142  if (d_max_basis_dimension != -1 && d_max_basis_dimension < hard_cutoff) {
143  hard_cutoff = d_max_basis_dimension;
144  }
145  int ncolumns = hard_cutoff < sigma_cutoff ? hard_cutoff : sigma_cutoff;
146  CAROM_VERIFY(ncolumns >= 0);
147  ncolumns = std::min(ncolumns, d_subspace_dim);
148 
149  // Allocate the appropriate matrices and gather their elements.
150  d_S.reset(new Vector(ncolumns, false));
151  {
152  CAROM_VERIFY(ncolumns >= 0);
153  unsigned nc = static_cast<unsigned>(ncolumns);
154  memset(&d_S->item(0), 0, nc*sizeof(double));
155  }
156 
157  d_basis.reset(new Matrix(svd_input_mat_distributed_rows, ncolumns, false));
158  d_basis_right.reset(new Matrix(d_subspace_dim, ncolumns, false));
159  // Since the input to the SVD was transposed, U and V are switched.
160  for (int rank = 0; rank < d_num_procs; ++rank) {
161  // V is computed in the transposed order so no reordering necessary.
162  gather_block(&d_basis->item(0, 0), d_factorizer->V, 1, 1,
163  ncolumns, d_subspace_dim, rank);
164  // gather_transposed_block does the same as gather_block, but transposes
165  // it; here, it is used to go from column-major to row-major order.
166  gather_transposed_block(&d_basis_right->item(0, 0), d_factorizer->U,
167  1,
168  1, svd_input_mat_distributed_rows,
169  ncolumns, rank);
170  }
171 
172  for (int i = 0; i < ncolumns; ++i)
173  d_S->item(i) = d_factorizer->S[static_cast<unsigned>(i)];
174 
175  // Lift solution back to higher dimension
176  Matrix* d_new_basis = new Matrix(Q->numRows(), d_basis->numColumns(),
177  Q->distributed());
178  Q->mult(*d_basis, *d_new_basis);
179  d_basis.reset(d_new_basis);
180 
181  if (num_rows <= num_cols) {
182  d_basis.swap(d_basis_right);
183 
184  int local_rows = -1;
185  if (d_basis->numRows() == d_total_dim)
186  local_rows = d_dim;
187  else {
188  printf("WARNING: RandomizedSVD::d_basis has a subspace dimension %d, different from sample dimension %d\n",
189  d_basis->numRows(), num_rows);
190  local_rows = split_dimension(d_basis->numRows());
191  }
192  d_basis->distribute(local_rows);
193  d_basis_right->gather();
194  }
195 
196  d_basis_is_current = true;
197  release_context(&svd_input);
198 
199  if (d_debug_algorithm) {
200  if (d_rank == 0) {
201  printf("Distribution of sampler's A and U:\n");
202  }
203  print_debug_info(d_samples.get());
204  MPI_Barrier(MPI_COMM_WORLD);
205 
206  if (d_rank == 0) {
207  printf("Distribution of sampler's V:\n");
208  }
209  print_debug_info(d_factorizer->V);
210  MPI_Barrier(MPI_COMM_WORLD);
211 
212  if (d_rank == 0) {
213  printf("Computed singular values: ");
214  for (int i = 0; i < ncolumns; ++i)
215  printf("%8.4E ", d_factorizer->S[i]);
216  printf("\n");
217  }
218  }
219 
220 }
221 
222 }
int split_dimension(const int dim, const MPI_Comm &comm)
Distribute the global size dim into MPI processes as equally as possible.
Definition: mpi_utils.cpp:23
int get_global_offsets(const int local_dim, std::vector< int > &offsets, const MPI_Comm &comm)
Save integer offsets for each MPI rank under MPI communicator comm, where each rank as the size of lo...
Definition: mpi_utils.cpp:41