def test_deconstruct_field(original_ff_spectral,
                             kx_array,
                             kz_array,
                             Nm,
                             y,
                             c,
                             Re,
                             baseflow,
                             mean_profile,
                             sparse):

    #===============================================================#
    #### Store the resolvent modes and amplitude coefficients    ####
    #    at each  Fourier mode pair                                 #
    #===============================================================#
    resolvent_modes_array = np.zeros((len(kx_array), len(kz_array), 3*Nm, 3*Nm), dtype=np.complex128)
    forcing_modes_array = np.zeros((len(kx_array), len(kz_array), 3*Nm, 3*Nm), dtype=np.complex128)
    coefficients_array = np.zeros((len(kx_array), len(kz_array), 3*Nm), dtype=np.complex128)
    sing_vals_array = np.zeros((len(kx_array), len(kz_array), 3*Nm), dtype=np.float64)

    #================================================================
    #### Loop through wavenumbers 
    #================================================================
    chebyshev_differentiation, mean_flow_derivatives = ps.calculate_derivatives(Nm+2, mean_profile, baseflow)


    #================================================================
    #### FFT mean profile
    #================================================================
    spectral_deviation_profile = np.zeros((3*Nm),dtype=complex)
    if len(mean_profile) == 0:
        spectral_deviation_profile = original_ff_spectral[0,:,0]
    
    else:
        # Remove baseflow from turbulent mean profile
        baseflow_profile = []
        if baseflow == "lam": # Laminary base flow
            baseflow_profile = 1.0 - y**2.0
        elif baseflow == "cou": # Couette base flow
            baseflow_profile = y

        # Remove baseflow from mean to get turbulent deviation profile
        vel_profile = mean_profile - np.asarray(baseflow_profile)
        deviation_profile_sp = np.fft.fft(vel_profile)
        spectral_deviation_profile[1:Nm] = deviation_profile_sp[1:Nm]
    
    
    #================================================================
    #### Loop through wavenumbers 
    #================================================================
    startTime = datetime.now()
    for mx in range(0, len(kx_array)):
        kx = kx_array[mx]
        print('\n\nkx:'+ str(kx))

        for mz in range(0, len(kz_array)):
            kz  = kz_array[mz]
            sys.stdout.write(".")
            sys.stdout.flush()

            if kx == 0 and kz == 0: # Zeroth Fourier modes
                resolvent_modes_array[mx, mz, :, 0] = spectral_deviation_profile
                continue # Start the loop again


            #--------------------------------------------------------
            #### Calculate the state vectors
            #--------------------------------------------------------
            omega = kx * c
            wegihted_transfer_function, w = ps.calculate_transfer_function(kx, kz, Re, Nm, omega, chebyshev_differentiation, mean_flow_derivatives)


            #--------------------------------------------------------
            #### Perform SVD
            #--------------------------------------------------------
            if sparse:
                weighted_resolvent_modes, singular_values, weighted_forcing_modes = svd(wegihted_transfer_function, full_matrices=False)
            else:
                weighted_resolvent_modes, singular_values, weighted_forcing_modes = svd(wegihted_transfer_function)


            #--------------------------------------------------------
            #### Test: SVD
            #--------------------------------------------------------
            Tests.SVD(weighted_resolvent_modes, singular_values, weighted_forcing_modes, wegihted_transfer_function, sparse)


            #--------------------------------------------------------
            #### Test: Orthogonality
            #--------------------------------------------------------
            Tests.orthogonality(weighted_resolvent_modes)
            Tests.orthogonality(weighted_forcing_modes)


            #--------------------------------------------------------
            #### Test: Invertibility
            #--------------------------------------------------------
            Tests.invertible(np.diag(singular_values))
            
            
            #--------------------------------------------------------
            #### Retrieve non-grid-weighted (physical) modes
            #--------------------------------------------------------
            weighted_resolvent_modes = np.asmatrix(weighted_resolvent_modes)
            weighted_forcing_modes = np.asmatrix(weighted_forcing_modes)
            resolvent_modes = np.linalg.solve(w, weighted_resolvent_modes)
            forcing_modes = np.linalg.solve(w, weighted_forcing_modes)
            
            
            #--------------------------------------------------------
            #### Check that the continuity condition is satisfied
            #--------------------------------------------------------
            Tests.continuity(resolvent_modes, np.diag(singular_values), kx, kz, Nm, chebyshev_differentiation['D1'])


            #--------------------------------------------------------
            #### Fix phase of resolvent modes based on critical layer or centreline
            #--------------------------------------------------------
            phase_shift = np.zeros((resolvent_modes.shape[1], resolvent_modes.shape[1]), dtype=np.complex128)
            ind0 = Nm/2 + 1 # Use centreline, unless
            if c < 1.0:
                inds = Tests.indices(mean_flow_derivatives['U'].diagonal(), lambda x: x > c)
                if len(inds) > 0:
                    ind0 = inds[0]

            np.fill_diagonal(phase_shift, np.exp(-1j * np.angle(resolvent_modes[ind0,:])))
            resolvent_modes *= phase_shift


            #--------------------------------------------------------
            #### Project resolvent modes to get amplitude coefficients
            #--------------------------------------------------------
            # Initialize amplitude coefficients vector
            ampl_coeffs = np.zeros((3*Nm, 1), dtype=np.complex128)    

            # Projection
            ampl_coeffs = inv(np.diag(singular_values)) * resolvent_modes.H * w.H * w * np.asmatrix(original_ff_spectral[mx, :, mz]).T
            Tests.projection(ampl_coeffs, np.diag(singular_values), resolvent_modes, np.asmatrix(original_ff_spectral[mx, :, mz]).T, 1e-10)
            
