function [opt_par_values,fval,exitflag,hessian_mat,options_,Scale,new_rat_hess_info]=dynare_minimize_objective(objective_function,start_par_value,minimizer_algorithm,options_,bounds,parameter_names,prior_information,Initial_Hessian,varargin)
% function [opt_par_values,fval,exitflag,hessian_mat,options_,Scale,new_rat_hess_info]=dynare_minimize_objective(objective_function,start_par_value,minimizer_algorithm,options_,bounds,parameter_names,prior_information,Initial_Hessian,new_rat_hess_info,varargin)
% Calls a minimizer
%
% INPUTS
%   objective_function  [function handle]                   handle to the objective function
%   start_par_value     [n_params by 1] vector of doubles   starting values for the parameters
%   minimizer_algorithm [scalar double, or string]          code of the optimizer algorithm, or string for the name of a user defined optimization routine (not shipped with dynare).
%   options_            [MATLAB structure]                  Dynare options structure
%   bounds              [n_params by 2] vector of doubles   2 row vectors containing lower and upper bound for parameters
%   parameter_names     [n_params by 1] cell array          strings containing the parameters names
%   prior_information   [MATLAB structure]                  Dynare prior information structure (bayestopt_) provided for algorithm 6
%   Initial_Hessian     [n_params by n_params] matrix       initial Hessian matrix provided for algorithm 6
%   new_rat_hess_info   [MATLAB structure]                  step size info used by algorithm 5
%   varargin            [cell array]                        Input arguments for objective function
%
% OUTPUTS
%   opt_par_values      [n_params by 1] vector of doubles   optimal parameter values minimizing the objective
%   fval                [scalar double]                     value of the objective function at the minimum
%   exitflag            [scalar double]                     return code of the respective optimizer
%   hessian_mat         [n_params by n_params] matrix       Hessian matrix at the mode returned by optimizer
%   options_            [MATLAB structure]                  Dynare options structure (to return options set by algorithms 5)
%   Scale               [scalar double]                     scaling parameter returned by algorith 6
%
% SPECIAL REQUIREMENTS
%   none.
%
%
% Copyright © 2014-2023 Dynare Team
%
% This file is part of Dynare.
%
% Dynare is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% Dynare is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with Dynare.  If not, see <https://www.gnu.org/licenses/>.

if isfield(options_,'occbin') && ((options_.occbin.smoother.status && options_.occbin.smoother.inversion_filter) || (options_.occbin.likelihood.status && options_.occbin.likelihood.inversion_filter))
    warning('off','MATLAB:nearlySingularMatrix')    
end
%% set bounds and parameter names if not already set
n_params=size(start_par_value,1);
if isempty(bounds)
    bounds=[-Inf(n_params,1) Inf(n_params,1)];
end

