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

Import.
author Daniele Nicolodi <nicolodi@science.unitn.it>
date Wed, 23 Nov 2011 19:22:13 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/m-toolbox/classes/@ao/xfit.m	Wed Nov 23 19:22:13 2011 +0100
@@ -0,0 +1,3410 @@
+% 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
+