谱本征正交分解 (SPOD)附matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。

个人主页:Matlab科研工作室

个人信条:格物致知。

更多Matlab仿真内容点击

智能优化算法  神经网络预测 雷达通信  无线传感器

信号处理 图像处理 路径规划 元胞自动机 无人机  电力系统

⛄ 内容介绍

SPOD() 是频域形式的本征正交分解(POD,也称为主成分分析或 Karhunen-Loève 分解)的 Matlab 实现,称为谱本征正交分解 (SPOD)。SPOD 源自固定流 [1,2] 的时空 POD 问题,并导致每个模式都以单一频率振荡。SPOD 模式代表动态结构,可以最佳地解释平稳随机过程的统计变异性。

⛄ 部分代码

function [L,P,f,Lc,A] = spod(X,varargin)

%SPOD Spectral proper orthogonal decomposition

%   [L,P,F] = SPOD(X) returns the spectral proper orthogonal decomposition

%   of the data matrix X whose first dimension is time. X can have any

%   number of additional spatial dimensions or variable indices. The

%   columns of L contain the modal energy spectra. P contains the SPOD

%   modes whose spatial dimensions are identical to those of X. The first

%   index of P is the frequency and the last one the mode number ranked in

%   descending order by modal energy. F is the frequency vector. If DT is

%   not specified, a unit frequency sampling is assumed. For real-valued

%   data, adjusted one-sided eigenvalue spectra are returned. Although

%   SPOD(X) automatically chooses default spectral estimation parameters,

%   the user is encouraged to manually specify problem-dependent parameters

%   on a case-to-case basis.

%

%   [L,P,F] = SPOD(X,WINDOW) uses a temporal window. If WINDOW is a vector,

%   X is divided into segments of the same length as WINDOW. Each segment

%   is then weighted (pointwise multiplied) by WINDOW. If WINDOW is a

%   scalar, a Hamming window of length WINDOW is used. If WINDOW is omitted

%   or set as empty, a Hamming window is used. Multitaper-Welch estimates

%   are computed with the syntax SPOD(X,[NFFT BW],...), where WINDOW is an

%   array of two scalars. NFFT is the window length and BW the

%   time-halfbandwidth product. See [4] for details. By default, a number

%   of FLOOR(2*BW)-1 discrete prolate spheroidal sequences (DPSS) is used

%   as tapers. Custom tapers can be specified in a column matrix of tapers

%   as WINDOW.

%

%   [L,P,F] = SPOD(X,WINDOW,WEIGHT) uses a spatial inner product weight in

%   which the SPOD modes are optimally ranked and orthogonal at each

%   frequency. WEIGHT must have the same spatial dimensions as X. 

%

%   [L,P,F] = SPOD(X,WINDOW,WEIGHT,NOVERLAP) increases the number of

%   segments by overlapping consecutive blocks by NOVERLAP snapshots.

%   NOVERLAP defaults to 50% of the length of WINDOW if not specified. 

%

%   [L,P,F] = SPOD(X,WINDOW,WEIGHT,NOVERLAP,DT) uses the time step DT

%   between consecutive snapshots to determine a physical frequency F. 

%

%   [L,P,F] = SPOD(XFUN,...,OPTS) accepts a function handle XFUN that

%   provides the i-th snapshot as x(i) = XFUN(i). Like the data matrix X,

%   x(i) can have any dimension. It is recommended to specify the total

%   number of snaphots in OPTS.nt (see below). If not specified, OPTS.nt

%   defaults to 10000. OPTS.isreal should be specified if a two-sided

%   spectrum is desired even though the data is real-valued, or if the data

%   is initially real-valued, but complex-valued for later snaphots.

%

%   [L,P,F] = SPOD(X,WINDOW,WEIGHT,NOVERLAP,DT,OPTS) specifies options:

%   OPTS.savefft: save FFT blocks to avoid storing all data in memory [{false} | true]

%   OPTS.deletefft: delete FFT blocks after calculation is completed [{true} | false]

%   OPTS.savedir: directory where FFT blocks and results are saved [ string | {'results'}]

%   OPTS.savefreqs: save results for specified frequencies only [ vector | {all} ]

%   OPTS.loadfft: load previously saved FFT blocks instead of recalculating [{false} | true]