if isempty(parameter_names)
    parameter_names=cellstr([repmat('parameter ',n_params,1),num2str((1:n_params)')]);
end

%% initialize function outputs
hessian_mat=[];
Scale=[];
exitflag=1;
fval=NaN;
opt_par_values=NaN(size(start_par_value));
new_rat_hess_info=[];

switch minimizer_algorithm
  case 1
    if isoctave && ~user_has_octave_forge_package('optim', '1.6')
        error('Optimization algorithm 1 is not available, you need to install the optim Forge package, version 1.6 or above')
    elseif ~isoctave && ~user_has_matlab_license('optimization_toolbox')
        error('Optimization algorithm 1 requires the Optimization Toolbox')
    end
    % Set default optimization options for fmincon.
    if ~isoctave
        optim_options = optimoptions('fmincon','display','iter', 'MaxFunEvals',100000, 'TolFun',1e-8, 'TolX',1e-6);
    else
        optim_options = optimset('display','iter', 'MaxFunEvals',100000, 'TolFun',1e-8, 'TolX',1e-6);
    end
    if isoctave
        % Under Octave, use the active-set (i.e. octave_sqp of nonlin_min)
        % algorithm. On a simple example (fs2000.mod), the default algorithm
        % is not able to even move away from the initial point.
        optim_options = optimset(optim_options, 'Algorithm','active-set');
    end
    if options_.analytic_derivation || (isfield(options_,'mom') && options_.mom.analytic_jacobian==1)
        if ~isoctave
            optim_options = optimoptions(optim_options,'GradObj','on','TolX',1e-7); %alter default TolX
        else
            optim_options = optimset('GradObj','on','TolX',1e-7);
        end
    end
    if ~isempty(options_.optim_opt)
        if ~isoctave
            eval(['optim_options = optimoptions(optim_options,' options_.optim_opt ');']);
        else
            eval(['optim_options = optimset(optim_options,' options_.optim_opt ');']);
        end
    end
    if options_.silent_optimizer
        if ~isoctave
            optim_options = optimoptions(optim_options,'display','off');
        else
            optim_options = optimset(optim_options,'display','off');            
        end
    end
    if options_.analytic_derivation || (isfield(options_,'mom') && options_.mom.analytic_jacobian==1) %use wrapper
        func = @(x) analytic_gradient_wrapper(x,objective_function,varargin{:});
        if ~isoctave
            [opt_par_values,fval,exitflag,~,~,~,hessian_mat] = ...
                fmincon(func,start_par_value,[],[],[],[],bounds(:,1),bounds(:,2),[],optim_options);
        else
            % Under Octave, use a wrapper, since fmincon() does not have an 11th
            % arg. Also, only the first 4 output arguments are available.
            [opt_par_values,fval,exitflag] = ...
                fmincon(func,start_par_value,[],[],[],[],bounds(:,1),bounds(:,2),[],optim_options);
        end
    else
        if ~isoctave
            [opt_par_values,fval,exitflag,~,~,~,hessian_mat] = ...
                fmincon(objective_function,start_par_value,[],[],[],[],bounds(:,1),bounds(:,2),[],optim_options,varargin{:});
        else
            % Under Octave, use a wrapper, since fmincon() does not have an 11th
            % arg. Also, only the first 4 output arguments are available.
            func = @(x) objective_function(x,varargin{:});
            [opt_par_values,fval,exitflag] = ...
                fmincon(func,start_par_value,[],[],[],[],bounds(:,1),bounds(:,2),[],optim_options);
        end    
    end
    
  case 2
    %simulating annealing
    sa_options = options_.saopt;
    if ~isempty(options_.optim_opt)
        options_list = read_key_value_string(options_.optim_opt);
        for i=1:rows(options_list)
            switch options_list{i,1}
              case 'neps'
                sa_options.neps = options_list{i,2};
              case 'rt'
                sa_options.rt = options_list{i,2};
              case 'MaxIter'
                sa_options.MaxIter = options_list{i,2};
              case 'TolFun'
                sa_options.TolFun = options_list{i,2};
              case 'verbosity'
                sa_options.verbosity = options_list{i,2};
              case 'initial_temperature'
                sa_options.initial_temperature = options_list{i,2};
              case 'ns'
                sa_options.ns = options_list{i,2};
              case 'nt'
                sa_options.nt = options_list{i,2};
              case 'step_length_c'
                sa_options.step_length_c = options_list{i,2};
              case 'initial_step_length'
                sa_options.initial_step_length = options_list{i,2};
              otherwise
                warning(['solveopt: Unknown option (' options_list{i,1} ')!'])
            end
        end
    end
    if options_.silent_optimizer
        sa_options.verbosity = 0;
    end
    npar=length(start_par_value);
    [LB, UB]=set_bounds_to_finite_values(bounds, options_.huge_number);
    if sa_options.verbosity
        fprintf('\nNumber of parameters= %d, initial temperatur= %4.3f \n', npar,sa_options.initial_temperature);
        fprintf('rt=  %4.3f; TolFun=  %4.3f; ns=  %4.3f;\n',sa_options.rt,sa_options.TolFun,sa_options.ns);
        fprintf('nt=  %4.3f; neps=  %4.3f; MaxIter=  %d\n',sa_options.nt,sa_options.neps,sa_options.MaxIter);
        fprintf('Initial step length(vm): %4.3f; step_length_c: %4.3f\n', sa_options.initial_step_length,sa_options.step_length_c);
        fprintf('%-20s  %-6s    %-6s    %-6s\n','Name:', 'LB;','Start;','UB;');
        for pariter=1:npar
            fprintf('%-20s  %6.4f;   %6.4f;  %6.4f;\n',parameter_names{pariter}, LB(pariter),start_par_value(pariter),UB(pariter));
        end
    end
    sa_options.initial_step_length= sa_options.initial_step_length*ones(npar,1); %bring step length to correct vector size
    sa_options.step_length_c= sa_options.step_length_c*ones(npar,1); %bring step_length_c to correct vector size
    [opt_par_values, fval,exitflag] =...
        simulated_annealing(objective_function,start_par_value,sa_options,LB,UB,varargin{:});
  case 3
    if isoctave && ~user_has_octave_forge_package('optim')
        error('Optimization algorithm 3 requires the optim package')
    elseif ~isoctave && ~user_has_matlab_license('optimization_toolbox')
        error('Optimization algorithm 3 requires the Optimization Toolbox')
    end
    % Set default optimization options for fminunc.
    if ~isoctave
        optim_options = optimoptions('fminunc','display','iter','MaxFunEvals',100000,'TolFun',1e-8,'TolX',1e-6);
    else
        optim_options = optimset('display','iter','MaxFunEvals',100000,'TolFun',1e-8,'TolX',1e-6);
    end
    if ~isempty(options_.optim_opt)
        if ~isoctave
            eval(['optim_options = optimoptions(optim_options,' options_.optim_opt ');']);
        else
             eval(['optim_options = optimset(optim_options,' options_.optim_opt ');']);
        end
    end
    if options_.silent_optimizer
        if ~isoctave
            optim_options = optimoptions(optim_options,'display','off');
        else
            optim_options = optimset(optim_options,'display','off');            
        end
    end
    if options_.analytic_derivation || (isfield(options_,'mom') && options_.mom.analytic_jacobian==1)
        if ~isoctave
            optim_options = optimoptions(optim_options,'GradObj','on');
            func = @(x) analytic_gradient_wrapper(x,objective_function,varargin{:});
            [opt_par_values,fval,exitflag] = fminunc(func,start_par_value,optim_options);
        else
            optim_options = optimset(optim_options,'GradObj','on');
            % Under Octave, use a wrapper, since fminunc() does not have a 4th arg
            func = @(x) analytic_gradient_wrapper(x,objective_function,varargin{:});
            [opt_par_values,fval,exitflag] = fminunc(func,start_par_value,optim_options);
        end
    else
        if ~isoctave
            [opt_par_values,fval,exitflag] = fminunc(objective_function,start_par_value,optim_options,varargin{:});
        else
            % Under Octave, use a wrapper, since fminunc() does not have a 4th arg
            func = @(x) objective_function(x,varargin{:});
            [opt_par_values,fval,exitflag] = fminunc(func,start_par_value,optim_options);
        end
    end
  case 4
    % Set default options.
    H0 = 1e-4*eye(n_params);
    crit = options_.csminwel.tolerance.f;
    nit = options_.csminwel.maxiter;
    numgrad = options_.gradient_method;
    epsilon = options_.gradient_epsilon;
    Verbose = options_.csminwel.verbosity;
    Save_files = options_.csminwel.Save_files;
    % Change some options.
    if ~isempty(options_.optim_opt)
        options_list = read_key_value_string(options_.optim_opt);
        for i=1:rows(options_list)
            switch options_list{i,1}
              case 'MaxIter'
                nit = options_list{i,2};
              case 'InitialInverseHessian'
                H0 = eval(options_list{i,2});
              case 'TolFun'
                crit = options_list{i,2};
              case 'NumgradAlgorithm'
                numgrad = options_list{i,2};
              case 'NumgradEpsilon'
                epsilon = options_list{i,2};
              case 'verbosity'
                Verbose = options_list{i,2};
              case 'SaveFiles'
                Save_files = options_list{i,2};
              otherwise
                warning(['csminwel: Unknown option (' options_list{i,1} ')!'])
            end
        end
    end
    if options_.silent_optimizer
        Save_files = 0;
        Verbose = 0;
    end
    % Set flag for analytical gradient.
    if options_.analytic_derivation || (isfield(options_,'mom') && options_.mom.analytic_jacobian==1)
        analytic_grad=1;
    else
        analytic_grad=[];
    end
    % Call csminwell.
    [fval,opt_par_values,~,inverse_hessian_mat,~,~,exitflag] = ...
        csminwel1(objective_function, start_par_value, H0, analytic_grad, crit, nit, numgrad, epsilon, Verbose, Save_files, varargin{:});
    hessian_mat=inv(inverse_hessian_mat);
  case 5
    if isempty(prior_information) %mr_hessian requires it, but can be NaN
        prior_information.p2=NaN(n_params,1);
    end
    if options_.analytic_derivation
        analytic_grad=1;
        crit = options_.newrat.tolerance.f_analytic;
        newratflag = 0; %analytical Hessian
    else
        analytic_grad=0;
        crit=options_.newrat.tolerance.f;
        newratflag = options_.newrat.hess; %default
    end
    nit=options_.newrat.maxiter;
    robust = options_.newrat.robust;
    gstep_crit = options_.newrat.tolerance.gstep;
    gstep_crit_rel = options_.newrat.tolerance.gstep_rel;
    Verbose = options_.newrat.verbosity;
    Save_files = options_.newrat.Save_files;
    if ~isempty(options_.optim_opt)
        options_list = read_key_value_string(options_.optim_opt);
        for i=1:rows(options_list)
            switch options_list{i,1}
              case 'MaxIter'
                nit = options_list{i,2};
              case 'Hessian'
                flag=options_list{i,2};
                if options_.analytic_derivation && flag~=0
                    error('newrat: analytic_derivation is incompatible with numerical Hessian. Using analytic Hessian')
                else
                    newratflag=flag;
                end
              case 'robust'
                robust = options_list{i,2};
              case 'TolFun'
                crit = options_list{i,2};
              case 'TolGstep'
                gstep_crit = options_list{i,2};
              case 'TolGstepRel'
                gstep_crit_rel = options_list{i,2};
              case 'verbosity'
                Verbose = options_list{i,2};
              case 'SaveFiles'
                Save_files = options_list{i,2};
              otherwise
                warning(['newrat: Unknown option (' options_list{i,1} ')!'])
            end
        end
    end
    if options_.silent_optimizer
        Save_files = 0;
        Verbose = 0;
    end
    if isnan(gstep_crit)
        gstep_crit = crit;
    end
    hess_info.gstep=options_.gstep;
    hess_info.htol = gstep_crit;
    hess_info.htol_rel = gstep_crit_rel;
    hess_info.h1=options_.gradient_epsilon*ones(n_params,1);
    hess_info.robust=robust;
    % here we force 7th input argument (flagg) to be 0, since outer product
    % gradient Hessian is handled in dynare_estimation_1
    [opt_par_values,hessian_mat,~,fval,~,new_rat_hess_info] = newrat(objective_function,start_par_value,bounds,analytic_grad,crit,nit,0,Verbose,Save_files,hess_info,prior_information.p2,options_.gradient_epsilon,parameter_names,varargin{:});    %hessian_mat is the plain outer product gradient Hessian
    new_rat_hess_info.new_rat_hess_info = new_rat_hess_info;
    new_rat_hess_info.newratflag = newratflag;
  case 6
    if isempty(prior_information) %Inf will be reset
        prior_information.p2=Inf(n_params,1);
    end
    [opt_par_values, hessian_mat, Scale, fval] = gmhmaxlik(objective_function, start_par_value, ...
                                                      Initial_Hessian, options_.mh_jscale, bounds, prior_information.p2, options_.gmhmaxlik, options_.optim_opt, varargin{:});
  case 7
    % MATLAB's simplex (Optimization toolbox needed).
    if isoctave && ~user_has_octave_forge_package('optim')
        error('Option mode_compute=7 requires the optim package')
    elseif ~isoctave && ~user_has_matlab_license('optimization_toolbox')
        error('Option mode_compute=7 requires the Optimization Toolbox')
    end
    optim_options = optimset('display','iter','MaxFunEvals',1000000,'MaxIter',6000,'TolFun',1e-8,'TolX',1e-6);
    if ~isempty(options_.optim_opt)
        eval(['optim_options = optimset(optim_options,' options_.optim_opt ');']);
    end
    if options_.silent_optimizer
        optim_options = optimset(optim_options,'display','off');
    end
    if ~isoctave
        [opt_par_values,fval,exitflag] = fminsearch(objective_function,start_par_value,optim_options,varargin{:});
    else
        % Under Octave, use a wrapper, since fminsearch() does not have a
        % 4th arg.
        func = @(x) objective_function(x,varargin{:});
        [opt_par_values,fval,exitflag] = fminsearch(func,start_par_value,optim_options);
    end
  case 8
    % Dynare implementation of the simplex algorithm.
    simplexOptions = options_.simplex;
    if ~isempty(options_.optim_opt)
        options_list = read_key_value_string(options_.optim_opt);
        for i=1:rows(options_list)
            switch options_list{i,1}
              case 'MaxIter'
                simplexOptions.maxiter = options_list{i,2};
              case 'TolFun'
                simplexOptions.tolerance.f = options_list{i,2};
              case 'TolX'
                simplexOptions.tolerance.x = options_list{i,2};
              case 'MaxFunEvals'
                simplexOptions.maxfcall = options_list{i,2};
              case 'MaxFunEvalFactor'
                simplexOptions.maxfcallfactor = options_list{i,2};
              case 'InitialSimplexSize'
                simplexOptions.delta_factor = options_list{i,2};
              case 'verbosity'
                simplexOptions.verbosity = options_list{i,2};
              otherwise
                warning(['simplex: Unknown option (' options_list{i,1} ')!'])
            end
        end
    end
    if options_.silent_optimizer
        simplexOptions.verbosity = 0;
    end
    [opt_par_values,fval,exitflag] = simplex_optimization_routine(objective_function,start_par_value,simplexOptions,parameter_names,varargin{:});
  case 9
    % Set defaults
    H0 = (bounds(:,2)-bounds(:,1))*0.2;
    H0(~isfinite(H0)) = 0.01;
    while max(H0)/min(H0)>1e6 %make sure initial search volume (SIGMA) is not badly conditioned
        H0(H0==max(H0))=0.9*H0(H0==max(H0));
    end
    cmaesOptions = options_.cmaes;
    cmaesOptions.LBounds = bounds(:,1);
    cmaesOptions.UBounds = bounds(:,2);
    % Modify defaults
    if ~isempty(options_.optim_opt)
        options_list = read_key_value_string(options_.optim_opt);
        for i=1:rows(options_list)
            switch options_list{i,1}
              case 'MaxIter'
                cmaesOptions.MaxIter = options_list{i,2};
              case 'TolFun'
                cmaesOptions.TolFun = options_list{i,2};
              case 'TolX'
                cmaesOptions.TolX = options_list{i,2};
              case 'MaxFunEvals'
                cmaesOptions.MaxFunEvals = options_list{i,2};
              case 'verbosity'
                if options_list{i,2}==0
                    cmaesOptions.DispFinal  = 'off';   % display messages like initial and final message';
                    cmaesOptions.DispModulo = '0';   % [0:Inf], disp messages after every i-th iteration';
                end
              case 'SaveFiles'
                if options_list{i,2}==0
                    cmaesOptions.SaveVariables='off';
                    cmaesOptions.LogModulo = '0';    % [0:Inf] if >1 record data less frequently after gen=100';
                    cmaesOptions.LogTime   = '0';    % [0:100] max. percentage of time for recording data';
                end
              case 'CMAESResume'
                if options_list{i,2}==1
                    cmaesOptions.Resume = 'yes';
                end
              otherwise
                warning(['cmaes: Unknown option (' options_list{i,1}  ')!'])
            end
        end
    end
    if options_.silent_optimizer
        cmaesOptions.DispFinal  = 'off';   % display messages like initial and final message';
        cmaesOptions.DispModulo = '0';   % [0:Inf], disp messages after every i-th iteration';
        cmaesOptions.SaveVariables='off';
        cmaesOptions.LogModulo = '0';    % [0:Inf] if >1 record data less frequently after gen=100';
        cmaesOptions.LogTime   = '0';    % [0:100] max. percentage of time for recording data';
    end
    warning('off','CMAES:NonfinitenessRange');
    warning('off','CMAES:InitialSigma');
    [~, ~, ~, ~, ~, BESTEVER] = cmaes(func2str(objective_function),start_par_value,H0,cmaesOptions,varargin{:});
    opt_par_values=BESTEVER.x;
    fval=BESTEVER.f;
  case 10
    simpsaOptions = options_.simpsa;
    if ~isempty(options_.optim_opt)
        options_list = read_key_value_string(options_.optim_opt);
        for i=1:rows(options_list)
            switch options_list{i,1}
              case 'MaxIter'
                simpsaOptions.MAX_ITER_TOTAL = options_list{i,2};
              case 'TolFun'
                simpsaOptions.TOLFUN = options_list{i,2};
              case 'TolX'
                tolx = options_list{i,2};
                if tolx<0
                    simpsaOptions = rmfield(simpsaOptions,'TOLX'); % Let simpsa choose the default.
                else
                    simpsaOptions.TOLX = tolx;
                end
              case 'EndTemparature'
                simpsaOptions.TEMP_END = options_list{i,2};
              case 'MaxFunEvals'
                simpsaOptions.MAX_FUN_EVALS = options_list{i,2};
              case 'verbosity'
                if options_list{i,2} == 0
                    simpsaOptions.DISPLAY = 'none';
                else
                    simpsaOptions.DISPLAY = 'iter';
                end
              otherwise
                warning(['simpsa: Unknown option (' options_list{i,1}  ')!'])
            end
        end
    end
    if options_.silent_optimizer
        simpsaOptions.DISPLAY = 'none';
    end
    simpsaOptionsList = options2cell(simpsaOptions);
    simpsaOptions = simpsaset(simpsaOptionsList{:});
    [LB, UB]=set_bounds_to_finite_values(bounds, options_.huge_number);
    [opt_par_values, fval, exitflag] = simpsa(func2str(objective_function),start_par_value,LB,UB,simpsaOptions,varargin{:});
  case 11
    % waiting for validation 
    %    options_.cova_compute = 0;
    %    subvarargin = [varargin(1), varargin(3:6), varargin(8)];
    %    opt_par_values = online_auxiliary_filter(start_par_value, subvarargin{:});
    warning('Online particle filter is no more available with mode_compute=11; use posterior_sampler with option online')
  case 12
    if isoctave
        error('Option mode_compute=12 is not available under Octave')
    elseif ~user_has_matlab_license('GADS_Toolbox')
        error('Option mode_compute=12 requires the Global Optimization Toolbox')
    end
    tmp = transpose([fieldnames(options_.particleswarm), struct2cell(options_.particleswarm)]);
    particleswarmOptions = optimoptions(@particleswarm);
    particleswarmOptions = optimoptions(particleswarmOptions, tmp{:});
    if ~isempty(options_.optim_opt)
        options_list = read_key_value_string(options_.optim_opt);
        SupportedListOfOptions = {'CreationFcn', 'Display', 'DisplayInterval', 'FunctionTolerance', ...
                                  'FunValCheck', 'HybridFcn', 'InertiaRange', 'InitialSwarmMatrix', 'InitialSwarmSpan', ...
                                  'MaxIterations', 'MaxStallIterations', 'MaxStallTime', 'MaxTime', ...
                                  'MinNeighborsFraction', 'ObjectiveLimit', 'OutputFcn', 'PlotFcn', 'SelfAdjustmentWeight', ...
                                  'SocialAdjustmentWeight', 'SwarmSize', 'UseParallel', 'UseVectorized'};
        for i=1:rows(options_list)
            if ismember(options_list{i,1}, SupportedListOfOptions)
                particleswarmOptions = optimoptions(particleswarmOptions, options_list{i,1}, options_list{i,2});
            else
                warning(['particleswarm: Unknown option (' options_list{i,1} ')!'])
            end
        end
    end
    % Get number of instruments.
    numberofvariables = length(start_par_value);
    % Set objective function.
    objfun = @(x) objective_function(x, varargin{:});
    if ischar(particleswarmOptions.SwarmSize)
        particleswarmOptions.SwarmSize = eval(particleswarmOptions.SwarmSize);
    end
    if isempty(particleswarmOptions.InitialSwarmMatrix)
        fprintf('particleswarm: inititalizing swarm matrix...')
        particleswarmOptions.InitialSwarmMatrix = zeros(particleswarmOptions.SwarmSize, numberofvariables);
        p = 1;
        FVALS = zeros(particleswarmOptions.SwarmSize, 1);
        while p<=particleswarmOptions.SwarmSize
            if p == 1 % first particle exactly at initial values (has been checked already)
                candidate = start_par_value;
            else
                candidate = zeros(numberofvariables, 1);
                for i = 1:numberofvariables
                    % compute local "delta" around the start
                    param_range = bounds(i,2) - bounds(i,1);
                    if isfinite(param_range)
                        % for bounded parameters use 10% of range
                        param_delta = 0.1 * param_range;
                    else
                        % for unbounded parameters use 20% of initial parameter value
                        param_delta = 0.2 * abs(start_par_value(i));
                        if param_delta == 0
                            param_delta = 0.1; % if initial parameter value is zero
                        end
                    end
                    % uniform draw in [start - delta, start + delta]
                    candidate(i) = start_par_value(i) + (2*rand - 1) * param_delta;
                    % enforce hard bounds
                    candidate(i) = max(candidate(i), bounds(i,1));
                    candidate(i) = min(candidate(i), bounds(i,2));
                end
            end
            [fval, ~, exit_flag] = objfun(candidate);
            if exit_flag && ~isinf(fval)
                particleswarmOptions.InitialSwarmMatrix(p,:) = transpose(candidate);
                FVALS(p) = fval;
                p = p + 1;
            end
        end
        fprintf('done!\n');
    end
    % particleswarm errors if objective function is inf, so we use penalized objective function
    % and set the base penalty to the worst value of the objective function in the InitialSwarmMatrix
    TMP = [particleswarmOptions.InitialSwarmMatrix, FVALS];
    TMP = sortrows(TMP, length(start_par_value)+1);
    penalty = TMP(end,end);
    % Define penalized objective.
    objfun = @(x) penalty_objective_function(x, objective_function, penalty, varargin{:});
    % Minimize the penalized objective (note that the penalty is not updated).
    [opt_par_values, fval, exitflag] = particleswarm(objfun, length(start_par_value), bounds(:,1), bounds(:,2), particleswarmOptions);
    opt_par_values = opt_par_values(:);
  case 13
    % MATLAB's lsqnonlin (Optimization toolbox needed).
    if ~isfield(options_,'mom')
        error('Option mode_compute=13 is available only for method_of_moments estimation.')
    end
    if isoctave && ~user_has_octave_forge_package('optim')
        error('Option mode_compute=13 requires the optim package')
    elseif ~isoctave && ~user_has_matlab_license('optimization_toolbox')
        error('Option mode_compute=13 requires the Optimization Toolbox')
    end
    if ~isoctave
        optim_options = optimoptions('lsqnonlin','display','iter','MaxFunEvals',5000,'MaxIter',5000,'TolFun',1e-6,'TolX',1e-6);
    else
        optim_options = optimset('display','iter','MaxFunEvals',5000,'MaxIter',5000,'TolFun',1e-6,'TolX',1e-6);
    end
    if ~isempty(options_.optim_opt)
        if ~isoctave
            eval(['optim_options = optimoptions(optim_options,' options_.optim_opt ');']);
        else
            eval(['optim_options = optimset(optim_options,' options_.optim_opt ');']);
        end
    end
    if options_.silent_optimizer
        optim_options.Display='off';
    end
    if options_.analytic_derivation || (isfield(options_,'mom') && options_.mom.analytic_jacobian==1)
        if isoctave
            optim_options.Jacobian = 'on';
        else
            optim_options.SpecifyObjectiveGradient = true;
        end
        func = @(x) analytic_gradient_wrapper(x,objective_function,varargin{:});
        [opt_par_values,~,fval,exitflag] = ...
            lsqnonlin(func,start_par_value,bounds(:,1),bounds(:,2),optim_options);
    else
        if ~isoctave
            [opt_par_values,~,fval,exitflag] = lsqnonlin(objective_function,start_par_value,bounds(:,1),bounds(:,2),optim_options,varargin{:});
        else
            % Under Octave, use a wrapper, since lsqnonlin() does not have a 6th arg
            func = @(x)objective_function(x,varargin{:});
            [opt_par_values,~,fval,exitflag] = lsqnonlin(func,start_par_value,bounds(:,1),bounds(:,2),optim_options);
        end
    end    
  case 101
    solveoptoptions = options_.solveopt;
    if ~isempty(options_.optim_opt)
        options_list = read_key_value_string(options_.optim_opt);
        for i=1:rows(options_list)
            switch options_list{i,1}
              case 'TolX'
                solveoptoptions.TolX = options_list{i,2};
              case 'TolFun'
                solveoptoptions.TolFun = options_list{i,2};
              case 'MaxIter'
                solveoptoptions.MaxIter = options_list{i,2};
              case 'verbosity'
                solveoptoptions.verbosity = options_list{i,2};
              case 'SpaceDilation'
                solveoptoptions.SpaceDilation = options_list{i,2};
              case 'LBGradientStep'
                solveoptoptions.LBGradientStep = options_list{i,2};
              otherwise
                warning(['solveopt: Unknown option (' options_list{i,1} ')!'])
            end
        end
    end
    if options_.silent_optimizer
        solveoptoptions.verbosity = 0;
    end
    if options_.analytic_derivation || (isfield(options_,'mom') && options_.mom.analytic_jacobian==1)
        func = @(x) analytic_gradient_wrapper(x,objective_function,varargin{:});
        [opt_par_values,fval]=solvopt(start_par_value,func,1,[],[],solveoptoptions);
    else
        [opt_par_values,fval]=solvopt(start_par_value,objective_function,[],[],[],solveoptoptions,varargin{:});
    end
  case 102
    if isoctave
        error('Optimization algorithm 102 is not available under Octave')
    elseif ~user_has_matlab_license('GADS_Toolbox')
        error('Optimization algorithm 102 requires the Global Optimization Toolbox')
    end
    % Set default optimization options for simulannealbnd.
    if ~isoctave
        optim_options = optimoptions('simulannealbnd','display','iter','TolFun',1e-8);
    else
        optim_options = saoptimset('display','iter','TolFun',1e-8);
    end
    if ~isempty(options_.optim_opt)
        if ~isoctave
            eval(['optim_options = optimoptions(optim_options,' options_.optim_opt ');']);
        else
            eval(['optim_options = saoptimset(optim_options,' options_.optim_opt ');']);
        end
    end
    if options_.silent_optimizer
        if ~isoctave
            optim_options = optimoptions(optim_options,'display','off');
        else
            optim_options = saoptimset(optim_options,'display','off');
        end
    end
    func = @(x)objective_function(x,varargin{:});
    [opt_par_values,fval,exitflag] = simulannealbnd(func,start_par_value,bounds(:,1),bounds(:,2),optim_options);
  otherwise
    if ischar(minimizer_algorithm)
        if exist(minimizer_algorithm)
            [opt_par_values, fval, exitflag] = feval(minimizer_algorithm,objective_function,start_par_value,varargin{:});
        else
            error('No minimizer with the provided name detected.')
        end
    else
        error(['Optimization algorithm ' int2str(minimizer_algorithm) ' is unknown!'])
    end
end

if isfield(options_,'occbin') && ((options_.occbin.smoother.status && options_.occbin.smoother.inversion_filter) || (options_.occbin.likelihood.status && options_.occbin.likelihood.inversion_filter))
    warning('on','MATLAB:nearlySingularMatrix')
end

end % dynare_minimize_objective

function [LB, UB] = set_bounds_to_finite_values(bounds, huge_number)
LB = bounds(:,1);
LB(isinf(LB)) = -huge_number;
UB = bounds(:,2);
UB(isinf(UB)) = huge_number;
end
