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  Matrix* rand_proj = snapshot_matrix->mult(rand_mat);
104  int rand_proj_rows = rand_proj->numRows();
105  delete rand_mat;
106 
107  Matrix* Q = rand_proj->qr_factorize();
108 
109  // Project d_samples onto Q
110  Matrix* svd_input_mat = Q->transposeMult(snapshot_matrix);
111  int svd_input_mat_distributed_rows = svd_input_mat->numDistributedRows();
112 
113  SLPK_Matrix svd_input;
114  initialize_matrix(&svd_input, svd_input_mat->numColumns(),
115  svd_input_mat->numDistributedRows(),
116  d_npcol, d_nprow, d_blocksize, d_blocksize);
117  scatter_block(&svd_input, 1, 1,
118  svd_input_mat->getData(),
119  svd_input_mat->numColumns(), svd_input_mat->numDistributedRows(),0);
120  delete svd_input_mat;
121  delete snapshot_matrix;
122 
123  // This block does the actual ScaLAPACK call to do the factorization.
124  svd_init(d_factorizer.get(), &svd_input);
125 
126  d_factorizer->dov = 1;
127 
128  factorize(d_factorizer.get());
129  free_matrix_data(&svd_input);
130 
131  // Compute how many basis vectors we will actually return.
132  int sigma_cutoff = 0, hard_cutoff = num_cols;
133  if (d_singular_value_tol == 0) {
134  sigma_cutoff = std::numeric_limits<int>::max();
135  } else {
136  for (int i = 0; i < num_cols; ++i) {
137  if (d_factorizer->S[i] / d_factorizer->S[0] > d_singular_value_tol) {
138  sigma_cutoff += 1;
139  } else {
140  break;
141  }
142  }
143  }
144  if (d_max_basis_dimension != -1 && d_max_basis_dimension < hard_cutoff) {
145  hard_cutoff = d_max_basis_dimension;
146  }
147  int ncolumns = hard_cutoff < sigma_cutoff ? hard_cutoff : sigma_cutoff;
148  CAROM_VERIFY(ncolumns >= 0);
149  ncolumns = std::min(ncolumns, d_subspace_dim);
150 
151  // Allocate the appropriate matrices and gather their elements.
152  d_S = new Vector(ncolumns, false);
153  {
154  CAROM_VERIFY(ncolumns >= 0);
155  unsigned nc = static_cast<unsigned>(ncolumns);
156  memset(&d_S->item(0), 0, nc*sizeof(double));
157  }
158 
159  d_basis = new Matrix(svd_input_mat_distributed_rows, ncolumns, false);
160  d_basis_right = new Matrix(d_subspace_dim, ncolumns, false);
161  // Since the input to the SVD was transposed, U and V are switched.
162  for (int rank = 0; rank < d_num_procs; ++rank) {
163  // V is computed in the transposed order so no reordering necessary.
164  gather_block(&d_basis->item(0, 0), d_factorizer->V, 1, 1,
165  ncolumns, d_subspace_dim, rank);
166  // gather_transposed_block does the same as gather_block, but transposes
167  // it; here, it is used to go from column-major to row-major order.
168  gather_transposed_block(&d_basis_right->item(0, 0), d_factorizer->U,
169  1,
170  1, svd_input_mat_distributed_rows,
171  ncolumns, rank);
172  }
173 
174  for (int i = 0; i < ncolumns; ++i)
175  d_S->item(i) = d_factorizer->S[static_cast<unsigned>(i)];
176 
177  // Lift solution back to higher dimension
178  Matrix* d_new_basis = Q->mult(d_basis);
179  delete d_basis;
180  d_basis = d_new_basis;
181 
182  if (num_rows <= num_cols) {
183  Matrix* temp = d_basis;
184  d_basis = d_basis_right;
185  d_basis_right = temp;
186 
187  int local_rows = -1;
188  if (d_basis->numRows() == d_total_dim)
189  local_rows = d_dim;
190  else {
191  printf("WARNING: RandomizedSVD::d_basis has a subspace dimension %d, different from sample dimension %d\n",
192  d_basis->numRows(), num_rows);
193  local_rows = split_dimension(d_basis->numRows());
194  }
195  d_basis->distribute(local_rows);
196  d_basis_right->gather();
197  }
198 
199  d_basis_is_current = true;
200  delete Q;
201  release_context(&svd_input);
202 
203  if (d_debug_algorithm) {
204  if (d_rank == 0) {
205  printf("Distribution of sampler's A and U:\n");
206  }
207  print_debug_info(d_samples.get());
208  MPI_Barrier(MPI_COMM_WORLD);
209 
210  if (d_rank == 0) {
211  printf("Distribution of sampler's V:\n");
212  }
213  print_debug_info(d_factorizer->V);
214  MPI_Barrier(MPI_COMM_WORLD);
215 
216  if (d_rank == 0) {
217  printf("Computed singular values: ");
218  for (int i = 0; i < ncolumns; ++i)
219  printf("%8.4E ", d_factorizer->S[i]);
220  printf("\n");
221  }
222  }
223 
224 }
225 
226 }
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