Mercurial > hg > ltpda
view m-toolbox/classes/@ao/xfit.m @ 44:409a22968d5e default
Add unit tests
author | Daniele Nicolodi <nicolodi@science.unitn.it> |
---|---|
date | Tue, 06 Dec 2011 18:42:11 +0100 |
parents | f0afece42f48 |
children |
line wrap: on
line source
% XFIT fit a function of x to data. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % DESCRIPTION: XFIT performs a non-linear fit of a function of x to data. % smodels fitting is also supported. % % ALGORITHM: XFIT does a chi-squared minimization by means of different % algorithms (see details in the default plist). Covariance matrix is also % computed from the Fisher's Information Matrix. In case the Information % Matrix is not positive-definite, uncertainties will not be stored in the % output. % % CALL: b = xfit(a, pl) % % INPUTS: a - input AO to fit to % pl - parameter list (see below) % % OUTPUTs: b - a pest object containing the best-fit parameters, % goodness-of-fit reduced chi-squared, fit degree-of-freedom % covariance matrix and uncertainties. Additional % quantities, like the Information Matrix, are contained % within the procinfo. The best-fit model can be evaluated % from pest\eval. % % <a href="matlab:utils.helper.displayMethodInfo('ao', 'xfit')">Parameters Description</a> % % EXAMPLES: % % % 1) Fit to a frequency-series % % % Create a frequency-series % datapl = plist('fsfcn', '0.01./(0.0001+f) + 5*abs(randn(size(f))) ', 'f1', 1e-5, 'f2', 5, 'nf', 1000, ... % 'xunits', 'Hz', 'yunits', 'N/Hz'); % data = ao(datapl); % data.setName; % % % Do fit % fitpl = plist('Function', 'P(1)./(P(2) + Xdata) + P(3)', ... % 'P0', [0.1 0.01 1]); % params = xfit(data, fitpl) % % % Evaluate model % BestModel = eval(params,plist('type','fsdata','xdata',data,'xfield','x')); % BestModel.setName; % % % Display results % iplot(data,BestModel) % % % 2) Fit to a noisy sine-wave % % % Create a noisy sine-wave % fs = 10; % nsecs = 500; % datapl = plist('waveform', 'Sine wave', 'f', 0.01, 'A', 0.6, 'fs', fs, 'nsecs', nsecs, ... % 'xunits', 's', 'yunits', 'm'); % sw = ao(datapl); % noise = ao(plist('tsfcn', '0.01*randn(size(t))', 'fs', fs, 'nsecs', nsecs)); % data = sw+noise; % data.setName; % % % Do fit % fitpl = plist('Function', 'P(1).*sin(2*pi*P(2).*Xdata + P(3))', ... % 'P0', [1 0.01 0]); % params = xfit(data, fitpl) % % % Evaluate model % BestModel = eval(params,plist('type','tsdata','xdata',sw,'xfield','x')); % BestModel.setName; % % % Display results % iplot(data,BestModel) % % % 3) Fit an smodel of a straight line to some data % % % Create a noisy straight-line % datapl = plist('xyfcn', '2.33 + 0.1*x + 0.01*randn(size(x))', 'x', 0:0.1:10, ... % 'xunits', 's', 'yunits', 'm'); % data = ao(datapl); % data.setName; % % % Model to fit % mdl = smodel('a + b*x'); % mdl.setXvar('x'); % mdl.setParams({'a', 'b'}, {1 2}); % % % Fit model % fitpl = plist('Function', mdl, 'P0', [1 1]); % params = xfit(data, fitpl) % % % Evaluate model % BestModel = eval(params,plist('xdata',data,'xfield','x')); % BestModel.setName; % % % Display results % iplot(data,BestModel) % % % 4) Fit a chirp-sine firstly starting from an initial guess (quite close % % to the true values) (bad convergency) and secondly by a Monte Carlo % % search (good convergency) % % % Create a noisy chirp-sine % fs = 10; % nsecs = 1000; % % % Model to fit and generate signal % mdl = smodel(plist('name', 'chirp', 'expression', 'A.*sin(2*pi*(f + f0.*t).*t + p)', ... % 'params', {'A','f','f0','p'}, 'xvar', 't', 'xunits', 's', 'yunits', 'm')); % % % signal % s = mdl.setValues({10,1e-4,1e-5,0.3}); % s.setXvals(0:1/fs:nsecs-1/fs); % signal = s.eval; % signal.setName; % % % noise % noise = ao(plist('tsfcn', '1*randn(size(t))', 'fs', fs, 'nsecs', nsecs)); % % % data % data = signal + noise; % data.setName; % % % Fit model from the starting guess % fitpl_ig = plist('Function', mdl, 'P0',[8,9e-5,9e-6,0]); % params_ig = xfit(data, fitpl_ig); % % % Evaluate model % BestModel_ig = eval(params_ig,plist('xdata',data,'xfield','x')); % BestModel_ig.setName; % % % Display results % iplot(data,BestModel_ig) % % % Fit model by a Monte Carlo search % fitpl_mc = plist('Function', mdl, ... % 'MonteCarlo', 'yes', 'Npoints', 1000, 'LB', [8,9e-5,9e-6,0], 'UB', [11,3e-4,2e-5,2*pi]); % params_mc = xfit(data, fitpl_mc) % % % Evaluate model % BestModel_mc = eval(params_mc,plist('xdata',data,'xfield','x')); % BestModel_mc.setName; % % % Display results % iplot(data,BestModel_mc) % % % 5) Multichannel fit of smodels % % % Ch.1 data % datapl = plist('xyfcn', '0.1*x + 0.01*randn(size(x))', 'x', 0:0.1:10, 'name', 'channel 1', ... % 'xunits', 'K', 'yunits', 'Pa'); % a1 = ao(datapl); % % Ch.2 data % datapl = plist('xyfcn', '2.5*x + 0.1*sin(2*pi*x) + 0.01*randn(size(x))', 'x', 0:0.1:10, 'name', 'channel 2', ... % 'xunits', 'K', 'yunits', 'T'); % a2 = ao(datapl); % % % Model to fit % mdl1 = smodel('a*x'); % mdl1.setXvar('x'); % mdl1.setParams({'a'}, {1}); % mdl1.setXunits('K'); % mdl1.setYunits('Pa'); % % mdl2 = smodel('b*x + a*sin(2*pi*x)'); % mdl2.setXvar('x'); % mdl2.setParams({'a','b'}, {1,2}); % mdl2.setXunits('K'); % mdl2.setYunits('T'); % % % Fit model % params = xfit(a1,a2, plist('Function', [mdl1,mdl2])); % % % evaluate model % b = eval(params, plist('index',1,'xdata',a1,'xfield','x')); % b.setName('fit Ch.1'); % r = a1-b; % r.setName('residuals'); % iplot(a1,b,r) % % b = eval(params, plist('index',2,'xdata',a2,'xfield','x')); % b.setName('fit Ch.2'); % r = a2-b; % r.setName('residuals'); % iplot(a2,b,r) % % <a href="matlab:utils.helper.displayMethodInfo('ao', 'xfit')">Parameters Description</a> % % VERSION: $Id: xfit.m,v 1.83 2011/05/12 07:46:29 mauro Exp $ % % HISTORY: 17-09-2009 G. Congedo % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function varargout = xfit(varargin) % global variables global Pidx Ydata weights modelFuncs dFcns hFcns lb ub Nch Ndata estimator % 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('### xfit cannot be used as a modifier. Please give an output variable.'); end % combine plists pl = parse(pl, getDefaultPlist()); % Extract necessary parameters targetFcn = pl.find('Function'); P0 = pl.find('P0'); % ADDP = pl.find('ADDP'); userOpts = pl.find('OPTSET'); weights = pl.find('WEIGHTS'); FitUnc = pl.find('FitUnc'); UncMtd = pl.find('UncMtd'); linUnc = pl.find('LinUnc'); FastHess = pl.find('FastHess'); SymDiff = pl.find('SymDiff'); SymGrad = pl.find('SymGrad'); SymHess = pl.find('SymHess'); DiffOrder = pl.find('DiffOrder'); lb = pl.find('LB'); ub = pl.find('UB'); MCsearch = pl.find('MonteCarlo'); Npoints = pl.find('Npoints'); Noptims = pl.find('Noptims'); % SVDsearch = pl.find('SVD'); % nSVD = pl.find('nSVD'); Algorithm = pl.find('Algorithm'); % AdpScale = pl.find('AdpScale'); % only for function handle fitting pnames = pl.find('pnames'); % pvalues = pl.find('pvalues'); estimator = pl.find('estimator'); % Convert yes/no, true/false, etc. to booleans FitUnc = utils.prog.yes2true(FitUnc); linUnc = utils.prog.yes2true(linUnc); FastHess = utils.prog.yes2true(FastHess); SymDiff = utils.prog.yes2true(SymDiff); MCsearch = utils.prog.yes2true(MCsearch); % SVDsearch = utils.prog.yes2true(SVDsearch); % AdpScale = utils.prog.yes2true(AdpScale); % Check the fitting algorithm % AlgoCheck = ~strcmpi(Algorithm,'fminsearch') & ~strcmpi(Algorithm,'fminunc') & ~isempty(Algorithm); if isempty(Algorithm) Algorithm = 'fminsearch'; utils.helper.msg(msg.IMPORTANT, 'using %s as the fitting algorithm', upper(Algorithm)); elseif ischar(Algorithm) Algorithm = lower(Algorithm); switch Algorithm case 'fminsearch' utils.helper.msg(msg.IMPORTANT, 'using %s as the fitting algorithm', upper(Algorithm)); Algorithm = 'fminsearch'; case 'fminunc' if exist('fminunc','file')==2 utils.helper.msg(msg.IMPORTANT, 'using %s as the fitting algorithm', upper(Algorithm)); Algorithm = 'fminunc'; else error('### you must install Optimization Toolbox in order to use %s', upper(Algorithm)) end case 'fmincon' if exist('fmincon','file')==2 utils.helper.msg(msg.IMPORTANT, 'using %s as the fitting algorithm', upper(Algorithm)); Algorithm = 'fmincon'; else error('### you must install Optimization Toolbox in order to use %s', upper(Algorithm)) end case 'patternsearch' if exist('patternsearch','file')==2 utils.helper.msg(msg.IMPORTANT, 'using %s as the fitting algorithm', upper(Algorithm)); Algorithm = 'patternsearch'; else error('### you must install Genetic Algorithm and Direct Search Toolbox in order to use %s', upper(Algorithm)) end case 'ga' if exist('ga','file')==2 utils.helper.msg(msg.IMPORTANT, 'using %s as the fitting algorithm', upper(Algorithm)); Algorithm = 'ga'; else error('### you must install Genetic Algorithm and Direct Search Toolbox in order to use %s', upper(Algorithm)) end case 'simulannealbnd' if exist('simulannealbnd','file')==2 utils.helper.msg(msg.IMPORTANT, 'using %s as the fitting algorithm', upper(Algorithm)); Algorithm = 'simulannealbnd'; else error('### you must install Genetic Algorithm and Direct Search Toolbox in order to use %s', upper(Algorithm)) end otherwise error('### unknown fitting algorithm') end else error('### unknown format for ALGORITHM parameter') end % Estimator if isempty(estimator) estimator = 'chi2'; elseif ~strcmp(estimator,{'chi2','abs','log','median'}) error('### unknown name of the estimator') end % Data we will fit to Xdata = as.x; Ydata = as.y; % dYdata = as.dy; % Number of data point per each channel Ndata = numel(as(1).x); % Number of channels Nch = numel(as); multiCh = Nch-1; % Number of models if all(isa(targetFcn, 'smodel')) Nmdls = numel(targetFcn); elseif iscell(targetFcn) Nmdls = numel(targetFcn); else Nmdls = 1; end % consistency check on the data units Xunits = as.xunits; % Xunits = as(1).xunits.strs; if Nch>1 XunitsCheck = Xunits(1).strs; for kk=2:Nch if ~strcmp(Xunits(kk).strs,XunitsCheck) error('### in multi-channel fitting the xunits of all data objects must be the same') end end end Xunits = as(1).xunits; Yunits = as.yunits; % consistency check on the inputs if isempty(targetFcn) error('### please specify at least a model'); end if Nch~=Nmdls error('### number of data channels and models do not match') end for kk=1:Nch if any(numel(as(kk).x)~=Ndata) error('### the number of data is not self-consistent: please check that all channels have the same length') end end for kk=1:Nch for kkk=1:Nch if any(Xdata(:,kk)~=Xdata(:,kkk)) error('### in multi-channel fitting the x-field of all data objects must be the same') end end end cellMdl = iscell(targetFcn); if multiCh if cellMdl for kk=1:Nmdls if ~isa(targetFcn{kk}, 'function_handle') error('### please use a cell array of function handles') end end else for kk=1:Nmdls if ~isa(targetFcn(kk), 'smodel') error('### multi-channel fitting is only possible with smodels') end if isempty(targetFcn(kk).expr) error('### please specify a target function for all smodels'); end if ~isempty(targetFcn(kk).values) & size(targetFcn(kk).values)~=size(targetFcn(kk).params) error('### please give an initial value for each parameter') end end if ~isempty(P0) error('in multi-channel fitting the initial values for the parameters must be within each smodel') end checkExistAllParams = 0; for kk=1:Nch checkExistAllParams = checkExistAllParams + ~isempty(targetFcn(kk).values); end if checkExistAllParams==0 && ~MCsearch error('### please give initial values for all parameters or use Monte Carlo instead') end end end % check P0 if isempty(P0) && ~MCsearch for kk=1:Nmdls if isa(targetFcn(kk), 'smodel') && isempty(targetFcn(kk).values) error('### please give initial values for all parameters or use Monte Carlo instead') elseif ischar(targetFcn) error('### please give initial values for all parameters or use Monte Carlo instead') % elseif cellMdl % error('### please give initial values for all parameters or use Monte Carlo instead') end end end % Extract anonymous functions if multiCh if ~cellMdl % concatenate models and parameters [params,mdl_params,Pidx] = cat_mdls(targetFcn); else%if iscell(P0) [params,mdl_params,Pidx] = cat_mdls_cell(targetFcn, pnames); % else % params = pnames; % Pidx = cell(1,Nch); % mdl_params = pnames; % for ii=1:Nch % Pidx{ii} = ones(1,numel(pnames)); % end end % create the full initial value array if ~MCsearch && ~cellMdl P0 = zeros(1,numel(params)); for ii=1:numel(params) for kk=1:Nmdls for jj=1:numel(targetFcn(kk).params) if strcmp(params{ii},targetFcn(kk).params{jj}) P0(ii) = targetFcn(kk).values{jj}; end end end end elseif cellMdl if isempty(P0)% || ~iscell(pvalues) error('### please give initial values') end if isempty(pnames) || ~iscell(pnames) error('### please give parameter names in a cell array') end if size(P0)~=size(pnames) error('### the size of pnames and pvalues does not match') end % P0 = zeros(1,numel(params)); % for ii=1:numel(P0) % P0(ii) = pvalues{ii}; % end if iscell(P0) && ~MCsearch for ii=1:numel(P0) if isempty(P0{ii}) error('### please give initial values for all parameters or use Monte Carlo instead'); end end for ii=1:numel(params) for kk=1:Nmdls for jj=1:numel(pnames{kk}) if strcmp(params{ii},pnames{kk}{jj}) P0new(ii) = P0{kk}(jj); end end end end P0 = P0new; end end if all(isa(targetFcn,'smodel')) % anonymous fcns modelFuncs = cell(1,Nch); for kk=1:Nch targetFcn(kk).setXvals(Xdata(:,kk)); modelFuncs{kk} = targetFcn(kk).fitfunc; end end % if cellMdl % for kk=1:Nch % modelFuncs{kk} = targetFcn{kk}; % end % end else % Check parameters if isempty(P0) if isa(targetFcn, 'smodel') P0 = [targetFcn.values{:}]; elseif isempty(P0) && ~MCsearch error('### please give initial values for all parameters or use Monte Carlo instead'); end end if size(P0)~=[1 numel(P0)] P0 = P0'; end % convert input regular expression to anonymous function only for % single-channel fitting % create anonymouse function from user input expression if ischar(targetFcn) checkFcn = regexp(targetFcn, 'Xdata'); if isempty(checkFcn) error('### when using a string expression for the input model, the independent variable must be named as Xdata') end % tfunc = eval(['@(P,Xdata,ADDP) (',targetFcn,')']); tfunc = eval(['@(P,Xdata) (',targetFcn,')']); % now create another anonymous function that only depends on the % parameters % modelFunc = @(x)tfunc(x, Xdata, ADDP); modelFuncs{1} = @(x)tfunc(x, Xdata); elseif isa(targetFcn,'smodel') targetFcn.setXvals(Xdata); modelFuncs{1} = targetFcn.fitfunc; % elseif isa(targetFcn,'function_handle') % modelFunc = targetFcn; end end if cellMdl for kk=1:Nch modelFuncs{kk} = targetFcn{kk}; end % if ~multiCh % modelFunc = modelFuncs{1}; % end end % check lb and ub % constrained search or not conSearch = ~isempty(lb) || ~isempty(ub); if MCsearch || conSearch if isempty(lb) || isempty(ub) error('### please give LB and UB') end if multiCh && (~iscell(lb) || ~iscell(ub)) error('### in multi-channel fitting upper and lower bounds must be cell array') end if size(lb)~=size(ub) % | size(lb)~=size(P0) | size(ub)~=size(P0) error('### LB and UB must be of the same size'); end if multiCh && numel(lb)~=Nch && numel(ub)~=Nch error('### in multi-channel fitting LB and UB must be cell array whose number of elements is equal to the number of models'); end if ~multiCh && ~all(lb<=ub) error('### UB must me greater equal to LB'); end if multiCh for kk=1:Nmdls if numel(lb{kk})~=numel(mdl_params{kk}) error('### please give the proper number of values for LB for each model') end if numel(ub{kk})~=numel(mdl_params{kk}) error('### please give the proper number of values for UB for each model') end if ~all(lb{kk}<=ub{kk}) error('### UB must me greater equal to LB for all parameters and models'); end if size(lb{kk})~=[1 numel(lb{kk})] lb{kk} = lb{kk}'; ub{kk} = ub{kk}'; end end end if ~multiCh if size(lb)~=[1 numel(lb)] lb = lb'; ub = ub'; end end end % create the full bounds array if (MCsearch || conSearch) && multiCh && ~cellMdl lb_full = zeros(1,numel(params)); ub_full = zeros(1,numel(params)); for ii=1:numel(params) for kk=1:Nmdls for jj=1:numel(targetFcn(kk).params) if strcmp(params{ii},targetFcn(kk).params{jj}) lb_full(ii) = lb{kk}(jj); ub_full(ii) = ub{kk}(jj); end end end end lb = lb_full; ub = ub_full; elseif (MCsearch || conSearch) && multiCh && cellMdl lb_full = zeros(1,numel(params)); ub_full = zeros(1,numel(params)); for ii=1:numel(params) for kk=1:Nmdls for jj=1:numel(mdl_params{kk}) if strcmp(params{ii},mdl_params{kk}(jj)) lb_full(ii) = lb{kk}(jj); ub_full(ii) = ub{kk}(jj); end end end end lb = lb_full; ub = ub_full; % elseif cellMdl % lb = lb; % ub = ub; end % if ~iscell(ADDP) % ADDP = {ADDP}; % end % Get input options switch Algorithm case 'fminsearch' opts = optimset(@fminsearch); if isstruct(userOpts) opts = optimset(opts, userOpts); else for j=1:2:numel(userOpts) opts = optimset(opts, userOpts{j}, userOpts{j+1}); end end case 'fminunc' opts = optimset(@fminunc); if isstruct(userOpts) opts = optimset(opts, userOpts); else for j=1:2:numel(userOpts) opts = optimset(opts, userOpts{j}, userOpts{j+1}); end end case 'fmincon' opts = optimset(@fmincon); if isstruct(userOpts) opts = optimset(opts, userOpts); else for j=1:2:numel(userOpts) opts = optimset(opts, userOpts{j}, userOpts{j+1}); end end case 'patternsearch' opts = psoptimset(@patternsearch); if isstruct(userOpts) opts = psoptimset(opts, userOpts); else for j=1:2:numel(userOpts) opts = psoptimset(opts, userOpts{j}, userOpts{j+1}); end end case 'ga' opts = gaoptimset(@ga); if isstruct(userOpts) opts = gaoptimset(opts, userOpts); else for j=1:2:numel(userOpts) opts = gaoptimset(opts, userOpts{j}, userOpts{j+1}); end end case 'simulannealbnd' opts = saoptimset(@simulannealbnd); if isstruct(userOpts) opts = saoptimset(opts, userOpts); else for j=1:2:numel(userOpts) opts = saoptimset(opts, userOpts{j}, userOpts{j+1}); end end end % compute the right weights [weights,unweighted] = find_weights(as,weights); % define number of free parameters if ~MCsearch Nfreeparams = length(P0); else Nfreeparams = length(lb); end if Nch==1 Pidx{1}=1:Nfreeparams; % modelFuncs{1}=modelFunc; end % Check for user-supplied analytical gradient as cell-array of function % handles if ~isempty(SymGrad) dFcns = cell(Nmdls,1); for kk=1:Nmdls for ii=1:numel(SymGrad{kk}) dFcns{kk}{ii} = SymGrad{kk}{ii}; end end end % Check for user-supplied analytical hessian as cell-array of function % handles if ~isempty(SymGrad) && ~isempty(SymHess) hFcns = cell(Nmdls,1); for kk=1:Nmdls for jj=1:numel(dFcns{kk}) for ii=1:jj hFcns{kk}{ii,jj} = SymHess{kk}{ii,jj}; end end end end % If requested, compute the analytical gradient if SymDiff || linUnc && isempty(SymGrad) utils.helper.msg(msg.IMPORTANT, 'evaluating symbolic derivatives'); if ~isa(targetFcn,'smodel') error('### smodel functions must be used in order to do symbolic differentiation') end % compute symbolic 1st-order differentiation dFcnsSmodel = cell(Nmdls,1); for kk=1:Nmdls p = targetFcn(kk).params; for ii=1:numel(p) dFcnsSmodel{kk}(ii) = diff(targetFcn(kk),p{ii}); end end % extract anonymous function dFcns = cell(Nmdls,1); for kk=1:Nmdls for ii=1:numel(dFcnsSmodel{kk}) dFcns{kk}{ii} = dFcnsSmodel{kk}(ii).fitfunc; end end if DiffOrder==2; % compute symbolic 2nd-order differentiation hFcnsSmodel = cell(Nmdls,1); for kk=1:Nmdls p = targetFcn(kk).params; for jj=1:numel(p) for ii=1:jj hFcnsSmodel{kk}(ii,jj) = diff(dFcnsSmodel{kk}(ii),p{jj}); end end end % extract anonymous function hFcns = cell(Nmdls,1); for kk=1:Nmdls for jj=1:numel(dFcnsSmodel{kk}) for ii=1:jj hFcns{kk}{ii,jj} = hFcnsSmodel{kk}(ii,jj).fitfunc; end end end end end % Set optimset in order to take care of eventual symbolic differentiation if ~isempty(dFcns) && any(strcmp(Algorithm,{'fminunc','fmincon'})) opts = optimset(opts, 'GradObj', 'on'); end if ~isempty(hFcns) && any(strcmp(Algorithm,{'fminunc','fmincon'})) opts = optimset(opts, 'Hessian', 'on'); end % Start the best-fit search if MCsearch utils.helper.msg(msg.IMPORTANT, 'performing a Monte Carlo search'); % find best-fit by a Monte Carlo search [P,chi2,exitflag,output,h,MChistory] = ... find_bestfit_montecarlo(lb, ub, Npoints, Noptims, opts, Algorithm, FastHess); else utils.helper.msg(msg.IMPORTANT, 'looking for a best-fit from the initial guess'); % find best-fit starting from an initial guess [P,chi2,exitflag,output,h,chainHistory] = ... find_bestfit_guess(P0, opts, Algorithm, FastHess); end % degrees of freedom in the problem % dof = Nch * (Ndata - Nfreeparams); dof = Nch * Ndata - Nfreeparams; % redefine MChistory's column to put the reduced chi2s if MCsearch MChistory(:,1) = MChistory(:,1)./dof; end % redefine history to put the reduced chi2s if ~MCsearch chainHistory.fval = chainHistory.fval./dof; end % Confidence intervals contruction if FitUnc utils.helper.msg(msg.IMPORTANT, 'estimating confidence intervals'); % find best-fit errors [se,Sigma,Corr,I,H,errH,J,errJ] = ... find_errors(modelFuncs, P, chi2, dof, UncMtd, FastHess, h, weights, unweighted, linUnc, dFcns); % report issue on covariance matrix in case of % degeneracy/quasi-singularity/ill-conditioning, etc. posDef = all(diag(Sigma)>=0); if ~posDef % analysis of information matrix in order to cancel out un-important % parameters Inorm=I./norm(I); pnorms = zeros(1,size(Inorm,2)); for ii=1:size(I,2); pnorms(ii) = norm(Inorm(:,ii)); end [pnorms_sort,IX] = sort(pnorms,'descend'); if Nmdls>1 pnames_sort = params(IX); elseif isa(targetFcn,'smodel') && Nmdls==1%&& ~isempty(targetFcn.params) params = targetFcn.params; pnames_sort = params(IX); elseif isa(targetFcn,'function_handle') %&& ~isempty(targetFcn.params) pnames_sort = pnames(IX); end utils.helper.msg(msg.IMPORTANT, ['Information matrix is quasi-singular due to degeneracy or ill-conditioning: \n'... 'consider eliminating the parameters with low information.']); % ...'In the following, parameters are reported in descending order of information']); for ii=1:numel(pnorms_sort); if exist('pnames_sort','var') utils.helper.msg(msg.IMPORTANT, 'param %s: %g ', pnames_sort{ii}, pnorms_sort(ii)); else utils.helper.msg(msg.IMPORTANT, 'param %d: %g ', IX(ii), pnorms_sort(ii)); end end end end utils.helper.msg(msg.IMPORTANT, 'final best-fit found at reduced chi2, dof: %g %d ', chi2/dof, dof); if FitUnc && ~isempty(se) for kk=1:numel(P) utils.helper.msg(msg.IMPORTANT, 'best-fit param %d: %g +- %2.1g ', kk, P(kk), se(kk)); end else for kk=1:numel(P) utils.helper.msg(msg.IMPORTANT, 'best-fit param %d: %g ', kk, P(kk)); end end % check the existence of all variables if ~exist('se','var'); se = []; end if ~exist('Sigma','var'); Sigma = []; end if ~exist('Corr','var'); Corr = []; end if ~exist('I','var'); I = []; end if ~exist('H','var'); H = []; end if ~exist('errH','var'); errH = []; end if ~exist('J','var'); J = []; end if ~exist('errJ','var'); errJ = []; end if ~exist('Sigma','var'); Sigma = []; end if ~exist('MChistory','var'); MChistory = []; end % if ~exist('SVDhistory','var'); SVDhistory = []; end if ~exist('output','var'); output = []; end % Make output pest out = pest; if Nch>1 %exist('params') out.setNames(params); elseif Nch==1 && isa(targetFcn, 'smodel') out.setNames(targetFcn.params); end out.setY(P'); out.setDy(se'); out.setCov(Sigma); out.setCorr(Corr); out.setChi2(chi2/dof); out.setDof(dof); if ~MCsearch out.setChain([chainHistory.fval,chainHistory.x]); end % add the output best-fit models outFcn = targetFcn; if isa(targetFcn, 'smodel') && Nmdls>1 for kk=1:Nmdls p = P(Pidx{kk}); outFcn(kk).setValues(p); % outFcn(kk).setXvals(Xdata(:,1)); outFcn(kk).setXunits(Xunits); outFcn(kk).setYunits(Yunits(kk)); outFcn(kk).setName(['Best-fit model ' num2str(kk)]); end out.setModels(outFcn); elseif isa(targetFcn, 'smodel') && Nmdls==1 outFcn.setValues(P'); % outFcn.setXvals(Xdata(:,1)); outFcn.setXunits(Xunits); outFcn.setYunits(Yunits); outFcn.setName('Best-fit model'); out.setModels(outFcn); elseif ischar(targetFcn) % convert regular expression into smodel targetFcn = regexprep(targetFcn, 'Xdata', 'x'); for ii=1:Nfreeparams targetFcn = regexprep(targetFcn, ['P\(' num2str(ii) '\)'], ['P' num2str(ii)]); end outFcn = smodel((targetFcn)); pnames = cell(1,Nfreeparams); for ii=1:Nfreeparams pnames{ii} = ['P' num2str(ii)]; end outFcn.setParams(pnames, P'); outFcn.setXvar('x'); % outFcn.setXvals(Xdata); outFcn.setXunits(Xunits); outFcn.setYunits(Yunits); outFcn.setName('Best-fit model'); out.setModels(outFcn); out.setNames(pnames); end % Set Name, History and Procinfo if ~cellMdl mdlname = char(targetFcn); if numel(mdlname)>20 mdlname = mdlname(1:20); end out.name = sprintf('xfit(%s)', mdlname); else mdlname = func2str(targetFcn{1}); for kk=2:Nch mdlname = strcat(mdlname,[',' func2str(targetFcn{kk})]); end out.name = sprintf('xfit(%s)', mdlname); end out.addHistory(getInfo('None'), pl, ao_invars(:), [as(:).hist]); out.procinfo = plist('algorithm', Algorithm, 'exitflag', exitflag, 'output', output, ... 'InfoMatrix', I,... 'MChistory', MChistory); % 'SVDhistory', SVDhistory, 'res', res, 'hess', H, 'errhess', errH, 'jac', J, 'errjac', errJ); % Set outputs if nargout > 0 varargout{1} = out; end clear global Pidx Ydata weights modelFuncs dFcns hFcns lb ub scale Nch Ndata estimator end %-------------------------------------------------------------------------- % Included Functions %-------------------------------------------------------------------------- function [chi2,g,H] = fit_chi2(x) global Pidx Ydata weights modelFuncs dFcns hFcns lb ub scale estimator Nfreeparams = numel(x); Nmdls = numel(modelFuncs); Ndata = numel(Ydata(:,1)); mdldata = zeros(Ndata,Nmdls); for kk=1:Nmdls p = x(Pidx{kk}).*scale(Pidx{kk}); mdldata(:,kk) = modelFuncs{kk}(p); end res = (mdldata-Ydata).*weights; if all(x.*scale>=lb & x.*scale<=ub) % chi2 = res'*res; % chi2 = sum(diag(chi2)); if strcmp(estimator,'chi2') chi2 = sum(sum(res.^2)); elseif strcmp(estimator,'abs') chi2 = sum(sum(abs(res))); elseif strcmp(estimator,'log') chi2 = sum(sum(log(1+res.^2/2))); elseif strcmp(estimator,'median') dof = Ndata*Nmdls - Nfreeparams; res = reshape(res,numel(res),1); chi2 = median(abs(res))*dof; end else chi2 = 10e50; end if nargout > 1 % gradient required % if all(x.*scale>=lb & x.*scale<=ub) grad = cell(Nmdls,1); g = zeros(Nmdls,Nfreeparams); for kk=1:Nmdls p = x(Pidx{kk}).*scale(Pidx{kk}); Np = numel(p); grad{kk} = zeros(Ndata,Np); for ii=1:Np grad{kk}(:,ii) = dFcns{kk}{ii}(p); g(kk,Pidx{kk}(ii)) = 2.*res(:,kk)'*grad{kk}(:,ii); % dF=dFcns{kk}{ii}(p); % g(kk,Pidx{kk}(ii)) = 2.*sum(res(:,kk)'*dF); end end if Nmdls>1 g = sum(g); end % elseif any(x.*scale<lb) % g = repmat(-10e50,[1 Nfreeparams]); % elseif any(x.*scale>ub) % g = repmat(10e50,[1 Nfreeparams]); % end end if nargout > 2 % hessian required % hess = cell(Nmdls,1); H = zeros(Nmdls,Nfreeparams,Nfreeparams); for kk=1:Nmdls p = x(Pidx{kk}).*scale(Pidx{kk}); Np = numel(p); % hess{kk} = zeros(Ndata,Np); for jj=1:Np for ii=1:jj hF = hFcns{kk}{ii,jj}(p); H1 = sum(res(:,kk)'*hF); H2 = sum(weights(:,kk).*grad{kk}(:,ii).*grad{kk}(:,jj)); H(kk,Pidx{kk}(ii),Pidx{kk}(jj)) = 2*(H1+H2); end end end H = squeeze(sum(H,1)); % if Nmdls>1 % H = sum(H); % end H = H+triu(H,1)'; end end %-------------------------------------------------------------------------- % function chi2 = fit_chi2(P, ydata, weights, fcn) % % % evaluate model % mdldata = fcn(P); % % if ~isequal(size(mdldata), size(ydata)) % % ydata = ydata.'; % % end % % chi2 = sum((abs((mdldata-ydata)).^2).*weights); % % end %-------------------------------------------------------------------------- % function chi2 = fit_chi2_bounds(P, ydata, weights, fcn, LB, UB) % % % evaluate model % mdldata = fcn(P); % % if ~isequal(size(mdldata), size(ydata)) % % ydata = ydata.'; % % end % % if all(P>=LB & P<=UB) % chi2 = sum((abs((mdldata-ydata)).^2).*weights); % else % chi2 = 10e50; % end % % end %-------------------------------------------------------------------------- % function g = scaling(f, x, scale) % % g = f(x.*scale); % % end %-------------------------------------------------------------------------- function scale = find_scale(P0, LB, UB) if ~isempty(P0) Nfreeparams = numel(P0); else Nfreeparams = numel(LB); end % define parameter scale % scale = ones(1,Nfreeparams); % if ~isempty(LB) & ~isempty(UB) % for ii=1:Nfreeparams % if ~(LB(ii)==0 & UB(ii)==0) % if LB(ii)==0 | UB(ii)==0 % scale(ii) = max(abs(LB(ii)),abs(UB(ii))); % elseif abs(LB(ii))==abs(UB(ii)) % scale(ii) = abs(UB); % elseif LB(ii)~=UB(ii) % scale(ii) = sqrt(abs(LB(ii)) * abs(UB(ii))); % (abs(lb(ii)) + abs(ub(ii)))/2; % end % end % end % end if ~isempty(LB) && ~isempty(UB) scale = sqrt(abs(LB) .* abs(UB)); for ii=1:Nfreeparams if scale(ii)==0 scale(ii) = max(abs(LB(ii)),abs(UB(ii))); end end else scale = abs(P0); for ii=1:Nfreeparams if scale(ii)==0 scale(ii) = 1; end end end end %-------------------------------------------------------------------------- function [weightsOut,unweighted] = find_weights(as,weightsIn) Ydata = as.y; dYdata = as.dy; noWeights = isempty(weightsIn); noDY = isempty(dYdata); unweighted = noWeights & noDY; % check data uncertainties if any(dYdata==0) && ~noDY error('### some of the data uncertainties are zero: cannot fit') end if length(dYdata)~=length(Ydata) && ~noDY error('### length of Y fields and dY fields do not match') end if noWeights % The user did not input weights. % Looking for the dy field of the input data if noDY % Really no input from user weightsOut = ones(size(Ydata)); else % Uses uncertainties in Ydata to evaluate data weights weightsOut = 1./dYdata.^2; end % elseif isnumeric(weightsIn) % if size(weightsIn)~=size(Ydata) % weightsOut = weightsIn'; % end elseif numel(weightsIn) == 1 if isnumeric(weightsIn) weightsOut = weightsIn .* ones(size(Ydata)); elseif isa(weightsIn, 'ao') if weightsIn.yunits == as.yunits weightsOut = weightsIn.y; elseif size(weightsIn.data.getY)~=size(Ydata) error('### size of data ao and weights ao do not match') else error('### units for data and uncertainties do not match') end else error('### unknown format for weights parameter'); end else weightsOut = weightsIn; end end %-------------------------------------------------------------------------- function [params,mdl_params,paramidx] = cat_mdls(mdls) % This function concatenates the parameter names and values for % all the input models to construct the new parameter list. Nmdls = numel(mdls); % create the full parameter name array params_cat = horzcat(mdls(:).params); params_new = cell(1); for ii=1:numel(params_cat) if ~strcmp(params_cat{ii},params_new) params_new = [params_new params_cat{ii}]; end end params = params_new(2:numel(params_new)); % create the parameter list for each model for kk=1:Nmdls mdl_params{kk} = mdls(kk).params; end % create a cell array of indeces which map the parameter vector of each % model to the full one paramidx = cell(1,Nmdls); for kk=1:Nmdls for jdx=1:length(mdl_params{kk}) for idx=1:length(params) if (strcmp(params{idx}, mdl_params{kk}(jdx))) paramidx{kk}(jdx) = idx; break; else paramidx{kk}(jdx) = 0; end end end end end %-------------------------------------------------------------------------- function [params,mdl_params,paramidx] = cat_mdls_cell(mdls, pnames) % This function concatenates the parameter names and values for % all the input models to construct the new parameter list. Nmdls = numel(mdls); % create the full parameter name array params_cat = horzcat(pnames{:}); params_new = cell(1); for ii=1:numel(params_cat) if ~strcmp(params_cat{ii},params_new) params_new = [params_new params_cat{ii}]; end end params = params_new(2:numel(params_new)); % create the parameter list for each model for kk=1:Nmdls mdl_params{kk} = pnames{kk}; end % create a cell array of indeces which map the parameter vector of each % model to the full one paramidx = cell(1,Nmdls); for kk=1:Nmdls for jdx=1:length(mdl_params{kk}) for idx=1:length(params) if (strcmp(params{idx}, mdl_params{kk}(jdx))) paramidx{kk}(jdx) = idx; break; else paramidx{kk}(jdx) = 0; end end end end end %-------------------------------------------------------------------------- % function chi2 = fit_chi2_multich(P, Pidx, ydata, weights, fcns) % % Nmdls = numel(fcns); % Ndata = numel(ydata(:,1)); % % mdldata = zeros(Ndata,Nmdls); % for kk=1:Nmdls % p = P(Pidx{kk}); % mdldata(:,kk) = fcns{kk}(p); % end % % res = (ydata-mdldata).*weights; % % chi2 = res'*res; % chi2 = sum(diag(chi2)); % % end %-------------------------------------------------------------------------- % function chi2 = fit_chi2_multich_bounds(P, Pidx, ydata, weights, fcns, LB, UB) % % if all(P>=LB & P<=UB) % % Nmdls = numel(fcns); % Ndata = numel(ydata(:,1)); % % mdldata = zeros(Ndata,Nmdls); % for kk=1:Nmdls % p = P(Pidx{kk}); % mdldata(:,kk) = fcns{kk}(p); % end % % res = (ydata-mdldata).*weights; % % chi2 = res'*res; % chi2 = sum(diag(chi2)); % % else % chi2 = 10e50; % end % % end %-------------------------------------------------------------------------- function chi2_guesses = montecarlo(func, LB, UB, Npoints) Nfreeparams = numel(LB); % construct the guess matrix: each rows contain the chi2 and the % parameter guess guesses = zeros(Npoints,Nfreeparams); for jj=1:Nfreeparams guesses(:,jj) = LB(jj)+(UB(jj)-LB(jj))*rand(1,Npoints); end % evaluate the chi2 at each guess chi2_guesses = zeros(1,Nfreeparams+1); for ii=1:Npoints % check = ii-1/Npoints*100; % if rem(fix(check),10)==0 && check==fix(check) % % disp(sprintf('%d%% done',check)); % end chi2_guess = func(guesses(ii,:)); chi2_guesses = cat(1,chi2_guesses,[chi2_guess,guesses(ii,:)]); end chi2_guesses(1,:) = []; % sort the guesses for ascending chi2 chi2_guesses = sortrows(chi2_guesses); end %-------------------------------------------------------------------------- function [x,fval,exitflag,output,h,MChistory] = ... find_bestfit_montecarlo(LB, UB, Npoints, Noptims, opts, algorithm, FastHess) global scale Nfreeparams = length(LB); % check Npoints if isempty(Npoints) Npoints = 100000; end % check Noptims if isempty(Noptims) Noptims = 10; end if Npoints<Noptims error('### Npoints must be at least equal to Noptims') end % define parameter scale scale = find_scale([], LB, UB); % scale function to minimize % func = @(x)scaling(func, x, scale); % scale bounds LB = LB./scale; UB = UB./scale; % do a Monte Carlo search chi2_guesses = montecarlo(@fit_chi2, LB, UB, Npoints); % minimize over the first guesses fitresults = zeros(Noptims,Nfreeparams+1); exitflags = {}; outputs = {}; hs = {}; for ii=1:Noptims P0 = chi2_guesses(ii,2:Nfreeparams+1); if strcmpi(algorithm,'fminunc') [x,fval,exitflag,output,dummy,h] = fminunc(@fit_chi2, P0, opts); elseif strcmpi(algorithm,'fmincon') [x,fval,exitflag,output,dummy,dummy,h] = fmincon(@fit_chi2, P0, [], [], [], [], LB, UB, [], opts); elseif strcmpi(algorithm,'patternsearch') [x,fval,exitflag,output] = patternsearch(@fit_chi2, P0, [], [], [], [], LB, UB, [], opts); elseif strcmpi(algorithm,'ga') [x,fval,exitflag,output] = ga(@fit_chi2, numel(P0), [], [], [], [], LB, UB, [], opts); elseif strcmpi(algorithm,'simulannealbnd') [x,fval,exitflag,output] = simulannealbnd(@fit_chi2, P0, LB, UB, opts); else [x,fval,exitflag,output] = fminsearch(@fit_chi2, P0, opts); end fitresults(ii,1) = fval; fitresults(ii,2:Nfreeparams+1) = x; exitflags{ii} = exitflag; outputs{ii} = output; if FastHess && ~exist('h','var') h = fastHessian(@fit_chi2,x,fval); end if exist('h','var') hs{ii} = h; end end % sort the results [dummy, ix] = sort(fitresults(:,1)); fitresults = fitresults(ix,:); % % refine fit over the first bestfit % P0 = fitresults(1,2:Nfreeparams+1); % if strcmpi(algorithm,'fminunc') % [x,fval,exitflag,output] = fminunc(func, P0, opts); % elseif strcmpi(algorithm,'fmincon') % [x,fval,exitflag,output] = fmincon(func, P0, [], [], [], [], LB, UB, [], opts); % elseif strcmpi(algorithm,'patternsearch') % [x,fval,exitflag,output] = patternsearch(func, P0, [], [], [], [], LB, UB, [], opts); % elseif strcmpi(algorithm,'ga') % [x,fval,exitflag,output] = ga(func, numel(P0), [], [], [], [], LB, UB, [], opts); % ga does not improve the bestfit % elseif strcmpi(algorithm,'simulannealbnd') % [x,fval,exitflag,output] = simulannealbnd(func, P0, LB, UB, opts); % else % [x,fval,exitflag,output] = fminsearch(func, P0, opts); % end % set the best-fit fval = fitresults(1,1); x = fitresults(1,2:Nfreeparams+1); % scale best-fit x = x.*scale; % scale hessian if exist('hs','var') for kk=1:numel(hs) for ii=1:numel(x) for jj=1:numel(x) hs{kk}(ii,jj) = hs{kk}(ii,jj)/scale(ii)/scale(jj); end end end % else % h = []; end % scale fit results MChistory = fitresults; for ii=1:size(MChistory,1) MChistory(ii,2:Nfreeparams+1) = MChistory(ii,2:Nfreeparams+1).*scale; end % set the proper exitflag & output exitflags = exitflags(ix); outputs = outputs(ix); if exist('hs','var') && ~(isempty(hs)) hs = hs(ix); h = hs{1}; else h = []; end exitflag = exitflags{1}; output = outputs{1}; clear global scale end %-------------------------------------------------------------------------- function [x,fval,exitflag,output,h,outHistory] = ... find_bestfit_guess(P0, opts, algorithm, FastHess) global lb ub scale chainHistory if isempty(lb) lb = -Inf; end if isempty(ub) ub = Inf; end % define parameter scale scale = find_scale(P0, [], []); % scale initial guess P0 = P0./scale; % initialize history chainHistory.x = []; chainHistory.fval = []; opts=optimset(opts,'outputfcn',@outfun); % minimize over the initial guess if strcmpi(algorithm,'fminunc') [x,fval,exitflag,output,dummy,h] = fminunc(@fit_chi2, P0, opts); elseif strcmpi(algorithm,'fmincon') [x,fval,exitflag,output,dummy,dummy,h] = fmincon(@fit_chi2, P0, [], [], [], [], lb, ub, [], opts); elseif strcmpi(algorithm,'patternsearch') [x,fval,exitflag,output] = patternsearch(@fit_chi2, P0, [], [], [], [], lb, ub, [], opts); elseif strcmpi(algorithm,'ga') [x,fval,exitflag,output] = ga(@fit_chi2, numel(P0), [], [], [], [], lb, ub, [], opts); elseif strcmpi(algorithm,'simulannealbnd') [x,fval,exitflag,output] = simulannealbnd(@fit_chi2, P0, lb, ub, opts); else [x,fval,exitflag,output] = fminsearch(@fit_chi2, P0, opts); end if FastHess && ~exist('h','var') h = fastHessian(@fit_chi2,x,fval); end % scale best-fit x = x.*scale; % scale hessian if exist('h','var') for ii=1:numel(x) for jj=1:numel(x) h(ii,jj) = h(ii,jj)/scale(ii)/scale(jj); end end else h = []; end outHistory = chainHistory; outHistory.x = chainHistory.x.*repmat(scale,size(chainHistory.x,1),1); clear global lb ub scale chainHistory end %-------------------------------------------------------------------------- function stop = outfun(x,optimvalues,state) global chainHistory stop = false; if strcmp(state,'iter') chainHistory.fval = [chainHistory.fval; optimvalues.fval]; chainHistory.x = [chainHistory.x; x]; end end %-------------------------------------------------------------------------- function hess = fastHessian(fun,x0,fx0) % fastHessian calculates the numerical Hessian of fun evaluated at x % using finite differences. nVars = numel(x0); hess = zeros(nVars); % Define stepsize stepSize = eps^(1/4)*sign(x0).*max(abs(x0),1); % Min e Max change DiffMinChange = 1e-008; DiffMaxChange = 0.1; % Make sure step size lies within DiffMinChange and DiffMaxChange stepSize = sign(stepSize+eps).*min(max(abs(stepSize),DiffMinChange),DiffMaxChange); % Calculate the upper triangle of the finite difference Hessian element % by element, using only function values. The forward difference formula % we use is % % Hessian(i,j) = 1/(h(i)*h(j)) * [f(x+h(i)*ei+h(j)*ej) - f(x+h(i)*ei) % - f(x+h(j)*ej) + f(x)] (2) % % The 3rd term in (2) is common within each column of Hessian and thus % can be reused. We first calculate that term for each column and store % it in the row vector fplus_array. fplus_array = zeros(1,nVars); for j = 1:nVars xplus = x0; xplus(j) = x0(j) + stepSize(j); % evaluate fplus = fun(xplus); fplus_array(j) = fplus; end for i = 1:nVars % For each row, calculate the 2nd term in (4). This term is common to % the whole row and thus it can be reused within the current row: we % store it in fplus_i. xplus = x0; xplus(i) = x0(i) + stepSize(i); % evaluate fplus_i = fun(xplus); for j = i:nVars % start from i: only upper triangle % Calculate the 1st term in (2); this term is unique for each element % of Hessian and thus it cannot be reused. xplus = x0; xplus(i) = x0(i) + stepSize(i); xplus(j) = xplus(j) + stepSize(j); % evaluate fplus = fun(xplus); hess(i,j) = (fplus - fplus_i - fplus_array(j) + fx0)/(stepSize(i)*stepSize(j)); end end % Fill in the lower triangle of the Hessian hess = hess + triu(hess,1)'; end %-------------------------------------------------------------------------- % function [x,fval,exitflag,output,h,SVDhistory] = ... % find_bestfit_guess_SVD(P0, lb, ub, opts, algorithm, FastHess, nSVD) % % if isempty(lb) % lb = -Inf; % end % if isempty(ub) % ub = Inf; % end % % % define parameter scale % scale = find_scale(P0, [], []); % % % % scale function to minimize % % func = @(x)scaling(func, x, scale); % % % scale initial guess % P0 = P0./scale; % % % scale bounds % if ~isempty(lb) % lb = lb./scale; % end % if ~isempty(ub) % ub = ub./scale; % end % % % set the guess for 0-iteration % x = P0; % % Nfreeparams = numel(P0); % % fitresults = zeros(nSVD,Nfreeparams+1); % exitflags = {}; % outputs = {}; % hs = {}; % % % start SVD loop % for ii=1:nSVD % % % minimize over the old parameter space % if strcmpi(algorithm,'fminunc') % [x,fval,exitflag,output,grad,h] = fminunc(@fit_chi2, x, opts); % elseif strcmpi(algorithm,'fmincon') % [x,fval,exitflag,output,lambda,grad,h] = fmincon(@fit_chi2, x, [], [], [], [], lb, ub, [], opts); % elseif strcmpi(algorithm,'patternsearch') % [x,fval,exitflag,output] = patternsearch(@fit_chi2, P0, [], [], [], [], lb, ub, [], opts); % elseif strcmpi(algorithm,'ga') % [x,fval,exitflag,output] = ga(@fit_chi2, numel(P0), [], [], [], [], lb, ub, [], opts); % elseif strcmpi(algorithm,'simulannealbnd') % [x,fval,exitflag,output] = simulannealbnd(@fit_chi2, P0, lb, ub, opts); % else % [x,fval,exitflag,output] = fminsearch(@fit_chi2, P0, opts); % end % % if FastHess && ~exist('h','var') % h = fastHessian(func,x,fval); % end % % % compute Jacobian % J = zeros(Nch*Ndata,Np); % for kk=1:Nch % for ll=1:Np % J(((ii-1)*Ndata+1):ii*Ndata,ll) = dFcns{kk}{ll}(x.*scale); % end % end % % % take SVD decomposition of hessian % [U,S,V] = svd(J); % % % cancel out column with very low information % thresh = 100*eps; % idx = (diag(S)./norm(diag(S)))<=thresh; % pos = find(idx~=0); % % % reduced matrix % Sred = S; % Ured = U; % Vred = V; % Sred(:,pos) = []; % Sred(pos,:) = []; % Ured(:,pos) = []; % Ured(pos,:) = []; % Vred(:,pos) = []; % Vred(pos,:) = []; % % % % start from here inserting norm-rescaling... also in funcSVD! % % % guess in the new parameter space % % pay attention because here x is row-vector and xSVD ic column-vector % xSVD = U'*x'; % xSVD(pos) = []; % % % % bounds in the new parameter space % % if ~isempty(lb) && ~isempty(ub) % % lbSVD = U'*lb'; % % ubSVD = U'*ub'; % % end % % % do the change in the variables % funcSVD = @(xSVD)func_SVD(func,xSVD,U,pos); % % % minimize over the new parameter space % if strcmpi(algorithm,'fminunc') % [xSVD,fvalSVD,exitflag,output,grad,hSVD] = fminunc(funcSVD, xSVD, opts); % elseif strcmpi(algorithm,'fmincon') % [xSVD,fvalSVD,exitflag,output,lambda,grad,hSVD] = fmincon(funcSVD, xSVD, [], [], [], [], [], [], [], opts); % elseif strcmpi(algorithm,'patternsearch') % [xSVD,fvalSVD,exitflag,output] = patternsearch(funcSVD, xSVD, [], [], [], [], [], [], [], opts); % elseif strcmpi(algorithm,'ga') % [xSVD,fvalSVD,exitflag,output] = ga(funcSVD, numel(xSVD), [], [], [], [], [], [], [], opts); % elseif strcmpi(algorithm,'simulannealbnd') % [xSVD,fvalSVD,exitflag,output] = simulannealbnd(funcSVD, xSVD, [], [], opts); % else % [xSVD,fvalSVD,exitflag,output] = fminsearch(funcSVD, xSVD, opts); % end % % if FastHess && ~exist('hSVD','var') % hSVD = fastHessian(funcSVD,xSVD,fvalSVD); % end % % % if fvalSVD<fval % % % take the new % % x = (U'*xSVD)'; % % fval = fvalSVD; % % % trasformare la nuova h nella vecchia % % h = U'*hSVD; % % else % % % take the old % % end % % % return to the full parameter vector % xSVDfull = zeros(numel(xSVD)+numel(pos),1); % setVals = setdiff(1:numel(xSVDfull),pos); % xSVDfull(setVals) = xSVD; % % x = xb; % % % back to the old parameter space % x = (U*xSVDfull)'; % fval = fvalSVD; % hRed = Ured*hSVD*Vred'; % h = hRed; % for kk=1:numel(pos) % h(pos(kk),:) = zeros(1,size(h,2)); % h(:,pos(kk)) = zeros(size(h,1),1); % end % % fitresults(ii,1) = fval; % fitresults(ii,2:Nfreeparams+1) = x; % exitflags{ii} = exitflag; % outputs{ii} = output; % hs{ii} = h; % % end % % % sort the results % [chi2s, ix] = sort(fitresults(:,1)); % fitresults = fitresults(ix,:); % % % set the best-fit % fval = fitresults(1,1); % x = fitresults(1,2:Nfreeparams+1); % % % scale best-fit % x = x.*scale; % % % scale hessian % for ii=1:numel(x) % for jj=1:numel(x) % h(ii,jj) = h(ii,jj)/scale(ii)/scale(jj); % end % end % % % scale fit results % SVDhistory = fitresults; % for ii=1:size(SVDhistory,1) % SVDhistory(ii,2:Nfreeparams+1) = SVDhistory(ii,2:Nfreeparams+1).*scale; % end % % % set the proper exitflag & output % exitflags = exitflags(ix); % outputs = outputs(ix); % hs = hs(ix); % exitflag = exitflags{1}; % output = outputs{1}; % h = hs{1}; % % end %-------------------------------------------------------------------------- % function z = func_SVD(func,y,U,pos) % % yFull = zeros(numel(y)+numel(pos),1); % setVals = setdiff(1:numel(yFull),pos); % yFull(setVals) = y; % % x = (U*yFull)'; % % z = func(x); % % end %-------------------------------------------------------------------------- % function adaptive_scaling(P0, scaleIn, func, Niter, opts, algorithm) % % Nfreeparams = numel(P0); % % for ii=1:Niter % % % new guess % P0_new = P0; % % new scale % scale = find_scale(P0, [], []); % % % scale bounds % LB = LB./scale; % UB = UB./scale; % % % if ~multiCh % % rescale model % modelFunc = @(x)scaling(modelFunc, x, scale_new./scale); % % % define function to minimize % if isempty(lb) & isempty(ub) % func = @(x)fit_chi2(x, Ydata, weights, modelFunc); % else % lb = lb.*scale./scale_new; % ub = ub.*scale./scale_new; % func = @(x)fit_chi2_bounds(x, Ydata, weights, modelFunc, lb, ub); % end % % % else % % % func = @(x)fit_chi2_multich(params, x, Ydata, mdl_params, modelFuncs); % % func = @(x)scaling(func, x, scale_new/scale); % % end % % % Make new fit % [x,fval_new,exitflag,output] = fminsearch(func, P0_new./scale_new, opts); % % stopping criterion % if abs(fval_new-fval) <= 3*eps(fval_new-fval) % fval = fval_new; % scale = scale_new; % break % end % fval = fval_new; % scale = scale_new; % end % end % % end %-------------------------------------------------------------------------- function [se,Sigma,Corr,I,H,errH,J,errJ] = find_errors(modelFcn, x, fval, dof, UncMtd, FastHess, h, weights, unweighted, linUnc, dFcns) global scale Nch Ndata lb ub % set infinite bounds lb = repmat(-Inf,size(x)); ub = repmat(Inf,size(x)); % check on UncMtd and FastHess if strcmpi(UncMtd,'jacobian') FastHess = 0; elseif FastHess==1 && isempty(h) FastHess = 0; end Nfreeparams = numel(x); if ~FastHess % find new scale based on the best-fit found scale = find_scale(x, [], []); else scale = ones(1,Nfreeparams); end % scale best-fit x = x./scale; % standard deviation of the residuals sdr = sqrt(fval/dof); % compute information matrix from linear approximation: error propagation % from symbolical gradient if linUnc % compute Jacobian J = zeros(Nch*Ndata,Nfreeparams); for ii=1:Nch for ll=1:Nfreeparams J(((ii-1)*Ndata+1):ii*Ndata,ll) = dFcns{ii}{ll}(x.*scale); end end elseif ~strcmpi(UncMtd,'jacobian') || numel(modelFcn)~=1 && ~linUnc % Hessian method. % Brief description. % Here I use the most general method: Hessian of chi2 function. For further readings see % Numerical Recipes. Note that for inv, H should be non-singular. % Badly-scaled matrices may take to singularities: use Cholesky % factorization instead. % In practice, a singular Hessian matrix may go out for the following main reasons: % 1 - you have reached a false minimum (H should be strictly % positive-definite for a well-behaved minimum); % 2 - strong correlation between some parameters; % 3 - independency of the model on some parameters. % In the last two cases you should reduce the dimensionality of the % problem. % One last point discussed here in Trento is that the Hessian method reduces % to the Jacobian, when considering linear models. % Feedbacks are welcome. % Trento, Giuseppe Congedo if ~FastHess % scale function to find the hessian % func = @(x)scaling(func, x, scale); [H,errH] = hessian(@fit_chi2,x); elseif ~isempty(h) && FastHess % here the hessian is not scaled H = h; end if unweighted % information matrix based on sdr I = H / sdr^2 / 2; else % information matrix based on canonical form I = H./2; end elseif ~linUnc % % scale function to find the jacobian % modelFcn = @(x)scaling(modelFcn, x, scale); % Jacobian method [J,errJ] = jacobianest(modelFcn{1},x); end if exist('J','var') if unweighted % information matrix based on sdr I = J'*J ./ sdr^2; else % redefine the jacobian to take care of the weights if size(weights)~=size(J(:,1)) weights = weights'; end for ii=1:size(J,2) J(:,ii) = sqrt(weights).*J(:,ii); end % information matrix based on jacobian I = J'*J; end end % check positive-definiteness % [R,p] = chol(I); % posDef = p==0; % Covariance matrix from inverse Sigma = inv(I); % % Covariance matrix from pseudo-inverse % if issparse(I) % % [U,S,V] = svds(I,Nfreeparams); % % I = full(I); % I = full(I); % [U,S,V] = svd(I); % else % [U,S,V] = svd(I); % end % Sigma = V/S*U'; % Does the covariance give proper positive-definite variances? posDef = all(diag(Sigma)>=0); if posDef % Compute correlation matrix Corr = zeros(size(Sigma)); for ii=1:size(Sigma,1) for jj=1:size(Sigma,2) Corr(ii,jj) = Sigma(ii,jj) / sqrt(Sigma(ii,ii)) / sqrt(Sigma(jj,jj)); end end % Parameter standard errors se = sqrt(diag(Sigma))'; % rescale errors and covariance matrix if ~FastHess && ~linUnc % otherwise H is already scaled in find_bestfit function se = se.*scale; for ii=1:Nfreeparams for jj=1:Nfreeparams Sigma(ii,jj) = Sigma(ii,jj)*scale(ii)*scale(jj); end end end end % rescale information matrix if ~FastHess && ~linUnc % otherwise H is already scaled in find_bestfit function for ii=1:Nfreeparams for jj=1:Nfreeparams I(ii,jj) = I(ii,jj)/scale(ii)/scale(jj); end end end if ~exist('se','var'); se = []; end if ~exist('Sigma','var'); Sigma = []; end if ~exist('Corr','var'); Corr = []; end if ~exist('I','var'); I = []; end if ~exist('H','var'); H = []; end if ~exist('errH','var'); errH = []; end if ~exist('J','var'); J = []; end if ~exist('errJ','var'); errJ = []; end clear global scale 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: xfit.m,v 1.83 2011/05/12 07:46:29 mauro Exp $', sets, pl); ii.setModifier(false); 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(); % Function p = param({'Function', 'The function (or symbolic model <tt>smodel</tt>) of Xdata that you want to fit. For example: ''P(1)*Xdata''.'}, paramValue.EMPTY_STRING); pl.append(p); % Weights p = param({'Weights', ['An array of weights, one value per X sample.<br>'... 'By default, <tt>xfit</tt> takes data uncertainties from the dy field of the input object.'... 'If provided, <tt>weights</tt> make <tt>xfit</tt> ignore these.'... 'Otherwise <tt>weights</tt> will be considered as ones.']}, paramValue.EMPTY_STRING); pl.append(p); % P0 p = param({'P0', ['An array of starting guesses for the parameters.<br>'... 'This is not necessary if you fit with <tt>smodel</tt>s; in that case the parameter'... 'values from the <tt>smodel</tt> are taken as the initial guess.']}, paramValue.EMPTY_DOUBLE); pl.append(p); % % ADDP % p = param({'ADDP', 'A cell-array of additional parameters to pass to the function. These will not be fit.'}, {1, {'{}'}, paramValue.OPTIONAL}); % pl.append(p); % LB p = param({'LB', ['Lower bounds for the parameters.<br>'... 'This improves the convergency. Mandatory for Monte Carlo.<br>'... 'For multichannel fitting it has to be a cell-array.']}, paramValue.EMPTY_DOUBLE); pl.append(p); % UB p = param({'UB', ['Upper bounds for the parameters.<br>'... 'This improves the convergency. Mandatory for Monte Carlo.<br>'... 'For multichannel fitting it has to be a cell-array.']}, paramValue.EMPTY_DOUBLE); pl.append(p); % Algorithm p = param({'ALGORITHM', ['A string defining the fitting algorithm.<br>'... '<tt>fminunc</tt>, <tt>fmincon</tt> require ''Optimization Toolbox'' to be installed.<br>'... '<tt>patternsearch</tt>, <tt>ga</tt>, <tt>simulannealbnd</tt> require ''Genetic Algorithm and Direct Search'' to be installed.<br>']}, ... {1, {'fminsearch', 'fminunc', 'fmincon', 'patternsearch', 'ga', 'simulannealbnd'}, paramValue.SINGLE}); pl.append(p); % OPTSET p = param({'OPTSET', ['An optimisation structure to pass to the fitting algorithm.<br>'... 'See <tt>fminsearch</tt>, <tt>fminunc</tt>, <tt>fmincon</tt>, <tt>optimset</tt>, for details.<br>'... 'See <tt>patternsearch</tt>, <tt>psoptimset</tt>, for details.<br>'... 'See <tt>ga</tt>, <tt>gaoptimset</tt>, for details.<br>'... 'See <tt>simulannealbnd</tt>, <tt>saoptimset</tt>, for details.']}, paramValue.EMPTY_STRING); pl.append(p); % FitUnc p = param({'FitUnc', 'Fit parameter uncertainties or not.'}, paramValue.YES_NO); pl.append(p); % UncMtd p = param({'UncMtd', ['Choose the uncertainties estimation method.<br>'... 'For multi-channel fitting <tt>hessian</tt> is mandatory.']}, {1, {'hessian', 'jacobian'}, paramValue.SINGLE}); pl.append(p); % FastHess p = param({'FastHess', ['Choose whether or not the hessian should be estimated from a fast-forward finite differences algorithm; '... 'use this method to achieve better performance in CPU-time, but accuracy is not ensured; '... 'otherwise, the default (computer expensive, but more accurate) method will be used.']}, paramValue.NO_YES); pl.append(p); % MonteCarlo p = param({'MonteCarlo', ['Do a Monte Carlo search in the parameter space.<br>'... 'Useful when dealing with high multiplicity of local minima. May be computer-expensive.<br>'... 'Note that, if used, P0 will be ignored. It also requires to define LB and UB.']}, paramValue.NO_YES); pl.append(p); % Npoints p = param({'Npoints', 'Set the number of points in the parameter space to be extracted.'}, 100000); pl.append(p); % Noptims p = param({'Noptims', 'Set the number of optimizations to be performed after the Monte Carlo.'}, 10); pl.append(p); % estimator p = param({'estimator', ['Choose the robust local M-estimate as the statistical estimator of the fit:<br>'... ' ''chi2'' (least square) for data with normal distribution;<br>'... ' ''abs'' (mean absolute deviation) for data with double exponential distribution;<br>'... ' ''log'' (log least square) for data with Lorentzian distribution.']}, ... {1, {'chi2', 'abs', 'log'}, paramValue.SINGLE}); pl.append(p); end % END %-------------------------------------------------------------------------- % Author: John D'Errico % e-mail: woodchips@rochester.rr.com %-------------------------------------------------------------------------- % DERIVEST SUITE %-------------------------------------------------------------------------- function [der,errest,finaldelta] = derivest(fun,x0,varargin) % DERIVEST: estimate the n'th derivative of fun at x0, provide an error estimate % usage: [der,errest] = DERIVEST(fun,x0) % first derivative % usage: [der,errest] = DERIVEST(fun,x0,prop1,val1,prop2,val2,...) % % Derivest will perform numerical differentiation of an % analytical function provided in fun. It will not % differentiate a function provided as data. Use gradient % for that purpose, or differentiate a spline model. % % The methods used by DERIVEST are finite difference % approximations of various orders, coupled with a generalized % (multiple term) Romberg extrapolation. This also yields % the error estimate provided. DERIVEST uses a semi-adaptive % scheme to provide the best estimate that it can by its % automatic choice of a differencing interval. % % Finally, While I have not written this function for the % absolute maximum speed, speed was a major consideration % in the algorithmic design. Maximum accuracy was my main goal. % % % Arguments (input) % fun - function to differentiate. May be an inline function, % anonymous, or an m-file. fun will be sampled at a set % of distinct points for each element of x0. If there are % additional parameters to be passed into fun, then use of % an anonymous function is recommended. % % fun should be vectorized to allow evaluation at multiple % locations at once. This will provide the best possible % speed. IF fun is not so vectorized, then you MUST set % 'vectorized' property to 'no', so that derivest will % then call your function sequentially instead. % % Fun is assumed to return a result of the same % shape as its input x0. % % x0 - scalar, vector, or array of points at which to % differentiate fun. % % Additional inputs must be in the form of property/value pairs. % Properties are character strings. They may be shortened % to the extent that they are unambiguous. Properties are % not case sensitive. Valid property names are: % % 'DerivativeOrder', 'MethodOrder', 'Style', 'RombergTerms' % 'FixedStep', 'MaxStep' % % All properties have default values, chosen as intelligently % as I could manage. Values that are character strings may % also be unambiguously shortened. The legal values for each % property are: % % 'DerivativeOrder' - specifies the derivative order estimated. % Must be a positive integer from the set [1,2,3,4]. % % DEFAULT: 1 (first derivative of fun) % % 'MethodOrder' - specifies the order of the basic method % used for the estimation. % % For 'central' methods, must be a positive integer % from the set [2,4]. % % For 'forward' or 'backward' difference methods, % must be a positive integer from the set [1,2,3,4]. % % DEFAULT: 4 (a second order method) % % Note: higher order methods will generally be more % accurate, but may also suffere more from numerical % problems. % % Note: First order methods would usually not be % recommended. % % 'Style' - specifies the style of the basic method % used for the estimation. 'central', 'forward', % or 'backwards' difference methods are used. % % Must be one of 'Central', 'forward', 'backward'. % % DEFAULT: 'Central' % % Note: Central difference methods are usually the % most accurate, but sometiems one must not allow % evaluation in one direction or the other. % % 'RombergTerms' - Allows the user to specify the generalized % Romberg extrapolation method used, or turn it off % completely. % % Must be a positive integer from the set [0,1,2,3]. % % DEFAULT: 2 (Two Romberg terms) % % Note: 0 disables the Romberg step completely. % % 'FixedStep' - Allows the specification of a fixed step % size, preventing the adaptive logic from working. % This will be considerably faster, but not necessarily % as accurate as allowing the adaptive logic to run. % % DEFAULT: [] % % Note: If specified, 'FixedStep' will define the % maximum excursion from x0 that will be used. % % 'Vectorized' - Derivest will normally assume that your % function can be safely evaluated at multiple locations % in a single call. This would minimize the overhead of % a loop and additional function call overhead. Some % functions are not easily vectorizable, but you may % (if your matlab release is new enough) be able to use % arrayfun to accomplish the vectorization. % % When all else fails, set the 'vectorized' property % to 'no'. This will cause derivest to loop over the % successive function calls. % % DEFAULT: 'yes' % % % 'MaxStep' - Specifies the maximum excursion from x0 that % will be allowed, as a multiple of x0. % % DEFAULT: 100 % % 'StepRatio' - Derivest uses a proportionally cascaded % series of function evaluations, moving away from your % point of evaluation. The StepRatio is the ratio used % between sequential steps. % % DEFAULT: 2 % % % See the document DERIVEST.pdf for more explanation of the % algorithms behind the parameters of DERIVEST. In most cases, % I have chosen good values for these parameters, so the user % should never need to specify anything other than possibly % the DerivativeOrder. I've also tried to make my code robust % enough that it will not need much. But complete flexibility % is in there for your use. % % % Arguments: (output) % der - derivative estimate for each element of x0 % der will have the same shape as x0. % % errest - 95% uncertainty estimate of the derivative, such that % % abs(der(j) - f'(x0(j))) < erest(j) % % finaldelta - The final overall stepsize chosen by DERIVEST % % % Example usage: % First derivative of exp(x), at x == 1 % [d,e]=derivest(@(x) exp(x),1) % d = % 2.71828182845904 % % e = % 1.02015503167879e-14 % % True derivative % exp(1) % ans = % 2.71828182845905 % % Example usage: % Third derivative of x.^3+x.^4, at x = [0,1] % derivest(@(x) x.^3 + x.^4,[0 1],'deriv',3) % ans = % 6 30 % % True derivatives: [6,30] % % % See also: gradient % % % Author: John D'Errico % e-mail: woodchips@rochester.rr.com % Release: 1.0 % Release date: 12/27/2006 par.DerivativeOrder = 1; par.MethodOrder = 4; par.Style = 'central'; par.RombergTerms = 2; par.FixedStep = []; par.MaxStep = 100; par.StepRatio = 2; par.NominalStep = []; par.Vectorized = 'yes'; na = length(varargin); if (rem(na,2)==1) error 'Property/value pairs must come as PAIRS of arguments.' elseif na>0 par = parse_pv_pairs(par,varargin); end par = check_params(par); % Was fun a string, or an inline/anonymous function? if (nargin<1) help derivest return elseif isempty(fun) error 'fun was not supplied.' elseif ischar(fun) % a character function name fun = str2func(fun); end % no default for x0 if (nargin<2) || isempty(x0) error 'x0 was not supplied' end par.NominalStep = max(x0,0.02); % was a single point supplied? nx0 = size(x0); n = prod(nx0); % Set the steps to use. if isempty(par.FixedStep) % Basic sequence of steps, relative to a stepsize of 1. delta = par.MaxStep*par.StepRatio .^(0:-1:-25)'; ndel = length(delta); else % Fixed, user supplied absolute sequence of steps. ndel = 3 + ceil(par.DerivativeOrder/2) + ... par.MethodOrder + par.RombergTerms; if par.Style(1) == 'c' ndel = ndel - 2; end delta = par.FixedStep*par.StepRatio .^(-(0:(ndel-1)))'; end % generate finite differencing rule in advance. % The rule is for a nominal unit step size, and will % be scaled later to reflect the local step size. fdarule = 1; switch par.Style case 'central' % for central rules, we will reduce the load by an % even or odd transformation as appropriate. if par.MethodOrder==2 switch par.DerivativeOrder case 1 % the odd transformation did all the work fdarule = 1; case 2 % the even transformation did all the work fdarule = 2; case 3 % the odd transformation did most of the work, but % we need to kill off the linear term fdarule = [0 1]/fdamat(par.StepRatio,1,2); case 4 % the even transformation did most of the work, but % we need to kill off the quadratic term fdarule = [0 1]/fdamat(par.StepRatio,2,2); end else % a 4th order method. We've already ruled out the 1st % order methods since these are central rules. switch par.DerivativeOrder case 1 % the odd transformation did most of the work, but % we need to kill off the cubic term fdarule = [1 0]/fdamat(par.StepRatio,1,2); case 2 % the even transformation did most of the work, but % we need to kill off the quartic term fdarule = [1 0]/fdamat(par.StepRatio,2,2); case 3 % the odd transformation did much of the work, but % we need to kill off the linear & quintic terms fdarule = [0 1 0]/fdamat(par.StepRatio,1,3); case 4 % the even transformation did much of the work, but % we need to kill off the quadratic and 6th order terms fdarule = [0 1 0]/fdamat(par.StepRatio,2,3); end end case {'forward' 'backward'} % These two cases are identical, except at the very end, % where a sign will be introduced. % No odd/even trans, but we already dropped % off the constant term if par.MethodOrder==1 if par.DerivativeOrder==1 % an easy one fdarule = 1; else % 2:4 v = zeros(1,par.DerivativeOrder); v(par.DerivativeOrder) = 1; fdarule = v/fdamat(par.StepRatio,0,par.DerivativeOrder); end else % par.MethodOrder methods drop off the lower order terms, % plus terms directly above DerivativeOrder v = zeros(1,par.DerivativeOrder + par.MethodOrder - 1); v(par.DerivativeOrder) = 1; fdarule = v/fdamat(par.StepRatio,0,par.DerivativeOrder+par.MethodOrder-1); end % correct sign for the 'backward' rule if par.Style(1) == 'b' fdarule = -fdarule; end end % switch on par.style (generating fdarule) nfda = length(fdarule); % will we need fun(x0)? if (rem(par.DerivativeOrder,2) == 0) || ~strncmpi(par.Style,'central',7) if strcmpi(par.Vectorized,'yes') f_x0 = fun(x0); else % not vectorized, so loop f_x0 = zeros(size(x0)); for j = 1:numel(x0) f_x0(j) = fun(x0(j)); end end else f_x0 = []; end % Loop over the elements of x0, reducing it to % a scalar problem. Sorry, vectorization is not % complete here, but this IS only a single loop. der = zeros(nx0); errest = der; finaldelta = der; for i = 1:n x0i = x0(i); h = par.NominalStep(i); % a central, forward or backwards differencing rule? % f_del is the set of all the function evaluations we % will generate. For a central rule, it will have the % even or odd transformation built in. if par.Style(1) == 'c' % A central rule, so we will need to evaluate % symmetrically around x0i. if strcmpi(par.Vectorized,'yes') f_plusdel = fun(x0i+h*delta); f_minusdel = fun(x0i-h*delta); else % not vectorized, so loop f_minusdel = zeros(size(delta)); f_plusdel = zeros(size(delta)); for j = 1:numel(delta) f_plusdel(j) = fun(x0i+h*delta(j)); f_minusdel(j) = fun(x0i-h*delta(j)); end end if ismember(par.DerivativeOrder,[1 3]) % odd transformation f_del = (f_plusdel - f_minusdel)/2; else f_del = (f_plusdel + f_minusdel)/2 - f_x0(i); end elseif par.Style(1) == 'f' % forward rule % drop off the constant only if strcmpi(par.Vectorized,'yes') f_del = fun(x0i+h*delta) - f_x0(i); else % not vectorized, so loop f_del = zeros(size(delta)); for j = 1:numel(delta) f_del(j) = fun(x0i+h*delta(j)) - f_x0(i); end end else % backward rule % drop off the constant only if strcmpi(par.Vectorized,'yes') f_del = fun(x0i-h*delta) - f_x0(i); else % not vectorized, so loop f_del = zeros(size(delta)); for j = 1:numel(delta) f_del(j) = fun(x0i-h*delta(j)) - f_x0(i); end end end % check the size of f_del to ensure it was properly vectorized. f_del = f_del(:); if length(f_del)~=ndel error 'fun did not return the correct size result (fun must be vectorized)' end % Apply the finite difference rule at each delta, scaling % as appropriate for delta and the requested DerivativeOrder. % First, decide how many of these estimates we will end up with. ne = ndel + 1 - nfda - par.RombergTerms; % Form the initial derivative estimates from the chosen % finite difference method. der_init = vec2mat(f_del,ne,nfda)*fdarule.'; % scale to reflect the local delta der_init = der_init(:)./(h*delta(1:ne)).^par.DerivativeOrder; % Each approximation that results is an approximation % of order par.DerivativeOrder to the desired derivative. % Additional (higher order, even or odd) terms in the % Taylor series also remain. Use a generalized (multi-term) % Romberg extrapolation to improve these estimates. switch par.Style case 'central' rombexpon = 2*(1:par.RombergTerms) + par.MethodOrder - 2; otherwise rombexpon = (1:par.RombergTerms) + par.MethodOrder - 1; end [der_romb,errors] = rombextrap(par.StepRatio,der_init,rombexpon); % Choose which result to return % first, trim off the if isempty(par.FixedStep) % trim off the estimates at each end of the scale nest = length(der_romb); switch par.DerivativeOrder case {1 2} trim = [1 2 nest-1 nest]; case 3 trim = [1:4 nest+(-3:0)]; case 4 trim = [1:6 nest+(-5:0)]; end [der_romb,tags] = sort(der_romb); der_romb(trim) = []; tags(trim) = []; errors = errors(tags); trimdelta = delta(tags); [errest(i),ind] = min(errors); finaldelta(i) = h*trimdelta(ind); der(i) = der_romb(ind); else [errest(i),ind] = min(errors); finaldelta(i) = h*delta(ind); der(i) = der_romb(ind); end end end % mainline end function [jac,err] = jacobianest(fun,x0) % gradest: estimate of the Jacobian matrix of a vector valued function of n variables % usage: [jac,err] = jacobianest(fun,x0) % % % arguments: (input) % fun - (vector valued) analytical function to differentiate. % fun must be a function of the vector or array x0. % % x0 - vector location at which to differentiate fun % If x0 is an nxm array, then fun is assumed to be % a function of n*m variables. % % % arguments: (output) % jac - array of first partial derivatives of fun. % Assuming that x0 is a vector of length p % and fun returns a vector of length n, then % jac will be an array of size (n,p) % % err - vector of error estimates corresponding to % each partial derivative in jac. % % % Example: (nonlinear least squares) % xdata = (0:.1:1)'; % ydata = 1+2*exp(0.75*xdata); % fun = @(c) ((c(1)+c(2)*exp(c(3)*xdata)) - ydata).^2; % % [jac,err] = jacobianest(fun,[1 1 1]) % % jac = % -2 -2 0 % -2.1012 -2.3222 -0.23222 % -2.2045 -2.6926 -0.53852 % -2.3096 -3.1176 -0.93528 % -2.4158 -3.6039 -1.4416 % -2.5225 -4.1589 -2.0795 % -2.629 -4.7904 -2.8742 % -2.7343 -5.5063 -3.8544 % -2.8374 -6.3147 -5.0518 % -2.9369 -7.2237 -6.5013 % -3.0314 -8.2403 -8.2403 % % err = % 5.0134e-15 5.0134e-15 0 % 5.0134e-15 0 2.8211e-14 % 5.0134e-15 8.6834e-15 1.5804e-14 % 0 7.09e-15 3.8227e-13 % 5.0134e-15 5.0134e-15 7.5201e-15 % 5.0134e-15 1.0027e-14 2.9233e-14 % 5.0134e-15 0 6.0585e-13 % 5.0134e-15 1.0027e-14 7.2673e-13 % 5.0134e-15 1.0027e-14 3.0495e-13 % 5.0134e-15 1.0027e-14 3.1707e-14 % 5.0134e-15 2.0053e-14 1.4013e-12 % % (At [1 2 0.75], jac should be numerically zero) % % % See also: derivest, gradient, gradest % % % Author: John D'Errico % e-mail: woodchips@rochester.rr.com % Release: 1.0 % Release date: 3/6/2007 % get the length of x0 for the size of jac nx = numel(x0); MaxStep = 100; StepRatio = 2; % was a string supplied? if ischar(fun) fun = str2func(fun); end % get fun at the center point f0 = fun(x0); f0 = f0(:); n = length(f0); if n==0 % empty begets empty jac = zeros(0,nx); err = jac; return end relativedelta = MaxStep*StepRatio .^(0:-1:-25); nsteps = length(relativedelta); % total number of derivatives we will need to take jac = zeros(n,nx); err = jac; for i = 1:nx x0_i = x0(i); if x0_i ~= 0 delta = x0_i*relativedelta; else delta = relativedelta; end % evaluate at each step, centered around x0_i % difference to give a second order estimate fdel = zeros(n,nsteps); for j = 1:nsteps fdif = fun(swapelement(x0,i,x0_i + delta(j))) - ... fun(swapelement(x0,i,x0_i - delta(j))); fdel(:,j) = fdif(:); end % these are pure second order estimates of the % first derivative, for each trial delta. derest = fdel.*repmat(0.5 ./ delta,n,1); % The error term on these estimates has a second order % component, but also some 4th and 6th order terms in it. % Use Romberg exrapolation to improve the estimates to % 6th order, as well as to provide the error estimate. % loop here, as rombextrap coupled with the trimming % will get complicated otherwise. for j = 1:n [der_romb,errest] = rombextrap(StepRatio,derest(j,:),[2 4]); % trim off 3 estimates at each end of the scale nest = length(der_romb); trim = [1:3, nest+(-2:0)]; [der_romb,tags] = sort(der_romb); der_romb(trim) = []; tags(trim) = []; errest = errest(tags); % now pick the estimate with the lowest predicted error [err(j,i),ind] = min(errest); jac(j,i) = der_romb(ind); end end end % mainline function end function [grad,err,finaldelta] = gradest(fun,x0) % gradest: estimate of the gradient vector of an analytical function of n variables % usage: [grad,err,finaldelta] = gradest(fun,x0) % % Uses derivest to provide both derivative estimates % and error estimates. fun needs not be vectorized. % % arguments: (input) % fun - analytical function to differentiate. fun must % be a function of the vector or array x0. % % x0 - vector location at which to differentiate fun % If x0 is an nxm array, then fun is assumed to be % a function of n*m variables. % % arguments: (output) % grad - vector of first partial derivatives of fun. % grad will be a row vector of length numel(x0). % % err - vector of error estimates corresponding to % each partial derivative in grad. % % finaldelta - vector of final step sizes chosen for % each partial derivative. % % % Example: % [grad,err] = gradest(@(x) sum(x.^2),[1 2 3]) % grad = % 2 4 6 % err = % 5.8899e-15 1.178e-14 0 % % % Example: % At [x,y] = [1,1], compute the numerical gradient % of the function sin(x-y) + y*exp(x) % % z = @(xy) sin(diff(xy)) + xy(2)*exp(xy(1)) % % [grad,err ] = gradest(z,[1 1]) % grad = % 1.7183 3.7183 % err = % 7.537e-14 1.1846e-13 % % % Example: % At the global minimizer (1,1) of the Rosenbrock function, % compute the gradient. It should be essentially zero. % % rosen = @(x) (1-x(1)).^2 + 105*(x(2)-x(1).^2).^2; % [g,err] = gradest(rosen,[1 1]) % g = % 1.0843e-20 0 % err = % 1.9075e-18 0 % % % See also: derivest, gradient % % % Author: John D'Errico % e-mail: woodchips@rochester.rr.com % Release: 1.0 % Release date: 2/9/2007 % get the size of x0 so we can reshape % later. sx = size(x0); % total number of derivatives we will need to take nx = numel(x0); grad = zeros(1,nx); err = grad; finaldelta = grad; for ind = 1:nx [grad(ind),err(ind),finaldelta(ind)] = derivest( ... @(xi) fun(swapelement(x0,ind,xi)), ... x0(ind),'deriv',1,'vectorized','no', ... 'methodorder',2); end end % mainline function end function [HD,err,finaldelta] = hessdiag(fun,x0) % HESSDIAG: diagonal elements of the Hessian matrix (vector of second partials) % usage: [HD,err,finaldelta] = hessdiag(fun,x0) % % When all that you want are the diagonal elements of the hessian % matrix, it will be more efficient to call HESSDIAG than HESSIAN. % HESSDIAG uses DERIVEST to provide both second derivative estimates % and error estimates. fun needs not be vectorized. % % arguments: (input) % fun - SCALAR analytical function to differentiate. % fun must be a function of the vector or array x0. % % x0 - vector location at which to differentiate fun % If x0 is an nxm array, then fun is assumed to be % a function of n*m variables. % % arguments: (output) % HD - vector of second partial derivatives of fun. % These are the diagonal elements of the Hessian % matrix, evaluated at x0. % HD will be a row vector of length numel(x0). % % err - vector of error estimates corresponding to % each second partial derivative in HD. % % finaldelta - vector of final step sizes chosen for % each second partial derivative. % % % Example usage: % [HD,err] = hessdiag(@(x) x(1) + x(2)^2 + x(3)^3,[1 2 3]) % HD = % 0 2 18 % % err = % 0 0 0 % % % See also: derivest, gradient, gradest % % % Author: John D'Errico % e-mail: woodchips@rochester.rr.com % Release: 1.0 % Release date: 2/9/2007 % get the size of x0 so we can reshape % later. sx = size(x0); % total number of derivatives we will need to take nx = numel(x0); HD = zeros(1,nx); err = HD; finaldelta = HD; for ind = 1:nx [HD(ind),err(ind),finaldelta(ind)] = derivest( ... @(xi) fun(swapelement(x0,ind,xi)), ... x0(ind),'deriv',2,'vectorized','no'); end end % mainline function end function [hess,err] = hessian(fun,x0) % hessian: estimate elements of the Hessian matrix (array of 2nd partials) % usage: [hess,err] = hessian(fun,x0) % % Hessian is NOT a tool for frequent use on an expensive % to evaluate objective function, especially in a large % number of dimensions. Its computation will use roughly % O(6*n^2) function evaluations for n parameters. % % arguments: (input) % fun - SCALAR analytical function to differentiate. % fun must be a function of the vector or array x0. % fun does not need to be vectorized. % % x0 - vector location at which to compute the Hessian. % % arguments: (output) % hess - nxn symmetric array of second partial derivatives % of fun, evaluated at x0. % % err - nxn array of error estimates corresponding to % each second partial derivative in hess. % % % Example usage: % Rosenbrock function, minimized at [1,1] % rosen = @(x) (1-x(1)).^2 + 105*(x(2)-x(1).^2).^2; % % [h,err] = hessian(rosen,[1 1]) % h = % 842 -420 % -420 210 % err = % 1.0662e-12 4.0061e-10 % 4.0061e-10 2.6654e-13 % % % Example usage: % cos(x-y), at (0,0) % Note: this hessian matrix will be positive semi-definite % % hessian(@(xy) cos(xy(1)-xy(2)),[0 0]) % ans = % -1 1 % 1 -1 % % % See also: derivest, gradient, gradest, hessdiag % % % Author: John D'Errico % e-mail: woodchips@rochester.rr.com % Release: 1.0 % Release date: 2/10/2007 % parameters that we might allow to change params.StepRatio = 2; params.RombergTerms = 3; % get the size of x0 so we can reshape % later. sx = size(x0); % was a string supplied? if ischar(fun) fun = str2func(fun); end % total number of derivatives we will need to take nx = length(x0); % get the diagonal elements of the hessian (2nd partial % derivatives wrt each variable.) [hess,err] = hessdiag(fun,x0); % form the eventual hessian matrix, stuffing only % the diagonals for now. hess = diag(hess); err = diag(err); if nx<2 % the hessian matrix is 1x1. all done return end % get the gradient vector. This is done only to decide % on intelligent step sizes for the mixed partials [grad,graderr,stepsize] = gradest(fun,x0); % Get params.RombergTerms+1 estimates of the upper % triangle of the hessian matrix dfac = params.StepRatio.^(-(0:params.RombergTerms)'); for i = 2:nx for j = 1:(i-1) dij = zeros(params.RombergTerms+1,1); for k = 1:(params.RombergTerms+1) dij(k) = fun(x0 + swap2(zeros(sx),i, ... dfac(k)*stepsize(i),j,dfac(k)*stepsize(j))) + ... fun(x0 + swap2(zeros(sx),i, ... -dfac(k)*stepsize(i),j,-dfac(k)*stepsize(j))) - ... fun(x0 + swap2(zeros(sx),i, ... dfac(k)*stepsize(i),j,-dfac(k)*stepsize(j))) - ... fun(x0 + swap2(zeros(sx),i, ... -dfac(k)*stepsize(i),j,dfac(k)*stepsize(j))); end dij = dij/4/prod(stepsize([i,j])); dij = dij./(dfac.^2); % Romberg extrapolation step [hess(i,j),err(i,j)] = rombextrap(params.StepRatio,dij,[2 4]); hess(j,i) = hess(i,j); err(j,i) = err(i,j); end end end % mainline function end % ============================================ % subfunction - romberg extrapolation % ============================================ function [der_romb,errest] = rombextrap(StepRatio,der_init,rombexpon) % do romberg extrapolation for each estimate % % StepRatio - Ratio decrease in step % der_init - initial derivative estimates % rombexpon - higher order terms to cancel using the romberg step % % der_romb - derivative estimates returned % errest - error estimates % amp - noise amplification factor due to the romberg step srinv = 1/StepRatio; % do nothing if no romberg terms nexpon = length(rombexpon); rmat = ones(nexpon+2,nexpon+1); switch nexpon case 0 % rmat is simple: ones(2,1) case 1 % only one romberg term rmat(2,2) = srinv^rombexpon; rmat(3,2) = srinv^(2*rombexpon); case 2 % two romberg terms rmat(2,2:3) = srinv.^rombexpon; rmat(3,2:3) = srinv.^(2*rombexpon); rmat(4,2:3) = srinv.^(3*rombexpon); case 3 % three romberg terms rmat(2,2:4) = srinv.^rombexpon; rmat(3,2:4) = srinv.^(2*rombexpon); rmat(4,2:4) = srinv.^(3*rombexpon); rmat(5,2:4) = srinv.^(4*rombexpon); end % qr factorization used for the extrapolation as well % as the uncertainty estimates [qromb,rromb] = qr(rmat,0); % the noise amplification is further amplified by the Romberg step. % amp = cond(rromb); % this does the extrapolation to a zero step size. ne = length(der_init); rhs = vec2mat(der_init,nexpon+2,max(1,ne - (nexpon+2))); rombcoefs = rromb\(qromb.'*rhs); der_romb = rombcoefs(1,:).'; % uncertainty estimate of derivative prediction s = sqrt(sum((rhs - rmat*rombcoefs).^2,1)); rinv = rromb\eye(nexpon+1); cov1 = sum(rinv.^2,2); % 1 spare dof errest = s.'*12.7062047361747*sqrt(cov1(1)); end % rombextrap % ============================================ % subfunction - vec2mat % ============================================ function mat = vec2mat(vec,n,m) % forms the matrix M, such that M(i,j) = vec(i+j-1) [i,j] = ndgrid(1:n,0:m-1); ind = i+j; mat = vec(ind); if n==1 mat = mat.'; end end % vec2mat % ============================================ % subfunction - fdamat % ============================================ function mat = fdamat(sr,parity,nterms) % Compute matrix for fda derivation. % parity can be % 0 (one sided, all terms included but zeroth order) % 1 (only odd terms included) % 2 (only even terms included) % nterms - number of terms % sr is the ratio between successive steps srinv = 1./sr; switch parity case 0 % single sided rule [i,j] = ndgrid(1:nterms); c = 1./factorial(1:nterms); mat = c(j).*srinv.^((i-1).*j); case 1 % odd order derivative [i,j] = ndgrid(1:nterms); c = 1./factorial(1:2:(2*nterms)); mat = c(j).*srinv.^((i-1).*(2*j-1)); case 2 % even order derivative [i,j] = ndgrid(1:nterms); c = 1./factorial(2:2:(2*nterms)); mat = c(j).*srinv.^((i-1).*(2*j)); end end % fdamat % ============================================ % subfunction - check_params % ============================================ function par = check_params(par) % check the parameters for acceptability % % Defaults % par.DerivativeOrder = 1; % par.MethodOrder = 2; % par.Style = 'central'; % par.RombergTerms = 2; % par.FixedStep = []; % DerivativeOrder == 1 by default if isempty(par.DerivativeOrder) par.DerivativeOrder = 1; else if (length(par.DerivativeOrder)>1) || ~ismember(par.DerivativeOrder,1:4) error 'DerivativeOrder must be scalar, one of [1 2 3 4].' end end % MethodOrder == 2 by default if isempty(par.MethodOrder) par.MethodOrder = 2; else if (length(par.MethodOrder)>1) || ~ismember(par.MethodOrder,[1 2 3 4]) error 'MethodOrder must be scalar, one of [1 2 3 4].' elseif ismember(par.MethodOrder,[1 3]) && (par.Style(1)=='c') error 'MethodOrder==1 or 3 is not possible with central difference methods' end end % style is char valid = {'central', 'forward', 'backward'}; if isempty(par.Style) par.Style = 'central'; elseif ~ischar(par.Style) error 'Invalid Style: Must be character' end ind = find(strncmpi(par.Style,valid,length(par.Style))); if (length(ind)==1) par.Style = valid{ind}; else error(['Invalid Style: ',par.Style]) end % vectorized is char valid = {'yes', 'no'}; if isempty(par.Vectorized) par.Vectorized = 'yes'; elseif ~ischar(par.Vectorized) error 'Invalid Vectorized: Must be character' end ind = find(strncmpi(par.Vectorized,valid,length(par.Vectorized))); if (length(ind)==1) par.Vectorized = valid{ind}; else error(['Invalid Vectorized: ',par.Vectorized]) end % RombergTerms == 2 by default if isempty(par.RombergTerms) par.RombergTerms = 2; else if (length(par.RombergTerms)>1) || ~ismember(par.RombergTerms,0:3) error 'RombergTerms must be scalar, one of [0 1 2 3].' end end % FixedStep == [] by default if (length(par.FixedStep)>1) || (~isempty(par.FixedStep) && (par.FixedStep<=0)) error 'FixedStep must be empty or a scalar, >0.' end % MaxStep == 10 by default if isempty(par.MaxStep) par.MaxStep = 10; elseif (length(par.MaxStep)>1) || (par.MaxStep<=0) error 'MaxStep must be empty or a scalar, >0.' end end % check_params % ============================================ % Included subfunction - parse_pv_pairs % ============================================ function params=parse_pv_pairs(params,pv_pairs) % parse_pv_pairs: parses sets of property value pairs, allows defaults % usage: params=parse_pv_pairs(default_params,pv_pairs) % % arguments: (input) % default_params - structure, with one field for every potential % property/value pair. Each field will contain the default % value for that property. If no default is supplied for a % given property, then that field must be empty. % % pv_array - cell array of property/value pairs. % Case is ignored when comparing properties to the list % of field names. Also, any unambiguous shortening of a % field/property name is allowed. % % arguments: (output) % params - parameter struct that reflects any updated property/value % pairs in the pv_array. % % Example usage: % First, set default values for the parameters. Assume we % have four parameters that we wish to use optionally in % the function examplefun. % % - 'viscosity', which will have a default value of 1 % - 'volume', which will default to 1 % - 'pie' - which will have default value 3.141592653589793 % - 'description' - a text field, left empty by default % % The first argument to examplefun is one which will always be % supplied. % % function examplefun(dummyarg1,varargin) % params.Viscosity = 1; % params.Volume = 1; % params.Pie = 3.141592653589793 % % params.Description = ''; % params=parse_pv_pairs(params,varargin); % params % % Use examplefun, overriding the defaults for 'pie', 'viscosity' % and 'description'. The 'volume' parameter is left at its default. % % examplefun(rand(10),'vis',10,'pie',3,'Description','Hello world') % % params = % Viscosity: 10 % Volume: 1 % Pie: 3 % Description: 'Hello world' % % Note that capitalization was ignored, and the property 'viscosity' % was truncated as supplied. Also note that the order the pairs were % supplied was arbitrary. npv = length(pv_pairs); n = npv/2; if n~=floor(n) error 'Property/value pairs must come in PAIRS.' end if n<=0 % just return the defaults return end if ~isstruct(params) error 'No structure for defaults was supplied' end % there was at least one pv pair. process any supplied propnames = fieldnames(params); lpropnames = lower(propnames); for i=1:n p_i = lower(pv_pairs{2*i-1}); v_i = pv_pairs{2*i}; ind = strmatch(p_i,lpropnames,'exact'); if isempty(ind) ind = find(strncmp(p_i,lpropnames,length(p_i))); if isempty(ind) error(['No matching property found for: ',pv_pairs{2*i-1}]) elseif length(ind)>1 error(['Ambiguous property name: ',pv_pairs{2*i-1}]) end end p_i = propnames{ind}; % override the corresponding default in params params = setfield(params,p_i,v_i); %#ok end end % parse_pv_pairs % ======================================= % sub-functions % ======================================= function vec = swapelement(vec,ind,val) % swaps val as element ind, into the vector vec vec(ind) = val; end % sub-function end % ======================================= % sub-functions % ======================================= function vec = swap2(vec,ind1,val1,ind2,val2) % swaps val as element ind, into the vector vec vec(ind1) = val1; vec(ind2) = val2; end % sub-function end % End of DERIVEST SUITE