diff --git a/src/functionObjects/field/DMD/DMD.C b/src/functionObjects/field/DMD/DMD.C new file mode 100644 index 0000000000000000000000000000000000000000..e225d31cb484fe14bcfb7139007a36aba99b833a --- /dev/null +++ b/src/functionObjects/field/DMD/DMD.C @@ -0,0 +1,234 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2020-2021 OpenCFD Ltd. +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. + +\*---------------------------------------------------------------------------*/ + +#include "DMD.H" +#include "DMDModel.H" +#include "addToRunTimeSelectionTable.H" + +// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // + +namespace Foam +{ +namespace functionObjects +{ + defineTypeNameAndDebug(DMD, 0); + addToRunTimeSelectionTable(functionObject, DMD, dictionary); +} +} + + +// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // + +void Foam::functionObjects::DMD::snapshot() +{ + bool processed = false; + processed = processed || getSnapshot<scalar>(); + processed = processed || getSnapshot<vector>(); + processed = processed || getSnapshot<sphericalTensor>(); + processed = processed || getSnapshot<symmTensor>(); + processed = processed || getSnapshot<tensor>(); + + if (!processed) + { + FatalErrorInFunction + << " functionObjects::" << type() << " " << name() << ":" + << " cannot find required input field during snapshot loading: " + << fieldName_ << nl + << " Do you execute required functionObjects" + << " before executing DMD, e.g. mapFields?" + << exit(FatalError); + } +} + + +void Foam::functionObjects::DMD::initialise() +{ + const label nComps = DMDModelPtr_->nComponents(fieldName_); + + if (patch_.empty()) + { + nSnap_ = nComps*mesh_.nCells(); + } + else + { + const label patchi = mesh_.boundaryMesh().findPatchID(patch_); + + if (patchi < 0) + { + FatalErrorInFunction + << "Cannot find patch " << patch_ + << exit(FatalError); + } + + nSnap_ = nComps*(mesh_.C().boundaryField()[patchi]).size(); + } + + const label nSnapTotal = returnReduce(nSnap_, sumOp<label>()); + + if (nSnapTotal <= 0) + { + FatalErrorInFunction + << " # Zero-size input field = " << fieldName_ << " #" + << exit(FatalError); + } + + if (nSnap_ > 0) + { + z_ = RMatrix(2*nSnap_, 1, Zero); + } + else + { + z_ = RMatrix(1, 1, Zero); + } +} + + +// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // + +Foam::functionObjects::DMD::DMD +( + const word& name, + const Time& runTime, + const dictionary& dict +) +: + fvMeshFunctionObject(name, runTime, dict), + DMDModelPtr_(DMDModel::New(mesh_, name, dict)), + z_(), + fieldName_(dict.get<word>("field")), + patch_(dict.getOrDefault<word>("patch", word::null)), + nSnap_(0), + step_(0) +{ + // Check if computation time-step is varying + if (runTime.isAdjustTimeStep()) + { + WarningInFunction + << " # DMD: Available only for fixed time-step computations. #" + << endl; + } + + // Check if mesh topology is changing + if (mesh_.topoChanging()) + { + FatalErrorInFunction + << " # DMD: Available only for non-changing mesh topology. #" + << exit(FatalError); + } + + read(dict); +} + + +// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // + +bool Foam::functionObjects::DMD::read(const dictionary& dict) +{ + Info<< type() << " " << name() << ":" << endl; + + if (fvMeshFunctionObject::read(dict) && DMDModelPtr_->read(dict)) + { + return true; + } + + return false; +} + + +bool Foam::functionObjects::DMD::execute() +{ + Log << type() << " " << name() << " execute:" << endl; + + snapshot(); + + if (step_ == 1) + { + DMDModelPtr_->initialise(z_); + } + + if (step_ > 1) + { + DMDModelPtr_->update(z_); + } + + ++step_; + + Log << tab << "Execution index = " << step_ << " for field: " << fieldName_ + << endl; + + return true; +} + + +bool Foam::functionObjects::DMD::write() +{ + if (postProcess) + { + return true; + } + + return end(); +} + + +bool Foam::functionObjects::DMD::end() +{ + if (step_ == 0) + { + // Avoid double execution of write() when writeControl=onEnd + return true; + } + + Log << type() << " " << name() << " write:" << endl; + + if (step_ < 2) + { + WarningInFunction + << " # DMD needs at least three snapshots to produce output #" + << nl + << " # Only " << step_ + 1 << " snapshots are available #" + << nl + << " # Skipping DMD output calculation and write #" + << endl; + + return false; + } + + z_.clear(); + + DMDModelPtr_->fit(); + + mesh_.time().printExecutionTime(Info); + + // Restart the incremental orthonormal basis update + step_ = 0; + + return true; +} + + +// ************************************************************************* // diff --git a/src/functionObjects/field/DMD/DMD.H b/src/functionObjects/field/DMD/DMD.H new file mode 100644 index 0000000000000000000000000000000000000000..b468e85f8648b73667729ade808b387c6b32f0cf --- /dev/null +++ b/src/functionObjects/field/DMD/DMD.H @@ -0,0 +1,278 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2020-2021 OpenCFD Ltd. +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. + +Class + Foam::functionObjects::DMD + +Group + grpFieldFunctionObjects + +Description + Computes a dynamic mode decomposition model on a specified field. + + Dynamic mode decomposition (i.e. DMD) is a data-driven + dimensionality reduction method. DMD is being used as a mathematical + processing tool to reveal dominant modes out of a given field (or dataset) + each of which is associated with a constant frequency and decay rate, + so that dynamic features of a given flow may become interpretable, + tractable, and even reproducible without computing simulations. + DMD only relies on input data, therefore it is an equation-free approach. + + References: + \verbatim + DMD characteristics: + Brunton S. L. (2018). + Dynamic mode decomposition overview. + Seattle, Washington: University of Washington. + youtu.be/sQvrK8AGCAo (Retrieved:24-04-20) + \endverbatim + + Operands: + \table + Operand | Type | Location + input | {vol,surface}\<Type\>Field(s) <!-- + --> | \<time\>/\<inputField\>(s) + output file | dat | postProcessing/\<FO\>/\<time\>/\<file\>(s) + output field | volVectorField(s) | \<time\>/\<outputField\>(s) + \endtable + + where \c \<Type\>=Scalar/Vector/SphericalTensor/SymmTensor/Tensor. + + Output fields: + \verbatim + modeRe_<modeIndex>_<field>_<FO> | Real part of a mode field + modeIm_<modeIndex>_<field>_<FO> | Imaginary part of a mode field + \endverbatim + + Output files: + \verbatim + dynamics_<field>.dat | Dynamics data for each mode + filteredDynamics_<field>.dat | Filtered dynamics data for each mode + \endverbatim + + wherein for each mode, the following quantities are output into files: + \vartable + freq | Frequency + mag | Magnitude + ampRe | Amplitude coefficients (real part) + ampIm | Amplitude coefficients (imaginary part) + evalRe | Eigenvalue (real part) + evalIm | Eigenvalue (imaginary part) + \endvartable + +Usage + Minimal example by using \c system/controlDict.functions: + \verbatim + DMD1 + { + // Mandatory entries (unmodifiable) + type DMD; + libs (fieldFunctionObjects); + DMDModel <DMDModel>; + field <inputField>; + + // Optional entries (unmodifiable) + patch <patchName>; + + // Mandatory/Optional (inherited) entries + ... + } + \endverbatim + + where the entries mean: + \table + Property | Description | Type | Reqd | Deflt + type | Type name: DMD | word | yes | - + libs | Library name: fieldFunctionObjects | word | yes | - + DMDModel | Name of specified DMD model | word | yes | - + field | Name of operand field | word | yes | - + patch | Name of operand patch | word | no | null + \endtable + + Options for the \c DMDModel entry: + \verbatim + STDMD | Streaming total dynamic mode decomposition + \endverbatim + + The inherited entries are elaborated in: + - \link functionObject.H \endlink + - \link writeFile.H \endlink + + Minimal example by using the \c postProcess utility: + \verbatim + <solver> -postProcess -fields '(U p)' -time '10:' + \endverbatim + +Note + - Warning: DMD is an active research area at the time of writing; + therefore, there could be cases whereat oddities can be seen. + +See also + - Foam::functionObjects::fvMeshFunctionObject + - Foam::functionObjects::writeFile + - Foam::DMDModel + - Foam::DMDModels::STDMD + +SourceFiles + DMD.C + DMDTemplates.C + +\*---------------------------------------------------------------------------*/ + +#ifndef functionObjects_DMD_H +#define functionObjects_DMD_H + +#include "fvMeshFunctionObject.H" +#include "RectangularMatrix.H" + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +namespace Foam +{ + +// Forward Declarations +class DMDModel; + +namespace functionObjects +{ + +/*---------------------------------------------------------------------------*\ + Class DMD Declaration +\*---------------------------------------------------------------------------*/ + +class DMD +: + public fvMeshFunctionObject +{ + typedef RectangularMatrix<scalar> RMatrix; + + // Private Data + + //- Dynamic mode decomposition model + autoPtr<DMDModel> DMDModelPtr_; + + //- Snapshot matrix (effectively a column vector) + // Upper half = current-time snapshot slot + // Lower half = previous-time snapshot slot + // A snapshot is an input dataset to be processed per execution step + RMatrix z_; + + //- Name of operand field + const word fieldName_; + + //- Name of operand patch + const word patch_; + + //- Number of elements in a snapshot + label nSnap_; + + //- Current execution-step index of DMD, + //- not necessarily that of the simulation + label step_; + + + // Private Member Functions + + // Process + + //- Initialise snapshot at the first-execution step + // Initialisation at the ctor or read level is not possible + // since the operand field is not available in the database + void initialise(); + + //- Create operand snapshot by using + //- current-time and previous-time operand fields + void snapshot(); + + //- Get operand field based on its base type + template<class Type> + bool getSnapshot(); + + //- Get operand field based on its geometric field type + // Move previous-time field into previous-time slot in snapshot + // copy new current-time field into current-time slot in snapshot + template<class GeoFieldType> + bool getSnapshotField(); + + +public: + + //- Runtime type information + TypeName("DMD"); + + + // Constructors + + //- Construct from Time and dictionary + DMD + ( + const word& name, + const Time& runTime, + const dictionary& dict + ); + + //- No copy construct + DMD(const DMD&) = delete; + + //- No copy assignment + void operator=(const DMD&) = delete; + + + //- Destructor + virtual ~DMD() = default; + + + // Member Functions + + //- Read DMD settings + virtual bool read(const dictionary& dict); + + //- Execute DMD + virtual bool execute(); + + //- Write DMD results + virtual bool write(); + + //- Write DMD results + virtual bool end(); +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +} // End namespace functionObjects +} // End namespace Foam + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#ifdef NoRepository + #include "DMDTemplates.C" +#endif + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#endif + +// ************************************************************************* // diff --git a/src/functionObjects/field/DMD/DMDModels/DMDModel/DMDModel.C b/src/functionObjects/field/DMD/DMDModels/DMDModel/DMDModel.C new file mode 100644 index 0000000000000000000000000000000000000000..c43727548c83d8f42e0f75c289ba04cd807c04f4 --- /dev/null +++ b/src/functionObjects/field/DMD/DMDModels/DMDModel/DMDModel.C @@ -0,0 +1,78 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2020-2021 OpenCFD Ltd. +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. + +\*---------------------------------------------------------------------------*/ + +#include "DMDModel.H" + +// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // + +namespace Foam +{ + defineTypeNameAndDebug(DMDModel, 0); + defineRunTimeSelectionTable(DMDModel, dictionary); +} + + +// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // + +Foam::DMDModel::DMDModel +( + const fvMesh& mesh, + const word& name, + const dictionary& dict +) +: + writeFile(mesh, name, typeName, dict, false), + mesh_(mesh), + name_(name) +{} + + +// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // + +Foam::label Foam::DMDModel::nComponents(const word& fieldName) const +{ + label nComps = 0; + bool processed = false; + processed = processed || nComponents<scalar>(fieldName, nComps); + processed = processed || nComponents<vector>(fieldName, nComps); + processed = processed || nComponents<sphericalTensor>(fieldName, nComps); + processed = processed || nComponents<symmTensor>(fieldName, nComps); + processed = processed || nComponents<tensor>(fieldName, nComps); + + if (!processed) + { + FatalErrorInFunction + << " # Unknown type of input field during initialisation = " + << fieldName << " #" << nl + << exit(FatalError); + } + + return nComps; +} + + +// ************************************************************************* // diff --git a/src/functionObjects/field/DMD/DMDModels/DMDModel/DMDModel.H b/src/functionObjects/field/DMD/DMDModels/DMDModel/DMDModel.H new file mode 100644 index 0000000000000000000000000000000000000000..8081db5a01a88d06e68acf89467e1ed4383ed7eb --- /dev/null +++ b/src/functionObjects/field/DMD/DMDModels/DMDModel/DMDModel.H @@ -0,0 +1,203 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2020-2021 OpenCFD Ltd. +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. + +Namespace + Foam::DMDModels + +Description + A namespace for various dynamic mode + decomposition (DMD) model implementations. + +Class + Foam::DMDModel + +Description + Abstract base class for DMD models to handle DMD + characteristics for the \c DMD function object. + +SourceFiles + DMDModel.C + DMDModelNew.C + DMDModelTemplates.C + +\*---------------------------------------------------------------------------*/ + +#ifndef DMDModel_H +#define DMDModel_H + +#include "fvMesh.H" +#include "dictionary.H" +#include "HashSet.H" +#include "runTimeSelectionTables.H" +#include "writeFile.H" +#include "OFstream.H" + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +namespace Foam +{ + +/*---------------------------------------------------------------------------*\ + Class DMDModel Declaration +\*---------------------------------------------------------------------------*/ + +class DMDModel +: + public functionObjects::writeFile +{ + typedef SquareMatrix<scalar> SMatrix; + typedef RectangularMatrix<scalar> RMatrix; + + +protected: + + // Protected Data + + //- Reference to the mesh + const fvMesh& mesh_; + + //- Name of operand function object + const word name_; + + + // Protected Member Functions + + // Evaluation + + //- Compute and write mode dynamics + virtual bool dynamics() = 0; + + //- Compute and write modes + virtual bool modes() = 0; + + +public: + + //- Runtime type information + TypeName("DMDModel"); + + + // Declare runtime constructor selection table + + declareRunTimeSelectionTable + ( + autoPtr, + DMDModel, + dictionary, + ( + const fvMesh& mesh, + const word& name, + const dictionary& dict + ), + (mesh, name, dict) + ); + + + // Selectors + + //- Return a reference to the selected DMD model + static autoPtr<DMDModel> New + ( + const fvMesh& mesh, + const word& name, + const dictionary& dict + ); + + + // Constructors + + //- Construct from components + DMDModel + ( + const fvMesh& mesh, + const word& name, + const dictionary& dict + ); + + //- No copy construct + DMDModel(const DMDModel&) = delete; + + //- No copy assignment + void operator=(const DMDModel&) = delete; + + + //- Destructor + virtual ~DMDModel() = default; + + + // Member Functions + + // Process + + //- Initialise model data members with a given snapshot + virtual bool initialise(const RMatrix& snapshot) = 0; + + //- Update model data members with a given snapshot + virtual bool update(const RMatrix& snapshot) = 0; + + //- Compute and write modes and + //- mode dynamics of model data members + virtual bool fit() = 0; + + //- Compute and write a reconstruction of flow field + //- based on given modes and mode dynamics (currently no-op) + virtual void reconstruct(const wordList modes) + { + NotImplemented; + } + + + // Access + + //- Return number of components of the base type of a given field + label nComponents(const word& fieldName) const; + + //- Get the number of components of the base type of a given field + template<class Type> + bool nComponents(const word& fieldName, label& nComps) const; + + + // IO + + //- Read model settings + virtual bool read(const dictionary& dict) = 0; +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +} // End namespace Foam + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#ifdef NoRepository + #include "DMDModelTemplates.C" +#endif + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#endif + +// ************************************************************************* // diff --git a/src/functionObjects/field/DMD/DMDModels/DMDModel/DMDModelNew.C b/src/functionObjects/field/DMD/DMDModels/DMDModel/DMDModelNew.C new file mode 100644 index 0000000000000000000000000000000000000000..e3a72a4a5c2967a68c101ec4fa504d7914a7d9b4 --- /dev/null +++ b/src/functionObjects/field/DMD/DMDModels/DMDModel/DMDModelNew.C @@ -0,0 +1,58 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2020-2021 OpenCFD Ltd. +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. + +\*---------------------------------------------------------------------------*/ + +#include "DMDModel.H" + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +Foam::autoPtr<Foam::DMDModel> Foam::DMDModel::New +( + const fvMesh& mesh, + const word& name, + const dictionary& dict +) +{ + const word modelType(dict.get<word>("DMDModel")); + + auto cstrIter = dictionaryConstructorTablePtr_->cfind(modelType); + + if (!cstrIter.found()) + { + FatalIOErrorInLookup + ( + dict, + "DMDModel", + modelType, + *dictionaryConstructorTablePtr_ + ) << exit(FatalIOError); + } + + return autoPtr<DMDModel>(cstrIter()(mesh, name, dict)); +} + + +// ************************************************************************* // diff --git a/src/functionObjects/field/DMD/DMDModels/DMDModel/DMDModelTemplates.C b/src/functionObjects/field/DMD/DMDModels/DMDModel/DMDModelTemplates.C new file mode 100644 index 0000000000000000000000000000000000000000..9281dd2385a779afffcf74b165463eaaa82b70e5 --- /dev/null +++ b/src/functionObjects/field/DMD/DMDModels/DMDModel/DMDModelTemplates.C @@ -0,0 +1,54 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2021 OpenCFD Ltd. +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. + +\*---------------------------------------------------------------------------*/ + +#include "volFields.H" +#include "surfaceFields.H" + +// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // + +template<class Type> +bool Foam::DMDModel::nComponents(const word& fieldName, label& nComps) const +{ + typedef GeometricField<Type, fvPatchField, volMesh> VolFieldType; + typedef GeometricField<Type, fvsPatchField, surfaceMesh> SurfaceFieldType; + + if (mesh_.foundObject<VolFieldType>(fieldName)) + { + nComps = pTraits<typename VolFieldType::value_type>::nComponents; + return true; + } + else if (mesh_.foundObject<SurfaceFieldType>(fieldName)) + { + nComps = pTraits<typename SurfaceFieldType::value_type>::nComponents; + return true; + } + + return false; +} + + +// ************************************************************************* // diff --git a/src/functionObjects/field/DMD/DMDModels/derived/STDMD/STDMD.C b/src/functionObjects/field/DMD/DMDModels/derived/STDMD/STDMD.C new file mode 100644 index 0000000000000000000000000000000000000000..ac180feb7ac0f29ed629b541825ac96ba2de3854 --- /dev/null +++ b/src/functionObjects/field/DMD/DMDModels/derived/STDMD/STDMD.C @@ -0,0 +1,988 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2020-2021 OpenCFD Ltd. +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. + +\*---------------------------------------------------------------------------*/ + +#include "STDMD.H" +#include "EigenMatrix.H" +#include "QRMatrix.H" +#include "addToRunTimeSelectionTable.H" + +using namespace Foam::constant::mathematical; + +// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // + +namespace Foam +{ +namespace DMDModels +{ + defineTypeNameAndDebug(STDMD, 0); + addToRunTimeSelectionTable(DMDModel, STDMD, dictionary); +} +} + +const Foam::Enum +< + Foam::DMDModels::STDMD::modeSorterType +> +Foam::DMDModels::STDMD::modeSorterTypeNames +({ + { modeSorterType::FIRST_SNAPSHOT, "firstSnapshot" }, + { modeSorterType::KIEWAT , "kiewat" }, + { modeSorterType::KOU_ZHANG , "kouZhang" } +}); + + +// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // + +Foam::scalar Foam::DMDModels::STDMD::L2norm(const RMatrix& z) const +{ + #ifdef FULLDEBUG + // Check if the given RectangularMatrix is effectively a column vector + if (z.n() != 1) + { + FatalErrorInFunction + << " # Input matrix is not a column vector. #" + << exit(FatalError); + } + #endif + const bool noSqrt = true; + scalar result = z.columnNorm(0, noSqrt); + reduce(result, sumOp<scalar>()); + + // Heuristic addition to avoid very small or zero norm + return max(SMALL, Foam::sqrt(result)); +} + + +Foam::RectangularMatrix<Foam::scalar> Foam::DMDModels::STDMD::orthonormalise +( + RMatrix ez +) const +{ + RMatrix dz(Q_.n(), 1, Zero); + + for (label i = 0; i < nGramSchmidt_; ++i) + { + dz = Q_ & ez; + reduce(dz, sumOp<RMatrix>()); + ez -= Q_*dz; + } + + return ez; +} + + +void Foam::DMDModels::STDMD::expand(const RMatrix& ez, const scalar ezNorm) +{ + Info<< tab << "Expanding orthonormal basis for field: " << fieldName_ + << endl; + + // Stack a column "(ez/ezNorm)" to "Q" + Q_.resize(Q_.m(), Q_.n() + 1); + Q_.subColumn(Q_.n() - 1) = ez/ezNorm; + + // Stack a row (Zero) and column (Zero) to "G" + G_.resize(G_.m() + 1); +} + + +void Foam::DMDModels::STDMD::compress() +{ + Info<< tab << "Compressing orthonormal basis for field: " << fieldName_ + << endl; + + RMatrix q(1, 1, Zero); + + if (Pstream::master()) + { + const bool symmetric = true; + const EigenMatrix<scalar> EM(G_, symmetric); + const SquareMatrix<scalar>& EVecs = EM.EVecs(); + DiagonalMatrix<scalar> EVals(EM.EValsRe()); + + // Sort eigenvalues in descending order, and track indices + const auto descend = [&](scalar a, scalar b){ return a > b; }; + const List<label> permutation(EVals.sortPermutation(descend)); + EVals.applyPermutation(permutation); + EVals.resize(EVals.size() - 1); + + // Update "G" + G_ = SMatrix(maxRank_, Zero); + G_.diag(EVals); + + q.resize(Q_.n(), maxRank_); + for (label i = 0; i < maxRank_; ++i) + { + q.subColumn(i) = EVecs.subColumn(permutation[i]); + } + } + Pstream::scatter(G_); + Pstream::scatter(q); + + // Update "Q" + Q_ = Q_*q; +} + + +Foam::SquareMatrix<Foam::scalar> Foam::DMDModels::STDMD:: +reducedKoopmanOperator() +{ + Info<< tab << "Computing Atilde" << endl; + + Info<< tab << "Computing local Rx" << endl; + + RMatrix Rx(1, 1, Zero); + { + QRMatrix<RMatrix> QRM + ( + Qupper_, + QRMatrix<RMatrix>::outputTypes::REDUCED_R, + QRMatrix<RMatrix>::colPivoting::FALSE + ); + Rx = QRM.R(); + } + Rx.round(); + + // Convenience objects A1, A2, A3 to compute "Atilde" + RMatrix A1(1, 1, Zero); + + if (Pstream::parRun()) + { + // Parallel direct tall-skinny QR decomposition + // (BGD:Fig. 5) & (DGHL:Fig. 2) + // Tests revealed that the distribution of "Q" does not affect + // the final outcome of TSQR decomposition up to sign + + PstreamBuffers pBufs(Pstream::commsTypes::nonBlocking); + + const label myProcNo = Pstream::myProcNo(); + const label procNoInSubset = myProcNo % nAgglomerationProcs_; + + // Send Rx from sender subset neighbours to receiver subset master + if (procNoInSubset != 0) + { + const label procNoSubsetMaster = myProcNo - procNoInSubset; + + UOPstream toSubsetMaster(procNoSubsetMaster, pBufs); + toSubsetMaster << Rx; + + Rx.clear(); + } + + pBufs.finishedSends(); + + // Receive Rx by receiver subset masters + if (procNoInSubset == 0) + { + // Accept only subset masters possessing sender subset neighbours + if (myProcNo + 1 < Pstream::nProcs()) + { + for + ( + label nbr = myProcNo + 1; + ( + nbr < myProcNo + nAgglomerationProcs_ + && nbr < Pstream::nProcs() + ); + ++nbr + ) + { + RMatrix recvMtrx; + + UIPstream fromNbr(nbr, pBufs); + fromNbr >> recvMtrx; + + // Append received Rx to Rx of receiver subset masters + if (recvMtrx.size() > 0) + { + Rx.resize(Rx.m() + recvMtrx.m(), Rx.n()); + Rx.subMatrix + ( + Rx.m() - recvMtrx.m(), + Rx.n() - recvMtrx.n() + ) = recvMtrx; + } + } + } + + // Apply interim QR decomposition on Rx of receiver subset masters + QRMatrix<RMatrix> QRM + ( + Rx, + QRMatrix<RMatrix>::outputTypes::REDUCED_R, + QRMatrix<RMatrix>::storeMethods::IN_PLACE, + QRMatrix<RMatrix>::colPivoting::FALSE + ); + Rx.round(); + } + + pBufs.clear(); + + // Send interim Rx from subset masters to the master + if (procNoInSubset == 0) + { + if (!Pstream::master()) + { + UOPstream toMaster(Pstream::masterNo(), pBufs); + toMaster << Rx; + } + } + + pBufs.finishedSends(); + + // Receive interim Rx by the master, and apply final operations + if (Pstream::master()) + { + for + ( + label nbr = Pstream::masterNo() + nAgglomerationProcs_; + nbr < Pstream::nProcs(); + nbr += nAgglomerationProcs_ + ) + { + RMatrix recvMtrx; + + UIPstream fromSubsetMaster(nbr, pBufs); + fromSubsetMaster >> recvMtrx; + + // Append received Rx to Rx of the master + if (recvMtrx.size() > 0) + { + Rx.resize(Rx.m() + recvMtrx.m(), Rx.n()); + Rx.subMatrix + ( + Rx.m() - recvMtrx.m(), + Rx.n() - recvMtrx.n() + ) = recvMtrx; + } + } + + Info<< tab << "Computing TSQR" << endl; + QRMatrix<RMatrix> QRM + ( + Rx, + QRMatrix<RMatrix>::outputTypes::REDUCED_R, + QRMatrix<RMatrix>::storeMethods::IN_PLACE, + QRMatrix<RMatrix>::colPivoting::FALSE + ); + Rx.round(); + + // Rx produced by TSQR is unique up to the sign, hence the revert + for (scalar& x : Rx) + { + x *= -1; + } + + Info<< tab << "Computing RxInv" << endl; + RxInv_ = pinv(Rx); + + Info<< tab << "Computing A1" << endl; + A1 = RxInv_*RMatrix(pinv(Rx*(G_^Rx))); + Rx.clear(); + } + Pstream::scatter(RxInv_); + Pstream::scatter(A1); + + Info<< tab << "Computing A2" << endl; + SMatrix A2(Qupper_ & Qlower_); + reduce(A2, sumOp<SMatrix>()); + Qlower_.clear(); + + Info<< tab << "Computing A3" << endl; + SMatrix A3(Qupper_ & Qupper_); + reduce(A3, sumOp<SMatrix>()); + + Info<< tab << "Computing Atilde" << endl; + // by optimized matrix chain multiplication + // obtained by dynamic programming memoisation + return SMatrix(RxInv_ & ((A2*(G_*A3))*A1)); + } + else + { + Info<< tab << "Computing RxInv" << endl; + RxInv_ = pinv(Rx); + + Info<< tab << "Computing A1" << endl; + A1 = RxInv_*RMatrix(pinv(Rx*(G_^Rx))); + Rx.clear(); + + Info<< tab << "Computing A2" << endl; + SMatrix A2(Qupper_ & Qlower_); + Qlower_.clear(); + + Info<< tab << "Computing A3" << endl; + SMatrix A3(Qupper_ & Qupper_); + + Info<< tab << "Computing Atilde" << endl; + return SMatrix(RxInv_ & ((A2*(G_*A3))*A1)); + } +} + + +bool Foam::DMDModels::STDMD::eigendecomposition(SMatrix& Atilde) +{ + bool fail = false; + + // Compute eigenvalues, and clip eigenvalues with values < "minEval" + if (Pstream::master()) + { + Info<< tab << "Computing eigendecomposition" << endl; + + // Replace "Atilde" by eigenvalues (in-place eigendecomposition) + const EigenMatrix<scalar> EM(Atilde); + const DiagonalMatrix<scalar>& evalsRe = EM.EValsRe(); + const DiagonalMatrix<scalar>& evalsIm = EM.EValsIm(); + + evals_.resize(evalsRe.size()); + evecs_ = RCMatrix(EM.complexEVecs()); + + // Convert scalar eigenvalue pairs to complex eigenvalues + label i = 0; + for (auto& eval : evals_) + { + eval = complex(evalsRe[i], evalsIm[i]); + ++i; + } + + Info<< tab << "Filtering eigenvalues" << endl; + + List<complex> cp(evals_.size()); + auto it = + std::copy_if + ( + evals_.cbegin(), + evals_.cend(), + cp.begin(), + [&](const complex& x){ return mag(x) > minEval_; } + ); + cp.resize(std::distance(cp.begin(), it)); + + if (cp.size() == 0) + { + WarningInFunction + << "No eigenvalue with mag(eigenvalue) larger than " + << "minEval = " << minEval_ << " was found." << nl + << " Input field may contain only zero-value elements," << nl + << " e.g. no-slip velocity condition on a given patch." << nl + << " Otherwise, please decrease the value of minEval." << nl + << " Skipping further dynamics/mode computations." << nl + << endl; + + fail = true; + } + else + { + evals_ = cp; + } + } + Pstream::scatter(fail); + + if (fail) + { + return false; + } + + Pstream::scatter(evals_); + Pstream::scatter(evecs_); + + Atilde.clear(); + + return true; +} + + +void Foam::DMDModels::STDMD::frequencies() +{ + if (Pstream::master()) + { + Info<< tab << "Computing frequencies" << endl; + + freqs_.resize(evals_.size()); + + // Frequency equation (K:Eq. 81) + auto frequencyEquation = + [&](const complex& eval) + { + return Foam::log(max(eval, complex(SMALL))).imag()/(twoPi*dt_); + }; + + // Compute frequencies + std::transform + ( + evals_.cbegin(), + evals_.cend(), + freqs_.begin(), + frequencyEquation + ); + + Info<< tab << "Computing frequency indices" << endl; + + // Initialise iterator by the first search + auto margin = [&](const scalar& x){ return (fMin_ <= x && x < fMax_); }; + + auto it = std::find_if(freqs_.cbegin(), freqs_.cend(), margin); + + while (it != freqs_.end()) + { + freqsi_.append(std::distance(freqs_.cbegin(), it)); + it = std::find_if(std::next(it), freqs_.cend(), margin); + } + } + Pstream::scatter(freqs_); + Pstream::scatter(freqsi_); +} + + +void Foam::DMDModels::STDMD::amplitudes() +{ + const IOField<scalar> snapshot0 + ( + IOobject + ( + "snapshot0_" + name_ + "_" + fieldName_, + timeName0_, + mesh_, + IOobject::MUST_READ, + IOobject::NO_WRITE, + false + ) + ); + + RMatrix snapshot(1, 1, Zero); + if (!empty_) + { + snapshot.resize(Qupper_.m(), 1); + std::copy(snapshot0.cbegin(), snapshot0.cend(), snapshot.begin()); + } + + RMatrix R((RxInv_.T()^Qupper_)*snapshot); + reduce(R, sumOp<RMatrix>()); + + if (Pstream::master()) + { + Info<< tab << "Computing amplitudes" << endl; + + amps_.resize(R.m()); + const RCMatrix pEvecs(pinv(evecs_)); + + // amps_ = pEvecs*R; + for (label i = 0; i < amps_.size(); ++i) + { + for (label j = 0; j < R.m(); ++j) + { + amps_[i] += pEvecs(i, j)*R(j, 0); + } + } + } + Pstream::scatter(amps_); +} + + +void Foam::DMDModels::STDMD::magnitudes() +{ + if (Pstream::master()) + { + Info<< tab << "Computing magnitudes" << endl; + + mags_.resize(amps_.size()); + + Info<< tab << "Sorting modes with "; + + switch (modeSorter_) + { + case modeSorterType::FIRST_SNAPSHOT: + { + Info<< "method of first snapshot" << endl; + + std::transform + ( + amps_.cbegin(), + amps_.cend(), + mags_.begin(), + [&](const complex& val){ return mag(val); } + ); + break; + } + + case modeSorterType::KIEWAT: + { + Info<< "modified weighted amplitude scaling method" << endl; + + // Eigendecomposition returns evecs with + // the unity norm, hence "modeNorm = 1" + const scalar modeNorm = 1; + const scalar pr = 1; + List<scalar> w(step_); + std::iota(w.begin(), w.end(), 1); + w = sin(twoPi/step_*(w - 1 - 0.25*step_))*pr + pr; + + forAll(amps_, i) + { + mags_[i] = sorter(w, amps_[i], evals_[i], modeNorm); + } + + break; + } + + case modeSorterType::KOU_ZHANG: + { + Info<< "weighted amplitude scaling method" << endl; + + const scalar modeNorm = 1; + const List<scalar> w(step_, 1.0); + + forAll(amps_, i) + { + mags_[i] = sorter(w, amps_[i], evals_[i], modeNorm); + } + + break; + } + + default: + break; + } + + Info<< tab << "Computing magnitude indices" << endl; + + magsi_ = freqsi_; + + auto descend = + [&](const label i1, const label i2) + { + return !(mags_[i1] < mags_[i2]); + }; + + std::sort(magsi_.begin(), magsi_.end(), descend); + } + Pstream::scatter(mags_); + Pstream::scatter(magsi_); +} + + +Foam::scalar Foam::DMDModels::STDMD::sorter +( + const List<scalar>& weight, + const complex& amplitude, + const complex& eigenvalue, + const scalar modeNorm +) const +{ + // Omit eigenvalues with very large or very small mags + if (!(mag(eigenvalue) < GREAT && mag(eigenvalue) > VSMALL)) + { + Info<< " Returning zero magnitude for mag(eigenvalue) = " + << mag(eigenvalue) << endl; + + return 0; + } + + // Omit eigenvalue-STDMD step combinations that pose a risk of overflow + if (mag(eigenvalue)*step_ > sortLimiter_) + { + Info<< " Returning zero magnitude for" + << " mag(eigenvalue) = " << mag(eigenvalue) + << " current index = " << step_ + << " sortLimiter = " << sortLimiter_ + << endl; + + return 0; + } + + scalar magnitude = 0; + + for (label j = 0; j < step_; ++j) + { + magnitude += weight[j]*modeNorm*mag(amplitude*pow(eigenvalue, j + 1)); + } + + return magnitude; +} + + +bool Foam::DMDModels::STDMD::dynamics() +{ + SMatrix Atilde(reducedKoopmanOperator()); + G_.clear(); + + if(!eigendecomposition(Atilde)) + { + return false; + } + + frequencies(); + + amplitudes(); + + magnitudes(); + + return true; +} + + +bool Foam::DMDModels::STDMD::modes() +{ + Info<< tab << "Computing modes" << endl; + + bool processed = false; + processed = processed || modes<scalar>(); + processed = processed || modes<vector>(); + processed = processed || modes<sphericalTensor>(); + processed = processed || modes<symmTensor>(); + processed = processed || modes<tensor>(); + + if (!processed) + { + return false; + } + + return true; +} + + +void Foam::DMDModels::STDMD::writeToFile(const word& fileName) const +{ + Info<< tab << "Writing objects of dynamics" << endl; + + // Write objects of dynamics + { + autoPtr<OFstream> osPtr = + createFile + ( + fileName + "_" + fieldName_, + mesh_.time().timeOutputValue() + ); + OFstream& os = osPtr.ref(); + + writeFileHeader(os); + + for (const auto& i : labelRange(0, freqs_.size())) + { + os << freqs_[i] << tab + << mags_[i] << tab + << amps_[i].real() << tab + << amps_[i].imag() << tab + << evals_[i].real() << tab + << evals_[i].imag() << endl; + } + } + + Info<< tab << "Ends output processing for field: " << fieldName_ + << endl; +} + + +void Foam::DMDModels::STDMD::writeFileHeader(Ostream& os) const +{ + writeHeader(os, "DMD output"); + writeCommented(os, "Frequency"); + writeTabbed(os, "Magnitude"); + writeTabbed(os, "Amplitude (real)"); + writeTabbed(os, "Amplitude (imag)"); + writeTabbed(os, "Eigenvalue (real)"); + writeTabbed(os, "Eigenvalue (imag)"); + os << endl; +} + + +void Foam::DMDModels::STDMD::filter() +{ + Info<< tab << "Filtering objects of dynamics" << endl; + + // Filter objects according to iMags + filterIndexed(evals_, magsi_); + filterIndexed(evecs_, magsi_); + filterIndexed(freqs_, magsi_); + filterIndexed(amps_, magsi_); + filterIndexed(mags_, magsi_); + + // Clip objects if need be (assuming objects have the same len) + if (freqs_.size() > nModes_) + { + evals_.resize(nModes_); + evecs_.resize(evecs_.m(), nModes_); + freqs_.resize(nModes_); + amps_.resize(nModes_); + mags_.resize(nModes_); + } +} + + +// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // + +Foam::DMDModels::STDMD::STDMD +( + const fvMesh& mesh, + const word& name, + const dictionary& dict +) +: + DMDModel(mesh, name, dict), + modeSorter_ + ( + modeSorterTypeNames.getOrDefault + ( + "modeSorter", + dict, + modeSorterType::FIRST_SNAPSHOT + ) + ), + Q_(), + G_(), + Qupper_(1, 1, Zero), + Qlower_(1, 1, Zero), + RxInv_(1, 1, Zero), + evals_(Zero), + evecs_(1, 1, Zero), + freqs_(Zero), + freqsi_(), + amps_(Zero), + mags_(Zero), + magsi_(Zero), + fieldName_(dict.get<word>("field")), + patch_(dict.getOrDefault<word>("patch", word::null)), + timeName0_(), + minBasis_(0), + minEval_(0), + dt_(0), + sortLimiter_(500), + fMin_(0), + fMax_(pTraits<label>::max), + nGramSchmidt_(5), + maxRank_(pTraits<label>::max), + step_(0), + nModes_(pTraits<label>::max), + nAgglomerationProcs_(20), + empty_(false) +{} + + +// * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * // + +bool Foam::DMDModels::STDMD::read(const dictionary& dict) +{ + writeToFile_ = dict.getOrDefault<bool>("writeToFile", true); + + modeSorter_ = + modeSorterTypeNames.getOrDefault + ( + "modeSorter", + dict, + modeSorterType::FIRST_SNAPSHOT + ); + + minBasis_ = + dict.getCheckOrDefault<scalar>("minBasis", 1e-8, scalarMinMax::ge(0)); + + minEval_ = + dict.getCheckOrDefault<scalar>("minEVal", 1e-8, scalarMinMax::ge(0)); + + dt_ = + dict.getCheckOrDefault<scalar> + ( + "interval", + ( + dict.getCheck<label>("executeInterval", labelMinMax::ge(1)) + *mesh_.time().deltaT().value() + ), + scalarMinMax::ge(0) + ); + + sortLimiter_ = + dict.getCheckOrDefault<scalar>("sortLimiter", 500, scalarMinMax::ge(1)); + + nGramSchmidt_ = + dict.getCheckOrDefault<label>("nGramSchmidt", 5, labelMinMax::ge(1)); + + maxRank_ = + dict.getCheckOrDefault<label> + ( + "maxRank", + pTraits<label>::max, + labelMinMax::ge(1) + ); + + nModes_ = + dict.getCheckOrDefault<label> + ( + "nModes", + pTraits<label>::max, + labelMinMax::ge(1) + ); + + fMin_ = dict.getCheckOrDefault<label>("fMin", 0, labelMinMax::ge(0)); + + fMax_ = + dict.getCheckOrDefault<label> + ( + "fMax", + pTraits<label>::max, + labelMinMax::ge(fMin_ + 1) + ); + + nAgglomerationProcs_ = + dict.getCheckOrDefault<label> + ( + "nAgglomerationProcs", + 20, + labelMinMax::ge(1) + ); + + Info<< tab << "Settings are read for:" << nl + << " field: " << fieldName_ << nl + << " modeSorter: " << modeSorterTypeNames[modeSorter_] << nl + << " nModes: " << nModes_ << nl + << " maxRank: " << maxRank_ << nl + << " nGramSchmidt: " << nGramSchmidt_ << nl + << " fMin: " << fMin_ << nl + << " fMax: " << fMax_ << nl + << " minBasis: " << minBasis_ << nl + << " minEVal: " << minEval_ << nl + << " sortLimiter: " << sortLimiter_ << nl + << " nAgglomerationProcs: " << nAgglomerationProcs_ << nl + << endl; + + return true; +} + + +bool Foam::DMDModels::STDMD::initialise(const RMatrix& z) +{ + const scalar norm = L2norm(z); + + if (mag(norm) > 0) + { + // First-processed snapshot required by the mode-sorting + // algorithms at the final output computations (K:p. 43) + { + const label nSnap = z.m()/2; + + if (nSnap == 0) + { + empty_ = true; + } + + scalarField snapshot0(nSnap); + std::copy(z.cbegin(), z.cbegin() + nSnap, snapshot0.begin()); + timeName0_ = mesh_.time().timeName(); + + IOField<scalar> + ( + IOobject + ( + "snapshot0_" + name_ + "_" + fieldName_, + timeName0_, + mesh_, + IOobject::NO_READ, + IOobject::NO_WRITE + ), + snapshot0 + ).write(); + } + + Q_ = z/norm; + G_ = SMatrix(1); + G_(0,0) = sqr(norm); + + ++step_; + + return true; + } + + return false; +} + + +bool Foam::DMDModels::STDMD::update(const RMatrix& z) +{ + { + //- Working copy of the augmented snapshot matrix "z" + //- being used in the classical Gram-Schmidt process + const RMatrix ez(orthonormalise(z)); + + // Check basis for "z" and, if necessary, expand "Q" and "G" + const scalar ezNorm = L2norm(ez); + const scalar zNorm = L2norm(z); + + if (ezNorm/zNorm > minBasis_) + { + expand(ez, ezNorm); + } + } + + // Update "G" before the potential orthonormal basis compression + { + RMatrix zTilde(Q_ & z); + reduce(zTilde, sumOp<RMatrix>()); + + G_ += SMatrix(zTilde^zTilde); + } + + // Compress the orthonormal basis if required + if (Q_.n() >= maxRank_) + { + compress(); + } + + ++step_; + + return true; +} + + +bool Foam::DMDModels::STDMD::fit() +{ + // DMD statistics and mode evaluation (K:Fig. 16) + const label nSnap = Q_.m()/2; + + // Move upper and lower halves of "Q" to new containers + Qupper_ = RMatrix(Q_.subMatrix(0, 0, max(nSnap, 1))); + Qlower_ = RMatrix(Q_.subMatrix(nSnap, 0, max(nSnap, 1))); + Q_.clear(); + + if (!dynamics()) + { + return true; + } + + modes(); + + if (Pstream::master() && writeToFile_) + { + writeToFile(word("dynamics")); + + filter(); + + writeToFile(word("filteredDynamics")); + } + + step_ = 0; + + return true; +} + + +// ************************************************************************* // diff --git a/src/functionObjects/field/DMD/DMDModels/derived/STDMD/STDMD.H b/src/functionObjects/field/DMD/DMDModels/derived/STDMD/STDMD.H new file mode 100644 index 0000000000000000000000000000000000000000..0b86e137fc861b0a6ba7bf3f362eb1aa768ad930 --- /dev/null +++ b/src/functionObjects/field/DMD/DMDModels/derived/STDMD/STDMD.H @@ -0,0 +1,530 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2020-2021 OpenCFD Ltd. +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. + +Class + Foam::DMDModels::STDMD + +Description + Streaming Total Dynamic Mode Decomposition (i.e. \c STDMD) + is a variant of dynamic mode decomposition. + + Among other Dynamic Mode Decomposition (DMD) variants, \c STDMD is + presumed to provide the general DMD method capabilities alongside + economised and feasible memory and CPU usage. + + The code implementation corresponds to Figs. 15-16 of the first citation + below, more broadly to Section 2.4. + + References: + \verbatim + DMD and mode-sorting algorithms (tags:K, HRDC, KZ, HWR): + Kiewat, M. (2019). + Streaming modal decomposition approaches for vehicle aerodynamics. + PhD thesis. Munich: Technical University of Munich. + URL:mediatum.ub.tum.de/doc/1482652/1482652.pdf + + Hemati, M. S., Rowley, C. W., + Deem, E. A., & Cattafesta, L. N. (2017). + De-biasing the dynamic mode decomposition + for applied Koopman spectral analysis of noisy datasets. + Theoretical and Computational Fluid Dynamics, 31(4), 349-368. + DOI:10.1007/s00162-017-0432-2 + + Kou, J., & Zhang, W. (2017). + An improved criterion to select + dominant modes from dynamic mode decomposition. + European Journal of Mechanics-B/Fluids, 62, 109-129. + DOI:10.1016/j.euromechflu.2016.11.015 + + Hemati, M. S., Williams, M. O., & Rowley, C. W. (2014). + Dynamic mode decomposition for large and streaming datasets. + Physics of Fluids, 26(11), 111701. + DOI:10.1063/1.4901016 + + Parallel classical Gram-Schmidt process (tag:Ka): + Katagiri, T. (2003). + Performance evaluation of parallel + Gram-Schmidt re-orthogonalization methods. + In: Palma J. M. L. M., Sousa A. A., Dongarra J., Hernández V. (eds) + High Performance Computing for Computational Science — VECPAR 2002. + Lecture Notes in Computer Science, vol 2565, p. 302-314. + Berlin, Heidelberg: Springer. + DOI:10.1007/3-540-36569-9_19 + + Parallel direct tall-skinny QR decomposition (tags:BGD, DGHL): + Benson, A. R., Gleich, D. F., & Demmel, J. (2013). + Direct QR factorizations for + tall-and-skinny matrices in MapReduce architectures. + 2013 IEEE International Conference on Big Data. + DOI:10.1109/bigdata.2013.6691583 + + Demmel, J., Grigori, L., Hoemmen, M., & Langou, J. (2012). + Communication-optimal parallel + and sequential QR and LU factorizations. + SIAM Journal on Scientific Computing, 34(1), A206-A239. + DOI:10.1137/080731992 + \endverbatim + +Usage + Minimal example by using \c system/controlDict.functions: + \verbatim + DMD1 + { + // Mandatory/Optional entries + ... + + // Mandatory entries (unmodifiable) + DMDModel STDMD; + + // Conditional mandatory entries (runtime modifiable) + + // Option-1 + interval 5.5; + + // Option-2 + executeInterval 10; + + // Optional entries (runtime modifiable) + modeSorter kiewat; + nGramSchmidt 5; + maxRank 50; + nModes 50; + fMin 0; + fMax 1000000000; + nAgglomerationProcs 20; + + // Optional entries (runtime modifiable, yet not recommended) + minBasis 0.00000001; + minEVal 0.00000001; + sortLimiter 500.0; + + // Mandatory/Optional (inherited) entries + ... + } + \endverbatim + + where the entries mean: + \table + Property | Description | Type | Reqd | Deflt + DMDModel | Type: STDMD | word | yes | - + interval | STDMD time-step size [s] | scalar | cndtnl <!-- + --> | executeInterval*(current time-step of the simulation) + modeSorter | Mode-sorting algorithm model | word | no <!-- + --> | firstSnapshot + nModes | Number of output modes in input frequency range <!-- + --> | label | no | GREAT + maxRank | Max columns in accumulated matrices | label | no | GREAT + nGramSchmidt | Number of Gram-Schmidt iterations | label | no | 5 + fMin | Min (non-negative) output frequency | label | no | 0 + fMax | Max output frequency | label | no | GREAT + nAgglomerationProcs | Number of processors at each agglomeration <!-- + --> unit during the computation of reduced Koopman <!-- + --> operator | label | no | 20 + minBasis | Orthogonal basis expansion threshold | scalar| no | 1e-8 + minEVal | Min eigenvalue for below eigenvalues are omitted <!-- + --> | scalar| no | 1e-8 + sortLimiter | Max allowable magnitude for <!-- + --> mag(eigenvalue)*(number of DMD steps) to be used in <!-- + --> modeSorter={kiewat,kouZhang} to avoid overflow errors <!-- + --> | scalar | no | 500.0 + \endtable + + Options for the \c modeSorter entry: + \verbatim + kiewat | Modified weighted-amplitude scaling method + kouZhang | Weighted-amplitude scaling method + firstSnapshot | First-snapshot amplitude magnitude method + \endverbatim + +Note + - To specify the STDMD time-step size (not necessarily equal to the + time step of the simulation), entries of either \c interval or + \c executeInterval must be available (highest to lowest precedence). While + \c interval allows users to directly specify the STDMD time-step size + in seconds, in absence of \c interval, for convenience, + \c executeInterval allows users to compute the STDMD time-step internally + by multiplying itself with the current time-step size of the simulation. + - Limitation: Restart is currently not available since intermediate writing + of STDMD matrices are not supported. + - Limitation: Non-physical input (e.g. full-zero fields) can upset STDMD. + - Warning: In \c modeSorter algorithms of \c kiewat and \c kouZhang, a + function of \f$power(x,y)\f$ exists where \c x is the magnitude of an + eigenvalue, and \c y is the number of STDMD snapshots. This function poses + a risk of overflow errors since, for example, if \c x ends up above 1.5 with + 500 snapshots or more, this function automatically throws the floating point + error meaning overflow. Therefore, the domain-range of this function is + heuristically constrained by the optional entry \c sortLimiter which skips + the evaluation of this function for a given + mag(eigenvalue)*(no. of STDMD steps), i.e. x*y, whose magnitude is larger + than \c sortLimiter. + +See also + - Foam::functionObjects::DMD + - Foam::DMDModel + +SourceFiles + STDMD.C + STDMDTemplates.C + +\*---------------------------------------------------------------------------*/ + +#ifndef DMDModels_STDMD_H +#define DMDModels_STDMD_H + +#include "DMDModel.H" +#include "SquareMatrix.H" +#include "RectangularMatrix.H" + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +namespace Foam +{ +namespace DMDModels +{ + +/*---------------------------------------------------------------------------*\ + Class STDMD Declaration +\*---------------------------------------------------------------------------*/ + +class STDMD +: + public DMDModel +{ + typedef SquareMatrix<scalar> SMatrix; + typedef RectangularMatrix<scalar> RMatrix; + typedef RectangularMatrix<complex> RCMatrix; + + + // Private Enumerations + + //- Options for the mode-sorting algorithm + enum modeSorterType : char + { + FIRST_SNAPSHOT = 0, //!< "First-snapshot amplitude magnitude method" + KIEWAT, //!< "Modified weighted-amplitude scaling method" + KOU_ZHANG //!< "Weighted-amplitude scaling method" + }; + + //- Names for modeSorterType + static const Enum<modeSorterType> modeSorterTypeNames; + + + // Private Data + + //- Mode-sorting algorithm + enum modeSorterType modeSorter_; + + //- Accumulated-in-time unitary similarity matrix produced by the + //- orthonormal decomposition of the augmented snapshot matrix 'z' + // (K:Eq. 60) + RMatrix Q_; + + //- Accumulated-in-time (squared) upper triangular matrix produced by + //- the orthonormal decomposition of the augmented snapshot matrix 'z' + // (K:Eq. 64) + SMatrix G_; + + //- Upper half of 'Q' + RMatrix Qupper_; + + //- Lower half of 'Q' + RMatrix Qlower_; + + //- Moore-Penrose pseudo-inverse of 'R' produced by + //- the QR decomposition of the last time-step 'Q' + RMatrix RxInv_; + + //- Eigenvalues of modes + List<complex> evals_; + + //- Eigenvectors of modes + RCMatrix evecs_; + + //- (Non-negative) frequencies of modes + List<scalar> freqs_; + + //- Indices of 'freqs' where frequencies are + //- non-negative and within ['fMin', 'fMax'] + DynamicList<label> freqsi_; + + //- Amplitudes of modes + List<complex> amps_; + + //- (Descending) magnitudes of (complex) amplitudes of modes + List<scalar> mags_; + + //- Indices of 'mags' + List<label> magsi_; + + //- Name of operand field + const word fieldName_; + + //- Name of operand patch + const word patch_; + + //- First-processed snapshot required by the mode-sorting + //- algorithms at the final output computations (K:p. 43) + word timeName0_; + + //- Min value to execute orthogonal basis expansion of 'Q' and 'G' + scalar minBasis_; + + //- Min eigenvalue magnitude below where 'evals' are omitted + scalar minEval_; + + //- STDMD time-step size that equals to + //- (executeInterval of DMD)*(deltaT of simulation) [s] + scalar dt_; + + //- Maximum allowable magnitude for mag(eigenvalue)*(number of + //- STDMD steps) to be used in modeSorter={kiewat,kouZhang} to + //- avoid overflow errors + scalar sortLimiter_; + + //- Min frequency: Output only entries corresponding to freqs >= 'fMin' + label fMin_; + + //- Max frequency: Output only entries corresponding to freqs <= 'fMax' + label fMax_; + + //- Number of maximum iterations of the classical + //- Gram-Schmidt procedure for the orthonormalisation + label nGramSchmidt_; + + //- Maximum allowable matrix column for 'Q' and 'G' + // 'Q' is assumed to always have full-rank, thus 'Q.n() = rank' + label maxRank_; + + //- Current execution-step index of STDMD, + //- not necessarily that of the simulation + label step_; + + //- Number of output modes within input frequency range + //- starting from the most energetic mode + label nModes_; + + //- Number of processors at each agglomeration unit + //- during the computation of reduced Koopman operator + label nAgglomerationProcs_; + + //- (Internal) Flag to tag snapshots which are effectively empty + bool empty_; + + + // Private Member Functions + + // Process + + //- Return (parallel) L2-norm of a given column vector + scalar L2norm(const RMatrix& z) const; + + //- Execute (parallel) classical Gram-Schmidt + //- process to orthonormalise 'ez' (Ka:Fig. 5) + RMatrix orthonormalise(RMatrix ez) const; + + //- Expand orthonormal bases 'Q' and 'G' by stacking a column + //- '(ez/ezNorm)' to 'Q', and a row (Zero) and column (Zero) + //- to 'G' if '(ezNorm/zNorm > minBasis)' + void expand(const RMatrix& ez, const scalar ezNorm); + + //- Compress orthonormal basis for 'Q' and 'G' if '(Q.n()>maxRank)' + void compress(); + + + // Evaluation + + //- Return reduced Koopman operator 'Atilde' (K:Eq. 78) + // Also fills 'RxInv'. + // The function was not divided into subsections to ensure + // minimal usage of memory, hence the complexity of the function + SMatrix reducedKoopmanOperator(); + + //- Compute eigenvalues and eigenvectors of 'Atilde' + // Remove any eigenvalues whose magnitude is smaller than + // 'minEVal' while keeping the order of elements the same + bool eigendecomposition(SMatrix& Atilde); + + //- Compute and filter frequencies and its indices (K:Eq. 81) + // Indices of 'freqs' where frequencies are + // non-negative and within ['fMin', 'fMax'] + void frequencies(); + + //- Compute amplitudes + void amplitudes(); + + //- Compute magnitudes and ordered magnitude indices + // Includes advanced sorting methods using + // the chosen weighted amplitude scaling method + void magnitudes(); + + //- Eigenvalue weighted amplitude scaling (KZ:Eq. 33) + //- Modified eigenvalue weighted amplitude scaling (K) + scalar sorter + ( + const List<scalar>& weight, + const complex& amplitude, + const complex& eigenvalue, + const scalar modeNorm + ) const; + + //- Compute and write mode dynamics + virtual bool dynamics(); + + //- Compute and write modes + virtual bool modes(); + + //- Select field type for modes + template<class Type> + bool modes(); + + //- Compute modes based on input field type + template<class GeoFieldType> + bool calcModes(); + + //- Compute a mode for a given scalar-based input field + template<class GeoFieldType> + typename std::enable_if + < + std::is_same<scalar, typename GeoFieldType::value_type>::value, + void + >::type calcMode + ( + GeoFieldType& modeRe, + GeoFieldType& modeIm, + const RMatrix& primitiveMode, + const label i + ); + + //- Compute a mode for a given non-scalar-based input field + template<class GeoFieldType> + typename std::enable_if + < + !std::is_same<scalar, typename GeoFieldType::value_type>::value, + void + >::type calcMode + ( + GeoFieldType& modeRe, + GeoFieldType& modeIm, + const RMatrix& primitiveMode, + const label i + ); + + + // IO + + //- Write objects of dynamics + void writeToFile(const word& fileName) const; + + // Notifying the compiler that we want both writeToFile functions + using Foam::functionObjects::writeFile::writeToFile; + + //- Write file header information + void writeFileHeader(Ostream& os) const; + + //- Filter objects of dynamics according to 'freqsi' and 'magsi' + void filter(); + + //- Return a new List containing elems of List at 'indices' + template<class Type> + void filterIndexed + ( + List<Type>& lst, + const UList<label>& indices + ); + + //- Return a new Matrix containing columns of Matrix at 'indices' + template<class MatrixType> + void filterIndexed + ( + MatrixType& lst, + const UList<label>& indices + ); + + +public: + + //- Runtime type information + TypeName("STDMD"); + + + // Constructors + + //- Construct from components + STDMD + ( + const fvMesh& mesh, + const word& name, + const dictionary& dict + ); + + //- No copy construct + STDMD(const STDMD&) = delete; + + //- No copy assignment + void operator=(const STDMD&) = delete; + + + //- Destructor + virtual ~STDMD() = default; + + + // Member Functions + + // Evaluation + + //- Initialise 'Q' and 'G' (both require the first two snapshots) + virtual bool initialise(const RMatrix& z); + + //- Incremental orthonormal basis update (K:Fig. 15) + virtual bool update(const RMatrix& z); + + //- Compute and write modes and + //- mode dynamics of model data members + virtual bool fit(); + + + // IO + + //- Read STDMD settings + virtual bool read(const dictionary& dict); +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +} // End namespace DMDModels +} // End namespace Foam + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#ifdef NoRepository + #include "STDMDTemplates.C" +#endif + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#endif + +// ************************************************************************* // diff --git a/src/functionObjects/field/DMD/DMDModels/derived/STDMD/STDMDTemplates.C b/src/functionObjects/field/DMD/DMDModels/derived/STDMD/STDMDTemplates.C new file mode 100644 index 0000000000000000000000000000000000000000..408f2075fe2fd2e51294f43b01957b6a6cea5a92 --- /dev/null +++ b/src/functionObjects/field/DMD/DMDModels/derived/STDMD/STDMDTemplates.C @@ -0,0 +1,236 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2020-2021 OpenCFD Ltd. +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. + +\*---------------------------------------------------------------------------*/ + +#include "volFields.H" +#include "surfaceFields.H" +#include "fixedValueFvPatchField.H" + +// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // + +template<class Type> +void Foam::DMDModels::STDMD::filterIndexed +( + List<Type>& lst, + const UList<label>& indices +) +{ + // Elements within [a, b] + List<Type> lstWithin(indices.size()); + + // Copy if frequency of element is within [a, b] + label j = 0; + for (const auto& i : indices) + { + lstWithin[j] = lst[i]; + ++j; + } + lst.transfer(lstWithin); +} + + +template<class MatrixType> +void Foam::DMDModels::STDMD::filterIndexed +( + MatrixType& mat, + const UList<label>& indices +) +{ + // Elements within [a, b] + MatrixType matWithin(labelPair(mat.m(), indices.size())); + + // Copy if frequency of element is within [a, b] + label j = 0; + for (const auto& i : indices) + { + matWithin.subColumn(j) = mat.subColumn(i); + ++j; + } + mat.transfer(matWithin); +} + + +template<class Type> +bool Foam::DMDModels::STDMD::modes() +{ + typedef GeometricField<Type, fvPatchField, volMesh> VolFieldType; + typedef GeometricField<Type, fvsPatchField, surfaceMesh> SurfaceFieldType; + + if (mesh_.foundObject<VolFieldType>(fieldName_)) + { + return calcModes<VolFieldType>(); + } + else if (mesh_.foundObject<SurfaceFieldType>(fieldName_)) + { + return calcModes<SurfaceFieldType>(); + } + + return false; +} + + +template<class GeoFieldType> +bool Foam::DMDModels::STDMD::calcModes() +{ + typedef typename GeoFieldType::value_type Type; + + // Resize the number of output variables to "nModes" if requested + if (magsi_.size() > nModes_) + { + magsi_.resize(nModes_); + } + + // Compute and write modes one by one + const RMatrix primitiveMode(Qupper_*RxInv_); + Qupper_.clear(); + RxInv_.clear(); + + label modei = 0; + for (const label i : magsi_) + { + GeoFieldType modeRe + ( + IOobject + ( + "modeRe_" + std::to_string(modei) + + "_" + fieldName_ + "_" + name_, + mesh_.time().timeName(), + mesh_, + IOobject::NO_READ, + IOobject::NO_WRITE, + false + ), + mesh_, + dimensioned<Type>(dimless, Zero), + fixedValueFvPatchField<Type>::typeName + ); + + GeoFieldType modeIm + ( + IOobject + ( + "modeIm_" + std::to_string(modei) + + "_" + fieldName_ + "_" + name_, + mesh_.time().timeName(), + mesh_, + IOobject::NO_READ, + IOobject::NO_WRITE, + false + ), + mesh_, + dimensioned<Type>(dimless, Zero), + fixedValueFvPatchField<Type>::typeName + ); + + if (modeRe.size() != 0 && !empty_) + { + if (patch_.empty()) + { + auto& inModeRe = modeRe.primitiveFieldRef(); + auto& inModeIm = modeIm.primitiveFieldRef(); + + calcMode(inModeRe, inModeIm, primitiveMode, i); + } + else + { + const label patchi = mesh_.boundaryMesh().findPatchID(patch_); + + auto& bfModeRe = modeRe.boundaryFieldRef()[patchi]; + auto& bfModeIm = modeIm.boundaryFieldRef()[patchi]; + + calcMode(bfModeRe, bfModeIm, primitiveMode, i); + } + } + + modeRe.write(); + modeIm.write(); + ++modei; + } + + return true; +} + + +template<class GeoFieldType> +typename std::enable_if +< + std::is_same<Foam::scalar, typename GeoFieldType::value_type>::value, + void +>::type Foam::DMDModels::STDMD::calcMode +( + GeoFieldType& modeRe, + GeoFieldType& modeIm, + const RMatrix& primitiveMode, + const label i +) +{ + const label fieldSize = modeRe.size(); + + for (label p = 0; p < primitiveMode.m(); ++p) + { + complex mode(Zero); + for (label q = 0; q < evecs_.m(); ++q) + { + mode += primitiveMode(p, q)*evecs_(q, i); + } + label p1 = p%fieldSize; + modeRe[p1] = mode.real(); + modeIm[p1] = mode.imag(); + } +} + + +template<class GeoFieldType> +typename std::enable_if +< + !std::is_same<Foam::scalar, typename GeoFieldType::value_type>::value, + void +>::type Foam::DMDModels::STDMD::calcMode +( + GeoFieldType& modeRe, + GeoFieldType& modeIm, + const RMatrix& primitiveMode, + const label i +) +{ + const label fieldSize = modeRe.size(); + + for (label p = 0; p < primitiveMode.m(); ++p) + { + complex mode(Zero); + for (label q = 0; q < evecs_.m(); ++q) + { + mode += primitiveMode(p, q)*evecs_(q, i); + } + label p1 = p%fieldSize; + label p2 = p/fieldSize; + modeRe[p1][p2] = mode.real(); + modeIm[p1][p2] = mode.imag(); + } +} + + +// ************************************************************************* // diff --git a/src/functionObjects/field/STDMD/STDMDTemplates.C b/src/functionObjects/field/DMD/DMDTemplates.C similarity index 51% rename from src/functionObjects/field/STDMD/STDMDTemplates.C rename to src/functionObjects/field/DMD/DMDTemplates.C index e32bb2ddc33ff8e8680071ae986b51e50a45826e..a08fc5c02ed69cb05c2906762784cb3e665f1051 100644 --- a/src/functionObjects/field/STDMD/STDMDTemplates.C +++ b/src/functionObjects/field/DMD/DMDTemplates.C @@ -5,7 +5,7 @@ \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- - Copyright (C) 2020 OpenCFD Ltd. + Copyright (C) 2020-2021 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -30,111 +30,82 @@ License // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // -template<class GeoFieldType> -bool Foam::functionObjects::STDMD::getSnapshot() -{ - if (!initialised_) - { - init(); - } - - // Move previous-time snapshot into previous-time slot in z_ - // Effectively moves the lower half of z_ to its upper half - std::rotate(z_.begin(), z_.begin() + nSnap_, z_.end()); - - // Copy new current-time snapshot into current-time slot in z_ - // Effectively copies the new field elements into the lower half of z_ - const GeoFieldType& Field = lookupObject<GeoFieldType>(fieldName_); - const label nField = Field.size(); - - for (direction dir = 0; dir < nComps_; ++dir) - { - z_.subColumn(0, nSnap_ + dir*nField, nField) = Field.component(dir); - } - - return true; -} - - template<class Type> -bool Foam::functionObjects::STDMD::getSnapshotType() +bool Foam::functionObjects::DMD::getSnapshot() { typedef GeometricField<Type, fvPatchField, volMesh> VolFieldType; typedef GeometricField<Type, fvsPatchField, surfaceMesh> SurfaceFieldType; if (foundObject<VolFieldType>(fieldName_)) { - return getSnapshot<VolFieldType>(); + return getSnapshotField<VolFieldType>(); } else if (foundObject<SurfaceFieldType>(fieldName_)) { - return getSnapshot<SurfaceFieldType>(); + return getSnapshotField<SurfaceFieldType>(); } return false; } -template<class Type> -bool Foam::functionObjects::STDMD::getComps() +template<class GeoFieldType> +bool Foam::functionObjects::DMD::getSnapshotField() { - typedef GeometricField<Type, fvPatchField, volMesh> VolFieldType; - typedef GeometricField<Type, fvsPatchField, surfaceMesh> SurfaceFieldType; - - if (foundObject<VolFieldType>(fieldName_)) + if (step_ == 0) { - nComps_ = pTraits<typename VolFieldType::value_type>::nComponents; - return true; + initialise(); } - else if (foundObject<SurfaceFieldType>(fieldName_)) + + if (z_.size() == 1) { - nComps_ = pTraits<typename SurfaceFieldType::value_type>::nComponents; return true; } - return false; -} + // Move previous-time snapshot into previous-time slot in "z" + // Effectively moves the lower half of "z" to its upper half + std::rotate(z_.begin(), z_.begin() + nSnap_, z_.end()); + // Copy new current-time snapshot into current-time slot in "z" + // Effectively copies the new field elements into the lower half of "z" + const label nComps = + pTraits<typename GeoFieldType::value_type>::nComponents; -template<class Type> -void Foam::functionObjects::STDMD::filterIndexed -( - List<Type>& lst, - const UList<label>& indices -) -{ - // Elems within [a, b] - List<Type> lstWithin(indices.size()); + const GeoFieldType& field = lookupObject<GeoFieldType>(fieldName_); - // Copy if frequency of elem is within [a, b] - label j = 0; - for (const auto& i : indices) + if (patch_.empty()) { - lstWithin[j] = lst[i]; - ++j; + const label nField = field.size(); + + for (direction dir = 0; dir < nComps; ++dir) + { + z_.subColumn(0, nSnap_ + dir*nField, nField) = field.component(dir); + } } - lst.transfer(lstWithin); -} + else + { + const label patchi = mesh_.boundaryMesh().findPatchID(patch_); + if (patchi < 0) + { + FatalErrorInFunction + << "Cannot find patch " << patch_ + << exit(FatalError); + } -template<class MatrixType> -void Foam::functionObjects::STDMD::filterIndexed -( - MatrixType& mat, - const UList<label>& indices -) -{ - // Elems within [a, b] - MatrixType matWithin(labelPair(mat.m(), indices.size())); + const typename GeoFieldType::Boundary& bf = field.boundaryField(); - // Copy if frequency of elem is within [a, b] - label j = 0; - for (const auto& i : indices) - { - matWithin.subColumn(j) = mat.subColumn(i); - ++j; + const Field<typename GeoFieldType::value_type>& pbf = bf[patchi]; + + const label nField = pbf.size(); + + for (direction dir = 0; dir < nComps; ++dir) + { + z_.subColumn(0, nSnap_ + dir*nField, nField) = pbf.component(dir); + } } - mat.transfer(matWithin); + + return true; } diff --git a/src/functionObjects/field/Make/files b/src/functionObjects/field/Make/files index 2b84b1f263356a10c2baf91aaf1c9c6973f009fc..1ca2168217c384887478b799111d13973fa2f704 100644 --- a/src/functionObjects/field/Make/files +++ b/src/functionObjects/field/Make/files @@ -121,6 +121,9 @@ stabilityBlendingFactor/stabilityBlendingFactor.C interfaceHeight/interfaceHeight.C -STDMD/STDMD.C +DMD/DMD.C +DMD/DMDModels/DMDModel/DMDModel.C +DMD/DMDModels/DMDModel/DMDModelNew.C +DMD/DMDModels/derived/STDMD/STDMD.C LIB = $(FOAM_LIBBIN)/libfieldFunctionObjects diff --git a/src/functionObjects/field/Make/options b/src/functionObjects/field/Make/options index 57ea31c1cfb026a75c927836f43aed29ef24bf1d..93f082e386b2940c9517351ed9fb8f9bd156ded9 100644 --- a/src/functionObjects/field/Make/options +++ b/src/functionObjects/field/Make/options @@ -1,5 +1,6 @@ EXE_INC = \ -I$(LIB_SRC)/finiteVolume/lnInclude \ + -I$(LIB_SRC)/finiteArea/lnInclude \ -I$(LIB_SRC)/fileFormats/lnInclude \ -I$(LIB_SRC)/surfMesh/lnInclude \ -I$(LIB_SRC)/meshTools/lnInclude \ @@ -22,6 +23,7 @@ EXE_INC = \ LIB_LIBS = \ -lfiniteVolume \ + -lfiniteArea \ -lfileFormats \ -lsurfMesh \ -lmeshTools \ diff --git a/src/functionObjects/field/STDMD/STDMD.C b/src/functionObjects/field/STDMD/STDMD.C deleted file mode 100644 index 4fc192d670a6d399ae95c10633c283db3b0f8353..0000000000000000000000000000000000000000 --- a/src/functionObjects/field/STDMD/STDMD.C +++ /dev/null @@ -1,1280 +0,0 @@ -/*---------------------------------------------------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | - \\ / A nd | www.openfoam.com - \\/ M anipulation | -------------------------------------------------------------------------------- - Copyright (C) 2020 OpenCFD Ltd. -------------------------------------------------------------------------------- -License - This file is part of OpenFOAM. - - OpenFOAM is free software: you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - OpenFOAM is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - for more details. - - You should have received a copy of the GNU General Public License - along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. - -\*---------------------------------------------------------------------------*/ - -#include "STDMD.H" -#include "addToRunTimeSelectionTable.H" - -using namespace Foam::constant::mathematical; - -// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // - -namespace Foam -{ -namespace functionObjects -{ - defineTypeNameAndDebug(STDMD, 0); - addToRunTimeSelectionTable(functionObject, STDMD, dictionary); -} -} - - -const Foam::Enum -< - Foam::functionObjects::STDMD::modeSorterType -> -Foam::functionObjects::STDMD::modeSorterTypeNames -({ - { modeSorterType::KIEWAT , "kiewat" }, - { modeSorterType::KOU_ZHANG , "kouZhang" }, - { modeSorterType::FIRST_SNAPSHOT, "firstSnap" } -}); - - -// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // - -Foam::scalar Foam::functionObjects::STDMD::parnorm -( - const RMatrix& colVector -) const -{ - #ifdef FULLDEBUG - // Check if the given RectangularMatrix is effectively a column vector - if (colVector.n() != 1) - { - FatalErrorInFunction - << " # Input matrix is not a column vector. #" - << abort(FatalError); - } - #endif - const bool noSqrt = true; - scalar result(colVector.columnNorm(0, noSqrt)); - reduce(result, sumOp<scalar>()); - - // Heuristic addition to avoid very small or zero norm - return max(SMALL, Foam::sqrt(result)); -} - - -void Foam::functionObjects::STDMD::snapshot() -{ - bool processed = false; - processed = processed || getSnapshotType<scalar>(); - processed = processed || getSnapshotType<vector>(); - processed = processed || getSnapshotType<sphericalTensor>(); - processed = processed || getSnapshotType<symmTensor>(); - processed = processed || getSnapshotType<tensor>(); - - if (!processed) - { - FatalErrorInFunction - << " # Unknown type of input field during snapshot loading = " - << fieldName_ << " #" << nl - << " # Do you execute required functionObjects " - << "before executing STDMD, e.g. mapFields?" - << abort(FatalError); - } -} - - -void Foam::functionObjects::STDMD::init() -{ - bool processed = false; - processed = processed || getComps<scalar>(); - processed = processed || getComps<vector>(); - processed = processed || getComps<sphericalTensor>(); - processed = processed || getComps<symmTensor>(); - processed = processed || getComps<tensor>(); - - if (!processed) - { - FatalErrorInFunction - << " # Unknown type of input field during initialisation = " - << fieldName_ << " #" << nl - << " # Do you execute required functionObjects " - << "before executing STDMD, e.g. mapFields?" - << abort(FatalError); - } - - nSnap_ = nComps_*mesh_.nCells(); - - if (nSnap_ <= 0) - { - FatalErrorInFunction - << " # Zero-size input field = " << fieldName_ << " #" - << abort(FatalError); - } - - currIndex_ = 0; - zNorm_ = 0; - ezNorm_ = 0; - - z_ = RMatrix(2*nSnap_, 1, Zero); - ez_ = z_; - X1_ = RMatrix(nSnap_, 1, Zero); - Qz_ = z_; - Gz_ = SMatrix(1); - - RxInv_.clear(); - Ap_.clear(); - oEVecs_.clear(); - oEVals_.clear(); - oAmps_.clear(); - oFreqs_.clear(); - iFreqs_.clear(); - oMags_.clear(); - iMags_.clear(); - - initialised_ = true; -} - - -void Foam::functionObjects::STDMD::initBasis() -{ - std::copy(z_.cbegin(), z_.cbegin() + nSnap_, X1_.begin()); - - zNorm_ = parnorm(z_); - Qz_ = z_/zNorm_; - Gz_(0,0) = sqr(zNorm_); -} - - -void Foam::functionObjects::STDMD::GramSchmidt() -{ - ez_ = z_; - RMatrix dz(Qz_.n(), 1, Zero); - - for (label i = 0; i < nGramSchmidt_; ++i) - { - dz = Qz_ & ez_; - reduce(dz, sumOp<RMatrix>()); - ez_ -= Qz_*dz; - } -} - - -void Foam::functionObjects::STDMD::expandBasis() -{ - Log<< tab << "# " << name() << ":" - << " Expanding orthonormal basis for field = " << fieldName_ << " #" - << endl; - - // Stack a column (ez_/ezNorm_) to Qz_ - Qz_.resize(Qz_.m(), Qz_.n() + 1); - Qz_.subColumn(Qz_.n() - 1) = ez_/ezNorm_; - - // Stack a row (Zero) and column (Zero) to Gz_ - Gz_.resize(Gz_.m() + 1); -} - - -void Foam::functionObjects::STDMD::updateGz() -{ - RMatrix zTilde(Qz_ & z_); - reduce(zTilde, sumOp<RMatrix>()); - - const SMatrix zTildes(zTilde^zTilde); - - Gz_ += zTildes; -} - - -void Foam::functionObjects::STDMD::compressBasis() -{ - Log<< tab << "# " << name() << ":" - << " Compressing orthonormal basis for field = " << fieldName_ << " #" - << endl; - - RMatrix qz; - - if (Pstream::master()) - { - const bool symmetric = true; - const EigenMatrix<scalar> EM(Gz_, symmetric); - const SquareMatrix<scalar>& EVecs = EM.EVecs(); - DiagonalMatrix<scalar> EVals(EM.EValsRe()); - - if (testEigen_) - { - testEigenvalues(Gz_, EVals); - testEigenvectors(Gz_, EVals, EVecs); - } - - // Sort eigenvalues in descending order, and track indices - const auto descend = [&](scalar a, scalar b){ return a > b; }; - const List<label> permut(EVals.sortPermutation(descend)); - EVals.applyPermutation(permut); - EVals.resize(EVals.size() - 1); - - // Update Gz_ - Gz_ = SMatrix(maxRank_, Zero); - Gz_.diag(EVals); - - qz.resize(Qz_.n(), maxRank_); - for (label i = 0; i < maxRank_; ++i) - { - qz.subColumn(i) = EVecs.subColumn(permut[i]); - } - } - Pstream::scatter(Gz_); - Pstream::scatter(qz); - - // Update Qz_ - Qz_ = Qz_*qz; -} - - -void Foam::functionObjects::STDMD::calcAp() -{ - Log<< tab << "# " << name() << ": Computing Ap matrix #" << endl; - - Log<< tab << "# " << name() << ": Computing local Rx #" << endl; - RMatrix Rx; - { - QRMatrix<RMatrix> QRM - ( - QzUH_, - QRMatrix<RMatrix>::outputTypes::REDUCED_R, - QRMatrix<RMatrix>::colPivoting::FALSE - ); - Rx = QRM.R(); - } - Rx.round(); - - RMatrix A1; // Convenience objects A1, A2, A3 to compute Ap - if (Pstream::parRun()) - { - // Parallel direct tall-skinny QR decomposition - // (BGD:Fig. 5) & (DGHL:Fig. 2) - // Tests revealed that the distribution of Qz_ does not affect - // the final outcome of TSQR decomposition up to sign - - Log<< tab << "# " << name() << ": Gathering all local Rx #" << endl; - List<RMatrix> RxList(Pstream::nProcs()); - RxList[Pstream::myProcNo()] = Rx; - Pstream::gatherList(RxList); - Pstream::scatterList(RxList); - - // Create a global object Rx - if (Pstream::master()) - { - // Determine the row size of the global Rx - label mRowSum = 0; - forAll(RxList, i) - { - mRowSum += RxList[i].m(); - } - - Rx.resize(mRowSum, Rx.n()); - - Log<< tab << "# " << name() - << ": Populating the global Rx #" << endl; - label m = 0; - for (const int i : Pstream::allProcs()) - { - const label mRows = RxList[i].m(); - - Rx.subMatrix(m, 0, mRows) = RxList[i]; - - m += mRows; - } - - Log<< tab << "# " << name() - << ": Computing the parallel-direct tall-skinny QR decomp. #" - << endl; - QRMatrix<RMatrix> QRM - ( - Rx, - QRMatrix<RMatrix>::outputTypes::REDUCED_R, - QRMatrix<RMatrix>::storeMethods::IN_PLACE, - QRMatrix<RMatrix>::colPivoting::FALSE - ); - Rx.round(); - - Log<< tab << "# " << name() - << ": Computing Moore-Penrose pseudo-inverse of Rx #" - << endl; - RxInv_ = pinv(Rx); - - Log<< tab << "# " << name() << ": Computing Gx #" << endl; - const RMatrix Gx(Rx*(Gz_^Rx)); - - Log<< tab << "# " << name() - << ": Computing Moore-Penrose pseudo-inverse of Gx #" - << endl; - const RMatrix GxInv(pinv(Gx)); - - Log<< tab << "# " << name() << ": Computing A1 #" << endl; - A1 = RxInv_*GxInv; - } - Pstream::scatter(RxInv_); - Pstream::scatter(A1); - - Log<< tab << "# " << name() << ": Computing A2 #" << endl; - SMatrix A2(QzUH_ & QzUH_); - reduce(A2, sumOp<SMatrix>()); - - Log<< tab << "# " << name() << ": Computing A3 #" << endl; - SMatrix A3(QzUH_ & QzLH_); - reduce(A3, sumOp<SMatrix>()); - - Log<< tab << "# " << name() << ": Computing Ap #" << endl; - // by optimized matrix chain multiplication - // obtained by dynamic programming memoisation - Ap_ = SMatrix(RxInv_ & ((A3*(Gz_*A2))*A1)); - } - else - { - { - Log<< tab << "# " << name() - << ": Computing Moore-Penrose pseudo-inverse of Rx #" - << endl; - RxInv_ = pinv(Rx); - - Log<< tab << "# " << name() << ": Computing Gx #" << endl; - const RMatrix Gx(Rx*(Gz_^Rx)); - - Log<< tab << "# " << name() - << ": Computing Moore-Penrose pseudo-inverse of Gx #" - << endl; - const RMatrix GxInv(pinv(Gx)); - - Log<< tab << "# " << name() << ": Computing A1 #" << endl; - A1 = RxInv_*GxInv; - } - - Log<< tab << "# " << name() << ": Computing A2 #" << endl; - SMatrix A2(QzUH_ & QzUH_); - - Log<< tab << "# " << name() << ": Computing A3 #" << endl; - SMatrix A3(QzUH_ & QzLH_); - - Log<< tab << "# " << name() << ": Computing Ap #" << endl; - // by optimized matrix chain multiplication - // obtained by dynamic programming memoisation - Ap_ = SMatrix(RxInv_ & ((A3*(Gz_*A2))*A1)); - } -} - - -void Foam::functionObjects::STDMD::calcEigen() -{ - Log<< tab << "# " << name() << ": Computing eigendecomposition #" << endl; - - // Compute eigenvalues, and clip eigenvalues with values < minMagEVal_ - if (Pstream::master()) - { - // Replace Ap by eigenvalues (in-place eigendecomposition) - const EigenMatrix<scalar> EM(Ap_); - const DiagonalMatrix<scalar>& evalsRe = EM.EValsRe(); - const DiagonalMatrix<scalar>& evalsIm = EM.EValsIm(); - - oEVals_.resize(evalsRe.size()); - oEVecs_ = RectangularMatrix<complex>(EM.complexEVecs()); - - // Convert scalar eigenvalue pairs to complex eigenvalues - label i = 0; - for (auto& eval : oEVals_) - { - eval = complex(evalsRe[i], evalsIm[i]); - ++i; - } - - if (testEigen_) - { - testEigenvalues(Ap_, evalsRe); - testEigenvectors(Ap_, oEVals_, oEVecs_); - } - } -} - - -bool Foam::functionObjects::STDMD::close -( - const scalar s1, - const scalar s2, - const scalar absTol, - const scalar relTol -) const -{ - if (s1 == s2) return true; - - return (mag(s1 - s2) <= max(relTol*max(mag(s1), mag(s2)), absTol)); -} - - -void Foam::functionObjects::STDMD::testEigenvalues -( - const SquareMatrix<scalar>& A, - const DiagonalMatrix<scalar>& EValsRe -) const -{ - const scalar trace = A.trace(); - // Imaginary part of complex conjugates cancel each other - const scalar EValsSum = sum(EValsRe); - - const bool clse = close(EValsSum - trace, 0, absTol_, relTol_); - - if (clse) - { - Info<< tab - << " ## PASS: Eigenvalues ##" << endl; - } - else - { - Info<< tab - << " ## INCONCLUSIVE: Eigenvalues ##" << nl - << " # sum(eigenvalues) = " << EValsSum << nl - << " # trace(A) = " << trace << nl - << " # sum(eigenvalues) - trace(A) ~ 0 ?= " - << EValsSum - trace << nl - << " #######################" - << endl; - - if (dumpEigen_) - { - Info<< tab - << " ## Operands ##" << nl - << " # eigenvalues:" << nl << EValsRe << nl - << " # input matrix A:" << nl << A << nl - << " ##############" - << endl; - } - } -} - - -void Foam::functionObjects::STDMD::testEigenvectors -( - const SquareMatrix<scalar>& A, - const DiagonalMatrix<scalar>& EValsRe, - const SquareMatrix<scalar>& EVecs -) const -{ - unsigned nInconclusive = 0; - - for (label i = 0; i < A.n(); ++i) - { - const RectangularMatrix<scalar> EVec(EVecs.subColumn(i)); - const scalar EVal = EValsRe[i]; - const RectangularMatrix<scalar> leftSide(A*EVec); - const RectangularMatrix<scalar> rightSide(EVal*EVec); - const scalar rslt = (leftSide - rightSide).norm(); - - const bool clse = close(rslt, 0, absTol_, relTol_); - - if (!clse) - { - Info<< tab - << " ## INCONCLUSIVE: Eigenvector ##" << nl - << " # (A & EVec - EVal*EVec).norm() ~ 0 ?= " << rslt << nl - << " ##################################" - << endl; - - if (dumpEigen_) - { - Info<< tab - << " ## Operands ##" << nl - << " # eigenvalue:" << nl << EVal << nl - << " # input matrix A:" << nl << A << nl - << " # eigenvector:" << nl << EVec << nl - << " # (A & EVec):" << nl << leftSide << nl - << " # (EVal*EVec):" << nl << rightSide << nl - << " ##############" - << endl; - } - - ++nInconclusive; - } - } - - if (!nInconclusive) - { - Info<< tab - << " ## PASS: Eigenvectors ##" << endl; - } -} - - -void Foam::functionObjects::STDMD::testEigenvectors -( - const SquareMatrix<scalar>& A, - const List<complex>& EVals, - const RectangularMatrix<complex>& EVecs -) const -{ - SquareMatrix<complex> B(A.m()); - auto convertToComplex = [&](const scalar& val) { return complex(val); }; - std::transform - ( - A.cbegin(), - A.cend(), - B.begin(), - convertToComplex - ); - - unsigned nInconclusive = 0; - - for (label i = 0; i < B.n(); ++i) - { - const RectangularMatrix<complex> EVec(EVecs.subColumn(i)); - const complex EVal = EVals[i]; - const RectangularMatrix<complex> leftSide(B*EVec); - const RectangularMatrix<complex> rightSide(EVal*EVec); - const scalar rslt = (leftSide - rightSide).norm(); - - const bool clse = close(rslt, 0, absTol_, relTol_); - - if (!clse) - { - Info<< tab - << " ## INCONCLUSIVE: Eigenvector ##" << nl - << " # (A & EVec - EVal*EVec).norm() ~ 0 ?= " << rslt << nl - << " ##################################" - << endl; - - if (dumpEigen_) - { - Info<< tab - << " ## Operands ##" << nl - << " # eigenvalue:" << nl << EVal << nl - << " # input matrix A:" << nl << A << nl - << " # eigenvector:" << nl << EVec << nl - << " # (A & EVec):" << nl << leftSide << nl - << " # (EVal*EVec):" << nl << rightSide << nl - << " ##############" - << endl; - } - - ++nInconclusive; - } - } - - if (!nInconclusive) - { - Info<< tab - << " ## PASS: Eigenvectors ##" << endl; - } -} - - -void Foam::functionObjects::STDMD::filterEVals() -{ - Log<< tab << "# " << name() << ": Filtering eigenvalues #" << endl; - - if (Pstream::master()) - { - List<complex> cpEVals(oEVals_.size()); - auto it = - std::copy_if - ( - oEVals_.cbegin(), - oEVals_.cend(), - cpEVals.begin(), - [&](const complex& x){ return minMagEVal_ < mag(x); } - ); - cpEVals.resize(std::distance(cpEVals.begin(), it)); - - if (cpEVals.size() == 0) - { - WarningInFunction - << "No eigenvalue with mag(eigenvalue) larger than " - << "minMagEVal_ = " << minMagEVal_ << " was found." - << endl; - } - else - { - oEVals_ = cpEVals; - } - } - Pstream::scatter(oEVals_); - Pstream::scatter(oEVecs_); -} - - -void Foam::functionObjects::STDMD::calcFreqs() -{ - Log<< tab << "# " << name() << ": Computing frequencies #" << endl; - - if (Pstream::master()) - { - oFreqs_.resize(oEVals_.size()); - - // Frequency equation (K:Eq. 81) - auto fEq = - [&](const complex& EVal) - { - return Foam::log(max(EVal, complex(SMALL))).imag()/(twoPi*dt_); - }; - - // Compute frequencies - std::transform(oEVals_.cbegin(), oEVals_.cend(), oFreqs_.begin(), fEq); - } -} - - -void Foam::functionObjects::STDMD::calcFreqI() -{ - Log<< tab << "# " << name() << ": Computing frequency indices #" << endl; - - if (Pstream::master()) - { - // Initialise iterator by the first search - auto margin = [&](const scalar& x){ return (fMin_ <= x && x < fMax_); }; - - auto it = std::find_if(oFreqs_.cbegin(), oFreqs_.cend(), margin); - - while (it != oFreqs_.end()) - { - iFreqs_.append(std::distance(oFreqs_.cbegin(), it)); - it = std::find_if(std::next(it), oFreqs_.cend(), margin); - } - } - Pstream::scatter(oFreqs_); - Pstream::scatter(iFreqs_); -} - - -void Foam::functionObjects::STDMD::calcAmps() -{ - Log<< tab << "# " << name() << ": Computing amplitudes #" << endl; - - RMatrix temp((RxInv_.T()^QzUH_)*X1_); - reduce(temp, sumOp<RMatrix>()); - - if (Pstream::master()) - { - oAmps_.resize(temp.m()); - const RCMatrix pinvEVecs(pinv(oEVecs_)); - - // oAmps_ = pinvEVecs*temp; - for (label i = 0; i < oAmps_.size(); ++i) - { - for (label k = 0; k < temp.m(); ++k) - { - oAmps_[i] += pinvEVecs(i, k)*temp(k, 0); - } - } - } - Pstream::scatter(oAmps_); -} - - -void Foam::functionObjects::STDMD::calcMags() -{ - Log<< tab << "# " << name() << ": Computing magnitudes #" << endl; - - if (Pstream::master()) - { - oMags_.resize(oAmps_.size()); - - Log<< tab << "# " << name() << ": Sorting modes with "; - - switch (modeSorter_) - { - case modeSorterType::FIRST_SNAPSHOT: - { - Log<< "method of first snapshot #" << endl; - - std::transform - ( - oAmps_.cbegin(), - oAmps_.cend(), - oMags_.begin(), - [&](const complex& val){ return mag(val); } - ); - break; - } - - case modeSorterType::KIEWAT: - { - Log<< "modified weighted amplitude scaling method #" << endl; - - // Eigendecomposition returns eigenvectors with - // the unity norm, hence modeNorm = 1 - const scalar modeNorm = 1; - const scalar pr = 1; - List<scalar> w(currIndex_); - std::iota(w.begin(), w.end(), 1); - w = sin(twoPi/currIndex_*(w - 1 - 0.25*currIndex_))*pr + pr; - - label i = 0; - for (const auto& amp : oAmps_) - { - oMags_[i] = sorter(w, amp, oEVals_[i], modeNorm); - ++i; - } - break; - } - - case modeSorterType::KOU_ZHANG: - { - Log<< "weighted amplitude scaling method #" << endl; - - const scalar modeNorm = 1; - const List<scalar> w(currIndex_, 1.0); - label i = 0; - for (const auto& amp : oAmps_) - { - oMags_[i] = sorter(w, amp, oEVals_[i], modeNorm); - ++i; - } - break; - } - - default: - break; - } - } -} - - -void Foam::functionObjects::STDMD::calcMagI() -{ - Log<< tab << "# " << name() << ": Computing magnitude indices #" << endl; - - if (Pstream::master()) - { - iMags_ = iFreqs_; - - auto descend = - [&](const label i1, const label i2) - { - return !(oMags_[i1] < oMags_[i2]); - }; - - std::sort(iMags_.begin(), iMags_.end(), descend); - } - Pstream::scatter(oMags_); - Pstream::scatter(iMags_); -} - - -void Foam::functionObjects::STDMD::calcModes() -{ - Log<< tab << "# " << name() << ": Computing modes #" << endl; - - // Resize the number of output variables to nModes_, if need be - if (nModes_ < iMags_.size()) - { - iMags_.resize(nModes_); - } - - // Compute and write out modes one by one - label oModeI = 0; - const RMatrix temp(QzUH_*RxInv_); - - for (const auto& i : iMags_) - { - List<complex> mode(temp.m(), Zero); - - // mode = temp*oEVecs_.subColumn(i); - for (label p = 0; p < mode.size(); ++p) - { - for (label q = 0; q < oEVecs_.m(); ++q) - { - mode[p] += temp(p, q)*oEVecs_(q, i); - } - } - - volVectorField modeReal - ( - IOobject - ( - "modeReal" + std::to_string(oModeI) + fieldName_ + "_" + name(), - time_.timeName(), - mesh_, - IOobject::NO_READ, - IOobject::NO_WRITE - ), - mesh_, - dimensionedVector(dimless, Zero) - ); - - volVectorField modeImag - ( - IOobject - ( - "modeImag" + std::to_string(oModeI) + fieldName_ + "_" + name(), - time_.timeName(), - mesh_, - IOobject::NO_READ, - IOobject::NO_WRITE - ), - mesh_, - dimensionedVector(dimless, Zero) - ); - - label s = 0; - for (label k = 0; k < nComps_; ++k) - { - for (label j = 0; j < modeReal.size(); ++j) - { - const complex& m = mode[s]; - modeReal[j].replace(k, m.real()); - modeImag[j].replace(k, m.imag()); - ++s; - } - } - - modeReal.write(); - modeImag.write(); - - ++oModeI; - } -} - - -Foam::scalar Foam::functionObjects::STDMD::sorter -( - const List<scalar>& weight, - const complex& amplitude, - const complex& eval, - const scalar modeNorm -) const -{ - // Omit eigenvalues with very large or very small magnitudes - if (!(mag(eval) < GREAT && mag(eval) > VSMALL)) - { - Info<< " Returning zero magnitude for mag(eval) = " << mag(eval) - << endl; - - return 0.0; - } - - // Omit eigenvalue-STDMD step combinations that pose a risk of overflow - if (mag(eval)*currIndex_ > sortLimiter_) - { - Info<< " Returning zero magnitude for" - << " mag(eval) = " << mag(eval) - << " currIndex = " << currIndex_ - << " sortLimiter = " << sortLimiter_ - << endl; - - return 0.0; - } - - scalar magnitude = 0; - - for (label j = 0; j < currIndex_; ++j) - { - magnitude += weight[j]*modeNorm*mag(amplitude*pow(eval, j + 1)); - } - - return magnitude; -} - - -void Foam::functionObjects::STDMD::writeFileHeader(Ostream& os) const -{ - writeHeader(os, "STDMD output"); - writeCommented(os, "Frequency"); - writeTabbed(os, "Magnitude"); - writeTabbed(os, "Amplitude (real)"); - writeTabbed(os, "Amplitude (imag)"); - writeTabbed(os, "Eigenvalue (real)"); - writeTabbed(os, "Eigenvalue (imag)"); - os << endl; -} - - -void Foam::functionObjects::STDMD::filterOutput() -{ - Log<< tab << "# " << name() << ": Filtering text output #" << endl; - - if (Pstream::master()) - { - // Filter objects according to iMags - filterIndexed(oEVals_, iMags_); - filterIndexed(oEVecs_, iMags_); - filterIndexed(oFreqs_, iMags_); - filterIndexed(oAmps_, iMags_); - filterIndexed(oMags_, iMags_); - - // Clip objects if need be (assuming objects have the same len) - if (nModes_ < oFreqs_.size()) - { - oEVals_.resize(nModes_); - oEVecs_.resize(oEVecs_.m(), nModes_); - oFreqs_.resize(nModes_); - oAmps_.resize(nModes_); - oMags_.resize(nModes_); - } - } -} - - -void Foam::functionObjects::STDMD::writeOutput(OFstream& os) const -{ - Log<< tab << "# " << name() << ": Writing text output #" << endl; - - writeFileHeader(os); - - for (const label i : labelRange(oFreqs_.size())) - { - os << oFreqs_[i] << tab - << oMags_[i] << tab - << oAmps_[i].real() << tab - << oAmps_[i].imag() << tab - << oEVals_[i].real() << tab - << oEVals_[i].imag() << endl; - } -} - - -void Foam::functionObjects::STDMD::calcOutput() -{ - Log<< tab << "# " << name() << ":" - << " Starts output processing for field = " << fieldName_ << " #" - << endl; - - // Move upper and lower halves of Qz_ to new containers - QzUH_ = Qz_.subMatrix(0, 0, nSnap_); - QzLH_ = Qz_.subMatrix(nSnap_, 0, nSnap_); - Qz_.clear(); - - calcAp(); - - QzLH_.clear(); - Gz_.clear(); - - calcEigen(); - - filterEVals(); - - Ap_.clear(); - - calcFreqs(); - - calcFreqI(); - - calcAmps(); - - calcMags(); - - calcMagI(); - - calcModes(); - - // Write out unfiltered data - if (Pstream::master() && writeToFile_) - { - autoPtr<OFstream> osPtr = - createFile("uSTDMD" + fieldName_, mesh_.time().timeOutputValue()); - OFstream& os = osPtr.ref(); - - writeOutput(os); - } - - filterOutput(); - - // Write out filtered data - if (Pstream::master() && writeToFile_) - { - autoPtr<OFstream> osPtr = - createFile("STDMD" + fieldName_, mesh_.time().timeOutputValue()); - OFstream& os = osPtr.ref(); - - writeOutput(os); - } - - Log<< tab << "# " << name() << ":" - << " Ends output processing for field = " << fieldName_ << " #" - << endl; -} - - -// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // - -Foam::functionObjects::STDMD::STDMD -( - const word& name, - const Time& runTime, - const dictionary& dict -) -: - fvMeshFunctionObject(name, runTime, dict), - writeFile(mesh_, name, typeName, dict, false), - modeSorter_ - ( - modeSorterTypeNames.getOrDefault - ( - "modeSorter", - dict, - modeSorterType::FIRST_SNAPSHOT - ) - ), - fieldName_(dict.get<word>("field")), - initialised_(false), - testEigen_(false), - dumpEigen_(false), - nModes_ - ( - dict.getCheckOrDefault<label> - ( - "nModes", - pTraits<label>::max, - labelMinMax::ge(1) - ) - ), - maxRank_ - ( - dict.getCheckOrDefault<label> - ( - "maxRank", - pTraits<label>::max, - labelMinMax::ge(1) - ) - ), - nGramSchmidt_ - ( - dict.getCheckOrDefault<label> - ( - "nGramSchmidt", - 5, - labelMinMax::ge(1) - ) - ), - fMin_ - ( - dict.getCheckOrDefault<label> - ( - "fMin", - 0, - labelMinMax::ge(0) - ) - ), - fMax_ - ( - dict.getCheckOrDefault<label> - ( - "fMax", - pTraits<label>::max, - labelMinMax::ge(fMin_ + 1) - ) - ), - nComps_(Zero), - nSnap_(Zero), - currIndex_(Zero), - sortLimiter_(500), - absTol_(1e-2), - relTol_(1e-3), - minBasis_(Zero), - dt_(Zero), - minMagEVal_(Zero), - zNorm_(Zero), - ezNorm_(Zero), - z_(), - ez_(), - X1_(), - Qz_(), - Gz_(), - QzUH_(), - QzLH_(), - RxInv_(), - Ap_(), - oEVecs_(), - oEVals_(), - oAmps_(), - oFreqs_(), - iFreqs_(), - oMags_(), - iMags_() -{ - Info<< nl - << " ### This STDMD release is the beta release ###" << nl - << " Therefore small-to-medium changes in input/output interfaces " - << "and internal structures should be expected in the next versions." - << endl; - - // Check if varying or fixed time-step computation - if (runTime.isAdjustTimeStep()) - { - WarningInFunction - << " # STDMD: Viable only for fixed time-step computations. #" - << endl; - } - - // Check if mesh topology changing - if (mesh_.topoChanging()) - { - FatalErrorInFunction - << " # STDMD: Available only for non-changing mesh topology. #" - << abort(FatalError); - } - - read(dict); -} - - -// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // - -bool Foam::functionObjects::STDMD::read(const dictionary& dict) -{ - fvMeshFunctionObject::read(dict); - - testEigen_ = dict.getOrDefault<bool>("testEigen", false); - - if (testEigen_) - { - dumpEigen_ = dict.getOrDefault<bool>("dumpEigen", false); - } - - sortLimiter_ = - dict.getCheckOrDefault<scalar> - ( - "sortLimiter", - 500, - scalarMinMax::ge(1) - ); - - absTol_ = - dict.getCheckOrDefault<scalar> - ( - "absTol", - 1e-2, - scalarMinMax::ge(0) - ); - - relTol_ = - dict.getCheckOrDefault<scalar> - ( - "relTol", - 1e-3, - scalarMinMax::ge(0) - ); - - minBasis_ = - dict.getCheckOrDefault<scalar> - ( - "minBasis", - 1e-8, - scalarMinMax::ge(0) - ); - - dt_ = - dict.getCheckOrDefault<scalar> - ( - "stdmdInterval", - ( - dict.getCheck<label>("executeInterval", labelMinMax::ge(1)) - *mesh_.time().deltaT().value() - ), - scalarMinMax::ge(0) - ); - - minMagEVal_ = - dict.getCheckOrDefault<scalar> - ( - "minEVal", - 1e-8, - scalarMinMax::ge(0) - ); - - writeToFile_ = dict.getOrDefault<bool>("writeToFile", true); - - Info<< nl << name() << ":" << nl - << tab << "# Input is read for field = " - << fieldName_ << " #" << nl << endl; - - return true; -} - - -bool Foam::functionObjects::STDMD::execute() -{ - Log << type() << " " << name() << " execute:" << endl; - - // STDMD incremental orthonormal basis update (K:Fig. 15) - snapshot(); - - if (currIndex_ == 1) - { - initBasis(); - } - - if (1 < currIndex_) - { - GramSchmidt(); - - // Check basis for z_ and, if necessary, expand Qz_ and Gz_ - zNorm_ = parnorm(z_); - ezNorm_ = parnorm(ez_); - - if (minBasis_ < ezNorm_/zNorm_) - { - expandBasis(); - } - - updateGz(); - - // Compress the orthonormal basis if required - if (maxRank_ < Qz_.n()) - { - compressBasis(); - } - } - ++currIndex_; - - Log<< tab << "# " << name() << ":" - << " Execution index = " << currIndex_ - << " for field = " << fieldName_ << " #" - << endl; - - return true; -} - - -bool Foam::functionObjects::STDMD::write() -{ - Log << type() << " " << name() << " write:" << endl; - - if (currIndex_ < 2) - { - WarningInFunction - << " # STDMD needs at least three snapshots to produce output #" - << nl - << " # Only " << currIndex_ + 1 << " snapshots are available #" - << nl - << " # Skipping STDMD output calculation and write #" - << endl; - - return false; - } - - // STDMD mode evaluation (K:Fig. 16) - calcOutput(); - - // Restart STDMD incremental orthonormal basis update - initialised_ = false; - - mesh_.time().printExecutionTime(Info); - - return true; -} - - -// ************************************************************************* // diff --git a/src/functionObjects/field/STDMD/STDMD.H b/src/functionObjects/field/STDMD/STDMD.H deleted file mode 100644 index 42d137b97062e718903944ccc7c529191048adb7..0000000000000000000000000000000000000000 --- a/src/functionObjects/field/STDMD/STDMD.H +++ /dev/null @@ -1,645 +0,0 @@ -/*---------------------------------------------------------------------------*\ - ========= | - \\ / F ield | OpenFOAM: The Open Source CFD Toolbox - \\ / O peration | - \\ / A nd | www.openfoam.com - \\/ M anipulation | -------------------------------------------------------------------------------- - Copyright (C) 2020 OpenCFD Ltd. -------------------------------------------------------------------------------- -License - This file is part of OpenFOAM. - - OpenFOAM is free software: you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - OpenFOAM is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - for more details. - - You should have received a copy of the GNU General Public License - along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. - -Class - Foam::functionObjects::STDMD - -Group - grpFieldFunctionObjects - -Description - (Beta Release) STDMD (i.e. Streaming Total Dynamic Mode Decomposition) is - a variant of a data-driven dimensionality reduction method. - - STDMD is being used as a mathematical post-processing tool to compute - a set of dominant modes out of a given flow (or dataset) each of which is - associated with a constant frequency and decay rate, so that dynamic - features of a given flow may become interpretable, and tractable. - Among other Dynamic Mode Decomposition (DMD) variants, STDMD is presumed - to provide the general DMD method capabilities alongside economised and - feasible memory and CPU usage. - - The code implementation corresponds to Figs. 15-16 of the first citation - below, more broadly to Section 2.4. - - References: - \verbatim - STDMD and mode-sorting algorithms (tags:K, HRDC, KZ, HWR): - Kiewat, M. (2019). - Streaming modal decomposition approaches for vehicle aerodynamics. - PhD thesis. Munich: Technical University of Munich. - URL:mediatum.ub.tum.de/doc/1482652/1482652.pdf - - Hemati, M. S., Rowley, C. W., - Deem, E. A., & Cattafesta, L. N. (2017). - De-biasing the dynamic mode decomposition - for applied Koopman spectral analysis of noisy datasets. - Theoretical and Computational Fluid Dynamics, 31(4), 349-368. - DOI:10.1007/s00162-017-0432-2 - - Kou, J., & Zhang, W. (2017). - An improved criterion to select - dominant modes from dynamic mode decomposition. - European Journal of Mechanics-B/Fluids, 62, 109-129. - DOI:10.1016/j.euromechflu.2016.11.015 - - Hemati, M. S., Williams, M. O., & Rowley, C. W. (2014). - Dynamic mode decomposition for large and streaming datasets. - Physics of Fluids, 26(11), 111701. - DOI:10.1063/1.4901016 - - Parallel classical Gram-Schmidt process (tag:Ka): - Katagiri, T. (2003). - Performance evaluation of parallel - Gram-Schmidt re-orthogonalization methods. - In: Palma J. M. L. M., Sousa A. A., Dongarra J., Hernández V. (eds) - High Performance Computing for Computational Science — VECPAR 2002. - Lecture Notes in Computer Science, vol 2565, p. 302-314. - Berlin, Heidelberg: Springer. - DOI:10.1007/3-540-36569-9_19 - - Parallel direct tall-skinny QR decomposition (tags:BGD, DGHL): - Benson, A. R., Gleich, D. F., & Demmel, J. (2013). - Direct QR factorizations for - tall-and-skinny matrices in MapReduce architectures. - 2013 IEEE International Conference on Big Data. - DOI:10.1109/bigdata.2013.6691583 - - Demmel, J., Grigori, L., Hoemmen, M., & Langou, J. (2012). - Communication-optimal parallel - and sequential QR and LU factorizations. - SIAM Journal on Scientific Computing, 34(1), A206-A239. - DOI:10.1137/080731992 - - DMD properties: - Brunton S. L. (2018). - Dynamic mode decomposition overview. - Seattle, Washington: University of Washington. - youtu.be/sQvrK8AGCAo (Retrieved:24-04-20) - \endverbatim - - Operands: - \table - Operand | Type | Location - input | {vol,surface}\<Type\>Field <!-- - --> | $FOAM_CASE/\<time\>/\<inpField\> - output file | dat <!-- - --> | $FOAM_CASE/postProcessing/\<FO\>/\<time\>/\<file\>(s) - output field | volScalarField(s) | $FOAM_CASE/\<time\>/\<outField\>(s) - \endtable - - where \c \<Type\>=Scalar/Vector/SphericalTensor/SymmTensor/Tensor. - - Output fields: - \verbatim - modeReal<modeIndex><field> | Real part of a mode field - modeImag<modeIndex><field> | Imaginary part of a mode field - \endverbatim - - Output files: - \verbatim - uSTDMD.dat | Unfiltered STDMD output - STDMD.dat | Filtered STDMD output - \endverbatim - - wherein for each mode, the following quantities are output into files: - \verbatim - Frequency - Magnitude - Amplitude (real part) - Amplitude (imaginary part) - Eigenvalue (real part) - Eigenvalue (imaginary part) - \endverbatim - -Note - Operations on boundary fields, e.g. \c wallShearStress, are currently not - available. - -Usage - Minimal example by using \c system/controlDict.functions: - \verbatim - STDMD1 - { - // Mandatory entries (unmodifiable) - type STDMD; - libs (fieldFunctionObjects); - field <inpField>; - - // Conditional mandatory entries (unmodifiable) - // either of the below - stdmdInterval 5.5; - executeInterval 10; - - // Optional entries (unmodifiable) - modeSorter kiewat; - nModes 50; - maxRank 50; - nGramSchmidt 5; - fMin 0; - fMax 1000000000; - - // Optional entries (run-time modifiable, yet not recommended) - testEigen false; - dumpEigen false; - minBasis 0.00000001; - minEVal 0.00000001; - absTol 0.001; - relTol 0.0001; - sortLimiter 500; - - // Optional (inherited) entries - ... - } - \endverbatim - - where the entries mean: - \table - Property | Description | Type | Req'd | Dflt - type | Type name: STDMD | word | yes | - - libs | Library name: fieldFunctionObjects | word | yes | - - field | Name of the operand field | word | yes | - - stdmdInterval | STDMD time-step size [s] | scalar| conditional | <!-- - --> executeInterval*(current time-step of the simulation) - modeSorter | Mode-sorting algorithm variant | word | no | firstSnap - nModes | Number of output modes in input freq range | label | no | GREAT - maxRank | Max columns in accumulated matrices | label | no | GREAT - nGramSchmidt | Number of Gram-Schmidt iterations | label | no | 5 - fMin | Min (non-negative) output frequency | label | no | 0 - fMax | Max output frequency | label | no | GREAT - testEigen | Flag to verify eigendecompositions | bool | no | false - dumpEigen | Flag to log operands of eigendecomps | bool | no | false - minBasis | Orthogonal basis expansion threshold | scalar| no | 1e-8 - minEVal | Min EVal for below EVals are omitted | scalar| no | 1e-8 - absTol | Min abs tol in eigendecomposition tests | scalar| no | 1e-4 - relTol | Relative tol in eigendecomposition tests | scalar| no | 1e-6 - sortLimiter | Maximum allowable magnitude for <!-- - --> mag(eigenvalue)*(number of STDMD steps) to be used in <!-- - --> modeSorter={kiewat,kouZhang} to avoid overflow errors <!-- - --> | scalar | no | 500.0 - \endtable - - Options for the \c modeSorter entry: - \verbatim - kiewat | Modified weighted amplitude scaling method - kouZhang | Weighted amplitude scaling method - firstSnap | Method of first snapshot amplitude magnitude - \endverbatim - - The inherited entries are elaborated in: - - \link functionObject.H \endlink - - \link writeFile.H \endlink - - Usage by \c postProcess utility is not available. - -Note - - To specify the STDMD time-step size (not necessarily equal to the - time step of the simulation), entries of either \c stdmdInterval or - \c executeInterval must be available (highest to lowest precedence). While - \c stdmdInterval allows users to directly specify the STDMD time-step size - in seconds, in absence of \c stdmdInterval, for convenience, - \c executeInterval allows users to compute the STDMD time-step internally - by multiplying itself with the current time-step size of the simulation. - - Limitation: Restart is currently not available since intermediate writing - of STDMD matrices are not supported. - - Limitation: Non-physical input (e.g. full-zero fields) may upset STDMD. - - Warning: DMD is an active research area at the time of writing; therefore, - there would be cases whereat oddities might be encountered. - - Warning: This STDMD release is the \b beta release; therefore, - small-to-medium changes in input/output interfaces and internal structures - should be expected in the next versions. - - Warning: In \c modeSorter algorithms of \c kiewat and \c kouZhang, there - exists a function of \f$power(x,y)\f$ where \c x is the magnitude of an - eigenvalue, and \c y is the number of STDMD snapshots. This function poses - a risk of overflow errors since, for example, if \c x ends up above 1.5 with - 500 snapshots or more, this function automatically throws the floating point - error meaning overflow. Therefore, the domain-range of this function is - heuristically constrained by the optional entry \c sortLimiter which skips - the evaluation of this function for a given - mag(eigenvalue)*(no. of STDMD steps), i.e. x*y, whose magnitude is larger - than \c sortLimiter. - -See also - - Foam::functionObjects::fvMeshFunctionObject - - Foam::functionObjects::writeFile - - ExtendedCodeGuide::functionObjects::field::STDMD - -SourceFiles - STDMD.C - STDMDTemplates.C - -\*---------------------------------------------------------------------------*/ - -#ifndef functionObjects_STDMD_H -#define functionObjects_STDMD_H - -#include "fvMeshFunctionObject.H" -#include "writeFile.H" -#include "EigenMatrix.H" -#include "QRMatrix.H" - -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -namespace Foam -{ -namespace functionObjects -{ - -/*---------------------------------------------------------------------------*\ - Class STDMD Declaration -\*---------------------------------------------------------------------------*/ - -class STDMD -: - public fvMeshFunctionObject, - public writeFile -{ - - typedef SquareMatrix<scalar> SMatrix; - typedef SquareMatrix<complex> SCMatrix; - typedef RectangularMatrix<scalar> RMatrix; - typedef RectangularMatrix<complex> RCMatrix; - - - // Private Enumerations - - //- Options for the mode-sorting algorithm - enum modeSorterType - { - KIEWAT, //!< "Modified weighted amplitude scaling method" - KOU_ZHANG, //!< "Weighted amplitude scaling method" - FIRST_SNAPSHOT //!< "Method of first snapshot amplitude magnitude" - }; - - //- Names for modeSorterType - static const Enum<modeSorterType> modeSorterTypeNames; - - - // Private Data - - //- Mode-sorting algorithm (default=modeSorterType::FIRST_SNAPSHOT) - const enum modeSorterType modeSorter_; - - //- Name of the operand volume or surface field - const word fieldName_; - - //- Flag: First execution-step initialisation - bool initialised_; - - //- Flag: Verify eigendecompositions by using theoretical relations - bool testEigen_; - - //- Flag: Write operands of eigendecompositions to log - // To activate it, testEigen=true - bool dumpEigen_; - - //- Number of output modes within input frequency range - //- starting from the most energetic mode - const label nModes_; - - //- Maximum allowable matrix column for Qz_ and Gz_ - // Qz_ is assumed to always have full-rank, thus Qz_.n() = rank - const label maxRank_; - - //- Number of maximum iterations of the classical Gram-Schmidt procedure - const label nGramSchmidt_; - - //- Min frequency: Output only entries corresponding to freqs >= fMin - const label fMin_; - - //- Max frequency: Output only entries corresponding to freqs <= fMax - const label fMax_; - - //- Number of base components of input field, e.g. 3 for vector - label nComps_; - - //- Number of elements in a snapshot - // A snapshot is an input dataset to be processed per execution-step - label nSnap_; - - //- Current execution-step index of STDMD, - //- not necessarily that of the simulation - label currIndex_; - - //- Maximum allowable magnitude for mag(eigenvalue)*(number of - //- STDMD steps) to be used in modeSorter={kiewat,kouZhang} to avoid - //- overflow errors - scalar sortLimiter_; - - //- Min absolute tolerance being used in eigendecomposition tests - scalar absTol_; - - //- Relative tolerance being used in eigendecomposition tests - scalar relTol_; - - //- Min value to execute orthogonal basis expansion of Qz_ and Gz_ - scalar minBasis_; - - //- STDMD time-step size that equals to - //- (executeInterval of STDMD)*(deltaT of simulation) [s] - scalar dt_; - - //- Min EVal magnitude threshold below where EVals are omitted - scalar minMagEVal_; - - //- L2-norm of column vector z_ - scalar zNorm_; - - //- L2-norm of column vector ez_ - scalar ezNorm_; - - //- Augmented snapshot matrix (effectively a column vector) (K:Eq. 60) - // Upper half z_ = current-time snapshot slot - // Lower half z_ = previous-time snapshot slot - RMatrix z_; - - //- Working copy of the augmented snapshot matrix z_ - //- being used in the classical Gram-Schmidt process - RMatrix ez_; - - //- First-processed snapshot required by the mode-sorting - //- algorithms at the final output computations (K:p. 43) - RMatrix X1_; - - //- Accumulated-in-time unitary similarity matrix produced by the - //- orthonormal decomposition of the augmented snapshot matrix z_ - // (K:Eq. 60) - RMatrix Qz_; - - //- Accumulated-in-time (squared) upper triangular matrix produced by - //- the orthonormal decomposition of the augmented snapshot matrix z_ - // (K:Eq. 64) - SMatrix Gz_; - - //- Upper half of Qz_ - RMatrix QzUH_; - - //- Lower half of Qz_ - RMatrix QzLH_; - - //- Moore-Penrose pseudo-inverse of R produced by - //- the QR decomposition of the last time-step QzUH_ - RMatrix RxInv_; - - //- Projected STDMD operator (K:Eq. 78) - SMatrix Ap_; - - //- Output eigenvectors - RCMatrix oEVecs_; - - //- Output eigenvalues - List<complex> oEVals_; - - //- Output amplitudes - List<complex> oAmps_; - - //- Output (non-negative) frequencies - List<scalar> oFreqs_; - - //- Indices of oFreqs_ where freqs are - //- non-negative and within [fMin_, fMax_] - DynamicList<label> iFreqs_; - - //- Output (descending) magnitudes of (complex) amplitudes - List<scalar> oMags_; - - //- Indices of oMags_ - List<label> iMags_; - - - // Private Member Functions - - // Process - - //- Move previous-time snapshot into previous-time slot in z_ - //- and copy new current-time snapshot into current-time slot in z_ - template<class GeoFieldType> - bool getSnapshot(); - - //- Get the input field type to be processed by snapshot() - template<class Type> - bool getSnapshotType(); - - //- Get the number of base components of input field - template<class GeoFieldType> - bool getComps(); - - //- Return (parallel) L2-norm of a given column vector - scalar parnorm(const RMatrix& colVector) const; - - //- Move the current-time snapshot into the previous-time snapshot in z_ - //- and copy the new field into the current-time snapshot - void snapshot(); - - //- Initialise all working matrices at the first execution-step - void init(); - - //- Initialise Qz_, Gz_ (both require the first two snapshots) and X1_ - void initBasis(); - - //- Execute (parallel) classical Gram-Schmidt - //- process to orthonormalise ez_ (Ka:Fig. 5) - void GramSchmidt(); - - //- Expand orthonormal bases Qz_ and Gz_ by stacking a column - //- (ez_/ezNorm_) to Qz_, and a row (Zero) and column (Zero) - //- to Gz_ if (minBasis_ < ezNorm_/zNorm_) - void expandBasis(); - - //- Update Gz_ before the potential orthonormal basis compression - void updateGz(); - - //- Compress orthonormal basis for Qz_ and Gz_ if (maxRank_ < Qz_.n()) - void compressBasis(); - - - // Postprocess - - //- Return a new List containing elems of List at indices - template<class Type> - void filterIndexed - ( - List<Type>& lst, - const UList<label>& indices - ); - - //- Return a new Matrix containing columns of Matrix at indices - template<class MatrixType> - void filterIndexed - ( - MatrixType& lst, - const UList<label>& indices - ); - - //- Compute global Ap (K:Eq. 78) - void calcAp(); - - //- Compute eigenvalues and eigenvectors - void calcEigen(); - - //- Weak-type floating-point comparison - // bit.ly/2Trdbgs (Eq. 2), and PEP-485 - bool close - ( - const scalar s1, - const scalar s2, - const scalar absTol = 0, //<! comparisons near zero - const scalar relTol = 1e-8 //<! e.g. vals the same within 8 decimals - ) const; - - //- Test real/complex eigenvalues by using - //- the theoretical relation: (sum(eigenvalues) - trace(A) ~ 0) - void testEigenvalues - ( - const SquareMatrix<scalar>& A, - const DiagonalMatrix<scalar>& EValsRe - ) const; - - //- Test real eigenvectors by using the theoretical relation: - //- ((A & EVec - EVal*EVec).norm() ~ 0) - void testEigenvectors - ( - const SquareMatrix<scalar>& A, - const DiagonalMatrix<scalar>& EValsRe, - const SquareMatrix<scalar>& EVecs - ) const; - - //- Test complex eigenvectors by using the theoretical relation: - //- ((A & EVec - EVal*EVec).norm() ~ 0) - void testEigenvectors - ( - const SquareMatrix<scalar>& A, - const List<complex>& EVals, - const RectangularMatrix<complex>& EVecs - ) const; - - //- Remove any eigenvalues whose magnitude is smaller than - //- minMagEVal_ while keeping the order of elements the same - void filterEVals(); - - //- Compute and filter frequencies (K:Eq. 81) - void calcFreqs(); - - //- Compute frequency indices - // Locate indices where oFreqs_ are - // in [fMin_, fMax_], and store them in iFreqs_ indices - void calcFreqI(); - - //- Compute amplitudes - void calcAmps(); - - //- Compute magnitudes - // Includes advanced sorting methods using - // the chosen weighted amplitude scaling method - void calcMags(); - - //- Compute the ordered magnitude indices - void calcMagI(); - - //- Compute modes - void calcModes(); - - //- Eigenvalue weighted amplitude scaling (KZ:Eq. 33) - //- Modified eigenvalue weighted amplitude scaling (K) - scalar sorter - ( - const List<scalar>& weight, - const complex& amplitude, - const complex& eval, - const scalar modeNorm - ) const; - - //- Output file header information - virtual void writeFileHeader(Ostream& os) const; - - //- Filter objects according to iFreqs_ and iMags_ - void filterOutput(); - - //- Write unfiltered/filtered data - void writeOutput(OFstream& os) const; - - //- Compute STDMD output - void calcOutput(); - - -public: - - //- Runtime type information - TypeName("STDMD"); - - - // Constructors - - //- No default construct - STDMD() = delete; - - //- Construct from Time and dictionary - STDMD - ( - const word& name, - const Time& runTime, - const dictionary& dict - ); - - //- No copy construct - STDMD(const STDMD&) = delete; - - //- No copy assignment - void operator=(const STDMD&) = delete; - - - //- Destructor - virtual ~STDMD() = default; - - - // Member Functions - - //- Read STDMD settings - virtual bool read(const dictionary&); - - //- Execute STDMD - virtual bool execute(); - - //- Write STDMD output - virtual bool write(); -}; - - -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -} // End namespace functionObjects -} // End namespace Foam - -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -#ifdef NoRepository - #include "STDMDTemplates.C" -#endif - -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // - -#endif - -// ************************************************************************* //