#            phase_test = False
#            norm_test  = False
#            xi_norm = np.linalg.norm(xi)
#            if kz == 2.0 or kz == -2.0: 
#            #xi_norm >= 1e-10:
#                # Phase test
#                if phase_test:
#                    print("\nApplying phase shift at: " + str(kz) + "\n ")
#                    # shift the field by pi/3
#                    xi += 1.0j*(np.pi/3.0)
#
#                # Norm test
#                if norm_test:
#                    print("\nApplying norm doubling.")
#                    # Double the energy of the field
#                    xi *= np.sqrt(2.0) + 1.0j*np.sqrt(2.0)
#
#                # Fix xi to see if same coefficients are retrieved from projection
#                if fixXi:
#                    xi[0] = 1.0
#                    xi[1] = 0.0
#                    print("\nXI:")
#                    print("norm: " + str(xi_norm))
#                    print(xi)
#                    print("")
#                print(xi)


            #--------------------------------------------------------
            #### Store resolvent modes, singular values and amplitude coeffs.
            #--------------------------------------------------------
            resolvent_modes_array[mx, mz, :, :] = resolvent_modes
            forcing_modes_array[mx, mz, :, :] = forcing_modes
            coefficients_array[mx, mz, :] = np.squeeze(np.asarray(ampl_coeffs))
            sing_vals_array[mx, mz, :] = singular_values


    calcTime = datetime.now() - startTime
    print("\n\n\n")
    print(calcTime)
    print("\n\n\n")
    deconstructed_dict = {}
    deconstructed_dict['resolvent_modes'] = resolvent_modes_array
    deconstructed_dict['forcing_modes'] = forcing_modes_array
    deconstructed_dict['singular_values'] = sing_vals_array
    deconstructed_dict['coefficients'] = coefficients_array


    return deconstructed_dict
