Mercurial > hg > ltpda
diff m-toolbox/classes/@matrix/modelselect.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/@matrix/modelselect.m Wed Nov 23 19:22:13 2011 +0100 @@ -0,0 +1,482 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% modelselect.m - method to compute the Bayes Factor using +% reverse jump mcmc algorithm +% +% call - Bxy = modelselect(out,pl) +% +% inputs - out: matrix objects with measured outputs +% pl: parameter list +% +% outputs - Bxy: an array of AOs containing the evolution +% of the Bayes factors. (comparing each model +% with each other) +% +% +% <a href="matlab:utils.helper.displayMethodInfo('matrix','modelselect')">Parameters Description</a> +% +% N. Karnesis 27/09/2011 +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function varargout = modelselect(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); + +% Method can not be used as a modifier +if nargout == 0 + error('### mcmc cannot be used as a modifier. Please give an output variable.'); +end + +% Collect input variable names +in_names = cell(size(varargin)); +for ii = 1:nargin,in_names{ii} = inputname(ii);end + +% Collect all AOs smodels and plists +[mtxs, mtxs_invars] = utils.helper.collect_objects(varargin(:), 'matrix', in_names); +pl = utils.helper.collect_objects(varargin(:), 'plist', in_names); + +% Combine plists +pl = parse(pl, getDefaultPlist); + +% Get parameters +N = find(pl, 'N'); +Tc = find(pl,'Tc'); +xi = find(pl,'heat'); +param = find(pl,'FitParams'); +cvar = find(pl, 'cov'); +rng = find(pl,'range'); +flim = find(pl,'frequencies'); +fsout = find(pl,'fsout'); +search = find(pl,'search'); +xo = find(pl,'x0'); +mtxin = find(pl,'input'); +mdlin = find(pl,'models'); +mdlFreqDependent = find(pl,'modelFreqDependent'); +nse = find(pl,'noise'); +jumps = find(pl,'jumps'); +parplot = find(pl,'plot'); +debug = find(pl,'debug'); +outModel = find(pl,'outModel'); +inModel = find(pl,'inModel1'); +outNames = find(pl,'outNames'); +inNames = find(pl,'inNames'); +anneal = find(pl,'anneal'); +DeltaL = find(pl,'DeltaL'); +SNR0 = find(pl,('SNR0')); + +nummodels = numel(mdlin); + +% Decide on a deep copy or a modify +in = copy(mtxin, nargout); +out = copy(mtxs, nargout); +for k = 1:nummodels + mdl(k) = copy(mdlin{1,k}, nargout); + nparam(k) = numel(param{1,k}); + % lighten the model + mdl(k).clearHistory; + if isa(mdl(k), 'ssm') + mdl(k).clearNumParams; + mdl(k).clearAllUnits; + mdl(k).params.simplify; + end +end + + + +% Check input parameters +if isempty(rng) + error('### Please define a search range ''range'''); +end +if isempty(param) + error('### Please define the parameters ''param'''); +end +if size(mdl(1)) ~= size(nse) + error('### Parameters ''model'' and ''noise'' must be the same dimension'); +end +if size(in) ~= size(out) + error('### Number of input and output experiments must be the same'); +end + + +% Get range for parameters +for jj = 1:nummodels + for i = 1:numel(param{1,jj}) + rang{1,jj}(:,i) = rng{1,jj}{i}'; + end +end +% Get number of experiments +nexp = numel(in); + +switch class(mdl) + case 'matrix' + % Check model sizes + for k = 1:nummodels + if (isempty(outModel) && isempty(inModel)) + if (~(numel(in(1).objs) == mdl(k).ncols) || ~(numel(out(1).objs) == mdl(k).nrows)) + error('Check model or input/output sizes'); + end + elseif isempty(inModel) + if (~(numel(in(1).objs) == mdl(k).ncols) || ~(size(outModel,2) == mdl(k).nrows) || ~(size(outModel,1) == numel(out(1).objs))) + error('Check model or input/output sizes'); + end + elseif isempty(outModel) + if (~(numel(in(1).objs) == size(inModel,2)) || ~(size(inModel,1) == mdl(k).ncols) || ~(numel(out(1).objs) == mdl(k).nrows)) + error('Check model or input/output sizes'); + end + else + if (~(numel(in(1).objs) == size(inModel,2)) || ~(size(inModel,1) == mdl(k).ncols) || ~(size(outModel,2) == mdl(k).nrows) || ~(numel(out(1).objs) == size(outModel,1))) + error('Check model or input/output sizes'); + end + end + end + case 'ssm' + inNames = find(pl,'inNames'); + outNames = find(pl,'outNames'); + if( (numel(inNames) ~= numel(in(1).objs)) || numel(outNames) ~= numel(out(1).objs)) + error('Check model inNames and outNames, they do not match with the input objects') + end + otherwise + error('### Model must be either from the ''matrix'' or the ''ssm'' class. Please check the inputs') +end + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%% Frequency domain pre-process %%%%%%%%%%%%%%%%%%%%%%%% + +utils.helper.msg(msg.IMPORTANT, 'Computing frequency domain objects', mfilename('class'), mfilename); +for k = 1:nexp + + if((numel(out(1).objs) == 2) && (numel(in(1).objs) == 2)) % previous code done by miquel + % optional resampling (not matrix/resample method yet) + if ~isempty(fsout) + for i = 1:numel(in(k).objs(:)) + in(k).objs(i).resample(plist('fsout',fsout)); + out(k).objs(i).resample(plist('fsout',fsout)); + end + end + + % fft + fin(k) = fft(in(k)); + fout(k) = fft(out(k)); + + Nsamples = length(fin(k).getObjectAtIndex(1).x); + fs = fin(k).getObjectAtIndex(1).fs; + + + if (isempty(outModel)) + % Get rid of fft f =0, reduce frequency range if needed + if ~isempty(flim) + fin(k) = split(fin(k),plist('frequencies',[flim(1) flim(end)])); + fout(k) = split(fout(k),plist('frequencies',[flim(1) flim(end)])); + end + elseif (~isempty(outModel)) + % Get rid of fft f =0, reduce frequency range if needed + if ~isempty(flim) + fin(k) = split(fin(k),plist('frequencies',[flim(1) flim(end)])); + fout(k) = split(fout(k),plist('frequencies',[flim(1) flim(end)])); + if(~isempty(outModel)) + for lll=1:size(outModel,1) + for kkk=1:size(outModel,2) + outModel(lll,kkk) = split(outModel(lll,kkk),plist('frequencies',[flim(1) flim(end)])); + end + end + end + if(~isempty(inModel)) + inModel = split(inModel,plist('frequencies',[flim(1) flim(end)])); + end + end + end + + % use signal fft to get frequency vector. Take into account signal + % could be empty or set to zero + + % 1st channel + if all(fin(k).getObjectAtIndex(1,1).y == 0) || isempty(fin(k).getObjectAtIndex(1,1).y) + i1 = ao(plist('type','fsdata','xvals',0,'yvals',0)); + % rebuild input with new zeroed signal + fin(k) = matrix(i1,fin(k).getObjectAtIndex(2,1),plist('shape',[2 1])); + else + freqs = fin(k).getObjectAtIndex(1,1).x; + end + % 2nd channel + if all(fin(k).getObjectAtIndex(2,1).y == 0) || isempty(fin(k).getObjectAtIndex(2,1).y) + i2 = ao(plist('type','fsdata','xvals',0,'yvals',0)); + % rebuild input with new zeroed signal + fin(k) = matrix(fin(k).getObjectAtIndex(1,1),i2,plist('shape',[2 1])); + else + freqs = fin(k).getObjectAtIndex(2,1).x; + end + + % Compute psd + n1 = Nsamples*fs/2*psd(nse(k).getObjectAtIndex(1,1), pl); + n2 = Nsamples*fs/2*psd(nse(k).getObjectAtIndex(2,1), pl); + n12 = Nsamples*fs/2*cpsd(nse(k).getObjectAtIndex(1,1),nse(k).getObjectAtIndex(2,1), pl); + + % interpolate noise to fft frequencies + S11 = interp(n1,plist('vertices',freqs)); + S12 = interp(n12,plist('vertices',freqs)); + S22 = interp(n2,plist('vertices',freqs)); + S21 = conj(S12); + % build cross-spectrum matrix + mnse(k) = matrix(S11,S21,S12,S22,plist('shape',[2 2])); + + elseif((numel(out(1).objs) == 1) && (numel(in(1).objs) == 1)) + % optional resampling (not matrix/resample method yet) + if ~isempty(fsout) + for i = 1:numel(in(k).objs(:)) + in(k).objs(i).resample(plist('fsout',fsout)); + out(k).objs(i).resample(plist('fsout',fsout)); + end + end + + % fft + fin(k) = fft(in(k)); + fout(k) = fft(out(k)); + + Nsamples = length(fin(k).getObjectAtIndex(1).x); + fs = fin(k).getObjectAtIndex(1).fs; + + + if (isempty(outModel)) + % Get rid of fft f =0, reduce frequency range if needed + if ~isempty(flim) + fin(k) = split(fin(k),plist('frequencies',[flim(1) flim(end)])); + fout(k) = split(fout(k),plist('frequencies',[flim(1) flim(end)])); + end + elseif (~isempty(outModel)) + % Get rid of fft f =0, reduce frequency range if needed + if ~isempty(flim) + fin(k) = split(fin(k),plist('frequencies',[flim(1) flim(end)])); + fout(k) = split(fout(k),plist('frequencies',[flim(1) flim(end)])); + if(~isempty(outModel)) + for lll=1:size(outModel,1) + for kkk=1:size(outModel,2) + outModel(lll,kkk) = split(outModel(lll,kkk),plist('frequencies',[flim(1) flim(end)])); + end + end + end + if(~isempty(inModel)) + inModel = split(inModel,plist('frequencies',[flim(1) flim(end)])); + end + end + end + + % use signal fft to get frequency vector. Take into account signal + % could be empty or set to zero + + % 1st and only channel + if all(fin(k).getObjectAtIndex(1,1).y == 0) || isempty(fin(k).getObjectAtIndex(1,1).y) + i1 = ao(plist('type','fsdata','xvals',0,'yvals',0)); + % rebuild input with new zeroed signal + fin(k) = matrix(i1,fin(k).getObjectAtIndex(2,1),plist('shape',[2 1])); + else + freqs = fin(k).getObjectAtIndex(1,1).x; + end + + % Compute psd + n1 = Nsamples*fs/2*psd(nse(k).getObjectAtIndex(1,1), pl); + + % interpolate noise to fft frequencies + S11 = interp(n1,plist('vertices',freqs)); + + % build cross-spectrum matrix + mnse(k) = matrix(S11); + + else + error('### Sorry, implemented only for 2 inputs - 2 outputs and 1 input - 1 output only...') + end + + switch class(mdl) + + case 'matrix' + for jj = 1:nummodels + for i = 1:numel(mdl(jj).objs) + if (mdlFreqDependent) + % set Xvals + mdl(jj).objs(i).setXvals(freqs); + % set alias + mdl(jj).objs(i).assignalias(mdl(jj).objs(i),plist('xvals',freqs)); + else + mdl(jj).objs(i).setXvals(1); + end + end + end + case 'ssm' + if k < 2 + for jj = 1:nummodels + mdl(jj).clearNumParams; + spl = plist('set', 'for bode', ... + 'outputs', outNames, ... + 'inputs', inNames); + % first optimise our model for the case in hand + mdl(jj).reorganize(spl); + % make it lighter + mdl(jj).optimiseForFitting(); + end + end + end + % add lines to the matrix of models. Each line corresponds to one + % experiment + mmdl(k,:) = mdl; +end + + [Bxy LogLambda] = utils.math.rjsample(mmdl,fin,fout,mnse,cvar,N,rang,param,Tc,xi,xo,search,jumps,parplot,debug,inNames,outNames,anneal,SNR0,DeltaL,inModel,outModel); + +% Set output +numfactors = 0; +for gg = 1:(nummodels) + for dd = gg+1:(nummodels) + numfactors = numfactors + 1; + BayesFactor(numfactors) = ao(Bxy{gg,dd}); + BayesFactor(numfactors).setName(sprintf('B%d%d',gg,dd)); + BayesFactor(numfactors).procinfo = plist('Likelihoods',LogLambda); + end +end + +output = {BayesFactor}; +varargout = output(1:nargout); + + +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.sigproc, '$Id: mcmc.m,v 1.33 2011/07/31 17:38:34 nikos 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(); + +% inNames +p = param({'inNames','Input names. Used for ssm models'}, paramValue.EMPTY_STRING); +pl.append(p); + +% outNames +p = param({'outNames','Output names. Usde for ssm models'}, paramValue.EMPTY_STRING); +pl.append(p); + +%Model +p = param({'model','An array input of models.'}, paramValue.EMPTY_STRING); +pl.append(p); + +% Param +p = param({'FitParams','A cell array of evaluated parameters for each model.'}, paramValue.EMPTY_DOUBLE); +pl.append(p); + +% Input +p = param({'input','A matrix array of input signals.'}, paramValue.EMPTY_STRING); +pl.append(p); + +pl = plist.MCH_FIT_PLIST; + +% N +p = param({'N','number of samples of the chain.'}, paramValue.DOUBLE_VALUE(1000)); +pl.append(p); + +% Sigma +p = param({'cov','Cell array containing the covariances of the gaussian jumping distribution for each model.'}, paramValue.DOUBLE_VALUE(1e-4)); +pl.append(p); + +% Noise +p = param({'noise','A matrix array of noise spectrum (PSD) used to compute the likelihood.'}, paramValue.EMPTY_STRING); +pl.append(p); + +% Search +p = param({'modelFreqDependent','Set to true to use frequency dependent s models, set to false when using constant models'}, paramValue.TRUE_FALSE); +pl.append(p); + +% Search +p = param({'search','Set to true to use bigger jumps in parameter space during annealing and cool down.'}, paramValue.TRUE_FALSE); +pl.append(p); + +% Frequencies +p = param({'frequencies','Range of frequencies where the analysis is performed. If an array, only first and last are used'}, paramValue.EMPTY_DOUBLE); +pl.append(p); + +% Resample +p = param({'fsout','Desired sampling frequency to resample the input time series'}, paramValue.EMPTY_DOUBLE); +pl.append(p); + +% heat +p = param({'heat','The heat index flattening likelihood surface during annealing.'}, paramValue.DOUBLE_VALUE(1)); +pl.append(p); + +% Tc +p = param({'Tc','An array of two values setting the initial and final value for the cooling down.'}, paramValue.EMPTY_STRING); +pl.append(p); + +% initial values +p = param({'x0','The proposed initial values (cell array again).'}, paramValue.EMPTY_DOUBLE); +pl.append(p); + +% ranges +p = param({'range','ranges'}, paramValue.EMPTY_DOUBLE); +pl.append(p); + +% jumps +p = param({'jumps','An array of four numbers setting the rescaling of the covariance matrix during the search phase.',... + 'The first value is the one applied by default, the following thhree apply just when the chain sample is',... + 'mod(10), mod(25) and mod(100) respectively.'}, paramValue.EMPTY_DOUBLE); +pl.append(p); + +% heat +p = param({'plot','If set equal to one, the evolution of the Bayes factor is plotted every 500 steps.'}, paramValue.EMPTY_DOUBLE); +pl.append(p); + +% debug +p = param({'debug','Set to true to get debug information of the MCMC process.'}, paramValue.FALSE_TRUE); +pl.append(p); + +% outModel +p = param({'outModel','Output model. Still under test'}, paramValue.EMPTY_STRING); +pl.append(p); + +p = param({'anneal',['Choose type of annealing during sampling. Default value is ',... + 'simulated annealing. Choose "thermo" for annealing with a thermostat.',... + ' SNR is computed and if it is larger than a fixed value SNR0 (provided also in the plist), ',... + 'then the chains are heated by a factor of (SNR(1)/SNR0)^2. Choosing "simple" ',... + 'the deviation of the loglikelihood of every 10 points in the chains is stored. If this deviation ',... + 'is larger or smaller than two fixed values the chains are cooled or heated respectively.']}, {1, {'simul','thermo', 'simple'}, paramValue.SINGLE}); +pl.append(p); + +p = param({'SNR0','Fixed value for thermostated annealing.'}, {1, {200}, paramValue.OPTIONAL}); +pl.append(p); + +p = param({'DeltaL',['Deviation of Loglikelihood for 10 points of the chains. Used for',... + 'the "simple" choice of annealing with a thermostat.']}, {1, {[100 600 2 3]}, paramValue.OPTIONAL}); +pl.append(p); + + +end + +