from pseudo import *
import matplotlib.pyplot as plt
import ops


flowDict = {'a':0.5/1.72, 'b':0.1/1.72, 'Re':800./1.72}
N = 151
linOps = ops.linearize(N=N,flowClass="bl",Y=15.)
OSSunweighted = linOps.OSS(**flowDict)
OSS = linOps._weightMat(OSSunweighted)
# Don't need to solve the eigenvalue problem for this. 
# All I need to do is check if the eigenvalues from S&H hold. 
cSH = np.array([0.391929-0.043498j, 0.481315-0.139048j, 0.281945-0.264561j, 0.641946-0.290094j , 0.518690-0.353818j, 0.815075-0.378080j, 0.729010-0.420048j,
	0.18924427-0.109716j, 0.33172068-0.190194j, 0.449629-0.253847j, 0.555771-0.3070491j, 0.56631838-0.351969j, 0.751205-0.389606j, 0.845408-0.420542j])


if True:
    evals, evecs = linOps.eig(linOps.OSS(**flowDict), weighted=True)
    omega = -1.j*evals
    c = omega/flowDict['a']

    plt.scatter(cSH.real, cSH.imag,marker="x")
    plt.scatter(c.real, c.imag,marker="+")
    plt.xlabel('$c_r$',fontsize=15)
    plt.ylabel('$c_i$',fontsize=15)
    plt.xlim([0.15,0.9])
    plt.ylim([-0.5,0.])
    plt.title('Low-speed eigenvalues for a boundary-layer, \n with a=0.5,b=0.1,Re=800 using N=%d'%N,fontsize=15)
    plt.legend(('Schmid & Henningson', 'Current work'))
    fig = plt.gcf()
    fig.set_size_inches(6,6)
Exemple #2
0
from pseudo import *
import matplotlib.pyplot as plt
import ops


flowDict = {'a':0.5, 'b':1., 'Re':2000.}
N = 81
y = chebdif(N,1)[0]
U = 1.- y**2; dU = -2.*y; d2U=-2.*np.ones(U.size)
linOps = ops.linearize(N=N,flowClass="channel", U=U, dU=dU, d2U=d2U,Re=flowDict['Re'] )
#linOps = ops.linearize(N=N,flowClass="channel")

evals, evecs = linOps.eig(linOps.OSS(**flowDict), weighted=True)
#evals, evecs = linOps.eig(OSS, b=b, weighted=True)
print(evals.shape, evecs.shape, y.shape)

omega = 1.j*evals
c = omega/flowDict['a']

cSH = [0.3723-0.0374j, 0.4994-0.0992j , 0.8877-0.1095j, 0.8880-0.1096j, 0.7953-0.1933j , 0.7265-0.2610j , 0.6478-0.2697j, 0.7047-0.2987j , 0.4332-0.3066j,\
0.9776-0.0236j , 0.9329-0.0683j , 0.8882-0.1131j, 0.8435-0.1578j , 0.3123-0.1699j, 0.3123-0.1699j , 0.7987-0.2025j , 0.7536-0.2470j, 0.5347-0.2714j, 0.5347-0.2715j]

