diff m-toolbox/classes/@ao/spsdSubtraction.m @ 0:f0afece42f48

Import.
author Daniele Nicolodi <nicolodi@science.unitn.it>
date Wed, 23 Nov 2011 19:22:13 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/m-toolbox/classes/@ao/spsdSubtraction.m	Wed Nov 23 19:22:13 2011 +0100
@@ -0,0 +1,504 @@
+% SPSDSUBTRACTION makes a sPSD-weighted least-square iterative fit
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% DESCRIPTION: SPSDSUBTRACTION makes a sPSD-weighted least-square iterative fit
+%
+% CALL: [MPest, plOut, aoResiduum, aoP, aoPini] = spsdSubtraction(ao_Y, [ao_U1, ao_U2, ao_U3 ...]);
+%       [MPest, plOut, aoResiduum, aoP, aoPini] = spsdSubtraction(ao_Y, [ao_U1, ao_U2, ao_U3 ...], pl);
+%
+%  The function finds the optimal M that minimizes the sum of the weighted sPSD of
+%  (ao_Y - M * [ao_U1 ao_U2 ao_U3 ...] )
+%  if ao_Y is a vector of aos, the use the matrix/spsdSubtraction is
+%  advised
+%
+%  OUTPUTS: - MPest: output PEST object with parameter estimates
+%           - aoResiduum: residuum times series
+%           - plOut: plist containing data like the parameter estimates
+%           - aoP: last weight used in the optimization (fater last
+%             Maximization/Expectation step)
+%           - aoPini: initial weight used in the optimization (before first
+%             Maximization/Expectation step)
+%
+% <a href="matlab:utils.helper.displayMethodInfo('ao', 'spsdSubtraction')">Parameters Description</a>
+%
+% VERSION : $Id: spsdSubtraction.m,v 1.6 2011/08/03 19:21:10 adrien Exp $
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+function varargout = spsdSubtraction(varargin)
+  
+  % use the caller is method flag
+  callerIsMethod = utils.helper.callerIsMethod;
+  
+  % Check if this is a call for parameters
+  if utils.helper.isinfocall(varargin{:})
+    varargout{1} = getInfo(varargin{3});
+    return
+  end
+  
+  % Collect input variable names
+  in_names = cell(size(varargin));
+  for ii = 1:nargin,in_names{ii} = inputname(ii);end
+  
+  if ~nargin>1
+    error('optSubtraction requires at least the two input aos as first and second entries')
+  end
+  
+  %% retrieving the two input aos
+  [aos_in, ao_invars] = utils.helper.collect_objects(varargin(:), 'ao', in_names);
+  
+  aosY = varargin{1};
+  aosU = varargin{2};
+  if (~isa(aosY, 'ao')) || (~isa(aosU, 'ao'))
+    error('first two inputs should be two ao-arrays involved in the subtraction')
+  end
+  
+  % Collect plist
+  pl = utils.helper.collect_objects(varargin(:), 'plist');
+  
+  % Get default parameters
+  pl = combine(pl, getDefaultPlist);
+  
+  %% checking data sizes
+  NY = numel(aosY);
+  if NY==0
+    error('Nothing to subtract to!')
+  end
+  NU = size(aosU,2);
+  if NU==0
+    error('Nothing to subtract!')
+  end
+  if ~(size(aosY,2)==1)
+    error('The input ao Y array should be a column vector')
+  end
+  if ~(size(aosU,1)==NY)
+    error('The fields ''subtracted'' should be an array of aos with the height of numel(initial)')
+  end
+  
+  %% collecting history
+  if callerIsMethod
+    % we don't need the history of the input
+  else
+    inhist  = [aosY(:).hist aosU(:).hist];
+  end
+  
+  %% retrieving general quantities
+  ndata = numel(aosY(1).y);
+  Ts = 1/aosY(1).fs;
+  nFreqs = floor(ndata/2);
+  freqs = 1/(2*Ts) * linspace(0,1,nFreqs);
+  
+  %% produce window
+  Win = find(pl, 'Win');
+  if isa(Win, 'plist')
+    Win = ao( combine(plist( 'length', ndata), Win) );
+    W = Win.y;
+  elseif isa(Win, 'ao')
+    if ~isa(Win.data, 'tsdata')
+      error('An ao window should be a time series')
+    end
+    W = Win.y;
+    if ~length(W)==ndata
+      error('signals and windows don''t have the same length')
+    end
+  else
+    error('input option Win is not acceptable (not a plist nor an ao)!')
+  end
+  
+  %% get initial M coefficient matrix
+  M = pl.find('coefs');
+  if isempty(M)
+    M = zeros(1,NU);
+  end
+  
+  %% get criterion thinness
+  linCoef = pl.find('lincoef');
+  logCoef = pl.find('logcoef');
+  
+  %% getting the input data Y and taking FFT
+  Y = zeros(NY, nFreqs);
+  YLocNorm = zeros(NY,1);
+  
+  for ii=1:NY
+    if isempty(aosY(ii).data)
+      error('One ao for Y is empty!')
+    end
+    if ~(length(aosY(ii).y)==ndata)
+      error('various Y vectors do not have the same length')
+    end
+    yLoc = fft(aosY(ii).y .* W, ndata);
+    YLocNorm(ii) = norm(aosY(ii).y .* W)/sqrt(ndata);
+    Y(ii,:) = yLoc(1:nFreqs)/YLocNorm(ii);
+  end
+  
+  %% getting the data U norm
+  ULocNorm = zeros(NY,NU);
+  for iU=1:NU
+    for iY=1:NY
+      if ~isempty(aosU(iY,iU).data)
+        ULocNorm(iY,iU,:) = norm(aosU(iY,iU).y  .* W)/sqrt(ndata);
+      end
+    end
+  end
+  ULocNorm = max(ULocNorm,[],1);
+  
+  %% getting the input data U and taking FFT
+  U = zeros(NY,NU, nFreqs);
+  for iY=1:NY
+    for iU=1:NU
+      if ~isempty(aosU(iY,iU).data)
+        if ~(length(aosU(iY,iU).y)==ndata)
+          error('various U vectors do not have the same length as Y')
+        end
+        uLoc = fft(aosU(iY,iU).y .* W, ndata);
+        U(iY,iU,:) = uLoc(1:nFreqs)/ (YLocNorm(iY) * ULocNorm(iU));
+      end
+    end
+  end
+  
+  %% getting the weight powAvgWeight
+  weightingMethod =pl.find('weightingMethod');
+  switch lower(weightingMethod)
+    case 'pzmodel'
+      weightModel =pl.find('pzmodelWeight');
+      if numel(weightModel)~=NY
+        error('there should be as many pzmodels as weighted entries')
+      end
+      for ii=1:NY
+        weight = weightModel(ii).resp(freqs);
+        weight = abs(weight).^2;
+        pow = [0 ; weight.y(2:nFreqs)];
+        [freqsAvg, powAvgs, nFreqsAvg, nDofs, binningMatrix] = ltpda_spsd(freqs, pow, linCoef, logCoef);
+        powAvgWeight(ii,:) = powAvgs; %#ok<AGROW>
+      end
+    case 'ao'
+      weight = pl.find('aoWeight');
+      if numel(weight)~=NY
+        error('there should be as many AOs as weighted entries')
+      end
+      for ii=1:NY
+        if ~isa(weight(ii).data, 'fsdata')
+          error('if weight is an ao, it should be a FSdata')
+        elseif length(weight(ii).y)~=nFreqs
+          error(['length of FS weight is not length of the FFT vector : ' num2str(length(weight(ii).y)) ' instead of ' num2str(nFreqs)])
+        else
+          pow = weight(ii).y;
+          [freqsAvg, powAvgs, nFreqsAvg, nDofs, binningMatrix] = ltpda_spsd(freqs, pow, linCoef, logCoef);
+          powAvgWeight(ii,:) = powAvgs; %#ok<AGROW>
+          %% add unit check here!!
+        end
+      end
+    case 'residual'
+      [freqsAvg, powAvgWeight, nFreqsAvg, nDofs, binningMatrix] = computeWeight(Y, M, U, freqs, linCoef, logCoef);
+    otherwise
+      error('weighting method requested does not exist!')
+  end
+  powAvgInv = (powAvgWeight.*(nFreqsAvg.')./(nDofs.')).^-1;
+  
+  %% get ME iterations termination conditions
+  iterMax = pl.find('iterMax');
+  normCoefs = pl.find('normCoefs');
+  normCriterion = pl.find('normCriterion');
+  
+  %% Maximization Expectation iterations loop
+  for i_iter = 1:iterMax
+    utils.helper.msg(utils.const.msg.PROC3, ['starting iteration ', num2str(i_iter)]);
+    
+    %% initializing history
+    if i_iter==1 % storing intial weight
+      Pini = powAvgWeight;
+      MHist(1,:) = reshape(M, [1, numel(M)] );
+    end
+    fValIni = optimalCriterion(Y, M, U, powAvgInv, linCoef, logCoef);
+    
+    %% solving LSQ problem
+    [M, hessian] = solveProblem(M, Y, U, powAvgInv, nFreqsAvg, binningMatrix);
+    fval = optimalCriterion(Y, M, U, powAvgInv, linCoef, logCoef);
+    
+    %% store history
+    fValHist(i_iter) = fval/fValIni; %#ok<AGROW>
+    MHist(i_iter+1,:) = reshape(M, [1, numel(M)] ); %#ok<AGROW>
+    
+    %% updating weight, recomputing residuum power
+    [freqsAvg, powAvgWeight, nFreqsAvg, nDofs] = computeWeight(Y, M, U, freqs, linCoef, logCoef);
+    powAvgInv = (powAvgWeight.*(nFreqsAvg.')./(nDofs.')).^-1;
+    
+    %% deciding whether to pursue or not ME iterations
+    if strcmpi( weightingMethod, 'pzmodel')
+      display('One iteration for Pzmodel weighting only')
+      break
+    elseif strcmpi( weightingMethod, 'ao')
+      display('One iteration for ao weighting only')
+      break
+    elseif norm(fValHist(i_iter)-1) < normCriterion && norm(MHist(i_iter+1,:)-MHist(i_iter,:))<normCoefs
+      display(['Iterations stopped at iteration ' num2str(i_iter) ' because not enough progress was made (see parameter "normCriterion" and "normCoefs")'])
+      break
+    elseif i_iter == iterMax
+      display(['Iterations stopped at maximum number of iterations ' num2str(i_iter) ' (see parameter "iterMax")'])
+      break
+    end
+  end
+  
+  %% creating output pest
+  MVals = M * diag( ULocNorm.^-1 );
+  MStd = diag(diag(ULocNorm) * hessian * diag(ULocNorm)).^-0.5;
+  MCov = diag(ULocNorm)^-1 * hessian^-1 * diag(ULocNorm)^-1;
+  
+  % prepare model, units, names
+  model = [];
+  for jj = 1:NU
+    names{jj} = ['U' num2str(jj)]; %#ok<AGROW>
+    units{jj} = aosY(1).yunits / aosU(1,jj).yunits; %#ok<AGROW>
+    xunits{jj} = aosU(1,jj).yunits; %#ok<AGROW>
+    MNames{jj} = ['M' num2str(jj)]; %#ok<AGROW>
+    if jj == 1
+      model = ['M' num2str(jj) '*U' num2str(jj)];
+    else
+      model = [model ' + M' num2str(jj) '*U' num2str(jj)]; %#ok<AGROW>
+    end
+  end
+  
+  model = smodel(plist('expression', model, ...
+    'params', MNames, ...
+    'values', MVals.', ...
+    'xvar', names, ...
+    'xunits', xunits, ...
+    'yunits', aosY(1).yunits ...
+    ));
+  
+  % collect inputs names
+  argsname = aosY(1).name;
+  for jj = 1:numel(NU)
+    argsname = [argsname ',' aosU(jj).name];
+  end
+  
+  % Build the output pest object
+  MPest = pest;
+  MPest.setY( MVals.' );
+  MPest.setDy(MStd);
+  MPest.setCov(MCov);
+  MPest.setChi2(0);
+  MPest.setNames(names{:});
+  MPest.setYunits(units{:});
+  MPest.setModels(model);
+  MPest.name = sprintf('optSubtraction(%s)', argsname);
+  
+  % Set procinfo object
+  MPest.procinfo = plist('MPsdE', 0);
+  % Propagate 'plotinfo'
+  plotinfo = [aosY(:).plotinfo aosU(:).plotinfo];
+  if ~isempty(plotinfo)
+    MPest.plotinfo = combine(plotinfo);
+  end
+  
+  %% creating output plist
+  plOut = plist;
+  
+  p = param({ 'criterion' , 'last value of the criterion in the last optimization'}, fval );
+  plOut.append(p);
+  p = param({ 'M' , 'Best fitting value'}, MVals );
+  plOut.append(p);
+  p = param({ 'Mhist' , 'History of the best fit, through iteration'}, MHist * diag( ULocNorm.^-1 ) );
+  plOut.append(p);
+  p = param({ 'fValHist' , 'History of the criterion value, through iteration'}, fValHist );
+  plOut.append(p);
+  p = param({ 'hessian' , 'fitting hessian'},  diag(ULocNorm) * hessian * diag(ULocNorm) );
+  plOut.append(p);
+  %add history and use Mdata/Pest instead
+  
+  %% creating aos for the weights used
+  if nargout>2
+    aoP = ao.initObjectWithSize(NY, 1);
+    aoPini = ao.initObjectWithSize(NY, 1);
+    for ii=1:NY
+      aoP(ii).setData(fsdata( freqsAvg, YLocNorm(ii)^2 * powAvgWeight(ii,:) ));
+      aoP(ii).setName('final weight');
+      aoP(ii).setXunits('Hz');
+      aoP(ii).setYunits(aosY(ii).yunits^2 * unit('Hz^-1'));
+      aoP(ii).setDescription(['final weight in the channel "' aosY(ii).name '"']);
+      aoP(ii).setT0(aosY(ii).t0);
+      aoPini(ii).setData(fsdata( freqsAvg, YLocNorm(ii)^2 * Pini(ii,:) ));
+      aoPini(ii).setName('initial weight');
+      aoPini(ii).setXunits('Hz');
+      aoPini(ii).setYunits(aosY(ii).yunits^2 * unit('Hz^-1'));
+      aoPini(ii).setDescription(['initial weight in the channel "' aosY(ii).name '"']);
+      aoPini(ii).setT0(aosY(ii).t0);
+    end
+  end
+  
+  %% creating residuum time-series
+  if nargout>2
+    aoResiduum = ao.initObjectWithSize(NY,1);
+    for ii=1:NY
+      aoResiduumValue = aosY(ii).y;
+      for jj = 1:NU
+        aoResiduumValue = aoResiduumValue - MVals(jj)*aosU(ii,jj).y;
+      end
+      aoResiduum(ii).setData(tsdata( aoResiduumValue, aosY(ii).fs ));
+      aoResiduum(ii).setName(['residual in the channel "' aosY(ii).name '"' ]);
+      aoResiduum(ii).setXunits('s');
+      aoResiduum(ii).setYunits(aosY(ii).yunits);
+      aoResiduum(ii).setDescription(['residual corresponding to "' aosY(ii).description '"' ]);
+      aoResiduum(ii).setT0(aosY(ii).t0);
+    end
+  end
+  
+  %% adding history
+  if callerIsMethod
+    % we don't need to set the history
+  else
+    MPest.addHistory(getInfo('None'), pl, ao_invars, inhist);
+    if nargout>2
+      for ii=1:NY
+        aoP(ii).addHistory(getInfo('None'), pl, ao_invars, inhist);
+        aoPini(ii).addHistory(getInfo('None'), pl, ao_invars, inhist);
+        aoResiduum(ii).addHistory(getInfo('None'), pl, ao_invars, inhist);
+      end
+    end
+  end
+  
+  %% return coefficients and hessian and Jfinal and powAvgWeight
+  if nargout>2
+    varargout = {MPest, plOut, aoResiduum, aoP, aoPini};
+  else
+    varargout = {MPest, plOut};
+  end
+end
+
+%% weight for optimal criterion
+function [freqsAvg, powAvgWeight, nFreqsAvg, nDofs, binningMatrix] = computeWeight(Y, M, U, freqs, linCoef, logCoef)
+  errDft =  subtraction( Y, M, U);
+  errPow = real(errDft).^2 + imag(errDft).^2;
+  for ii=1:size(errDft,1)
+    [freqsAvg, powAvgs, nFreqsAvg, nDofs, binningMatrix] = ltpda_spsd(freqs, errPow, linCoef, logCoef);
+    powAvgWeight(ii,:) = powAvgs;  %#ok<AGROW>
+  end
+end
+
+%% optimal criterion
+function j = optimalCriterion(Y, M, U, powAvgInv, linCoef, logCoef)
+  errDft = subtraction(Y, M, U);
+  errPow = real(errDft).^2 + imag(errDft).^2;
+  j = 0;
+  for ii=1:size(errDft,1)
+    [freqsAvg, powAvgs, nFreqsAvg, nDofs] = ltpda_spsd([], errPow, linCoef, logCoef); %#ok<ASGLU>
+    powSum = powAvgs .* nDofs; % binning frequencies as in sPSD
+    j  = j + sum( powSum .* powAvgInv(:,ii) );
+    %     alpha = 4;
+    %     logProbaDensityFactor =  - nFreqsAvg * log(2) - gammaln(nFreqsAvg);
+    %     normlzChi2Sum = ((alpha*2)*powSum) .* powAvgInv(:,ii); % divide the sum by the expected average of each terms, so the chi2 is normalized
+    %     logProbaDensities = logProbaDensityFactor + (nFreqsAvg-1).*log(normlzChi2Sum) - normlzChi2Sum/2 ; % here computing log of probability
+    %     j = j - sum(logProbaDensities); % better than taking product of probabilities
+  end
+end
+
+%% time-series subtraction function
+function Y = subtraction( Y, M, U)
+  ndata = size(Y,2);
+  for ii=1:size(Y,1)
+    for j=1:numel(M)
+      Y(ii,:) = Y(ii,:) - reshape( M(j)*U(ii,j,:) , [1,ndata] );
+    end
+  end
+end
+
+%% Direct solver
+function [M, hessian] = solveProblem(M, Y, U, powAvgInv, nFreqsAvg, binningMatrix)
+  errDft = subtraction(Y, M, U);
+  NU = size(U,2);
+  NFreqs = size(binningMatrix,2);
+  ATB = zeros(NU,1);
+  ATA = zeros(NU,NU);
+  % matrix for frequency binning & Weighting & Summing :
+  matBSW = powAvgInv * binningMatrix;
+  for iiParam = 1:NU
+    Uii = reshape(U(1,iiParam,:), [1 NFreqs]);
+    ATB(iiParam) = 2 * ( matBSW * real( Uii .* conj(errDft) ).' );
+    for jjParam = 1:NU
+      Ujj = reshape(U(1,jjParam,:), [1 NFreqs]);
+      ATA(iiParam,jjParam) = 2 * ( matBSW * real( Uii .* conj(Ujj) ).' );
+    end
+  end
+  try
+    MUpdate = ATA^-1 * ATB;
+    M = M + MUpdate.';
+  catch 
+    warning('Numerical accuracy limited the number of iterations')
+  end
+  hessian = ATA;
+end
+
+
+%--------------------------------------------------------------------------
+% Get Info Object
+%--------------------------------------------------------------------------
+function ii = getInfo(varargin)
+  if nargin == 1 && strcmpi(varargin{1}, 'None')
+    sets = {};
+    pl   = [];
+  else
+    sets = {'Default'};
+    pl   = getDefaultPlist;
+  end
+  % Build info object
+  ii = minfo(mfilename, 'ao', 'ltpda', utils.const.categories.op, '$Id: spsdSubtraction.m,v 1.6 2011/08/03 19:21:10 adrien Exp $', sets, pl);
+end
+
+%--------------------------------------------------------------------------
+% Get Default Plist
+%--------------------------------------------------------------------------
+function plout = getDefaultPlist()
+  persistent pl;
+  if exist('pl', 'var')==0 || isempty(pl)
+    pl = buildplist();
+  end
+  plout = pl;
+end
+
+function pl = buildplist()
+  pl = plist;
+  
+  % initial coefficients for subtraction initialization
+  p = param({ 'coefs' , 'initial subtracted coefficients, must be a nY*nU double array. If not provided zeros are assumed'},  [] );
+  pl.append(p);
+  
+  % weighting scheme
+  p = param({ 'weightingMethod' , 'choose to define a frequency weighting scheme'},  {1, {'residual', 'ao', 'pzmodel'}, paramValue.SINGLE} );
+  pl.append(p);
+  
+  p = param({ 'aoWeight' , 'ao to define a frequency weighting scheme (if chosen in ''weightingMethod'')'},  ao.initObjectWithSize(0,0) );
+  pl.append(p);
+  
+  p = param({ 'pzmodelWeight' , 'pzmodel to define a frequency weighting scheme (if chosen in ''weightingMethod'')'},  pzmodel.initObjectWithSize(0,0) );
+  pl.append(p);
+  
+  p = param({ 'lincoef' , 'linear coefficient for scaling frequencies in chi2'},  5 );
+  pl.append(p);
+  
+  p = param({ 'logcoef' , 'logarithmic coefficient for scaling frequencies in chi2'},  0.3 );
+  pl.append(p);
+  
+  % iterations convergence stop criterion
+  p = param({ 'iterMax' , 'max number of Mex/Exp iterations'},  20 );
+  pl.append(p);
+  
+  p = param({ 'normCoefs' , 'tolerance on inf norm of coefficient update (used depending on ''CVCriterion'')'},  1e-15 );
+  pl.append(p);
+  
+  p = param({ 'normCriterion' , 'tolerance on norm of criterion variation (used depending on ''CVCriterion'')'},  1e-15 );
+  pl.append(p);
+  
+  % windowing options
+  p = param({ 'win' , 'window to operate FFT, may be a plist/ao'},  plist('win', 'levelledHanning', 'PSLL', 200, 'levelOrder', 4 ) );
+  pl.append(p);
+  
+  % display
+  p = param({ 'display' , 'choose how much to display of the optimizer output'},  {1, {'off', 'iter', 'final'}, paramValue.SINGLE} );
+  pl.append(p);
+  
+  % optimizer options
+  p = param({ 'maxcall' , 'maximum number of calls to the criterion function'},  5000 );
+  pl.append(p);
+  
+end
+
+