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