%   OPTS.mean: provide a mean that is subtracted from each snapshot [ array of size X | 'blockwise' | {temporal mean of X; 0 if XFUN} ]

%   OPTS.nsave: number of most energtic modes to be saved [ integer | {all} ]

%   OPTS.isreal: complex-valuedity of X or represented by XFUN [{determined from X or first snapshot if XFUN is used} | logical ]

%   OPTS.nt: number of snapshots [ integer | {determined from X; defaults to 10000 if XFUN is used}]

%   OPTS.conflvl: confidence interval level [ scalar between 0 and 1 | {0.95} ]

%   OPTS.normvar: normalize each block by pointwise variance [{false} | true]

%

%   [L,PFUN,F] = SPOD(...,OPTS) returns a function PFUN instead of the SPOD

%   data matrix P if OPTS.savefft is true. The function returns the j-th

%   most energetic SPOD mode at the i-th frequency as p = PFUN(i,j) by

%   reading the modes from the saved files. Saving the data on the hard

% Determine whether data is real-valued or complex-valued to decide on one-

% or two-sided spectrum. If "opts.isreal" is not set, determine from data.

% If data is provided through a function handle XFUN and opts.isreal is not

% specified, deternime complex-valuedity from first snapshot.

if isfield(opts,'isreal')

    isrealx = opts.isreal;

elseif ~xfun

    isrealx = isreal(X);

else

    x1      = X(1);

    isrealx = isreal(x1);

    clear x1;

end

% get default spectral estimation parameters and options

[window,weight,nOvlp,dt,nDFT,nBlks,nTapers] = spod_parser(nt,nx,isrealx,varargin{:});

nSamples    = nBlks*nTapers;

% determine correction for FFT window gain

winWeight   = 1./mean(abs(window)); % row vector for multitaper                                          

% Use data mean if not provided through "opts.mean".

blk_mean    = false;

if isfield(opts,'mean')

    if strcmp('blockwise',opts.mean)

        blk_mean    = true;       

    end

end

if blk_mean

    mean_name   = 'blockwise mean';

elseif isfield(opts,'mean')

    x_mean      = opts.mean(:);

    mean_name   = 'user specified';

else

    switch xfun

        case true

            x_mean      = 0;

            warning('No mean subtracted. Consider providing long-time mean through "opts.mean" for better accuracy at low frequencies.');

            mean_name   = '0';

        case false

            x_mean      = mean(X,1);

            x_mean      = x_mean(:);

            mean_name   = 'data mean';

    end

end

disp(['Mean                      : ' mean_name]);

% obtain frequency axis

f = (0:nDFT-1)/dt/nDFT;

if isrealx

    f = (0:ceil(nDFT/2))/nDFT/dt;

else

    if mod(nDFT,2)==0

        f(nDFT/2+1:end) = f(nDFT/2+1:end)-1/dt;

    else

        f((nDFT+1)/2+1:end) = f((nDFT+1)/2+1:end)-1/dt;

    end

end

nFreq = length(f);

% set default for confidence interval 

if nargout>=4

    confint = true;

    if ~isfield(opts,'conflvl')

        opts.conflvl = 0.95;

    end

    xi2_upper   = 2*gammaincinv(1-opts.conflvl,nBlks);

    xi2_lower   = 2*gammaincinv(  opts.conflvl,nBlks);

    Lc          = zeros(nFreq,nBlks,2);

else

    confint = false;

    Lc      = [];

end

% set defaults for options for saving FFT data blocks

if ~isfield(opts,'savefreqs'),  opts.savefreqs  = 1:nFreq;   end

if opts.savefft

    if ~isfield(opts,'savedir'),    opts.savedir   = pwd;           end

    saveDir = fullfile(opts.savedir,['nfft' num2str(nDFT) '_novlp' num2str(nOvlp) '_nblks' num2str(nSamples)]);

    if ~exist(saveDir,'dir'),       mkdir(saveDir);                 end

    if ~isfield(opts,'nsave'),      opts.nsave      = nSamples;     end   

    if ~isfield(opts,'deletefft'),  opts.deletefft  = true;         end    

    omitFreqIdx = 1:nFreq; omitFreqIdx(opts.savefreqs) = []; 