plt.scatter(np.real(cSH),np.imag(cSH),marker="x")
#plt.hold(True)
plt.scatter(np.real(c[0:]),np.imag(c[0:]),marker="+")
plt.xlabel('cr')
plt.ylabel('ci')
plt.title('Least-stable eigenvalues for a channel flow, \n with a=0.5,b=1.0,Re=2000 using N=151')
plt.legend(('Schmid & Henningson', 'Current work'),loc='lower left')
plt.xlim([0.,1.5])
plt.ylim([-1.,0.2])
def H2norm(aArr,
           bArr,
           Re=2000.,
           N=41,
           turb=False,
           eddy=False,
           epsArr=np.array([1. / 2000.]),
           y0Arr=np.array([-0.9]),
           fsAmp=np.identity(3),
           tau=None):
    """ Compute H2 norms for Fourier modes
    Inputs:
        aArr :  Range of streamwise wavenumbers
        bArr :  Range of spanwise wavenumbers
        Re (= 2000): Reynolds number
        N  (= 41 ) : Number of collocation nodes (internal)
        turb (=False):  If True, use turbulent base flow, otherwise laminar
        eddy (=False):  If True, use eddy viscosity in the dynamics matrix
        tau (=None): If set to a positive integer, uses exponential discounting,
                    by multiplying the energy at each time with e^{-t/tau},
                    so that later times (scaled by tau) contribute lesser to the norm
    Outputs:
        H2normArr   of shape (aArr.size,bArr.size)
    """
    linInst = ops.linearize(N=N, Re=Re, turb=turb, flowClass='channel')
    weightDict = pseudo.weightMats(N)
    H2normArr = np.zeros((aArr.size, bArr.size, y0Arr.size, fsAmp.shape[0]))
    I2 = np.identity(2 * N, dtype=np.complex)
    if (tau is not None) and (tau > 0.):
        print(
            "Using exponential discounting for finite time horizon, over tau=",
            tau)
        diffA = -(1. / tau) * I2
    else:
        diffA = 0. * I2

    fsArr = np.zeros((y0Arr.size, fsAmp.shape[0], 3 * N))
    y = linInst.y
    N = linInst.N
    D1 = linInst.D1
    W2 = weightDict['W2']
    for i2 in range(y0Arr.size):
        try:
            eps = epsArr[i2]
        except:
            eps = epsArr[-1]
        y0 = y0Arr[i2]
        fsArr[i2] = _fs(fsAmp=fsAmp, eps=eps, y0=y0, N=N, Re=Re,
                        turb=turb)['fs']

    def _FsFun(fsVec, a, b, DeltaInv):
        # fsVec should be shape (3,N)
        fsVec = fsVec.reshape((3, N))
        Fs = np.zeros((2 * N), dtype=np.complex)
        Fsadj = Fs.copy()
        Fs[:N] += -1.j * a * DeltaInv @ (D1 @ fsVec[0])
        Fs[N:] += 1.j * b * fsVec[0]
        Fs[:N] += -(a**2 + b**2) * DeltaInv @ fsVec[1]
        Fs[N:] += 0.
        Fs[:N] += -1.j * b * DeltaInv @ (D1 @ fsVec[2])
        Fs[N:] += -1.j * a * fsVec[2]

        Fsadj += 1. / (a**2 + b**2) * W2 @ np.concatenate(
            (-1.j * a * D1 @ fsVec[0], -1.j * b * fsVec[0]))
        Fsadj += W2 @ np.concatenate((fsVec[1], np.zeros(N, dtype=np.complex)))
        Fsadj += 1. / (a**2 + b**2) * W2 @ np.concatenate(
            (-1.j * b * D1 @ fsVec[2], 1.j * a * fsVec[2]))
        return Fs, Fsadj

    #print("Running for aArr=",aArr)
    for i0 in range(aArr.size):
        a = aArr[i0]
        print("a=", a)
        for i1 in range(bArr.size):
            b = bArr[i1]

            systemDict = linInst.makeSystem(a=a, b=b, eddy=eddy, adjoint=True)
            A = systemDict['A']
            Aadj = systemDict['Aadj']
            C = systemDict['C']
            B = systemDict['B']
            Cadj = B.copy()

            DeltaInv = systemDict['DeltaInv']

            # In case finite time horizon is to be used
            A = A + diffA
            Aadj = Aadj + diffA

            Y = solve_sylvester(Aadj, A, -Cadj @ C)
            for i2 in range(y0Arr.size):
                y0 = y0Arr[i2]
                try:
                    eps = epsArr[i2]
                except:
                    epsArr[-1]

                for i3 in range(fsAmp.shape[0]):
                    fsVec = fsArr[i2, i3].flatten()
                    Fs, Fsadj = _FsFun(fsVec, a, b, DeltaInv)
                    H2normArr[i0, i1, i2, i3] = np.real(Fsadj @ Y @ Fs)

    return np.sqrt(2. * H2normArr)
Exemple #4
0
bArr = b0 * np.arange(nb + 1)

