From b3d1e8412be46a1e5c96f1e8d5c44bc522d3a348 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Villemot?= <sebastien@dynare.org>
Date: Tue, 8 Oct 2019 18:35:57 +0200
Subject: [PATCH] Add support for mode_compute=1 under Octave
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Since version 1.6, the optim Forge package has an implementation of fmincon.
Hence we can now use mode_compute=1 under Octave.

This commit also adds tests/optimizers/fs2000_1.mod to the testsuite. It will
be skipped under MATLAB if the optimization toolbox is not there, or under
Octave if optim ≥ 1.6 is not there.
---
 doc/manual/source/the-model-file.rst          |  6 ++--
 .../optimization/dynare_minimize_objective.m  | 29 +++++++++++++++----
 matlab/user_has_octave_forge_package.m        | 25 ++++++++++------
 tests/Makefile.am                             |  1 +
 tests/optimizers/fs2000_1.mod                 |  2 +-
 5 files changed, 45 insertions(+), 18 deletions(-)

diff --git a/doc/manual/source/the-model-file.rst b/doc/manual/source/the-model-file.rst
index d7fc37c69b..3e5803b8db 100644
--- a/doc/manual/source/the-model-file.rst
+++ b/doc/manual/source/the-model-file.rst
@@ -4652,8 +4652,10 @@ block decomposition of the model (see :opt:`block`).
            ``1``
 
                 Uses ``fmincon`` optimization routine (available under
-                MATLAB if the Optimization Toolbox is installed; not
-                available under Octave).
+                MATLAB if the Optimization Toolbox is installed; available
+                under Octave if the `optim
+                <https://octave.sourceforge.io/optim/>`__ package from
+                Octave-Forge, version 1.6 or above, is installed).
 
            ``2``
 
diff --git a/matlab/optimization/dynare_minimize_objective.m b/matlab/optimization/dynare_minimize_objective.m
index 6103fa167a..c3988bec95 100644
--- a/matlab/optimization/dynare_minimize_objective.m
+++ b/matlab/optimization/dynare_minimize_objective.m
@@ -26,7 +26,7 @@ function [opt_par_values,fval,exitflag,hessian_mat,options_,Scale,new_rat_hess_i
 %   none.
 %
 %
-% Copyright (C) 2014-2017 Dynare Team
+% Copyright (C) 2014-2019 Dynare Team
 %
 % This file is part of Dynare.
 %
@@ -64,13 +64,22 @@ new_rat_hess_info=[];
 
 switch minimizer_algorithm
   case 1
-    if isoctave
-        error('Optimization algorithm 1 is not available under Octave')
-    elseif ~user_has_matlab_license('optimization_toolbox')
+    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.
-    optim_options = optimset('display','iter', 'LargeScale','off', 'MaxFunEvals',100000, 'TolFun',1e-8, 'TolX',1e-6);
+    optim_options = optimset('display','iter', 'MaxFunEvals',100000, 'TolFun',1e-8, 'TolX',1e-6);
+    if ~isoctave
+        optim_options = optimset(optim_options, 'LargeScale','off');
+    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 ~isempty(options_.optim_opt)
         eval(['optim_options = optimset(optim_options,' options_.optim_opt ');']);
     end
@@ -80,8 +89,16 @@ switch minimizer_algorithm
     if options_.analytic_derivation
         optim_options = optimset(optim_options,'GradObj','on','TolX',1e-7);
     end
-    [opt_par_values,fval,exitflag,output,lamdba,grad,hessian_mat] = ...
+    if ~isoctave
+        [opt_par_values,fval,exitflag,output,lamdba,grad,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
   case 2
     %simulating annealing
     sa_options = options_.saopt;
diff --git a/matlab/user_has_octave_forge_package.m b/matlab/user_has_octave_forge_package.m
index a3f1bc38db..3b0abf4849 100644
--- a/matlab/user_has_octave_forge_package.m
+++ b/matlab/user_has_octave_forge_package.m
@@ -1,7 +1,8 @@
-function [hasPackage] = user_has_octave_forge_package(package)
+function [hasPackage] = user_has_octave_forge_package(package, min_version)
 % Checks for the availability of a given Octave Forge package
+% Optionally, a minimal version can be required for the package
 
-% Copyright (C) 2012 Dynare Team
+% Copyright (C) 2012-2019 Dynare Team
 %
 % This file is part of Dynare.
 %
@@ -18,13 +19,19 @@ function [hasPackage] = user_has_octave_forge_package(package)
 % You should have received a copy of the GNU General Public License
 % along with Dynare.  If not, see <http://www.gnu.org/licenses/>.
 
-[desc,flag] = pkg('describe', package);
+[desc,flag] = pkg("describe", package);
 
-if isequal(flag{1,1}, 'Not installed')
+if isequal(flag{1,1}, "Not installed")
     hasPackage = 0;
 else
-    if isequal(flag{1,1}, 'Not loaded')
-        pkg('load', package);
-    end
-    hasPackage = 1;
-end
+    if isequal(flag{1,1}, "Not loaded")
+        pkg("load", package);
+    endif
+    if nargin > 1 && compare_versions(desc{1,1}.version, min_version, "<")
+        hasPackage = 0;
+    else
+        hasPackage = 1;
+    endif
+endif
+endfunction
+
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 7222592ea4..1b6fef0141 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -333,6 +333,7 @@ MODFILES = \
 	smoother2histval/fs2000_simul.mod \
 	smoother2histval/fs2000_smooth.mod \
 	smoother2histval/fs2000_smooth_stoch_simul.mod \
+	optimizers/fs2000_1.mod \
 	optimizers/fs2000_2.mod \
 	optimizers/fs2000_3.mod \
 	optimizers/fs2000_4.mod \
diff --git a/tests/optimizers/fs2000_1.mod b/tests/optimizers/fs2000_1.mod
index fe37048359..65b261d42d 100644
--- a/tests/optimizers/fs2000_1.mod
+++ b/tests/optimizers/fs2000_1.mod
@@ -1,5 +1,5 @@
 @#include "fs2000.common.inc"
 
-if ~isoctave() && exist('fmincon','file')
+if (isoctave && user_has_octave_forge_package('optim', '1.6')) || (~isoctave && user_has_matlab_license('optimization_toolbox'))
   estimation(mode_compute=1,order=1, datafile='../fs2000/fsdat_simul', nobs=192, mh_replic=0);
 end
-- 
GitLab