STDMD.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2020-2022 OpenCFD Ltd.
9 -------------------------------------------------------------------------------
10 License
11  This file is part of OpenFOAM.
12 
13  OpenFOAM is free software: you can redistribute it and/or modify it
14  under the terms of the GNU General Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
19  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21  for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
25 
26 \*---------------------------------------------------------------------------*/
27 
28 #include "STDMD.H"
29 #include "EigenMatrix.H"
30 #include "QRMatrix.H"
32 
33 using namespace Foam::constant::mathematical;
34 
35 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
36 
37 namespace Foam
38 {
39 namespace DMDModels
40 {
43 }
44 }
45 
46 const Foam::Enum
47 <
48  Foam::DMDModels::STDMD::modeSorterType
49 >
50 Foam::DMDModels::STDMD::modeSorterTypeNames
51 ({
52  { modeSorterType::FIRST_SNAPSHOT, "firstSnapshot" },
53  { modeSorterType::KIEWAT , "kiewat" },
54  { modeSorterType::KOU_ZHANG , "kouZhang" }
55 });
56 
57 
58 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
59 
60 Foam::scalar Foam::DMDModels::STDMD::L2norm(const RMatrix& z) const
61 {
62  #ifdef FULLDEBUG
63  // Check if the given RectangularMatrix is effectively a column vector
64  if (z.n() != 1)
65  {
67  << "Input matrix is not a column vector."
68  << exit(FatalError);
69  }
70  #endif
71  const bool noSqrt = true;
72  scalar result = z.columnNorm(0, noSqrt);
73  reduce(result, sumOp<scalar>());
74 
75  // Heuristic addition to avoid very small or zero norm
76  return max(SMALL, Foam::sqrt(result));
77 }
78 
79 
80 Foam::RectangularMatrix<Foam::scalar> Foam::DMDModels::STDMD::orthonormalise
81 (
82  RMatrix ez
83 ) const
84 {
85  List<scalar> dz(Q_.n());
86  const label sz0 = ez.m();
87  const label sz1 = dz.size();
88 
89  for (label i = 0; i < nGramSchmidt_; ++i)
90  {
91  // dz = Q_ & ez;
92  dz = Zero;
93  for (label i = 0; i < sz0; ++i)
94  {
95  for (label j = 0; j < sz1; ++j)
96  {
97  dz[j] += Q_(i,j)*ez(i,0);
98  }
99  }
100 
101  reduce(dz, sumOp<List<scalar>>());
102 
103  // ez -= Q_*dz;
104  for (label i = 0; i < sz0; ++i)
105  {
106  scalar t = 0;
107  for (label j = 0; j < sz1; ++j)
108  {
109  t += Q_(i,j)*dz[j];
110  }
111  ez(i,0) -= t;
112  }
113  }
114 
115  return ez;
116 }
117 
118 
119 void Foam::DMDModels::STDMD::expand(const RMatrix& ez, const scalar ezNorm)
120 {
121  Info<< tab << "Expanding orthonormal basis for field: " << fieldName_
122  << endl;
123 
124  // Stack a column "(ez/ezNorm)" to "Q"
125  Q_.resize(Q_.m(), Q_.n() + 1);
126  Q_.subColumn(Q_.n() - 1) = ez/ezNorm;
127 
128  // Stack a row (Zero) and column (Zero) to "G"
129  G_.resize(G_.m() + 1);
130 }
131 
132 
133 void Foam::DMDModels::STDMD::updateG(const RMatrix& z)
134 {
135  List<scalar> zTilde(Q_.n(), Zero);
136  const label sz0 = z.m();
137  const label sz1 = zTilde.size();
138 
139  // zTilde = Q_ & z;
140  for (label i = 0; i < sz0; ++i)
141  {
142  for (label j = 0; j < sz1; ++j)
143  {
144  zTilde[j] += Q_(i,j)*z(i,0);
145  }
146  }
147 
148  reduce(zTilde, sumOp<List<scalar>>());
149 
150  // G_ += SMatrix(zTilde^zTilde);
151  for (label i = 0; i < G_.m(); ++i)
152  {
153  for (label j = 0; j < G_.n(); ++j)
154  {
155  G_(i, j) += zTilde[i]*zTilde[j];
156  }
157  }
158 }
159 
160 
161 void Foam::DMDModels::STDMD::compress()
162 {
163  Info<< tab << "Compressing orthonormal basis for field: " << fieldName_
164  << endl;
165 
166  RMatrix q(1, 1, Zero);
167 
168  if (Pstream::master())
169  {
170  const bool symmetric = true;
171  const EigenMatrix<scalar> EM(G_, symmetric);
172  const SquareMatrix<scalar>& EVecs = EM.EVecs();
173  DiagonalMatrix<scalar> EVals(EM.EValsRe());
174 
175  // Sort eigenvalues in descending order, and track indices
176  const auto descend = [&](scalar a, scalar b){ return a > b; };
177  const List<label> permutation(EVals.sortPermutation(descend));
178  EVals.applyPermutation(permutation);
179  EVals.resize(EVals.size() - 1);
180 
181  // Update "G"
182  G_ = SMatrix(maxRank_, Zero);
183  G_.diag(EVals);
184 
185  q.resize(Q_.n(), maxRank_);
186  for (label i = 0; i < maxRank_; ++i)
187  {
188  q.subColumn(i) = EVecs.subColumn(permutation[i]);
189  }
190  }
191  Pstream::broadcast(G_);
193 
194  // Update "Q"
195  Q_ = Q_*q;
196 }
197 
198 
199 Foam::SquareMatrix<Foam::scalar> Foam::DMDModels::STDMD::
200 reducedKoopmanOperator()
201 {
202  Info<< tab << "Computing Atilde" << endl;
203 
204  Info<< tab << "Computing local Rx" << endl;
205 
206  RMatrix Rx(1, 1, Zero);
207  {
208  QRMatrix<RMatrix> QRM
209  (
210  Qupper_,
211  QRMatrix<RMatrix>::modes::ECONOMY,
212  QRMatrix<RMatrix>::outputs::ONLY_R
213  );
214  RMatrix& R = QRM.R();
215  Rx.transfer(R);
216  }
217  Rx.round();
218 
219  // Convenience objects A1, A2, A3 to compute "Atilde"
220  RMatrix A1(1, 1, Zero);
221 
222  if (Pstream::parRun())
223  {
224  // Parallel direct tall-skinny QR decomposition
225  // (BGD:Fig. 5) & (DGHL:Fig. 2)
226  // Tests revealed that the distribution of "Q" does not affect
227  // the final outcome of TSQR decomposition up to sign
228 
229  PstreamBuffers pBufs(Pstream::commsTypes::nonBlocking);
230 
231  const label myProcNo = Pstream::myProcNo();
232  const label procNoInSubset = myProcNo % nAgglomerationProcs_;
233 
234  // Send Rx from sender subset neighbours to receiver subset master
235  if (procNoInSubset != 0)
236  {
237  const label procNoSubsetMaster = myProcNo - procNoInSubset;
238 
239  UOPstream toSubsetMaster(procNoSubsetMaster, pBufs);
240  toSubsetMaster << Rx;
241 
242  Rx.clear();
243  }
244 
245  pBufs.finishedSends();
246 
247  // Receive Rx by receiver subset masters
248  if (procNoInSubset == 0)
249  {
250  // Accept only subset masters possessing sender subset neighbours
251  if (myProcNo + 1 < Pstream::nProcs())
252  {
253  for
254  (
255  label nbr = myProcNo + 1;
256  (
257  nbr < myProcNo + nAgglomerationProcs_
258  && nbr < Pstream::nProcs()
259  );
260  ++nbr
261  )
262  {
263  RMatrix recvMtrx;
264 
265  UIPstream fromNbr(nbr, pBufs);
266  fromNbr >> recvMtrx;
267 
268  // Append received Rx to Rx of receiver subset masters
269  if (recvMtrx.size() > 0)
270  {
271  Rx.resize(Rx.m() + recvMtrx.m(), Rx.n());
272  Rx.subMatrix
273  (
274  Rx.m() - recvMtrx.m(),
275  Rx.n() - recvMtrx.n()
276  ) = recvMtrx;
277  }
278  }
279  }
280 
281  {
282  // Apply interim QR decomposition
283  // on Rx of receiver subset masters
284  QRMatrix<RMatrix> QRM
285  (
286  QRMatrix<RMatrix>::modes::ECONOMY,
287  QRMatrix<RMatrix>::outputs::ONLY_R,
288  QRMatrix<RMatrix>::pivoting::FALSE,
289  Rx
290  );
291  RMatrix& R = QRM.R();
292  Rx.transfer(R);
293  }
294  Rx.round();
295  }
296 
297  pBufs.clear();
298 
299  // Send interim Rx from subset masters to the master
300  if (procNoInSubset == 0)
301  {
302  if (!Pstream::master())
303  {
304  UOPstream toMaster(Pstream::masterNo(), pBufs);
305  toMaster << Rx;
306  }
307  }
308 
309  pBufs.finishedSends();
310 
311 
312  // Receive interim Rx by the master, and apply final operations
313  if (Pstream::master())
314  {
315  for
316  (
317  label nbr = Pstream::masterNo() + nAgglomerationProcs_;
318  nbr < Pstream::nProcs();
319  nbr += nAgglomerationProcs_
320  )
321  {
322  RMatrix recvMtrx;
323 
324  UIPstream fromSubsetMaster(nbr, pBufs);
325  fromSubsetMaster >> recvMtrx;
326 
327  // Append received Rx to Rx of the master
328  if (recvMtrx.size() > 0)
329  {
330  Rx.resize(Rx.m() + recvMtrx.m(), Rx.n());
331  Rx.subMatrix
332  (
333  Rx.m() - recvMtrx.m(),
334  Rx.n() - recvMtrx.n()
335  ) = recvMtrx;
336  }
337  }
338 
339  Info<< tab << "Computing TSQR" << endl;
340  {
341  QRMatrix<RMatrix> QRM
342  (
343  QRMatrix<RMatrix>::modes::ECONOMY,
344  QRMatrix<RMatrix>::outputs::ONLY_R,
345  QRMatrix<RMatrix>::pivoting::FALSE,
346  Rx
347  );
348  RMatrix& R = QRM.R();
349  Rx.transfer(R);
350  }
351  Rx.round();
352 
353  Info<< tab << "Computing RxInv" << endl;
354  RxInv_ = MatrixTools::pinv(Rx);
355 
356  Info<< tab << "Computing A1" << endl;
357  A1 = RxInv_*RMatrix(MatrixTools::pinv(Rx*(G_^Rx)));
358  Rx.clear();
359  }
360  Pstream::broadcast(RxInv_);
361  Pstream::broadcast(A1);
362 
363  Info<< tab << "Computing A2" << endl;
364  SMatrix A2(Qupper_ & Qlower_);
365  reduce(A2, sumOp<SMatrix>());
366  Qlower_.clear();
367 
368  Info<< tab << "Computing A3" << endl;
369  SMatrix A3(Qupper_ & Qupper_);
370  reduce(A3, sumOp<SMatrix>());
371 
372  Info<< tab << "Computing Atilde" << endl;
373  // by optimized matrix chain multiplication
374  // obtained by dynamic programming memoisation
375  return SMatrix(RxInv_ & ((A2*(G_*A3))*A1));
376  }
377  else
378  {
379  Info<< tab << "Computing RxInv" << endl;
380  RxInv_ = MatrixTools::pinv(Rx);
381 
382  Info<< tab << "Computing A1" << endl;
383  A1 = RxInv_*RMatrix(MatrixTools::pinv(Rx*(G_^Rx)));
384  Rx.clear();
385 
386  Info<< tab << "Computing A2" << endl;
387  SMatrix A2(Qupper_ & Qlower_);
388  Qlower_.clear();
389 
390  Info<< tab << "Computing A3" << endl;
391  SMatrix A3(Qupper_ & Qupper_);
392 
393  Info<< tab << "Computing Atilde" << endl;
394  return SMatrix(RxInv_ & ((A2*(G_*A3))*A1));
395  }
396 }
397 
398 
399 bool Foam::DMDModels::STDMD::eigendecomposition(SMatrix& Atilde)
400 {
401  bool fail = false;
402 
403  // Compute eigenvalues, and clip eigenvalues with values < "minEval"
404  if (Pstream::master())
405  {
406  Info<< tab << "Computing eigendecomposition" << endl;
407 
408  // Replace "Atilde" by eigenvalues (in-place eigendecomposition)
409  const EigenMatrix<scalar> EM(Atilde);
410  const DiagonalMatrix<scalar>& evalsRe = EM.EValsRe();
411  const DiagonalMatrix<scalar>& evalsIm = EM.EValsIm();
412 
413  evals_.resize(evalsRe.size());
414  evecs_ = RCMatrix(EM.complexEVecs());
415 
416  // Convert scalar eigenvalue pairs to complex eigenvalues
417  label i = 0;
418  for (auto& eval : evals_)
419  {
420  eval = complex(evalsRe[i], evalsIm[i]);
421  ++i;
422  }
423 
424  Info<< tab << "Filtering eigenvalues" << endl;
425 
426  List<complex> cp(evals_.size());
427  auto it =
428  std::copy_if
429  (
430  evals_.cbegin(),
431  evals_.cend(),
432  cp.begin(),
433  [&](const complex& x){ return mag(x) > minEval_; }
434  );
435  cp.resize(std::distance(cp.begin(), it));
436 
437  if (cp.size() == 0)
438  {
440  << "No eigenvalue with mag(eigenvalue) larger than "
441  << "minEval = " << minEval_ << " was found." << nl
442  << " Input field may contain only zero-value elements," << nl
443  << " e.g. no-slip velocity condition on a given patch." << nl
444  << " Otherwise, please decrease the value of minEval." << nl
445  << " Skipping further dynamics/mode computations." << nl
446  << endl;
447 
448  fail = true;
449  }
450  else
451  {
452  evals_ = cp;
453  }
454  }
455  Pstream::broadcast(fail);
456 
457  if (fail)
458  {
459  return false;
460  }
461 
462  Pstream::broadcast(evals_);
463  Pstream::broadcast(evecs_);
464 
465  return true;
466 }
467 
468 
469 void Foam::DMDModels::STDMD::frequencies()
470 {
471  if (Pstream::master())
472  {
473  Info<< tab << "Computing frequencies" << endl;
474 
475  freqs_.resize(evals_.size());
476 
477  // Frequency equation (K:Eq. 81)
478  auto frequencyEquation =
479  [&](const complex& eval)
480  {
481  return Foam::log(max(eval, complex(SMALL))).imag()/(twoPi*dt_);
482  };
483 
484  // Compute frequencies
486  (
487  evals_.cbegin(),
488  evals_.cend(),
489  freqs_.begin(),
490  frequencyEquation
491  );
492 
493  Info<< tab << "Computing frequency indices" << endl;
494 
495  // Initialise iterator by the first search
496  auto margin = [&](const scalar& x){ return (fMin_ <= x && x < fMax_); };
497 
498  auto it = std::find_if(freqs_.cbegin(), freqs_.cend(), margin);
499 
500  while (it != freqs_.end())
501  {
502  freqsi_.append(std::distance(freqs_.cbegin(), it));
503  it = std::find_if(std::next(it), freqs_.cend(), margin);
504  }
505  }
506  Pstream::broadcast(freqs_);
507  Pstream::broadcast(freqsi_);
508 }
509 
510 
511 void Foam::DMDModels::STDMD::amplitudes()
512 {
513  IOField<scalar> snapshot0
514  (
515  IOobject
516  (
517  "snapshot0_" + name_ + "_" + fieldName_,
518  timeName0_,
519  mesh_,
522  false
523  )
524  );
525 
526  // RxInv^T*(Qupper^T*snapshot0)
527  // tmp = (Qupper^T*snapshot0)
528  List<scalar> tmp(Qupper_.n(), Zero);
529  const label sz0 = snapshot0.size();
530  const label sz1 = tmp.size();
531 
532  for (label i = 0; i < sz0; ++i)
533  {
534  for (label j = 0; j < sz1; ++j)
535  {
536  tmp[j] += Qupper_(i,j)*snapshot0[i];
537  }
538  }
539  snapshot0.clear();
540 
541  // R = RxInv^T*tmp
542  List<scalar> R(Qupper_.n(), Zero);
543  for (label i = 0; i < sz1; ++i)
544  {
545  for (label j = 0; j < R.size(); ++j)
546  {
547  R[j] += RxInv_(i,j)*tmp[i];
548  }
549  }
550  tmp.clear();
551 
552  reduce(R, sumOp<List<scalar>>());
553 
554  if (Pstream::master())
555  {
556  Info<< tab << "Computing amplitudes" << endl;
557 
558  amps_.resize(R.size());
559  const RCMatrix pEvecs(MatrixTools::pinv(evecs_));
560 
561  // amps_ = pEvecs*R;
562  for (label i = 0; i < amps_.size(); ++i)
563  {
564  for (label j = 0; j < R.size(); ++j)
565  {
566  amps_[i] += pEvecs(i,j)*R[j];
567  }
568  }
569  }
570  Pstream::broadcast(amps_);
571 }
572 
573 
574 void Foam::DMDModels::STDMD::magnitudes()
575 {
576  if (Pstream::master())
577  {
578  Info<< tab << "Computing magnitudes" << endl;
579 
580  mags_.resize(amps_.size());
581 
582  Info<< tab << "Sorting modes with ";
583 
584  switch (modeSorter_)
585  {
586  case modeSorterType::FIRST_SNAPSHOT:
587  {
588  Info<< "method of first snapshot" << endl;
589 
591  (
592  amps_.cbegin(),
593  amps_.cend(),
594  mags_.begin(),
595  [&](const complex& val){ return mag(val); }
596  );
597  break;
598  }
599 
600  case modeSorterType::KIEWAT:
601  {
602  Info<< "modified weighted amplitude scaling method" << endl;
603 
604  // Eigendecomposition returns evecs with
605  // the unity norm, hence "modeNorm = 1"
606  const scalar modeNorm = 1;
607  const scalar pr = 1;
608  List<scalar> w(step_);
609  std::iota(w.begin(), w.end(), 1);
610  w = sin(twoPi/step_*(w - 1 - 0.25*step_))*pr + pr;
611 
612  forAll(mags_, i)
613  {
614  mags_[i] = sorter(w, amps_[i], evals_[i], modeNorm);
615  }
616 
617  break;
618  }
619 
620  case modeSorterType::KOU_ZHANG:
621  {
622  Info<< "weighted amplitude scaling method" << endl;
623 
624  const scalar modeNorm = 1;
625  const List<scalar> w(step_, 1);
626 
627  forAll(mags_, i)
628  {
629  mags_[i] = sorter(w, amps_[i], evals_[i], modeNorm);
630  }
631 
632  break;
633  }
634 
635  default:
636  break;
637  }
638 
639  Info<< tab << "Computing magnitude indices" << endl;
640 
641  magsi_ = freqsi_;
642 
643  auto descend =
644  [&](const label i1, const label i2)
645  {
646  return !(mags_[i1] < mags_[i2]);
647  };
648 
649  std::sort(magsi_.begin(), magsi_.end(), descend);
650  }
651  Pstream::broadcast(mags_);
652  Pstream::broadcast(magsi_);
653 }
654 
655 
656 Foam::scalar Foam::DMDModels::STDMD::sorter
657 (
658  const List<scalar>& weight,
659  const complex& amplitude,
660  const complex& eigenvalue,
661  const scalar modeNorm
662 ) const
663 {
664  // Omit eigenvalues with very large or very small mags
665  if (!(mag(eigenvalue) < GREAT && mag(eigenvalue) > VSMALL))
666  {
667  Info<< " Returning zero magnitude for mag(eigenvalue) = "
668  << mag(eigenvalue) << endl;
669 
670  return 0;
671  }
672 
673  // Omit eigenvalue-STDMD step combinations that pose a risk of overflow
674  if (mag(eigenvalue)*step_ > sortLimiter_)
675  {
676  Info<< " Returning zero magnitude for"
677  << " mag(eigenvalue) = " << mag(eigenvalue)
678  << " current index = " << step_
679  << " sortLimiter = " << sortLimiter_
680  << endl;
681 
682  return 0;
683  }
684 
685  scalar magnitude = 0;
686 
687  for (label j = 0; j < step_; ++j)
688  {
689  magnitude += weight[j]*modeNorm*mag(amplitude*pow(eigenvalue, j + 1));
690  }
691 
692  return magnitude;
693 }
694 
695 
696 bool Foam::DMDModels::STDMD::dynamics()
697 {
698  SMatrix Atilde(reducedKoopmanOperator());
699  G_.clear();
700 
701  if(!eigendecomposition(Atilde))
702  {
703  return false;
704  }
705 
706  Atilde.clear();
707 
708  frequencies();
709 
710  amplitudes();
711 
712  magnitudes();
713 
714  return true;
715 }
716 
717 
718 bool Foam::DMDModels::STDMD::modes()
719 {
720  Info<< tab << "Computing modes" << endl;
721 
722  bool processed = false;
723  processed = processed || modes<scalar>();
724  processed = processed || modes<vector>();
725  processed = processed || modes<sphericalTensor>();
726  processed = processed || modes<symmTensor>();
727  processed = processed || modes<tensor>();
728 
729  if (!processed)
730  {
731  return false;
732  }
733 
734  return true;
735 }
736 
737 
738 void Foam::DMDModels::STDMD::writeToFile(const word& fileName) const
739 {
740  Info<< tab << "Writing objects of dynamics" << endl;
741 
742  // Write objects of dynamics
743  {
744  autoPtr<OFstream> osPtr =
745  newFileAtTime
746  (
747  fileName + "_" + fieldName_,
748  mesh_.time().timeOutputValue()
749  );
750  OFstream& os = osPtr.ref();
751 
752  writeFileHeader(os);
753 
754  for (const auto& i : labelRange(0, freqs_.size()))
755  {
756  os << freqs_[i] << tab
757  << mags_[i] << tab
758  << amps_[i].real() << tab
759  << amps_[i].imag() << tab
760  << evals_[i].real() << tab
761  << evals_[i].imag() << endl;
762  }
763  }
764 
765  Info<< tab << "Ends output processing for field: " << fieldName_
766  << endl;
767 }
768 
769 
770 void Foam::DMDModels::STDMD::writeFileHeader(Ostream& os) const
771 {
772  writeHeader(os, "DMD output");
773  writeCommented(os, "Frequency");
774  writeTabbed(os, "Magnitude");
775  writeTabbed(os, "Amplitude (real)");
776  writeTabbed(os, "Amplitude (imag)");
777  writeTabbed(os, "Eigenvalue (real)");
778  writeTabbed(os, "Eigenvalue (imag)");
779  os << endl;
780 }
781 
782 
783 void Foam::DMDModels::STDMD::filter()
784 {
785  Info<< tab << "Filtering objects of dynamics" << endl;
786 
787  // Filter objects according to magsi
788  filterIndexed(evals_, magsi_);
789  filterIndexed(evecs_, magsi_);
790  filterIndexed(freqs_, magsi_);
791  filterIndexed(amps_, magsi_);
792  filterIndexed(mags_, magsi_);
793 
794  // Clip objects if need be (assuming objects have the same len)
795  if (freqs_.size() > nModes_)
796  {
797  evals_.resize(nModes_);
798  evecs_.resize(evecs_.m(), nModes_);
799  freqs_.resize(nModes_);
800  amps_.resize(nModes_);
801  mags_.resize(nModes_);
802  }
803 }
804 
805 
806 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
807 
809 (
810  const fvMesh& mesh,
811  const word& name,
812  const dictionary& dict
813 )
814 :
815  DMDModel(mesh, name, dict),
816  modeSorter_
817  (
818  modeSorterTypeNames.getOrDefault
819  (
820  "modeSorter",
821  dict,
822  modeSorterType::FIRST_SNAPSHOT
823  )
824  ),
825  Q_(),
826  G_(),
827  Qupper_(1, 1, Zero),
828  Qlower_(1, 1, Zero),
829  RxInv_(1, 1, Zero),
830  evals_(Zero),
831  evecs_(1, 1, Zero),
832  freqs_(Zero),
833  freqsi_(),
834  amps_(Zero),
835  mags_(Zero),
836  magsi_(Zero),
837  patches_
838  (
839  dict.getOrDefault<wordRes>
840  (
841  "patches",
842  dict.found("patch") ? wordRes(1,dict.get<word>("patch")) : wordRes()
843  )
844  ),
845  fieldName_(dict.get<word>("field")),
846  timeName0_(),
847  minBasis_(0),
848  minEval_(0),
849  dt_(0),
850  sortLimiter_(500),
851  fMin_(0),
852  fMax_(pTraits<label>::max),
853  nGramSchmidt_(5),
854  maxRank_(pTraits<label>::max),
855  step_(0),
856  nModes_(pTraits<label>::max),
857  nAgglomerationProcs_(20),
858  empty_(false)
859 {}
860 
861 
862 // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
863 
865 {
866  writeToFile_ = dict.getOrDefault<bool>("writeToFile", true);
867 
868  modeSorter_ =
869  modeSorterTypeNames.getOrDefault
870  (
871  "modeSorter",
872  dict,
873  modeSorterType::FIRST_SNAPSHOT
874  );
875 
876  minBasis_ =
877  dict.getCheckOrDefault<scalar>("minBasis", 1e-8, scalarMinMax::ge(0));
878 
879  minEval_ =
880  dict.getCheckOrDefault<scalar>("minEVal", 1e-8, scalarMinMax::ge(0));
881 
882  dt_ =
883  dict.getCheckOrDefault<scalar>
884  (
885  "interval",
886  (
887  dict.getCheck<label>("executeInterval", labelMinMax::ge(1))
888  *mesh_.time().deltaT().value()
889  ),
891  );
892 
893  sortLimiter_ =
894  dict.getCheckOrDefault<scalar>("sortLimiter", 500, scalarMinMax::ge(1));
895 
896  nGramSchmidt_ =
897  dict.getCheckOrDefault<label>("nGramSchmidt", 5, labelMinMax::ge(1));
898 
899  maxRank_ =
900  dict.getCheckOrDefault<label>
901  (
902  "maxRank",
904  labelMinMax::ge(1)
905  );
906 
907  nModes_ =
908  dict.getCheckOrDefault<label>
909  (
910  "nModes",
912  labelMinMax::ge(1)
913  );
914 
915  fMin_ = dict.getCheckOrDefault<label>("fMin", 0, labelMinMax::ge(0));
916 
917  fMax_ =
918  dict.getCheckOrDefault<label>
919  (
920  "fMax",
922  labelMinMax::ge(fMin_ + 1)
923  );
924 
925  nAgglomerationProcs_ =
926  dict.getCheckOrDefault<label>
927  (
928  "nAgglomerationProcs",
929  20,
930  labelMinMax::ge(1)
931  );
932 
933  Info<< tab << "Settings are read for:" << nl
934  << tab << " field: " << fieldName_ << nl
935  << tab << " modeSorter: " << modeSorterTypeNames[modeSorter_] << nl
936  << tab << " nModes: " << nModes_ << nl
937  << tab << " maxRank: " << maxRank_ << nl
938  << tab << " nGramSchmidt: " << nGramSchmidt_ << nl
939  << tab << " fMin: " << fMin_ << nl
940  << tab << " fMax: " << fMax_ << nl
941  << tab << " minBasis: " << minBasis_ << nl
942  << tab << " minEVal: " << minEval_ << nl
943  << tab << " sortLimiter: " << sortLimiter_ << nl
944  << tab << " nAgglomerationProcs: " << nAgglomerationProcs_ << nl
945  << endl;
946 
947  return true;
948 }
949 
950 
951 bool Foam::DMDModels::STDMD::initialise(const RMatrix& z)
952 {
953  const scalar norm = L2norm(z);
954 
955  if (mag(norm) > 0)
956  {
957  // First-processed snapshot required by the mode-sorting
958  // algorithms at the final output computations (K:p. 43)
959  {
960  const label nSnap = z.m()/2;
961  timeName0_ =
962  mesh_.time().timeName(mesh_.time().startTime().value());
963 
964  if (nSnap == 0)
965  {
966  empty_ = true;
967  }
968 
969  IOField<scalar> snapshot0
970  (
971  IOobject
972  (
973  "snapshot0_" + name_ + "_" + fieldName_,
974  timeName0_,
975  mesh_,
978  false
979  ),
980  nSnap
981  );
982 
983  std::copy
984  (
985  z.cbegin(),
986  z.cbegin() + nSnap,
987  snapshot0.begin()
988  );
989 
990  const IOstreamOption streamOpt
991  (
992  mesh_.time().writeFormat(),
993  mesh_.time().writeCompression()
994  );
995 
996  fileHandler().writeObject(snapshot0, streamOpt, true);
997  }
998 
999  Q_ = z/norm;
1000  G_ = SMatrix(1);
1001  G_(0,0) = sqr(norm);
1002 
1003  ++step_;
1004 
1005  return true;
1006  }
1007 
1008  return false;
1009 }
1010 
1011 
1012 bool Foam::DMDModels::STDMD::update(const RMatrix& z)
1013 {
1014  {
1015  //- Working copy of the augmented snapshot matrix "z"
1016  //- being used in the classical Gram-Schmidt process
1017  const RMatrix ez(orthonormalise(z));
1018 
1019  // Check basis for "z" and, if necessary, expand "Q" and "G"
1020  const scalar ezNorm = L2norm(ez);
1021  const scalar zNorm = L2norm(z);
1022 
1023  if (ezNorm/zNorm > minBasis_)
1024  {
1025  expand(ez, ezNorm);
1026  }
1027  }
1028 
1029  // Update "G" before the potential orthonormal basis compression
1030  updateG(z);
1031 
1032  // Compress the orthonormal basis if required
1033  if (Q_.n() >= maxRank_)
1034  {
1035  compress();
1036  }
1037 
1038  ++step_;
1040  return true;
1041 }
1042 
1043 
1045 {
1046  // DMD statistics and mode evaluation (K:Fig. 16)
1047  const label nSnap = Q_.m()/2;
1048 
1049  // Move upper and lower halves of "Q" to new containers
1050  Qupper_ = RMatrix(Q_.subMatrix(0, 0, max(nSnap, 1)));
1051  Qlower_ = RMatrix(Q_.subMatrix(nSnap, 0, max(nSnap, 1)));
1052  Q_.clear();
1053 
1054  if (!dynamics())
1055  {
1056  return true;
1057  }
1058 
1059  modes();
1060 
1061  if (Pstream::master() && writeToFile_)
1062  {
1063  writeToFile(word("dynamics"));
1064 
1065  filter();
1066 
1067  writeToFile(word("filtered_dynamics"));
1068  }
1069 
1070  step_ = 0;
1071 
1072  return true;
1073 }
1074 
1075 
1076 // ************************************************************************* //
List< ReturnType > get(const UPtrList< T > &list, const AccessOp &aop)
List of values generated by applying the access operation to each list item.
dictionary dict
static void writeHeader(Ostream &os, const word &fieldName)
dimensionedScalar log(const dimensionedScalar &ds)
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
virtual bool update(const RMatrix &z)
Incremental orthonormal basis update (K:Fig. 15)
Definition: STDMD.C:1005
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
A list of keyword definitions, which are a keyword followed by a number of values (eg...
Definition: dictionary.H:120
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:578
Streaming Total Dynamic Mode Decomposition (i.e. STDMD) is a variant of dynamic mode decomposition...
Definition: STDMD.H:299
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:40
dimensionedSymmTensor sqr(const dimensionedVector &dv)
virtual bool fit()
Compute and write modes and mode dynamics of model data members.
Definition: STDMD.C:1039
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:49
dimensionedScalar sqrt(const dimensionedScalar &ds)
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:487
bool cp(const fileName &src, const fileName &dst, const bool followLink=true)
Copy the source to the destination (recursively if necessary).
Definition: POSIX.C:1016
A traits class, which is primarily used for primitives.
Definition: pTraits.H:50
static bool & parRun() noexcept
Test if this a parallel run.
Definition: UPstream.H:639
autoPtr< fileOperation > fileHandler(std::nullptr_t)
Delete current file handler.
scalar distance(const vector &p1, const vector &p2)
Definition: curveTools.C:12
constexpr char tab
The tab &#39;\t&#39; character(0x09)
Definition: Ostream.H:48
A simple container for options an IOstream can normally have.
virtual bool initialise(const RMatrix &z)
Initialise &#39;Q&#39; and &#39;G&#39; (both require the first two snapshots)
Definition: STDMD.C:944
Ignore writing from objectRegistry::writeObject()
static int myProcNo(const label communicator=worldComm)
Number of this process (starting from masterNo() = 0)
Definition: UPstream.H:688
Macros for easy insertion into run-time selection tables.
static void broadcast(Type &value, const label comm=UPstream::worldComm)
Broadcast content (contiguous or non-contiguous) to all processes in communicator.
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:413
virtual bool read(const dictionary &dict)
Read STDMD settings.
Definition: STDMD.C:857
STDMD(const fvMesh &mesh, const word &name, const dictionary &dict)
Construct from components.
Definition: STDMD.C:802
static label nProcs(const label communicator=worldComm)
Number of ranks in parallel run (for given communicator) is 1 for serial run.
Definition: UPstream.H:656
dynamicFvMesh & mesh
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for INVALID.
Definition: exprTraits.C:52
const dimensionedScalar b
Wien displacement law constant: default SI units: [m.K].
Definition: createFields.H:27
constexpr scalar twoPi(2 *M_PI)
Mathematical constants.
A class for handling words, derived from Foam::string.
Definition: word.H:63
void sort(UList< T > &list)
Sort the list.
Definition: UList.C:334
MatrixType pinv(const MatrixType &A, scalar tol=1e-5)
Moore-Penrose inverse of singular/non-singular square/rectangular scalar/complex matrices (KPP:p...
Definition: QRMatrix.C:577
static constexpr int masterNo() noexcept
Process index of the master (always 0)
Definition: UPstream.H:664
static MinMax< scalar > ge(const scalar &minVal)
A semi-infinite range from minVal to the type max.
Definition: MinMaxI.H:24
A List of wordRe with additional matching capabilities.
Definition: wordRes.H:47
A templated (M x N) rectangular matrix of objects of <Type>, containing M*N elements, derived from Matrix.
virtual bool writeToFile() const
Flag to allow writing to file.
Definition: writeFile.C:281
iterator begin() noexcept
Return an iterator to begin traversing the UList.
Definition: UListI.H:322
label m() const noexcept
The number of rows.
Definition: MatrixI.H:89
dimensionedScalar sin(const dimensionedScalar &ds)
OBJstream os(runTime.globalPath()/outputName)
defineTypeNameAndDebug(combustionModel, 0)
addToRunTimeSelectionTable(decompositionMethod, kahipDecomp, dictionary)
dimensionedScalar pow(const dimensionedScalar &ds, const dimensionedScalar &expt)
#define R(A, B, C, D, E, F, K, M)
#define WarningInFunction
Report a warning using Foam::Warning.
Enum is a wrapper around a list of names/values that represent particular enumeration (or int) values...
Mesh data needed to do the Finite Volume discretisation.
Definition: fvMesh.H:79
static bool master(const label communicator=worldComm)
Am I the master rank.
Definition: UPstream.H:672
Nothing to be read.
void reduce(const List< UPstream::commsStruct > &comms, T &value, const BinaryOp &bop, const int tag, const label comm)
Reduce inplace (cf. MPI Allreduce) using specified communication schedule.
"nonBlocking" : (MPI_Isend, MPI_Irecv)
messageStream Info
Information stream (stdout output on master, null elsewhere)
label find_if(const ListType &input, const UnaryPredicate &pred, const label start=0)
Find index of the first occurrence that satisfies the predicate.
Abstract base class for DMD models to handle DMD characteristics for the DMD function object...
Definition: DMDModel.H:62
constexpr scalar e(M_E)
dimensionSet transform(const dimensionSet &ds)
Return the argument; transformations do not change the dimensions.
Definition: dimensionSet.C:529
tensor Rx(const scalar omega)
Rotational transformation tensor about the x-axis by omega radians.
Definition: transform.H:83
Defines the attributes of an object for which implicit objectRegistry management is supported...
Definition: IOobject.H:166
bool found
A primitive field of type <T> with automated input and output.
A templated (N x N) square matrix of objects of <Type>, containing N*N elements, derived from Matrix...
Definition: SquareMatrix.H:59
Namespace for OpenFOAM.
string expand(const std::string &s, const HashTable< string > &mapping, const char sigil='$')
Expand occurrences of variables according to the mapping and return the expanded string.
Definition: stringOps.C:705
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:157