end 

if ~isfield(opts,'loadfft'),    opts.loadfft    = false;     end

if ~isfield(opts,'normvar'),    opts.normvar    = false;     end

% loop over number of blocks and generate Fourier realizations

disp(' ')

disp('Calculating temporal DFT')

disp('------------------------------------')

if ~opts.savefft

    Q_hat = zeros(nFreq,nx,nSamples);

end

Q_blk       = zeros(nDFT,nx);

for iBlk    = 1:nBlks

    

    % check if all FFT files are pre-saved

    all_exist   = 0;

    if opts.loadfft        

        for iFreq = opts.savefreqs

            if ~isempty(dir(fullfile(saveDir,['fft_block' num2str([iBlk iFreq],'%.4i_freq%.4i.mat')])))

                all_exist   = all_exist + 1;

            end

        end

    end

    if opts.loadfft && all_exist==length(opts.savefreqs)

        disp(['loading FFT of block ' num2str(iBlk) '/' num2str(nBlks) ' from file'])

    else

        

        % get time index for present block

        offset                  = min((iBlk-1)*(nDFT-nOvlp)+nDFT,nt)-nDFT;

        timeIdx                 = (1:nDFT) + offset;

        % build present block

        if blk_mean, x_mean = 0; end

        if xfun

            for ti = timeIdx

                x                  = X(ti);

                Q_blk(ti-offset,:) = x(:) - x_mean;

            end

        else

            Q_blk   = bsxfun(@minus,X(timeIdx,:),x_mean.');

        end

        % if block mean is to be subtracted, do it now that all data is

        % collected

        if blk_mean

            Q_blk   = bsxfun(@minus,Q_blk,mean(Q_blk,1));           

        end        

        

        % normalize by pointwise variance

        if opts.normvar

            Q_var   = sum(bsxfun(@minus,Q_blk,mean(Q_blk,1)).^2,1)/(nDFT-1);

            % address division-by-0 problem with NaNs             

            Q_var(Q_var<4*eps)  = 1; 

            Q_blk   = bsxfun(@rdivide,Q_blk,Q_var);

        end        

        

        for iTaper  = 1:nTapers

            iSample = iBlk+((iTaper-1)*nBlks);

            if nTapers>1

               taperString  = [', taper ' num2str(iTaper) '/' num2str(nTapers)];

            else

               taperString  = []; 

            end

            disp(['block ' num2str(iBlk) '/' num2str(nBlks) taperString ' (snapshots ' ...

            num2str(timeIdx(1)) ':' num2str(timeIdx(end)) ')'])

            % window and Fourier transform block

            Q_blk_win               = bsxfun(@times,Q_blk,window(:,iTaper));

            Q_blk_hat               = winWeight(iTaper)/nDFT*fft(Q_blk_win);

            Q_blk_hat               = Q_blk_hat(1:nFreq,:);

            if ~opts.savefft

                % keep FFT blocks in memory

                Q_hat(:,:,iSample)         = Q_blk_hat;

            else

                for iFreq = opts.savefreqs

                    file = fullfile(saveDir,['fft_block' num2str([iSample iFreq],'%.4i_freq%.4i')]);

                    Q_blk_hat_fi        = single(Q_blk_hat(iFreq,:));

                    save(file,'Q_blk_hat_fi','-v7.3');

                end

            end

        end

    end

end

% loop over all frequencies and calculate SPOD

L   = zeros(nFreq,nSamples);

A   = zeros(nFreq,nSamples,nSamples);

disp(' ')

if nTapers>1

    disp('Calculating Multitaper SPOD')

else

    disp('Calculating SPOD')

end

disp('------------------------------------')

% unbiased estimator of CSD 

if blk_mean

    nIndep = nSamples-1;

else

    nIndep = nSamples;

end

if ~opts.savefft

    % keep everything in memory (default)

    P   = zeros(nFreq,nx,nSamples);

    for iFreq = 1:nFreq

        disp(['frequency ' num2str(iFreq) '/' num2str(nFreq) ' (f=' num2str(f(iFreq),'%.3g') ')'])

        Q_hat_f             = squeeze(Q_hat(iFreq,:,:));

        M                   = Q_hat_f'*bsxfun(@times,Q_hat_f,weight)/nIndep;

        [Theta,Lambda]      = eig(M);

        Lambda              = diag(Lambda);

        [Lambda,idx]        = sort(Lambda,'descend');

        Theta               = Theta(:,idx);

        Psi                 = Q_hat_f*Theta*diag(1./sqrt(Lambda)/sqrt(nIndep));

        P(iFreq,:,:)        = Psi;

        A(iFreq,:,:)        = diag(sqrt(nBlks)*sqrt(Lambda))*Theta';

        L(iFreq,:)          = abs(Lambda);

        % adjust mode energies for one-sided spectrum

        if isrealx

            if iFreq~=1&&iFreq~=nFreq

                L(iFreq,:)  = 2*L(iFreq,:);

            end

        end

        if confint

            Lc(iFreq,:,1)       = L(iFreq,:)*2*nIndep/xi2_lower;

            Lc(iFreq,:,2)       = L(iFreq,:)*2*nIndep/xi2_upper;

        end

    end

    P   = reshape(P,[nFreq dim(2:end) nSamples]);

else

    % save FFT blocks on hard drive (for large data)

    for iFreq = opts.savefreqs

        disp(['frequency ' num2str(iFreq) '/' num2str(nFreq) ' (f=' num2str(f(iFreq),'%.3g') ')'])

        % load FFT data from previously saved file

        Q_hat_f             = zeros(nx,nSamples);

        for iBlk    = 1:nSamples

            file    = fullfile(saveDir,['fft_block' num2str([iBlk iFreq],'%.4i_freq%.4i')]);

            load(file,'Q_blk_hat_fi');

            Q_hat_f(:,iBlk) = Q_blk_hat_fi;

        end

        M                   = Q_hat_f'*bsxfun(@times,Q_hat_f,weight)/nIndep;

        [Theta,Lambda]      = eig(M);

        Lambda              = diag(Lambda);

        [Lambda,idx]        = sort(Lambda,'descend');

        Theta               = Theta(:,idx);

        Psi                 = Q_hat_f*Theta*diag(1./sqrt(Lambda)/sqrt(nIndep));

        A(iFreq,:,:)        = diag(sqrt(nIndep)*sqrt(Lambda))*Theta';

        if opts.nsave>0

            Psi             = single(reshape(Psi(:,1:opts.nsave),[dim(2:end) opts.nsave]));

            file            = fullfile(saveDir,['spod_f' num2str(iFreq,'%.4i')]);

            save(file,'Psi','-v7.3');

        else

            Psi             = [];

        end

        L(iFreq,:)          = abs(Lambda);

        % adjust mode energies for one-sided spectrum

        if isrealx

            if iFreq~=1&&iFreq~=nFreq

                L(iFreq,:)  = 2*L(iFreq,:);

            end

        end

        if confint

            Lc(iFreq,:,1)       = L(iFreq,:)*2*nIndep/xi2_lower;

            Lc(iFreq,:,2)       = L(iFreq,:)*2*nIndep/xi2_upper;          

        end

    end

    % return anonymous function that loads SPOD modes from files instead of

    % actual solution

    P   = @(iFreq,iMode) getmode(iFreq,iMode,saveDir);

    

    file            = fullfile(saveDir,'spod_energy');

    save(file,'L','Lc','f','A','-v7.3');

    

    % clean up

    if opts.deletefft

        for iFreq = opts.savefreqs

            for iBlk    = 1:nSamples

                file    = fullfile(saveDir,['fft_block' num2str([iBlk iFreq],'%.4i_freq%.4i.mat')]);

                delete(file);

            end

        end

    end

    

    disp(' ')

    disp(['Results saved in folder ' saveDir])

end

end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function [P] = getmode(iFreq,iMode,saveDir)

%GETMODE Anonymous function that loads SPOD modes from files

file        = matfile(fullfile(saveDir,['spod_f' num2str(iFreq,'%.4i')]));

dim         = size(file.Psi);

for di = 1:length(dim)-1

    idx{di} = 1:dim(di);

end

idx{di+1}   = iMode;

P           = file.Psi(idx{:});

end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function [window,weight,nOvlp,dt,nDFT,nBlks,nTapers] = spod_parser(nt,nx,isrealx,varargin)

%SPOD_PARSER Parser for SPOD parameters

% read input arguments from cell array

window = []; weight = []; nOvlp = []; dt = [];

nvarargin = length(varargin);

if nvarargin >= 1

    window = varargin{1};

    if nvarargin >= 2

        weight   = varargin{2};

        if nvarargin >= 3

            nOvlp   = varargin{3};

            if nvarargin >= 4

                dt      = varargin{4};

            end

        end

    end

end

if size(window,2)>size(window,1) % ensure column matrix format 

    window = permute(window,[2 1]);

end

% check arguments and determine default spectral estimation parameters

% window size and type

if isempty(window)

    nDFT        = 2^floor(log2(nt/10));

    window      = hammwin(nDFT);

    window_name = 'Hamming';

elseif length(window)==1

    nDFT        = window;

    window      = hammwin(window);

    window_name = 'Hamming';

elseif length(window)==2

    nDFT        = window(1);

    bw          = window(2);

    nTapers     = floor(2*bw)-1;

    window      =  slepsec(nDFT,bw,nTapers);

    window_name = 'DPSS';    

elseif length(window) == 2^nextpow2(length(window))

    nDFT        = length(window);

    window_name = 'user specified';

else

    nDFT        = length(window);

    window_name = 'user specified';

end

nTapers     = size(window,2);

weight      = weight(:);

% block overlap

if isempty(nOvlp)

    nOvlp = floor(nDFT/2);

elseif nOvlp > nDFT-1

    error('Overlap too large.')

end

% time step between consecutive snapshots

if isempty(dt)

    dt = 1;

end

% inner product weight

if isempty(weight)

    weight      = ones(nx,1);

    weight_name = 'uniform';

elseif numel(weight) ~= nx

    error('Weights must have the same spatial dimensions as data.');

else

    weight_name = 'user specified';

end

% number of blocks

nBlks = floor((nt-nOvlp)/(nDFT-nOvlp));

% test feasibility

if nDFT < 4 || nBlks*nTapers < 3

    error('Spectral estimation parameters not meaningful.');

end

    

% display parameter summary

disp(' ')

if nTapers>1

    disp('Multitaper SPOD parameters')

else

    disp('SPOD parameters')

end

disp('------------------------------------')

if isrealx

    disp('Spectrum type             : one-sided (real-valued signal)')

else

    disp('Spectrum type             : two-sided (complex-valued signal)')

end

disp(['No. of snaphots per block : ' num2str(nDFT)])

disp(['Block overlap             : ' num2str(nOvlp)])

disp(['No. of blocks             : ' num2str(nBlks)])

disp(['Windowing fct. (time)     : ' window_name])

disp(['Weighting fct. (space)    : ' weight_name])

if nTapers>1

    disp(['No. of tapers             : ' num2str(nTapers)])

end

if exist('bw','var')

    disp(['Time-halfbandwidth product: ' num2str(bw)])

end

end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function [window] = hammwin(N)

%HAMMWIN Standard Hamming window of lentgh N

    window = 0.54-0.46*cos(2*pi*(0:N-1)/(N-1))';

end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function [window] = slepsec(nDFT,bw,nTapers)

%SLEPSEC Discrete prolate spheroidal sequences of length nDFT and

%time-halfbandwidth product bw

    df      = bw/nDFT;

    j       = 1:nDFT-1;

    r1      = [df*2*pi, sin(2*pi*df*j)./j];

    S       = toeplitz(r1);

    [U,L]   = eig(S);

    [~,idx] = sort(diag(L),'descend');

    U       = U(:,idx);

    window  = U(:,1:nTapers);

end

⛄ 运行结果

谱本征正交分解 (SPOD)附matlab代码_第1张图片

⛄ 参考文献

 [1] A. Nekkanti, O. T. Schmidt, Frequency鈥搕ime analysis, low-rank reconstruction and denoising of turbulent flows using SPOD,  Journal of Fluid Mechanics 926, A26, 2021

⛄ Matlab代码关注

❤️部分理论引用网络文献,若有侵权联系博主删除

❤️ 关注我领取海量matlab电子书和数学建模资料

 

你可能感兴趣的:(基础教程,matlab,开发语言)