fsAmp = np.zeros((9, 3))
fsAmp[0] = np.array([1., 0., 0.])
fsAmp[1] = np.array([0., 1., 0.])
fsAmp[2] = np.array([0., 0., 1.])
fsAmp[3] = np.array([5., 1., 0.])
fsAmp[4] = np.array([-5., 1., 0.])
fsAmp[5] = np.array([1., 1., 0.])
fsAmp[6] = np.array([-1., 1., 0.])
fsAmp[7] = np.array([1., 1., 1.])
fsAmp[8] = np.array([-1., 1., 1.])

eArr = np.zeros(
    (y0Arr.size, aArr.size, bArr.size, fsAmp.shape[0], 3, tArr.size))
linInst = ops.linearize(N=N, Re=Re, turb=True)
for k in range(y0Arr.size):
    y0 = y0Arr[k]
    print("y0, y0plus: %.4g, %.4g" % (y0, Re * (1 + y0)))
    impulseArgs = {'N': N, 'Re': Re, 'turb': True, 'y0': y0, 'eps': eps}
    for i0 in range(aArr.size):
        a = aArr[i0]
        if i0 % 5 == 0: print("a=", a)
        for i1 in range(bArr.size):
            b = bArr[i1]
            if (a == 0.) and (b == 0.):
                continue
            caseDict = impres.timeMap(a,b,tArr,linInst=linInst,\
                            fsAmp=fsAmp,eddy=eddy,\
                            coeffs=False, impulseArgs=impulseArgs.copy())
            energyArr = caseDict.pop('energyArr')
