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 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"
30 
31 using namespace Foam::constant::mathematical;
32 
33 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
34 
35 namespace Foam
36 {
37 namespace functionObjects
38 {
39  defineTypeNameAndDebug(STDMD, 0);
40  addToRunTimeSelectionTable(functionObject, STDMD, dictionary);
41 }
42 }
43 
44 
45 const Foam::Enum
46 <
47  Foam::functionObjects::STDMD::modeSorterType
48 >
49 Foam::functionObjects::STDMD::modeSorterTypeNames
50 ({
51  { modeSorterType::KIEWAT , "kiewat" },
52  { modeSorterType::KOU_ZHANG , "kouZhang" },
53  { modeSorterType::FIRST_SNAPSHOT, "firstSnap" }
54 });
55 
56 
57 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
58 
59 Foam::scalar Foam::functionObjects::STDMD::parnorm
60 (
61  const RMatrix& colVector
62 ) const
63 {
64  #ifdef FULLDEBUG
65  // Check if the given RectangularMatrix is effectively a column vector
66  if (colVector.n() != 1)
67  {
69  << " # Input matrix is not a column vector. #"
70  << abort(FatalError);
71  }
72  #endif
73  const bool noSqrt = true;
74  scalar result(colVector.columnNorm(0, noSqrt));
75  reduce(result, sumOp<scalar>());
76 
77  // Heuristic addition to avoid very small or zero norm
78  return max(SMALL, Foam::sqrt(result));
79 }
80 
81 
82 void Foam::functionObjects::STDMD::snapshot()
83 {
84  bool processed = false;
85  processed = processed || getSnapshotType<scalar>();
86  processed = processed || getSnapshotType<vector>();
87  processed = processed || getSnapshotType<sphericalTensor>();
88  processed = processed || getSnapshotType<symmTensor>();
89  processed = processed || getSnapshotType<tensor>();
90 
91  if (!processed)
92  {
94  << " # Unknown type of input field during snapshot loading = "
95  << fieldName_ << " #" << nl
96  << " # Do you execute required functionObjects "
97  << "before executing STDMD, e.g. mapFields?"
98  << abort(FatalError);
99  }
100 }
101 
102 
103 void Foam::functionObjects::STDMD::init()
104 {
105  bool processed = false;
106  processed = processed || getComps<scalar>();
107  processed = processed || getComps<vector>();
108  processed = processed || getComps<sphericalTensor>();
109  processed = processed || getComps<symmTensor>();
110  processed = processed || getComps<tensor>();
111 
112  if (!processed)
113  {
115  << " # Unknown type of input field during initialisation = "
116  << fieldName_ << " #" << nl
117  << " # Do you execute required functionObjects "
118  << "before executing STDMD, e.g. mapFields?"
119  << abort(FatalError);
120  }
121 
122  nSnap_ = nComps_*mesh_.nCells();
123 
124  if (nSnap_ <= 0)
125  {
127  << " # Zero-size input field = " << fieldName_ << " #"
128  << abort(FatalError);
129  }
130 
131  currIndex_ = 0;
132  zNorm_ = 0;
133  ezNorm_ = 0;
134 
135  z_ = RMatrix(2*nSnap_, 1, Zero);
136  ez_ = z_;
137  X1_ = RMatrix(nSnap_, 1, Zero);
138  Qz_ = z_;
139  Gz_ = SMatrix(1);
140 
141  RxInv_.clear();
142  Ap_.clear();
143  oEVecs_.clear();
144  oEVals_.clear();
145  oAmps_.clear();
146  oFreqs_.clear();
147  iFreqs_.clear();
148  oMags_.clear();
149  iMags_.clear();
150 
151  initialised_ = true;
152 }
153 
154 
155 void Foam::functionObjects::STDMD::initBasis()
156 {
157  std::copy(z_.cbegin(), z_.cbegin() + nSnap_, X1_.begin());
158 
159  zNorm_ = parnorm(z_);
160  Qz_ = z_/zNorm_;
161  Gz_(0,0) = sqr(zNorm_);
162 }
163 
164 
165 void Foam::functionObjects::STDMD::GramSchmidt()
166 {
167  ez_ = z_;
168  RMatrix dz(Qz_.n(), 1, Zero);
169 
170  for (label i = 0; i < nGramSchmidt_; ++i)
171  {
172  dz = Qz_ & ez_;
173  reduce(dz, sumOp<RMatrix>());
174  ez_ -= Qz_*dz;
175  }
176 }
177 
178 
179 void Foam::functionObjects::STDMD::expandBasis()
180 {
181  Log<< tab << "# " << name() << ":"
182  << " Expanding orthonormal basis for field = " << fieldName_ << " #"
183  << endl;
184 
185  // Stack a column (ez_/ezNorm_) to Qz_
186  Qz_.resize(Qz_.m(), Qz_.n() + 1);
187  Qz_.subColumn(Qz_.n() - 1) = ez_/ezNorm_;
188 
189  // Stack a row (Zero) and column (Zero) to Gz_
190  Gz_.resize(Gz_.m() + 1);
191 }
192 
193 
194 void Foam::functionObjects::STDMD::updateGz()
195 {
196  RMatrix zTilde(Qz_ & z_);
197  reduce(zTilde, sumOp<RMatrix>());
198 
199  const SMatrix zTildes(zTilde^zTilde);
200 
201  Gz_ += zTildes;
202 }
203 
204 
205 void Foam::functionObjects::STDMD::compressBasis()
206 {
207  Log<< tab << "# " << name() << ":"
208  << " Compressing orthonormal basis for field = " << fieldName_ << " #"
209  << endl;
210 
211  RMatrix qz;
212 
213  if (Pstream::master())
214  {
215  const bool symmetric = true;
216  const EigenMatrix<scalar> EM(Gz_, symmetric);
217  const SquareMatrix<scalar>& EVecs = EM.EVecs();
218  DiagonalMatrix<scalar> EVals(EM.EValsRe());
219 
220  if (testEigen_)
221  {
222  testEigenvalues(Gz_, EVals);
223  testEigenvectors(Gz_, EVals, EVecs);
224  }
225 
226  // Sort eigenvalues in descending order, and track indices
227  const auto descend = [&](scalar a, scalar b){ return a > b; };
228  const List<label> permut(EVals.sortPermutation(descend));
229  EVals.applyPermutation(permut);
230  EVals.resize(EVals.size() - 1);
231 
232  // Update Gz_
233  Gz_ = SMatrix(maxRank_, Zero);
234  Gz_.diag(EVals);
235 
236  qz.resize(Qz_.n(), maxRank_);
237  for (label i = 0; i < maxRank_; ++i)
238  {
239  qz.subColumn(i) = EVecs.subColumn(permut[i]);
240  }
241  }
242  Pstream::scatter(Gz_);
243  Pstream::scatter(qz);
244 
245  // Update Qz_
246  Qz_ = Qz_*qz;
247 }
248 
249 
250 void Foam::functionObjects::STDMD::calcAp()
251 {
252  Log<< tab << "# " << name() << ": Computing Ap matrix #" << endl;
253 
254  Log<< tab << "# " << name() << ": Computing local Rx #" << endl;
255  RMatrix Rx;
256  {
257  QRMatrix<RMatrix> QRM
258  (
259  QzUH_,
260  QRMatrix<RMatrix>::outputTypes::REDUCED_R,
261  QRMatrix<RMatrix>::colPivoting::FALSE
262  );
263  Rx = QRM.R();
264  }
265  Rx.round();
266 
267  RMatrix A1; // Convenience objects A1, A2, A3 to compute Ap
268  if (Pstream::parRun())
269  {
270  // Parallel direct tall-skinny QR decomposition
271  // (BGD:Fig. 5) & (DGHL:Fig. 2)
272  // Tests revealed that the distribution of Qz_ does not affect
273  // the final outcome of TSQR decomposition up to sign
274 
275  Log<< tab << "# " << name() << ": Gathering all local Rx #" << endl;
276  List<RMatrix> RxList(Pstream::nProcs());
277  RxList[Pstream::myProcNo()] = Rx;
278  Pstream::gatherList(RxList);
279  Pstream::scatterList(RxList);
280 
281  // Create a global object Rx
282  if (Pstream::master())
283  {
284  // Determine the row size of the global Rx
285  label mRowSum = 0;
286  forAll(RxList, i)
287  {
288  mRowSum += RxList[i].m();
289  }
290 
291  Rx.resize(mRowSum, Rx.n());
292 
293  Log<< tab << "# " << name()
294  << ": Populating the global Rx #" << endl;
295  label m = 0;
296  for (const int i : Pstream::allProcs())
297  {
298  const label mRows = RxList[i].m();
299 
300  Rx.subMatrix(m, 0, mRows) = RxList[i];
301 
302  m += mRows;
303  }
304 
305  Log<< tab << "# " << name()
306  << ": Computing the parallel-direct tall-skinny QR decomp. #"
307  << endl;
308  QRMatrix<RMatrix> QRM
309  (
310  Rx,
311  QRMatrix<RMatrix>::outputTypes::REDUCED_R,
312  QRMatrix<RMatrix>::storeMethods::IN_PLACE,
313  QRMatrix<RMatrix>::colPivoting::FALSE
314  );
315  Rx.round();
316 
317  Log<< tab << "# " << name()
318  << ": Computing Moore-Penrose pseudo-inverse of Rx #"
319  << endl;
320  RxInv_ = pinv(Rx);
321 
322  Log<< tab << "# " << name() << ": Computing Gx #" << endl;
323  const RMatrix Gx(Rx*(Gz_^Rx));
324 
325  Log<< tab << "# " << name()
326  << ": Computing Moore-Penrose pseudo-inverse of Gx #"
327  << endl;
328  const RMatrix GxInv(pinv(Gx));
329 
330  Log<< tab << "# " << name() << ": Computing A1 #" << endl;
331  A1 = RxInv_*GxInv;
332  }
333  Pstream::scatter(RxInv_);
334  Pstream::scatter(A1);
335 
336  Log<< tab << "# " << name() << ": Computing A2 #" << endl;
337  SMatrix A2(QzUH_ & QzUH_);
338  reduce(A2, sumOp<SMatrix>());
339 
340  Log<< tab << "# " << name() << ": Computing A3 #" << endl;
341  SMatrix A3(QzUH_ & QzLH_);
342  reduce(A3, sumOp<SMatrix>());
343 
344  Log<< tab << "# " << name() << ": Computing Ap #" << endl;
345  // by optimized matrix chain multiplication
346  // obtained by dynamic programming memoisation
347  Ap_ = SMatrix(RxInv_ & ((A3*(Gz_*A2))*A1));
348  }
349  else
350  {
351  {
352  Log<< tab << "# " << name()
353  << ": Computing Moore-Penrose pseudo-inverse of Rx #"
354  << endl;
355  RxInv_ = pinv(Rx);
356 
357  Log<< tab << "# " << name() << ": Computing Gx #" << endl;
358  const RMatrix Gx(Rx*(Gz_^Rx));
359 
360  Log<< tab << "# " << name()
361  << ": Computing Moore-Penrose pseudo-inverse of Gx #"
362  << endl;
363  const RMatrix GxInv(pinv(Gx));
364 
365  Log<< tab << "# " << name() << ": Computing A1 #" << endl;
366  A1 = RxInv_*GxInv;
367  }
368 
369  Log<< tab << "# " << name() << ": Computing A2 #" << endl;
370  SMatrix A2(QzUH_ & QzUH_);
371 
372  Log<< tab << "# " << name() << ": Computing A3 #" << endl;
373  SMatrix A3(QzUH_ & QzLH_);
374 
375  Log<< tab << "# " << name() << ": Computing Ap #" << endl;
376  // by optimized matrix chain multiplication
377  // obtained by dynamic programming memoisation
378  Ap_ = SMatrix(RxInv_ & ((A3*(Gz_*A2))*A1));
379  }
380 }
381 
382 
383 void Foam::functionObjects::STDMD::calcEigen()
384 {
385  Log<< tab << "# " << name() << ": Computing eigendecomposition #" << endl;
386 
387  // Compute eigenvalues, and clip eigenvalues with values < minMagEVal_
388  if (Pstream::master())
389  {
390  // Replace Ap by eigenvalues (in-place eigendecomposition)
391  const EigenMatrix<scalar> EM(Ap_);
392  const DiagonalMatrix<scalar>& evalsRe = EM.EValsRe();
393  const DiagonalMatrix<scalar>& evalsIm = EM.EValsIm();
394 
395  oEVals_.resize(evalsRe.size());
396  oEVecs_ = RectangularMatrix<complex>(EM.complexEVecs());
397 
398  // Convert scalar eigenvalue pairs to complex eigenvalues
399  label i = 0;
400  for (auto& eval : oEVals_)
401  {
402  eval = complex(evalsRe[i], evalsIm[i]);
403  ++i;
404  }
405 
406  if (testEigen_)
407  {
408  testEigenvalues(Ap_, evalsRe);
409  testEigenvectors(Ap_, oEVals_, oEVecs_);
410  }
411  }
412 }
413 
414 
415 bool Foam::functionObjects::STDMD::close
416 (
417  const scalar s1,
418  const scalar s2,
419  const scalar absTol,
420  const scalar relTol
421 ) const
422 {
423  if (s1 == s2) return true;
424 
425  return (mag(s1 - s2) <= max(relTol*max(mag(s1), mag(s2)), absTol));
426 }
427 
428 
429 void Foam::functionObjects::STDMD::testEigenvalues
430 (
431  const SquareMatrix<scalar>& A,
432  const DiagonalMatrix<scalar>& EValsRe
433 ) const
434 {
435  const scalar trace = A.trace();
436  // Imaginary part of complex conjugates cancel each other
437  const scalar EValsSum = sum(EValsRe);
438 
439  const bool clse = close(EValsSum - trace, 0, absTol_, relTol_);
440 
441  if (clse)
442  {
443  Info<< tab
444  << " ## PASS: Eigenvalues ##" << endl;
445  }
446  else
447  {
448  Info<< tab
449  << " ## INCONCLUSIVE: Eigenvalues ##" << nl
450  << " # sum(eigenvalues) = " << EValsSum << nl
451  << " # trace(A) = " << trace << nl
452  << " # sum(eigenvalues) - trace(A) ~ 0 ?= "
453  << EValsSum - trace << nl
454  << " #######################"
455  << endl;
456 
457  if (dumpEigen_)
458  {
459  Info<< tab
460  << " ## Operands ##" << nl
461  << " # eigenvalues:" << nl << EValsRe << nl
462  << " # input matrix A:" << nl << A << nl
463  << " ##############"
464  << endl;
465  }
466  }
467 }
468 
469 
470 void Foam::functionObjects::STDMD::testEigenvectors
471 (
472  const SquareMatrix<scalar>& A,
473  const DiagonalMatrix<scalar>& EValsRe,
474  const SquareMatrix<scalar>& EVecs
475 ) const
476 {
477  unsigned nInconclusive = 0;
478 
479  for (label i = 0; i < A.n(); ++i)
480  {
481  const RectangularMatrix<scalar> EVec(EVecs.subColumn(i));
482  const scalar EVal = EValsRe[i];
483  const RectangularMatrix<scalar> leftSide(A*EVec);
484  const RectangularMatrix<scalar> rightSide(EVal*EVec);
485  const scalar rslt = (leftSide - rightSide).norm();
486 
487  const bool clse = close(rslt, 0, absTol_, relTol_);
488 
489  if (!clse)
490  {
491  Info<< tab
492  << " ## INCONCLUSIVE: Eigenvector ##" << nl
493  << " # (A & EVec - EVal*EVec).norm() ~ 0 ?= " << rslt << nl
494  << " ##################################"
495  << endl;
496 
497  if (dumpEigen_)
498  {
499  Info<< tab
500  << " ## Operands ##" << nl
501  << " # eigenvalue:" << nl << EVal << nl
502  << " # input matrix A:" << nl << A << nl
503  << " # eigenvector:" << nl << EVec << nl
504  << " # (A & EVec):" << nl << leftSide << nl
505  << " # (EVal*EVec):" << nl << rightSide << nl
506  << " ##############"
507  << endl;
508  }
509 
510  ++nInconclusive;
511  }
512  }
513 
514  if (!nInconclusive)
515  {
516  Info<< tab
517  << " ## PASS: Eigenvectors ##" << endl;
518  }
519 }
520 
521 
522 void Foam::functionObjects::STDMD::testEigenvectors
523 (
524  const SquareMatrix<scalar>& A,
525  const List<complex>& EVals,
526  const RectangularMatrix<complex>& EVecs
527 ) const
528 {
529  SquareMatrix<complex> B(A.m());
530  auto convertToComplex = [&](const scalar& val) { return complex(val); };
532  (
533  A.cbegin(),
534  A.cend(),
535  B.begin(),
536  convertToComplex
537  );
538 
539  unsigned nInconclusive = 0;
540 
541  for (label i = 0; i < B.n(); ++i)
542  {
543  const RectangularMatrix<complex> EVec(EVecs.subColumn(i));
544  const complex EVal = EVals[i];
545  const RectangularMatrix<complex> leftSide(B*EVec);
546  const RectangularMatrix<complex> rightSide(EVal*EVec);
547  const scalar rslt = (leftSide - rightSide).norm();
548 
549  const bool clse = close(rslt, 0, absTol_, relTol_);
550 
551  if (!clse)
552  {
553  Info<< tab
554  << " ## INCONCLUSIVE: Eigenvector ##" << nl
555  << " # (A & EVec - EVal*EVec).norm() ~ 0 ?= " << rslt << nl
556  << " ##################################"
557  << endl;
558 
559  if (dumpEigen_)
560  {
561  Info<< tab
562  << " ## Operands ##" << nl
563  << " # eigenvalue:" << nl << EVal << nl
564  << " # input matrix A:" << nl << A << nl
565  << " # eigenvector:" << nl << EVec << nl
566  << " # (A & EVec):" << nl << leftSide << nl
567  << " # (EVal*EVec):" << nl << rightSide << nl
568  << " ##############"
569  << endl;
570  }
571 
572  ++nInconclusive;
573  }
574  }
575 
576  if (!nInconclusive)
577  {
578  Info<< tab
579  << " ## PASS: Eigenvectors ##" << endl;
580  }
581 }
582 
583 
584 void Foam::functionObjects::STDMD::filterEVals()
585 {
586  Log<< tab << "# " << name() << ": Filtering eigenvalues #" << endl;
587 
588  if (Pstream::master())
589  {
590  List<complex> cpEVals(oEVals_.size());
591  auto it =
592  std::copy_if
593  (
594  oEVals_.cbegin(),
595  oEVals_.cend(),
596  cpEVals.begin(),
597  [&](const complex& x){ return minMagEVal_ < mag(x); }
598  );
599  cpEVals.resize(std::distance(cpEVals.begin(), it));
600 
601  if (cpEVals.size() == 0)
602  {
604  << "No eigenvalue with mag(eigenvalue) larger than "
605  << "minMagEVal_ = " << minMagEVal_ << " was found."
606  << endl;
607  }
608  else
609  {
610  oEVals_ = cpEVals;
611  }
612  }
613  Pstream::scatter(oEVals_);
614  Pstream::scatter(oEVecs_);
615 }
616 
617 
618 void Foam::functionObjects::STDMD::calcFreqs()
619 {
620  Log<< tab << "# " << name() << ": Computing frequencies #" << endl;
621 
622  if (Pstream::master())
623  {
624  oFreqs_.resize(oEVals_.size());
625 
626  // Frequency equation (K:Eq. 81)
627  auto fEq =
628  [&](const complex& EVal)
629  {
630  return Foam::log(max(EVal, complex(SMALL))).imag()/(twoPi*dt_);
631  };
632 
633  // Compute frequencies
634  std::transform(oEVals_.cbegin(), oEVals_.cend(), oFreqs_.begin(), fEq);
635  }
636 }
637 
638 
639 void Foam::functionObjects::STDMD::calcFreqI()
640 {
641  Log<< tab << "# " << name() << ": Computing frequency indices #" << endl;
642 
643  if (Pstream::master())
644  {
645  // Initialise iterator by the first search
646  auto margin = [&](const scalar& x){ return (fMin_ <= x && x < fMax_); };
647 
648  auto it = std::find_if(oFreqs_.cbegin(), oFreqs_.cend(), margin);
649 
650  while (it != oFreqs_.end())
651  {
652  iFreqs_.append(std::distance(oFreqs_.cbegin(), it));
653  it = std::find_if(std::next(it), oFreqs_.cend(), margin);
654  }
655  }
656  Pstream::scatter(oFreqs_);
657  Pstream::scatter(iFreqs_);
658 }
659 
660 
661 void Foam::functionObjects::STDMD::calcAmps()
662 {
663  Log<< tab << "# " << name() << ": Computing amplitudes #" << endl;
664 
665  RMatrix temp((RxInv_.T()^QzUH_)*X1_);
666  reduce(temp, sumOp<RMatrix>());
667 
668  if (Pstream::master())
669  {
670  oAmps_.resize(temp.m());
671  const RCMatrix pinvEVecs(pinv(oEVecs_));
672 
673  // oAmps_ = pinvEVecs*temp;
674  for (label i = 0; i < oAmps_.size(); ++i)
675  {
676  for (label k = 0; k < temp.m(); ++k)
677  {
678  oAmps_[i] += pinvEVecs(i, k)*temp(k, 0);
679  }
680  }
681  }
682  Pstream::scatter(oAmps_);
683 }
684 
685 
686 void Foam::functionObjects::STDMD::calcMags()
687 {
688  Log<< tab << "# " << name() << ": Computing magnitudes #" << endl;
689 
690  if (Pstream::master())
691  {
692  oMags_.resize(oAmps_.size());
693 
694  Log<< tab << "# " << name() << ": Sorting modes with ";
695 
696  switch (modeSorter_)
697  {
698  case modeSorterType::FIRST_SNAPSHOT:
699  {
700  Log<< "method of first snapshot #" << endl;
701 
703  (
704  oAmps_.cbegin(),
705  oAmps_.cend(),
706  oMags_.begin(),
707  [&](const complex& val){ return mag(val); }
708  );
709  break;
710  }
711 
712  case modeSorterType::KIEWAT:
713  {
714  Log<< "modified weighted amplitude scaling method #" << endl;
715 
716  // Eigendecomposition returns eigenvectors with
717  // the unity norm, hence modeNorm = 1
718  const scalar modeNorm = 1;
719  const scalar pr = 1;
720  List<scalar> w(currIndex_);
721  std::iota(w.begin(), w.end(), 1);
722  w = sin(twoPi/currIndex_*(w - 1 - 0.25*currIndex_))*pr + pr;
723 
724  label i = 0;
725  for (const auto& amp : oAmps_)
726  {
727  oMags_[i] = sorter(w, amp, oEVals_[i], modeNorm);
728  ++i;
729  }
730  break;
731  }
732 
733  case modeSorterType::KOU_ZHANG:
734  {
735  Log<< "weighted amplitude scaling method #" << endl;
736 
737  const scalar modeNorm = 1;
738  const List<scalar> w(currIndex_, 1.0);
739  label i = 0;
740  for (const auto& amp : oAmps_)
741  {
742  oMags_[i] = sorter(w, amp, oEVals_[i], modeNorm);
743  ++i;
744  }
745  break;
746  }
747 
748  default:
749  break;
750  }
751  }
752 }
753 
754 
755 void Foam::functionObjects::STDMD::calcMagI()
756 {
757  Log<< tab << "# " << name() << ": Computing magnitude indices #" << endl;
758 
759  if (Pstream::master())
760  {
761  iMags_ = iFreqs_;
762 
763  auto descend =
764  [&](const label i1, const label i2)
765  {
766  return !(oMags_[i1] < oMags_[i2]);
767  };
768 
769  std::sort(iMags_.begin(), iMags_.end(), descend);
770  }
771  Pstream::scatter(oMags_);
772  Pstream::scatter(iMags_);
773 }
774 
775 
776 void Foam::functionObjects::STDMD::calcModes()
777 {
778  Log<< tab << "# " << name() << ": Computing modes #" << endl;
779 
780  // Resize the number of output variables to nModes_, if need be
781  if (nModes_ < iMags_.size())
782  {
783  iMags_.resize(nModes_);
784  }
785 
786  // Compute and write out modes one by one
787  label oModeI = 0;
788  const RMatrix temp(QzUH_*RxInv_);
789 
790  for (const auto& i : iMags_)
791  {
792  List<complex> mode(temp.m(), Zero);
793 
794  // mode = temp*oEVecs_.subColumn(i);
795  for (label p = 0; p < mode.size(); ++p)
796  {
797  for (label q = 0; q < oEVecs_.m(); ++q)
798  {
799  mode[p] += temp(p, q)*oEVecs_(q, i);
800  }
801  }
802 
803  volVectorField modeReal
804  (
805  IOobject
806  (
807  "modeReal" + std::to_string(oModeI) + fieldName_ + "_" + name(),
808  time_.timeName(),
809  mesh_,
812  ),
813  mesh_,
815  );
816 
817  volVectorField modeImag
818  (
819  IOobject
820  (
821  "modeImag" + std::to_string(oModeI) + fieldName_ + "_" + name(),
822  time_.timeName(),
823  mesh_,
826  ),
827  mesh_,
829  );
830 
831  label s = 0;
832  for (label k = 0; k < nComps_; ++k)
833  {
834  for (label j = 0; j < modeReal.size(); ++j)
835  {
836  const complex& m = mode[s];
837  modeReal[j].replace(k, m.real());
838  modeImag[j].replace(k, m.imag());
839  ++s;
840  }
841  }
842 
843  modeReal.write();
844  modeImag.write();
845 
846  ++oModeI;
847  }
848 }
849 
850 
851 Foam::scalar Foam::functionObjects::STDMD::sorter
852 (
853  const List<scalar>& weight,
854  const complex& amplitude,
855  const complex& eval,
856  const scalar modeNorm
857 ) const
858 {
859  // Omit eigenvalues with very large or very small magnitudes
860  if (!(mag(eval) < GREAT && mag(eval) > VSMALL))
861  {
862  Info<< " Returning zero magnitude for mag(eval) = " << mag(eval)
863  << endl;
864 
865  return 0.0;
866  }
867 
868  // Omit eigenvalue-STDMD step combinations that pose a risk of overflow
869  if (mag(eval)*currIndex_ > sortLimiter_)
870  {
871  Info<< " Returning zero magnitude for"
872  << " mag(eval) = " << mag(eval)
873  << " currIndex = " << currIndex_
874  << " sortLimiter = " << sortLimiter_
875  << endl;
876 
877  return 0.0;
878  }
879 
880  scalar magnitude = 0;
881 
882  for (label j = 0; j < currIndex_; ++j)
883  {
884  magnitude += weight[j]*modeNorm*mag(amplitude*pow(eval, j + 1));
885  }
886 
887  return magnitude;
888 }
889 
890 
891 void Foam::functionObjects::STDMD::writeFileHeader(Ostream& os) const
892 {
893  writeHeader(os, "STDMD output");
894  writeCommented(os, "Frequency");
895  writeTabbed(os, "Magnitude");
896  writeTabbed(os, "Amplitude (real)");
897  writeTabbed(os, "Amplitude (imag)");
898  writeTabbed(os, "Eigenvalue (real)");
899  writeTabbed(os, "Eigenvalue (imag)");
900  os << endl;
901 }
902 
903 
904 void Foam::functionObjects::STDMD::filterOutput()
905 {
906  Log<< tab << "# " << name() << ": Filtering text output #" << endl;
907 
908  if (Pstream::master())
909  {
910  // Filter objects according to iMags
911  filterIndexed(oEVals_, iMags_);
912  filterIndexed(oEVecs_, iMags_);
913  filterIndexed(oFreqs_, iMags_);
914  filterIndexed(oAmps_, iMags_);
915  filterIndexed(oMags_, iMags_);
916 
917  // Clip objects if need be (assuming objects have the same len)
918  if (nModes_ < oFreqs_.size())
919  {
920  oEVals_.resize(nModes_);
921  oEVecs_.resize(oEVecs_.m(), nModes_);
922  oFreqs_.resize(nModes_);
923  oAmps_.resize(nModes_);
924  oMags_.resize(nModes_);
925  }
926  }
927 }
928 
929 
930 void Foam::functionObjects::STDMD::writeOutput(OFstream& os) const
931 {
932  Log<< tab << "# " << name() << ": Writing text output #" << endl;
933 
934  writeFileHeader(os);
935 
936  for (const label i : labelRange(oFreqs_.size()))
937  {
938  os << oFreqs_[i] << tab
939  << oMags_[i] << tab
940  << oAmps_[i].real() << tab
941  << oAmps_[i].imag() << tab
942  << oEVals_[i].real() << tab
943  << oEVals_[i].imag() << endl;
944  }
945 }
946 
947 
948 void Foam::functionObjects::STDMD::calcOutput()
949 {
950  Log<< tab << "# " << name() << ":"
951  << " Starts output processing for field = " << fieldName_ << " #"
952  << endl;
953 
954  // Move upper and lower halves of Qz_ to new containers
955  QzUH_ = Qz_.subMatrix(0, 0, nSnap_);
956  QzLH_ = Qz_.subMatrix(nSnap_, 0, nSnap_);
957  Qz_.clear();
958 
959  calcAp();
960 
961  QzLH_.clear();
962  Gz_.clear();
963 
964  calcEigen();
965 
966  filterEVals();
967 
968  Ap_.clear();
969 
970  calcFreqs();
971 
972  calcFreqI();
973 
974  calcAmps();
975 
976  calcMags();
977 
978  calcMagI();
979 
980  calcModes();
981 
982  // Write out unfiltered data
983  if (Pstream::master() && writeToFile_)
984  {
985  autoPtr<OFstream> osPtr =
986  createFile("uSTDMD" + fieldName_, mesh_.time().timeOutputValue());
987  OFstream& os = osPtr.ref();
988 
989  writeOutput(os);
990  }
991 
992  filterOutput();
993 
994  // Write out filtered data
995  if (Pstream::master() && writeToFile_)
996  {
997  autoPtr<OFstream> osPtr =
998  createFile("STDMD" + fieldName_, mesh_.time().timeOutputValue());
999  OFstream& os = osPtr.ref();
1000 
1001  writeOutput(os);
1002  }
1003 
1004  Log<< tab << "# " << name() << ":"
1005  << " Ends output processing for field = " << fieldName_ << " #"
1006  << endl;
1007 }
1008 
1009 
1010 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
1011 
1014  const word& name,
1015  const Time& runTime,
1016  const dictionary& dict
1017 )
1018 :
1020  writeFile(mesh_, name, typeName, dict, false),
1021  modeSorter_
1022  (
1023  modeSorterTypeNames.getOrDefault
1024  (
1025  "modeSorter",
1026  dict,
1027  modeSorterType::FIRST_SNAPSHOT
1028  )
1029  ),
1030  fieldName_(dict.get<word>("field")),
1031  initialised_(false),
1032  testEigen_(false),
1033  dumpEigen_(false),
1034  nModes_
1035  (
1036  dict.getCheckOrDefault<label>
1037  (
1038  "nModes",
1040  labelMinMax::ge(1)
1041  )
1042  ),
1043  maxRank_
1044  (
1045  dict.getCheckOrDefault<label>
1046  (
1047  "maxRank",
1049  labelMinMax::ge(1)
1050  )
1051  ),
1052  nGramSchmidt_
1053  (
1054  dict.getCheckOrDefault<label>
1055  (
1056  "nGramSchmidt",
1057  5,
1058  labelMinMax::ge(1)
1059  )
1060  ),
1061  fMin_
1062  (
1063  dict.getCheckOrDefault<label>
1064  (
1065  "fMin",
1066  0,
1067  labelMinMax::ge(0)
1068  )
1069  ),
1070  fMax_
1071  (
1072  dict.getCheckOrDefault<label>
1073  (
1074  "fMax",
1076  labelMinMax::ge(fMin_ + 1)
1077  )
1078  ),
1079  nComps_(Zero),
1080  nSnap_(Zero),
1081  currIndex_(Zero),
1082  sortLimiter_(500),
1083  absTol_(1e-2),
1084  relTol_(1e-3),
1085  minBasis_(Zero),
1086  dt_(Zero),
1087  minMagEVal_(Zero),
1088  zNorm_(Zero),
1089  ezNorm_(Zero),
1090  z_(),
1091  ez_(),
1092  X1_(),
1093  Qz_(),
1094  Gz_(),
1095  QzUH_(),
1096  QzLH_(),
1097  RxInv_(),
1098  Ap_(),
1099  oEVecs_(),
1100  oEVals_(),
1101  oAmps_(),
1102  oFreqs_(),
1103  iFreqs_(),
1104  oMags_(),
1105  iMags_()
1106 {
1107  Info<< nl
1108  << " ### This STDMD release is the beta release ###" << nl
1109  << " Therefore small-to-medium changes in input/output interfaces "
1110  << "and internal structures should be expected in the next versions."
1111  << endl;
1112 
1113  // Check if varying or fixed time-step computation
1114  if (runTime.isAdjustTimeStep())
1115  {
1117  << " # STDMD: Viable only for fixed time-step computations. #"
1118  << endl;
1119  }
1120 
1121  // Check if mesh topology changing
1122  if (mesh_.topoChanging())
1123  {
1125  << " # STDMD: Available only for non-changing mesh topology. #"
1126  << abort(FatalError);
1127  }
1128 
1129  read(dict);
1130 }
1131 
1132 
1133 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
1134 
1136 {
1138 
1139  testEigen_ = dict.getOrDefault<bool>("testEigen", false);
1140 
1141  if (testEigen_)
1142  {
1143  dumpEigen_ = dict.getOrDefault<bool>("dumpEigen", false);
1144  }
1145 
1146  sortLimiter_ =
1147  dict.getCheckOrDefault<scalar>
1148  (
1149  "sortLimiter",
1150  500,
1151  scalarMinMax::ge(1)
1152  );
1153 
1154  absTol_ =
1155  dict.getCheckOrDefault<scalar>
1156  (
1157  "absTol",
1158  1e-2,
1159  scalarMinMax::ge(0)
1160  );
1161 
1162  relTol_ =
1163  dict.getCheckOrDefault<scalar>
1164  (
1165  "relTol",
1166  1e-3,
1167  scalarMinMax::ge(0)
1168  );
1169 
1170  minBasis_ =
1171  dict.getCheckOrDefault<scalar>
1172  (
1173  "minBasis",
1174  1e-8,
1175  scalarMinMax::ge(0)
1176  );
1177 
1178  dt_ =
1179  dict.getCheckOrDefault<scalar>
1180  (
1181  "stdmdInterval",
1182  (
1183  dict.getCheck<label>("executeInterval", labelMinMax::ge(1))
1184  *mesh_.time().deltaT().value()
1185  ),
1186  scalarMinMax::ge(0)
1187  );
1188 
1189  minMagEVal_ =
1190  dict.getCheckOrDefault<scalar>
1191  (
1192  "minEVal",
1193  1e-8,
1194  scalarMinMax::ge(0)
1195  );
1196 
1197  writeToFile_ = dict.getOrDefault<bool>("writeToFile", true);
1198 
1199  Info<< nl << name() << ":" << nl
1200  << tab << "# Input is read for field = "
1201  << fieldName_ << " #" << nl << endl;
1202 
1203  return true;
1204 }
1205 
1206 
1208 {
1209  Log << type() << " " << name() << " execute:" << endl;
1210 
1211  // STDMD incremental orthonormal basis update (K:Fig. 15)
1212  snapshot();
1213 
1214  if (currIndex_ == 1)
1215  {
1216  initBasis();
1217  }
1218 
1219  if (1 < currIndex_)
1220  {
1221  GramSchmidt();
1222 
1223  // Check basis for z_ and, if necessary, expand Qz_ and Gz_
1224  zNorm_ = parnorm(z_);
1225  ezNorm_ = parnorm(ez_);
1226 
1227  if (minBasis_ < ezNorm_/zNorm_)
1228  {
1229  expandBasis();
1230  }
1231 
1232  updateGz();
1233 
1234  // Compress the orthonormal basis if required
1235  if (maxRank_ < Qz_.n())
1236  {
1237  compressBasis();
1238  }
1239  }
1240  ++currIndex_;
1241 
1242  Log<< tab << "# " << name() << ":"
1243  << " Execution index = " << currIndex_
1244  << " for field = " << fieldName_ << " #"
1245  << endl;
1246 
1247  return true;
1248 }
1249 
1250 
1252 {
1253  Log << type() << " " << name() << " write:" << endl;
1254 
1255  if (currIndex_ < 2)
1256  {
1258  << " # STDMD needs at least three snapshots to produce output #"
1259  << nl
1260  << " # Only " << currIndex_ + 1 << " snapshots are available #"
1261  << nl
1262  << " # Skipping STDMD output calculation and write #"
1263  << endl;
1264 
1265  return false;
1266  }
1267 
1268  // STDMD mode evaluation (K:Fig. 16)
1269  calcOutput();
1270 
1271  // Restart STDMD incremental orthonormal basis update
1272  initialised_ = false;
1273 
1274  mesh_.time().printExecutionTime(Info);
1275 
1276  return true;
1277 }
1278 
1279 
1280 // ************************************************************************* //
Foam::addToRunTimeSelectionTable
addToRunTimeSelectionTable(decompositionMethod, kahipDecomp, dictionary)
Foam::IOobject::NO_WRITE
Definition: IOobject.H:130
runTime
engineTime & runTime
Definition: createEngineTime.H:13
Foam::roots::complex
Definition: Roots.H:57
p
volScalarField & p
Definition: createFieldRefs.H:8
Foam::Enum
Enum is a wrapper around a list of names/values that represent particular enumeration (or int) values...
Definition: IOstreamOption.H:57
Log
#define Log
Definition: PDRblock.C:35
Foam::dimless
const dimensionSet dimless(0, 0, 0, 0, 0, 0, 0)
Dimensionless.
Definition: dimensionSets.H:50
Foam::Time
Class to control time during OpenFOAM simulations that is also the top-level objectRegistry.
Definition: Time.H:73
Foam::word
A class for handling words, derived from Foam::string.
Definition: word.H:62
STDMD.H
s
gmvFile<< "tracers "<< particles.size()<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().x()<< " ";}gmvFile<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().y()<< " ";}gmvFile<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().z()<< " ";}gmvFile<< nl;forAll(lagrangianScalarNames, i){ word name=lagrangianScalarNames[i];IOField< scalar > s(IOobject(name, runTime.timeName(), cloud::prefix, mesh, IOobject::MUST_READ, IOobject::NO_WRITE))
Definition: gmvOutputSpray.H:25
Foam::Zero
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:131
Foam::Pstream::scatterList
static void scatterList(const List< commsStruct > &comms, List< T > &Values, const int tag, const label comm)
Scatter data. Reverse of gatherList.
Definition: gatherScatterList.C:215
Foam::sin
dimensionedScalar sin(const dimensionedScalar &ds)
Definition: dimensionedScalar.C:264
Foam::pinv
MatrixType pinv(const MatrixType &A, const scalar tol=1e-5)
Definition: QRMatrix.C:493
Foam::read
bool read(const char *buf, int32_t &val)
Same as readInt32.
Definition: int32.H:108
Foam::UPstream::parRun
static bool & parRun()
Test if this a parallel run, or allow modify access.
Definition: UPstream.H:434
B
static const Foam::dimensionedScalar B("", Foam::dimless, 18.678)
A
static const Foam::dimensionedScalar A("", Foam::dimPressure, 611.21)
Foam::UPstream::master
static bool master(const label communicator=worldComm)
Am I the master process.
Definition: UPstream.H:458
Foam::Pstream::scatter
static void scatter(const List< commsStruct > &comms, T &Value, const int tag, const label comm)
Scatter data. Distribute without modification. Reverse of gather.
Definition: gatherScatter.C:150
Foam::constant::mathematical::e
constexpr scalar e(M_E)
Foam::endl
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:350
Foam::functionObjects::STDMD::read
virtual bool read(const dictionary &)
Read STDMD settings.
Definition: STDMD.C:1135
Foam::writeHeader
static void writeHeader(Ostream &os, const word &fieldName)
Definition: rawSurfaceWriterImpl.C:49
Foam::transform
dimensionSet transform(const dimensionSet &ds)
Return the argument; transformations do not change the dimensions.
Definition: dimensionSet.C:519
Foam::functionObjects::fvMeshFunctionObject
Specialization of Foam::functionObject for an Foam::fvMesh, providing a reference to the Foam::fvMesh...
Definition: fvMeshFunctionObject.H:64
Foam::Rx
tensor Rx(const scalar &omega)
Rotational transformation tensor about the x-axis by omega radians.
Definition: transform.H:85
forAll
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:296
Foam::dimensionedVector
dimensioned< vector > dimensionedVector
Dimensioned vector obtained from generic dimensioned type.
Definition: dimensionedVector.H:50
Foam::mode
mode_t mode(const fileName &name, const bool followLink=true)
Return the file mode, normally following symbolic links.
Definition: MSwindows.C:564
Foam::reduce
void reduce(const List< UPstream::commsStruct > &comms, T &Value, const BinaryOp &bop, const int tag, const label comm)
Definition: PstreamReduceOps.H:51
Foam::constant::physicoChemical::b
const dimensionedScalar b
Wien displacement law constant: default SI units: [m.K].
Definition: createFields.H:27
Foam::Info
messageStream Info
Information stream (uses stdout - output is on the master only)
Foam::name
word name(const complex &c)
Return string representation of complex.
Definition: complex.C:76
Foam::sort
void sort(UList< T > &a)
Definition: UList.C:254
Foam::pow
dimensionedScalar pow(const dimensionedScalar &ds, const dimensionedScalar &expt)
Definition: dimensionedScalar.C:75
Foam::max
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:47
Foam::blockMeshTools::read
void read(Istream &, label &, const dictionary &)
In-place read with dictionary lookup.
Definition: blockMeshTools.C:33
Foam::constant::mathematical::twoPi
constexpr scalar twoPi(2 *M_PI)
dict
dictionary dict
Definition: searchingEngine.H:14
Foam::FatalError
error FatalError
Foam::dictionary
A list of keyword definitions, which are a keyword followed by a number of values (eg,...
Definition: dictionary.H:121
Foam::log
dimensionedScalar log(const dimensionedScalar &ds)
Definition: dimensionedScalar.C:262
addToRunTimeSelectionTable.H
Macros for easy insertion into run-time selection tables.
Foam
Namespace for OpenFOAM.
Definition: atmBoundaryLayer.C:33
Foam::abort
errorManip< error > abort(error &err)
Definition: errorManip.H:144
Foam::distance
scalar distance(const vector &p1, const vector &p2)
Definition: curveTools.C:12
Foam::functionObjects::STDMD::STDMD
STDMD()=delete
No default construct.
Foam::volVectorField
GeometricField< vector, fvPatchField, volMesh > volVectorField
Definition: volFieldsFwd.H:62
FatalErrorInFunction
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:381
Foam::Pstream::gatherList
static void gatherList(const List< commsStruct > &comms, List< T > &Values, const int tag, const label comm)
Gather data but keep individual values separate.
Definition: gatherScatterList.C:52
Foam::UPstream::allProcs
static rangeType allProcs(const label communicator=worldComm)
Range of process indices for all processes.
Definition: UPstream.H:509
Foam::sqr
dimensionedSymmTensor sqr(const dimensionedVector &dv)
Definition: dimensionedSymmTensor.C:51
Foam::constant::mathematical
Mathematical constants.
Foam::tab
constexpr char tab
Definition: Ostream.H:384
Foam::UPstream::myProcNo
static int myProcNo(const label communicator=worldComm)
Number of this process (starting from masterNo() = 0)
Definition: UPstream.H:464
Foam::nl
constexpr char nl
Definition: Ostream.H:385
Foam::MinMax::ge
static MinMax< T > ge(const T &minVal)
A semi-infinite range from minVal to the type max.
Definition: MinMaxI.H:31
Foam::sqrt
dimensionedScalar sqrt(const dimensionedScalar &ds)
Definition: dimensionedScalar.C:144
Foam::pTraits
Traits class for primitives.
Definition: pTraits.H:54
Foam::mag
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
Foam::type
fileName::Type type(const fileName &name, const bool followLink=true)
Return the file type: DIRECTORY or FILE, normally following symbolic links.
Definition: MSwindows.C:590
k
label k
Boltzmann constant.
Definition: LISASMDCalcMethod2.H:41
Foam::sum
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &df)
Definition: DimensionedFieldFunctions.C:327
x
x
Definition: LISASMDCalcMethod2.H:52
Foam::functionObjects::writeFile
Base class for writing single files from the function objects.
Definition: writeFile.H:119
Foam::functionObjects::STDMD::execute
virtual bool execute()
Execute STDMD.
Definition: STDMD.C:1207
Foam::IOobject::NO_READ
Definition: IOobject.H:123
Foam::functionObjects::STDMD::write
virtual bool write()
Write STDMD output.
Definition: STDMD.C:1251
Foam::defineTypeNameAndDebug
defineTypeNameAndDebug(combustionModel, 0)
WarningInFunction
#define WarningInFunction
Report a warning using Foam::Warning.
Definition: messageStream.H:303
Foam::UPstream::nProcs
static label nProcs(const label communicator=worldComm)
Number of processes in parallel run, and 1 for serial run.
Definition: UPstream.H:446