function [opt_par_values,fval,exitflag,hessian_mat,Scale,new_rat_hess_info,optimization_info]=dynare_minimize_objective(objective_function,start_par_value,minimizer_algorithm,options_,bounds,parameter_names,prior_information,Initial_Hessian,varargin)
% [opt_par_values,fval,exitflag,hessian_mat,Scale,new_rat_hess_info,optimization_info]=dynare_minimize_objective(objective_function,start_par_value,minimizer_algorithm,options_,bounds,parameter_names,prior_information,Initial_Hessian,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
%   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
%   Scale               [scalar double]                     scaling parameter returned by algorithm 6
%   new_rat_hess_info   [MATLAB structure]                  options related to mode_compute=5 settings
%   optimization_info   [MATLAB structure]                  optimization information on runtime, iterations, function count, exitflag, and message
%
% SPECIAL REQUIREMENTS
%   none.

% Copyright © 2014-2026 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_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
    opt_runtime_start = tic;
    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,output,~,~,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,output] = ...
                fmincon(func,start_par_value,[],[],[],[],bounds(:,1),bounds(:,2),[],optim_options);
        end
    else
        if ~isoctave
            [opt_par_values,fval,exitflag,output,~,~,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,output] = ...
                fmincon(func,start_par_value,[],[],[],[],bounds(:,1),bounds(:,2),[],optim_options);
        end
    end
    optimization_info.runtime = toc(opt_runtime_start);
    optimization_info.exitflag = exitflag;
    if ~isoctave
        optimization_info.funcCount = output.funcCount;
        optimization_info.iterations = output.iterations;
        optimization_info.message = strrep(output.message, newline, ' ');
    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_runtime_start = tic;
    [opt_par_values, fval, exitflag, n_accepted_draws, n_total_draws, n_out_of_bounds_draws, ~, ~, message] = ...
        simulated_annealing(objective_function,start_par_value,sa_options,LB,UB,varargin{:});
    optimization_info.runtime = toc(opt_runtime_start);
    if ~options_.silent_optimizer
        fprintf('Accepted draws = %d\nTotal draws = %d\nOut of bounds draws = %d\n', n_accepted_draws, n_total_draws, n_out_of_bounds_draws);
    end
    optimization_info.iterations = n_total_draws;
    optimization_info.funcCount = n_total_draws;
    optimization_info.exitflag = exitflag;
    optimization_info.message = strrep(message, newline, ' ');

  case 3
    if isoctave && ~user_has_octave_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
    opt_runtime_start = tic;
    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,output] = 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,output] = fminunc(func,start_par_value,optim_options);
        end
    else
        if ~isoctave
            [opt_par_values,fval,exitflag,output] = 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,output] = fminunc(func,start_par_value,optim_options);
        end
    end
    optimization_info.runtime = toc(opt_runtime_start);
    optimization_info.iterations = output.iterations;
    optimization_info.funcCount = output.funcCount;
    optimization_info.exitflag = exitflag;
    if ~isoctave
        optimization_info.message = strrep(output.message, newline, ' ');
    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.
    opt_runtime_start = tic;
    [fval,opt_par_values,~,inverse_hessian_mat,iterations,funcCount,exitflag,message] = ...
        csminwel1(objective_function, start_par_value, H0, analytic_grad, crit, nit, numgrad, epsilon, Verbose, Save_files, varargin{:});
    optimization_info.runtime = toc(opt_runtime_start);
    optimization_info.iterations = iterations;
    optimization_info.funcCount = funcCount;
    optimization_info.exitflag = exitflag;
    optimization_info.message = strrep(message, newline, ' ');
    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_runtime_start = tic;
    [opt_par_values,hessian_mat,~,fval,~,new_rat_hess_info,iterations,funcCount,exitflag,message] = ...
        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
    optimization_info.runtime = toc(opt_runtime_start);
    optimization_info.iterations = iterations;
    optimization_info.funcCount = funcCount;
    optimization_info.exitflag = exitflag;
    optimization_info.message = strrep(message, newline, ' ');
    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
    if options_.silent_optimizer
        options_.gmhmaxlik.silent = true;
    end
    if options_.console_mode
        options_.gmhmaxlik.console_mode = true;
    else
        options_.gmhmaxlik.console_mode = false;
    end
    opt_runtime_start = tic;
    [opt_par_values, hessian_mat, Scale, fval, funcCount] = ...
        gmhmaxlik(objective_function, start_par_value, Initial_Hessian, options_.mh_jscale, bounds, prior_information.p2, options_.gmhmaxlik, options_.optim_opt, varargin{:});
    optimization_info.runtime = toc(opt_runtime_start);
    optimization_info.iterations = NaN;
    optimization_info.funcCount = funcCount;
    optimization_info.exitflag = 1;
    optimization_info.message = 'Optimization successful.';

  case 7
    % MATLAB's simplex (Optimization toolbox needed).
    if isoctave && ~user_has_octave_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
    opt_runtime_start = tic;
    if ~isoctave
        [opt_par_values,fval,exitflag,output] = 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,output] = fminsearch(func,start_par_value,optim_options);
    end
    optimization_info.runtime = toc(opt_runtime_start);
    optimization_info.iterations = output.iterations;
    optimization_info.funcCount = output.funcCount;
    optimization_info.exitflag = exitflag;
    if ~isoctave
        optimization_info.message = strrep(output.message, newline, ' ');
    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_runtime_start = tic;
    [opt_par_values,fval,exitflag,iterations,funcCount,message] = ...
        simplex_optimization_routine(objective_function,start_par_value,simplexOptions,parameter_names,varargin{:});
    optimization_info.runtime = toc(opt_runtime_start);
    optimization_info.iterations = iterations;
    optimization_info.funcCount = funcCount;
    optimization_info.exitflag = exitflag;
    optimization_info.message = strrep(message, newline, ' ');

  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');
    opt_runtime_start = tic;
    [~, ~, ~, message, output, BESTEVER] = cmaes(func2str(objective_function),start_par_value,H0,cmaesOptions,varargin{:});
    optimization_info.runtime = toc(opt_runtime_start);
    if any(strcmp(message, 'tolfun')) || any(strcmp(message, 'tolx'))
        exitflag = 1;
    elseif strcmp(message,'warnconditioncov')
        exitflag = 2;
    else
        exitflag = -1;
    end
    optimization_info.iterations = output.countiter;
    optimization_info.funcCount = output.evals + 1; % need to add one more for initial function evaluation
    optimization_info.exitflag = exitflag;
    optimization_info.message = strjoin(message, ' ');
    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 'EndTemperature'
                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_runtime_start = tic;
    [opt_par_values, fval, exitflag, output] = ...
        simpsa(func2str(objective_function),start_par_value,LB,UB,simpsaOptions,varargin{:});
    optimization_info.runtime = toc(opt_runtime_start);
    optimization_info.iterations = output.nITERATIONS;
    optimization_info.funcCount = output.nFUN_EVALS;
    optimization_info.exitflag = exitflag;
    optimization_info.message = strrep(output.MESSAGE, newline, ' ');

  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
    if options_.silent_optimizer
        particleswarmOptions = optimoptions(particleswarmOptions,'Display','off');
    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)
        if ~options_.silent_optimizer
            fprintf('particleswarm: inititalizing swarm matrix...');
        end
        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
        if ~options_.silent_optimizer
            fprintf('done!\n');
        end
    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.
    opt_runtime_start = tic;
    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, output] = particleswarm(objfun, length(start_par_value), bounds(:,1), bounds(:,2), particleswarmOptions);
    optimization_info.runtime = toc(opt_runtime_start);
    optimization_info.iterations = output.iterations;
    optimization_info.funcCount = output.funccount;
    optimization_info.exitflag = exitflag;
    optimization_info.message = strrep(output.message, newline, ' ');
    optimization_info.rngstate = output.rngstate;
    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_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
    opt_runtime_start = tic;
    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,output] = ...
            lsqnonlin(func,start_par_value,bounds(:,1),bounds(:,2),optim_options);
    else
        if ~isoctave
            [opt_par_values,~,fval,exitflag,output] = 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,output] = lsqnonlin(func,start_par_value,bounds(:,1),bounds(:,2),optim_options);
        end
    end
    optimization_info.runtime = toc(opt_runtime_start);
    optimization_info.exitflag = exitflag;
    if ~isoctave
        optimization_info.iterations = output.iterations;
        optimization_info.funcCount = output.funcCount;
        optimization_info.message = strrep(output.message, newline, ' ');
    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 = -1;
    end
    opt_runtime_start = tic;
    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,exitflag,n_f_evals,n_grad_evals,~,~,iterations,message]=solvopt(start_par_value,func,1,[],[],solveoptoptions);
        n_grad_evals = n_grad_evals - 1; % remove double count of the initial function evaluation with analytic gradient
    else
        [opt_par_values,fval,exitflag,n_f_evals,n_grad_evals,~,~,iterations,message]=solvopt(start_par_value,objective_function,[],[],[],solveoptoptions,varargin{:});
    end
    if exitflag > 0 % positive numbers are equal to number of iterations
      exitflag = 1;
    end
    optimization_info.runtime = toc(opt_runtime_start);
    optimization_info.iterations = iterations;
    optimization_info.funcCount = n_f_evals + n_grad_evals;
    optimization_info.exitflag = exitflag;
    optimization_info.message = strrep(message, newline, ' ');

  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_runtime_start = tic;
    [opt_par_values,fval,exitflag,output] = simulannealbnd(func,start_par_value,bounds(:,1),bounds(:,2),optim_options);
    optimization_info.runtime = toc(opt_runtime_start);
    optimization_info.iterations = output.iterations;
    optimization_info.funcCount = output.funccount;
    optimization_info.exitflag = exitflag;
    optimization_info.message = strrep(output.message, newline, ' ');
    optimization_info.rngstate = output.rngstate;

  otherwise
    if ischar(minimizer_algorithm)
        if exist(minimizer_algorithm,'file') %do not replace by isfile, needs to be flexible
            opt_runtime_start = tic;
            [opt_par_values, fval, exitflag, output] = feval(minimizer_algorithm,objective_function,start_par_value,varargin{:});
            optimization_info.runtime = toc(opt_runtime_start);
            if isfield(output, 'iterations')
                optimization_info.iterations = output.iterations;
            else
                optimization_info.iterations = [];
            end
            if isfield(output, 'funcCount')
                optimization_info.funcCount = output.funcCount;
            else
                optimization_info.funcCount = [];
            end
            optimization_info.exitflag = exitflag;
            if isfield(output, 'message')
                optimization_info.message = strrep(output.message, newline, ' ');
            else
                optimization_info.message = [];
            end
        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
