Revision ea88b3bc868dd0753ff90f2adda0aaca4474e7fb authored by Niklas Neubrand on 18 July 2024, 08:33:01 UTC, committed by GitHub on 18 July 2024, 08:33:01 UTC
Update Links to DREAM challenges. The domain changed.
1 parent 3f56245
Raw File
arFit.m
% [ar_out] = arFit([ar_in],[silent])
%
% Fit model parameters to data using lsqnonlin (by default) or other
% optimizers
% 
%   arFit
%   arFit(silent)
%   arFit(ar_in)
%   ar_out = arFit(ar_in,silent)
%
%   silent  if 'true', no output will be printed into the command line
%           [false]
%   ar_in   optionally, an ar struct can be used independentely from the 
%           global ar struct. It can also be passed through without 
%           imparing the global ar struct
%
%   ar_out  if ar_in is provided, results are stored in ar_out. Otherwise
%           it is written to the global ar
% 
%   Select different optimizers by editing ar.config.optimizer
%       1 - lsqnonlin (default)
%       2 - fmincon
%       4 - STRSCNE (Bellavia et al, A Scaled Trust Region Solver for 
%           Constrained Nonlinear Equations)
%       5 - arNLS (additional method choices under ar.config.optimizerStep;
%           see help arNLS)
%       6 - fmincon_as_lsq
%       7 - arNLS with SR1 updates
%       8 - NL2SOL (Denis et al, Algorithm 573:  NL2SOL--An Adaptive
%           Nonlinear Least-Squares)
%       9 - TRESNEI (B.Morini, M.Porcelli "TRESNEI, a Matlab trust-region 
%           solver for systems of nonlinear equalities and inequalities")
%      10 - Ceres (Sameer Agarwal and Keir Mierle and Others, Google Solver)
%      11 - lsqnonlin_repeated - repeated runs of lsqnonlin until convergence
%      12 - fminsearchbnd
%      13 - patternsearch
%      14 - patternsearch combined with fminsearchbnd
%      15 - particleswarm
%      16 - simulated annealing
%      17 - ga (geneticalgorithm)
%      18 - Repeated optimization alternating between 1 and 5 (Joep's heuristics)
%      19 - enhanced Scatter Search (eSS) Egea et al. "Dynamic Optimization 
%           of Nonlinear Processes with an Enhanced Scatter Search Method"
%           link to MEIGO toolbox needed: https://bitbucket.org/jrbanga_/meigo64
%
%   lsqnonlin.m exit flag description:
%       1  LSQNONLIN converged to a solution X.
%       2  Change in X smaller than the specified tolerance.
%       3  Change in the residual smaller than the specified tolerance.
%       4  Magnitude search direction smaller than the specified tolerance.
%       0  Maximum number of function evaluations or of iterations reached.
%      -1  Algorithm terminated by the output function.
%      -2  Bounds are inconsistent.
%      -4  Line search cannot sufficiently decrease the residual along the
%           current search direction.
% 
%   ar.fit contains information about the latest fit. It also consist of
%   ar.fit.checksums which contains information (e.g. checksums) to uniquely
%   identify/discriminate the same/different fit settings.
%
%   ar.config.showFitting   (off by default)
%   Convergence of the optimization algorithm can be stored by setting
%   ar.config.logFitting = 1. Then interesing variables for each iteration
%   is stored in ar.fit.optimLog. (All this does *not* refer to fitting on 
%   the logarithmic scale, but only to storing the results)
%
%   ar.config.optimizerStep   (0 by default)
%   sets submethod in arNLS, see arNLS for more detail
%
%   ar.config.showFitting   (off by default)
%   if the to 'true', trajectories in the plos are updates during
%   optimization. (not recommended, slow and buggy)
%
% See also arFitLHS, arFits, arNLS, arFitObs, arFitDyn, arFitSingle,
% arFitSome


function varargout = arFit(varargin)

global ar
% this struct is used to store information of individual fits
% a global variable is used to enable extension of the stored information
% e.g. by using global fit in snls.m
global fit
fit = struct; % overwrite old fit struct

if(nargin==0)
    qglobalar = true;
    silent = false;
else
    if(isstruct(varargin{1}))
        qglobalar = false;
        ar = varargin{1};
        if(nargin>1)
            varargin = varargin(2:end);
        else
            varargin = {};
        end
    else
        qglobalar = true;
    end
    
    if(~isempty(varargin))
        silent = varargin{1};
    else
        silent = false;
    end
end


fit = struct;
fit.checksums = struct;
[~,fit.checksums.folder]=fileparts(pwd);
fit.checksums.fkt = ar.fkt;                         % checksum for model properties
fit.checksums.checkstr_parameters = arChecksumPara; % checksum for parameter properties
fit.checksums.checkstr_fitting = arChecksumFitting; % checksum for fit and integration settings
fit.checksums.checkstr_data = arChecksumData;       % checksum for data properties
fit.checksums.pstart = ar.p; % initial guess

if(~isfield(ar.config, 'optimizer'))
    ar.config.optimizer = 1;
end
if(~isfield(ar.config, 'optimizerStep'))
    ar.config.optimizerStep = 0;
end
if(~isfield(ar.config, 'showFitting'))
    ar.config.showFitting = 0;
end
if(~isfield(ar.config, 'logFitting'))
    ar.config.logFitting = 0;
end
ar.config.optim.OutputFcn = cell(0);
if(ar.config.logFitting)
    ar.config.optim.OutputFcn = [ar.config.optim.OutputFcn, {@arLogFitDetailed}];
elseif(isfield(ar,'fit'));
    if(isfield(ar.fit,'optimLog'))
        ar.fit = rmfield(ar.fit,'optimLog');
    end
end
if(ar.config.showFitting)
    ar.config.optim.OutputFcn = [ar.config.optim.OutputFcn, {@arPlotFast}];
end

if(ar.config.useSensis)
    ar.config.optim.Jacobian = 'on';
else
    ar.config.optim.Jacobian = 'off';
end

removeL1path = false;
if (any(ar.type==3) || any(ar.type==5))
    if(~isfield(ar.config, 'l1trdog'))
        ar.config.l1trdog = 1;
        l1trdog(); % Modify trdog for L1 regularization
    end
    
    removeL1path = true;
    ar_path = fileparts(which('arInit.m'));
    addpath([ar_path '/L1/trdog'])
end

fit(1).iter_count = 0;
fit.fevals = 0;

if(ar.config.logFitting)
    fit.chi2_hist = nan(1,ar.config.optim.MaxIter);
    fit.constr_hist = nan(1,ar.config.optim.MaxIter);
    fit.opti_hist = nan(1,ar.config.optim.MaxIter);
    fit.p_hist = nan(ar.config.optim.MaxIter,length(ar.p));
    fit.maxstepsize_hist = nan(1,ar.config.optim.MaxIter);
    fit.stepsize_hist = nan(1,ar.config.optim.MaxIter);
end

ar = arCalcMerit(ar, true, ar.p(ar.qFit==1));
chi2_old = arGetMerit('chi2fit');

if(sum(ar.qFit==1)<=0)
    error('No parameters are allowed to be fitted. Check ar.qFit.')
end

ub = ar.ub;
lb = ar.lb;
ub(ar.type==2) = ub(ar.type==2) + 1;
lb(ar.type==2) = lb(ar.type==2) - 1;
ub = ub(ar.qFit==1);
lb = lb(ar.qFit==1);

% Make a backup of the parameters before we start
arPush('arFit');

% lsqnonlin
if(ar.config.optimizer == 1)    
    [pFit, ~, resnorm, exitflag, output, lambda, jac] = ...
        lsqnonlin(@merit_fkt, ar.p(ar.qFit==1), lb, ub, ar.config.optim);

% fmincon
elseif(ar.config.optimizer == 2)
    options = optimset('fmincon');
    options.GradObj = 'on';
    options.GradConstr = 'on';
    options.TolFun = ar.config.optim.TolFun;
    options.TolX = ar.config.optim.TolX;
    options.Display = ar.config.optim.Display;
    options.MaxIter = ar.config.optim.MaxIter;
    options.OutputFcn = ar.config.optim.OutputFcn;
    
    options.Algorithm = 'interior-point';
    % options.Algorithm = 'trust-region-reflective';
    options.SubproblemAlgorithm = 'cg';
    % options.Hessian = 'fin-diff-grads';
    options.Hessian = 'user-supplied';
    options.HessFcn = @fmincon_hessianfcn;
    % options2.InitBarrierParam = 1e+6;
    % options2.InitTrustRegionRadius = 1e-1;
    
    switch options.Algorithm
        case 'interior-point'
            myconfun = @confun;
        case 'trust-region-reflective'
            myconfun = [];
    end

    [pFit, ~, exitflag, output, lambda, grad] = ...
        fmincon(@merit_fkt_fmincon, ar.p(ar.qFit==1),[],[],[],[],lb,ub, ...
        myconfun,options);
    resnorm = merit_fkt(pFit);
    jac = [];
    fit.grad = grad;
    
% PSO
elseif(ar.config.optimizer == 3) 
    [pFit, ~, resnorm, exitflag, output, lambda, jac] = ...
        arFitPSO(lb, ub);
   
% STRSCNE
elseif(ar.config.optimizer == 4)
    
    if(~isempty(ar.config.optim.Display))
        silent = strcmp(ar.config.optim.Display,'iter')==0;
    else
        silent = 1;
    end
    if(~isempty(ar.config.optim.MaxIter))
        maxiter = ar.config.optim.MaxIter;
    else
        maxiter = 1e3;
    end
    if(~isempty(ar.config.optim.MaxFunEvals))
        maxfneval = ar.config.optim.MaxFunEvals;
    else
        maxfneval = 100*length(ar.p(ar.qFit==1));
    end
    delta = 1;
    warnreset = warning;
    warning('off','MATLAB:rankDeficientMatrix');
    [pFit, exitflag, output] = ...
        STRSCNE(ar.p(ar.qFit==1), @merit_fkt_STRSCNE, [-Inf,0], ...
        lb, ub, [maxiter,maxfneval,delta,~silent], @merit_dfkt_STRSCNE);
    warning(warnreset);
    resnorm = merit_fkt(pFit);
    jac = merit_dfkt_STRSCNE(pFit);
    lambda = [];
    % convert to lsqnonlin exitflag
    if(exitflag==1 || exitflag == 2)
        exitflag = 0;
    else
        exitflag = NaN;
    end

% arNLS
elseif(ar.config.optimizer == 5)
    [pFit, ~, resnorm, exitflag, output, lambda, jac] = ...
        arNLS(@merit_fkt, ar.p(ar.qFit==1), lb, ub, ar.config.optim, ar.config.optimizerStep);
    
% fmincon as least squares fit
elseif(ar.config.optimizer == 6)
    options = optimset('fmincon');
    options.GradObj = 'on';
    options.TolFun = ar.config.optim.TolFun;
    options.TolX = ar.config.optim.TolX;
    options.Display = ar.config.optim.Display;
    options.MaxIter = ar.config.optim.MaxIter;
    options.OutputFcn = ar.config.optim.OutputFcn;
    
    [pFit, ~, exitflag, output, lambda, jac] = ...
        fmincon(@merit_fkt_fmincon_lsq, ar.p(ar.qFit==1),[],[],[],[],lb,ub, ...
        [],options);% x l, u are transposed because TRESNEI.m uses column notation, opposed to row notation in D2D

    resnorm = merit_fkt(pFit);
    
% arNLS boosted by SR1 updates
elseif(ar.config.optimizer == 7)
    [pFit, ~, resnorm, exitflag, output, lambda, jac] = ...
        arNLS(@merit_fkt_sr1, ar.p(ar.qFit==1), lb, ub, ar.config.optim, ar.config.optimizerStep);

% NL2SOL
elseif(ar.config.optimizer == 8)
    if ~exist('mexnl2sol', 'file')
        compileNL2SOL;
    end
    [pFit, ~, resnorm, exitflag, output.iterations, lambda, jac] = ...
        mexnl2sol(@merit_fkt, ar.p(ar.qFit==1), lb, ub, ar.config.optim, 1);

% TRESNEI
elseif(ar.config.optimizer == 9)
    [pFit, exitflag, output, lambda, jac] = ...
        arTRESNEI(@merit_fkt, ar.p(ar.qFit==1), lb, ub, ar.config.optim);
    resnorm = merit_fkt(pFit);  
 
% Ceres
elseif(ar.config.optimizer == 10)
    if ~exist('ceresd2d', 'file')
         compileCeres;
    end
    [pFit, ~, ~, exitflag, output.iterations, jac, ceresexitmessage] = ...
        ceresd2d(@merit_fkt, ar.p(ar.qFit==1), lb, ub, ar.config.optimceres);
    resnorm = merit_fkt(pFit);
    lambda = [];
    fit.ceresexitmessage = ceresexitmessage;
    
% Repeated optimization with lsqnonlin
elseif(ar.config.optimizer == 11)
    [pFit, resnorm, res, exitflag, output, lambda, jac] = ...
        lsqnonlin(@merit_fkt, ar.p(ar.qFit==1), lb, ub, ar.config.optim);
    
    % Keep going until we really converged
    resnormOld = inf;
    while (((resnormOld-resnorm)/resnorm) > 1e-5)
        resnormOld = resnorm;
        [pFit, resnorm, res, exitflag, output, lambda, jac] = ...
            lsqnonlin(@merit_fkt, pFit, lb, ub, ar.config.optim);
    end
    resnorm = res;
    
% fminsearchbnd
elseif(ar.config.optimizer == 12)
    options = optimset('fminsearch');
    if(~isempty(ar.config.optim.Display))
        options.Display = ar.config.optim.Display;
    end
    if(~isempty(ar.config.optim.TolX))
        options.TolX = ar.config.optim.TolX;
    end
    if(~isempty(ar.config.optim.TolFun))
        options.TolFun = ar.config.optim.TolFun;
    end    
    [pFit, ~, exitflag, output] = ...
        fminsearchbnd(@merit_fkt_chi2, ar.p(ar.qFit==1), lb, ub, options);
    resnorm = merit_fkt(pFit);
    lambda = [];
    jac = [];

% patternsearch    
elseif(ar.config.optimizer == 13)
    options = psoptimset;
    if(~isempty(ar.config.optim.Display))
        options.Display = ar.config.optim.Display;
    end
    [pFit, ~, exitflag, output] = ...
        patternsearch(@merit_fkt_chi2, ar.p(ar.qFit==1), [], [], [], [], lb, ub, [], options);
    resnorm = merit_fkt(pFit);
    lambda = [];
    jac = [];

% patternsearch hybrid
elseif(ar.config.optimizer == 14)
    options = psoptimset;
    if(~isempty(ar.config.optim.Display))
        options.Display = ar.config.optim.Display;
    end
    pFit = ...
        patternsearch(@merit_fkt_chi2, ar.p(ar.qFit==1), [], [], [], [], lb, ub, [], options);

    options = optimset('fminsearch');
    if(~isempty(ar.config.optim.Display))
        options.Display = ar.config.optim.Display;
    end
    if(~isempty(ar.config.optim.TolX))
        options.TolX = ar.config.optim.TolX;
    end
    if(~isempty(ar.config.optim.TolFun))
        options.TolFun = ar.config.optim.TolFun;
    end 

    [pFit, ~, exitflag, output] = ...
        fminsearchbnd(@merit_fkt_chi2, pFit, lb, ub, options);
    resnorm = merit_fkt(pFit);
    lambda = [];
    jac = [];   

% particleswarm
elseif(ar.config.optimizer == 15)
    options = optimoptions('particleswarm');
    if(~isempty(ar.config.optim.Display))
        options.Display = ar.config.optim.Display;
    end
    [pFit, ~, exitflag, output] = ...
        particleswarm(@merit_fkt_chi2, length(ar.p(ar.qFit==1)), lb, ub, options);
    resnorm = merit_fkt(pFit);
    lambda = [];
    jac = [];
    
% simulated annealing
elseif(ar.config.optimizer == 16)
    options = saoptimset('simulannealbnd');
    if(~isempty(ar.config.optim.Display))
        options.Display = ar.config.optim.Display;
    end
    [pFit, ~, exitflag, output] = ...
        simulannealbnd(@merit_fkt_chi2, ar.p(ar.qFit==1), lb, ub, options);
    resnorm = merit_fkt(pFit);
    lambda = [];
    jac = [];
      
% ga
elseif(ar.config.optimizer == 17)
    options = gaoptimset;
    if(~isempty(ar.config.optim.Display))
        options.Display = ar.config.optim.Display;
    end
    [pFit, ~, exitflag, output] = ...
        ga(@merit_fkt_chi2, sum(ar.qFit==1), [], [], [], [], lb, ub, [], options);
    resnorm = merit_fkt(pFit);
    lambda = [];
    jac = [];
    output.iterations = output.generations;

% Repeated optimization alternating between 1 and 5
elseif(ar.config.optimizer == 18)
    [pFit, resnorm, res, exitflag, output, lambda, jac] = ...
        lsqnonlin(@merit_fkt, ar.p(ar.qFit==1), lb, ub, ar.config.optim);
    
    repeatTol = 1e-5;   % Repetition tolerance
    pfac = 5e-3;        % Perturbation factor (should make this part of config options)
    repeatAttempts = 5; % Number of repetition attempts before tolerances are relaxed
    
    % Keep going until we really converged
    doArNLS = false;
    bestOptim = inf;
    
    % Small perturbation attempted?
    perturb = false;
    done = false;
    attempts = 0;
    while ( ~done )
        resnormOld = resnorm;
        
        % Alternate method
        doArNLS = ~doArNLS;
        if ( doArNLS )
            [pFit, resnorm, res, exitflag, output, lambda, jac] = ...
                arNLS(@merit_fkt, pFit, lb, ub, ar.config.optim, ar.config.optimizerStep);
        else
            [pFit, resnorm, res, exitflag, output, lambda, jac] = ...
                lsqnonlin(@merit_fkt, pFit, lb, ub, ar.config.optim);
        end
        
        if ( resnorm < bestOptim )
            bestOptim = resnorm;
            fprintf( 'Last fit is best fit so far: %g\n', bestOptim );
        else
            fprintf( 'Current resnorm %g; Best resnorm: %g\n', resnorm, bestOptim );
        end
        
        attempts = attempts + 1;
        if ( attempts > repeatAttempts )
            repeatTol = repeatTol * 10;
        end
        
        % We're not getting better
        if (((resnormOld-resnorm)/resnorm) < repeatTol)
            % Have we tried perturbing the parameters?
            if ( perturb )
                pFit = pFitPrePerturb;
                res = resPrePerturb;
                resnorm = resnormPrePerturb;
                exitflag = exitflagPrePerturb;
                output = outputPrePerturb;
                lambda = lambdaPrePerturb;
                jac = jacPrePerturb;

                done = true;
                disp( 'Perturbation failed. Reverting to previous values.' );
            else
                disp( 'Perturbing ...' );
                pFitPrePerturb = pFit;
                resPrePerturb = res;
                resnormPrePerturb = resnorm;
                exitflagPrePerturb = exitflag;
                outputPrePerturb = output;
                lambdaPrePerturb = lambda;
                jacPrePerturb = jac;
                
                % Add some random noise
                pFit(ar.qLog10(ar.qFit==1)==1) = pFit(ar.qLog10(ar.qFit==1)==1) + pfac * randn( 1, numel( pFit(ar.qLog10(ar.qFit==1)==1) ) );
                pFit(ar.qLog10(ar.qFit==1)==0) = pFit(ar.qLog10(ar.qFit==1)==0) .* ( 1 + pfac * randn( 1, numel( pFit(ar.qLog10(ar.qFit==1)==0) ) ) );
                
                % Enforce bounds
                pFit( pFit < ar.lb(ar.qFit==1) ) = ar.lb(pFit < ar.lb(ar.qFit==1));
                pFit( pFit > ar.ub(ar.qFit==1) ) = ar.ub(pFit > ar.ub(ar.qFit==1));
                
                perturb = true;
            end
        else
            if ( perturb )
                % We went down more, next time we can again try the perturbation
                disp( 'Perturbation successful. Continuing minimization process.' );
                perturb = false;
            end
        end
    end
    resnorm = res;    

% eSS via Link to MEIGO
elseif(ar.config.optimizer == 19)
    ar.config.optimizers{19} = 'eSS_MEIGO';
    
    if(~isempty(ar.config.optim.Display))
        silent = strcmp(ar.config.optim.Display,'iter')==0;
    else
        silent = 1;
    end
    
    
    problem.f = @merit_fkt_eSS;
    problem.x_0 = ar.p(ar.qFit==1);
    problem.x_L = lb;
    problem.x_U = ub;
    
    if(~isempty(ar.config.optim.MaxIter))
        maxiter = ar.config.optim.MaxIter;
    else
        maxiter = 1e3;
    end
    
    opts.local.solver = 'lsqnonlin';
    opts.maxeval = maxiter;
    opts.iterprint = ~strcmp(ar.config.optim.Display,'off');
    opts.local.iterprint = ~strcmp(ar.config.optim.Display,'off');
    
    Results = MEIGO(problem,opts,'ESS');
    
    pFit = Results.xbest;
    resnorm = merit_fkt(pFit);
    exitflag = Results.end_crit;
    output.iterations = NaN;
    output.funcCount = Results.numeval;
    output.firstorderopt= NaN;
    output.message = 'You used enhanced scatter search (eSS). There is no other output-message.';
    lambda = NaN;
    jac = [];
    
% TODO: Automatically delete the automatically generated file  called ess_report.mat
else
    error('ar.config.optimizer invalid');    
end

if ~isreal(pFit)  % if parameters are complex numbers, throw warning once
    persistent didwarn
    if isempty(didwarn)
        didwarn = 0;
    else
        didwarn = 1;
    end
    if didwarn ==0
        warning('D2D:ImaginaryPfit','Parameters are imaginary. This can occur due to imaginary gradients. If this is not intended, more stringent ODE tolerances and/or usage of ar.model.qPositiveX might be a solution.')
    end
end

if(isfield(ar, 'ms_count_snips') && ar.ms_count_snips>0)
    if(max(ar.ms_violation) > ar.ms_threshold)
        arFprintf(1, 'Multiple Shooting: continuity constains violated %e > %e\n', max(ar.ms_violation), ar.ms_threshold);
    end
end

if removeL1path
    rmpath([ar_path '/L1/trdog'])
end

ar.p(ar.qFit==1) = pFit;
ar = arCalcMerit(ar, true, ar.p(ar.qFit==1));

fit.exitflag = exitflag;
fit.output = output;
fit.iter = output.iterations;
fit.chi2 = arGetMerit('chi2fit');
fit.lambda = lambda;
fit.qFit = ar.qFit;
fit.res = resnorm;
fit.sres = full(jac);
fit.improve = chi2_old - fit.chi2;
if(isfield(ar,'fit') && isfield(ar.fit,'optimLog'))
    fit.optimLog = ar.fit.optimLog;
end

ar.fit = fit;

if(silent==0 || (silent==1 && exitflag < 1)) % silent=2 can entirely suppress this message
    arFitPrint;
end

if(~silent)
    ar = arCalcMerit(ar, true);
end

if(nargout>0 && ~qglobalar)
    varargout{1} = ar;
else
    varargout = {};
end

% Discard the parameter set again without taking its values
arPop(1);

% eSS - under development 
function [J,G,res,sres] = merit_fkt_eSS(pTrial)
global ar

% Only compute sensis when requested
if ( isfield( ar.config, 'sensiSkip' ) )
    sensiskip = ar.config.sensiSkip;
else
    sensiskip = false;
end
sensi = ar.config.useSensis;% && (~sensiskip || (nargout > 1));

arCalcMerit(sensi, pTrial);
J = arGetMerit(true);
G = 0;
arLogFit(ar);
res = [ar.res ar.constr];
if(nargout>1 && ar.config.useSensis)
    sres = [];
    if(~isempty(ar.sres))
        sres = ar.sres(:, ar.qFit==1);
    end
    if(~isempty(ar.sconstr))
        sres = [sres; ar.sconstr(:, ar.qFit==1)];
    end
end

np = sum(ar.qFit==1);
if ( numel(res) < np )
    tres = zeros(1,np);
    tres(1:length(res)) = res;
    if (nargout>1 && ar.config.useSensis)
        tsres = zeros(np);
        tsres(1:length(res), 1:np) = sres;
    end
    
    res = tres;
    if (nargout>1 && ar.config.useSensis)
        sres = tsres;
    end
end

% lsqnonlin and arNLS
function [res, sres] = merit_fkt(pTrial)
global ar

% Only compute sensis when requested
if ( isfield( ar.config, 'sensiSkip' ) )
    sensiskip = ar.config.sensiSkip;
else
    sensiskip = false;
end
sensi = ar.config.useSensis;% && (~sensiskip || (nargout > 1));

arCalcMerit(sensi, pTrial);
arLogFit(ar);
res = [ar.res ar.constr];
if(nargout>1 && ar.config.useSensis)
    sres = [];
    if(~isempty(ar.sres))
        sres = ar.sres(:, ar.qFit==1);
    end
    if(~isempty(ar.sconstr))
        sres = [sres; ar.sconstr(:, ar.qFit==1)];
    end
end

np = sum(ar.qFit==1);
if ( numel(res) < np )
    tres = zeros(1,np);
    tres(1:length(res)) = res;
    if (nargout>1 && ar.config.useSensis)
        tsres = zeros(np);
        tsres(1:length(res), 1:np) = sres;
    end
    
    res = tres;
    if (nargout>1 && ar.config.useSensis)
        sres = tsres;
    end
end

% arNLS boosted by SR1 updates
function [res, sres, H, ssres] = merit_fkt_sr1(p, pc, ~, sresc, ssresc)
global ar
arCalcMerit(ar.config.useSensis, p);
arLogFit(ar);
res = [ar.res ar.constr];
if(nargout>1 && ar.config.useSensis)
    sres = [];
    if(~isempty(ar.sres))
        sres = ar.sres(:, ar.qFit==1);
    end
    if(~isempty(ar.sconstr))
        sres = [sres; ar.sconstr(:, ar.qFit==1)];
    end
    
    if(nargout>2)
        if(nargin<2)
            H = 2*(sres'*sres);
            ssres = zeros([length(p) length(p) length(res)]);
            return;
        end
        
        % SR1 update on residuals
        ssres = nan(size(ssresc));
        for j=1:length(res)
            ssres(:,:,j) = sr1_update(ssresc(:,:,j), pc, sresc(j,:), p, sres(j,:));
            % ssres(j,:,:) = sr1_update(ssresc(j,:,:), pc, 2*sresc(j,:), p, 2*sres(j,:));
            % ssres(:,:,j) = sr1_update(ssresc(:,:,j), pc, 0.5*sresc(j,:), p, 0.5*sres(j,:));
            % ssres(j,:,:) = sr1_update(ssresc(:,:,j), pc, -2*sresc(j,:), p, -2*sres(j,:));
            % ssres(j,:,:) = sr1_update(ssresc(:,:,j), pc, -sresc(j,:), p, -sres(j,:));
        end
        
%         figure(1);
%         subplot(1,2,1)
%         imagesc(2*(sres'*sres));
%         subplot(1,2,2)
%         imagesc(squeeze(sum(bsxfun(@times, res', ssres),1)));
        
        H = 2*(sres'*sres) + sum(bsxfun(@times, shiftdim(res,-1), ssres),3);
    end
end

% SR1 updates
function H = sr1_update(H, pC, gC, pO, gO)
sk = transpose(pC - pO);
yk = transpose(gC - gO);
rk = yk - H*sk;
c1 = 0.5;
if(abs(rk'*sk) > c1*norm(rk)*norm(sk))
    H = H + (rk*rk') / (rk'*sk);
end

% fmincon
function [l, g, H] = merit_fkt_fmincon(pTrial)
global ar
arCalcMerit(ar.config.useSensis, pTrial);
arLogFit(ar);
l = sum(ar.res.^2);
if(nargout>1)
    g = 2*ar.res*ar.sres(:, ar.qFit==1);
end
if(nargout>2)
    type3_ind = ar.type == 3;
    type3_ind = type3_ind(ar.qFit==1);
    type5_ind = (ar.type == 5 ) & (ar.qFit == 1);
    
    H = 2*ar.sres(:, ar.qFit==1)'*ar.sres(:, ar.qFit==1);
    H(type3_ind,type3_ind) = H(type3_ind,type3_ind) .* ~eye(sum(type3_ind));
end

% fmincon as lsq
function [l, g] = merit_fkt_fmincon_lsq(pTrial)
global ar
arCalcMerit(ar.config.useSensis, pTrial);
arLogFit(ar);
res = [ar.res ar.constr];
if(nargout>1 && ar.config.useSensis)
    sres = [];
    if(~isempty(ar.sres))
        sres = ar.sres(:, ar.qFit==1);
    end
    if(~isempty(ar.sconstr))
        sres = [sres; ar.sconstr(:, ar.qFit==1)];
    end
end
l = sum(res.^2);
if(nargout>1)
    g = res*sres;
end

function [c, ceq, gc, gceq] = confun(pTrial)
global ar
arCalcMerit(ar.config.useSensis, pTrial);
arLogFit(ar);
% Nonlinear inequality constraints
c = [];
% Nonlinear equality constraints
ceq = ar.constr;
if(nargout>2)
    gc = [];
    if(~isempty(ar.sconstr))
        gceq = ar.sconstr(:, ar.qFit==1)';
    else
        gceq = [];
    end
end

function hessian = fmincon_hessianfcn(pTrial, lambda)
global ar
arCalcMerit(ar.config.useSensis, pTrial);
arLogFit(ar);
H = ar.sres(:, ar.qFit==1)'*ar.sres(:, ar.qFit==1);
Hconstr = zeros(size(H));
for jc = 1:length(ar.constr)
    Hconstr = Hconstr + lambda.eqnonlin(jc)*(ar.sconstr(jc, ar.qFit==1)'*ar.sconstr(jc, ar.qFit==1));
end
hessian = H + Hconstr;

% STRSCNE
function res = merit_fkt_STRSCNE(pTrial)
global ar
arCalcMerit(ar.config.useSensis, pTrial);
arLogFit(ar);
res = [ar.res ar.constr]';

% derivatives for STRSCNE
function sres = merit_dfkt_STRSCNE(pTrial)
global ar
if(ar.config.useSensis)
    arCalcMerit(ar.config.useSensis, pTrial);
    sres = ar.sres(:, ar.qFit==1);
    if(~isempty(ar.sconstr))
        sres = [sres; ar.sconstr(:, ar.qFit==1)];
    end
end

% fminsearch, particleswarm, simulannealbnd, patternsearch
function chi2 = merit_fkt_chi2(pTrial)
global ar

try
    arCalcMerit(false, pTrial);
    arLogFit(ar);
    chi2 = sum([ar.res ar.constr].^2);
catch
    % workaround for particleswarm
    chi2 = rand(1)*1e23;
end

% plot fitting
function stop = arPlotFast(~,~,state)
global ar
stop = false;
if(strcmp(state, 'iter'))    
    if(ar.config.showFitting)
        % note that the ~ar.config.useNewPlots is a temporary fix. arPlot2,
        % which is called when ar.config.useNewPlots is set to 1 does not 
        % properly redraw -2 log(L) labels when forced to do a fastplot
        % TO DO: The proper solution would be to make sure that fastplot 
        % also updates the -2 log(L) text label
        arPlot(false, ~ar.config.useNewPlots, true, true, true);
        drawnow;
    end
end

% log fitting
function arLogFit(ar)

% this struct is used to store information of individual fits
% a global variable is used to enable extension of the stored information
% e.g. by using global fit in snls.m
global fit

fit.fevals = fit.fevals + 1;

if(fit.iter_count>0)
    if(arGetMerit('chi2all') > (fit.chi2_hist(fit.iter_count) + ...
            fit.constr_hist(fit.iter_count)))
        return;
    end
end

fit.chi2_hist(fit.iter_count+1)   = arGetMerit('chi2fit');
fit.constr_hist(fit.iter_count+1) = arGetMerit('chi2constr');
fit.p_hist(fit.iter_count+1,:) = ar.p;
fit.opti_hist(fit.iter_count+1,:) = ar.firstorderopt;
fit.maxstepsize_hist(fit.iter_count+1) = nan;
if(fit.iter_count>0)
    q = fit.p_hist(fit.iter_count,:);
    fit.stepsize_hist(fit.iter_count+1) = norm(q(:) - ar.p(:));
end
fit.iter_count = fit.iter_count + 1;


function stop = arLogFitDetailed(~,optimValues,state)
stop = false;
global ar

if(ar.config.optimizer ==1)
    fn = {'iteration','funccount','stepsize','firstorderopt','cgiterations','positivedefinite','ratio','degenerate','trustregionradius','resnorm','gradient_norm'};
else
    error('Behaviour that should be executed when ar.config.logfitting = 1 is not defined for the selected optimization algorithm.');
end

optimValues.gradient_norm = norm(optimValues.gradient);
% optimValues.x = x;
% optimValues = rmfield(optimValues,'gradient');
% optimValues = rmfield(optimValues,'residual');
switch state
    case 'init'
        ar.fit.optimLog.values = NaN(ar.config.optim.MaxIter,length(fn));
        ar.fit.optimLog.labels = fn;
    case 'iter'
        for i=1:length(fn)
            ar.fit.optimLog.values(optimValues.funccount,i) = optimValues.(fn{i});
        end
    case 'done'
        ar.fit.optimLog.values = ar.fit.optimLog.values(sum(~isnan(ar.fit.optimLog.values),2)>0,:);
    otherwise
end

back to top