├── LICENSE ├── README.md ├── FIG4.m ├── VALSE.m └── newSVALSEv1p00.m /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 YONGSUNG PARK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sequential Variational Bayesian estimation for directions-of-arrival 2 | 3 | A set of MATLAB codes for direction-of-arrival (DOA) estimation, beamforming. 4 | 5 | # Features 6 | 7 | The codes provide: 8 | 9 | -Beamforming using variational Bayesian line spectral estimation (VALSE) [Badiu 16] 10 | 11 | -Beamforming using sequential VALSE (SVALSE) 12 | 13 | # Citation 14 | 15 | -**[Y. Park, F. Meyer, and P. Gerstoft, “Graph-based sequential beamforming,” J. Acoust. Soc. Am. 153(1) (2023).](https://doi.org/10.1121/10.0016876)** 16 | -[F. Meyer, Y. Park, and P. Gerstoft, “Variational Bayesian estimation of time-varying DOAs,” in Proc. IEEE FUSION (2020), pp. 1–6.](https://ieeexplore.ieee.org/abstract/document/9190217) 17 | -[Y. Park, F. Meyer, and P. Gerstoft, “Learning-Aided Initialization for Variational Bayesian DOA Estimation,” in Proc. IEEE ICASSP (2022), pp. 4938–4942.](https://ieeexplore.ieee.org/abstract/document/9746180) 18 | 19 | [[pdf]](https://www.dropbox.com/sh/qgi9symf43rki41/AADSrGg567PS86_S4A7j6aWEa?dl=0) 20 | 21 | -[M.-A. Badiu, T. L. Hansen, and B. H. Fleury, “Variational Bayesian inference of line spectra,” IEEE Trans. Signal Process. 65(9) (2017), pp. 2247–2261.](https://ieeexplore.ieee.org/abstract/document/7827161?casa_token=0hexzsSvHEkAAAAA:cNaPS8ZqfN1TT3dueLPXZMqc86OBIiLb3q_DPnAnz728_5gX2dimYDrhjy1DwCJyFBGadWCH6rA) 22 | -[J. Zhu, Q. Zhang, P. Gerstoft, M.-A. Badiu, and Z. Xu, “Grid-less variational Bayesian line spectral estimation with multiple measurement vectors,” Signal Processing 161 (2019), pp. 155–164.](https://www.sciencedirect.com/science/article/pii/S0165168419301173?casa_token=lG-8B0y74_8AAAAA:EEpg0-90WSlh9p8yk-8-hNk1eqgCd7xSxrvGdAq5AektWe9gpU0lfTvD2mXkSuNspqbuMhNGJpo) 23 | 24 | Sparse Bayesian learning (SBL) implementation is also available. [[CODE]](https://github.com/ysparkwin/Sequential_SBL) 25 | -[Y. Park, F. Meyer, and P. Gerstoft, “Sequential sparse Bayesian learning for time-varying direction of arrival,” J. Acoust. Soc. Am. 149(3) (2021).](https://doi.org/10.1121/10.0003802) 26 | 27 | # Updates 28 | 29 | Version 1.0: (01/21/2023 by Y. Park) 30 | 31 | # Contact 32 | 33 | [Yongsung Park](https://scholar.google.com/citations?user=jcMeNjMAAAAJ&hl=en&oi=ao), [Florian Meyer](https://scholar.google.com/citations?user=XnMK9VcAAAAJ&hl=en&oi=ao), & [Peter Gerstoft](https://scholar.google.com/citations?user=oLMfDnYAAAAJ&hl=en) 34 | AOPE/WHOI 35 | MPL/SIO/UCSD 36 | yongsung.park@whoi.edu 37 | flmeyer@ucsd.edu 38 | gerstoft@ucsd.edu 39 | ## [Noiselab](http://noiselab.ucsd.edu/) / [F. Meyer's lab](http://fmeyer.ucsd.edu/) 40 | -------------------------------------------------------------------------------- /FIG4.m: -------------------------------------------------------------------------------- 1 | % Version 1.0: (01/21/2023) 2 | % written by Yongsung Park 3 | 4 | % Yongsung Park, Florian Meyer, & Peter Gerstoft 5 | % MPL/SIO/UCSD 6 | % yongsungpark@ucsd.edu / flmeyer@ucsd.edu / gerstoft@ucsd.edu 7 | % noiselab.ucsd.edu 8 | 9 | % Citation 10 | % Y. Park, F. Meyer, and P. Gerstoft, "Graph-based sequential beamforming," J. Acoust. Soc. Am. 153(1), (2023). 11 | % https://doi.org/10.1121/10.0016876 12 | % F. Meyer, Y. Park, and P. Gerstoft, "Variational Bayesian estimation of time-varying DOAs," in Proc. IEEE FUSION (2020), pp. 1–6. 13 | % https://doi.org/10.23919/FUSION45008.2020.9190217 14 | % Y. Park, F. Meyer, and P. Gerstoft, "Learning-Aided Initialization for Variational Bayesian DOA Estimation," in Proc. IEEE ICASSP (2022), pp. 4938–4942. 15 | % https://doi.org/10.1109/ICASSP43922.2022.9746180 16 | 17 | %% 18 | clear; clc; 19 | close all; 20 | 21 | dbstop if error; 22 | 23 | % addpath([cd,'/_common']) 24 | 25 | SNRlist = [20]; 26 | for nSNR = 1:length(SNRlist) 27 | Nsim = 1; 28 | for nsim=1:Nsim 29 | Nrng=27; rng(Nrng+nsim) 30 | % rng(nsim) 31 | disp(['SNR',num2str(SNRlist(nSNR)),'_',num2str(nsim)]) 32 | % Environment parameters 33 | c = 1500; % speed of sound 34 | f = 200; % frequency 35 | lambda = c/f; % wavelength 36 | 37 | % ULA-horizontal array configuration 38 | Nsensor = 15; % number of sensors 39 | d = 1/2*lambda; % intersensor spacing 40 | q = (0:1:(Nsensor-1))'; % sensor numbering 41 | xq = (q-(Nsensor-1)/2)*d; % sensor locations 42 | 43 | % sensor configuration structure 44 | Sensor_s.Nsensor = Nsensor; 45 | Sensor_s.lambda = lambda; 46 | Sensor_s.d = d; 47 | Sensor_s.q = q; 48 | Sensor_s.xq = xq; 49 | 50 | % signal generation parameters 51 | SNR = SNRlist(nSNR); 52 | 53 | % total number of snapshots 54 | Nsnapshot = 50; 55 | 56 | % range of angle space 57 | thetalim = [-90 90]; 58 | 59 | theta_separation = 0.5; 60 | 61 | % Angular search grid 62 | theta = (thetalim(1):theta_separation:thetalim(2))'; 63 | Ntheta = length(theta); 64 | 65 | % Design/steering matrix (Sensing matrix) 66 | sin_theta = sind(theta); 67 | sensingMatrix = exp(-1i*2*pi/lambda*xq*sin_theta.')/sqrt(Nsensor); 68 | 69 | % Generate received signal 70 | anglesTrue = [-70; -55; -40; 35; 50; 65]; % DOA of sources at first snapshot [deg] 71 | anglesTracks = repmat(anglesTrue,[1,Nsnapshot]); 72 | anglesTracks(3,:) = anglesTracks(3,1) - 2*anglesTracks(3,1)./(1+exp(-.1*(-Nsnapshot/2:-Nsnapshot/2+Nsnapshot-1))); 73 | anglesTracks(4,:) = anglesTracks(4,1) - 1.00*(0:Nsnapshot-1)'; 74 | sinAnglesTracks = sind(anglesTracks); 75 | Nsources = numel(anglesTrue); 76 | 77 | receivedSignal = zeros(Nsensor,Nsnapshot); 78 | source_amp = zeros(Nsources,Nsnapshot); 79 | for snapshot = 1:Nsnapshot 80 | % Source generation 81 | % Complex Gaussian with zero-mean 82 | source_amp(:,snapshot) = complex(randn(size(anglesTrue)),randn(size(anglesTrue)))/sqrt(2); 83 | Xsource = source_amp(:,snapshot); 84 | 85 | % Represenation matrix (steering matrix) 86 | transmitMatrix = exp( -1i*2*pi/lambda*xq*sinAnglesTracks(:,snapshot).' )/sqrt(Nsensor); 87 | 88 | % Received signal without noise 89 | receivedSignal(:,snapshot) = sum(transmitMatrix*diag(Xsource),2); 90 | 91 | % add noise to the signals 92 | rnl = 10^(-SNR/20)*norm(Xsource); 93 | nwhite = complex(randn(Nsensor,1),randn(Nsensor,1))/sqrt(2*Nsensor); 94 | e = nwhite * rnl; % error vector 95 | receivedSignal(:,snapshot) = receivedSignal(:,snapshot) + e; 96 | end 97 | ActSrcInd = cell(1,50); ActSrcInd(:) = {(1:6).'}; 98 | 99 | for snapshot = 1:Nsnapshot 100 | snapshot 101 | %% Original VALSE 102 | disp('Original VALSE is running ...') 103 | outputValse = VALSE( receivedSignal(:,snapshot), q, 1, receivedSignal(:,snapshot) ); 104 | % outputValse.ospa = getOSPA(anglesTracks(:,snapshot),asind(-outputValse.freqs * lambda/( 2 * pi * d)),8,2); 105 | % outputValse.card = numel(outputValse.freqs); 106 | 107 | if exist('outputsValse','var')==0, outputsValse = []; end 108 | outputsValse = [outputsValse;outputValse]; 109 | 110 | %% Sequential VALSE 111 | % 1/sqrt(148) = 0.0822 rad (sigma_r) 112 | % asind( (1/sqrt(148)) / ((2*pi*198 * 1500/198/2)/1500) ) = 1.5 deg 113 | % 1/power( (sind(1.5)* (2*pi*198 * 1500/198/2)/1500 ),2 ) = 148 114 | kappaAdd = 148; 115 | disp('SVALSE w/ new initialization is running ...') 116 | if snapshot==1, prior2 = []; end 117 | outputSValseNI = newSVALSEv1p00( receivedSignal(:,snapshot), q, 1, receivedSignal(:,snapshot), prior2 ); 118 | % outputSValseNI.ospa = getOSPA(anglesTracks(:,snapshot),asind(-outputSValseNI.freqs * lambda/( 2 * pi * d)),8,2); 119 | % outputSValseNI.card = numel(outputSValseNI.freqs); 120 | 121 | rhoPriorExisting = 0.75; 122 | rhoPriorNonExisting = 0.10; 123 | prior2.mus = outputSValseNI.mus; 124 | prior2.kappas = 1./(1./outputSValseNI.kappas + 1/kappaAdd); % new angle is old angle plus noise 125 | numPriorComponents = size(outputSValseNI.freqs,1); 126 | prior2.rho = rhoPriorNonExisting * ones(Nsensor,1); 127 | prior2.rho(1:numPriorComponents) = rhoPriorExisting; 128 | 129 | if exist('outputsSValseNI','var')==0, outputsSValseNI = []; end 130 | outputsSValseNI = [outputsSValseNI;outputSValseNI]; 131 | 132 | end 133 | end 134 | % vars = who(); 135 | % varnames = vars(contains(vars, 'outputs')); 136 | % save(['D_CG6s_SNR',num2str(SNR),'_',num2str(nsim)],... 137 | % 'anglesTracks',varnames{:}); 138 | end 139 | % save(['results_CG6_',num2str(SNR)]) 140 | % save(['data_results']) 141 | 142 | %% Plot CBF & VALSE 143 | if exist('outputPlot','var') == 0, outputPlot = outputsValse; end 144 | if exist('FoS','var') == 0, FoS = 18; end 145 | 146 | figure(2); 147 | set(gcf,'position',[530,100,560,420]); 148 | imagesc(1:Nsnapshot,theta,-inf); 149 | caxis([-20 0]) 150 | 151 | % load hotAndCold, colormap(cmap) 152 | colormap parula 153 | 154 | rtIndex = []; rtTheta = []; rtMu = []; 155 | for index=1:Nsnapshot 156 | rTheta = asind(-outputPlot(index).freqs * lambda/( 2 * pi * d)); 157 | rMu = abs(outputPlot(index).amps); 158 | rMu = 20*log10( rMu / max(rMu) ); 159 | 160 | rtIndex = [rtIndex;index*ones(size(rMu))]; 161 | rtTheta = [rtTheta;rTheta]; 162 | rtMu = [rtMu;rMu]; 163 | end 164 | 165 | % hold on; scatter(rtIndex,rtTheta,8,rtMu,'o','linewidth',1); hold off; 166 | hold on; scatter(rtIndex,rtTheta,50,rtMu,'filled','o','linewidth',.5,... 167 | 'MarkerEdgeColor','k'); hold off; 168 | 169 | title('Non-sequential VALSE') 170 | xlabel('Time step','interpreter','latex') 171 | ylabel('DOA~[$^\circ$]','interpreter','latex') 172 | box on 173 | set(gca,'fontsize',FoS,'YDir','normal','TickLabelInterpreter','latex','YTick',-80:40:80) 174 | axis([.5 Nsnapshot+.5 -90 90]) 175 | % set(gca,'YTickLabel','') 176 | 177 | outputBeamformer = sensingMatrix' * receivedSignal; 178 | figure(3); 179 | set(gcf,'position',[1,100,560,420]); 180 | imagesc(1:Nsnapshot,theta,10*log10( (abs(outputBeamformer).^2) ./ max((abs(outputBeamformer).^2),[],1))); 181 | % load hotAndCold, colormap(cmap) 182 | colormap parula 183 | caxis([-20 0]) 184 | title('Conventional beamforming') 185 | xlabel('Time step','interpreter','latex') 186 | ylabel('DOA~[$^\circ$]','interpreter','latex') 187 | box on 188 | set(gca,'fontsize',FoS,'YDir','normal','TickLabelInterpreter','latex','YTick',-80:40:80) 189 | axis([.5 Nsnapshot+.5 -90 90]) 190 | 191 | hold on; 192 | for snapshot=1:50 193 | srcind = ActSrcInd{snapshot}; 194 | plot(snapshot,anglesTracks(srcind,snapshot),'kx',... 195 | 'linewidth',1.5,'markersize',8) 196 | end 197 | hold off 198 | 199 | %% Plot SVALSE 200 | outputPlot = outputsSValseNI; 201 | Nsnapshot = numel(outputPlot); 202 | if exist('FoS','var') == 0, FoS = 18; end 203 | 204 | figure(1); 205 | set(gcf,'position',[1060,100,560,420]); 206 | imagesc(1:Nsnapshot,theta,-inf); 207 | caxis([-20 0]) 208 | 209 | % load hotAndCold, colormap(cmap) 210 | colormap parula 211 | 212 | rtIndex = []; rtTheta = []; rtMu = []; 213 | for index=1:Nsnapshot 214 | rTheta = asind(-outputPlot(index).freqs * lambda/( 2 * pi * d)); 215 | rMu = abs(outputPlot(index).amps); 216 | rMu = 20*log10( rMu / max(rMu) ); 217 | 218 | rtIndex = [rtIndex;index*ones(size(rMu))]; 219 | rtTheta = [rtTheta;rTheta]; 220 | rtMu = [rtMu;rMu]; 221 | end 222 | 223 | % hold on; scatter(rtIndex,rtTheta,8,rtMu,'o','linewidth',1); hold off; 224 | hold on; scatter(rtIndex,rtTheta,50,rtMu,'filled','o','linewidth',.5,... 225 | 'MarkerEdgeColor','k'); hold off; 226 | 227 | title('Sequential VALSE') 228 | xlabel('Time step','interpreter','latex') 229 | ylabel('DOA~[$^\circ$]','interpreter','latex') 230 | box on 231 | set(gca,'fontsize',FoS,'YDir','normal','TickLabelInterpreter','latex','YTick',-80:40:80) 232 | axis([.5 Nsnapshot+.5 -90 90]) 233 | % set(gca,'YTickLabel','') 234 | 235 | %% 236 | % rmpath([cd,'/_common']) -------------------------------------------------------------------------------- /VALSE.m: -------------------------------------------------------------------------------- 1 | function out = VALSE( y, m, ha, x ) 2 | %VALSE algorithm for line spectral estimation 3 | % INPUTS: 4 | % y - measurement vector of size M 5 | % m - is a vector containing the indices (in ascending order) of the M 6 | % measurements; subset of {0,1,...,m(end)} 7 | % ha - indicator determining which approximation of the 8 | % frequency posterior pdfs will be used: 9 | % ha=1 will use Heuristic #1 10 | % ha=2 will use Heuristic #2 11 | % ha=3 will use point estimation of the frequencies (VALSE-pt) 12 | % x - the true signal - used for computing the MSE vs iterations 13 | % 14 | % OUTPUTS: 15 | % out - structure 16 | % .freqs - vector of frequency estimates 17 | % .amps - vector of amplitude estimates 18 | % .x_estimate - reconstructed signal 19 | % .noise_var - estimate of the noise variance 20 | % .iterations - number of iterations until convergence 21 | % .mse - evolution of the mse of x_estimate with iterations 22 | % .K - evolution of the estimated number of components with iterations 23 | % 24 | % See full paper: 25 | % "Variational Bayesian Inference of Line Spectra" 26 | % preprint available at https://arxiv.org/abs/1604.03744 27 | % by Mihai Badiu, Thomas Hansen, and Bernard Fleury 28 | % 29 | % Matlab code written by Mihai Badiu, March 2016 30 | 31 | M = size(y,1); 32 | N = m(M)+1; % size of full data 33 | y2 = y'*y; 34 | L = N; % assumed number of components 35 | A = zeros(L,L); 36 | J = zeros(L,L); 37 | h = zeros(L,1); 38 | w = zeros(L,1); 39 | C = zeros(L); 40 | T = 2000; % max number of iterations (5000 is very conservative, typically converges in tens of iterations) 41 | mse = zeros(T,1); 42 | Kt = zeros(T,1); 43 | t = 1; 44 | 45 | % Initialization of the posterior pdfs of the frequencies 46 | res = y; 47 | for l=1:L 48 | % noncoherent estimation of the pdf 49 | yI = zeros(N,1); 50 | yI(m+1) = res; 51 | R = yI*yI'; 52 | sR = zeros(N-1,1); 53 | for i=2:N 54 | for k=1:i-1 55 | sR(i-k) = sR(i-k) + R(i,k); 56 | end 57 | end 58 | if l==1 % use the sample autocorrelation to initialize the model parameters 59 | Rh = toeplitz([sum(diag(R));sR])/N; 60 | evs = sort(real(eig(Rh)),'ascend'); 61 | nu = mean(evs(1:floor(N/4))); 62 | K = floor(L/2); 63 | rho = K/L; 64 | tau = (y2/M-nu)/(rho*L); 65 | end 66 | etaI = 2*sR/(M+nu/tau)/nu; 67 | ind = find(abs(etaI)>0); 68 | if ha~=3 69 | [~,mu,kappa] = Heuristic2(etaI(ind), ind); 70 | A(m+1,l) = exp(1i*m * mu) .* ( besseli(m,kappa,1)/besseli(0,kappa,1) ); 71 | else 72 | [~,mu] = pntFreqEst(etaI(ind), ind); 73 | A(m+1,l) = exp(1i*m * mu); 74 | end 75 | 76 | % compute weight estimates; rank one update 77 | w_temp = w(1:l-1); C_temp = C(1:l-1,1:l-1); 78 | J(1:l-1,l) = A(m+1,1:l-1)'*A(m+1,l); J(l,1:l-1) = J(1:l-1,l)'; J(l,l) = M; 79 | h(l) = A(m+1,l)'*y; 80 | v = nu / ( M + nu/tau - real(J(1:l-1,l)'*C_temp*J(1:l-1,l))/nu ); 81 | u = v .* (h(l) - J(1:l-1,l)'*w_temp)/nu; 82 | w(l) = u; 83 | ctemp = C_temp*J(1:l-1,l)/nu; 84 | w(1:l-1) = w_temp - ctemp*u; 85 | C(1:l-1,1:l-1) = C_temp + v*(ctemp*ctemp'); 86 | C(1:l-1,l) = -v*ctemp; C(l,1:l-1) = C(1:l-1,l)'; C(l,l) = v; 87 | 88 | % the residual signal 89 | res = y - A(m+1,1:l)*w(1:l); 90 | 91 | if l==K % save mse and K at initialization 92 | xro = A(:,1:l)*w(1:l); 93 | mse(t) = norm(x-xro)^2/norm(x)^2; 94 | Kt(t) = K; 95 | end 96 | end 97 | 98 | %%% Start the VALSE algorithm 99 | cont = 1; 100 | while cont 101 | t = t + 1; 102 | % Update the support and weights 103 | [ K, s, w, C ] = maxZ( J, h, M, nu, rho, tau ); 104 | % Update the noise variance, the variance of prior and the Bernoulli probability 105 | if K>0 106 | nu = real( y2 - 2*real(h(s)'*w(s)) + w(s)'*J(s,s)*w(s) + trace(J(s,s)*C(s,s)) )/M; 107 | tau = real( w(s)'*w(s)+trace(C(s,s)) )/K; 108 | if K= T) 149 | cont = 0; 150 | mse(t+1:end) = mse(t); 151 | Kt(t+1:end) = Kt(t); 152 | end 153 | xro = xr; 154 | end 155 | out = struct('freqs',th,'amps',w(s),'x_estimate',xr,'noise_var',nu,'iterations',t,'mse',mse,'K',Kt); 156 | 157 | end 158 | 159 | function [a, theta, kappa, mu] = Heuristic1( eta, m, D ) 160 | %Heuristic1 Uses the mixture of von Mises approximation of frequency pdfs 161 | %and Heuristic #1 to output a mixture of max D von Mises pdfs 162 | 163 | M = length(m); 164 | tmp = abs(eta); 165 | A = besseli(1,tmp,1)./besseli(0,tmp,1); 166 | kmix = Ainv( A.^(1./m.^2) ); 167 | [~,l] = sort(kmix,'descend'); 168 | eta_q = 0; 169 | for k=1:M 170 | if m(l(k)) ~= 0 171 | if m(l(k)) > 1 172 | mu2 = ( angle(eta(l(k))) + 2*pi*(1:m(l(k))).' )/m(l(k)); 173 | eta_f = kmix(l(k)) * exp( 1i*mu2 ); 174 | else 175 | eta_f = eta(l(k)); 176 | end 177 | eta_q = bsxfun(@plus,eta_q,eta_f.'); 178 | eta_q = eta_q(:); 179 | kappa = abs(eta_q); 180 | 181 | % to speed up, use the following 4 lines to throw away components 182 | % that are very small compared to the dominant one 183 | kmax = max(kappa); 184 | ind = (kappa > (kmax - 30) ); % corresponds to keeping those components with amplitudes divided by the highest amplitude is larger than exp(-30) ~ 1e-13 185 | eta_q = eta_q(ind); 186 | kappa = kappa(ind); 187 | 188 | if length(eta_q) > D 189 | [~, in] = sort(kappa,'descend'); 190 | eta_q = eta_q(in(1:D)); 191 | end 192 | end 193 | end 194 | kappa = abs(eta_q); 195 | mu = angle(eta_q); 196 | kmax = max(kappa); 197 | I0reg = besseli(0,kappa,1) .* exp(kappa-kmax); 198 | Zreg = sum(I0reg); 199 | n = 0:1:m(end); 200 | [n1,k1] = meshgrid(n, kappa); 201 | a = sum( (diag(exp(kappa-kmax))* besseli(n1,k1,1) /Zreg ).*exp(1i*mu*n),1).'; 202 | theta = angle(sum( (diag(exp(kappa-kmax))* besseli(1,kappa,1) /Zreg ).*exp(1i*mu*1),1)); 203 | end 204 | 205 | function [a, theta, kappa] = Heuristic2( eta, m ) 206 | %Heuristic2 Uses the mixture of von Mises approximation of frequency pdfs 207 | %and Heuristic #2 to output one von Mises pdf 208 | 209 | N = length(m); 210 | ka = abs(eta); 211 | A = besseli(1,ka,1)./besseli(0,ka,1); 212 | kmix = Ainv( A.^(1./m.^2) ); 213 | k = N; 214 | eta_q = kmix(k) * exp( 1i * ( angle(eta(k)) + 2*pi*(1:m(k)).' )/m(k) ); 215 | for k = N-1:-1:1 216 | if m(k) ~= 0 217 | phi = angle(eta(k)); 218 | eta_q = eta_q + kmix(k) * exp( 1i*( phi + 2*pi*round( (m(k)*angle(eta_q) - phi)/2/pi ) )/m(k) ); 219 | end 220 | end 221 | [~,in] = max(abs(eta_q)); 222 | mu = angle(eta_q(in)); 223 | d1 = -imag( eta' * ( m .* exp(1i*m*mu) ) ); 224 | d2 = -real( eta' * ( m.^2 .* exp(1i*m*mu) ) ); 225 | if d2<0 % if the function is locally concave (usually the case) 226 | theta = mu - d1/d2; 227 | kappa = Ainv( exp(0.5/d2) ); 228 | else % if the function is not locally concave (not sure if ever the case) 229 | theta = mu; 230 | kappa = abs(eta_q(in)); 231 | end 232 | n = (0:1:m(end))'; 233 | a = exp(1i*n * theta).*( besseli(n,kappa,1)/besseli(0,kappa,1) ); 234 | end 235 | 236 | function [a, theta] = pntFreqEst( eta, m ) 237 | %pntFreqEst - point estimation of the frequency 238 | 239 | th = -pi:2*pi/(100*max(m)):pi; 240 | 241 | [~,i] = max(real( eta'*exp(1i*m*th) )); 242 | mu = th(i); 243 | d1 = -imag( eta' * ( m .* exp(1i*m*mu) ) ); 244 | d2 = -real( eta' * ( m.^2 .* exp(1i*m*mu) ) ); 245 | if d2<0 % if the function is locally concave (usually the case) 246 | theta = mu - d1/d2; 247 | else % if the function is not locally concave (not sure if ever the case) 248 | theta = mu; 249 | end 250 | a = exp(1i*(0:1:m(end))' * theta); 251 | end 252 | 253 | function [ K, s, w, C ] = maxZ( J, h, M, nu, rho, tau ) 254 | %maxZ maximizes the function Z of the binary vector s, see Appendix A of 255 | %the paper 256 | 257 | L = size(h,1); 258 | cnst = log(rho/(1-rho)/tau); 259 | 260 | K = 0; % number of components 261 | s = false(L,1); % Initialize s 262 | w = zeros(L,1); 263 | C = zeros(L); 264 | u = zeros(L,1); 265 | v = zeros(L,1); 266 | Delta = zeros(L,1); 267 | if L > 1 268 | cont = 1; 269 | while cont 270 | if K0 282 | if s(k)==0 % activate 283 | w(k) = u(k); 284 | ctemp = C(s,s)*J(s,k)/nu; 285 | w(s) = w(s) - ctemp*u(k); 286 | C(s,s) = C(s,s) + v(k)*(ctemp*ctemp'); 287 | C(s,k) = -v(k)*ctemp; 288 | C(k,s) = C(s,k)'; 289 | C(k,k) = v(k); 290 | s(k) = ~s(k); K = K+1; 291 | else % deactivate 292 | s(k) = ~s(k); K = K-1; 293 | w(s) = w(s) - C(s,k)*w(k)/C(k,k); 294 | C(s,s) = C(s,s) - C(s,k)*C(k,s)/C(k,k); 295 | end 296 | C = (C+C')/2; % ensure the diagonal is real 297 | else 298 | break 299 | end 300 | end 301 | elseif L == 1 302 | if s == 0 303 | v = nu ./ ( M + nu/tau ); 304 | u = v * h/nu; 305 | Delta = log(v) + u*conj(u)/v + cnst; 306 | if Delta>0 307 | w = u; C = v; s = 1; K = 1; 308 | end 309 | else 310 | Delta = -log(C) - w*conj(w)/C - cnst; 311 | if Delta>0 312 | w = 0; C = 0; s = 0; K = 0; 313 | end 314 | end 315 | end 316 | end 317 | 318 | function [ k ] = Ainv( R ) 319 | % Returns the approximate solution of the equation R = A(k), 320 | % where A(k) = I_1(k)/I_0(k) is the ration of modified Bessel functions of 321 | % the first kind of first and zero order 322 | % Uses the approximation from 323 | % Mardia & Jupp - Directional Statistics, Wiley 2000, pp. 85-86. 324 | % 325 | % When input R is a vector, the output is a vector containing the 326 | % corresponding entries 327 | 328 | k = R; % define A with same dimensions 329 | in1 = (R<.53); % indices of the entries < .53 330 | in3 = (R>=.85);% indices of the entries >= .85 331 | in2 = logical(1-in1-in3); % indices of the entries >=.53 and <.85 332 | R1 = R(in1); % entries < .53 333 | R2 = R(in2); % entries >=.53 and <.85 334 | R3 = R(in3); % entries >= .85 335 | 336 | % compute for the entries which are < .53 337 | if ~isempty(R1) 338 | t = R1.*R1; 339 | k(in1) = R1 .* ( 2 + t + 5/6*t.*t ); 340 | end 341 | % compute for the entries which are >=.53 and <.85 342 | if ~isempty(R2) 343 | k(in2) = -.4 + 1.39*R2 + 0.43./(1-R2); 344 | end 345 | % compute for the entries which are >= .85 346 | if ~isempty(R3) 347 | k(in3) = 1./( R3.*(R3-1).*(R3-3) ); 348 | end 349 | 350 | end -------------------------------------------------------------------------------- /newSVALSEv1p00.m: -------------------------------------------------------------------------------- 1 | function out = newSVALSEv1p00( y, m, ha, x, prior ) 2 | %Sequential VALSE algorithm for line spectral estimation 3 | % INPUTS: 4 | % y - measurement vector of size M 5 | % m - is a vector containing the indices (in ascending order) of the M 6 | % measurements; subset of {0,1,...,m(end)} 7 | % ha - indicator determining which approximation of the 8 | % frequency posterior pdfs will be used: 9 | % ha=1 will use Heuristic #1 10 | % ha=2 will use Heuristic #2 11 | % ha=3 will use point estimation of the frequencies (VALSE-pt) 12 | % x - the true signal - used for computing the MSE vs iterations 13 | % prior - prior information for sequential processing 14 | % 15 | % OUTPUTS: 16 | % out - structure 17 | % .freqs - vector of frequency estimates 18 | % .amps - vector of amplitude estimates 19 | % .x_estimate - reconstructed signal 20 | % .noise_var - estimate of the noise variance 21 | % .iterations - number of iterations until convergence 22 | % .mse - evolution of the mse of x_estimate with iterations 23 | % .K - evolution of the estimated number of components with iterations 24 | % 25 | % See full paper: 26 | % Y. Park, F. Meyer, and P. Gerstoft, 27 | % "Graph-based sequential beamforming," J.Acoust.Soc.Am. 153(1), (2023). 28 | 29 | % Version 1.0: (01/21/2023) 30 | % written by Y. Park 31 | 32 | % Yongsung Park, Florian Meyer, & Peter Gerstoft 33 | % MPL/SIO/UCSD 34 | % yongsungpark@ucsd.edu / flmeyer@ucsd.edu / gerstoft@ucsd.edu 35 | % noiselab.ucsd.edu 36 | 37 | M = size(y,1); 38 | N = m(M)+1; % size of full data 39 | y2 = y'*y; 40 | L = N; % assumed number of components 41 | A = zeros(L,L); 42 | J = zeros(L,L); 43 | h = zeros(L,1); 44 | w = zeros(L,1); 45 | C = zeros(L); 46 | T = 5000; % max number of iterations (5000 is very conservative, typically converges in tens of iterations) 47 | mse = zeros(T,1); 48 | Kt = zeros(T,1); 49 | t = 1; 50 | 51 | % extract prior information 52 | etaPrior = nan(L,1); 53 | if(isempty(prior)) 54 | numPriorComponents = 0; 55 | else 56 | rhoPrior = prior.rho; 57 | rho = prior.rho; 58 | kappaPrior = prior.kappas; 59 | muPrior = prior.mus; 60 | numPriorComponents = numel(muPrior); 61 | etaPrior(1:numPriorComponents) = kappaPrior .* exp(1i * muPrior); 62 | K = numel(find(prior.rho==max(prior.rho))); 63 | end 64 | 65 | % Initialization of the posterior pdfs of the frequencies 66 | res = y; 67 | for l=1:L 68 | % else statement addded to incorporate prior information 69 | if (l > numPriorComponents) 70 | % noncoherent estimation of the pdf 71 | yI = zeros(N,1); 72 | yI(m+1) = res; 73 | R = yI*yI'; 74 | sR = zeros(N-1,1); 75 | for i=2:N 76 | for k=1:i-1 77 | sR(i-k) = sR(i-k) + R(i,k); 78 | end 79 | end 80 | if l==1 % use the sample autocorrelation to initialize the model parameters 81 | nu = trace(y*y')/size(y,1)/size(y,2)/100; 82 | K = floor(L/2); 83 | rho = K/L * ones(L,1); 84 | tau = (y2/M-nu)/(K); 85 | end 86 | etaI = 2*sR/(M+nu/tau)/nu; 87 | ind = find(abs(etaI)>0); 88 | if ha~=3 89 | [~,mu,kappa] = Heuristic2(etaI(ind), ind); 90 | A(m+1,l) = exp(1i*m * mu) .* ( besseli(m,kappa,1)/besseli(0,kappa,1) ); 91 | else 92 | [~,mu] = pntFreqEst(etaI(ind), ind); 93 | A(m+1,l) = exp(1i*m * mu); 94 | end 95 | else 96 | % incorporate prior information 97 | if l==1 % use the sample autocorrelation to initialize the model parameters 98 | nu = trace(y*y')/size(y,1)/size(y,2)/100; 99 | tau = (y2/M-nu)/(K); 100 | end 101 | A(m+1,l) = exp(1i*m * muPrior(l)); 102 | end 103 | 104 | % compute weight estimates; rank one update 105 | w_temp = w(1:l-1); C_temp = C(1:l-1,1:l-1); 106 | J(1:l-1,l) = A(m+1,1:l-1)'*A(m+1,l); J(l,1:l-1) = J(1:l-1,l)'; J(l,l) = M; 107 | h(l) = A(m+1,l)'*y; 108 | v = nu / ( M + nu/tau - real(J(1:l-1,l)'*C_temp*J(1:l-1,l))/nu ); 109 | u = v .* (h(l) - J(1:l-1,l)'*w_temp)/nu; 110 | w(l) = u; 111 | ctemp = C_temp*J(1:l-1,l)/nu; 112 | w(1:l-1) = w_temp - ctemp*u; 113 | C(1:l-1,1:l-1) = C_temp + v*(ctemp*ctemp'); 114 | C(1:l-1,l) = -v*ctemp; C(l,1:l-1) = C(1:l-1,l)'; C(l,l) = v; 115 | 116 | % the residual signal 117 | res = y - A(m+1,1:l)*w(1:l); 118 | 119 | if l==K % save mse and K at initialization 120 | xro = A(:,1:l)*w(1:l); 121 | mse(t) = norm(x-xro)^2/norm(x)^2; 122 | Kt(t) = K; 123 | end 124 | end 125 | 126 | 127 | allTh = nan(L,1); 128 | allKappas = nan(L,1); 129 | %%% Start the VALSE algorithm 130 | cont = 1; 131 | while cont 132 | t = t + 1; 133 | v = t; 134 | 135 | if(numPriorComponents) 136 | rho = rhoPrior; 137 | end 138 | 139 | % Update the support and weights 140 | [ K, s, w, C ] = maxZ( J, h, M, nu, rho, tau ); 141 | % Update the noise variance, the variance of prior and the Bernoulli probability 142 | if K>0 143 | nu = real( y2 - 2*real(h(s)'*w(s)) + w(s)'*J(s,s)*w(s) + trace(J(s,s)*C(s,s)) )/M; 144 | tau = real( w(s)'*w(s)+trace(C(s,s)) )/K; 145 | if Knumel(th), break; end 196 | end 197 | K = numel(th); 198 | if isempty(removeInd)==0 199 | C(s,s) = nu*inv(J(s,s)+nu/tau*eye(size(J(s,s)))); 200 | w(s) = (1/nu) * C(s,s) * h(s); 201 | end 202 | 203 | allTh(inz) = th; 204 | allKappas(inz) = kappa; 205 | 206 | % stopping criterion: 207 | % the relative change of the reconstructed signalis below threshold or 208 | % max number of iterations is reached 209 | xr = A(:,s)*w(s); 210 | mse(t) = norm(xr-x)^2/norm(x)^2; 211 | Kt(t) = K; 212 | if (norm(xr-xro)/norm(xro)<1e-6) || (norm(xro)==0&&norm(xr-xro)==0) || (t >= T) 213 | cont = 0; 214 | mse(t+1:end) = mse(t); 215 | Kt(t+1:end) = Kt(t); 216 | end 217 | xro = xr; 218 | end 219 | 220 | % output also previously active VM components (ordered such that active components first, followed by previously active components, never active at the end) 221 | s = double(s); 222 | s(isnan(allTh)) = -1; 223 | [s,indexes] = sort(s,'descend'); 224 | 225 | w = w(indexes); 226 | allTh = allTh(indexes); 227 | allKappas = allKappas(indexes); 228 | 229 | s(s<0) = 0; 230 | th = allTh(1:sum(s)); 231 | w = w(1:sum(s)); 232 | allTh = allTh(~isnan(allTh)); 233 | allKappas = allKappas(~isnan(allKappas)); 234 | out = struct('freqs',th,'amps',w,'x_estimate',xr,'nu',nu,'iterations',t,'mse',mse,'K',Kt,'kappas',allKappas,'mus',allTh,'rho',rho,'tau',tau); 235 | 236 | end 237 | 238 | function [a, theta, kappa] = Heuristic1( eta, m, D, etaPrior ) 239 | %Heuristic1 Uses the mixture of von Mises approximation of frequency pdfs 240 | %and Heuristic #1 to output a mixture of max D von Mises pdfs 241 | 242 | M = length(m); 243 | tmp = abs(eta); 244 | A = besseli(1,tmp,1)./besseli(0,tmp,1); 245 | kmix = Ainv( A.^(1./m.^2) ); 246 | %[~,l] = sort(kmix,'descend'); 247 | eta_q = 0; 248 | 249 | l = m + 1; 250 | 251 | for k=1:M 252 | if m(l(k)) ~= 0 253 | if m(l(k)) > 1 254 | mu2 = ( angle(eta(l(k))) + 2*pi*(1:m(l(k))).' )/m(l(k)); 255 | eta_f = kmix(l(k)) * exp( 1i*mu2 ); 256 | else 257 | eta_f = eta(l(k)); 258 | 259 | %introduce prior information for frequencies 260 | if(~isnan(etaPrior)) 261 | eta_f = eta_f + etaPrior; 262 | end 263 | 264 | end 265 | eta_q = bsxfun(@plus,eta_q,eta_f.'); 266 | eta_q = eta_q(:); 267 | 268 | kappa = abs(eta_q); 269 | 270 | % to speed up, use the following 4 lines to throw away components 271 | % that are very small compared to the dominant one 272 | kmax = max(kappa); 273 | ind = (kappa > (kmax - 30) ); % corresponds to keeping those components with amplitudes divided by the highest amplitude is larger than exp(-30) ~ 1e-13 274 | eta_q = eta_q(ind); 275 | kappa = kappa(ind); 276 | 277 | if length(eta_q) > D 278 | [~, in] = sort(kappa,'descend'); 279 | eta_q = eta_q(in(1:D)); 280 | end 281 | end 282 | end 283 | kappa = abs(eta_q); 284 | mu = angle(eta_q); 285 | kmax = max(kappa); 286 | I0reg = besseli(0,kappa,1) .* exp(kappa-kmax); 287 | Zreg = sum(I0reg); 288 | n = 0:1:m(end); 289 | [n1,k1] = meshgrid(n, kappa); 290 | a = sum( (diag(exp(kappa-kmax))* besseli(n1,k1,1) /Zreg ).*exp(1i*mu*n),1).'; 291 | theta = angle(sum( (diag(exp(kappa-kmax))* besseli(1,kappa,1) /Zreg ).*exp(1i*mu*1),1)); 292 | 293 | %moment matching to get kappa of single VM 294 | if(numel(kappa) > 1) 295 | variances = 1./kappa; 296 | weights = I0reg/Zreg; 297 | 298 | muNew = sum(weights.*mu,1); 299 | varianceNew = sum(weights.*(variances+mu.^2)) - muNew^2; 300 | kappa = 1/varianceNew; 301 | end 302 | end 303 | 304 | function [a, theta, kappa] = Heuristic2( eta, m ) 305 | %Heuristic2 Uses the mixture of von Mises approximation of frequency pdfs 306 | %and Heuristic #2 to output one von Mises pdf 307 | 308 | N = length(m); 309 | ka = abs(eta); 310 | A = besseli(1,ka,1)./besseli(0,ka,1); 311 | kmix = Ainv( A.^(1./m.^2) ); 312 | k = N; 313 | eta_q = kmix(k) * exp( 1i * ( angle(eta(k)) + 2*pi*(1:m(k)).' )/m(k) ); 314 | for k = N-1:-1:1 315 | if m(k) ~= 0 316 | phi = angle(eta(k)); 317 | eta_q = eta_q + kmix(k) * exp( 1i*( phi + 2*pi*round( (m(k)*angle(eta_q) - phi)/2/pi ) )/m(k) ); 318 | end 319 | end 320 | [~,in] = max(abs(eta_q)); 321 | mu = angle(eta_q(in)); 322 | d1 = -imag( eta' * ( m .* exp(1i*m*mu) ) ); 323 | d2 = -real( eta' * ( m.^2 .* exp(1i*m*mu) ) ); 324 | if d2<0 % if the function is locally concave (usually the case) 325 | theta = mu - d1/d2; 326 | kappa = Ainv( exp(0.5/d2) ); 327 | else % if the function is not locally concave (not sure if ever the case) 328 | theta = mu; 329 | kappa = abs(eta_q(in)); 330 | end 331 | n = (0:1:m(end))'; 332 | a = exp(1i*n * theta).*( besseli(n,kappa,1)/besseli(0,kappa,1) ); 333 | end 334 | 335 | function [a, theta] = pntFreqEst( eta, m ) 336 | %pntFreqEst - point estimation of the frequency 337 | 338 | th = -pi:2*pi/(100*max(m)):pi; 339 | 340 | [~,i] = max(real( eta'*exp(1i*m*th) )); 341 | mu = th(i); 342 | d1 = -imag( eta' * ( m .* exp(1i*m*mu) ) ); 343 | d2 = -real( eta' * ( m.^2 .* exp(1i*m*mu) ) ); 344 | if d2<0 % if the function is locally concave (usually the case) 345 | theta = mu - d1/d2; 346 | else % if the function is not locally concave (not sure if ever the case) 347 | theta = mu; 348 | end 349 | a = exp(1i*(0:1:m(end))' * theta); 350 | end 351 | 352 | function [ K, s, w, C ] = maxZ( J, h, M, nu, rho, tau ) 353 | %maxZ maximizes the function Z of the binary vector s, see Appendix A of 354 | %the paper 355 | 356 | L = size(h,1); 357 | 358 | K = 0; % number of components 359 | s = false(L,1); % Initialize s 360 | w = zeros(L,1); 361 | C = zeros(L); 362 | u = zeros(L,1); 363 | v = zeros(L,1); 364 | Delta = zeros(L,1); 365 | if L > 1 366 | cont = 1; 367 | while cont 368 | if K0 382 | if s(k)==0 % activate 383 | w(k) = u(k); 384 | ctemp = C(s,s)*J(s,k)/nu; 385 | w(s) = w(s) - ctemp*u(k); 386 | C(s,s) = C(s,s) + v(k)*(ctemp*ctemp'); 387 | C(s,k) = -v(k)*ctemp; 388 | C(k,s) = C(s,k)'; 389 | C(k,k) = v(k); 390 | s(k) = ~s(k); K = K+1; 391 | else % deactivate 392 | s(k) = ~s(k); K = K-1; 393 | w(s) = w(s) - C(s,k)*w(k)/C(k,k); 394 | C(s,s) = C(s,s) - C(s,k)*C(k,s)/C(k,k); 395 | end 396 | C = (C+C')/2; % ensure the diagonal is real 397 | else 398 | break 399 | end 400 | end 401 | elseif L == 1 402 | cnst = log(rho/(1-rho)/tau); 403 | if s == 0 404 | v = nu ./ ( M + nu/tau ); 405 | u = v * h/nu; 406 | Delta = log(v) + u*conj(u)/v + cnst; 407 | if Delta>0 408 | w = u; C = v; s = 1; K = 1; 409 | end 410 | else 411 | Delta = -log(C) - w*conj(w)/C - cnst; 412 | if Delta>0 413 | w = 0; C = 0; s = 0; K = 0; 414 | end 415 | end 416 | end 417 | end 418 | 419 | function [ k ] = Ainv( R ) 420 | % Returns the approximate solution of the equation R = A(k), 421 | % where A(k) = I_1(k)/I_0(k) is the ration of modified Bessel functions of 422 | % the first kind of first and zero order 423 | % Uses the approximation from 424 | % Mardia & Jupp - Directional Statistics, Wiley 2000, pp. 85-86. 425 | % 426 | % When input R is a vector, the output is a vector containing the 427 | % corresponding entries 428 | 429 | k = R; % define A with same dimensions 430 | in1 = (R<.53); % indices of the entries < .53 431 | in3 = (R>=.85);% indices of the entries >= .85 432 | in2 = logical(1-in1-in3); % indices of the entries >=.53 and <.85 433 | R1 = R(in1); % entries < .53 434 | R2 = R(in2); % entries >=.53 and <.85 435 | R3 = R(in3); % entries >= .85 436 | 437 | % compute for the entries which are < .53 438 | if ~isempty(R1) 439 | t = R1.*R1; 440 | k(in1) = R1 .* ( 2 + t + 5/6*t.*t ); 441 | end 442 | % compute for the entries which are >=.53 and <.85 443 | if ~isempty(R2) 444 | k(in2) = -.4 + 1.39*R2 + 0.43./(1-R2); 445 | end 446 | % compute for the entries which are >= .85 447 | if ~isempty(R3) 448 | k(in3) = 1./( R3.*(R3-1).*(R3-3) ); 449 | end 450 | 451 | end --------------------------------------------------------------------------------