├── routines ├── README.md ├── exactDMD.m ├── rDMD.m ├── DMDc.m ├── tlsDMD.m ├── mpEDMDqr.m ├── fbDMD.m ├── KoopPseudoSpecQR.m ├── subspacea.m ├── CoSaMP.m └── brewermap.m ├── data_sets └── rossler_data.mat ├── saved_data_from_runs └── Cylinder_Timings.mat ├── README.md ├── LICENSE ├── Lorenz_mpEDMD_example.m ├── Torus_DMDc_example.m ├── Cylinder_wake_example.m ├── Hankel_DMD_example.m ├── Lorenz_example.m ├── Noisy_cylinder_wake_example.m ├── Rossler_compactification_example.m ├── Lorenz_noise_reduction_example.m ├── Duffing_example.m ├── Compressed_cylinder_wake_example.m └── Duffing_EDMD_example.m /routines/README.md: -------------------------------------------------------------------------------- 1 | NB: Code for optDMD can be found here: https://github.com/duqbo/optdmd 2 | -------------------------------------------------------------------------------- /data_sets/rossler_data.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MColbrook/DMD-Multiverse/HEAD/data_sets/rossler_data.mat -------------------------------------------------------------------------------- /saved_data_from_runs/Cylinder_Timings.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MColbrook/DMD-Multiverse/HEAD/saved_data_from_runs/Cylinder_Timings.mat -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DMD-Multiverse 2 | MATLAB packages accompanying the review article "The Multiverse of Dynamic Mode Decomposition Algorithms" 3 | 4 | Each example should be self-explanatory. There are a few routines in the subfolder "routines" and the cylinder data can be found here under the name "Cylinder_wake_data": [https://www.dropbox.com/home/ResDMD_datasets](https://www.dropbox.com/sh/xj59e5in7dfsobi/AAAfkxqa1x9WFSTgrvqoqqRqa?st=fr97gjhk&dl=0) 5 | -------------------------------------------------------------------------------- /routines/exactDMD.m: -------------------------------------------------------------------------------- 1 | function [K,LAM,Phi] = exactDMD(X,Y,r) 2 | %%%%%%%%%%%%%%%%%% 3 | % Applies exact DMD 4 | % INPUTS: snapshot matrices X and Y, rank r 5 | % OUTPUTS: Koopman matrix K, diagonal matrix of eigenvalues LAM, DMD modes 6 | % Phi 7 | %%%%%%%%%%%%%%%%%% 8 | 9 | [U,S,V] = svd(X,'econ'); 10 | r = min(rank(S),r); 11 | U = U(:,1:r); V = V(:,1:r); S = S(1:r,1:r); Sinv = diag(1./diag(S)); 12 | K = (U')*Y*V*Sinv; % DMD matrix 13 | [W,LAM] = eig(K,'vector'); 14 | 15 | if (nargout > 2) 16 | Phi = Y*V*Sinv*W; % DMD modes 17 | end 18 | 19 | end -------------------------------------------------------------------------------- /routines/rDMD.m: -------------------------------------------------------------------------------- 1 | function [K,LAM,tm,Phi] = rDMD(X,Y,r,p) 2 | Z = [X,Y(:,end)]; 3 | 4 | tic 5 | Q = randQB(Z,r,p); 6 | 7 | X = Q'*Z; 8 | Y = X(:,2:end); 9 | X = X(:,1:end-1); 10 | 11 | [U,S,V] = svd(X,'econ'); 12 | r = min(rank(S),r); 13 | C = Y*V(:,1:r)*diag(1./diag(S(1:r,1:r))); 14 | K = (U(:,1:r))'*C; % DMD matrix 15 | [W,LAM] = eig(K,'vector'); 16 | 17 | if (nargout > 3) 18 | Phi = Q*C*W; % DMD modes 19 | end 20 | tm = toc; 21 | 22 | 23 | end 24 | 25 | 26 | 27 | function Q = randQB(X,k,p) 28 | 29 | OM = randn(size(X,2),k+p); 30 | Y = X*OM; 31 | 32 | % for j = 1:q 33 | % [Q,~] = qr(Y,"econ"); 34 | % [Z,~] = qr(X'*Q,"econ"); 35 | % Y = X*Z; 36 | % end 37 | 38 | [Q,~]=qr(Y,"econ"); 39 | 40 | end 41 | 42 | -------------------------------------------------------------------------------- /routines/DMDc.m: -------------------------------------------------------------------------------- 1 | function [A,B,LAM,Phi] = DMDc(X,Y,Upsilon,p,r) 2 | %%%%%%%%%%%%%%%%%% 3 | % Applies DMD with control 4 | % INPUTS: snapshot matrices X, Y and Upsilon, rank r 5 | % OUTPUTS: Matrices A and B, diagonal matrix of eigenvalues LAM, DMD modes 6 | % Phi 7 | %%%%%%%%%%%%%%%%%% 8 | 9 | [U,S,V] = svd([X;Upsilon],'econ'); 10 | p = min(rank(S),p); 11 | U = U(:,1:p); V = V(:,1:p); S = S(1:p,1:p); Sinv = diag(1./diag(S)); 12 | 13 | U1 = U(1:size(X,1),:); 14 | U2 = U(size(X,1)+1:end,:); 15 | 16 | [Uhat,~,~] = svd(Y,'econ'); 17 | r = min(rank(Uhat),r); 18 | Uhat = Uhat(:,1:r); 19 | 20 | A = (Uhat')*Y*V*Sinv*(U1')*Uhat; 21 | B = (Uhat')*Y*V*Sinv*(U2'); 22 | 23 | [W,LAM] = eig(A,'vector'); 24 | 25 | if (nargout > 3) 26 | Phi = Y*V*Sinv*(U1')*Uhat*W; % DMD modes 27 | end 28 | 29 | end -------------------------------------------------------------------------------- /routines/tlsDMD.m: -------------------------------------------------------------------------------- 1 | function [K,LAM,Phi] = tlsDMD(X,Y,r) 2 | %%%%%%%%%%%%%%%%%% 3 | % Applies total least-squares DMD 4 | % References for algorithm: 5 | % https://link.springer.com/article/10.1007/s00348-016-2127-7 and https://link.springer.com/article/10.1007/s00162-017-0432-2 6 | % INPUTS: snapshot matrices X and Y, rank r 7 | % OUTPUTS: Koopman matrix K, diagonal matrix of eigenvalues LAM, DMD modes 8 | % Phi 9 | %%%%%%%%%%%%%%%%%% 10 | 11 | % First project onto POD modes 12 | [U0,~,V] = svd([X,Y(:,end)],'econ'); 13 | r = min(min(rank(U0),floor(size(X,2)/2)),r); 14 | U0 = U0(:,1:r); 15 | Xtilde = U0'*X; 16 | Ytilde = U0'*Y; 17 | 18 | % Run the tls optimization 19 | [U,~,~] = svd([Xtilde;Ytilde],'econ'); 20 | 21 | U11 = U(1:end/2,1:r); 22 | U21 = U(end/2+1:end,1:r); 23 | 24 | % Now extract the DMD ouputs 25 | K = U21*pinv(U11); % DMD matrix 26 | [W,LAM] = eig(K,'vector'); 27 | 28 | if (nargout > 2) 29 | Phi = Y*V*Sinv*W; % DMD modes 30 | end 31 | 32 | end 33 | 34 | 35 | -------------------------------------------------------------------------------- /routines/mpEDMDqr.m: -------------------------------------------------------------------------------- 1 | function [mpK,mpV,mpD] = mpEDMDqr(PX,PY,W) 2 | % mpEDMD algorithm 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % INPUTS: PX: dictionary evaluated at snapshots, 5 | % PY: dictionary evaluated at snapshots one time step later 6 | % W: vector of weights for the quadrature 7 | % OUTPUTS: Koopman matrix mpK and its eigendecomposition [mpV,mpD] 8 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 9 | % PLEASE CITE: Colbrook, Matthew J. "The mpEDMD algorithm for data-driven computations of measure-preserving dynamical systems." 10 | % SIAM Journal on Numerical Analysis 61.3 (2023): 1585-1608. 11 | % Author and copyright: Matthew Colbrook, https://www.damtp.cam.ac.uk/user/mjc249/home.html 12 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 13 | 14 | W = W(:); 15 | [Q,R] = qr(sqrt(W).*PX,"econ") ; 16 | T = (R')\(PY'*(sqrt(W).*Q)); 17 | [U,~,V] = svd(T); 18 | [mpV,mpD] = schur(V*U','complex'); % Schur decomposition used to ensure orthonormal basis 19 | 20 | mpV = R\mpV; 21 | mpK = (R\(V*U'))*R; 22 | mpD = diag(diag(mpD)); 23 | 24 | end 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2023, Matthew Colbrook 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Lorenz_mpEDMD_example.m: -------------------------------------------------------------------------------- 1 | clear 2 | % close all 3 | rng(1); % set random seed to get identical snapshots each time 4 | addpath(genpath('./routines')) 5 | 6 | % NB general code for mpEDMD can be found at: https://github.com/MColbrook/Measure-preserving-Extended-Dynamic-Mode-Decomposition 7 | 8 | %% Set parameters 9 | options = odeset('RelTol',1e-14,'AbsTol',1e-14); % for the numerical solver 10 | SIGMA=10; BETA=8/3; RHO=28; 11 | ODEFUN=@(t,y) [SIGMA*(y(2)-y(1));y(1).*(RHO-y(3))-y(2);y(1).*y(2)-BETA*y(3)]; 12 | 13 | N=500; % number of delay embeddings 14 | g = @(x,y,z) tanh((x.*y-3*z)/5); % observable 15 | M=10^4; % number of data points 16 | dt=0.1; % time step for trajectory sampling 17 | 18 | %% Produce the data - slowest part of this script! 19 | Y0=(rand(3,1)-0.5)*4; 20 | [~,Y0]=ode45(ODEFUN,[0.000001 1, 100],Y0,options); Y0 = Y0(end,:)'; % sample after when on the attractor 21 | [~,DATA]=ode45(ODEFUN,[0.000001 (1:((M+(N+1))))*dt],Y0,options); 22 | 23 | %% Use delay embedding 24 | PX1=zeros(M,N); PX1(:,1)=DATA(1:M,1); PX2=zeros(M,N); PX2(:,1)=DATA(1:M,2); PX3=zeros(M,N); PX3(:,1)=DATA(1:M,3); 25 | PY1=zeros(M,N); PY1(:,1)=DATA((1:M)+1,1); PY2=zeros(M,N); PY2(:,1)=DATA((1:M)+1,2); PY3=zeros(M,N); PY3(:,1)=DATA((1:M)+1,3); 26 | for j=2:N 27 | PX1(:,j)=DATA((1:M)+(j-1),1); PX2(:,j)=DATA((1:M)+(j-1),2); PX3(:,j)=DATA((1:M)+(j-1),3); 28 | PY1(:,j)=DATA((1:M)+1+(j-1),1); PY2(:,j)=DATA((1:M)+1+(j-1),2); PY3(:,j)=DATA((1:M)+1+(j-1),3); 29 | end 30 | 31 | %% Run mpEDMD 32 | PX = g(PX1,PX2,PX3); 33 | PY = g(PY1,PY2,PY3); 34 | [~,mpV,mpD] = mpEDMDqr(PX,PY,1/M); 35 | 36 | % %% Plot the singular values 37 | % S = svd(PX); 38 | % figure 39 | % semilogy(S,'b.','markersize',15) 40 | % title('Singular Values','interpreter','latex','fontsize',18) 41 | % ax=gca; ax.FontSize=18; 42 | % exportgraphics(gcf,'saved_figures/Lorenz_SVD.pdf','ContentType','vector','BackgroundColor','none') 43 | 44 | %% Compute spectral measure 45 | c = zeros(N,1); c(1) = 1; % coefficients of g in Krylov basis 46 | piE = diag(mpD); TH=angle(piE*exp(1i*eps)); % eps here is to take into account MATLAB's convention for angle 47 | G = (PX'*PX)/M; 48 | MU=abs(mpV'*G*c).^2; 49 | 50 | %% Cdf plots 51 | [~,Ib] = sort(TH(:),'ascend'); 52 | THp=TH(Ib); THp=[THp(:)-10^(-14),THp(:)]'; THp=THp(:); 53 | cdf=0*THp; 54 | cc=0; 55 | for j=1:length(TH) 56 | cdf(2*j-1)=cc; cc=cc+MU(Ib(j)); cdf(2*j)=cc; 57 | end 58 | 59 | THp = [-pi;THp(:);pi]; cdf = [0;cdf(:);sum(MU)]; % for visualisation 60 | 61 | 62 | figure 63 | plot(THp,cdf/sum(MU),'b','linewidth',2) 64 | ylim([0,1]); xlim([-pi,pi]); 65 | 66 | ax=gca; ax.FontSize=18; 67 | title(sprintf('$F_{\\mu_g^{(%d)}}(\\theta)$',N),'interpreter','latex','fontsize',30) 68 | xlabel('$\theta$','interpreter','latex','fontsize',30) 69 | exportgraphics(gcf,sprintf('saved_figures/lorenz_cdf_%d.pdf',N),'ContentType','vector','BackgroundColor','none') 70 | -------------------------------------------------------------------------------- /routines/fbDMD.m: -------------------------------------------------------------------------------- 1 | function [K,LAM,Phi] = fbDMD(X,Y,r) 2 | %%%%%%%%%%%%%%%%%% 3 | % Applies forward-backward DMD 4 | % Reference for algorithm: https://link.springer.com/article/10.1007/s00348-016-2127-7 5 | % INPUTS: snapshot matrices X and Y, rank r 6 | % OUTPUTS: Koopman matrix K, diagonal matrix of eigenvalues LAM, DMD modes 7 | % Phi 8 | %%%%%%%%%%%%%%%%%% 9 | 10 | [U,S,V] = svd([X,Y(:,end)],'econ'); 11 | r = min(rank(U),r); 12 | U = U(:,1:r); V = V(:,1:r); S = S(1:r,1:r); Sinv = diag(1./diag(S)); 13 | X2 = U'*X; Y2 = U'*Y; 14 | 15 | [U1,S1,V1] = svd(X2,'econ'); 16 | [U2,S2,V2] = svd(Y2,'econ'); 17 | r = min(min(rank(S1),rank(S2)),r); 18 | 19 | U1 = U1(:,1:r); V1 = V1(:,1:r); S1 = S1(1:r,1:r); Sinv1 = diag(1./diag(S1)); 20 | K1 = (U1')*Y2*V1*Sinv1; % forward DMD matrix 21 | 22 | U2 = U2(:,1:r); V2 = V2(:,1:r); S2 = S2(1:r,1:r); Sinv2 = diag(1./diag(S2)); 23 | K2 = (U2')*X2*V2*Sinv2; % backward DMD matrix 24 | 25 | [wf,ef] = eig(K1); 26 | wf = Y2*(V1*(S1\wf)); 27 | 28 | [wb,eb] = eig(K2); 29 | wb = X2*(V2*(S2\wb)); 30 | 31 | K1 = wf*ef*pinv(wf); 32 | K2 = wb*eb*pinv(wb); 33 | 34 | % atilde = sqrtm(fatilde*pinv(batilde)); 35 | % [atilde,errtemp] = besterrnbyn(fatilde,atilde,ifoverride); 36 | % 37 | % [w,e] = eig(atilde); 38 | % e = diag(e); 39 | 40 | 41 | K = sqrtm(K1*pinv(K2)); 42 | % [K,~] = besterrnbyn(K1,K); 43 | 44 | 45 | [W,LAM] = eig(K,'vector'); 46 | 47 | % choose the signs of the eigenvalues 48 | Kfit = diag(W\(K1*W)); 49 | for jj = 1:size(Kfit) 50 | if abs(Kfit(jj)+LAM(jj)) 2) 61 | Phi = Y*V*Sinv*W; % DMD modes 62 | end 63 | 64 | end 65 | 66 | 67 | 68 | function [Cbest,besterr] = besterrnbyn(A,B) 69 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 70 | % 71 | % Find modification of B which is closest to 72 | % A. We assume that B is computed as a square root. 73 | % The nonuniqueness of matrix square roots 74 | % is up to +/- times each eigenvalue (2^n 75 | % possibilities for an n x n matrix) 76 | % 77 | % Warning: this is a slow routine! It is exponential 78 | % in n and should not be called for large n 79 | % 80 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 81 | 82 | [~,n] = size(A); 83 | 84 | if n > 25 85 | error('this will take forever. bomb') 86 | end 87 | 88 | C = B; 89 | besterr = norm(A-C,'fro'); 90 | Cbest = C; 91 | 92 | [w,e] = eig(B); 93 | de = diag(e); 94 | pw = pinv(w); 95 | 96 | for i = 1:2^n-1 97 | v = (-1).^str2num(dec2bin(i,n)'); 98 | C = w*diag(de.*v)*pw; 99 | err = norm(A-C,'fro'); 100 | if (err < besterr) 101 | besterr = err; 102 | Cbest = C; 103 | end 104 | end 105 | 106 | end 107 | 108 | 109 | -------------------------------------------------------------------------------- /Torus_DMDc_example.m: -------------------------------------------------------------------------------- 1 | clear 2 | close all 3 | 4 | addpath(genpath('./data_sets')) 5 | addpath(genpath('./routines')) 6 | 7 | %% Load dataset 8 | load('torus_data.mat') 9 | X = X_forced(:,1:end-1); 10 | Y = X_forced(:,2:end); 11 | Upsilon = U(:,1:end-1); 12 | 13 | %% Execute exactDMD and DMDc 14 | [~,LAM,Phi] = exactDMD(X,Y,10); 15 | w = log(LAM)/0.01; % work with logarithms of eigenvalues 16 | 17 | [~,~,LAMc,Phic] = DMDc(X,Y,Upsilon,11,10); 18 | wc = log(LAMc)/0.01; % work with logarithms of eigenvalues 19 | %% Plote the (logarithm) of eigenvalues 20 | figure 21 | plot([imag(wTrue),-imag(wTrue)],[real(wTrue),real(wTrue)],'xb','markersize',15,'linewidth',2) 22 | hold on 23 | plot(imag(wc),real(wc),'.','markersize',20) 24 | plot(imag(w),real(w),'.','markersize',20) 25 | plot([-40,40],[0,0],'-k') 26 | xlim([-11,11]); ylim([-0.1,0.5]); 27 | title('Eigenvalues','interpreter','latex','fontsize',18) 28 | xlabel('$\arg(\lambda)/\Delta t$','interpreter','latex','fontsize',18) 29 | ylabel('$\log(|\lambda|)/\Delta t$','interpreter','latex','fontsize',18) 30 | legend({'True','DMDc','exactDMD'},'interpreter','latex','fontsize',16,'location','north') 31 | pbaspect([4 1.5 1]) 32 | ax=gca; ax.FontSize=18; 33 | exportgraphics(gcf,'saved_figures/torus_eigenvalues.pdf','ContentType','vector','BackgroundColor','none') 34 | return 35 | %% Plot the modes 36 | close all 37 | clc 38 | I = find(imag(LAM)>0); 39 | Ic = find(imag(LAMc)>0); 40 | for jj=1:5 41 | % modes defined up to phase so make phase match that of true modes 42 | I2 = find(abs(wTrue-wc(Ic(jj)))==min(abs(wTrue-wc(Ic(jj))))); 43 | 44 | uc = reshape(Phic(:,Ic(jj)),128,128); 45 | uTrue = reshape(PhiTrue(:,I2),128,128); 46 | t = uTrue./uc; 47 | t = mean(t(:)); 48 | uc = t*uc; 49 | % subspacea(real(uc(:)),real(uTrue(:))) 50 | 51 | figure 52 | imagesc(real(uc)) 53 | colormap(brighten(brewermap([],'RdYlBu'),0)) 54 | axis equal off 55 | exportgraphics(gcf,sprintf('saved_figures/torus_DMDc_modes%d.png',jj),'BackgroundColor','none','Resolution',400) 56 | 57 | figure 58 | imagesc(real(uTrue)) 59 | colormap(brighten(brewermap([],'RdYlBu'),0)) 60 | axis equal off 61 | exportgraphics(gcf,sprintf('saved_figures/torus_true_modes%d.png',jj),'BackgroundColor','none','Resolution',400) 62 | 63 | find(abs(w-wc(Ic(jj)))==min(abs(w-wc(Ic(jj))))) 64 | u = reshape(Phi(:,abs(w-wc(Ic(jj)))==min(abs(w-wc(Ic(jj))))),128,128); 65 | 66 | t = uTrue./u; 67 | t = mean(t(:)); 68 | u = t*u; 69 | % subspacea(real(u(:)),real(uTrue(:))) 70 | figure 71 | imagesc(real(u)) 72 | colormap(brighten(brewermap([],'RdYlBu'),0)) 73 | axis equal off 74 | exportgraphics(gcf,sprintf('saved_figures/torus_DMD_modes%d.png',jj),'BackgroundColor','none','Resolution',400) 75 | 76 | u = reshape(Phi(:,I(jj)),128,128); 77 | imagesc(real(u)) 78 | colormap(brighten(brewermap([],'RdYlBu'),0)) 79 | axis equal off 80 | exportgraphics(gcf,sprintf('saved_figures/torus_DMD_modes_unordered%d.png',jj),'BackgroundColor','none','Resolution',400) 81 | close all 82 | 83 | end 84 | 85 | -------------------------------------------------------------------------------- /Cylinder_wake_example.m: -------------------------------------------------------------------------------- 1 | clear 2 | close all 3 | 4 | addpath(genpath('./data_sets')) 5 | addpath(genpath('./routines')) 6 | 7 | load('Cylinder_wake_data.mat') 8 | 9 | %% Perform exact DMD 10 | r = 47; 11 | M = 24*5; 12 | ind = (1:M); 13 | X = DATA(1:160000,ind); % only look at x component of velocity 14 | Y = DATA(1:160000,ind+1); 15 | [~,LAM,Phi] = exactDMD(X,Y,r); 16 | [~,I]=sort(abs(1-LAM),'ascend'); % reorder modes 17 | Phi = Phi(:,I); LAM = LAM(I); 18 | 19 | %% Plot the eigenvalues 20 | figure 21 | plot(cos(0:0.01:2*pi),sin(0:0.01:2*pi),'-k') 22 | hold on 23 | plot(real(LAM),imag(LAM),'b.','markersize',15) 24 | axis equal 25 | axis([-1.15,1.15,-1.15,1.15]) 26 | 27 | for j=0:5 28 | text(1.05*real(LAM(max(1,2*j))),1.05*imag(LAM(max(1,2*j))),sprintf('%d',j),'interpreter','latex','fontsize',13) 29 | 30 | end 31 | text(1.05*real(LAM(max(1,2*7)))+0.03,1.05*imag(LAM(max(1,2*7)))-0.03,'$\ddots$','interpreter','latex','fontsize',13,'rotation',-5) 32 | 33 | title('DMD Eigenvalues','interpreter','latex','fontsize',18) 34 | xlabel('$\mathrm{Re}(\lambda)$','interpreter','latex','fontsize',18) 35 | ylabel('$\mathrm{Im}(\lambda)$','interpreter','latex','fontsize',18) 36 | ax=gca; ax.FontSize=18; 37 | exportgraphics(gcf,'saved_figures/cylinder_wake_eigenvalues.pdf','ContentType','vector','BackgroundColor','none') 38 | 39 | %% Predictive power 40 | b = Phi\DATA(1:160000,1); 41 | Er = zeros(1000,1); 42 | m = mean(DATA(1:160000,1:M),2); 43 | 44 | for j=1:1000 45 | Er(j)=norm(Phi*(LAM.^(j).*b)-DATA(1:160000,j+1))/norm(DATA(1:160000,j+1)-m); 46 | end 47 | %% 48 | figure 49 | loglog(Er,'b','linewidth',2) 50 | hold on 51 | plot([120,120],[10^(-10),max(Er)],'--k','linewidth',2) 52 | title('Relative Prediction Error','interpreter','latex','fontsize',18) 53 | xlabel('Number of time steps ($n$)','interpreter','latex','fontsize',18) 54 | 55 | 56 | xx = [0.5 0.6]+0.05; yy = [0.8 0.7]; 57 | annotation('textarrow',xx,yy,'String','Extent of snapshot data','interpreter','latex','fontsize',16,'Linewidth',1) 58 | 59 | ax=gca; ax.FontSize=18; 60 | exportgraphics(gcf,'saved_figures/cylinder_wake_prediction.pdf','ContentType','vector','BackgroundColor','none') 61 | 62 | 63 | %% Plot modes 64 | figure 65 | tiledlayout(3,2,"TileSpacing","compact") 66 | ct=1; 67 | for j=[1,2,4,6,8,10] 68 | nexttile 69 | C = (reshape(Phi(1:160000,j),[800,200])); 70 | if mod(ct,2)==1 71 | C=real(C)+fliplr(real(C)); 72 | else 73 | C=real(C)-fliplr(real(C)); 74 | end 75 | vv=0.025:0.05:1; 76 | a=max(real(C(:))); 77 | b=min(real(C(:))); 78 | c=(a+b)/2; 79 | [~,h]=contourf((x-obst_x)/d,0.94*(y-obst_y+2.5)/d,real(C),[-vv,vv]*(b-a)+c,'edgecolor','k'); 80 | h.LineWidth = 0.001; 81 | colormap(brighten(brewermap([],'RdYlBu'),0.1)) 82 | axis equal 83 | hold on 84 | fill(obst_r*cos(0:0.01:2*pi)/d,obst_r*sin(0:0.01:2*pi)/d,'m','edgecolor','none') 85 | xlim([-2,10+0*max((x(:)-obst_x)/d)]) 86 | title(sprintf('Mode %d',ct-1),'interpreter','latex','fontsize',12) 87 | 88 | pause(0.01) 89 | ct =ct+1; 90 | end 91 | 92 | exportgraphics(gcf,'saved_figures/cylinder_wake_modesx.pdf','BackgroundColor','none','Resolution',400) 93 | 94 | 95 | -------------------------------------------------------------------------------- /routines/KoopPseudoSpecQR.m: -------------------------------------------------------------------------------- 1 | function [RES,RES2,V2] = KoopPseudoSpecQR(PX,PY,W,z_pts,varargin) 2 | % This code computes pseudospectrum of K (currently written for dense matrices). 3 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 4 | % INPUTS 5 | % PX: dictionary evaluated at snapshots 6 | % PY: dictionary evaluated at snapshots one time step later 7 | % W: vector of weights for the quadrature 8 | % z_pts: vector of complex points where we want to compute pseudospectra 9 | 10 | % OPTIONAL LABELLED INPUTS 11 | % parallel: parfor (on) or normal for (off) loop, default is "off" 12 | % z_pts2: vector of complex points where we want to compute 13 | % pseudoeigenfunctions 14 | % reg_param: regularisation parameter for G 15 | 16 | % OUTPUTS 17 | % RES: residual for shifts z_pts. 18 | % RES2: residual for pseudoeigenfunctions corresponding to shifts z_pts2. 19 | % RES2: pseudoeigenfunctions corresponding to shifts z_pts2. 20 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 21 | 22 | % Collect the optional inputs 23 | p = inputParser; 24 | validPar = {'on','off'}; 25 | checkPar = @(x) any(validatestring(x,validPar)); 26 | 27 | addParameter(p,'Parallel','off',checkPar) 28 | addParameter(p,'z_pts2',[],@isnumeric) 29 | addParameter(p,'reg_param',10^(-14),@isnumeric) 30 | 31 | p.CaseSensitive = false; 32 | parse(p,varargin{:}) 33 | 34 | %% compute the pseudospectrum 35 | W = W(:); 36 | [Q,R] = qr(sqrt(W).*PX,"econ"); 37 | 38 | C1 = (sqrt(W).*PY)/R; 39 | L = C1'*C1; 40 | G = eye(size(PX,2)); 41 | A = Q'*C1; 42 | 43 | z_pts=z_pts(:); 44 | LL=length(z_pts); 45 | RES=zeros(LL,1); 46 | 47 | if LL>0 48 | warning('off','all') 49 | pf = parfor_progress(LL); 50 | pfcleanup = onCleanup(@() delete(pf)); 51 | if p.Results.Parallel=="on" 52 | parfor jj=1:LL 53 | warning('off','all') 54 | RES(jj)=sqrt(real(eigs(L-z_pts(jj)*A'-conj(z_pts(jj))*A+abs(z_pts(jj))^2*G,1,'smallestabs'))); 55 | parfor_progress(pf); 56 | end 57 | else 58 | for jj=1:LL 59 | RES(jj)=sqrt(real(eigs(L-z_pts(jj)*A'-conj(z_pts(jj))*A+abs(z_pts(jj))^2*G,1,'smallestabs'))); 60 | parfor_progress(pf); 61 | end 62 | end 63 | end 64 | 65 | RES2=[]; 66 | V2=[]; 67 | 68 | if ~isempty(p.Results.z_pts2) 69 | RES2=zeros(length(p.Results.z_pts2),1); 70 | V2=zeros(size(G,1),length(p.Results.z_pts2)); 71 | pf = parfor_progress(length(p.Results.z_pts2)); 72 | pfcleanup = onCleanup(@() delete(pf)); 73 | if p.Results.Parallel=="on" 74 | parfor jj=1:length(p.Results.z_pts2) 75 | warning('off','all') 76 | [V,D]=eigs( L-p.Results.z_pts2(jj)*A'-conj(p.Results.z_pts2(jj))*A+abs(p.Results.z_pts2(jj))^2*G,1,'smallestabs'); 77 | V2(:,jj)=V; RES2(jj)=sqrt(real(D(1,1))); 78 | parfor_progress(pf); 79 | end 80 | else 81 | for jj=1:length(p.Results.z_pts2) 82 | [V,D]=eigs( L-p.Results.z_pts2(jj)*A'-conj(p.Results.z_pts2(jj))*A+abs(p.Results.z_pts2(jj))^2*G,1,'smallestabs'); 83 | V2(:,jj)=V; RES2(jj)=sqrt(real(D(1,1))); 84 | parfor_progress(pf); 85 | end 86 | end 87 | V2=R\V2; 88 | end 89 | 90 | warning('on','all') 91 | 92 | 93 | 94 | end -------------------------------------------------------------------------------- /Hankel_DMD_example.m: -------------------------------------------------------------------------------- 1 | clear 2 | close all 3 | 4 | addpath(genpath('./data_sets')) 5 | addpath(genpath('./routines')) 6 | 7 | load('Cylinder_wake_data.mat') % load the data 8 | %% Sort the physical coordinates 9 | x=(x-obst_x)/d; y=(y-obst_y+2.5)/d; x=x-4; 10 | [~,I]=sort(abs(x+1i*y),'ascend'); 11 | 12 | %% Collect data at a single point 13 | N = 100; 14 | M = 24*5; 15 | D = DATA(I(1),1:(M+N)); 16 | 17 | %% Form the Hankel matrices 18 | X = zeros(N,M); X(1,:) = D(1:M); 19 | Y = zeros(N,M); Y(1,:) = D((1:M)+1); 20 | 21 | for jj = 2:N 22 | X(jj,:) = D(jj:(M+jj-1)); 23 | Y(jj,:) = D((jj+1):(M+jj)); 24 | end 25 | 26 | %% Perform exact DMD 27 | [~,LAM] = exactDMD(X,Y,39); 28 | 29 | %% Plot the singular values 30 | S = svd(X); 31 | figure 32 | semilogy(S,'b.','markersize',15) 33 | title('Singular Values','interpreter','latex','fontsize',18) 34 | ax=gca; ax.FontSize=18; 35 | exportgraphics(gcf,'saved_figures/Hankel_SVD.pdf','ContentType','vector','BackgroundColor','none') 36 | 37 | %% Plot the eigenvalues 38 | figure 39 | plot(cos(0:0.01:2*pi),sin(0:0.01:2*pi),'-k') 40 | hold on 41 | plot(real(LAM),imag(LAM),'b.','markersize',15) 42 | axis equal 43 | axis([-1.15,1.15,-1.15,1.15]) 44 | title('Hankel-DMD Eigenvalues','interpreter','latex','fontsize',18) 45 | xlabel('$\mathrm{Re}(\lambda)$','interpreter','latex','fontsize',18) 46 | ylabel('$\mathrm{Im}(\lambda)$','interpreter','latex','fontsize',18) 47 | ax=gca; ax.FontSize=18; 48 | exportgraphics(gcf,'saved_figures/Hankel_eigenvalues.pdf','ContentType','vector','BackgroundColor','none') 49 | 50 | 51 | %% Compute the error in the eigenvalues with M 52 | Mvec = 11:100; 53 | Er = 0*Mvec; 54 | N=100; 55 | 56 | M = 24*10; 57 | ind = (1:M); 58 | X = DATA(1:160000,ind); 59 | Y = DATA(1:160000,ind+1); 60 | 61 | [~,LAM0] = exactDMD(X,Y,41); 62 | LAM0 = imag(log(LAM0))*1i; 63 | [~,II] = sort(abs(LAM0),'ascend'); 64 | LAM0 = LAM0(II(1:11)); 65 | 66 | ct = 1; 67 | for M = Mvec 68 | D = DATA(I(1),1:(M+N)); 69 | X = zeros(N,M); X(1,:) = D(1:M); 70 | Y = zeros(N,M); Y(1,:) = D((1:M)+1); 71 | for jj = 2:N 72 | X(jj,:) = D(jj:(M+jj-1)); 73 | Y(jj,:) = D((jj+1):(M+jj)); 74 | end 75 | [~,LAM] = exactDMD(X,Y,39); 76 | LAM = log(LAM); 77 | [~,II] = sort(abs(LAM),'ascend'); 78 | LAM = LAM(II(1:11)); 79 | Er(ct) = VecDist(LAM0,LAM); 80 | ct=ct+1; 81 | end 82 | 83 | figure 84 | semilogy(Mvec,Er,'b','linewidth',2) 85 | xlim([10,100]) 86 | 87 | title('Relative Eigenvalue Error','interpreter','latex','fontsize',18) 88 | xlabel('$M$','interpreter','latex','fontsize',18) 89 | ax=gca; ax.FontSize=18; 90 | exportgraphics(gcf,'saved_figures/Hankel_error.pdf','ContentType','vector','BackgroundColor','none') 91 | 92 | 93 | 94 | 95 | function a = VecDist(P,Q) 96 | %%% CURRENTLY ASSUMES LENGTH OF P AND Q ARE EQUAL %%% 97 | P = P(:); Q = Q(:); Q = flipud(Q); 98 | 99 | C = zeros(length(P),length(Q)); 100 | for ii = 1:length(P) 101 | for jj = 1:length(Q) 102 | C(ii,jj)=abs(P(ii)-Q(jj))^2; 103 | end 104 | end 105 | 106 | M = matchpairs(C,1000); 107 | 108 | P = P(M(:,1)); 109 | Q = Q(M(:,2)); 110 | [~,I] = sort(abs(P),'ascend'); 111 | P = P(I(1:11)); 112 | Q = Q(I(1:11)); 113 | 114 | 115 | a = norm(P(:)-Q(:))/norm(P(:)); 116 | 117 | end 118 | -------------------------------------------------------------------------------- /Lorenz_example.m: -------------------------------------------------------------------------------- 1 | clear 2 | close all 3 | 4 | addpath(genpath('./data_sets')) 5 | addpath(genpath('./routines')) 6 | 7 | %% Set parameters for numerical simulation 8 | options = odeset('RelTol',1e-14,'AbsTol',1e-14); 9 | SIGMA=10; BETA=8/3; RHO=28; 10 | ODEFUN=@(t,y) [SIGMA*(y(2)-y(1));y(1).*(RHO-y(3))-y(2);y(1).*y(2)-BETA*y(3)]; 11 | 12 | %% Set parameters 13 | 14 | for N=[10:10:50,100] % number of delay embeddings 15 | rng(1) 16 | M=5*10^5; % number of data points 17 | dt=0.001; % time step for trajectory sampling 18 | dt2=0.2; % time step for delays 19 | h=dt2/dt; 20 | 21 | %% Produce the data 22 | Y0=(rand(3,1)-0.5)*4; 23 | [~,Y0]=ode45(ODEFUN,[0.000001 1, 100],Y0,options); Y0 = Y0(end,:)'; % sample after when on the attractor 24 | [~,DATA]=ode45(ODEFUN,[0.000001 (1:((M+h*(N+1))))*dt],Y0,options); 25 | 26 | %% Use delay embedding 27 | PX=zeros(M,N); PX(:,1)=DATA(1:M,1); 28 | PX2=zeros(M,N); PX2(:,1)=DATA(1:M,2); 29 | PX3=zeros(M,N); PX3(:,1)=DATA(1:M,3); 30 | PY=zeros(M,N); PY(:,1)=DATA((1:M)+1,1); 31 | PY2=zeros(M,N); PY2(:,1)=DATA((1:M)+1,2); 32 | PY3=zeros(M,N); PY3(:,1)=DATA((1:M)+1,3); 33 | 34 | for j=2:N 35 | PX(:,j)=DATA((1:M)+h*(j-1),1); 36 | PX2(:,j)=DATA((1:M)+h*(j-1),2); 37 | PX3(:,j)=DATA((1:M)+h*(j-1),3); 38 | PY(:,j)=DATA((1:M)+1+h*(j-1),1); 39 | PY2(:,j)=DATA((1:M)+1+h*(j-1),2); 40 | PY3(:,j)=DATA((1:M)+1+h*(j-1),3); 41 | end 42 | 43 | K = [PX,PX2,PX3]\[PY,PY2,PY3]; 44 | [V,LAM] = eig(K,'vector'); 45 | [~,I]=sort(abs(angle(LAM)+0.006),'ascend'); 46 | V =V(:,I); LAM =LAM(I); 47 | 48 | w = log(LAM)/dt; 49 | 50 | figure 51 | plot([-40,40],[0,0],'-k') 52 | hold on 53 | plot(imag(w),real(w),'b.','markersize',15) 54 | title('DMD Eigenvalues','interpreter','latex','fontsize',18) 55 | xlabel('$\arg(\lambda)/\Delta t$','interpreter','latex','fontsize',18) 56 | ylabel('$\log(|\lambda|)/\Delta t$','interpreter','latex','fontsize',18) 57 | ax=gca; ax.FontSize=18; 58 | ylim([-0.2,0.2]); xlim([-25,25]) 59 | pbaspect([4 1.5 1]) 60 | text(-20,0.1,sprintf('$N=%d$',N),'interpreter','latex','fontsize',18) 61 | exportgraphics(gcf,sprintf('saved_figures/lorenz_eigenvalues%d.pdf',N),'ContentType','vector','BackgroundColor','none') 62 | 63 | for j=1 64 | figure 65 | u = [PX,PX2,PX3]*V(:,1); 66 | u = -real(u(1:min(10^6,M),:)); 67 | u = u/norm(u); 68 | [~,I]=sort(u,'ascend'); 69 | scatter3(PX(I,1),PX2(I,1),PX3(I,1),6,u(I),'filled'); 70 | colormap(brighten(brewermap([],'RdYlBu'),-0.3)) 71 | clim([-3*mean(abs(u)),3*mean(abs(u))]) 72 | view(gca,[13.1786087602293 -1.28469255513244]); 73 | axis tight; grid off; axis off 74 | axis equal 75 | set(gca,'DataAspectRatio',[1 1 1]); 76 | xlabel('$X$','interpreter','latex','fontsize',14) 77 | ylabel('$Y$','interpreter','latex','fontsize',14) 78 | zlabel('$Z$','interpreter','latex','fontsize',14,'rotation',0) 79 | text(-5,0,43,sprintf('$N=%d$',N),'interpreter','latex','fontsize',18) 80 | 81 | exportgraphics(gcf,sprintf('saved_figures/lorenz_efun_angle_%d_N%d.png',j,N),'ContentType','image','BackgroundColor','w','Resolution',200) 82 | pause(1) 83 | end 84 | end 85 | 86 | 87 | -------------------------------------------------------------------------------- /Noisy_cylinder_wake_example.m: -------------------------------------------------------------------------------- 1 | clear 2 | close all 3 | 4 | addpath(genpath('./data_sets')) 5 | addpath(genpath('./routines')) 6 | addpath(genpath('./saved_data_from_runs')) 7 | 8 | %% UNCOMMENT TO RUN CODE 9 | % % load('Cylinder_wake_data.mat') 10 | % % DATA = DATA(1:160000,:); 11 | % % DATA = (DATA- mean(DATA,2) )./std(DATA,[],2);% 12 | % % %% Set the parameters 13 | % % Mvec = 24*(5:41); 14 | % % sigma = 0.4; 15 | % % Ns = 1;%100; 16 | % % r = 11; % rank 17 | % % 18 | % % %% Compute Error 19 | % % rng(1) 20 | % % ct = 1; 21 | % % X = DATA(:,1:max(Mvec)); 22 | % % Y = DATA(:,2:(max(Mvec)+1)); 23 | % % 24 | % % 25 | % % [~,LAM] =tlsDMD(X,Y,15); 26 | % % E = imag(log(LAM))*1i; 27 | % % [~,I] = sort(abs(E),'ascend'); 28 | % % E = E(I(1:r)); 29 | % % Eval = cell(length(Mvec),Ns,5); 30 | % % DIST = zeros(length(Mvec),Ns,6); 31 | % % 32 | % % 33 | % % for M = Mvec 34 | % % X = DATA(:,1:M); 35 | % % Y = DATA(:,2:(M+1)); 36 | % % 37 | % % for jj = 1:Ns 38 | % % jj 39 | % % ct 40 | % % Nr = sigma*randn(size(DATA,1),M+1); 41 | % % X = DATA(:,1:M) + Nr(:,1:end-1); 42 | % % Y = DATA(:,2:(M+1)) + Nr(:,2:end); 43 | % % 44 | % % [~,LAM] = exactDMD(X,Y,r); 45 | % % LAM = log(LAM); 46 | % % [~,I] = sort(abs(LAM),'ascend'); 47 | % % LAM = LAM(I(1:r)); 48 | % % Eval{ct,jj,1} = LAM; 49 | % % DIST(ct,jj,1) = VecDist(E,LAM); 50 | % % 51 | % % [~,LAM] = fbDMD(X,Y,r); 52 | % % LAM = log(LAM); 53 | % % Eval{ct,jj,2} = LAM; 54 | % % DIST(ct,jj,2) = VecDist(E,LAM); 55 | % % 56 | % % [~,LAM] = tlsDMD(X,Y,r); 57 | % % LAM = log(LAM); 58 | % % Eval{ct,jj,3} = LAM; 59 | % % DIST(ct,jj,3) = VecDist(E,LAM); 60 | % % 61 | % % [~,LAM] = optdmd([X,Y(:,end)],(0:M)/10,r,2,varpro_opts('ifprint',0),1i*imag(LAM)*10); 62 | % % Eval{ct,jj,4} = LAM/10; 63 | % % DIST(ct,jj,4) = VecDist(E,LAM/10); 64 | % % end 65 | % % ct = ct+1 66 | % % end 67 | 68 | 69 | 70 | %% 71 | load('Noisy_Cylinder_Results.mat') 72 | figure 73 | 74 | mm = mean(DIST(:,:,1),2); 75 | st = std(DIST(:,:,1),[],2); 76 | h=loglog(Mvec(:),mm,'k'); 77 | hold on 78 | errorbar(Mvec(:),mm,st,'.-k','linewidth',1,'markersize',12) 79 | h. HandleVisibility='off'; 80 | 81 | mm = mean(DIST(:,:,2),2); 82 | st = std(DIST(:,:,2),[],2); 83 | errorbar(Mvec(:),mm,st,'.-g','linewidth',1,'markersize',12) 84 | 85 | mm = mean(DIST(:,:,3),2); 86 | st = std(DIST(:,:,3),[],2); 87 | errorbar(Mvec(:),mm,st,'.-r','linewidth',1,'markersize',12) 88 | 89 | mm = mean(DIST(:,:,4),2); 90 | st = std(DIST(:,:,4),[],2); 91 | errorbar(Mvec(:),mm,st,'.-b','linewidth',1,'markersize',12) 92 | xlim([100,1000]) 93 | ylim([10^(-5),2]) 94 | 95 | title('Mean Relative Eigenvalue Error','interpreter','latex','fontsize',18) 96 | xlabel('$M$','interpreter','latex','fontsize',18) 97 | grid on 98 | ax=gca; ax.FontSize=18; 99 | 100 | legend({'exactDMD','fbDMD','tlsDMD','optDMD'},'interpreter','latex','fontsize',12,'location','southwest') 101 | exportgraphics(gcf,'cylinder_denoised.pdf','ContentType','vector','BackgroundColor','none') 102 | 103 | function a = VecDist(P,Q) 104 | %%% CURRENTLY ASSUMES LENGTH OF P AND Q ARE EQUAL %%% 105 | P = P(:); Q = Q(:); Q = flipud(Q); 106 | 107 | C = zeros(length(P),length(Q)); 108 | for ii = 1:length(P) 109 | for jj = 1:length(Q) 110 | C(ii,jj)=abs(P(ii)-Q(jj))^2; 111 | end 112 | end 113 | 114 | M = matchpairs(C,1000); 115 | 116 | P = P(M(:,1)); 117 | Q = Q(M(:,2)); 118 | [~,I] = sort(abs(P),'ascend'); 119 | P = P(I(1:11)); 120 | Q = Q(I(1:11)); 121 | 122 | % a = sqrt(norm(P-Q)^2/length(P)); 123 | a = norm(P(:)-Q(:))/norm(P(:)); 124 | 125 | 126 | end 127 | -------------------------------------------------------------------------------- /Rossler_compactification_example.m: -------------------------------------------------------------------------------- 1 | clear 2 | close all 3 | 4 | addpath(genpath('./data_sets')) 5 | addpath(genpath('./routines')) 6 | 7 | load('rossler_data.mat') 8 | % This data was produced using the code of Claire Valva: https://github.com/clairevalva/resolvent_minimal 9 | % which also uses the code of Dimitrios Giannakis: https://github.com/dg227/NLSA 10 | 11 | %% Plot the approximate eigenfunctions and various trajectories 12 | T = round(150/dt); 13 | 14 | for IND = 1:3 15 | example_eigenfs(:,IND)=example_eigenfs(:,IND)/mean(abs(example_eigenfs(1:T,IND))); 16 | 17 | figure 18 | u = imag(example_eigenfs(:,IND)); 19 | scatter3(orig_data(1,:),orig_data(2,:),orig_data(3,:),7,u,'filled'); 20 | colormap(brighten(inferno,0.3)) 21 | colormap(brighten(brewermap([],'RdYlBu'),-0.4)) 22 | clim([-3*mean(abs(u)),3*mean(abs(u))]) 23 | axis tight; grid off; axis off 24 | axis equal 25 | set(gca,'DataAspectRatio',[1 1 1]); 26 | xlabel('$X$','interpreter','latex','fontsize',14) 27 | ylabel('$Y$','interpreter','latex','fontsize',14) 28 | zlabel('$Z$','interpreter','latex','fontsize',14,'rotation',0) 29 | view(gca,[3.7779 17.9883]); 30 | if IND ==1 31 | title({'$\sigma\approx 1.0261$'},'interpreter','latex','fontsize',18) 32 | elseif IND==2 33 | title({'$\sigma\approx 2.0516$'},'interpreter','latex','fontsize',18) 34 | else 35 | title({'$\sigma\approx 0.3785$'},'interpreter','latex','fontsize',18) 36 | end 37 | exportgraphics(gcf,sprintf('saved_figures/rossler_efun_%d.png',IND),'ContentType','image','BackgroundColor','w','Resolution',200) 38 | 39 | figure 40 | plot((0:length(u)-1)*dt,real(example_eigenfs(:,IND)),'b','linewidth',2) 41 | xlim([0,round(T*dt)]) 42 | xlabel('Time','interpreter','latex','fontsize',18) 43 | title('$\mathrm{Re}(\phi)$','interpreter','latex','fontsize',18) 44 | ax=gca; ax.FontSize=18; 45 | exportgraphics(gcf,sprintf('saved_figures/rossler_wave_%d.png',IND),'ContentType','image','BackgroundColor','w','Resolution',200) 46 | 47 | 48 | figure 49 | plot(real(example_eigenfs(1:T,IND)),imag(example_eigenfs(1:T,IND)),'b','linewidth',2) 50 | hold on 51 | plot(cos(0:0.001:2*pi),sin(0:0.001:2*pi),'g','linewidth',3) 52 | axis equal 53 | axis([-1,1,-1,1]*1.6) 54 | % title('DMD Eigenvalues','interpreter','latex','fontsize',18) 55 | xlabel('$\mathrm{Re}(\phi)$','interpreter','latex','fontsize',18) 56 | ylabel('$\mathrm{Im}(\phi)$','interpreter','latex','fontsize',18) 57 | ax=gca; ax.FontSize=18; 58 | exportgraphics(gcf,sprintf('saved_figures/rossler_spiral_%d.png',IND),'ContentType','image','BackgroundColor','w','Resolution',200) 59 | close all 60 | end 61 | 62 | 63 | 64 | %% Residual plot 65 | clear 66 | load('rossler_data.mat') 67 | T = round(1000/dt); 68 | res = zeros(T,3); 69 | 70 | for IND = 1:3 71 | example_eigenfs(:,IND)=example_eigenfs(:,IND)/norm(example_eigenfs(:,IND)); 72 | lam=example_freqs(IND); 73 | for del = 1:T 74 | u1 = example_eigenfs(1:end-del,IND); 75 | u2 = example_eigenfs((1+del):end,IND); 76 | res(del,IND) = norm(u2-exp(1i*lam*dt*del)*u1)/norm(u2); 77 | end 78 | end 79 | %% 80 | figure 81 | loglog((1:T)*dt,res,'linewidth',2) 82 | hold on 83 | plot([1,1]/0.071,[0.001,10],'--k','linewidth',2) 84 | xlabel('Time (s)','interpreter','latex','fontsize',18) 85 | title('$\|\phi(t)-\exp(i\sigma t)\phi(0)\|/\|\phi\|$','interpreter','latex','fontsize',18) 86 | legend({'$\sigma\approx 1.0261$','$\sigma\approx 2.0516$','$\sigma\approx 0.3785$','Lyapunov Time'},'interpreter','latex','fontsize',16,'location','northwest') 87 | ax=gca; ax.FontSize=18; 88 | xlim([0.01,1000]) 89 | ylim([min(res(:)/1.1),2]) 90 | exportgraphics(gcf,'saved_figures/rossler_residuals.pdf','ContentType','vector','BackgroundColor','none') 91 | 92 | 93 | return 94 | 95 | 96 | -------------------------------------------------------------------------------- /Lorenz_noise_reduction_example.m: -------------------------------------------------------------------------------- 1 | clear 2 | close all 3 | 4 | addpath(genpath('./data_sets')) 5 | addpath(genpath('./routines')) 6 | addpath(genpath('./saved_data_from_runs')) 7 | 8 | options = odeset('RelTol',1e-14,'AbsTol',1e-14); 9 | 10 | %% Set parameters 11 | 12 | N = 10; 13 | Mvec = round(10.^(3:0.2:6)); 14 | 15 | % Er = zeros(length(Mvec),3,50); 16 | % rng(1) 17 | % 18 | % for S = 1:size(Er,3) 19 | % %% Produce the data 20 | % dt=0.001; % time step for trajectory sampling 21 | % dt2=0.2; % time step for delays 22 | % h=dt2/dt; 23 | % 24 | % SIGMA=10; BETA=8/3; RHO=28; 25 | % ODEFUN=@(t,y) [SIGMA*(y(2)-y(1));y(1).*(RHO-y(3))-y(2);y(1).*y(2)-BETA*y(3)]; 26 | % Y0=(rand(3,1)-0.5)*4; 27 | % [~,Y0]=ode45(ODEFUN,[0.000001 1, 100],Y0,options); Y0 = Y0(end,:)'; % sample after when on the attractor 28 | % [~,DATA]=ode45(ODEFUN,[0.000001 (1:((max(Mvec)+h*(N+1))))*dt],Y0,options); 29 | % ctt = 1; 30 | % for M = Mvec % number of delay embeddings 31 | % 32 | % 33 | % 34 | % 35 | % 36 | % 37 | % %% Use delay embedding 38 | % PX=zeros(M,N); PX(:,1)=DATA(1:M,1); 39 | % PX2=zeros(M,N); PX2(:,1)=DATA(1:M,2); 40 | % PX3=zeros(M,N); PX3(:,1)=DATA(1:M,3); 41 | % PY=zeros(M,N); PY(:,1)=DATA((1:M)+1,1); 42 | % PY2=zeros(M,N); PY2(:,1)=DATA((1:M)+1,2); 43 | % PY3=zeros(M,N); PY3(:,1)=DATA((1:M)+1,3); 44 | % 45 | % for j=2:N 46 | % PX(:,j)=DATA((1:M)+h*(j-1),1); 47 | % PX2(:,j)=DATA((1:M)+h*(j-1),2); 48 | % PX3(:,j)=DATA((1:M)+h*(j-1),3); 49 | % PY(:,j)=DATA((1:M)+1+h*(j-1),1); 50 | % PY2(:,j)=DATA((1:M)+1+h*(j-1),2); 51 | % PY3(:,j)=DATA((1:M)+1+h*(j-1),3); 52 | % end 53 | % %% 54 | % G = (PX'*PX)/M; 55 | % A = (PX'*PY)/M; 56 | % L = (PY'*PY)/M; 57 | % G=([PX,PX2,PX3])'*([PX,PX2,PX3])/M; 58 | % A=([PX,PX2,PX3])'*([PY,PY2,PY3])/M; 59 | % K = [PX,PX2,PX3]\[PY,PY2,PY3]; 60 | % % 61 | % % [~,V,LAM] = mpEDMD(G,A); LAM=diag(LAM);LAM=LAM(:); 62 | % % [~,I]=sort(abs((LAM)),'descend'); 63 | % 64 | % % 65 | % 66 | % % 67 | % [~,LAM] = eig(K,'vector'); 68 | % w = log(LAM)/dt; 69 | % Er(ctt,1,S)=mean(abs(real(w))); 70 | % 71 | % 72 | % 73 | % % [~,LAM] = fbDMD(([PX,PX2,PX3])',([PY,PY2,PY3])',size(PX,2)*3); 74 | % % w = log(LAM)/dt; 75 | % % Er(ctt,2)=mean(abs(real(w))); 76 | % 77 | % [~,LAM] = tlsDMD(([PX,PX2,PX3])',([PY,PY2,PY3])',size(PX,2)*3); 78 | % w = log(LAM)/dt; 79 | % Er(ctt,2,S)=mean(abs(real(w))); 80 | % 81 | % 82 | % [~,LAM] = optdmd(([PX,PX2,PX3])',(1:M)*dt,3*N,2,varpro_opts('maxiter',100),1i*imag(log(LAM)/dt));%varpro_opts('ifprint',0,'maxiter',10,'tol',10^(-9)) 83 | % w = LAM; 84 | % Er(ctt,3,S)=mean(abs(real(w))); 85 | % close all 86 | % loglog(Mvec,mean(squeeze(Er(:,1,:)),2),'k'); 87 | % hold on 88 | % loglog(Mvec,mean(squeeze(Er(:,2,:)),2),'g'); 89 | % loglog(Mvec,mean(squeeze(Er(:,3,:)),2),'b'); 90 | % pause(0.1) 91 | % 92 | % ctt = ctt+1; 93 | % end 94 | % 95 | % 96 | % 97 | % 98 | % end 99 | 100 | %% Plot the figure 101 | load('Lorenz_Denoise.mat') 102 | figure 103 | loglog(Mvec,Er(:,1),'.-k','linewidth',1,'markersize',12) 104 | hold on 105 | loglog(Mvec,Er(:,2),'.-r','linewidth',1,'markersize',12) 106 | loglog(Mvec,Er(:,3),'.-b','linewidth',1,'markersize',12) 107 | legend({'exactDMD','tlsDMD','optDMD'},'interpreter','latex','fontsize',12,'location','southwest') 108 | title('Mean $|\log(|\lambda|)/\Delta t|$','interpreter','latex','fontsize',18) 109 | xlabel('$M$','interpreter','latex','fontsize',18) 110 | ax=gca; ax.FontSize=18; 111 | % ylim([10^(-15),1]) 112 | exportgraphics(gcf,'lorenz_denoised.pdf','ContentType','vector','BackgroundColor','none') 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /Duffing_example.m: -------------------------------------------------------------------------------- 1 | clear 2 | close all 3 | 4 | addpath(genpath('./saved_data_from_runs')) 5 | 6 | % NB general code for ResDMD can be found at: https://github.com/MColbrook/Residual-Dynamic-Mode-Decomposition 7 | 8 | %% UNCOMMENT TO RUN 9 | % rng(1) 10 | % %% Set parameters 11 | % M1=10^3; % number of data points 12 | % M2=50; 13 | % delta_t=0.25; % time step 14 | % ODEFUN=@(t,y) [y(2);y(1)-y(1).^3]; 15 | % options = odeset('RelTol',1e-12,'AbsTol',1e-12); 16 | % N=100; 17 | % PHI = @(r) exp(-r); % radial basis function 18 | % 19 | % x_pts = -1.2:0.04:1.2; y_pts = -0.05:0.04:1.2; 20 | % v=(10.^(-2:0.1:1)); 21 | % 22 | % %% Produce the data 23 | % X=[]; 24 | % Y=[]; 25 | % for jj=1:M1 26 | % Y0=(rand(2,1)-0.5)*4; 27 | % [~,Y1]=ode45(ODEFUN,[0 0.000001 (1:(3+M2))*delta_t],Y0,options); 28 | % Y1=Y1'; 29 | % X = [X,Y1(:,[1,3:M2+1])]; 30 | % Y = [Y,Y1(:,3:M2+2)]; 31 | % end 32 | % M = M1*M2; 33 | % 34 | % d=mean(vecnorm(X-mean(X')')); % scaling for radial function 35 | % 36 | % [~,C] = kmeans([X';Y'],N); % find centers 37 | % 38 | % PX = zeros(M,N); PY = zeros(M,N); 39 | % 40 | % for j = 1:N 41 | % R = sqrt((X(1,:)-C(j,1)).^2+(X(2,:)-C(j,2)).^2); 42 | % PX(:,j) = PHI(R(:)/d); 43 | % R = sqrt((Y(1,:)-C(j,1)).^2+(Y(2,:)-C(j,2)).^2); 44 | % PY(:,j) = PHI(R(:)/d); 45 | % end 46 | % %% EDMD 47 | % K=PX(1:M,:)\PY(1:M,:); 48 | % [V,LAM]=eig(K,'vector'); 49 | % 50 | % %% ResDMD for pseudospectrum, code available here: https://github.com/MColbrook/Residual-Dynamic-Mode-Decomposition 51 | % 52 | % z_pts=kron(x_pts,ones(length(y_pts),1))+1i*kron(ones(1,length(x_pts)),y_pts(:)); z_pts=z_pts(:); 53 | % 54 | % RES = KoopPseudoSpecQR(PX,PY,1/M,z_pts,'Parallel','off'); 55 | % RES = reshape(RES,length(y_pts),length(x_pts)); 56 | 57 | %% 58 | close all 59 | clear;load('duffing3.mat') 60 | 61 | figure 62 | contourf(reshape(real(z_pts),length(y_pts),length(x_pts)),reshape(imag(z_pts),length(y_pts),length(x_pts)),log10(max(min(v),real(RES))),log10(v)); 63 | hold on 64 | contourf(reshape(real(z_pts),length(y_pts),length(x_pts)),-reshape(imag(z_pts),length(y_pts),length(x_pts)),log10(max(min(v),real(RES))),log10(v)); 65 | cbh=colorbar; 66 | cbh.Ticks=log10([0.005,0.01,0.1,1]); 67 | cbh.TickLabels=[0,0.01,0.1,1]; 68 | clim([log10(min(v)),0]); 69 | reset(gcf) 70 | set(gca,'YDir','normal') 71 | colormap gray 72 | ax=gca; ax.FontSize=18; axis equal tight; axis([x_pts(1),x_pts(end),-y_pts(end),y_pts(end)]) 73 | xlabel('$\mathrm{Re}(\lambda)$','interpreter','latex','fontsize',18) 74 | ylabel('$\mathrm{Im}(\lambda)$','interpreter','latex','fontsize',18) 75 | title(sprintf('$\\mathrm{Sp}_\\epsilon(\\mathcal{K})$, $N=%d$',N),'interpreter','latex','fontsize',18) 76 | 77 | exportgraphics(gcf,sprintf('saved_figures/duffing_pseudospec_N%d.png',N),'ContentType','image','BackgroundColor','w','Resolution',200) 78 | 79 | figure 80 | plot(cos(0:0.001:2*pi),sin(0:0.001:2*pi),'g','linewidth',2) 81 | hold on 82 | plot(real(LAM),imag(LAM),'.r','markersize',12) 83 | ax=gca; ax.FontSize=18; axis equal tight; axis([x_pts(1),x_pts(end),-y_pts(end),y_pts(end)]) 84 | xlabel('$\mathrm{Re}(\lambda)$','interpreter','latex','fontsize',18) 85 | ylabel('$\mathrm{Im}(\lambda)$','interpreter','latex','fontsize',18) 86 | title(sprintf('DMD Eigenvalues, $N=%d$',N),'interpreter','latex','fontsize',18) 87 | 88 | exportgraphics(gcf,sprintf('saved_figures/duffing_evals_N%d.pdf',N),'ContentType','vector','BackgroundColor','w') 89 | 90 | res = (vecnorm(PY*V-PX*V*diag(LAM))./vecnorm(PX*V))'; % ResDMD for residuals 91 | 92 | figure 93 | histogram(res,10,'FaceColor',[0.8500 0.3250 0.0980]) 94 | xlabel('$\|(\mathcal{K}-\lambda_j I)g_j\|$','interpreter','latex','fontsize',18) 95 | title(sprintf('Histogram of Errors, $N=%d$',N),'interpreter','latex','fontsize',18) 96 | ax=gca; ax.FontSize=18; 97 | exportgraphics(gcf,sprintf('saved_figures/duffing_hist_N%d.pdf',N),'ContentType','vector','BackgroundColor','w') 98 | 99 | %% Plot the eigenfunctions 100 | [~,I]= sort(abs(1-LAM),'ascend'); 101 | LAM = LAM(I); V = V(:,I); 102 | close all 103 | 104 | xvec = (-2:0.25:0) - 0.01; 105 | xvec = [xvec,-xvec]; 106 | 107 | for j=1:14 108 | 109 | u=real([PX;PY]*V(:,j)); 110 | figure1=figure; 111 | axes1 = axes('Parent',figure1); 112 | hold(axes1,'on'); 113 | scatter([X(1,:),Y(1,:)],[X(2,:),Y(2,:)],5,u,'filled'); 114 | colormap(brighten(brewermap([],'RdYlBu'),-0.3)) 115 | clim([min(u),max(u)]) 116 | 117 | for jj=1:length(xvec) 118 | ODEFUN=@(t,y) [y(2);y(1)-y(1).^3]; 119 | [~,Y1]=ode45(ODEFUN,0:0.01:20,[xvec(jj),0],options); 120 | plot(Y1(:,1),Y1(:,2),'k','linewidth',1); 121 | end 122 | 123 | axis(axes1,'tight'); grid off; axis off%hold(axes1,'off'); 124 | set(axes1,'DataAspectRatio',[1 1 1]); 125 | pause(1) 126 | exportgraphics(gcf,sprintf('saved_figures/duffing_efun_%d.png',j),'ContentType','image','BackgroundColor','w','Resolution',200) 127 | close all 128 | end 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /Compressed_cylinder_wake_example.m: -------------------------------------------------------------------------------- 1 | clear 2 | close all 3 | 4 | addpath(genpath('./data_sets')) 5 | addpath(genpath('./routines')) 6 | 7 | load('Cylinder_wake_data.mat') 8 | %% Set parameters 9 | M = 24*5; 10 | pvec = 1:40; % sparsity parameter for convergence plot 11 | X = DATA(1:800*200,1:M); 12 | Y = DATA(1:800*200,2:(M+1)); 13 | 14 | %% Run standard DMD for reference 15 | [K,LAM,Phi] = exactDMD(X,Y,47); 16 | [~,I] = sort(abs(1-LAM),'ascend'); 17 | Phi = Phi(:,I); LAM = LAM(I); 18 | 19 | % %% Run exactDMD, cDMD and rDMD - uncomment to run 20 | % 21 | % rng(1); 22 | % Er_lam = zeros(6,length(pvec),25,3); 23 | % Er_vec = zeros(6,length(pvec),25,3); 24 | % tm = zeros(length(pvec),1,3); 25 | % % profile on 26 | % for s = 1:size(Er_lam,3) 27 | % 28 | % ct = 1; % counting 29 | % for p = pvec 30 | % 31 | % tic 32 | % [~,LAM1,Phi1] = exactDMD(X,Y,p); 33 | % tm(ct,s,1) = toc; 34 | % [~,I] = sort(abs(1-LAM1),'ascend'); 35 | % Phi1 = Phi1(:,I); LAM1 = LAM1(I); 36 | % 37 | % Er_lam(1,ct,s,1) = min(abs(LAM(1)-LAM1)); 38 | % Er_vec(1,ct,s,1) = subspacea(Phi(:,1),Phi1(:,1)); 39 | % for jj=1:5 40 | % if ct>2*jj 41 | % Er_lam(jj+1,ct,s,1) = min(abs(LAM(2*jj)-LAM1)); 42 | % Er_vec(jj+1,ct,s,1) = 5; 43 | % for kk = 1:min(2*jj+2,p) 44 | % Er_vec(jj+1,ct,s,1) = min(Er_vec(jj+1,ct,s,1),subspacea(Phi(:,2*jj),Phi1(:,kk))); 45 | % end 46 | % end 47 | % end 48 | % 49 | % tic 50 | % [C,theta] = rand_meas(p,800,200); 51 | % Xc = C*X; 52 | % Yc = C*Y; 53 | % [W1,LAM1,~,~,V1,Sinv1] = standard_DMD(Xc,Yc,p); 54 | % Phi1 = Y*V1*Sinv1*W1; 55 | % tm(ct,s,2) = toc; 56 | % [~,I] = sort(abs(1-LAM1),'ascend'); 57 | % Phi1 = Phi1(:,I); LAM1 = LAM1(I); 58 | % 59 | % Er_lam(1,ct,s,2) = min(abs(LAM(1)-LAM1)); 60 | % Er_vec(1,ct,s,2) = subspacea(Phi(:,1),Phi1(:,1)); 61 | % for jj=1:5 62 | % if ct>2*jj 63 | % Er_lam(jj+1,ct,s,2) = min(abs(LAM(2*jj)-LAM1)); 64 | % Er_vec(jj+1,ct,s,2) = 5; 65 | % for kk = 1:min(2*jj+2,p) 66 | % Er_vec(jj+1,ct,s,2) = min(Er_vec(jj+1,ct,s,2),subspacea(Phi(:,2*jj),Phi1(:,kk))); 67 | % end 68 | % end 69 | % end 70 | % 71 | % [~,LAM1,tm(ct,s,3),Phi1] = rDMD(X,Y,p,10); 72 | % [~,I] = sort(abs(1-LAM1),'ascend'); 73 | % Phi1 = Phi1(:,I); LAM1 = LAM1(I); 74 | % 75 | % Er_lam(1,ct,s,3) = min(abs(LAM(1)-LAM1)); 76 | % Er_vec(1,ct,s,3) = subspacea(Phi(:,1),Phi1(:,1)); 77 | % for jj=1:5 78 | % if ct>2*jj 79 | % Er_lam(jj+1,ct,s,3) = min(abs(LAM(2*jj)-LAM1)); 80 | % Er_vec(jj+1,ct,s,3) = 5; 81 | % for kk = 1:min(2*jj+2,p) 82 | % Er_vec(jj+1,ct,s,3) = min(Er_vec(jj+1,ct,s,3),subspacea(Phi(:,2*jj),Phi1(:,kk))); 83 | % end 84 | % end 85 | % end 86 | % end 87 | % 88 | % end 89 | 90 | 91 | %% 92 | load('Cylinder_Timings.mat') 93 | close all 94 | figure; 95 | semilogy(pvec,mean(mean(Er_lam(:,:,:,2),3),1),'linewidth',2) 96 | hold on 97 | semilogy(pvec,mean(mean(Er_vec(:,:,:,2),3),1),'linewidth',2) 98 | legend({'Eigenvalue Error','DMD Mode Error'},'interpreter','latex','fontsize',16,'location','southwest') 99 | title('cDMD','interpreter','latex','fontsize',18) 100 | xlabel('$p$','interpreter','latex','fontsize',18) 101 | ax=gca; ax.FontSize=18; 102 | ylim([10^(-15),1]) 103 | exportgraphics(gcf,'saved_figures/cylinder_compressed_error1.pdf','ContentType','vector','BackgroundColor','none') 104 | 105 | figure; 106 | semilogy(pvec,mean(mean(Er_lam(:,:,:,3),3),1),'linewidth',2) 107 | hold on 108 | semilogy(pvec,mean(mean(Er_vec(:,:,:,3),3),1),'linewidth',2) 109 | legend({'Eigenvalue Error','DMD Mode Error'},'interpreter','latex','fontsize',16,'location','southwest') 110 | title('rDMD','interpreter','latex','fontsize',18) 111 | xlabel('$r$','interpreter','latex','fontsize',18) 112 | ax=gca; ax.FontSize=18; 113 | ylim([10^(-15),1]) 114 | exportgraphics(gcf,'saved_figures/cylinder_compressed_error2.pdf','ContentType','vector','BackgroundColor','none') 115 | 116 | figure 117 | loglog(mean(tm(:,:,1),2),mean(mean(Er_lam(:,:,:,1),3),1),'.','markersize',14) 118 | hold on 119 | loglog(mean(tm(:,:,2),2),mean(mean(Er_lam(:,:,:,2),3),1),'.','markersize',14) 120 | loglog(mean(tm(:,:,3),2),mean(mean(Er_lam(:,:,:,3),3),1),'.','markersize',14) 121 | legend({'exactDMD','cDMD','rDMD'},'interpreter','latex','fontsize',16,'location','southwest') 122 | xlabel('Time (s)','interpreter','latex','fontsize',18) 123 | ylabel('Error','interpreter','latex','fontsize',18) 124 | grid on 125 | ax=gca; ax.FontSize=18; 126 | exportgraphics(gcf,'saved_figures/cylinder_compressed_error_times.pdf','ContentType','vector','BackgroundColor','none') 127 | 128 | %% Compressed sensing DMD 129 | 130 | rng(1); 131 | ct = 1; % counting 132 | for p = 800 133 | [C,theta] = rand_meas(p,800,200); 134 | Xc = C*X; 135 | Yc = C*Y; 136 | [W1,LAM1,~,~,V1,Sinv1] = standard_DMD(Xc,Yc,47); 137 | [~,I] = sort(abs(1-LAM1),'ascend'); 138 | W1 = W1(:,I); LAM1 = LAM1(I); 139 | Phi2 = zeros(size(Phi,1),6); 140 | Phic = Yc*V1*Sinv1*W1; 141 | for jj=1:6 142 | if 2*jj-1<=p 143 | z=CoSaMP( theta, Phic(:,2*jj-1),50, []); 144 | z2 = reshape(ifft2(ifftshift(reshape(z,800,200))),800*200,1)*sqrt(800*200); 145 | Phi2(:,jj) = z2; 146 | 147 | end 148 | end 149 | ct=ct+1 150 | end 151 | 152 | %% Plot reconstructed modes 153 | 154 | for jj=1:6 155 | C = real(reshape(Phi2(:,jj),800,200)); 156 | if jj==2 % just to make color schemes match 157 | C=-C; 158 | elseif jj==3 159 | C=-C; 160 | elseif jj==6 161 | C=-C; 162 | end 163 | vv=0.025:0.05:1; 164 | a=min(real(C(:))); 165 | b=max(real(C(:))); 166 | c=(a+b)/2; 167 | figure 168 | [~,h]=contourf((x-obst_x)/d,0.94*(y-obst_y)/d,real(C),[-vv,vv]*(b-a)+c,'edgecolor','none'); 169 | h.LineWidth = 0.001; 170 | colormap(brighten(brewermap([],'RdYlBu'),0.1)) 171 | axis equal 172 | hold on 173 | fill(obst_r*cos(0:0.01:2*pi)/d,obst_r*sin(0:0.01:2*pi)/d,'m','edgecolor','none') 174 | xlim([-2,10+0*max((x(:)-obst_x)/d)]) 175 | title(sprintf('Mode %d',jj-1),'interpreter','latex','fontsize',14) 176 | exportgraphics(gcf,sprintf('saved_figures/compressed_modes%d.pdf',jj),'BackgroundColor','none','Resolution',400) 177 | end 178 | 179 | 180 | 181 | 182 | 183 | 184 | function [C,Theta] = rand_meas(p,n1,n2) 185 | % Creates random measurement matrix for the compression 186 | C = zeros(p,n1*n2); 187 | Theta = zeros(p,n1*n2); 188 | for ii=1:p 189 | % xmeas= zeros(n1,n2); 190 | % xmeas(ceil(n1*rand),ceil(n2*rand)) = 1; 191 | xmeas = randn(n1,n2); 192 | C(ii,:) = reshape(xmeas,n1*n2,1); 193 | Theta(ii,:) = reshape(fftshift(fft2(xmeas)),n1*n2,1)'/sqrt(n1*n2); 194 | end 195 | end 196 | 197 | function [W,LAM,K,U,V,Sinv] = standard_DMD(X,Y,r) 198 | % Applies exact DMD 199 | [U,S,V] = svd(X,'econ'); 200 | r = min(rank(S),r); 201 | U = U(:,1:r); V = V(:,1:r); S = S(1:r,1:r); Sinv = diag(1./diag(S)); 202 | K = (U')*Y*V*Sinv; 203 | [W,LAM] = eig(K,'vector'); 204 | end 205 | -------------------------------------------------------------------------------- /Duffing_EDMD_example.m: -------------------------------------------------------------------------------- 1 | clear 2 | close all 3 | 4 | addpath(genpath('./data_sets')) 5 | addpath(genpath('./routines')) 6 | 7 | rng(1) 8 | %% Set parameters 9 | M1=10^3; M2=50; % number of data points 10 | delta_t=0.25; % time step 11 | ODEFUN=@(t,y) [y(2);-0.5*y(2)+y(1)-y(1).^3]; 12 | options = odeset('RelTol',1e-12,'AbsTol',1e-12); 13 | N=2000; 14 | PHI = @(r) exp(-r); % radial basis function used (others also work well) 15 | x_pts = -1.2:0.01:1.2; y_pts = -0.05:0.01:1.2; 16 | v=(10.^(-2:0.1:1)); 17 | 18 | %% Produce the data 19 | X = zeros(2,M1*M2); 20 | Y=[]; 21 | for jj=1:M1 22 | Y0=(rand(2,1)-0.5)*4; 23 | [~,Y1]=ode45(ODEFUN,[0 0.000001 (1:(3+M2))*delta_t],Y0,options); 24 | Y1=Y1'; 25 | X(:,(jj-1)*M2+1:jj*M2) = Y1(:,[1,3:M2+1]); 26 | Y(:,(jj-1)*M2+1:jj*M2) = Y1(:,3:M2+2); 27 | end 28 | M = M1*M2; 29 | 30 | d=mean(vecnorm(X-mean(X,2))); % scaling for radial function 31 | 32 | [~,C] = kmeans([X';Y'],N); % find centers 33 | 34 | PX = zeros(M,N); PY = zeros(M,N); 35 | 36 | for j = 1:N 37 | R = sqrt((X(1,:)-C(j,1)).^2+(X(2,:)-C(j,2)).^2); 38 | PX(:,j) = PHI(R(:)/d); 39 | R = sqrt((Y(1,:)-C(j,1)).^2+(Y(2,:)-C(j,2)).^2); 40 | PY(:,j) = PHI(R(:)/d); 41 | end 42 | %% EDMD 43 | 44 | K=PX(1:M,:)\PY(1:M,:); 45 | [V,LAM]=eig(K,'vector'); 46 | %% 47 | 48 | I1 = find(abs(LAM-exp(1*(-0.250 + 1.392i)*delta_t))==min(abs(LAM-exp(1*(-0.250 + 1.392i)*delta_t)))); 49 | I2 = find(abs(LAM-exp(2*(-0.250 + 1.392i)*delta_t))==min(abs(LAM-exp(2*(-0.250 + 1.392i)*delta_t)))); 50 | I3 = find(abs(LAM-exp(3*(-0.250 + 1.392i)*delta_t))==min(abs(LAM-exp(3*(-0.250 + 1.392i)*delta_t)))); 51 | 52 | figure 53 | plot(cos(0:0.001:2*pi),sin(0:0.001:2*pi),'g','linewidth',2) 54 | hold on 55 | plot(real(LAM),imag(LAM),'.r','markersize',12) 56 | plot(real(LAM([I1,I2,I3])),imag(LAM([I1,I2,I3])),'.b','markersize',20) 57 | plot(real(LAM([I1,I2,I3])),-imag(LAM([I1,I2,I3])),'.b','markersize',20) 58 | ax=gca; ax.FontSize=18; axis equal tight; axis([x_pts(1),x_pts(end),-y_pts(end),y_pts(end)]) 59 | xlabel('$\mathrm{Re}(\lambda)$','interpreter','latex','fontsize',18) 60 | ylabel('$\mathrm{Im}(\lambda)$','interpreter','latex','fontsize',18) 61 | title('EDMD Eigenvalues','interpreter','latex','fontsize',18) 62 | 63 | exportgraphics(gcf,'saved_figures/EDMD_evals_1.pdf','ContentType','vector','BackgroundColor','w') 64 | 65 | %% Plot the eigenfunctions 66 | [~,I]= sort(abs(LAM-1),'ascend'); 67 | V = V(:,I); 68 | 69 | u=real([PX;PY]*V(:,2)); 70 | figure1=figure; 71 | axes1 = axes('Parent',figure1); 72 | hold(axes1,'on'); 73 | scatter([X(1,:),Y(1,:)],[X(2,:),Y(2,:)],5,u,'filled'); 74 | colormap(brighten(brewermap([],'RdYlBu'),-0.3)) 75 | axis(axes1,'tight'); grid off; axis off 76 | set(axes1,'DataAspectRatio',[1 1 1]); 77 | pause(1) 78 | exportgraphics(gcf,'saved_figures/EDMDefun_1.png','ContentType','image','BackgroundColor','w','Resolution',200) 79 | 80 | 81 | %% Rerun on basin 82 | 83 | II = find(u(1:M)>mean(u)); 84 | M = length(II); 85 | X = X(:,II); Y = Y(:,II); 86 | d=mean(vecnorm(X-mean(X,2))); % scaling for radial function 87 | 88 | [~,C] = kmeans([X';Y'],N); % find centers 89 | 90 | PX = zeros(M,N); PY = zeros(M,N); 91 | 92 | for j = 1:N 93 | R = sqrt((X(1,:)-C(j,1)).^2+(X(2,:)-C(j,2)).^2); 94 | PX(:,j) = PHI(R(:)/d); 95 | R = sqrt((Y(1,:)-C(j,1)).^2+(Y(2,:)-C(j,2)).^2); 96 | PY(:,j) = PHI(R(:)/d); 97 | end 98 | 99 | K=PX(1:M,:)\PY(1:M,:); 100 | [V,LAM]=eig(K,'vector'); 101 | 102 | I1 = find(abs(LAM-exp(1*(-0.250 + 1.392i)*delta_t))==min(abs(LAM-exp(1*(-0.250 + 1.392i)*delta_t)))); 103 | I2 = find(abs(LAM-exp(2*(-0.250 + 1.392i)*delta_t))==min(abs(LAM-exp(2*(-0.250 + 1.392i)*delta_t)))); 104 | I3 = find(abs(LAM-exp(3*(-0.250 + 1.392i)*delta_t))==min(abs(LAM-exp(3*(-0.250 + 1.392i)*delta_t)))); 105 | 106 | figure 107 | plot(cos(0:0.001:2*pi),sin(0:0.001:2*pi),'g','linewidth',2) 108 | hold on 109 | plot(real(LAM),imag(LAM),'.r','markersize',12) 110 | plot(real(LAM([I1,I2,I3])),imag(LAM([I1,I2,I3])),'.b','markersize',20) 111 | plot(real(LAM([I1,I2,I3])),-imag(LAM([I1,I2,I3])),'.b','markersize',20) 112 | ax=gca; ax.FontSize=18; axis equal tight; axis([x_pts(1),x_pts(end),-y_pts(end),y_pts(end)]) 113 | xlabel('$\mathrm{Re}(\lambda)$','interpreter','latex','fontsize',18) 114 | ylabel('$\mathrm{Im}(\lambda)$','interpreter','latex','fontsize',18) 115 | title('EDMD Eigenvalues (basin)','interpreter','latex','fontsize',18) 116 | exportgraphics(gcf,'saved_figures/EDMD_evals_2.pdf','ContentType','vector','BackgroundColor','w') 117 | %% Plot the eigenfunctions corresponding to lattice structure 118 | 119 | close all 120 | 121 | [~,I]= sort(abs(LAM-exp(1*(-0.250 + 1.392i)*delta_t)),'ascend'); 122 | LAM = LAM(I); V = V(:,I); 123 | u=[PX;PY]*V(:,1); 124 | 125 | figure1=figure; 126 | axes1 = axes('Parent',figure1); 127 | hold(axes1,'on'); 128 | scatter([X(1,:),Y(1,:)],[X(2,:),Y(2,:)],5,abs(u),'filled'); 129 | colormap(brighten(brewermap([],'RdYlBu'),-0.3)) 130 | clim([0,max(rmoutliers(abs(u)))]) 131 | axis(axes1,'tight'); grid off; axis off 132 | set(axes1,'DataAspectRatio',[1 1 1]); 133 | exportgraphics(gcf,'saved_figures/EDMDefun_2a.png','ContentType','image','BackgroundColor','w','Resolution',200) 134 | 135 | figure1=figure; 136 | axes1 = axes('Parent',figure1); 137 | hold(axes1,'on'); 138 | scatter([X(1,:),Y(1,:)],[X(2,:),Y(2,:)],5,angle(u),'filled'); 139 | colormap(brighten(brewermap([],'RdYlBu'),-0.3)) 140 | axis(axes1,'tight'); grid off; axis off 141 | set(axes1,'DataAspectRatio',[1 1 1]); 142 | exportgraphics(gcf,'saved_figures/EDMDefun_2b.png','ContentType','image','BackgroundColor','w','Resolution',200) 143 | 144 | 145 | 146 | [~,I]= sort(abs(LAM-exp(2*(-0.250 + 1.392i)*delta_t)),'ascend'); 147 | LAM = LAM(I); V = V(:,I); 148 | u=[PX;PY]*V(:,1); 149 | 150 | 151 | figure1=figure; 152 | axes1 = axes('Parent',figure1); 153 | hold(axes1,'on'); 154 | scatter([X(1,:),Y(1,:)],[X(2,:),Y(2,:)],5,abs(u),'filled'); 155 | colormap(brighten(brewermap([],'RdYlBu'),-0.3)) 156 | clim([0,max(rmoutliers(abs(u)))]) 157 | axis(axes1,'tight'); grid off; axis off 158 | set(axes1,'DataAspectRatio',[1 1 1]); 159 | exportgraphics(gcf,'saved_figures/EDMDefun_3a.png','ContentType','image','BackgroundColor','w','Resolution',200) 160 | 161 | figure1=figure; 162 | axes1 = axes('Parent',figure1); 163 | hold(axes1,'on'); 164 | scatter([X(1,:),Y(1,:)],[X(2,:),Y(2,:)],5,angle(u),'filled'); 165 | colormap(brighten(brewermap([],'RdYlBu'),-0.3)) 166 | axis(axes1,'tight'); grid off; axis off 167 | set(axes1,'DataAspectRatio',[1 1 1]); 168 | exportgraphics(gcf,'saved_figures/EDMDefun_3b.png','ContentType','image','BackgroundColor','w','Resolution',200) 169 | 170 | 171 | [~,I]= sort(abs(LAM-exp(3*(-0.250 + 1.392i)*delta_t)),'ascend'); 172 | LAM = LAM(I); V = V(:,I); 173 | u=[PX;PY]*V(:,1); 174 | 175 | figure1=figure; 176 | axes1 = axes('Parent',figure1); 177 | hold(axes1,'on'); 178 | scatter([X(1,:),Y(1,:)],[X(2,:),Y(2,:)],5,abs(u),'filled'); 179 | colormap(brighten(brewermap([],'RdYlBu'),-0.3)) 180 | clim([0,max(rmoutliers(abs(u)))]) 181 | axis(axes1,'tight'); grid off; axis off 182 | set(axes1,'DataAspectRatio',[1 1 1]); 183 | exportgraphics(gcf,'saved_figures/EDMDefun_4a.png','ContentType','image','BackgroundColor','w','Resolution',200) 184 | 185 | figure1=figure; 186 | axes1 = axes('Parent',figure1); 187 | hold(axes1,'on'); 188 | scatter([X(1,:),Y(1,:)],[X(2,:),Y(2,:)],5,angle(u),'filled'); 189 | colormap(brighten(brewermap([],'RdYlBu'),-0.3)) 190 | axis(axes1,'tight'); grid off; axis off 191 | set(axes1,'DataAspectRatio',[1 1 1]); 192 | exportgraphics(gcf,'saved_figures/EDMDefun_4b.png','ContentType','image','BackgroundColor','w','Resolution',200) 193 | 194 | 195 | [~,I]= sort(abs(LAM-exp(4*(-0.250 + 1.392i)*delta_t)),'ascend'); 196 | LAM = LAM(I); V = V(:,I); 197 | u=[PX;PY]*V(:,1); 198 | 199 | figure1=figure; 200 | axes1 = axes('Parent',figure1); 201 | hold(axes1,'on'); 202 | scatter([X(1,:),Y(1,:)],[X(2,:),Y(2,:)],5,abs(u),'filled'); 203 | colormap(brighten(brewermap([],'RdYlBu'),-0.3)) 204 | clim([0,max(rmoutliers(abs(u)))]) 205 | axis(axes1,'tight'); grid off; axis off 206 | set(axes1,'DataAspectRatio',[1 1 1]); 207 | exportgraphics(gcf,'saved_figures/EDMDefun_5a.png','ContentType','image','BackgroundColor','w','Resolution',200) 208 | 209 | figure1=figure; 210 | axes1 = axes('Parent',figure1); 211 | hold(axes1,'on'); 212 | scatter([X(1,:),Y(1,:)],[X(2,:),Y(2,:)],5,angle(u),'filled'); 213 | colormap(brighten(brewermap([],'RdYlBu'),-0.3)) 214 | axis(axes1,'tight'); grid off; axis off 215 | set(axes1,'DataAspectRatio',[1 1 1]); 216 | exportgraphics(gcf,'saved_figures/EDMDefun_5b.png','ContentType','image','BackgroundColor','w','Resolution',200) 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /routines/subspacea.m: -------------------------------------------------------------------------------- 1 | function [theta,varargout] = subspacea(F,G,A) 2 | %SUBSPACEA angles between subspaces 3 | % subspacea(F,G,A) 4 | % Finds all min(size(orth(F),2),size(orth(G),2)) principal angles 5 | % between two subspaces spanned by the columns of matrices F and G 6 | % in the A-based scalar product x'*A*y, where A 7 | % is Hermitian and positive definite. 8 | % COS of principal angles is called canonical correlations in statistics. 9 | % [theta,U,V] = subspacea(F,G,A) also computes left and right 10 | % principal (canonical) vectors - columns of U and V, respectively. 11 | % 12 | % If F and G are vectors of unit length and A=I, 13 | % the angle is ACOS(F'*G) in exact arithmetic. 14 | % If A is not provided as a third argument, than A=I and 15 | % the function gives the same largest angle as SUBSPACE.m by Andrew Knyazev, 16 | % see 17 | % http://www.mathworks.com/matlabcentral/fileexchange/Files.jsp?type=category&id=&fileId=54 18 | % MATLAB's SUBSPACE.m function is still badly designed and fails to compute 19 | % some angles accurately. 20 | % 21 | % The optional parameter A is a Hermitian and positive definite matrix, 22 | % or a corresponding function. When A is a function, it must accept a 23 | % matrix as an argument. 24 | % This code requires ORTHA.m, Revision 1.5.8 or above, 25 | % which is included. The standard MATLAB version of ORTH.m 26 | % is used for orthonormalization, but could be replaced by QR.m. 27 | % 28 | % Examples: 29 | % F=rand(10,4); G=randn(10,6); theta = subspacea(F,G); 30 | % computes 4 angles between F and G, while in addition 31 | % A=hilb(10); [theta,U,V] = subspacea(F,G,A); 32 | % computes angles relative to A and corresponding vectors U and V. 33 | % 34 | % The algorithm is described in A. V. Knyazev and M. E. Argentati, 35 | % Principal Angles between Subspaces in an A-Based Scalar Product: 36 | % Algorithms and Perturbation Estimates. SIAM Journal on Scientific Computing, 37 | % 23 (2002), no. 6, 2009-2041. 38 | % http://epubs.siam.org/sam-bin/dbq/article/37733 39 | % Tested under MATLAB R2020a 40 | % Copyright (c) 2000-2022 Andrew Knyazev, Rico Argentati 41 | % Contact email: 42 | % License: free software (BSD) 43 | % $Revision: 4.6 $ $Date: 2021/12/20 44 | threshold=sqrt(2)/2; % Define threshold for determining when an angle is small 45 | if size(F,1) ~= size(G,1) 46 | error('MATLAB:subspacea:rowDimentionMismach',... 47 | ['The row dimension ' int2str(size(F,1)) ... 48 | ' of the matrix F is not the same as ' int2str(size(G,1)) ... 49 | ' the row dimension of G']) 50 | end 51 | if nargin<3 % Compute angles using standard inner product 52 | 53 | % Trivial column scaling first, if ORTH.m is used later 54 | for i=1:size(F,2) 55 | normi=norm(F(:,i),inf); 56 | %Adjustment makes tol consistent with experimental results 57 | if normi > eps^.981 58 | F(:,i)=F(:,i)/normi; 59 | % Else orth will take care of this 60 | end 61 | end 62 | for i=1:size(G,2) 63 | normi=norm(G(:,i),inf); 64 | %Adjustment makes tol consistent with experimental results 65 | if normi > eps^.981 66 | G(:,i)=G(:,i)/normi; 67 | % Else orth will take care of this 68 | end 69 | end 70 | % Compute angle using standard inner product 71 | 72 | QF = orth(F); %This can also be done using QR.m, in which case 73 | QG = orth(G); %the column scaling above is not needed 74 | 75 | q = min(size(QF,2),size(QG,2)); 76 | [Ys,s,Zs] = svd(QF'*QG,0); 77 | if size(s,1)==1 78 | % make sure s is column for output 79 | s=s(1); 80 | end 81 | s = min(diag(s),1); 82 | theta = max(acos(s),0); 83 | U = QF*Ys; 84 | V = QG*Zs; 85 | indexsmall = s > threshold; 86 | if max(indexsmall) % Check for small angles and recompute only small 87 | RF = U(:,indexsmall); 88 | RG = V(:,indexsmall); 89 | %[Yx,x,Zx] = svd(RG-RF*(RF'*RG),0); 90 | [~,x,Zx] = svd(RG-QF*(QF'*RG),0); % Provides more accurate results 91 | if size(x,1)==1 92 | % make sure x is column for output 93 | x=x(1); 94 | end 95 | Tmp = fliplr(RG*Zx); 96 | V(:,indexsmall) = Tmp(:,indexsmall); 97 | U(:,indexsmall) = RF*(RF'*V(:,indexsmall))*... 98 | diag(1./s(indexsmall)); 99 | x = diag(x); 100 | thetasmall=flipud(max(asin(min(x,1)),0)); 101 | theta(indexsmall) = thetasmall(indexsmall); 102 | end 103 | 104 | % Compute angle using inner product relative to A 105 | else 106 | [m,~] = size(F); 107 | if ~isstr(A) 108 | [mAc,mA] = size(A); 109 | if mAc ~= mA 110 | error('MATLAB:subspacea:matrixNotSquare',... 111 | 'Matrix A must be a square matrix or a string.') 112 | end 113 | if mA ~= m 114 | error('MATLAB:subspacea:matrixVectorDimentionMismach',... 115 | ['The size ' int2str(size(A)) ... 116 | ' of the matrix A is not the same as ' int2str(m) ... 117 | ' - the number of rows of F']) 118 | end 119 | end 120 | 121 | [QF,~]=ortha(A,F); 122 | [QG,AQG]=ortha(A,G); 123 | q = min(size(QF,2),size(QG,2)); 124 | [Ys,s,Zs] = svd(QF'*AQG,0); 125 | if size(s,1)==1 126 | % make sure s is column for output 127 | s=s(1); 128 | end 129 | s=min(diag(s),1); 130 | theta = max(acos(s),0); 131 | U = QF*Ys; 132 | V = QG*Zs; 133 | indexsmall = s > threshold; 134 | if max(indexsmall) % Check for small angles and recompute only small 135 | RG = V(:,indexsmall); 136 | AV = AQG*Zs; 137 | ARG = AV(:,indexsmall); 138 | RF = U(:,indexsmall); 139 | %S=RG-RF*(RF'*(ARG)); 140 | S=RG-QF*(QF'*(ARG));% A bit more cost, but seems more accurate 141 | 142 | % Normalize, so ortha would not delete wanted vectors 143 | QS = S; 144 | for i=1:size(S,2) 145 | normSi=norm(S(:,i),inf); 146 | %Adjustment makes tol consistent with experimental results 147 | if normSi > eps^1.981 148 | QS(:,i)=S(:,i)/normSi; 149 | % Else ortha will take care of this 150 | end 151 | end 152 | [~,AQS]=ortha(A,QS); 153 | [~,x,Zx] = svd(AQS'*S); 154 | if size(x,1)==1 155 | % make sure x is column for output 156 | x=x(1); 157 | end 158 | x = max(diag(x),0); 159 | 160 | Tmp = fliplr(RG*Zx); 161 | ATmp = fliplr(ARG*Zx); 162 | V(:,indexsmall) = Tmp(:,indexsmall); 163 | AVindexsmall = ATmp(:,indexsmall); 164 | U(:,indexsmall) = RF*(RF'*AVindexsmall)*... 165 | diag(1./s(indexsmall)); 166 | thetasmall=flipud(max(asin(min(x,1)),0)); 167 | 168 | %Add zeros if necessary 169 | if sum(indexsmall)-size(thetasmall,1)>0 170 | thetasmall=[zeros(sum(indexsmall)-size(thetasmall,1),1)',... 171 | thetasmall']'; 172 | end 173 | 174 | theta(indexsmall) = thetasmall(indexsmall); 175 | end 176 | end 177 | varargout(1)={U(:,1:q)}; 178 | varargout(2)={V(:,1:q)}; 179 | 180 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 181 | function [Q,varargout]=ortha(A,X) 182 | %ORTHA Orthonormalization Relative to matrix A 183 | % Q=ortha(A,X) 184 | % Q=ortha('Afunc',X) 185 | % Computes an orthonormal basis Q for the range of X, relative to the 186 | % scalar product using a positive definite and selfadjoint matrix A. 187 | % That is, Q'*A*Q = I, the columns of Q span the same space as 188 | % columns of X, and rank(Q)=rank(X). 189 | % 190 | % [Q,AQ]=ortha(A,X) also gives AQ = A*Q. 191 | % 192 | % Required input arguments: 193 | % A : either an m x m positive definite and selfadjoint matrix A 194 | % or a linear operator A=A(v) that is positive definite selfadjoint; 195 | % X : m x n matrix containing vectors to be orthonormalized relative 196 | % to A. 197 | % 198 | % ortha(eye(m),X) spans the same space as orth(X) 199 | % 200 | % Examples: 201 | % [q,Aq]=ortha(hilb(20),eye(20,5)) 202 | % computes 5 column-vectors q spanned by the first 5 coordinate vectors, 203 | % and orthonormal with respect to the scalar product given by the 204 | % 20x20 Hilbert matrix, 205 | % while an attempt to orthogonalize (in the same scalar product) 206 | % all 20 coordinate vectors using 207 | % [q,Aq]=ortha(hilb(20),eye(20)) 208 | % gives 14 column-vectors out of 20. 209 | % Note that rank(hilb(20)) = 13 in double precision. 210 | % 211 | % Algorithm: 212 | % X=orth(X), [U,S,V]=SVD(X'*A*X), then Q=X*U*S^(-1/2) 213 | % If A is ill conditioned an extra step is performed to 214 | % improve the result. This extra step is performed only 215 | % if a test indicates that the program is running on a 216 | % machine that supports higher precison arithmetic 217 | % (greater than 64 bit precision). 218 | % 219 | % See also ORTH, SVD 220 | % 221 | % Copyright (c) 2000 Andrew Knyazev, Rico Argentati 222 | % Contact email: knyazev@na-net.ornl.gov 223 | % License: free software (BSD) 224 | % $Revision: 1.5.8 $ $Date: 2001/8/28 225 | % Tested under MATLAB R10-12.1 226 | % Check input parameter A 227 | [m,~] = size(X); 228 | if ~isstr(A) 229 | [mAc,mA] = size(A); 230 | if mAc ~= mA 231 | error('MATLAB:subspacea:ortha:matrixNotSquare',... 232 | 'Matrix A must be a square matrix or a string.') 233 | end 234 | if mA ~= m 235 | error('MATLAB:subspacea:ortha:matrixVectorDimentionMismach',... 236 | ['The size ' int2str(size(A)) ... 237 | ' of the matrix A does not match with ' int2str(m) ... 238 | ' - the number of rows of X']) 239 | end 240 | end 241 | % Normalize, so ORTH below would not delete wanted vectors 242 | for i=1:size(X,2) 243 | normXi=norm(X(:,i),inf); 244 | %Adjustment makes tol consistent with experimental results 245 | if normXi > eps^.981 246 | X(:,i)=X(:,i)/normXi; 247 | % Else orth will take care of this 248 | end 249 | end 250 | % Make sure X is full rank and orthonormalize 251 | X=orth(X); %This can also be done using QR.m, in which case 252 | %the column scaling above is not needed 253 | %Set tolerance 254 | [m,n]=size(X); 255 | tol=max(m,n)*eps; 256 | % Compute an A-orthonormal basis 257 | if ~isstr(A) %#ok<*DISSTR> 258 | AX = A*X; 259 | else 260 | AX = feval(A,X); 261 | end 262 | XAX = X'*AX; 263 | XAX = 0.5.*(XAX' + XAX); 264 | [U,S,~]=svd(XAX); 265 | if n>1, s=diag(S); 266 | elseif n==1, s=S(1); 267 | else, s=0; 268 | end 269 | %Adjustment makes tol consistent with experimental results 270 | threshold1=max(m,n)*max(s)*eps^1.1; 271 | r=sum(s>threshold1); 272 | s(r+1:size(s,1))=1; 273 | S=diag(1./sqrt(s),0); 274 | X=X*U*S; 275 | AX=AX*U*S; 276 | XAX = X'*AX; 277 | % Check subspaceaError against tolerance 278 | subspaceaError=normest(XAX(1:r,1:r)-eye(r)); 279 | % Check internal precision, e.g., 80bit FPU registers of P3/P4 280 | precision_test=[1 eps/1024 -1]*[1 1 1]'; 281 | if subspaceaError1, s=diag(S); 297 | elseif n==1, s=S(1); 298 | else, s=0; 299 | end 300 | 301 | threshold2=max(m,n)*max(s)*eps; 302 | r=sum(s>threshold2); 303 | S=diag(1./sqrt(s(1:r)),0); 304 | Q=X*U(:,1:r)*S(1:r,1:r); 305 | varargout(1)={AX*U(:,1:r)*S(1:r,1:r)}; -------------------------------------------------------------------------------- /routines/CoSaMP.m: -------------------------------------------------------------------------------- 1 | function [x,r,normR,residHist, errHist] = CoSaMP( A, b, k, errFcn, opts ) 2 | % x = CoSaMP( A, b, k ) 3 | % uses the Compressive Sampling Matched Pursuit (CoSaMP) algorithm 4 | % (see Needell and Tropp's 2008 paper http://arxiv.org/abs/0803.2392 ) 5 | % to estimate the solution to the equation 6 | % b = A*x (or b = A*x + noise ) 7 | % where there is prior information that x is sparse. 8 | % 9 | % "A" may be a matrix, or it may be a cell array {Af,At} 10 | % where Af and At are function handles that compute the forward and transpose 11 | % multiplies, respectively. If the function handles are provided, 12 | % the the least-squares step is performed using LSQR (use could also use 13 | % CG on the normal equations, or other special variants). 14 | % 15 | % [x,r,normR,residHist,errHist] = CoSaMP( A, b, k, errFcn, opts ) 16 | % is the full version. 17 | % Outputs: 18 | % 'x' is the k-sparse estimate of the unknown signal 19 | % 'r' is the residual b - A*x 20 | % 'normR' = norm(r) 21 | % 'residHist' is a vector with normR from every iteration 22 | % 'errHist' is a vector with the outout of errFcn from every iteration 23 | % 24 | % Inputs: 25 | % 'A' is the measurement matrix 26 | % 'b' is the vector of observations 27 | % 'k' is the estimate of the sparsity (you may wish to purposefully 28 | % over- or under-estimate the sparsity, depending on noise) 29 | % N.B. k < size(A,1) is necessary, otherwise we cannot 30 | % solve the internal least-squares problem uniquely. 31 | % 'errFcn' (optional; set to [] to ignore) is a function handle 32 | % which will be used to calculate the error; the output 33 | % should be a scalar 34 | % 'opts' is a structure with more options, including: 35 | % .printEvery = is an integer which controls how often output is printed 36 | % .maxiter = maximum number of iterations 37 | % .normTol = desired size of norm(residual). This is also 38 | % used to detect convergence when the residual 39 | % has stopped decreasing in norm 40 | % .LSQR_tol = when "A" is a set of function handles, this controls 41 | % the tolerance in the iterative solver. For compatibility 42 | % with older versions, the fieldname "cg_tol" is also OK. 43 | % .LSQR_maxit = maximum number of steps in the iterative solver. For compatibility 44 | % with older versions, the fieldname "cg_maxit" is also OK. 45 | % N.B. "CG" stands for conjugate gradient, but this code 46 | % actually uses the LSQR solver. 47 | % .HSS = if true, use the variant of CoSaMP that is similar to 48 | % HHS Pursuit (see appendix A.2 of Needell/Tropp paper). Recommended. 49 | % .two_solves = if true, uses the variant of CoSaMP that re-solves 50 | % on the support of size 'k' at every iteration 51 | % (see appendix). This can be used with or without HSS variant. 52 | % .addK = the number of new entries to add each time. By default 53 | % this is 2*k (this was what is used in the paper). 54 | % If you experience divergence, try reducing this. 55 | % We recommend trying 1*k for most problems. 56 | % .support_tol = when adding the (addK) atoms with the largest 57 | % correlations, the CoSaMP method specifies that you do 58 | % not add the atoms if the correlation is exactly zero. 59 | % In practice, it is better to not add the atoms of their 60 | % correlation is nearly zero. "support_tol" controls 61 | % what this 'nearly zero' number is, e.g. 1e-10. 62 | % 63 | % Note that these field names are case sensitive! 64 | % 65 | % 66 | % Stephen Becker, Aug 1 2011 srbecker@alumni.caltech.edu 67 | % Updated Dec 12 2012 68 | % See also OMP, test_OMP_and_CoSaMP 69 | 70 | if nargin < 5, opts = []; end 71 | if ~isempty(opts) && ~isstruct(opts) 72 | error('"opts" must be a structure'); 73 | end 74 | 75 | function out = setOpts( field, default ) 76 | if ~isfield( opts, field ) 77 | opts.(field) = default; 78 | end 79 | out = opts.(field); 80 | end 81 | 82 | printEvery = setOpts( 'printEvery', 1000 ); 83 | maxiter = setOpts( 'maxiter', 1000 ); 84 | normTol = setOpts( 'normTol', 1e-10 ); 85 | cg_tol = setOpts( 'cg_tol', 1e-6 ); 86 | cg_maxit = setOpts( 'cg_maxit', 20 ); 87 | % Allow some synonyms 88 | cg_tol = setOpts( 'LSQR_tol', cg_tol ); 89 | cg_maxit = setOpts( 'LSQR_maxit', cg_maxit ); 90 | HSS = setOpts( 'HSS', false ); 91 | TWO_SOLVES = setOpts( 'two_solves', false ); 92 | addK = round(setOpts( 'addK', 2*k) ); 93 | support_tol = setOpts( 'support_tol', 1e-10 ); 94 | 95 | 96 | if nargin < 5 || isempty(printEvery) 97 | printEvery = round(k,maxiter); 98 | end 99 | 100 | if nargin < 4 101 | errFcn = []; 102 | elseif ~isempty(errFcn) && ~isa(errFcn,'function_handle') 103 | error('errFcn input must be a function handle (or leave the input empty)'); 104 | end 105 | 106 | if iscell(A) 107 | LARGESCALE = true; 108 | Af = A{1}; 109 | At = A{2}; % we don't really need this... 110 | else 111 | LARGESCALE = false; 112 | Af = @(x) A*x; 113 | At = @(x) A'*x; 114 | end 115 | 116 | 117 | % -- Intitialize -- 118 | % start at x = 0, so r = b - A*x = b 119 | r = b; 120 | Ar = At(r); 121 | N = size(Ar,1); % number of atoms 122 | M = size(r,1); % size of atoms 123 | if k > M/3 124 | error('K cannot be larger than the dimension of the atoms'); 125 | end 126 | x = zeros(N,1); 127 | ind_k = []; 128 | 129 | % indx_set = zeros(k,1); % created on-the-fly 130 | % A_T = zeros(M,k); 131 | residHist = zeros(k,1); 132 | errHist = zeros(k,1); 133 | 134 | fprintf('Iter, |T|, Resid'); 135 | if ~isempty(errFcn) 136 | fprintf(', Error'); 137 | end 138 | if LARGESCALE 139 | fprintf(', LSQR iterations and norm(residual)'); 140 | end 141 | fprintf('\n'); 142 | 143 | if LARGESCALE 144 | 145 | % if exist( 'lsqr_wrapper','file') 146 | % LSQR_ALG = @lsqr_wrapper; 147 | % % This is Stephen's wrapper (designed to imitate Matlab's 148 | % % syntax ) to the version of LSQR that you can get 149 | % % at http://www.stanford.edu/group/SOL/software/lsqr/matlab/lsqr.m 150 | % % This version of LSQR is better than Matlab's implementation. 151 | % elseif exist( 'lsqr','file' ) 152 | % LSQR_ALG = @lsqr; % Matlab's version 153 | % else 154 | % disp('You need to install LSQR! Download it from:'); 155 | % disp('http://www.stanford.edu/group/SOL/software/lsqr/matlab/lsqr.m'); 156 | % error('Need to have working copy of LSQR'); 157 | % end 158 | 159 | % Unfortunately, the Stanford group's version doesn't make it easy 160 | % to provide a starting value, so we will stick with Matlab's version. 161 | 162 | LSQR_ALG = @(RHS,Afcn,x0) lsqr(Afcn,RHS,cg_tol,cg_maxit,[],[],x0 ); 163 | end 164 | 165 | 166 | 167 | for kk = 1:maxiter 168 | 169 | % -- Step 1: find new index and atom to add 170 | y_sort = sort( abs(Ar),'descend'); 171 | cutoff = y_sort(addK); % addK is typically 2*k 172 | cutoff = max( cutoff, support_tol ); 173 | ind_new = find( abs(Ar) >= cutoff ); 174 | 175 | 176 | % -- Merge: 177 | T = union( ind_new, ind_k ); 178 | 179 | 180 | % -- Step 2: update residual 181 | if HSS 182 | RHS = r; % where r = b - A*x, so we'll need to add in "x" later 183 | x_warmstart = zeros(length(T),1); 184 | else 185 | RHS = b; 186 | x_warmstart = x(T); 187 | end 188 | 189 | 190 | % -- solve for x on the suppor set "T" 191 | if LARGESCALE 192 | % Could do CG on the normal equations... 193 | % Or, use LSQR: 194 | % x_T = LSQR_ALG(Afcn,RHS,cg_tol,cg_maxit); 195 | 196 | % use an initial guess to warm-start the solver 197 | Afcn = @(x,mode) partialA( N, T, Af, At, x, mode ); 198 | [x_T,flag,relres,CGiter] = LSQR_ALG(RHS,Afcn,x_warmstart); 199 | 200 | else 201 | x_T = A(:,T)\RHS; % more efficient; equivalent to pinv when underdetermined. 202 | % x_T = pinv( A(:,T) )*RHS; 203 | end 204 | 205 | 206 | if HSS 207 | % HSS variation of CoSaMP 208 | x_new = zeros(N,1); 209 | x_new(T) = x_T; 210 | x = x + x_new; % this is the key extra step in HSS 211 | cutoff = findCutoff( x, k ); 212 | x = x.*( abs(x) >= cutoff ); 213 | ind_k = find(x); 214 | 215 | if TWO_SOLVES 216 | if LARGESCALE 217 | Afcn = @(x,mode) partialA( N, ind_k, Af, At, x, mode ); 218 | [x_T2,flag,relres,CGiter] = LSQR_ALG(b,Afcn,x(ind_k)); % not using "r", just using "b" 219 | else 220 | x_T2 = A(:,ind_k)\b; 221 | end 222 | x( ind_k ) = x_T2; 223 | end 224 | 225 | % update r 226 | r_old = r; 227 | r = b - Af(x); 228 | else 229 | % Standard CoSaMP 230 | % Note: this is implemented *slightly* more efficiently 231 | % that the HSS variation 232 | 233 | % Prune x to keep only "k" entries: 234 | cutoff = findCutoff(x_T, k); 235 | Tk = find( abs(x_T) >= cutoff ); 236 | % This is assuming there are no ties. If there are, 237 | % from a practical standpoint, it probably doesn't 238 | % matter much what you do. So out of laziness, we don't worry about it. 239 | ind_k = T(Tk); 240 | x = 0*x; 241 | x( ind_k ) = x_T( Tk ); 242 | 243 | 244 | if TWO_SOLVES 245 | if LARGESCALE 246 | Afcn = @(x,mode) partialA( N, ind_k, Af, At, x, mode ); 247 | [x_T2,flag,relres,CGiter] = LSQR_ALG(b,Afcn,x(ind_k)); 248 | else 249 | x_T2 = A(:,ind_k)\b; 250 | end 251 | x( ind_k ) = x_T2; 252 | end 253 | 254 | % Update x and r 255 | r_old = r; 256 | if LARGESCALE 257 | r = b - Af( x ); 258 | else 259 | % don't do a full matrix-vector multiply, just use necessary columns 260 | r = b - A(:,ind_k)*x_T( Tk ); 261 | end 262 | end 263 | 264 | 265 | % -- Print some info -- 266 | PRINT = ( ~mod( kk, printEvery ) || kk == maxiter ); 267 | normR = norm(r); 268 | STOP = false; 269 | if normR < normTol || norm( r - r_old ) < normTol 270 | STOP = true; 271 | PRINT = true; 272 | end 273 | 274 | 275 | if ~isempty(errFcn) 276 | er = errFcn(x); 277 | errHist(kk) = er; 278 | end 279 | if PRINT 280 | fprintf('%4d, %4d, %.2e', kk, length(T), normR); 281 | if ~isempty(errFcn) 282 | fprintf(', %.2e',er); 283 | end 284 | if LARGESCALE 285 | fprintf(', %d, %.2e', CGiter,relres); 286 | end 287 | fprintf('\n'); 288 | end 289 | 290 | residHist(kk) = normR; 291 | 292 | if STOP 293 | disp('Reached stopping criteria'); 294 | break; 295 | end 296 | 297 | if kk < maxiter 298 | Ar = At(r); % prepare for next round 299 | end 300 | 301 | end 302 | 303 | end % -- end of main function 304 | 305 | function tau = findCutoff( x, k ) 306 | % finds the appropriate cutoff such that after hard-thresholding, 307 | % "x" will be k-sparse 308 | x = sort( abs(x),'descend'); 309 | if k > length(x) 310 | tau = x(end)*.999; 311 | else 312 | tau = x(k); 313 | end 314 | end 315 | 316 | 317 | % for LSQR, PCG, etc. 318 | function z = partialA( N, T, Af, At, x, mode ) 319 | % Multiplies A_T(x) or A_T'(x) (the transpose) 320 | % where _T denotes restriction to the set "T" 321 | 322 | switch lower(mode) 323 | case 'notransp' 324 | % zero-pad: 325 | y = zeros(N,1); 326 | y(T) = x; 327 | z = Af(y); 328 | 329 | case 'transp' 330 | y = At(x); 331 | % truncate: 332 | z = y(T); 333 | end 334 | end % -- end of subfuction 335 | 336 | -------------------------------------------------------------------------------- /routines/brewermap.m: -------------------------------------------------------------------------------- 1 | function [map,num,typ,scheme] = brewermap(N,scheme) %#ok<*ISMAT> 2 | % The complete selection of ColorBrewer colorschemes/palettes (RGB colormaps). 3 | % 4 | % (c) 2014-2022 Stephen Cobeldick 5 | % 6 | % Returns any RGB colormap from the ColorBrewer colorschemes, especially 7 | % intended for mapping and plots with attractive, distinguishable colors. 8 | % 9 | %%% Basic Syntax: 10 | % brewermap() % print summary 11 | % map = brewermap(N,scheme) 12 | % 13 | %%% Preset Syntax: 14 | % old = brewermap(scheme) 15 | % map = brewermap() 16 | % map = brewermap(N) 17 | % 18 | % [...,num,typ] = brewermap(...) 19 | % 20 | %% Colorschemes %% 21 | % 22 | % This product includes color specifications and designs developed by Cynthia Brewer. 23 | % See the ColorBrewer website for further information about each colorscheme, 24 | % colour-blind suitability, licensing, and citations: http://colorbrewer.org/ 25 | % Each colorscheme is defined by a set of hand-picked RGB values (nodes). 26 | % 27 | % To reverse the colormap sequence prefix the colorscheme name with '-'. 28 | % 29 | % Diverging | Qualitative | Sequential 30 | % ----------|-------------|------------------ 31 | % BrBG | Accent | Blues PuBuGn 32 | % PiYG | Dark2 | BuGn PuRd 33 | % PRGn | Paired | BuPu Purples 34 | % PuOr | Pastel1 | GnBu RdPu 35 | % RdBu | Pastel2 | Greens Reds 36 | % RdGy | Set1 | Greys YlGn 37 | % RdYlBu | Set2 | OrRd YlGnBu 38 | % RdYlGn | Set3 | Oranges YlOrBr 39 | % Spectral | | PuBu YlOrRd 40 | % 41 | % If is greater than the requested colorscheme's defining nodes then: 42 | % - Diverging and Sequential colorschemes are interpolated in Lab colorspace. 43 | % - Qualitative colorschemes repeat the nodes (i.e. just like LINES does). 44 | % Else: 45 | % - Exact values from the ColorBrewer colorschemes are returned. 46 | % 47 | %% Examples %% 48 | % 49 | %%% New colors for the COLORMAP example: 50 | % >> S = load('spine'); 51 | % >> image(S.X) 52 | % >> colormap(brewermap([],"YlGnBu")) 53 | % 54 | %%% New colors for the SURF example: 55 | % >> [X,Y,Z] = peaks(30); 56 | % >> surfc(X,Y,Z) 57 | % >> colormap(brewermap([],'RdYlGn')) 58 | % >> axis([-3,3,-3,3,-10,5]) 59 | % 60 | %%% Plot a colorscheme's RGB values: 61 | % >> rgbplot(brewermap(NaN, 'Blues')) % standard 62 | % >> rgbplot(brewermap(NaN,'-Blues')) % reversed 63 | % 64 | %%% View information about a colorscheme: 65 | % >> [~,num,typ] = brewermap(NaN,'Paired') 66 | % num = 12 67 | % typ = 'Qualitative' 68 | % 69 | %%% Multi-line plot using matrices: 70 | % >> N = 6; 71 | % >> axes('ColorOrder',brewermap(N,'Pastel2'),'NextPlot','replacechildren') 72 | % >> X = linspace(0,pi*3,1000); 73 | % >> Y = bsxfun(@(x,n)n*sin(x+2*n*pi/N), X(:), 1:N); 74 | % >> plot(X,Y, 'linewidth',4) 75 | % 76 | %%% Multi-line plot in a loop: 77 | % set(0,'DefaultAxesColorOrder',brewermap(NaN,'Accent')) 78 | % N = 6; 79 | % X = linspace(0,pi*3,1000); 80 | % Y = bsxfun(@(x,n)n*sin(x+2*n*pi/N), X(:), 1:N); 81 | % for n = 1:N 82 | % plot(X(:),Y(:,n), 'linewidth',4); 83 | % hold all 84 | % end 85 | % 86 | %% Input and Output Arguments %% 87 | % 88 | %%% Inputs: 89 | % N = NumericScalar, N>=0, an integer to specify the colormap length. 90 | % = [], same length as the current figure's colormap (see COLORMAP). 91 | % = NaN, same length as the defining RGB nodes (useful for line ColorOrder). 92 | % scheme = CharRowVector or StringScalar, a ColorBrewer colorscheme name. 93 | % 94 | %%% Outputs: 95 | % map = NumericMatrix, size Nx3, a colormap of RGB values between 0 and 1. 96 | % num = NumericVector, the number of nodes defining the ColorBrewer colorscheme. 97 | % typ = CharRowVector, the colorscheme type: 'Diverging'/'Qualitative'/'Sequential'. 98 | % 99 | % See also BREWERMAP_PLOT BREWERMAP_VIEW PRESET_COLORMAP CUBEHELIX MAXDISTCOLOR 100 | % LBMAP PARULA LINES RGBPLOT COLORMAP COLORBAR PLOT PLOT3 AXES SET CONTOURF 101 | 102 | %% Input Wrangling %% 103 | % 104 | persistent bmc scm txt 105 | % 106 | if isempty(bmc) 107 | bmc = bmColors(); 108 | end 109 | % 110 | if nargin==0 111 | N = []; 112 | end 113 | % 114 | err = 'First input must be a real positive scalar numeric or [] or NaN.'; 115 | % 116 | if nargout==0 && nargin==0 117 | hdr = { 'Type'; 'Scheme'; 'Nodes'}; 118 | tsn = [{bmc.typ};{bmc.str};{bmc.num}]; 119 | fprintf('%-12s %-9s %s\n',hdr{:}); 120 | fprintf('%-12s %-9s %u\n',tsn{:}); 121 | return 122 | elseif isnumeric(N) 123 | if isequal(N,[]) 124 | % Default N is the same as MATLAB colormaps: 125 | N = cmDefaultN(); 126 | else 127 | assert(isscalar(N),... 128 | 'SC:brewermap:N:NotScalarNumeric',err) 129 | assert(isnan(N) || isreal(N) && isfinite(N) && fix(N)==N && N>=0,... 130 | 'SC:brewermap:N:NotWholeRealNotNaN',err) 131 | end 132 | if nargin<2 133 | assert(~isempty(scm),... 134 | 'SC:colorbrewer:scheme:NotPreset',... 135 | 'Colorscheme must be preset before this call: BREWERMAP(SCHEME)') 136 | scheme = scm; 137 | else 138 | scheme = bm1s2c(scheme); 139 | assert(ischar(scheme) && ndims(scheme)==2 && size(scheme,1)==1,... 140 | 'SC:brewermap:scheme:NotText',... 141 | 'Input must be a character vector or string scalar.') 142 | end 143 | else % preset 144 | scheme = bm1s2c(N); 145 | assert(ischar(scheme) && ndims(scheme)==2 && size(scheme,1)==1,... 146 | 'SC:brewermap:N:NotText',... 147 | 'To preset the scheme must be a character vector or string scalar.') 148 | if strcmpi(scheme,'list') 149 | map = {bmc.str}; 150 | num = [bmc.num]; 151 | typ = {bmc.typ}; 152 | return 153 | end 154 | end 155 | % 156 | isr = strncmp(scheme,'-',1) || strncmp(scheme,'*',1); 157 | isd = strncmp(scheme,'+',1) || isr; % direction 158 | ids = strcmpi(scheme(1+isd:end),{bmc.str}); 159 | assert(any(ids),... 160 | 'SC:brewermap:scheme:UnknownColorscheme',... 161 | 'Unknown colorscheme name: "%s"',scheme) 162 | % 163 | num = bmc(ids).num; 164 | typ = bmc(ids).typ; 165 | % 166 | if ~isnumeric(N) % preset 167 | map = txt; 168 | txt = N; 169 | scm = scheme; 170 | return 171 | elseif N==0 172 | map = nan(0,3); 173 | return 174 | elseif isnan(N) 175 | N = num; 176 | end 177 | % 178 | % Downsample: 179 | [idx,itp] = bmIndex(N,num,typ); 180 | % 181 | map = bmc(ids).rgb(idx,:)/255; 182 | % 183 | % Interpolate: 184 | if itp 185 | % 186 | M = [... High-precision sRGB to XYZ matrix: 187 | 0.4124564,0.3575761,0.1804375;... 188 | 0.2126729,0.7151522,0.0721750;... 189 | 0.0193339,0.1191920,0.9503041]; 190 | % Source: http://brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html 191 | % 192 | wpt = [0.95047,1,1.08883]; % D65 193 | % 194 | map = bmRGB2Lab(map,M,wpt); % optional 195 | % 196 | % Extrapolate a small amount beyond the end nodes: 197 | %ido = linspace(0,num+1,N+2); 198 | %ido = ido(2:end-1); 199 | % Interpolation completely within the end nodes: 200 | ido = linspace(1,num,N); 201 | % 202 | switch typ 203 | case 'Diverging' 204 | mid = ceil(num/2); 205 | ida = 1:mid; 206 | idz = mid:num; 207 | map = [... 208 | interp1(ida,map(ida,:),ido(ido<=mid),'pchip');... 209 | interp1(idz,map(idz,:),ido(ido>mid),'pchip')]; 210 | case 'Sequential' 211 | map = interp1(1:num,map,ido,'pchip'); 212 | otherwise 213 | error('SC:brewermap:NoInterp','Cannot interpolate this type.') 214 | end 215 | % 216 | map = bmLab2RGB(map,M,wpt); % optional 217 | % 218 | end 219 | % 220 | % Limit output range: 221 | map = max(0,min(1,map)); 222 | % 223 | % Reverse row order: 224 | if isr 225 | map = map(end:-1:1,:); 226 | end 227 | % 228 | end 229 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%brewermap 230 | function N = cmDefaultN() 231 | % Get the colormap size from the current figure or default colormap. 232 | try 233 | F = get(groot,'CurrentFigure'); 234 | catch %#ok pre HG2 235 | N = size(get(gcf,'colormap'),1); 236 | return 237 | end 238 | if isempty(F) 239 | N = size(get(groot,'DefaultFigureColormap'),1); 240 | else 241 | N = size(F.Colormap,1); 242 | end 243 | end 244 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%cmDefaultN 245 | function arr = bm1s2c(arr) 246 | % If scalar string then extract the character vector, otherwise data is unchanged. 247 | if isa(arr,'string') && isscalar(arr) 248 | arr = arr{1}; 249 | end 250 | end 251 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bm1s2c 252 | function lab = bmRGB2Lab(rgb,M,wpt) 253 | % Convert a matrix of sRGB values to Lab. 254 | %applycform(rgb,makecform('srgb2lab','AdaptedWhitePoint',wpt)) 255 | % RGB2XYZ: 256 | xyz = bmGammaInv(rgb) * M.'; 257 | % XYZ2Lab: 258 | xyz = bsxfun(@rdivide,xyz,wpt); 259 | idx = xyz>(6/29)^3; 260 | F = idx.*(xyz.^(1/3)) + ~idx.*(xyz*(29/6)^2/3+4/29); 261 | lab(:,2:3) = bsxfun(@times,[500,200],F(:,1:2)-F(:,2:3)); 262 | lab(:,1) = 116*F(:,2) - 16; 263 | end 264 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmRGB2Lab 265 | function rgb = bmGammaInv(rgb) 266 | % Inverse gamma correction of sRGB data. 267 | idx = rgb <= 0.04045; 268 | rgb(idx) = rgb(idx) / 12.92; 269 | rgb(~idx) = real(((rgb(~idx) + 0.055) / 1.055).^2.4); 270 | end 271 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmGammaInv 272 | function rgb = bmLab2RGB(lab,M,wpt) 273 | % Convert a matrix of Lab values to sRGB. 274 | %applycform(lab,makecform('lab2srgb','AdaptedWhitePoint',wpt)) 275 | % Lab2XYZ 276 | tmp = bsxfun(@rdivide,lab(:,[2,1,3]),[500,Inf,-200]); 277 | tmp = bsxfun(@plus,tmp,(lab(:,1)+16)/116); 278 | idx = tmp>(6/29); 279 | tmp = idx.*(tmp.^3) + ~idx.*(3*(6/29)^2*(tmp-4/29)); 280 | xyz = bsxfun(@times,tmp,wpt); 281 | % XYZ2RGB 282 | rgb = max(0,min(1, bmGammaCor(xyz / M.'))); 283 | end 284 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%cbLab2RGB 285 | function rgb = bmGammaCor(rgb) 286 | % Gamma correction of sRGB data. 287 | idx = rgb <= 0.0031308; 288 | rgb(idx) = 12.92 * rgb(idx); 289 | rgb(~idx) = real(1.055 * rgb(~idx).^(1/2.4) - 0.055); 290 | end 291 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmGammaCor 292 | function [idx,itp] = bmIndex(N,num,typ) 293 | % Ensure exactly the same colors as the online ColorBrewer colorschemes. 294 | % 295 | itp = N>num; 296 | switch typ 297 | case 'Qualitative' 298 | itp = false; 299 | idx = 1+mod(0:N-1,num); 300 | case 'Diverging' 301 | switch N 302 | case 1 % extrapolated 303 | idx = 8; 304 | case 2 % extrapolated 305 | idx = [4,12]; 306 | case 3 307 | idx = [5,8,11]; 308 | case 4 309 | idx = [3,6,10,13]; 310 | case 5 311 | idx = [3,6,8,10,13]; 312 | case 6 313 | idx = [2,5,7,9,11,14]; 314 | case 7 315 | idx = [2,5,7,8,9,11,14]; 316 | case 8 317 | idx = [2,4,6,7,9,10,12,14]; 318 | case 9 319 | idx = [2,4,6,7,8,9,10,12,14]; 320 | case 10 321 | idx = [1,2,4,6,7,9,10,12,14,15]; 322 | otherwise 323 | idx = [1,2,4,6,7,8,9,10,12,14,15]; 324 | end 325 | case 'Sequential' 326 | switch N 327 | case 1 % extrapolated 328 | idx = 6; 329 | case 2 % extrapolated 330 | idx = [4,8]; 331 | case 3 332 | idx = [3,6,9]; 333 | case 4 334 | idx = [2,5,7,10]; 335 | case 5 336 | idx = [2,5,7,9,11]; 337 | case 6 338 | idx = [2,4,6,7,9,11]; 339 | case 7 340 | idx = [2,4,6,7,8,10,12]; 341 | case 8 342 | idx = [1,3,4,6,7,8,10,12]; 343 | otherwise 344 | idx = [1,3,4,6,7,8,10,11,13]; 345 | end 346 | otherwise 347 | error('SC:brewermap:UnknownType','Unknown type string.') 348 | end 349 | % 350 | end 351 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmIndex 352 | function bmc = bmColors() 353 | % Return a structure of all colorschemes: name, scheme type, RGB values, number of nodes. 354 | % Order: first sort by , then case-insensitive sort by : 355 | bmc(35).str = 'YlOrRd'; 356 | bmc(35).typ = 'Sequential'; 357 | bmc(35).rgb = [255,255,204;255,255,178;255,237,160;254,217,118;254,204,92;254,178,76;253,141,60;252,78,42;240,59,32;227,26,28;189,0,38;177,0,38;128,0,38]; 358 | bmc(34).str = 'YlOrBr'; 359 | bmc(34).typ = 'Sequential'; 360 | bmc(34).rgb = [255,255,229;255,255,212;255,247,188;254,227,145;254,217,142;254,196,79;254,153,41;236,112,20;217,95,14;204,76,2;153,52,4;140,45,4;102,37,6]; 361 | bmc(33).str = 'YlGnBu'; 362 | bmc(33).typ = 'Sequential'; 363 | bmc(33).rgb = [255,255,217;255,255,204;237,248,177;199,233,180;161,218,180;127,205,187;65,182,196;29,145,192;44,127,184;34,94,168;37,52,148;12,44,132;8,29,88]; 364 | bmc(32).str = 'YlGn'; 365 | bmc(32).typ = 'Sequential'; 366 | bmc(32).rgb = [255,255,229;255,255,204;247,252,185;217,240,163;194,230,153;173,221,142;120,198,121;65,171,93;49,163,84;35,132,67;0,104,55;0,90,50;0,69,41]; 367 | bmc(31).str = 'Reds'; 368 | bmc(31).typ = 'Sequential'; 369 | bmc(31).rgb = [255,245,240;254,229,217;254,224,210;252,187,161;252,174,145;252,146,114;251,106,74;239,59,44;222,45,38;203,24,29;165,15,21;153,0,13;103,0,13]; 370 | bmc(30).str = 'RdPu'; 371 | bmc(30).typ = 'Sequential'; 372 | bmc(30).rgb = [255,247,243;254,235,226;253,224,221;252,197,192;251,180,185;250,159,181;247,104,161;221,52,151;197,27,138;174,1,126;122,1,119;122,1,119;73,0,106]; 373 | bmc(29).str = 'Purples'; 374 | bmc(29).typ = 'Sequential'; 375 | bmc(29).rgb = [252,251,253;242,240,247;239,237,245;218,218,235;203,201,226;188,189,220;158,154,200;128,125,186;117,107,177;106,81,163;84,39,143;74,20,134;63,0,125]; 376 | bmc(28).str = 'PuRd'; 377 | bmc(28).typ = 'Sequential'; 378 | bmc(28).rgb = [247,244,249;241,238,246;231,225,239;212,185,218;215,181,216;201,148,199;223,101,176;231,41,138;221,28,119;206,18,86;152,0,67;145,0,63;103,0,31]; 379 | bmc(27).str = 'PuBuGn'; 380 | bmc(27).typ = 'Sequential'; 381 | bmc(27).rgb = [255,247,251;246,239,247;236,226,240;208,209,230;189,201,225;166,189,219;103,169,207;54,144,192;28,144,153;2,129,138;1,108,89;1,100,80;1,70,54]; 382 | bmc(26).str = 'PuBu'; 383 | bmc(26).typ = 'Sequential'; 384 | bmc(26).rgb = [255,247,251;241,238,246;236,231,242;208,209,230;189,201,225;166,189,219;116,169,207;54,144,192;43,140,190;5,112,176;4,90,141;3,78,123;2,56,88]; 385 | bmc(25).str = 'Oranges'; 386 | bmc(25).typ = 'Sequential'; 387 | bmc(25).rgb = [255,245,235;254,237,222;254,230,206;253,208,162;253,190,133;253,174,107;253,141,60;241,105,19;230,85,13;217,72,1;166,54,3;140,45,4;127,39,4]; 388 | bmc(24).str = 'OrRd'; 389 | bmc(24).typ = 'Sequential'; 390 | bmc(24).rgb = [255,247,236;254,240,217;254,232,200;253,212,158;253,204,138;253,187,132;252,141,89;239,101,72;227,74,51;215,48,31;179,0,0;153,0,0;127,0,0]; 391 | bmc(23).str = 'Greys'; 392 | bmc(23).typ = 'Sequential'; 393 | bmc(23).rgb = [255,255,255;247,247,247;240,240,240;217,217,217;204,204,204;189,189,189;150,150,150;115,115,115;99,99,99;82,82,82;37,37,37;37,37,37;0,0,0]; 394 | bmc(22).str = 'Greens'; 395 | bmc(22).typ = 'Sequential'; 396 | bmc(22).rgb = [247,252,245;237,248,233;229,245,224;199,233,192;186,228,179;161,217,155;116,196,118;65,171,93;49,163,84;35,139,69;0,109,44;0,90,50;0,68,27]; 397 | bmc(21).str = 'GnBu'; 398 | bmc(21).typ = 'Sequential'; 399 | bmc(21).rgb = [247,252,240;240,249,232;224,243,219;204,235,197;186,228,188;168,221,181;123,204,196;78,179,211;67,162,202;43,140,190;8,104,172;8,88,158;8,64,129]; 400 | bmc(20).str = 'BuPu'; 401 | bmc(20).typ = 'Sequential'; 402 | bmc(20).rgb = [247,252,253;237,248,251;224,236,244;191,211,230;179,205,227;158,188,218;140,150,198;140,107,177;136,86,167;136,65,157;129,15,124;110,1,107;77,0,75]; 403 | bmc(19).str = 'BuGn'; 404 | bmc(19).typ = 'Sequential'; 405 | bmc(19).rgb = [247,252,253;237,248,251;229,245,249;204,236,230;178,226,226;153,216,201;102,194,164;65,174,118;44,162,95;35,139,69;0,109,44;0,88,36;0,68,27]; 406 | bmc(18).str = 'Blues'; 407 | bmc(18).typ = 'Sequential'; 408 | bmc(18).rgb = [247,251,255;239,243,255;222,235,247;198,219,239;189,215,231;158,202,225;107,174,214;66,146,198;49,130,189;33,113,181;8,81,156;8,69,148;8,48,107]; 409 | bmc(17).str = 'Set3'; 410 | bmc(17).typ = 'Qualitative'; 411 | bmc(17).rgb = [141,211,199;255,255,179;190,186,218;251,128,114;128,177,211;253,180,98;179,222,105;252,205,229;217,217,217;188,128,189;204,235,197;255,237,111]; 412 | bmc(16).str = 'Set2'; 413 | bmc(16).typ = 'Qualitative'; 414 | bmc(16).rgb = [102,194,165;252,141,98;141,160,203;231,138,195;166,216,84;255,217,47;229,196,148;179,179,179]; 415 | bmc(15).str = 'Set1'; 416 | bmc(15).typ = 'Qualitative'; 417 | bmc(15).rgb = [228,26,28;55,126,184;77,175,74;152,78,163;255,127,0;255,255,51;166,86,40;247,129,191;153,153,153]; 418 | bmc(14).str = 'Pastel2'; 419 | bmc(14).typ = 'Qualitative'; 420 | bmc(14).rgb = [179,226,205;253,205,172;203,213,232;244,202,228;230,245,201;255,242,174;241,226,204;204,204,204]; 421 | bmc(13).str = 'Pastel1'; 422 | bmc(13).typ = 'Qualitative'; 423 | bmc(13).rgb = [251,180,174;179,205,227;204,235,197;222,203,228;254,217,166;255,255,204;229,216,189;253,218,236;242,242,242]; 424 | bmc(12).str = 'Paired'; 425 | bmc(12).typ = 'Qualitative'; 426 | bmc(12).rgb = [166,206,227;31,120,180;178,223,138;51,160,44;251,154,153;227,26,28;253,191,111;255,127,0;202,178,214;106,61,154;255,255,153;177,89,40]; 427 | bmc(11).str = 'Dark2'; 428 | bmc(11).typ = 'Qualitative'; 429 | bmc(11).rgb = [27,158,119;217,95,2;117,112,179;231,41,138;102,166,30;230,171,2;166,118,29;102,102,102]; 430 | bmc(10).str = 'Accent'; 431 | bmc(10).typ = 'Qualitative'; 432 | bmc(10).rgb = [127,201,127;190,174,212;253,192,134;255,255,153;56,108,176;240,2,127;191,91,23;102,102,102]; 433 | bmc(09).str = 'Spectral'; 434 | bmc(09).typ = 'Diverging'; 435 | bmc(09).rgb = [158,1,66;213,62,79;215,25,28;244,109,67;252,141,89;253,174,97;254,224,139;255,255,191;230,245,152;171,221,164;153,213,148;102,194,165;43,131,186;50,136,189;94,79,162]; 436 | bmc(08).str = 'RdYlGn'; 437 | bmc(08).typ = 'Diverging'; 438 | bmc(08).rgb = [165,0,38;215,48,39;215,25,28;244,109,67;252,141,89;253,174,97;254,224,139;255,255,191;217,239,139;166,217,106;145,207,96;102,189,99;26,150,65;26,152,80;0,104,55]; 439 | bmc(07).str = 'RdYlBu'; 440 | bmc(07).typ = 'Diverging'; 441 | bmc(07).rgb = [165,0,38;215,48,39;215,25,28;244,109,67;252,141,89;253,174,97;254,224,144;255,255,191;224,243,248;171,217,233;145,191,219;116,173,209;44,123,182;69,117,180;49,54,149]; 442 | bmc(06).str = 'RdGy'; 443 | bmc(06).typ = 'Diverging'; 444 | bmc(06).rgb = [103,0,31;178,24,43;202,0,32;214,96,77;239,138,98;244,165,130;253,219,199;255,255,255;224,224,224;186,186,186;153,153,153;135,135,135;64,64,64;77,77,77;26,26,26]; 445 | bmc(05).str = 'RdBu'; 446 | bmc(05).typ = 'Diverging'; 447 | bmc(05).rgb = [103,0,31;178,24,43;202,0,32;214,96,77;239,138,98;244,165,130;253,219,199;247,247,247;209,229,240;146,197,222;103,169,207;67,147,195;5,113,176;33,102,172;5,48,97]; 448 | bmc(04).str = 'PuOr'; 449 | bmc(04).typ = 'Diverging'; 450 | bmc(04).rgb = [127,59,8;179,88,6;230,97,1;224,130,20;241,163,64;253,184,99;254,224,182;247,247,247;216,218,235;178,171,210;153,142,195;128,115,172;94,60,153;84,39,136;45,0,75]; 451 | bmc(03).str = 'PRGn'; 452 | bmc(03).typ = 'Diverging'; 453 | bmc(03).rgb = [64,0,75;118,42,131;123,50,148;153,112,171;175,141,195;194,165,207;231,212,232;247,247,247;217,240,211;166,219,160;127,191,123;90,174,97;0,136,55;27,120,55;0,68,27]; 454 | bmc(02).str = 'PiYG'; 455 | bmc(02).typ = 'Diverging'; 456 | bmc(02).rgb = [142,1,82;197,27,125;208,28,139;222,119,174;233,163,201;241,182,218;253,224,239;247,247,247;230,245,208;184,225,134;161,215,106;127,188,65;77,172,38;77,146,33;39,100,25]; 457 | bmc(01).str = 'BrBG'; 458 | bmc(01).typ = 'Diverging'; 459 | bmc(01).rgb = [84,48,5;140,81,10;166,97,26;191,129,45;216,179,101;223,194,125;246,232,195;245,245,245;199,234,229;128,205,193;90,180,172;53,151,143;1,133,113;1,102,94;0,60,48]; 460 | % number of nodes: 461 | for k = 1:numel(bmc) 462 | switch bmc(k).typ 463 | case 'Diverging' 464 | bmc(k).num = 11; 465 | case 'Qualitative' 466 | bmc(k).num = size(bmc(k).rgb,1); 467 | case 'Sequential' 468 | bmc(k).num = 9; 469 | otherwise 470 | error('SC:brewermap:UnknownType','Unknown type string.') 471 | end 472 | end 473 | % 474 | end 475 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmColors 476 | % 477 | % Code and Implementation: 478 | % Copyright (c) 2014-2022 Stephen Cobeldick 479 | % Color Values Only: 480 | % Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University. 481 | % 482 | % Licensed under the Apache License, Version 2.0 (the "License"); 483 | % you may not use this file except in compliance with the License. 484 | % You may obtain a copy of the License at 485 | % 486 | % http://www.apache.org/licenses/LICENSE-2.0 487 | % 488 | % Unless required by applicable law or agreed to in writing, software 489 | % distributed under the License is distributed on an "AS IS" BASIS, 490 | % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 491 | % See the License for the specific language governing permissions and limitations under the License. 492 | % 493 | % Redistribution and use in source and binary forms, with or without 494 | % modification, are permitted provided that the following conditions are met: 495 | % 496 | % 1. Redistributions as source code must retain the above copyright notice, this 497 | % list of conditions and the following disclaimer. 498 | % 499 | % 2. The end-user documentation included with the redistribution, if any, must 500 | % include the following acknowledgment: "This product includes color 501 | % specifications and designs developed by Cynthia Brewer 502 | % (http://colorbrewer.org/)." Alternately, this acknowledgment may appear in the 503 | % software itself, if and wherever such third-party acknowledgments normally appear. 504 | % 505 | % 4. The name "ColorBrewer" must not be used to endorse or promote products 506 | % derived from this software without prior written permission. For written 507 | % permission, please contact Cynthia Brewer at cbrewer@psu.edu. 508 | % 509 | % 5. Products derived from this software may not be called "ColorBrewer", nor 510 | % may "ColorBrewer" appear in their name, without prior written permission of Cynthia Brewer. 511 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%license --------------------------------------------------------------------------------