def timeMap(a,
            b,
            tArr,
            fsAmp=None,
            coeffs=False,
            linInst=None,
            modeDict=None,
            eddy=False,
            impulseArgs=None,
            printOut=False):
    """
    Return energy and Fourier coeffs (if requested) for a single Fourier mode for different combinations of forcing at different times We're using the same shape for the impulse in y for each of x,y,z, but changing the relative amplitudes of fx, fy, fz. 
    Inputs:
        Compulsory args:
            a, b, tArr: wavenumbers and time
        keyword args:
            fsAmp (=None):      Relative amplitudes of fx, fy, fz, of shape (m,3)
                                    If not supplied, use [[1,0,0]]
                                    The total energy in [fx,fy,fz] will be normalized
            coeffs (=False):    If True, return Fourier coeffs as well
            linInst (=None):    Instance of class linearize(). 
            modeDict (=None):   If linInst is not supplied, use modeDict to build linInst
                                    keys:   'N', 'Re', 'turb'
            eddy (=False):      If True, use eddy viscosity
            impulseArgs (=None):Build impulse function (wall-normal using this)
                                    keys:   'fs' 
                                    If 'fs' is not supplied, use
                                        'y0', 'eps', and N,Re,turb,eddy from linInst to build fs using _fs()
    Outputs:
            outDict: dict with keys 
                        'energyArr', 'coeffArr' (if input kwargs 'coeffs' is True)
                        'fs', 'y0', 'eps', 'a', 'b', 'tArr', 'N', 'Re', 'turb', 'eddy','fsAmp'
                        If 'fs' was supplied as input to function, set y0 and eps to None
    """

    #=====================================================================
    # Figure out if tArr is uniformly/linearly spaced or not
    #===========================

    # If tArr is uniformly spaced, then e^{A.n.dt} = e^{A.dt} @ e^{A.(n-1).dt} can be used
    # If this isn't the case, but tArr is still integral multiples of some dt, then
    #       at least e^{A.n.dt} = (e^{A.dt})^n can be used
    # If neither is true, just do e^{A.t} = expm(A*t)
    tArr = np.array([tArr]).flatten()
    tArr.sort()
    t0 = np.min(tArr[np.nonzero(tArr)])
    assert (tArr >= 0.).all()
    if _areSame(tArr, t0 * np.arange(1, tArr.size + 1)):
        # If tArr goes t0*{1,2,3,...}
        uniformTime = True
        linearTime = True
        zeroTime = False
        tGCD = t0
    elif _areSame(tArr, t0 * np.arange(0, tArr.size)):
        # If tArr goes t0*{0,1,2,...}
        uniformTime = True
        linearTime = True
        zeroTime = True
        tGCD = t0
    else:
        tGCD = _arrGCD(tArr[np.nonzero(tArr)])
        # If I have to raise e^{A.dt} to very large powers, it's not worth it
        if tGCD // t0 <= 5:
            uniformTime = False
            linearTime = True
        # so, if tGCD is significantly smaller than t0, treat tArr to not be linear and use expm() all the time
        else:
            uniformTime = False
            linearTime = False
    # These aren't used until the final loop where expFactor is calculated

    #=====================================================================
    # Decide if forcings are handled directly or a 3d basis is used
    #=========================
    if fsAmp is None:
        fsAmp = np.array([1, 1, 1]).reshape((1, 3))
    else:
        fsAmp = np.array(fsAmp).reshape((fsAmp.size // 3, 3))

    # Because of linearity, I don't need to compute for different fs arrangements
    # Its sufficient to compute for fs =[fs0;0;0], [0;fs0;0], [0;0;fs0],
    #   and then superpose the velocity fields
    # Computing energy from superposed velocity fields is trivial
    if fsAmp.shape[0] > 3:
        useBasis = True
        fsAmp0 = np.array([1., 0., 0., 0., 1., 0., 0., 0., 1.]).reshape((3, 3))
    else:
        useBasis = False
        fsAmp0 = fsAmp

    #====================================================================
    # System matrices
    #=======================
    if linInst is None:
        # Ensure modeDict has entries for N and Re. turb defaults to False
        assert set(('N', 'Re')) <= set(modeDict)
        modeDict['turb'] = modeDict.get('turb', False)
        linInst = ops.linearize(flowClass='channel', **modeDict)
    N = linInst.N
    Re = linInst.Re
    # See if flow is turbulent
    turb = (linInst.flowState == 'turb')

    # Construct system matrices for the dynamical system:
    #   d_t psi  = A  psi + B @ fs  delta(t)
    #   [u, v, w]= C  psi ,         where psi = [v, omega_y]
    # or
    #   u = C_u psi;    v = C_v psi;    w = C_w psi
    # So that u(t) = C_u @ (e^{A(t/n)})^n @ B @ fs, and similarly for v, w
    systemDict = linInst.makeSystem(a=a, b=b, adjoint=False, eddy=eddy)
    A = systemDict['A']
    C = systemDict['C']
    B = systemDict['B']
    W = linInst.weightDict['W1']
    w = linInst.w

    #=================================================================
    # Forcing vectors
    #==============
    # Function to build Fs for each fsAmp[k]
    if impulseArgs is None:
        impulseArgs = {'eddy': eddy, 'turb': turb, 'N': N, 'Re': Re}
    else:
        impulseArgs.update({'eddy': eddy, 'turb': turb, 'N': N, 'Re': Re})

    if 'fsAmp' in impulseArgs.keys(): impulseArgs.pop('fsAmp')
    #pdb.set_trace()
    fsDict = _fs(fsAmp=fsAmp0, **impulseArgs)
    fs = fsDict['fs']
    fs = fs.T  # _fs returns fs so that fs[k] refers to a particular forcing, i.e. shape mx 3N
    # We want fs to a matrix of shape 3Nxm, so that B@fs is shape 2Nxm, and final u(t) is shape 3Nxm
    Fs = B @ fs

    if useBasis:
        # To ensure I don't mess up the weighting factors,
        # I want forcing to have magnitude unity, i.e. ||fs|| = 1
        # Let's denote such forcing with fs~
        # fs~ = fs/||fs|| = [Ax*fs0,Ay*fs0,Az*fs0]/||fs||, where ||fs|| = ||fs0|| sqrt{Ax^2+Ay^2+Az^2} = ||fs0|| |A|
        # Since fx~ = [fs0~;0;0] , fy~ = [0;fs0~;0] and fz~ = [0;0;fs0~], I can write fs~ as
        # fs~ = Ax/|A| fx~ + Ay/|A| fy~ + Az/|A| fz~
        # Ignore the abuse of notation for the amplitude A here..
        fsAmpNorm = np.sqrt(np.sum(fsAmp**2, axis=1)).reshape(
            (fsAmp.shape[0], 1))
        scaleFactors = fsAmp / fsAmpNorm  # scaleFactors[k] is [Ax/|A|, Ay/|A|, Az/|A|]
        scaleFactors = scaleFactors.T  # Make it of shape 3 x m, to allow
        #                               [ a1x; a2x; a3x; a4x;...]_3xm
        # us_3Nxm = [ux; uy; uz]_3Nx3   [ a1y; a2y; a3y; a4y;...]_3xm
        #                               [ a1z; a2z; a3z; a4z;...]_3xm
        # where a1x = Ax/|A| for the first forcing, a2x for the second forcing, and so on..

    #==================================================================
    # last bits of preparation
    #==================
    # Build energyArr by looping over fs.shape[0] and tArr
    energyArr = np.zeros((tArr.size, 3, fsAmp.shape[0]))
    expFactor = np.identity(2 * N, dtype=np.complex)
    fs = fs.reshape((fs.size // (3 * N), 3 * N)).T
    # So that fs is a matrix of shape 3N x m, allowing matrix multiplication u(t;fs) = C e^{At} B fs_{3N,m}
    if coeffs:
        coeffArr = np.zeros((tArr.size, 3 * N, fsAmp.shape[0]),
                            dtype=np.complex)
    w1 = w.reshape(
        (1, N))  # w1 is ClenCurt weight matrix, reshaped to a row vector

    def uvwEnergies(arr):
        # Use 2d arr, of shape 3Nx m
        returnArr = np.zeros((3, arr.shape[1]))
        returnArr[0:1] = w1 @ (np.abs(arr[:N])**2)
        returnArr[1:2] = w1 @ (np.abs(arr[N:2 * N])**2)
        returnArr[2:3] = w1 @ (np.abs(arr[2 * N:])**2)
        return returnArr

    #==============================================================
    # And here we go...
    #======================
    if linearTime:
        expFactor0 = expm(A * t0)
    for i0 in range(tArr.size):
        if tArr[i0] == 0.:
            expFactor = np.identity(2 * N, dtype=np.complex)
        else:
            if uniformTime:
                expFactor = expFactor @ expFactor0
            elif linearTime:
                expFactor = matrix_power(expFactor0,
                                         int(round(tArr[i0] / tGCD)))
            else:
                expFactor = expm(A * tArr[i0])

        tmpArr = C @ expFactor @ Fs  # This is a matrix of shape 3N x m
        # Each column of tmpArr corresponds to field due to a forcing, fsAmp0[m]
        if not useBasis:
            # If I'm not computing for too many kinds of forcing, I don't have to split into Fx, Fy, and Fz and then superpose
            # For such cases, just calculate individually
            energyArr[i0] = uvwEnergies(tmpArr)
            if coeffs: coeffArr[i0] = tmpArr.copy()
        else:
            assert tmpArr.shape[
                1] == 3, " Need 3d basis for forcing along x,y,z"
            # When using too many forcings, first compute velocity fields individually for Fx, Fy, Fz
            # Now, compute velocity fields for each forcing given by fsAmp
            # Need to be careful about how I weight each of these fields
            velVec = tmpArr @ scaleFactors
            energyArr[i0] = uvwEnergies(velVec)
            if coeffs: coeffArr[i0] = velVec.copy()

    #===================================================================
    # Prepare dict to return...
    #===========================
    if impulseArgs is None: impulseArgs = {}
    impulseArgs.update(fsDict)
    impulseArgs.update({'fsAmp':fsAmp, 'N':N, 'Re':linInst.Re, 'a':a, 'b':b, 'tArr':tArr,\
            'turb':(linInst.flowState=='turb'), 'eddy':eddy })
    outDict = {'energyArr': energyArr}
    outDict.update(impulseArgs)
    if coeffs: outDict.update({'coeffArr': coeffArr})

    return outDict