diff m-toolbox/classes/@ao/buildWhitener1D.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/buildWhitener1D.m	Wed Nov 23 19:22:13 2011 +0100
@@ -0,0 +1,446 @@
+% BUILDWHITENER1D builds a whitening filter based on the input frequency-series.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% DESCRIPTION:BUILDWHITENER1D builds a whitening filter based on the input frequency-series.
+%              The filter is built by fitting to the model provided.
+%              If no model is provided, a fit is made to a spectral-density estimate of the
+%              input time-series (made using psd+bin_data or lpsd).
+%              Note: The function assumes that the input model corresponds
+%              to the one-sided psd of the data to be whitened.
+%
+% ALGORITHM:
+%            1) If no model provided, make psd+bin_data or lpsd
+%               of time-series and take it as a model
+%               for the data power spectral density
+%            2) Fit a set of partial fraction z-domain filters using
+%               utils.math.psd2wf. The fit is automatically stopped when
+%               the accuracy tolerance is reached.
+%            3) Convert to array of MIIR filters
+%            4) Assemble into a parallel filterbank object
+%
+%
+% CALL:         b = buildWhitener1D(a, pl)
+%               [b1,b2,...,bn] = buildWhitener1D(a1,a2,...,an, pl);
+%
+% INPUT:
+%               - as is a time-series analysis object or a vector of
+%               analysis objects
+%               - pl is a plist with the input parameters
+%
+% OUTPUT:
+%               - b "whitening" filters, stored into a filterbank.
+%
+% <a href="matlab:utils.helper.displayMethodInfo('ao', 'buildWhitener1D')">Parameters Description</a>
+%
+% VERSION:     $Id: buildWhitener1D.m,v 1.11 2011/04/18 19:46:57 mauro Exp $
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+function varargout = buildWhitener1D(varargin)
+  
+  % Check if this is a call for parameters
+  if utils.helper.isinfocall(varargin{:})
+    varargout{1} = getInfo(varargin{3});
+    return
+  end
+  
+  import utils.const.*
+  utils.helper.msg(msg.PROC3, 'running %s/%s', mfilename('class'), mfilename);
+  
+  % Collect input variable names
+  in_names = cell(size(varargin));
+  for ii = 1:nargin,in_names{ii} = inputname(ii);end
+  
+  % Collect all AOs and plists
+  [as, ao_invars] = utils.helper.collect_objects(varargin(:), 'ao', in_names);
+  pl              = utils.helper.collect_objects(varargin(:), 'plist', in_names);
+  
+  if nargout == 0
+    error('### buildWhitener cannot be used as a modifier. Please give an output variable.');
+  end
+  
+  % combine plists
+  if isempty(pl)
+    model = 'psd';
+  else
+    model = find(pl, 'model');
+    if isempty(model)
+      model = 'psd';
+      pl.pset('model', model);
+    end
+  end
+  
+  if ischar(model)
+    pl = parse(pl, getDefaultPlist(model));
+  else
+    pl = parse(pl, getDefaultPlist('Default'));
+  end
+  pl.getSetRandState();
+  
+  % Collect input histories
+  inhists = [as.hist];
+  
+  % Initialize output objects
+  bs = filterbank.initObjectWithSize(1, numel(as));
+  
+  
+  % Loop over input AOs
+  for jj = 1:numel(as)
+    % 1) searching for input model
+    switch class(as(jj).data)
+      case 'tsdata'        
+          % Build the model based on input time-series
+          utils.helper.msg(msg.PROC1, 'user input tsdata object, estimating the model from it');
+          model = estimateModel(as(jj), pl);
+      case 'fsdata'
+        % The input data are the model
+        utils.helper.msg(msg.PROC1, 'user input fsdata object, taking it as the model');
+        model = as(jj);
+      otherwise
+        warning('!!! %s expects ao/tsdata or ao/fsdata objects. Skipping AO %s', mfilename, ao_invars{jj});
+        return;
+    end
+
+    %-------------- Whiten this AO
+    
+    % Extract necessary parameters
+    
+    % Tolerance for MSE Value
+    lrscond = find(pl, 'FITTOL');
+    % give an error for strange values of lrscond
+    if lrscond < 0
+      error('!!! Negative values for FITTOL are not allowed !!!')
+    end
+    % handling data
+    lrscond = -1 * log10(lrscond);
+    % give a warning for strange values of lrscond
+    if lrscond<0
+      warning('You are searching for a MSE lower than %s', num2str(10^(-1*lrscond)))
+    end
+    params.lrscond = lrscond;
+    
+    % Tolerance for the MSE relative variation
+    msevar = find(pl, 'MSEVARTOL');
+    % handling data
+    msevar = -1 * log10(msevar);
+    % give a warning for strange values of msevar
+    if msevar<0
+      warning('You are searching for MSE relative variation lower than %s', num2str(10^(-1*msevar)))
+    end
+    params.msevar = msevar;
+    
+    if isempty(params.msevar)
+      params.ctp = 'chival';
+    else
+      params.ctp = 'chivar';
+    end
+    
+    % Weights
+    switch find(pl, 'Weights')
+      case {'equal', 'flat', 1}
+        params.weightparam = 1;
+      case {'1/abs', '1./abs', 2}
+        params.weightparam = 2;
+      case {'1/abs^2', '1/abs2', '1./abs^2', '1./abs2', 3}
+        params.weightparam = 3;
+      otherwise
+        warning('Unrecognized weights option %s', find(pl, 'Weights'))
+    end
+    
+    % 2) Build filters
+    
+    % Build input structure for psd2wf
+    params.idtp = 1;
+    params.Nmaxiter = find(pl, 'MaxIter');
+    params.minorder = find(pl, 'MinOrder');
+    params.maxorder = find(pl, 'MaxOrder');
+    params.spolesopt = find(pl, 'PoleType');
+    params.spy = find(pl, 'Disp');
+    
+    
+    if (find(pl, 'plot'))
+      params.plot = 1;
+    else
+      params.plot = 0;
+    end
+    
+    fs = find(pl, 'fs');
+    if isempty(fs) || fs <= 0 || ~isfinite(fs)
+      if isempty(model.fs) || model.fs <= 0 || ~isfinite(model.fs)
+        error('### Invalid fs value %s. Please specify a meaningful fs, either via the model or in the plist', num2str(fs));
+      else
+        fs = model.fs;
+      end
+    end
+    
+    params.fs = fs;
+    params.usesym = 0;
+    params.dterm = 0; % it is better to fit without direct term
+    params.fullauto = 1;
+    
+    % call psd2wf
+    [res, poles, dterm, mresp, rdl] = ...
+      utils.math.psd2wf(model.y,[],[],[],model.x,params);
+    
+    % 3) Convert to MIIR filters
+    
+    % filtering with a stable model
+    pfilts = [];
+    for kk = 1:numel(res)
+      ft = miir(res(kk), [ 1 -poles(kk)], fs);
+      pfilts = [pfilts ft];
+    end
+    
+    % 4) Build the output filterbank object
+    bs(jj) = filterbank(plist('filters', pfilts, 'type', 'parallel'));
+    % set the input units to be the same as the model
+    bs(jj).setIunits(sqrt(model.yunits * unit('Hz')));
+    % set the output units to be empty
+    bs(jj).setOunits(unit());
+    
+    % set the name for this object
+    bs(jj).name = sprintf('buildWhitener1D(%s)', ao_invars{jj});
+    % add history
+    bs(jj).addHistory(getInfo('None'), pl, ao_invars(jj), inhists(jj));
+  end
+  
+  % Set output
+  if nargout == numel(bs)
+    % List of outputs
+    for ii = 1:numel(bs)
+      varargout{ii} = bs(ii);
+    end
+  else
+    % Single output
+    varargout{1} = bs;
+  end
+end
+
+%--------------------------------------------------------------------------
+% Get Info Object
+%--------------------------------------------------------------------------
+function ii = getInfo(varargin)
+  if nargin == 1 && strcmpi(varargin{1}, 'None')
+    sets = {};
+    pl   = [];
+  elseif nargin == 1 && ~isempty(varargin{1}) && ischar(varargin{1})
+    sets{1} = varargin{1};
+    pl = getDefaultPlist(sets{1});
+  else
+    sets = SETS();
+    % get plists
+    pl(size(sets)) = plist;
+    for kk = 1:numel(sets)
+      pl(kk) =  getDefaultPlist(sets{kk});
+    end
+  end
+  % Build info object
+  ii = minfo(mfilename, 'ao', 'ltpda', utils.const.categories.sigproc, '$Id: buildWhitener1D.m,v 1.11 2011/04/18 19:46:57 mauro Exp $', sets, pl);
+end
+
+
+%--------------------------------------------------------------------------
+% Defintion of Sets
+%--------------------------------------------------------------------------
+
+function out = SETS()
+  out = {...
+    'Default', ...
+    'PSD',    ...
+    'LPSD'   ...
+    };
+end
+
+%--------------------------------------------------------------------------
+% Get Default Plist
+%--------------------------------------------------------------------------
+function plout = getDefaultPlist(set)
+  persistent pl;  
+  persistent lastset;
+  if ~exist('pl', 'var') || isempty(pl) || ~strcmp(lastset, set)
+    pl = buildplist(set);
+    lastset = set;
+  end
+  plout = pl;  
+end
+
+
+function pl = buildplist(set)
+  pl = plist();
+  
+  % Model
+  p = param({'model', ['A model estimation technique in the case of tsdata input:<br>'...
+    '<li>PSD - using <tt>psd</tt> + <tt>bin_data</tt></li>'...
+    '<li>LPSD - using <tt>lpsd</tt></li>']}, {1, {'PSD', 'LPSD'}, paramValue.SINGLE});
+  pl.append(p);
+  
+  % Range
+  p = param({'range', ['The frequency range to evaluate the fitting.<br>' ...
+    'An empty value or [-inf inf] will include the whole range.<br>' ...
+    'The remaining part of the model will be completed according<br>' ...
+    'to the option chosen in the ''complete'' parameter.<br>' ...
+    ]}, paramValue.EMPTY_DOUBLE);
+  pl.append(p);
+  
+  % Complete
+  p = param({'complete_hf', ['Choose how to complete the frequency range up to fs/2.<ol>' ...
+    '<li>Assumes flat response</li>' ...
+    '<li>Assumes 4 poles low-pass type response</li>' ...
+    ]}, {1,{'flat', 'lowpass'}, paramValue.SINGLE});
+  pl.append(p);
+  
+  % fs
+  p = param({'fs', ['The sampling frequency to design the output filter on.<br>' ...
+    'If it is not a positive number, it will be taken from the model' ...
+    ]}, paramValue.EMPTY_DOUBLE);
+  pl.append(p);
+  
+  % MaxIter
+  p = param({'MaxIter', 'Maximum number of iterations in fit routine.'}, paramValue.DOUBLE_VALUE(30));
+  pl.append(p);
+  
+  % PoleType
+  p = param({'PoleType', ['Choose the pole type for fitting:<ol>'...
+    '<li>use real starting poles</li>'...
+    '<li>generates complex conjugate poles of the<br>'...
+    'type <tt>a.*exp(theta*pi*j)</tt>'...
+    'with <tt>theta = linspace(0,pi,N/2+1)</tt></li>'...
+    '<li>generates complex conjugate poles of the type<br>'...
+    '<tt>a.*exp(theta*pi*j)</tt><br>'...
+    'with <tt>theta = linspace(0,pi,N/2+2)</tt></li></ol>']}, {1, {1, 2, 3}, paramValue.SINGLE});
+  pl.append(p);
+  
+  % MinOrder
+  p = param({'MinOrder', 'Minimum order to fit with.'}, paramValue.DOUBLE_VALUE(2));
+  pl.append(p);
+  
+  % MaxOrder
+  p = param({'MaxOrder', 'Maximum order to fit with.'}, paramValue.DOUBLE_VALUE(25));
+  pl.append(p);
+  
+  % Weights
+  p = param({'Weights', ['Choose weighting method:<ol>'...
+    '<li>equal weights for each point</li>'...
+    '<li>weight with <tt>1/abs(model)</tt></li>'...
+    '<li>weight with <tt>1/abs(model).^2</tt></li></ol>']}, ...
+    {2, {'equal', '1/abs', '1/abs^2'}, paramValue.SINGLE});
+  pl.append(p);
+  
+  % Plot
+  p = param({'Plot', 'Plot results of each fitting step.'}, paramValue.FALSE_TRUE);
+  pl.append(p);
+  
+  % Disp
+  p = param({'Disp', 'Display the progress of the fitting iteration.'}, paramValue.FALSE_TRUE);
+  pl.append(p);
+  
+  % MSEVARTOL
+  p = param({'MSEVARTOL', ['Mean Squared Error Variation - Check if the<br>'...
+    'relative variation of the mean squared error is<br>'...
+    'smaller than the value specified. This<br>'...
+    'option is useful for finding the minimum of Chi-squared.']}, ...
+    paramValue.DOUBLE_VALUE(1e-1));
+  pl.append(p);
+  
+  % FITTOL
+  p = param({'FITTOL', ['Mean Squared Error Value - Check if the mean<br>'...
+    'squared error value is lower than the value<br>'...
+    'specified.']}, paramValue.DOUBLE_VALUE(1e-2));
+  pl.append(p);
+  
+  % Append sets of parameters according to the chosen spectral estimator
+  if ~utils.helper.ismember(lower(SETS), lower(set))
+    error('### Unknown set [%s]', set);
+  end
+  
+  switch lower(set)
+    case 'default'
+      pl.remove('model');
+    case 'psd'
+      pl = combine(pl, ao.getInfo('psd').plists);
+      pl.pset(...
+        'model', 'PSD', ...
+        'Navs', 16, ...
+        'order', 1, ...
+        'olap', 50 ...
+        );
+      pl = combine(pl, ao.getInfo('bin_data').plists);
+      pl.pset(...
+        'method', 'MEAN', ...
+        'resolution', 50 ...
+        );
+    case 'lpsd'
+      pl = combine(pl, ao.getInfo('lpsd').plists);
+      pl.pset(...
+        'model', 'LPSD' ...
+        );
+    otherwise
+  end
+end
+
+
+%--------------------------------------------------------------------------
+% Estimate a model from the data or from user input
+%--------------------------------------------------------------------------
+function model = estimateModel(b, pl)
+  
+  import utils.const.*
+  
+  
+  % Estimate a model for the PSD
+  model_all = find(pl, 'model');
+  if ischar(model_all)
+    switch lower(model_all)
+      case 'psd'
+        % Select only the parameters associated to ao/psd
+        pls = ao.getInfo('psd').plists;
+        % Call ao/psd
+        sp = psd(b, pl.subset(pls.getKeys()));
+        % Select only the parameters associated to ao/bin_data
+        pls = ao.getInfo('bin_data').plists;
+        % Call ao/bin_data
+        model_all = bin_data(sp, pl.subset(pls.getKeys()));
+      case 'lpsd'
+        % Select only the parameters associated to ao/lpsd
+        pls = ao.getInfo('lpsd').plists;
+        model_all = lpsd(b, pl.subset(pls.getKeys()));
+      otherwise
+        error('### Unknown model [%s]', model_all);
+    end
+  end
+  
+  % Select only a limited frequency range
+  frange = find(pl, 'range');
+  if isempty(frange)
+    frange = [-inf inf];
+  end
+  model = model_all.split(plist('frequencies', frange));
+  f1 = frange(1);
+  f2 = frange(2);
+  
+  if isfinite(f2)
+    % Select a technique to complete the high frequency range
+    complete_up_opt = find(pl, 'complete_hf');
+    switch complete_up_opt
+      case {'flat', 'allpass', 'all pass', 'all-pass'}
+        utils.helper.msg(msg.PROC1, 'Completing the frequency range from %s to %s with flat model', ...
+          num2str(f2), num2str(b.fs/2));
+        % Build a flat model response
+        r = ones(size(model_all.x));
+      case {'lowpass', 'low pass', 'low-pass'}
+        utils.helper.msg(msg.PROC1, 'Completing the frequency range from %s to %s with 4 poles low-pass model', ...
+          num2str(f2), num2str(b.fs/2));
+        % Build a 4 poles low pass resp
+        r = abs(resp(pzmodel(plist('gain', 1, 'poles', {10*f2,11*f2,12*f2,13*f2})), plist('f', model_all.x)));
+        r = r.y;
+      otherwise
+        error('### Unknown option [%s] for high frequency completion', complete_up_opt);
+    end
+    model_hf =  r * model.y(end);
+    model = join(model, ...
+      ao(plist('type', 'fsdata', 'xvals', model_all.x, 'yvals', model_hf, 'fs', model_all.fs, 'yunits', model_all.yunits)));
+  end
+  
+end
+
+