def deconstruct_field(original_ff_spectral,
                      kx_array,
                      kz_array,
                      Nm,
                      y,
                      c,
                      Re,
                      baseflow,
                      mean_profile,
                      sparse):
    ''' 
    Deconstruct given flow field and return the resolvent modes,
    forcing modes, singular values and amplitude coefficients 
    needed to reconstruct it.

    =================================================================
    INPUTS:
    =================================================================
    original_ff_spectral:           3D array of flow field to 
                                    approximate in xz spectral form 
                                    with Chebyshev nodes in 
                                    wall-normal direction 
                                    (Chebyshev spacing).
                                    Stacked in wall-normal direction:
                                    dimensions: (Nx, Nd*Ny, Nz).

    kx_array:                       1D array of streamwise Fourier 
                                    modes.

    kz_array:                       1D array of spanwise Fourier 
                                    modes.

    Nm:                             Number of interior Chebyshev nodes,
                                    i.e. Ny - 2 (endpoints removed).
                                    
    y:                              Grid points in the wall-normal 
                                    direction.

    c:                              Wavespeed.

    Re:                             Reynolds number based on laminar
                                    baseflow centreline velocity, i.e.
                                    Re = 1 / nu.

    baseflow:                       Base flow type: [laminar, Couette],
                                    laminar means Plane Poiseuille.

    mean_profile:                   1D array of streamwise velocity 
                                    profile of the turbulent mean in
                                    wall-normal direction, 
                                    (endpoints included).

    sparse:                         Boolean that determines whether to 
                                    use sparse or full SVD algorithm.

    =================================================================
    OUTPUTS:
    =================================================================
    A dictionary named 'deconstructed_dict' which contains:
    
    resolvent_modes:                4D array of resolvent modes
                                    at each Fourier mode combination:
                                        resolvent_modes[mx, mz, :, :] gives 
                                        column vectors of given rank, i.e.
                                        rank = resolvent_modes.shape[3].
    
    forcing_modes:                  4D array of forcing modes
                                    at each Fourier mode combination:=
                                        resolvent_modes[mx, mz, :, :] gives 
                                        column vectors of given rank, i.e.
                                        rank = resolvent_modes.shape[3].

    singular_values:                3D array of singular values
                                    at each Fourier mode combination:
                                        singular_values[mx, mz, :] gives
                                        1D array of singular values, where
                                        rank = len(singular_values[mx, mz, :]).

    coefficients:                   3D array of complex amplitude
                                    coefficients at each 
                                    Fourier mode combination:
                                        coefficients[mx, mz, :] gives
                                        1D array of coefficients, where
                                        rank = len(coefficients[mx, mz, :]).
    '''
    #===============================================================#
    #### Store the resolvent modes and amplitude coefficients    ####
    #    at each  Fourier mode pair                                 #
    #===============================================================#
    resolvent_modes_array = np.zeros((len(kx_array), len(kz_array), 3*Nm, 3*Nm), dtype=complex)
    forcing_modes_array = np.zeros((len(kx_array), len(kz_array), 3*Nm, 3*Nm), dtype=complex)
    coefficients_array = np.zeros((len(kx_array), len(kz_array), 3*Nm), dtype=complex)
    sing_vals_array = np.zeros((len(kx_array), len(kz_array), 3*Nm), dtype=float)
    #===============================================================#
    #### Calculate differentiation matrices and mean flow derivatives 
    #===============================================================#
    chebyshev_differentiation, mean_flow_derivatives = ps.calculate_derivatives(Nm+2, mean_profile, baseflow)
    #===============================================================#
    #### Loop through wavenumbers                                ####
    #===============================================================#
    startTime = datetime.now()
    for mx in range(0, len(kx_array)):
        kx = kx_array[mx]
#        print('\nkx:'+ str(kx))
        for mz in range(0, len(kz_array)):
            kz  = kz_array[mz]
