function nls(eqname, params, data, range, optimizer, varargin) % Estimates the parameters of a nonlinear backward equation by Nonlinear Least Squares. % % INPUTS % - eqname [string] Name of the equation. % - params [struct] Describes the parameters to be estimated. % - data [dseries] Database for the estimation % - range [dates] Range of dates for the estimation. % - optimizer [string] Set the optimization algorithm. Possible values % are 'fmincon', 'fminunc', 'csminwel', % 'fminsearch', 'simplex' and % 'annealing'. Default is 'csminwel'. % - varargin List of key/value pairs, each key must be a % string and values can be strings or real numbers. % % OUTPUTS % - none % % REMARKS % [1] The estimation results are printed in the command line window, and the % parameters are updated accordingly in M_.params. % [2] The second input is a structure. Each fieldname corresponds to the % name of an estimated parameter, the value of the field is the initial % guess used for the estimation (by NLS). % [3] The third input is a dseries object which must at least contain all % the variables appearing in the estimated equation. The residual of the % equation must have NaN values in the object. % [4] It is assumed that the residual is additive. % Copyright © 2021 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 . global M_ oo_ options_ % Read equation to be estimated. [LHS, RHS] = get_lhs_and_rhs(eqname, M_, true); % Without introducing the auxiliaries [lhs, rhs] = get_lhs_and_rhs(eqname, M_); % With auxiliaries. islaggedvariables = ~isempty(regexp(rhs, '\w+\(-(\d+)\)', 'match')); % Do we have lags on the RHS? if ~isempty(regexp(rhs, '\w+\(\d+\)', 'match')) error('Cannot estimate an equation ith leads.') end % Update database (for auxiliaries) data = feval([M_.fname '.dynamic_set_auxiliary_series'], data, M_.params); % % Check estimation range. % levels = regexp(RHS, '[\w+]\(-(?\d)\)', 'names'); diffs = regexp(RHS, '[diff\(\w+\)', 'match'); diffl = regexp(RHS, 'diff\([\w+]\(-(?\d)\)\)', 'names'); ddiffs = regexp(RHS, '[diff\(diff\(\w+\)\)', 'match'); ddiffl = regexp(RHS, 'diff\(diff\([\w+]\(-(?\d)\)\)\)', 'names'); if isempty(levels) llag = 0; else llag = max(cellfun(@str2num, {levels(:).lags})); end if ~isempty(diffs) llag = max(1, llag); end if ~isempty(ddiffs) llag = max(2, llag); end if ~isempty(diffl) llag = max(max(cellfun(@str2num, {diffl(:).lags}))+1, llag); end if ~isempty(ddiffl) llag = max(max(cellfun(@str2num, {ddiffl(:).lags}))+2, llag); end minperiod = data.dates(llag+1); if minperiod>range(1) error('Wrong range. Initial period cannot be smaller than %s', char(minperiod)) end if range(end)>data.dates(end) error('Wrong range. Terminal period cannot be greater than %s', char(data.dates(end))) end % Copy (sub)sample data in a matrix. DATA = data([range(1)-1, range]).data; % Get the parameters and variables in the equation. [pnames, enames, xnames, pid, eid, xid] = get_variables_and_parameters_in_equation(lhs, rhs, M_); % % Check that we have only one residual in the equation (we may have observed exogenous variables). % % Set the number of exogenous variables. xnbr = length(xnames); % Test if we have a residual and get its name (-> rname). if isequal(xnbr, 1) rname = M_.exo_names{strcmp(xnames{1}, M_.exo_names)}; if ~all(isnan(data{xnames{1}}.data)) error('The residual (%s) must have NaN values in the provided database.', xnames{1}) end else % We have observed exogenous variables in the estimated equation. tmp = data{xnames{:}}(range).data; idx = find(all(~isnan(tmp))); % Indices for the observed exogenous variables. if isequal(length(idx), length(xnames)) error('There is no residual in this equation, all the exogenous variables are observed.') else if length(idx)']); if islaggedvariables jlag = regexp(rhs, ['\<', objNames{i}, '\(-1\)']); if ~isempty(jlag) rhs = regexprep(rhs, ['\<' objNames{i} '\(-1\)'], sprintf('data(1:end-1,%u)', k)); end if ~isempty(setdiff(j, jlag)) rhs = regexprep(rhs, ['\<' objNames{i} '\>'], sprintf('data(2:end,%u)', k)); end else rhs = regexprep(rhs, ['\<' objNames{i} '\>'], sprintf('data(:,%u)', k)); end if contains(lhs, objNames{i}) if islaggedvariables lhs = strrep(lhs, objNames{i}, sprintf('data(2:end,%u)', k)); else lhs = strrep(lhs, objNames{i}, sprintf('data(:,%u)', k)); end end end end % Allow elementwise operations rhs = strrep(rhs, '^', '.^'); rhs = strrep(rhs, '/', './'); rhs = strrep(rhs, '*', '.*'); % Get list and indices of estimated parameters. pnames_ = fieldnames(params); ipnames_ = zeros(size(pnames_)); for i=1:length(ipnames_) ipnames_(i) = find(strcmp(pnames_{i}, M_.param_names)); end % Create a routine for evaluating the residuals of the nonlinear model fun = ['r_' eqname]; fid = fopen(['+' M_.fname filesep() fun '.m'], 'w'); fprintf(fid, 'function r = %s(params, data, DynareModel, DynareOutput)\n', fun); fprintf(fid, '\n'); fprintf(fid, '%% Evaluates the residuals for equation %s.\n', eqname); fprintf(fid, '%% File created by Dynare (%s).\n', datetime); fprintf(fid, '\n'); for i=1:length(ipnames_) fprintf(fid, 'DynareModel.params(%u) = params(%u);\n', ipnames_(i), i); end fprintf(fid, '\n'); fprintf(fid, 'r = %s-(%s);\n', lhs, rhs); fclose(fid); % Create a routine for evaluating the sum of squared residuals of the nonlinear model fun = ['ssr_' eqname]; fid = fopen(['+' M_.fname filesep() fun '.m'], 'w'); fprintf(fid, 'function [s, fake1, fake2, fake3, fake4] = %s(params, data, DynareModel, DynareOutput)\n', fun); fprintf(fid, '\n'); fprintf(fid, '%% Evaluates the sum of square residuals for equation %s.\n', eqname); fprintf(fid, '%% File created by Dynare (%s).\n', datetime); fprintf(fid, '\n'); fprintf(fid, 'fake1 = 0;\n'); fprintf(fid, 'fake2 = [];\n'); fprintf(fid, 'fake3 = [];\n'); fprintf(fid, 'fake4 = [];\n'); fprintf(fid, '\n'); for i=1:length(ipnames_) fprintf(fid, 'DynareModel.params(%u) = params(%u);\n', ipnames_(i), i); end fprintf(fid, '\n'); fprintf(fid, 'r = %s-(%s);\n', lhs, rhs); fprintf(fid, 's = r''*r;\n'); fclose(fid); % Create a function handle returning the sum of square residuals for a given vector of parameters. ssrfun = @(p) feval([M_.fname '.ssr_' eqname], p, DATA, M_, oo_); % Create a function handle returning the sum of square residuals for a given vector of parameters. resfun = @(p) feval([M_.fname '.r_' eqname], p, DATA, M_, oo_); % % Prepare call to the optimization routine. % % Set initial condition. params0 = cell2mat(struct2cell(params)); is_gauss_newton = false; is_lsqnonlin = false; if nargin>4 && (isequal(optimizer, 'GaussNewton') || isequal(optimizer, 'lsqnonlin')) switch optimizer case 'GaussNewton' is_gauss_newton = true; case 'lsqnonlin' is_lsqnonlin = true; end end % Set defaults for optimizers bounds = []; parameter_names = []; % Set optimizer routine. if nargin<5 || isempty(optimizer) % Use csminwel by default. minalgo = 4; else switch optimizer case 'GaussNewton' % Nothing to do here. case 'lsqnonlin' bounds = ones(length(params0),1)*[-Inf,Inf]; case 'fmincon' if isoctave && ~user_has_octave_forge_package('optim', '1.6') error('Optimization algorithm ''fmincon'' requires the optim package, version 1.6 or higher') elseif ~isoctave && ~user_has_matlab_license('optimization_toolbox') error('Optimization algorithm ''fmincon'' requires the Optimization Toolbox') end minalgo = 1; bounds = ones(length(params0),1)*[-Inf,Inf]; case 'fminunc' if isoctave && ~user_has_octave_forge_package('optim') error('Optimization algorithm ''fminunc'' requires the optim package') elseif ~isoctave && ~user_has_matlab_license('optimization_toolbox') error('Optimization algorithm ''fminunc'' requires the Optimization Toolbox') end minalgo = 3; case 'csminwel' minalgo = 4; case 'fminsearch' if isoctave && ~user_has_octave_forge_package('optim') error('Optimization algorithm ''fminsearch'' requires the optim package') elseif ~isoctave && ~user_has_matlab_license('optimization_toolbox') error('Optimization algorithm ''fminsearch'' requires the Optimization Toolbox') end minalgo = 7; case 'simplex' minalgo = 8; case 'annealing' minalgo = 2; bounds = ones(length(params0),1)*[-Inf,Inf]; parameter_names = fieldnames(params); case 'particleswarm' if isoctave error('Optimization ''particleswarm'' is not available under Octave') elseif ~user_has_matlab_license('global_optimization_toolbox') error('Optimization ''particleswarm'' requires the Global Optimization Toolbox') end minalgo = 12; bounds = ones(length(params0),1)*[-Inf,Inf]; parameter_names = fieldnames(params); otherwise msg = sprintf('%s is not an implemented optimization routine.\n', optimizer); msg = sprintf('%sAvailable algorithms are:\n', msg); msg = sprintf('%s - %s\n', msg, 'fmincon'); msg = sprintf('%s - %s\n', msg, 'fminunc'); msg = sprintf('%s - %s\n', msg, 'csminwel'); msg = sprintf('%s - %s\n', msg, 'fminsearch'); msg = sprintf('%s - %s\n', msg, 'simplex'); msg = sprintf('%s - %s\n', msg, 'annealing'); msg = sprintf('%s - %s\n', msg, 'lsqnonlin'); msg = sprintf('%s - %s\n', msg, 'GaussNewton'); error(msg) end end % Set options if provided as input arguments to nls routine. oldopt = options_.optim_opt; if nargin>5 if mod(nargin-5, 2) error('Options must come by key/value pairs.') end i = 1; while i