libROM  v1.0
Data-driven physical simulation library
IncrementalSVDBrand.cpp
1 
11 // Description: The concrete implementation of the incremental SVD algorithm
12 // using Matthew Brand's "fast update" method.
13 
14 #include "IncrementalSVDBrand.h"
15 #include "utils/HDFDatabase.h"
16 
17 #include "mpi.h"
18 
19 #include <cmath>
20 #include <limits>
21 
22 namespace CAROM {
23 
24 IncrementalSVDBrand::IncrementalSVDBrand(
25  Options options,
26  const std::string& basis_file_name) :
27  IncrementalSVD(
28  options,
29  basis_file_name),
30  d_singular_value_tol(options.singular_value_tol)
31 {
32  CAROM_VERIFY(options.singular_value_tol >= 0);
33 
34  // If the state of the SVD is to be restored, do it now. The base class,
35  // IncrementalSVD, has already opened the database and restored the state
36  // common to all incremental algorithms. This particular class must also
37  // read the state of d_Up and then compute the basis. If the database could
38  // not be found then we can not restore the state.
39  if (options.restore_state && d_state_database) {
40  // Read d_Up.
41  int num_rows;
42  d_state_database->getInteger("Up_num_rows", num_rows);
43  int num_cols;
44  d_state_database->getInteger("Up_num_cols", num_cols);
45  d_Up.reset(new Matrix(num_rows, num_cols, true));
46  d_state_database->getDoubleArray("Up",
47  &d_Up->item(0, 0),
48  num_rows*num_cols);
49 
50  // Close and delete the database.
51  d_state_database->close();
52  delete d_state_database;
53 
54  // Compute the basis.
55  computeBasis();
56  }
57 }
58 
59 IncrementalSVDBrand::~IncrementalSVDBrand()
60 {
61  // If the state of the SVD is to be saved, then create the database now.
62  // The IncrementalSVD base class destructor will save d_S and d_U. This
63  // derived class must save its specific state data, d_Up.
64  //
65  // If there are multiple time intervals then saving and restoring the state
66  // does not make sense as there is not one, all encompassing, basis.
67  if (d_save_state && (!isFirstSample())) {
68  // Create state database file.
69  d_state_database = new HDFDatabase();
70  d_state_database->create(d_state_file_name);
71 
72  // Save d_Up.
73  int num_rows = d_Up->numRows();
74  d_state_database->putInteger("Up_num_rows", num_rows);
75  int num_cols = d_Up->numColumns();
76  d_state_database->putInteger("Up_num_cols", num_cols);
77  d_state_database->putDoubleArray("Up",
78  &d_Up->item(0, 0),
79  num_rows*num_cols);
80  }
81 }
82 
83 std::shared_ptr<const Matrix>
84 IncrementalSVDBrand::getSpatialBasis()
85 {
86  updateSpatialBasis(); // WARNING: this is costly
87 
88  CAROM_ASSERT(d_basis != nullptr);
89  return d_basis;
90 }
91 
92 std::shared_ptr<const Matrix>
93 IncrementalSVDBrand::getTemporalBasis()
94 {
95  updateTemporalBasis();
96 
97  CAROM_ASSERT(d_basis_right != 0);
98  return d_basis_right;
99 }
100 
101 void
102 IncrementalSVDBrand::buildInitialSVD(
103  double* u)
104 {
105  CAROM_VERIFY(u != 0);
106 
107  // Build d_S for this new time interval.
108  d_S.reset(new Vector(1, false));
109  Vector u_vec(u, d_dim, true);
110  double norm_u = u_vec.norm();
111  d_S->item(0) = norm_u;
112 
113  // Build d_Up for this new time interval.
114  d_Up.reset(new Matrix(1, 1, false));
115  d_Up->item(0, 0) = 1.0;
116 
117  // Build d_U for this new time interval.
118  d_U.reset(new Matrix(d_dim, 1, true));
119  for (int i = 0; i < d_dim; ++i) {
120  d_U->item(i, 0) = u[i]/norm_u;
121  }
122 
123  // Build d_W for this new time interval.
124  if (d_update_right_SV) {
125  d_W.reset(new Matrix(1, 1, false));
126  d_W->item(0, 0) = 1.0;
127  }
128 
129  // We now have the first sample for the new time interval.
130  d_num_samples = 1;
131  d_num_rows_of_W = 1;
132 
133 }
134 
135 bool
136 IncrementalSVDBrand::buildIncrementalSVD(
137  double* u, bool add_without_increase)
138 {
139  CAROM_VERIFY(u != 0);
140 
141  // Compute the projection error
142  // (accurate down to the machine precision)
143  Vector u_vec(u, d_dim, true);
144  Vector e_proj(u, d_dim, true);
145 
146  Vector tmp(d_U->numColumns(), false);
147  Vector tmp2(d_U->numRows(), true);
148  d_U->transposeMult(e_proj, tmp);
149  d_U->mult(tmp, tmp2);
150  e_proj -= tmp2; // Gram-Schmidt
151  d_U->transposeMult(e_proj, tmp);
152  d_U->mult(tmp, tmp2);
153  e_proj -= tmp2; // Re-orthogonalization
154 
155  double k = e_proj.inner_product(e_proj);
156  if (k <= 0) {
157  if(d_rank == 0) printf("linearly dependent sample!\n");
158  k = 0;
159  }
160  else {
161  k = sqrt(k);
162  }
163 
164  // Use k to see if the vector addressed by u is linearly dependent
165  // on the left singular vectors.
166  bool linearly_dependent_sample;
167  if ( k < d_linearity_tol ) {
168  if(d_rank == 0) {
169  std::cout << "linearly dependent sample! k = " << k << "\n";
170  std::cout << "d_linearity_tol = " << d_linearity_tol << "\n";
171  }
172  k = 0;
173  linearly_dependent_sample = true;
174  } else if ( d_num_samples >= d_max_basis_dimension || add_without_increase ) {
175  k = 0;
176  linearly_dependent_sample = true;
177  }
178  // Check to see if the "number of samples" (in IncrementalSVD and
179  // its subclasses, d_num_samples appears to be equal to the number
180  // of columns of the left singular vectors) is greater than or equal
181  // to the dimension of snapshot vectors. If so, then the vector
182  // addressed by the pointer u must be linearly dependent on the left
183  // singular vectors.
184  else if (d_num_samples >= d_total_dim) {
185  linearly_dependent_sample = true;
186  }
187  else {
188  linearly_dependent_sample = false;
189  }
190 
191  // Create Q.
192  double* Q;
193  Vector U_mult_u(d_U->transposeMult(u_vec)->getData(), d_num_samples, false);
194  Vector l(d_Up->numColumns(), false);
195  d_Up->transposeMult(U_mult_u, l);
196  constructQ(Q, l, k);
197 
198  // Now get the singular value decomposition of Q.
199  Matrix* A;
200  Matrix* W;
201  Matrix* sigma;
202  bool result = svd(Q, A, sigma, W);
203 
204  // Done with Q.
205  delete [] Q;
206 
207  // If the svd was successful then add the sample. Otherwise clean up and
208  // return.
209  if (result) {
210 
211  // We need to add the sample if it is not linearly dependent or if it is
212  // linearly dependent and we are not skipping linearly dependent samples.
213  if ((linearly_dependent_sample && !d_skip_linearly_dependent) ) {
214  // This sample is linearly dependent and we are not skipping linearly
215  // dependent samples.
216  if(d_rank == 0) std::cout << "adding linearly dependent sample!\n";
217  addLinearlyDependentSample(*A, *W, *sigma);
218  delete sigma;
219  }
220  else if (!linearly_dependent_sample) {
221  // This sample is not linearly dependent.
222 
223  // Compute j
224  Vector* j = new Vector(e_proj.getData(), d_dim, false);
225  for (int i = 0; i < d_dim; ++i) {
226  j->item(i) /= k;
227  }
228 
229  // addNewSample will assign sigma to d_S hence it should not be
230  // deleted upon return.
231  addNewSample(*j, *A, *W, *sigma);
232  delete j;
233  }
234  delete A;
235  delete W;
236  }
237  else {
238  delete A;
239  delete W;
240  delete sigma;
241  }
242  return result;
243 }
244 
245 void
246 IncrementalSVDBrand::updateSpatialBasis()
247 {
248  d_basis = d_U->mult(*d_Up);
249 
250  // remove the smallest singular value if it is smaller than d_singular_value_tol
251  if ( (d_singular_value_tol != 0.0) &&
252  (d_S->item(d_num_samples-1) < d_singular_value_tol) &&
253  (d_num_samples != 1) ) {
254 
255  if (d_rank == 0) {
256  std::cout <<
257  "removing a spatial basis corresponding to the small singular value!\n";
258  }
259 
260  Matrix* d_basis_new = new Matrix(d_dim, d_num_samples-1,
261  d_basis->distributed());
262  for (int row = 0; row < d_dim; ++row) {
263  for (int col = 0; col < d_num_samples-1; ++col) {
264  d_basis_new->item(row, col) = d_basis->item(row,col);
265  }
266  }
267  d_basis.reset(d_basis_new);
268  }
269 
270  // Reorthogonalize if necessary.
271  // (not likely to be called anymore but left for safety)
272  if (fabs(checkOrthogonality(*d_basis)) >
273  std::numeric_limits<double>::epsilon()*static_cast<double>(d_num_samples)) {
274  d_basis->orthogonalize();
275  }
276 }
277 
278 void
279 IncrementalSVDBrand::updateTemporalBasis()
280 {
281  d_basis_right.reset(new Matrix(*d_W));
282 
283  // remove the smallest singular value if it is smaller than d_singular_value_tol
284  if ( (d_singular_value_tol != 0.0) &&
285  (d_S->item(d_num_samples-1) < d_singular_value_tol) &&
286  (d_num_samples != 1) ) {
287 
288  if (d_rank == 0) {
289  std::cout <<
290  "removing a temporal basis corresponding to the small singular value!\n";
291  }
292 
293  Matrix* d_basis_right_new = new Matrix(d_num_rows_of_W, d_num_samples-1,
294  d_basis_right->distributed());
295  for (int row = 0; row < d_num_rows_of_W; ++row) {
296  for (int col = 0; col < d_num_samples-1; ++col) {
297  d_basis_right_new->item(row, col) = d_basis_right->item(row,col);
298  }
299  }
300  d_basis_right.reset(d_basis_right_new);
301  }
302 
303  // Reorthogonalize if necessary.
304  // (not likely to be called anymore but left for safety)
305  if (fabs(checkOrthogonality(*d_basis_right)) >
306  std::numeric_limits<double>::epsilon()*d_num_samples) {
307  d_basis_right->orthogonalize();
308  }
309 }
310 
311 void
312 IncrementalSVDBrand::computeBasis()
313 {
314  if(d_rank == 0) {
315  std::cout << "d_num_samples = " << d_num_samples << "\n";
316  std::cout << "d_num_rows_of_W = " << d_num_rows_of_W << "\n";
317  std::cout << "d_singular_value_tol = " << d_singular_value_tol << "\n";
318  std::cout << "smallest SV = " << d_S->item(d_num_samples-1) << "\n";
319  if (d_num_samples > 1) {
320  std::cout << "next smallest SV = " << d_S->item(d_num_samples-2) << "\n";
321  }
322  }
323 
324  updateSpatialBasis();
325  if (d_update_right_SV)
326  {
327  updateTemporalBasis();
328  }
329 
330  // remove the smallest singular value if it is smaller than d_singular_value_tol
331  if ( (d_singular_value_tol != 0.0) &&
332  (d_S->item(d_num_samples-1) < d_singular_value_tol) &&
333  (d_num_samples != 1) ) {
334 
335  --d_num_samples;
336  }
337 }
338 
339 void
340 IncrementalSVDBrand::addLinearlyDependentSample(
341  const Matrix & A,
342  const Matrix & W,
343  const Matrix & sigma)
344 {
345  // Chop a row and a column off of A to form Amod. Also form
346  // d_S by chopping a row and a column off of sigma.
347  Matrix Amod(d_num_samples, d_num_samples, false);
348  for (int row = 0; row < d_num_samples; ++row) {
349  for (int col = 0; col < d_num_samples; ++col) {
350  Amod.item(row, col) = A.item(row, col);
351  if (row == col)
352  {
353  d_S->item(col) = sigma.item(row, col);
354  }
355  }
356  }
357 
358  // Multiply d_Up and Amod and put result into d_Up.
359  Matrix *Up_times_Amod = new Matrix(d_Up->numRows(), Amod.numColumns(), false);
360  d_Up->mult(Amod, *Up_times_Amod);
361  d_Up.reset(Up_times_Amod);
362 
363  if (d_update_right_SV) {
364  // The new d_W is the product of the current d_W extended by another row
365  // and column and W. The only new value in the extended version of d_W
366  // that is non-zero is the new lower right value and it is 1. We will
367  // construct this product without explicitly forming the extended version of
368  // d_W.
369  Matrix *new_d_W = new Matrix(d_num_rows_of_W+1, d_num_samples, false);
370  for (int row = 0; row < d_num_rows_of_W; ++row) {
371  for (int col = 0; col < d_num_samples; ++col) {
372  double new_d_W_entry = 0.0;
373  for (int entry = 0; entry < d_num_samples; ++entry) {
374  new_d_W_entry += d_W->item(row, entry)*W.item(entry, col);
375  }
376  new_d_W->item(row, col) = new_d_W_entry;
377  }
378  }
379  for (int col = 0; col < d_num_samples; ++col) {
380  new_d_W->item(d_num_rows_of_W, col) = W.item(d_num_samples, col);
381  }
382  d_W.reset(new_d_W);
383  ++d_num_rows_of_W;
384  }
385 
386 }
387 
388 void
389 IncrementalSVDBrand::addNewSample(
390  const Vector & j,
391  const Matrix & A,
392  const Matrix & W,
393  const Matrix & sigma)
394 {
395  // Add j as a new column of d_U.
396  Matrix* newU = new Matrix(d_dim, d_num_samples+1, true);
397  for (int row = 0; row < d_dim; ++row) {
398  for (int col = 0; col < d_num_samples; ++col) {
399  newU->item(row, col) = d_U->item(row, col);
400  }
401  newU->item(row, d_num_samples) = j.item(row);
402  }
403  d_U.reset(newU);
404 
405  if (d_update_right_SV) {
406  // The new d_W is the product of the current d_W extended by another row
407  // and column and W. The only new value in the extended version of d_W
408  // that is non-zero is the new lower right value and it is 1. We will
409  // construct this product without explicitly forming the extended version of
410  // d_W.
411  Matrix *new_d_W = new Matrix(d_num_rows_of_W+1, d_num_samples+1, false);
412  for (int row = 0; row < d_num_rows_of_W; ++row) {
413  for (int col = 0; col < d_num_samples+1; ++col) {
414  double new_d_W_entry = 0.0;
415  for (int entry = 0; entry < d_num_samples; ++entry) {
416  new_d_W_entry += d_W->item(row, entry)*W.item(entry, col);
417  }
418  new_d_W->item(row, col) = new_d_W_entry;
419  }
420  }
421  for (int col = 0; col < d_num_samples+1; ++col) {
422  new_d_W->item(d_num_rows_of_W, col) = W.item(d_num_samples, col);
423  }
424  d_W.reset(new_d_W);
425  }
426 
427  // The new d_Up is the product of the current d_Up extended by another row
428  // and column and A. The only new value in the extended version of d_Up
429  // that is non-zero is the new lower right value and it is 1. We will
430  // construct this product without explicitly forming the extended version of
431  // d_Up.
432  Matrix* new_d_Up = new Matrix(d_num_samples+1, d_num_samples+1, false);
433  for (int row = 0; row < d_num_samples; ++row) {
434  for (int col = 0; col < d_num_samples+1; ++col) {
435  double new_d_Up_entry = 0.0;
436  for (int entry = 0; entry < d_num_samples; ++entry) {
437  new_d_Up_entry += d_Up->item(row, entry)*A.item(entry, col);
438  }
439  new_d_Up->item(row, col) = new_d_Up_entry;
440  }
441  }
442  for (int col = 0; col < d_num_samples+1; ++col) {
443  new_d_Up->item(d_num_samples, col) = A.item(d_num_samples, col);
444  }
445  d_Up.reset(new_d_Up);
446 
447  // d_S = sigma.
448  int num_dim = std::min(sigma.numRows(), sigma.numColumns());
449  d_S.reset(new Vector(num_dim, false));
450  for (int i = 0; i < num_dim; i++) {
451  d_S->item(i) = sigma.item(i,i);
452  }
453 
454  // We now have another sample.
455  ++d_num_samples;
456  ++d_num_rows_of_W;
457 
458  // Reorthogonalize if necessary.
459  long int max_U_dim;
460  if (d_num_samples > d_total_dim) {
461  max_U_dim = d_num_samples;
462  }
463  else {
464  max_U_dim = d_total_dim;
465  }
466  if (fabs(checkOrthogonality(*d_Up)) >
467  std::numeric_limits<double>::epsilon()*static_cast<double>(max_U_dim)) {
468  d_Up->orthogonalize();
469  }
470  if (fabs(checkOrthogonality(*d_U)) >
471  std::numeric_limits<double>::epsilon()*static_cast<double>(max_U_dim)) {
472  d_U->orthogonalize(); // Will not be called, but just in case
473  }
474 
475  if(d_update_right_SV)
476  {
477  if (fabs(checkOrthogonality(*d_W)) >
478  std::numeric_limits<double>::epsilon()*d_num_samples) {
479  d_W->orthogonalize();
480  }
481  }
482 
483 }
484 
485 }