libROM  v1.0
Data-driven physical simulation library
DMDc.cpp
1 
11 // Description: Implementation of the DMDc algorithm.
12 
13 #include "DMD.h"
14 #include "DMDc.h"
15 
16 #include "linalg/Matrix.h"
17 #include "linalg/Vector.h"
18 #include "linalg/scalapack_wrapper.h"
19 #include "utils/Utilities.h"
20 #include "utils/CSVDatabase.h"
21 #include "utils/HDFDatabase.h"
22 #include "mpi.h"
23 
24 #include <cstring>
25 
26 /* Use C++11 built-in shared pointers if available; else fallback to Boost. */
27 #if __cplusplus >= 201103L
28 #include <memory>
29 #else
30 #include <boost/shared_ptr.hpp>
31 #endif
32 
33 /* Use automatically detected Fortran name-mangling scheme */
34 #define zgetrf CAROM_FC_GLOBAL(zgetrf, ZGETRF)
35 #define zgetri CAROM_FC_GLOBAL(zgetri, ZGETRI)
36 
37 extern "C" {
38  // LU decomposition of a general matrix.
39  void zgetrf(int*, int*, double*, int*, int*, int*);
40 
41  // Generate inverse of a matrix given its LU decomposition.
42  void zgetri(int*, double*, int*, int*, double*, int*, int*);
43 }
44 
45 namespace CAROM {
46 
47 DMDc::DMDc(int dim, int dim_c, std::shared_ptr<Vector> state_offset)
48 {
49  CAROM_VERIFY(dim > 0);
50  CAROM_VERIFY(dim_c > 0);
51 
52  // Get the rank of this process, and the number of processors.
53  int mpi_init;
54  MPI_Initialized(&mpi_init);
55  if (mpi_init == 0) {
56  MPI_Init(nullptr, nullptr);
57  }
58 
59  MPI_Comm_rank(MPI_COMM_WORLD, &d_rank);
60  MPI_Comm_size(MPI_COMM_WORLD, &d_num_procs);
61  d_dim = dim;
62  d_dim_c = dim_c;
63  d_trained = false;
64  d_init_projected = false;
65  setOffset(state_offset);
66 }
67 
68 DMDc::DMDc(int dim, int dim_c, double dt, std::shared_ptr<Vector> state_offset)
69 {
70  CAROM_VERIFY(dim > 0);
71  CAROM_VERIFY(dim_c > 0);
72  CAROM_VERIFY(dt > 0.0);
73 
74  // Get the rank of this process, and the number of processors.
75  int mpi_init;
76  MPI_Initialized(&mpi_init);
77  if (mpi_init == 0) {
78  MPI_Init(nullptr, nullptr);
79  }
80 
81  MPI_Comm_rank(MPI_COMM_WORLD, &d_rank);
82  MPI_Comm_size(MPI_COMM_WORLD, &d_num_procs);
83  d_dim = dim;
84  d_dim_c = dim_c;
85  d_dt = dt;
86  d_trained = false;
87  d_init_projected = false;
88  setOffset(state_offset);
89 }
90 
91 DMDc::DMDc(std::string base_file_name)
92 {
93  // Get the rank of this process, and the number of processors.
94  int mpi_init;
95  MPI_Initialized(&mpi_init);
96  if (mpi_init == 0) {
97  MPI_Init(nullptr, nullptr);
98  }
99 
100  MPI_Comm_rank(MPI_COMM_WORLD, &d_rank);
101  MPI_Comm_size(MPI_COMM_WORLD, &d_num_procs);
102  d_trained = true;
103  d_init_projected = true;
104 
105  load(base_file_name);
106 }
107 
108 DMDc::DMDc(std::vector<std::complex<double>> & eigs,
109  std::shared_ptr<Matrix> & phi_real,
110  std::shared_ptr<Matrix> & phi_imaginary,
111  std::shared_ptr<Matrix> & B_tilde, int k, double dt, double t_offset,
112  std::shared_ptr<Vector> & state_offset, std::shared_ptr<Matrix> & basis)
113 {
114  // Get the rank of this process, and the number of processors.
115  int mpi_init;
116  MPI_Initialized(&mpi_init);
117  if (mpi_init == 0) {
118  MPI_Init(nullptr, nullptr);
119  }
120 
121  MPI_Comm_rank(MPI_COMM_WORLD, &d_rank);
122  MPI_Comm_size(MPI_COMM_WORLD, &d_num_procs);
123  d_trained = true;
124  d_init_projected = false;
125 
126  d_eigs = eigs;
127  d_phi_real = phi_real;
128  d_phi_imaginary = phi_imaginary;
129  d_B_tilde = B_tilde;
130  d_k = k;
131  d_dt = dt;
132  d_t_offset = t_offset;
133  d_basis = basis;
134  setOffset(state_offset);
135 }
136 
137 void DMDc::setOffset(std::shared_ptr<Vector> & offset_vector)
138 {
139  d_state_offset = offset_vector;
140 }
141 
142 void DMDc::takeSample(double* u_in, double t, double* f_in, bool last_step)
143 {
144  CAROM_VERIFY(u_in != 0);
145  CAROM_VERIFY(t >= 0.0);
146  Vector* sample = new Vector(u_in, d_dim, true);
147 
148  double orig_t = t;
149  if (d_snapshots.empty())
150  {
151  d_t_offset = t;
152  t = 0.0;
153  }
154  else
155  {
156  t -= d_t_offset;
157  }
158 
159  // Erase any snapshots taken at the same or later time
160  while (!d_sampled_times.empty() && d_sampled_times.back() >= t)
161  {
162  if (d_rank == 0) std::cout << "Removing existing snapshot at time: " <<
163  d_t_offset + d_sampled_times.back() << std::endl;
164  d_snapshots.pop_back();
165  d_controls.pop_back();
166  d_sampled_times.pop_back();
167  }
168 
169  if (d_snapshots.empty())
170  {
171  d_t_offset = orig_t;
172  t = 0.0;
173  }
174  else
175  {
176  CAROM_VERIFY(d_sampled_times.back() < t);
177  }
178  d_snapshots.push_back(std::shared_ptr<Vector>(sample));
179 
180  if (!last_step)
181  {
182  Vector* control = new Vector(f_in, d_dim_c, false);
183  d_controls.push_back(std::shared_ptr<Vector>(control));
184  }
185 
186  d_sampled_times.push_back(t);
187 }
188 
189 void DMDc::train(double energy_fraction, const Matrix* B)
190 {
191  std::unique_ptr<const Matrix> f_snapshots = getSnapshotMatrix();
192  std::unique_ptr<const Matrix> f_controls = createSnapshotMatrix(d_controls);
193  CAROM_VERIFY(f_snapshots->numColumns() > 1);
194  CAROM_VERIFY(f_controls->numColumns() == f_snapshots->numColumns() - 1);
195  CAROM_VERIFY(energy_fraction > 0 && energy_fraction <= 1);
196  d_energy_fraction = energy_fraction;
197  constructDMDc(*f_snapshots, *f_controls, d_rank, d_num_procs, B);
198 }
199 
200 void DMDc::train(int k, const Matrix* B)
201 {
202  std::unique_ptr<const Matrix> f_snapshots = getSnapshotMatrix();
203  std::unique_ptr<const Matrix> f_controls = createSnapshotMatrix(d_controls);
204  CAROM_VERIFY(f_snapshots->numColumns() > 1);
205  CAROM_VERIFY(f_controls->numColumns() == f_snapshots->numColumns() - 1);
206  CAROM_VERIFY(k > 0 && k <= f_snapshots->numColumns() - 1);
207  d_energy_fraction = -1.0;
208  d_k = k;
209  constructDMDc(*f_snapshots, *f_controls, d_rank, d_num_procs, B);
210 }
211 
212 std::pair<Matrix*, Matrix*>
213 DMDc::computeDMDcSnapshotPair(const Matrix & snapshots, const Matrix & controls,
214  const Matrix* B)
215 {
216  CAROM_VERIFY(snapshots.numColumns() > 1);
217  CAROM_VERIFY(controls.numColumns() == snapshots.numColumns() - 1);
218 
219  // TODO: Making two copies of the snapshot matrix has a lot of overhead.
220  // We need to figure out a way to do submatrix multiplication and to
221  // reimplement this algorithm using one snapshot matrix.
222  int input_control_dim = (B == NULL) ? controls.numRows() : 0;
223  Matrix* f_snapshots_in = new Matrix(snapshots.numRows() + input_control_dim,
224  snapshots.numColumns() - 1, snapshots.distributed());
225  Matrix* f_snapshots_out = new Matrix(snapshots.numRows(),
226  snapshots.numColumns() - 1, snapshots.distributed());
227 
228  // Break up snapshots into snapshots_in and snapshots_out
229  // snapshots_in = all columns of snapshots except last
230  // snapshots_out = all columns of snapshots except first
231  for (int i = 0; i < snapshots.numRows(); i++)
232  {
233  for (int j = 0; j < snapshots.numColumns() - 1; j++)
234  {
235  f_snapshots_in->item(i, j) = snapshots(i, j);
236  f_snapshots_out->item(i, j) = snapshots(i, j + 1);
237  if (d_state_offset)
238  {
239  f_snapshots_in->item(i, j) -= d_state_offset->item(i);
240  f_snapshots_out->item(i, j) -= d_state_offset->item(i);
241  }
242  }
243  }
244 
245  for (int i = 0; i < controls.numRows(); i++)
246  {
247  if (B == NULL)
248  {
249  for (int j = 0; j < snapshots.numColumns() - 1; j++)
250  {
251  f_snapshots_in->item(snapshots.numRows() + i, j) = controls.item(i, j);
252  }
253  }
254  else
255  {
256  std::unique_ptr<Matrix> Bf = B->mult(controls);
257  *f_snapshots_out -= *Bf;
258  }
259  }
260 
261  return std::pair<Matrix*,Matrix*>(f_snapshots_in, f_snapshots_out);
262 }
263 
264 void
265 DMDc::constructDMDc(const Matrix & f_snapshots,
266  const Matrix & f_controls,
267  int d_rank,
268  int d_num_procs,
269  const Matrix* B)
270 {
271  std::pair<Matrix*, Matrix*> f_snapshot_pair = computeDMDcSnapshotPair(
272  f_snapshots, f_controls, B);
273  Matrix* f_snapshots_in = f_snapshot_pair.first;
274  Matrix* f_snapshots_out = f_snapshot_pair.second;
275 
276  int *row_offset = new int[d_num_procs + 1];
277  row_offset[d_num_procs] = f_snapshots_in->numDistributedRows();
278  row_offset[d_rank] = f_snapshots_in->numRows();
279 
280  CAROM_VERIFY(MPI_Allgather(MPI_IN_PLACE,
281  1,
282  MPI_INT,
283  row_offset,
284  1,
285  MPI_INT,
286  MPI_COMM_WORLD) == MPI_SUCCESS);
287  for (int i = d_num_procs - 1; i >= 0; i--) {
288  row_offset[i] = row_offset[i + 1] - row_offset[i];
289  }
290 
291  CAROM_VERIFY(row_offset[0] == 0);
292 
293  int d_blocksize = row_offset[d_num_procs] / d_num_procs;
294  if (row_offset[d_num_procs] % d_num_procs != 0) d_blocksize += 1;
295 
296  SLPK_Matrix svd_input;
297 
298  // Calculate svd of snapshots_in
299  initialize_matrix(&svd_input, f_snapshots_in->numColumns(),
300  f_snapshots_in->numDistributedRows(),
301  1, d_num_procs, d_blocksize, d_blocksize);
302 
303  for (int rank = 0; rank < d_num_procs; ++rank)
304  {
305  scatter_block(&svd_input, 1, row_offset[rank] + 1,
306  f_snapshots_in->getData(),
307  f_snapshots_in->numColumns(),
308  row_offset[rank + 1] - row_offset[rank], rank);
309  }
310 
311  std::unique_ptr<SVDManager> d_factorizer_in(new SVDManager);
312 
313  // This block does the actual ScaLAPACK call to do the factorization.
314  svd_init(d_factorizer_in.get(), &svd_input);
315  d_factorizer_in->dov = 1;
316  factorize(d_factorizer_in.get());
317  free_matrix_data(&svd_input);
318 
319  // Compute how many basis vectors we will actually use.
320  d_num_singular_vectors = std::min(f_snapshots_in->numColumns(),
321  f_snapshots_in->numDistributedRows());
322  for (int i = 0; i < d_num_singular_vectors; i++)
323  {
324  d_sv.push_back(d_factorizer_in->S[i]);
325  }
326 
327  int d_k_in = d_k;
328  if (d_energy_fraction != -1.0)
329  {
330  d_k_in = d_num_singular_vectors;
331  if (d_energy_fraction < 1.0)
332  {
333  double total_energy = 0.0;
334  for (int i = 0; i < d_num_singular_vectors; i++)
335  {
336  total_energy += d_factorizer_in->S[i];
337  }
338  double current_energy = 0.0;
339  for (int i = 0; i < d_num_singular_vectors; i++)
340  {
341  current_energy += d_factorizer_in->S[i];
342  if (current_energy / total_energy >= d_energy_fraction)
343  {
344  d_k_in = i + 1;
345  break;
346  }
347  }
348  }
349  }
350 
351  if (d_rank == 0) std::cout << "Using " << d_k_in << " basis vectors out of " <<
352  d_num_singular_vectors << " for input." << std::endl;
353 
354  // Allocate the appropriate matrices and gather their elements.
355  std::unique_ptr<Matrix> d_basis_in(new Matrix(f_snapshots_in->numRows(), d_k_in,
356  f_snapshots_in->distributed()));
357  Matrix* d_S_inv = new Matrix(d_k_in, d_k_in, false);
358  Matrix* d_basis_right = new Matrix(f_snapshots_in->numColumns(), d_k_in, false);
359 
360  for (int d_rank = 0; d_rank < d_num_procs; ++d_rank) {
361  // V is computed in the transposed order so no reordering necessary.
362  gather_block(&d_basis_in->item(0, 0), d_factorizer_in->V,
363  1, row_offset[static_cast<unsigned>(d_rank)]+1,
364  d_k_in, row_offset[static_cast<unsigned>(d_rank) + 1] -
365  row_offset[static_cast<unsigned>(d_rank)],
366  d_rank);
367 
368  // gather_transposed_block does the same as gather_block, but transposes
369  // it; here, it is used to go from column-major to row-major order.
370  gather_transposed_block(&d_basis_right->item(0, 0), d_factorizer_in->U, 1, 1,
371  f_snapshots_in->numColumns(), d_k_in, d_rank);
372  }
373 
374  // Get inverse of singular values by multiplying by reciprocal.
375  for (int i = 0; i < d_k_in; ++i)
376  {
377  d_S_inv->item(i, i) = 1 / d_factorizer_in->S[static_cast<unsigned>(i)];
378  }
379 
380  if (B == NULL)
381  {
382  // SVD on outputs
383  row_offset[d_num_procs] = f_snapshots_out->numDistributedRows();
384  row_offset[d_rank] = f_snapshots_out->numRows();
385 
386  CAROM_VERIFY(MPI_Allgather(MPI_IN_PLACE,
387  1,
388  MPI_INT,
389  row_offset,
390  1,
391  MPI_INT,
392  MPI_COMM_WORLD) == MPI_SUCCESS);
393  for (int i = d_num_procs - 1; i >= 0; i--) {
394  row_offset[i] = row_offset[i + 1] - row_offset[i];
395  }
396 
397  CAROM_VERIFY(row_offset[0] == 0);
398 
399  int d_blocksize = row_offset[d_num_procs] / d_num_procs;
400  if (row_offset[d_num_procs] % d_num_procs != 0) d_blocksize += 1;
401 
402  SLPK_Matrix svd_output;
403 
404  // Calculate svd of snapshots_out
405  initialize_matrix(&svd_output, f_snapshots_out->numColumns(),
406  f_snapshots_out->numDistributedRows(),
407  1, d_num_procs, d_blocksize, d_blocksize);
408 
409  for (int rank = 0; rank < d_num_procs; ++rank)
410  {
411  scatter_block(&svd_output, 1, row_offset[rank] + 1,
412  f_snapshots_out->getData(),
413  f_snapshots_out->numColumns(),
414  row_offset[rank + 1] - row_offset[rank], rank);
415  }
416 
417  std::unique_ptr<SVDManager> d_factorizer_out(new SVDManager);
418 
419  // This block does the actual ScaLAPACK call to do the factorization.
420  svd_init(d_factorizer_out.get(), &svd_output);
421  d_factorizer_out->dov = 1;
422  factorize(d_factorizer_out.get());
423  free_matrix_data(&svd_output);
424 
425  // Compute how many basis vectors we will actually use.
426  d_num_singular_vectors = std::min(f_snapshots_out->numColumns(),
427  f_snapshots_out->numDistributedRows());
428  for (int i = 0; i < d_num_singular_vectors; i++)
429  {
430  d_sv.push_back(d_factorizer_out->S[i]);
431  }
432 
433  if (d_energy_fraction != -1.0)
434  {
436  if (d_energy_fraction < 1.0)
437  {
438  double total_energy = 0.0;
439  for (int i = 0; i < d_num_singular_vectors; i++)
440  {
441  total_energy += d_factorizer_out->S[i];
442  }
443  double current_energy = 0.0;
444  for (int i = 0; i < d_num_singular_vectors; i++)
445  {
446  current_energy += d_factorizer_out->S[i];
447  if (current_energy / total_energy >= d_energy_fraction)
448  {
449  d_k = i + 1;
450  break;
451  }
452  }
453  }
454  }
455 
456  if (d_rank == 0) std::cout << "Using " << d_k << " basis vectors out of " <<
457  d_num_singular_vectors << " for output." << std::endl;
458 
459  // Allocate the appropriate matrices and gather their elements.
460  d_basis.reset(new Matrix(f_snapshots_out->numRows(), d_k,
461  f_snapshots_out->distributed()));
462  for (int d_rank = 0; d_rank < d_num_procs; ++d_rank) {
463  // V is computed in the transposed order so no reordering necessary.
464  gather_block(&d_basis->item(0, 0), d_factorizer_out->V,
465  1, row_offset[static_cast<unsigned>(d_rank)]+1,
466  d_k, row_offset[static_cast<unsigned>(d_rank) + 1] -
467  row_offset[static_cast<unsigned>(d_rank)],
468  d_rank);
469  }
470 
471  free_matrix_data(d_factorizer_out->U);
472  free_matrix_data(d_factorizer_out->V);
473  free(d_factorizer_out->U);
474  free(d_factorizer_out->V);
475  free(d_factorizer_out->S);
476 
477  release_context(&svd_output);
478  }
479  else
480  {
481  d_basis.reset(d_basis_in.release());
482  d_k = d_k_in;
483  }
484 
485  delete[] row_offset;
486 
487  // Calculate A_tilde and B_tilde
488  std::unique_ptr<Matrix> d_basis_mult_f_snapshots_out = d_basis->transposeMult(
489  *f_snapshots_out);
490  std::unique_ptr<Matrix> d_basis_mult_f_snapshots_out_mult_d_basis_right =
491  d_basis_mult_f_snapshots_out->mult(*d_basis_right);
492  std::shared_ptr<Matrix> d_A_tilde_orig =
493  d_basis_mult_f_snapshots_out_mult_d_basis_right->mult(
494  *d_S_inv);
495 
496  if (B == NULL)
497  {
498  Matrix* d_basis_in_state = new Matrix(f_snapshots.numRows(),
499  d_k_in, f_snapshots.distributed());
500  Matrix* d_basis_in_control_transpose = new Matrix(d_k_in, f_controls.numRows(),
501  false);
502  for (int j = 0; j < d_k_in; j++)
503  {
504  for (int i = 0; i < f_snapshots.numRows(); i++)
505  {
506  d_basis_in_state->item(i, j) = d_basis_in->item(i, j);
507  }
508  for (int i = 0; i < f_controls.numRows(); i++)
509  {
510  d_basis_in_control_transpose->item(j, i) =
511  d_basis_in->item(f_snapshots.numRows() + i, j);
512  }
513  }
514  std::unique_ptr<Matrix> d_basis_state_rot = d_basis_in_state->transposeMult(
515  *d_basis);
516  d_A_tilde = d_A_tilde_orig->mult(*d_basis_state_rot);
517  d_B_tilde = d_A_tilde_orig->mult(*d_basis_in_control_transpose);
518  delete d_basis_in_state;
519  delete d_basis_in_control_transpose;
520  }
521  else
522  {
523  d_A_tilde = d_A_tilde_orig;
524  d_B_tilde = d_basis->transposeMult(*B);
525  }
526 
527  // Calculate the right eigenvalues/eigenvectors of A_tilde
529  d_eigs = eigenpair.eigs;
530 
531  d_phi_real = d_basis->mult(*eigenpair.ev_real);
532  d_phi_imaginary = d_basis->mult(*eigenpair.ev_imaginary);
533 
534  Vector init(d_basis->numRows(), true);
535  for (int i = 0; i < init.dim(); i++)
536  {
537  init(i) = f_snapshots_in->item(i, 0);
538  }
539 
540  // Calculate the projection initial_condition onto column space of d_basis.
541  project(init, f_controls);
542 
543  d_trained = true;
544 
545  delete d_basis_right;
546  delete d_S_inv;
547  delete f_snapshots_in;
548  delete f_snapshots_out;
549  delete eigenpair.ev_real;
550  delete eigenpair.ev_imaginary;
551 
552  free_matrix_data(d_factorizer_in->U);
553  free_matrix_data(d_factorizer_in->V);
554  free(d_factorizer_in->U);
555  free(d_factorizer_in->V);
556  free(d_factorizer_in->S);
557 
558  release_context(&svd_input);
559 }
560 
561 void
562 DMDc::project(const Vector & init, const Matrix & controls, double t_offset)
563 {
564  std::shared_ptr<Matrix> d_phi_real_squared = d_phi_real->transposeMult(
565  *d_phi_real);
566  std::unique_ptr<Matrix> d_phi_real_squared_2 = d_phi_imaginary->transposeMult(
567  *d_phi_imaginary);
568  *d_phi_real_squared += *d_phi_real_squared_2;
569 
570  std::shared_ptr<Matrix> d_phi_imaginary_squared = d_phi_real->transposeMult(
571  *d_phi_imaginary);
572  std::unique_ptr<Matrix> d_phi_imaginary_squared_2 =
573  d_phi_imaginary->transposeMult(*d_phi_real);
574  *d_phi_imaginary_squared -= *d_phi_imaginary_squared_2;
575 
576  const int dprs_row = d_phi_real_squared->numRows();
577  const int dprs_col = d_phi_real_squared->numColumns();
578  double* inverse_input = new double[dprs_row * dprs_col * 2];
579  for (int i = 0; i < d_phi_real_squared->numRows(); i++)
580  {
581  int k = 0;
582  for (int j = 0; j < d_phi_real_squared->numColumns() * 2; j++)
583  {
584  if (j % 2 == 0)
585  {
586  inverse_input[d_phi_real_squared->numColumns() * 2 * i + j] =
587  d_phi_real_squared->item(i, k);
588  }
589  else
590  {
591  inverse_input[d_phi_imaginary_squared->numColumns() * 2 * i + j] =
592  d_phi_imaginary_squared->item(i, k);
593  k++;
594  }
595  }
596  }
597 
598  // Call lapack routines to do the inversion.
599  // Set up some stuff the lapack routines need.
600  int info;
601  int mtx_size = d_phi_real_squared->numColumns();
602  int lwork = mtx_size*mtx_size*std::max(10,d_num_procs);
603  int* ipiv = new int [mtx_size];
604  double* work = new double [lwork];
605 
606  // Now call lapack to do the inversion.
607  zgetrf(&mtx_size, &mtx_size, inverse_input, &mtx_size, ipiv, &info);
608  zgetri(&mtx_size, inverse_input, &mtx_size, ipiv, work, &lwork, &info);
609 
610  d_phi_real_squared_inverse = d_phi_real_squared;
611  d_phi_imaginary_squared_inverse = d_phi_imaginary_squared;
612 
613  for (int i = 0; i < d_phi_real_squared_inverse->numRows(); i++)
614  {
615  int k = 0;
616  for (int j = 0; j < d_phi_real_squared_inverse->numColumns() * 2; j++)
617  {
618  if (j % 2 == 0)
619  {
620  d_phi_real_squared_inverse->item(i, k) =
621  inverse_input[d_phi_real_squared_inverse->numColumns() * 2 * i + j];
622  }
623  else
624  {
625  d_phi_imaginary_squared_inverse->item(i, k) =
626  inverse_input[d_phi_imaginary_squared_inverse->numColumns() * 2 * i + j];
627  k++;
628  }
629  }
630  }
631 
632  // Initial condition
633  std::unique_ptr<Vector> init_real = d_phi_real->transposeMult(init);
634  std::unique_ptr<Vector> init_imaginary = d_phi_imaginary->transposeMult(init);
635 
636  std::unique_ptr<Vector> d_projected_init_real_1 =
637  d_phi_real_squared_inverse->mult(*init_real);
638  std::unique_ptr<Vector> d_projected_init_real_2 =
639  d_phi_imaginary_squared_inverse->mult(*init_imaginary);
640  d_projected_init_real = d_projected_init_real_1->plus(*d_projected_init_real_2);
641 
642  std::unique_ptr<Vector> d_projected_init_imaginary_1 =
643  d_phi_real_squared_inverse->mult(*init_imaginary);
644  std::unique_ptr<Vector> d_projected_init_imaginary_2 =
645  d_phi_imaginary_squared_inverse->mult(*init_real);
646  d_projected_init_imaginary = d_projected_init_imaginary_2->minus(
647  *d_projected_init_imaginary_1);
648 
649  // Controls
650  std::unique_ptr<Matrix> B_tilde_f = d_B_tilde->mult(controls);
651  std::unique_ptr<Matrix> UBf = d_basis->mult(*B_tilde_f);
652  std::unique_ptr<Matrix> controls_real = d_phi_real->transposeMult(*UBf);
653  std::unique_ptr<Matrix> controls_imaginary = d_phi_imaginary->transposeMult(
654  *UBf);
655 
657  std::unique_ptr<Matrix> d_projected_controls_real_2 =
658  d_phi_imaginary_squared_inverse->mult(*controls_imaginary);
659  *d_projected_controls_real += *d_projected_controls_real_2;
660 
662  *controls_real);
663  std::unique_ptr<Matrix> d_projected_controls_imaginary_2 =
664  d_phi_real_squared_inverse->mult(*controls_imaginary);
665  *d_projected_controls_imaginary -= *d_projected_controls_imaginary_2;
666 
667  delete [] inverse_input;
668  delete [] ipiv;
669  delete [] work;
670 
671  if (t_offset >= 0.0)
672  {
673  std::cout << "t_offset is updated from " << d_t_offset
674  << " to " << t_offset << std::endl;
675  d_t_offset = t_offset;
676  }
677  d_init_projected = true;
678 }
679 
680 std::unique_ptr<Vector>
681 DMDc::predict(double t)
682 {
683  CAROM_VERIFY(d_trained);
684  CAROM_VERIFY(d_init_projected);
685  CAROM_VERIFY(t >= 0.0);
686 
687  t -= d_t_offset;
688 
689  int n = round(t / d_dt);
690  //int n = min(round(t / d_dt), d_projected_controls_real->numColumns());
691 
692  std::pair<std::shared_ptr<Matrix>,std::shared_ptr<Matrix>> d_phi_pair =
693  phiMultEigs(t);
694  std::shared_ptr<Matrix> d_phi_mult_eigs_real = d_phi_pair.first;
695  std::shared_ptr<Matrix> d_phi_mult_eigs_imaginary = d_phi_pair.second;
696 
697  std::unique_ptr<Vector> d_predicted_state_real_1 = d_phi_mult_eigs_real->mult(
699  std::unique_ptr<Vector> d_predicted_state_real_2 =
700  d_phi_mult_eigs_imaginary->mult(*d_projected_init_imaginary);
701  std::unique_ptr<Vector> d_predicted_state_real =
702  d_predicted_state_real_1->minus(*d_predicted_state_real_2);
703  addOffset(*d_predicted_state_real);
704 
705  Vector* f_control_real = new Vector(d_basis->numRows(), false);
706  Vector* f_control_imaginary = new Vector(d_basis->numRows(), false);
707  for (int k = 0; k < n; k++)
708  {
709  t -= d_dt;
710  std::pair<std::shared_ptr<Matrix>, std::shared_ptr<Matrix>> d_phi_pair =
711  phiMultEigs(t);
712  d_phi_mult_eigs_real = d_phi_pair.first;
713  d_phi_mult_eigs_imaginary = d_phi_pair.second;
714 
715  d_projected_controls_real->getColumn(k, *f_control_real);
716  d_projected_controls_imaginary->getColumn(k, *f_control_imaginary);
717  d_predicted_state_real_1 = d_phi_mult_eigs_real->mult(
718  *f_control_real);
719  d_predicted_state_real_2 = d_phi_mult_eigs_imaginary->mult(
720  *f_control_imaginary);
721  *d_predicted_state_real += *d_predicted_state_real_1;
722  *d_predicted_state_real -= *d_predicted_state_real_2;
723  }
724 
725  delete f_control_real;
726  delete f_control_imaginary;
727 
728  return d_predicted_state_real;
729 }
730 
731 void
733 {
734  if (d_state_offset)
735  {
736  result += *d_state_offset;
737  }
738 }
739 
740 std::complex<double>
741 DMDc::computeEigExp(std::complex<double> eig, double t)
742 {
743  return std::pow(eig, t / d_dt);
744 }
745 
746 std::pair<std::shared_ptr<Matrix>,std::shared_ptr<Matrix>>
748 {
749  Matrix* d_eigs_exp_real = new Matrix(d_k, d_k, false);
750  Matrix* d_eigs_exp_imaginary = new Matrix(d_k, d_k, false);
751 
752  for (int i = 0; i < d_k; i++)
753  {
754  std::complex<double> eig_exp = computeEigExp(d_eigs[i], t);
755  d_eigs_exp_real->item(i, i) = std::real(eig_exp);
756  d_eigs_exp_imaginary->item(i, i) = std::imag(eig_exp);
757  }
758 
759  std::shared_ptr<Matrix> d_phi_mult_eigs_real = d_phi_real->mult(
760  *d_eigs_exp_real);
761  std::unique_ptr<Matrix> d_phi_mult_eigs_real_2 = d_phi_imaginary->mult(
762  *d_eigs_exp_imaginary);
763  *d_phi_mult_eigs_real -= *d_phi_mult_eigs_real_2;
764  std::shared_ptr<Matrix> d_phi_mult_eigs_imaginary = d_phi_real->mult(
765  *d_eigs_exp_imaginary);
766  std::unique_ptr<Matrix> d_phi_mult_eigs_imaginary_2 = d_phi_imaginary->mult(
767  *d_eigs_exp_real);
768  *d_phi_mult_eigs_imaginary += *d_phi_mult_eigs_imaginary_2;
769 
770  delete d_eigs_exp_real;
771  delete d_eigs_exp_imaginary;
772 
773  return std::pair<std::shared_ptr<Matrix>,std::shared_ptr<Matrix>>
774  (d_phi_mult_eigs_real,
775  d_phi_mult_eigs_imaginary);
776 }
777 
778 double
780 {
781  return d_t_offset;
782 }
783 
784 std::unique_ptr<const Matrix>
786 {
788 }
789 
790 std::unique_ptr<const Matrix>
791 DMDc::createSnapshotMatrix(const std::vector<std::shared_ptr<Vector>> &
792  snapshots)
793 {
794  CAROM_VERIFY(snapshots.size() > 0);
795  CAROM_VERIFY(snapshots[0]->dim() > 0);
796  for (int i = 0 ; i < snapshots.size() - 1; i++)
797  {
798  CAROM_VERIFY(snapshots[i]->dim() == snapshots[i + 1]->dim());
799  CAROM_VERIFY(snapshots[i]->distributed() == snapshots[i + 1]->distributed());
800  }
801 
802  Matrix* snapshot_mat = new Matrix(snapshots[0]->dim(), snapshots.size(),
803  snapshots[0]->distributed());
804 
805  for (int i = 0; i < snapshots[0]->dim(); i++)
806  {
807  for (int j = 0; j < snapshots.size(); j++)
808  {
809  snapshot_mat->item(i, j) = snapshots[j]->item(i);
810  }
811  }
812 
813  return std::unique_ptr<const Matrix>(snapshot_mat);
814 }
815 
816 void
817 DMDc::load(std::string base_file_name)
818 {
819  CAROM_ASSERT(!base_file_name.empty());
820 
821  char tmp[100];
822  std::string full_file_name = base_file_name;
823  HDFDatabase database;
824  database.open(full_file_name, "r");
825 
826  sprintf(tmp, "dt");
827  database.getDouble(tmp, d_dt);
828 
829  sprintf(tmp, "t_offset");
830  database.getDouble(tmp, d_t_offset);
831 
832  sprintf(tmp, "k");
833  database.getInteger(tmp, d_k);
834 
835  sprintf(tmp, "num_eigs");
836  int num_eigs;
837  database.getInteger(tmp, num_eigs);
838 
839  std::vector<double> eigs_real;
840  std::vector<double> eigs_imag;
841 
842  sprintf(tmp, "eigs_real");
843  eigs_real.resize(num_eigs);
844  database.getDoubleArray(tmp, &eigs_real[0], num_eigs);
845 
846  sprintf(tmp, "eigs_imag");
847  eigs_imag.resize(num_eigs);
848  database.getDoubleArray(tmp, &eigs_imag[0], num_eigs);
849 
850  for (int i = 0; i < num_eigs; i++)
851  {
852  d_eigs.push_back(std::complex<double>(eigs_real[i], eigs_imag[i]));
853  }
854  database.close();
855 
856  full_file_name = base_file_name + "_basis";
857  d_basis.reset(new Matrix());
858  d_basis->read(full_file_name);
859 
860  full_file_name = base_file_name + "_A_tilde";
861  d_A_tilde.reset(new Matrix());
862  d_A_tilde->read(full_file_name);
863 
864  full_file_name = base_file_name + "_B_tilde";
865  d_B_tilde.reset(new Matrix());
866  d_B_tilde->read(full_file_name);
867 
868  full_file_name = base_file_name + "_phi_real";
869  d_phi_real.reset(new Matrix());
870  d_phi_real->read(full_file_name);
871 
872  full_file_name = base_file_name + "_phi_imaginary";
873  d_phi_imaginary.reset(new Matrix());
874  d_phi_imaginary->read(full_file_name);
875 
876  full_file_name = base_file_name + "_phi_real_squared_inverse";
877  d_phi_real_squared_inverse.reset(new Matrix());
878  d_phi_real_squared_inverse->read(full_file_name);
879 
880  full_file_name = base_file_name + "_phi_imaginary_squared_inverse";
882  d_phi_imaginary_squared_inverse->read(full_file_name);
883 
884  full_file_name = base_file_name + "_projected_init_real";
885  d_projected_init_real.reset(new Vector());
886  d_projected_init_real->read(full_file_name);
887 
888  full_file_name = base_file_name + "_projected_init_imaginary";
889  d_projected_init_imaginary.reset(new Vector());
890  d_projected_init_imaginary->read(full_file_name);
891 
892  full_file_name = base_file_name + "_state_offset";
893  if (Utilities::file_exist(full_file_name + ".000000"))
894  {
895  d_state_offset.reset(new Vector());
896  d_state_offset->read(full_file_name);
897  }
898 
899  d_init_projected = true;
900  d_trained = true;
901 
902  MPI_Barrier(MPI_COMM_WORLD);
903 }
904 
905 void
906 DMDc::load(const char* base_file_name)
907 {
908  load(std::string(base_file_name));
909 }
910 
911 void
912 DMDc::save(std::string base_file_name)
913 {
914  CAROM_ASSERT(!base_file_name.empty());
915  CAROM_VERIFY(d_trained);
916 
917  if (d_rank == 0)
918  {
919  char tmp[100];
920  std::string full_file_name = base_file_name;
921  HDFDatabase database;
922  database.create(full_file_name);
923 
924  sprintf(tmp, "dt");
925  database.putDouble(tmp, d_dt);
926 
927  sprintf(tmp, "t_offset");
928  database.putDouble(tmp, d_t_offset);
929 
930  sprintf(tmp, "k");
931  database.putInteger(tmp, d_k);
932 
933  sprintf(tmp, "num_eigs");
934  database.putInteger(tmp, d_eigs.size());
935 
936  std::vector<double> eigs_real;
937  std::vector<double> eigs_imag;
938 
939  for (int i = 0; i < d_eigs.size(); i++)
940  {
941  eigs_real.push_back(d_eigs[i].real());
942  eigs_imag.push_back(d_eigs[i].imag());
943  }
944 
945  sprintf(tmp, "eigs_real");
946  database.putDoubleArray(tmp, &eigs_real[0], eigs_real.size());
947 
948  sprintf(tmp, "eigs_imag");
949  database.putDoubleArray(tmp, &eigs_imag[0], eigs_imag.size());
950  database.close();
951  }
952 
953  std::string full_file_name;
954 
955  if (d_basis != NULL)
956  {
957  full_file_name = base_file_name + "_basis";
958  d_basis->write(full_file_name);
959  }
960 
961  if (d_A_tilde != NULL)
962  {
963  full_file_name = base_file_name + "_A_tilde";
964  d_A_tilde->write(full_file_name);
965  }
966 
967  if (d_B_tilde != NULL)
968  {
969  full_file_name = base_file_name + "_B_tilde";
970  d_B_tilde->write(full_file_name);
971  }
972 
973  full_file_name = base_file_name + "_phi_real";
974  d_phi_real->write(full_file_name);
975 
976  full_file_name = base_file_name + "_phi_imaginary";
977  d_phi_imaginary->write(full_file_name);
978 
979  full_file_name = base_file_name + "_phi_real_squared_inverse";
980  d_phi_real_squared_inverse->write(full_file_name);
981 
982  full_file_name = base_file_name + "_phi_imaginary_squared_inverse";
983  d_phi_imaginary_squared_inverse->write(full_file_name);
984 
985  full_file_name = base_file_name + "_projected_init_real";
986  d_projected_init_real->write(full_file_name);
987 
988  full_file_name = base_file_name + "_projected_init_imaginary";
989  d_projected_init_imaginary->write(full_file_name);
990 
991  if (d_state_offset != NULL)
992  {
993  full_file_name = base_file_name + "_state_offset";
994  d_state_offset->write(full_file_name);
995  }
996 
997  MPI_Barrier(MPI_COMM_WORLD);
998 }
999 
1000 void
1001 DMDc::save(const char* base_file_name)
1002 {
1003  save(std::string(base_file_name));
1004 }
1005 
1006 void
1007 DMDc::summary(std::string base_file_name)
1008 {
1009  if (d_rank == 0)
1010  {
1011  CSVDatabase* csv_db(new CSVDatabase);
1012 
1013  csv_db->putDoubleVector(base_file_name + "_singular_value.csv", d_sv,
1015  csv_db->putComplexVector(base_file_name + "_eigenvalue.csv", d_eigs,
1016  d_eigs.size());
1017 
1018  delete csv_db;
1019  }
1020 }
1021 
1022 }
void putDoubleVector(const std::string &file_name, const std::vector< double > &data, int nelements, const bool distributed=false) override
Writes a vector of doubles associated with the supplied filename to the currently open CSV database f...
Definition: CSVDatabase.cpp:94
void putComplexVector(const std::string &file_name, const std::vector< std::complex< double >> &data, int nelements)
Writes a vector of complex doubles associated with the supplied filename.
std::vector< double > d_sv
std::vector holding the signular values.
Definition: DMDc.h:378
std::vector< std::shared_ptr< Vector > > d_controls
std::vector holding the controls.
Definition: DMDc.h:348
std::shared_ptr< Vector > d_projected_init_real
The real part of the projected initial condition.
Definition: DMDc.h:428
double d_dt
The time step size between samples.
Definition: DMDc.h:333
std::shared_ptr< Matrix > d_phi_real_squared_inverse
The real part of d_phi_squared_inverse.
Definition: DMDc.h:418
virtual void setOffset(std::shared_ptr< Vector > &offset_vector)
Set the state offset.
Definition: DMDc.cpp:137
int d_num_procs
The number of processors being run on.
Definition: DMDc.h:318
std::unique_ptr< Matrix > d_projected_controls_real
The real part of the projected controls.
Definition: DMDc.h:438
virtual void addOffset(Vector &result)
Add the state offset when predicting the solution.
Definition: DMDc.cpp:732
std::unique_ptr< Matrix > d_projected_controls_imaginary
The imaginary part of the projected controls.
Definition: DMDc.h:443
int d_num_singular_vectors
The maximum number of singular vectors.
Definition: DMDc.h:373
void project(const Vector &init, const Matrix &controls, double t_offset=-1.0)
Project U using d_phi, where U is the initial condition and the controls. Calculate pinv(phi) x U,...
Definition: DMDc.cpp:562
std::shared_ptr< Matrix > d_A_tilde
A_tilde.
Definition: DMDc.h:398
bool d_trained
Whether the DMDc has been trained or not.
Definition: DMDc.h:363
std::shared_ptr< Vector > d_projected_init_imaginary
The imaginary part of the projected initial condition.
Definition: DMDc.h:433
std::vector< std::shared_ptr< Vector > > d_snapshots
std::vector holding the snapshots.
Definition: DMDc.h:343
std::vector< std::complex< double > > d_eigs
A vector holding the complex eigenvalues of the eigenmodes.
Definition: DMDc.h:448
std::unique_ptr< Vector > predict(double t)
Predict state given a time. Uses the projected initial condition of the training dataset (the first c...
Definition: DMDc.cpp:681
double d_t_offset
The time offset of the first sample.
Definition: DMDc.h:338
virtual std::pair< Matrix *, Matrix * > computeDMDcSnapshotPair(const Matrix &snapshots, const Matrix &controls, const Matrix *B)
Returns a pair of pointers to the minus and plus snapshot matrices.
Definition: DMDc.cpp:213
void constructDMDc(const Matrix &f_snapshots, const Matrix &f_controls, int rank, int num_procs, const Matrix *B)
Construct the DMDc object.
Definition: DMDc.cpp:265
std::unique_ptr< const Matrix > getSnapshotMatrix()
Get the snapshot matrix contained within d_snapshots.
Definition: DMDc.cpp:785
virtual void save(std::string base_file_name)
Save the object state to a file.
Definition: DMDc.cpp:912
virtual std::complex< double > computeEigExp(std::complex< double > eig, double t)
Compute the appropriate exponential function when predicting the solution.
Definition: DMDc.cpp:741
int d_k
The number of columns used after obtaining the SVD decomposition.
Definition: DMDc.h:388
bool d_init_projected
Whether the initial condition has been projected.
Definition: DMDc.h:368
void summary(std::string base_file_name)
Output the DMDc record in CSV files.
Definition: DMDc.cpp:1007
std::unique_ptr< const Matrix > createSnapshotMatrix(const std::vector< std::shared_ptr< Vector >> &snapshots)
Get the snapshot matrix contained within d_snapshots.
Definition: DMDc.cpp:791
std::vector< double > d_sampled_times
The stored times of each sample.
Definition: DMDc.h:353
std::shared_ptr< Matrix > d_phi_real
The real part of d_phi.
Definition: DMDc.h:408
std::shared_ptr< Matrix > d_basis
The left singular vector basis.
Definition: DMDc.h:393
int d_dim_c
The total dimension of the control vector.
Definition: DMDc.h:328
std::shared_ptr< Vector > d_state_offset
State offset in snapshot.
Definition: DMDc.h:358
DMDc()
Unimplemented default constructor.
int d_rank
The rank of the process this object belongs to.
Definition: DMDc.h:313
double d_energy_fraction
The energy fraction used to obtain the DMDc modes.
Definition: DMDc.h:383
double getTimeOffset() const
Get the time offset contained within d_t_offset.
Definition: DMDc.cpp:779
std::pair< std::shared_ptr< Matrix >, std::shared_ptr< Matrix > > phiMultEigs(double t)
Internal function to multiply d_phi with the eigenvalues.
Definition: DMDc.cpp:747
int d_dim
The total dimension of the sample vector.
Definition: DMDc.h:323
std::shared_ptr< Matrix > d_B_tilde
B_tilde.
Definition: DMDc.h:403
std::shared_ptr< Matrix > d_phi_imaginary
The imaginary part of d_phi.
Definition: DMDc.h:413
std::shared_ptr< Matrix > d_phi_imaginary_squared_inverse
The imaginary part of d_phi_squared_inverse.
Definition: DMDc.h:423
virtual void takeSample(double *u_in, double t, double *f_in, bool last_step=false)
Sample the new state, u_in. Any samples in d_snapshots taken at the same or later time will be erased...
Definition: DMDc.cpp:142
virtual void load(std::string base_file_name)
Load the object state from a file.
Definition: DMDc.cpp:817
virtual void train(double energy_fraction, const Matrix *B=NULL)
Train the DMDc model with energy fraction criterion. The control matrix B may be available and used i...
Definition: DMDc.cpp:189
void getInteger(const std::string &key, int &data)
Reads an integer associated with the supplied key from the currently open database file.
Definition: Database.h:181
void putDouble(const std::string &key, double data)
Writes a double associated with the supplied key to currently open database file.
Definition: Database.h:128
void putInteger(const std::string &key, int data)
Writes an integer associated with the supplied key to currently open database file.
Definition: Database.h:94
void getDouble(const std::string &key, double &data)
Reads a double associated with the supplied key from the currently open database file.
Definition: Database.h:215
virtual void getDoubleArray(const std::string &key, double *data, int nelements, const bool distributed=false) override
Reads an array of doubles associated with the supplied key from the currently open HDF5 database file...
virtual bool create(const std::string &file_name, const MPI_Comm comm=MPI_COMM_NULL) override
Creates a new HDF5 database file with the supplied name.
Definition: HDFDatabase.cpp:45
virtual bool open(const std::string &file_name, const std::string &type, const MPI_Comm comm=MPI_COMM_NULL) override
Opens an existing HDF5 database file with the supplied name.
Definition: HDFDatabase.cpp:76
virtual void putDoubleArray(const std::string &key, const double *const data, int nelements, const bool distributed=false) override
Writes an array of doubles associated with the supplied key to the currently open HDF5 database file.
virtual bool close() override
Closes the currently open HDF5 database file.
double * getData() const
Get the matrix data as a pointer.
Definition: Matrix.h:844
bool distributed() const
Returns true if the Matrix is distributed.
Definition: Matrix.h:183
int numColumns() const
Returns the number of columns in the Matrix. This method will return the same value from each process...
Definition: Matrix.h:226
int numDistributedRows() const
Returns the number of rows of the Matrix across all processors.
Definition: Matrix.h:211
const double & item(int row, int col) const
Const Matrix member access. Matrix data is stored in row-major format.
Definition: Matrix.h:746
int numRows() const
Returns the number of rows of the Matrix on this processor.
Definition: Matrix.h:200
std::unique_ptr< Matrix > mult(const Matrix &other) const
Multiplies this Matrix with other and returns the product.
Definition: Matrix.h:273
std::unique_ptr< Matrix > transposeMult(const Matrix &other) const
Multiplies the transpose of this Matrix with other and returns the product.
Definition: Matrix.h:491
int dim() const
Returns the dimension of the Vector on this processor.
Definition: Vector.h:251
ComplexEigenPair NonSymmetricRightEigenSolve(const Matrix &A)
Computes the eigenvectors/eigenvalues of an NxN real nonsymmetric matrix.
Definition: Matrix.cpp:2031
Matrix * ev_real
The real component of the eigenvectors in matrix form.
Definition: Matrix.h:1126
std::vector< std::complex< double > > eigs
The corresponding complex eigenvalues.
Definition: Matrix.h:1136
Matrix * ev_imaginary
The imaginary component of the eigenvectors in matrix form.
Definition: Matrix.h:1131
static bool file_exist(const std::string &filename)
Verifies if a file exists.
Definition: Utilities.cpp:70