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-------------------------------------------------------------------------------
10License
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
33using namespace Foam::constant::mathematical;
34
35// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
36
37namespace Foam
38{
39namespace DMDModels
40{
43}
44}
45
46const Foam::Enum
47<
48 Foam::DMDModels::STDMD::modeSorterType
49>
50Foam::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
60Foam::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
80Foam::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
119void 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
133void 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
161void 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 }
193
194 // Update "Q"
195 Q_ = Q_*q;
196}
197
198
199Foam::SquareMatrix<Foam::scalar> Foam::DMDModels::STDMD::
200reducedKoopmanOperator()
201{
202 Info<< tab << "Computing Atilde" << endl;
203
204 Info<< tab << "Computing local Rx" << endl;
205
206 RMatrix Rx(1, 1, Zero);
207 {
209 (
210 Qupper_,
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
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
285 (
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 {
342 (
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::scatter(RxInv_);
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
399bool 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::scatter(fail);
456
457 if (fail)
458 {
459 return false;
460 }
461
462 Pstream::scatter(evals_);
463 Pstream::scatter(evecs_);
464
465 return true;
466}
467
468
469void 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
485 std::transform
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::scatter(freqs_);
507 Pstream::scatter(freqsi_);
508}
509
510
511void Foam::DMDModels::STDMD::amplitudes()
512{
513 IOField<scalar> snapshot0
514 (
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
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::scatter(amps_);
571}
572
573
574void 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
590 std::transform
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::scatter(mags_);
652 Pstream::scatter(magsi_);
653}
654
655
656Foam::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
696bool 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
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
739{
740 Info<< tab << "Writing objects of dynamics" << endl;
741
742 // Write objects of dynamics
743 {
744 autoPtr<OFstream> osPtr =
745 createFile
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
770void 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
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:
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",
905 );
906
907 nModes_ =
908 dict.getCheckOrDefault<label>
909 (
910 "nModes",
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,
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
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 (
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
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_;
1039
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// ************************************************************************* //
#define R(A, B, C, D, E, F, K, M)
bool found
Macros for easy insertion into run-time selection tables.
#define addToRunTimeSelectionTable(baseType, thisType, argNames)
Add to construction table with typeName as the key.
Abstract base class for DMD models to handle DMD characteristics for the DMD function object.
Definition: DMDModel.H:68
Streaming Total Dynamic Mode Decomposition (i.e. STDMD) is a variant of dynamic mode decomposition.
Definition: STDMD.H:306
virtual bool fit()
Definition: STDMD.C:1044
A templated (N x N) diagonal matrix of objects of <Type>, effectively containing N elements,...
EigenMatrix (i.e. eigendecomposition or spectral decomposition) decomposes a diagonalisable nonsymmet...
Definition: EigenMatrix.H:179
Enum is a wrapper around a list of names/values that represent particular enumeration (or int) values...
Definition: Enum.H:61
A primitive field of type <T> with automated input and output.
Definition: IOField.H:58
Defines the attributes of an object for which implicit objectRegistry management is supported,...
Definition: IOobject.H:170
The IOstreamOption is a simple container for options an IOstream can normally have.
A 1D array of objects of type <T>, where the size of the vector is known and used for subscript bound...
Definition: List.H:77
void resize(const label len)
Adjust allocated size of list.
Definition: ListI.H:139
static direction m() noexcept
The number of rows.
Definition: MatrixSpace.H:79
static direction n() noexcept
The number of columns.
Definition: MatrixSpace.H:85
const_iterator cbegin() const noexcept
Return const_iterator to begin traversing a constant Matrix.
Definition: MatrixI.H:551
label m() const noexcept
The number of rows.
Definition: MatrixI.H:96
ConstMatrixBlock< mType > subColumn(const label colIndex, const label rowIndex=0, label len=-1) const
Return const column or column's subset of Matrix.
Definition: MatrixI.H:289
static MinMax< scalar > ge(const scalar &minVal)
A semi-infinite range from minVal to the type max.
Definition: MinMaxI.H:31
Output to file stream, using an OSstream.
Definition: OFstream.H:57
An Ostream is an abstract base class for all output systems (streams, files, token lists,...
Definition: Ostream.H:62
Buffers for inter-processor communications streams (UOPstream, UIPstream).
label nProcs() const noexcept
Number of ranks associated with PstreamBuffers.
static void scatter(const List< commsStruct > &comms, T &value, const int tag, const label comm)
Broadcast data: Distribute without modification.
QRMatrix computes QR decomposition of a given scalar/complex matrix A into the following:
Definition: QRMatrix.H:212
modes
Options for the decomposition mode.
Definition: QRMatrix.H:221
virtual bool read()
Re-read model coefficients if they have changed.
A templated (M x N) rectangular matrix of objects of <Type>, containing M*N elements,...
A templated (N x N) square matrix of objects of <Type>, containing N*N elements, derived from Matrix.
Definition: SquareMatrix.H:66
iterator begin() noexcept
Return an iterator to begin traversing the UList.
Definition: UListI.H:329
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:114
@ nonBlocking
"nonBlocking"
static constexpr int masterNo() noexcept
Process index of the master (always 0)
Definition: UPstream.H:451
static bool & parRun() noexcept
Test if this a parallel run.
Definition: UPstream.H:433
Pointer management similar to std::unique_ptr, with some additional methods and type checking.
Definition: autoPtr.H:66
T & ref()
Return reference to the managed object without nullptr checking.
Definition: autoPtr.H:161
A complex number, similar to the C++ complex type.
Definition: complex.H:83
A list of keyword definitions, which are a keyword followed by a number of values (eg,...
Definition: dictionary.H:126
A class for handling file names.
Definition: fileName.H:76
virtual bool writeObject(const regIOobject &io, IOstreamOption streamOpt=IOstreamOption(), const bool valid=true) const
Writes a regIOobject (so header, contents and divider).
virtual bool writeToFile()
Write tracks to file.
Mesh data needed to do the Finite Volume discretisation.
Definition: fvMesh.H:91
virtual bool update()
Update the mesh for both mesh motion and topology change.
A range or interval of labels defined by a start and a size.
Definition: labelRange.H:58
A traits class, which is primarily used for primitives.
Definition: pTraits.H:59
int myProcNo() const noexcept
Return processor number.
void updateG()
Update G and calculate total heat flux on boundary.
Definition: fvDOM.C:742
splitCell * master() const
Definition: splitCell.H:113
A class for managing temporary objects.
Definition: tmp.H:65
void clear() const noexcept
Definition: tmpI.H:287
void initialise()
Initialise integral-scale box properties.
A List of wordRe with additional matching capabilities.
Definition: wordRes.H:54
A class for handling words, derived from Foam::string.
Definition: word.H:68
#define defineTypeNameAndDebug(Type, DebugSwitch)
Define the typeName and debug information.
Definition: className.H:121
dynamicFvMesh & mesh
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:453
OBJstream os(runTime.globalPath()/outputName)
#define WarningInFunction
Report a warning using Foam::Warning.
MatrixType pinv(const MatrixType &A, scalar tol=1e-5)
Definition: QRMatrix.C:584
Mathematical constants.
constexpr scalar twoPi(2 *M_PI)
Namespace for OpenFOAM.
const fileOperation & fileHandler()
Get current file handler.
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:47
tensor Rx(const scalar omega)
Rotational transformation tensor about the x-axis by omega radians.
Definition: transform.H:85
dimensionedSymmTensor sqr(const dimensionedVector &dv)
dimensionedScalar sin(const dimensionedScalar &ds)
static void writeHeader(Ostream &os, const word &fieldName)
messageStream Info
Information stream (stdout output on master, null elsewhere)
dimensionedScalar log(const dimensionedScalar &ds)
dimensionedScalar pow(const dimensionedScalar &ds, const dimensionedScalar &expt)
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:372
dimensionedScalar sqrt(const dimensionedScalar &ds)
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
void reduce(const List< UPstream::commsStruct > &comms, T &value, const BinaryOp &bop, const int tag, const label comm)
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:131
error FatalError
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for INVALID.
Definition: exprTraits.C:59
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:130
constexpr char nl
The newline '\n' character (0x0a)
Definition: Ostream.H:53
constexpr char tab
The tab '\t' character(0x09)
Definition: Ostream.H:52
dictionary dict
const volScalarField & cp
volScalarField & b
Definition: createFields.H:27
volScalarField & e
Definition: createFields.H:11
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:333
Functor wrapper of allow/deny lists of wordRe for filtering.
Definition: wordRes.H:174