#            sys.stdout.write(".")
#            sys.stdout.flush()
            if kx == 0 and kz == 0: # Zeroth Fourier modes
                resolvent_modes_array[mx, mz, :, 0] = original_ff_spectral[mx, :, mz]
                continue # Start the loop again
            #--------------------------------------------------------
            #### Calculate the state vectors                        -
            #--------------------------------------------------------
            omega = kx * c
            wegihted_transfer_function, w = ps.calculate_transfer_function(kx, kz, Re, Nm, omega, chebyshev_differentiation, mean_flow_derivatives)
            #--------------------------------------------------------
            #### Perform SVD                                        -
            #--------------------------------------------------------
            if sparse:
                weighted_resolvent_modes, singular_values, weighted_forcing_modes = svd(wegihted_transfer_function, full_matrices=False)
            else:
                weighted_resolvent_modes, singular_values, weighted_forcing_modes = svd(wegihted_transfer_function)
            #--------------------------------------------------------
            #### Test: SVD                                          -
            #--------------------------------------------------------
            Tests.SVD(weighted_resolvent_modes, singular_values, weighted_forcing_modes, wegihted_transfer_function, sparse)
            #--------------------------------------------------------
            #### Test: Orthogonality                                -
            #--------------------------------------------------------
            Tests.orthogonality(weighted_resolvent_modes)
            Tests.orthogonality(weighted_forcing_modes)
            #--------------------------------------------------------
            #### Test: Invertibility                                -
            #--------------------------------------------------------
            Tests.invertible(np.diag(singular_values))
            #--------------------------------------------------------
            #### Retrieve non-grid-weighted (physical) modes        -
            #--------------------------------------------------------
            weighted_resolvent_modes = np.asmatrix(weighted_resolvent_modes)
            weighted_forcing_modes = np.asmatrix(weighted_forcing_modes)
            resolvent_modes = np.linalg.solve(w, weighted_resolvent_modes)
            forcing_modes = np.linalg.solve(w, weighted_forcing_modes)
            #--------------------------------------------------------
            #### Tests: Continuity condition                        -
            #--------------------------------------------------------
            Tests.continuity(resolvent_modes, np.diag(singular_values), kx, kz, Nm, chebyshev_differentiation['D1'])
            #--------------------------------------------------------
            #### Fix phase of resolvent modes                       -
            # based on critical layer or centreline                 -
            #--------------------------------------------------------
            phase_shift = np.zeros((resolvent_modes.shape[1], resolvent_modes.shape[1]), dtype=np.complex128)
            ind0 = Nm/2 + 1 # Use centreline, unless
            if c < 1.0:
                inds = Tests.indices(mean_flow_derivatives['U'].diagonal(), lambda x: x > c)
                if len(inds) > 0:
                    ind0 = inds[0]

            np.fill_diagonal(phase_shift, np.exp(-1j * np.angle(resolvent_modes[ind0,:])))
            resolvent_modes *= phase_shift
            #--------------------------------------------------------
            #### Project resolvent modes to get coefficients        -
            #--------------------------------------------------------
            # Initialize amplitude coefficients vector
            xi = np.zeros((3*Nm, 1), dtype=complex)
            # Projection
            xi = inv(np.diag(singular_values)) * resolvent_modes.H * w.H * w * np.asmatrix(original_ff_spectral[mx, :, mz]).T
            Tests.projection(xi, np.diag(singular_values), resolvent_modes, np.asmatrix(original_ff_spectral[mx, :, mz]).T, 1e-10)
            #--------------------------------------------------------
            #### Store resolvent modes, singular values and coeffs. -
            #--------------------------------------------------------
            resolvent_modes_array[mx, mz, :, :] = resolvent_modes
            forcing_modes_array[mx, mz, :, :] = forcing_modes
            coefficients_array[mx, mz, :] = np.squeeze(np.asarray(xi))
            sing_vals_array[mx, mz, :] = singular_values
    calcTime = datetime.now() - startTime
#    print("\n\n\n")
#    print(calcTime)
#    print("\n\n\n")
    deconstructed_dict = {}
    deconstructed_dict['resolvent_modes'] = resolvent_modes_array
    deconstructed_dict['forcing_modes'] = forcing_modes_array
    deconstructed_dict['singular_values'] = sing_vals_array
    deconstructed_dict['coefficients'] = coefficients_array
    
    return deconstructed_dict