Using python for proper orthogonal decomposition and dynamic mode decomposition (DMD)

Using python for proper orthogonal decomposition and dynamic mode decomposition (DMD)#

Partial Orthogonal Decomposition (POD) and Dynamic Mode Decomposition (DMD) are numerical techniques which are used to extract dominant features of dynamical systems from time-series data.

Reduced order models of the dynamical systems being studied can be obtained from both, and both can be thought of as extensions of the Singular Value Decomposition (SVD), so they are relatively similar tools. Their main differences are that DMD modes are invariant in time, while POD modes are not, POD modes are orthogonal, while DMD modes are not and, while POD always uses SVD, some popular applications of DMD use krylov decomposition.

So why would we want to use this techniques?

As mentioned before, turbulent flows are complex non-linear systems, which require special tools in order to analyze them profoundly. With both POD and DMD, meaningful structures within a scalar or vector field can be extracted. For instance, by performing a DMD on a vorticity field, the underlying oscillatory modes of the turbulence structure can be obtained and understood.

Let’s consider the simulation of a 2D rotor, performed by the tutorial of OpenFOAM 11.

image

First we import the need module. The package pyDMD used to analyze and extract coherent structures, and OpenFoam python parser, Ofpp, used to read results from the OpenFOAM case.

import numpy as np
try:
    from pydmd import MrDMD, DMD, BOPDMD
    from pydmd.plotter import plot_eigs, plot_summary
    from pydmd.preprocessing import hankel_preprocessing
    from pydmd.plotter import plot_modes_2D
except:
    !pip install pydmd
    from pydmd import MrDMD, DMD, BOPDMD
    from pydmd.plotter import plot_eigs, plot_summary
    from pydmd.preprocessing import hankel_preprocessing
    from pydmd.plotter import plot_modes_2D
try:
    import os
except:
    !pip install os
    import os
try:
    import Ofpp
except:
    !pip install Ofpp
    import Ofpp
import matplotlib.pyplot as plt
import scipy

The case path and the field are defined, and the list of time directories is stored

filepath="./OF11/rotor2D/"
field="U"
## Buscar timesteps del cas, eliminant el 0 perque son condicions inicials ##
TstepDirList=[]
for directory in os.listdir(filepath): # iterate 
    if (directory[0].isnumeric()):
        if("." in directory):
            TstepDirList.append(float(directory))
        else:
            TstepDirList.append(int(directory))
TstepDirList.remove(0)
TstepDirList.sort()
nTsteps=len(TstepDirList)

The field is read for eaach time step and it is stored in an array \(U\) of \(n\) rows and \(T\) columns, where \(n\) is the number of cells of the simulation and \(T\) is the number of time snapshots.

#initial field
U = np.linalg.norm(
                  Ofpp.parse_internal_field('{}/{}/{}'.format(filepath,TstepDirList[0],field)),
                  axis=1)
#We append the next timesteps
for currTime in TstepDirList[1:]:
    U = np.c_[U,
              np.linalg.norm(
                  Ofpp.parse_internal_field('{}/{}/{}'.format(filepath,currTime,field)),
                  axis=1)
             ]

First the SVD (Singular Value Decomposition) can be computed with scipy.

fig = plt.plot(
    scipy.linalg.svdvals(
        np.array([snapshot.flatten() for snapshot in U]).T
    ),
    "o",
)
../_images/f1f7ec493e3b9839935bd73335bbac25920aed005e4b845997feb17c19f1f859.png

A Multi Resolution DMD is performed, based on a standard DMD, where no truncation of SVD is applied.

The funcion fit is the one that actually performs the DMD.

sub_dmd = DMD(svd_rank=-1,tlsq_rank=2, exact=True, opt=True)
dmd = MrDMD(sub_dmd, max_level=4, max_cycles=1)
dmd.fit(U)
/home/robert/.config/jupyterlab-desktop/jlab_server/lib/python3.8/site-packages/pydmd/mrdmd.py:490: ComplexWarning: Casting complex values to real discards the imaginary part
  newX = np.hstack(
<pydmd.mrdmd.MrDMD at 0x7f4bed401490>

The modes can be now reconstructed as OpenFOAM cases.

header = '''/*--------------------------------*- C++ -*----------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     | Website:  https://openfoam.org
    \\  /    A nd           | Version:  11
     \\/     M anipulation  |
\*---------------------------------------------------------------------------*/
FoamFile
{
    format      ascii;
    class       volScalarField;
    object      U;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

dimensions      [0 1 -1 0 0 0 0];

internalField   nonuniform List<scalar> 
'''
BC = ''' 
boundaryField
{
    rotor
    {
        type            movingWallVelocity;
        value           uniform 0;
    }
        freestream
    {
        type            freestreamVelocity;
        freestreamValue uniform (1 0 0);
        value           uniform 0;
    }
    frontAndBack
    {
        type            empty;
    }
}    
'''
for i in range(4):
    if i==0:
        pdata = dmd.partial_reconstructed_data(level=0)
    else:
        pdata += dmd.partial_reconstructed_data(level=i)
    i1=0
    while i1<len(TstepDirList):
        i2=0
        try:
            os.makedirs('{caseDir}DMD/{dmdlevel}/{tstep}'.format(caseDir=filepath,dmdlevel=i,tstep=TstepDirList[i1]))
        except OSError:
            pass
        with open('{caseDir}DMD/{dmdlevel}/{tstep}/{field}'.format(caseDir=filepath,dmdlevel=i,tstep=TstepDirList[i1],field=field),'w') as recFieldFile:
            os.popen('cp -r {caseDir}/{tstep}/polyMesh {caseDir}DMD/{dmdlevel}/{tstep}'.format(caseDir=filepath,dmdlevel=i,tstep=TstepDirList[i1],field=field))
            for row in header:
                recFieldFile.write(row)
            recFieldFile.write(str(pdata.shape[0]))
            recFieldFile.write('\n')
            recFieldFile.write('( \n')
            while i2<pdata.shape[0]:
                recFieldFile.write(str(pdata[i2][i1].real))
                recFieldFile.write('\n')
                i2+=1 
            recFieldFile.write(') \n')
            recFieldFile.write(';')
            for row in BC:
                recFieldFile.write(row)
            i1+=1

And these are the 4 modes:

image

image

image

image