From a17c3f10575f0ab8904ab3a53bb6c13d0052cfd9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Villemot?= <sebastien@dynare.org>
Date: Thu, 26 Sep 2024 17:20:13 +0200
Subject: [PATCH] MCP solver: use sparse representation for the model

Ref. #1859
---
 .../perfect_foresight_mcp_problem.m           | 151 +++++++-----------
 .../solve_stacked_problem.m                   |  29 ++--
 2 files changed, 81 insertions(+), 99 deletions(-)

diff --git a/matlab/perfect-foresight-models/perfect_foresight_mcp_problem.m b/matlab/perfect-foresight-models/perfect_foresight_mcp_problem.m
index 13bb412050..d6bbda7c7e 100644
--- a/matlab/perfect-foresight-models/perfect_foresight_mcp_problem.m
+++ b/matlab/perfect-foresight-models/perfect_foresight_mcp_problem.m
@@ -1,50 +1,43 @@
-function [residuals,JJacobian] = perfect_foresight_mcp_problem(y, dynamic_function, Y0, YT, ...
-                                                  exo_simul, params, steady_state, ...
-                                                  maximum_lag, T, ny, i_cols, ...
-                                                  i_cols_J1, i_cols_1, i_cols_T, ...
-                                                  i_cols_j, i_cols_0,i_cols_J0, eq_index)
-% function [residuals,JJacobian] = perfect_foresight_mcp_problem(y, dynamic_function, Y0, YT, ...
-%                                            exo_simul, params, steady_state, ...
-%                                            maximum_lag, T, ny, i_cols, ...
-%                                            i_cols_J1, i_cols_1, i_cols_T, ...
-%                                            i_cols_j,eq_index)
+function [residuals,JJacobian] = perfect_foresight_mcp_problem(y, dynamic_resid_function, ...
+                                                               dynamic_g1_function, Y0, YT, ...
+                                                               exo_simul, params, steady_state, ...
+                                                               dynamic_g1_sparse_rowval, ...
+                                                               dynamic_g1_sparse_colval, ...
+                                                               dynamic_g1_sparse_colptr, ...
+                                                               maximum_lag, T, ny, eq_index)
+% function [residuals,JJacobian] = perfect_foresight_mcp_problem(y, dynamic_resid_function, ...
+%                                                                dynamic_g1_function, Y0, YT, ...
+%                                                                exo_simul, params, steady_state, ...
+%                                                                dynamic_g1_sparse_rowval, ...
+%                                                                dynamic_g1_sparse_colval, ...
+%                                                                dynamic_g1_sparse_colptr, ...
+%                                                                maximum_lag, T, ny, eq_index)
 % Computes the residuals and the Jacobian matrix for a perfect foresight problem over T periods
 % in a mixed complementarity problem context
 %
 % INPUTS
-%   y                   [double] N*1 array, terminal conditions for the endogenous variables
-%   dynamic_function    [handle] function handle to _dynamic-file
-%   Y0                  [double] N*1 array, initial conditions for the endogenous variables
-%   YT                  [double] N*1 array, terminal conditions for the endogenous variables
-%   exo_simul           [double] nperiods*M_.exo_nbr matrix of exogenous variables (in declaration order)
-%                                for all simulation periods
-%   params              [double] nparams*1 array, parameter values
-%   steady_state        [double] endo_nbr*1 vector of steady state values
-%   maximum_lag         [scalar] maximum lag present in the model
-%   T                   [scalar] number of simulation periods
-%   ny                  [scalar] number of endogenous variables
-%   i_cols              [double] indices of variables appearing in M_.lead_lag_incidence
-%                                and that need to be passed to _dynamic-file
-%   i_cols_J1           [double] indices of contemporaneous and forward looking variables
-%                                appearing in M_.lead_lag_incidence
-%   i_cols_1            [double] indices of contemporaneous and forward looking variables in
-%                                M_.lead_lag_incidence in dynamic Jacobian (relevant in first period)
-%   i_cols_T            [double] columns of dynamic Jacobian related to contemporaneous and backward-looking
-%                                variables (relevant in last period)
-%   i_cols_j            [double] indices of variables in M_.lead_lag_incidence
-%                                in dynamic Jacobian (relevant in intermediate periods)
-%   eq_index            [double] N*1 array, index vector describing residual mapping resulting
-%                                from complementarity setup
+%   y                        [double] (ny*T)*1 array, path for the endogenous variables
+%   dynamic_resid_function   [handle] function handle to dynamic residuals
+%   dynamic_g1_function      [handle] function handle to dynamic Jacobian
+%   Y0                       [double] ny*1 array, initial conditions for the endogenous variables
+%   YT                       [double] ny*1 array, terminal conditions for the endogenous variables
+%   exo_simul                [double] (max_lag+nperiods+max_lead)*M_.exo_nbr matrix of exogenous
+%                                     variables (in declaration order) for all simulation periods
+%   params                   [double] nparams*1 array, parameter values
+%   steady_state             [double] ny*1 vector of steady state values
+%   dynamic_g1_sparse_rowval [integer vector] eponymous field in M_
+%   dynamic_g1_sparse_colval [integer vector] eponymous field in M_
+%   dynamic_g1_sparse_colptr [integer vector] eponymous field in M_
+%   maximum_lag              [scalar] maximum lag present in the model
+%   T                        [scalar] number of simulation periods
+%   ny                       [scalar] number of endogenous variables
+%   eq_index                 [double] ny*1 array, index vector describing residual mapping resulting
+%                                     from complementarity setup
 % OUTPUTS
-%   residuals           [double] (N*T)*1 array, residuals of the stacked problem
-%   JJacobian           [double] (N*T)*(N*T) array, Jacobian of the stacked problem
-% ALGORITHM
-%   None
-%
-% SPECIAL REQUIREMENTS
-%   None.
+%   residuals                [double] (ny*T)*1 array, residuals of the stacked problem
+%   JJacobian                [double] (ny*T)*(ny*T) array, Jacobian of the stacked problem
 
-% Copyright © 1996-2020 Dynare Team
+% Copyright © 1996-2024 Dynare Team
 %
 % This file is part of Dynare.
 %
@@ -61,65 +54,43 @@ function [residuals,JJacobian] = perfect_foresight_mcp_problem(y, dynamic_functi
 % You should have received a copy of the GNU General Public License
 % along with Dynare.  If not, see <https://www.gnu.org/licenses/>.
 
+YY = reshape([Y0; y; YT], ny, T+2);
 
-YY = [Y0; y; YT];
-
-residuals = zeros(T*ny,1);
+residuals = NaN(T*ny,1);
 if nargout == 2
     iJacobian = cell(T,1);
 end
 
-i_rows = 1:ny;
 offset = 0;
-i_cols_J = i_cols;
 
-for it = maximum_lag+(1:T)
-    if nargout == 1
-        res = dynamic_function(YY(i_cols),exo_simul, params, ...
-                               steady_state,it);
-        residuals(i_rows) = res(eq_index);
-    elseif nargout == 2
-        [res,jacobian] = dynamic_function(YY(i_cols),exo_simul, params, steady_state,it);
-        residuals(i_rows) = res(eq_index);
-        if T==1 && it==maximum_lag+1
-            [rows, cols, vals] = find(jacobian(eq_index,i_cols_0));
-            if size(jacobian, 1) == 1 % find() will return row vectors in this case
-                rows = rows';
-                cols = cols';
-                vals = vals';
-            end
-            iJacobian{1} = [rows, i_cols_J0(cols), vals];
-        elseif it == maximum_lag+1
-            [rows,cols,vals] = find(jacobian(eq_index,i_cols_1));
-            if numel(eq_index) == 1 % find() will return row vectors in this case
-                rows = rows';
-                cols = cols';
-                vals = vals';
-            end
-            iJacobian{1} = [offset+rows, i_cols_J1(cols), vals];
-        elseif it == maximum_lag+T
-            [rows,cols,vals] = find(jacobian(eq_index,i_cols_T));
-            if numel(eq_index) == 1 % find() will return row vectors in this case
-                rows = rows';
-                cols = cols';
-                vals = vals';
-            end
-            iJacobian{T} = [offset+rows, i_cols_J(i_cols_T(cols)), vals];
+for t = 1:T
+    y3n = YY(:, t+(0:2));
+    x = exo_simul(t+maximum_lag, :);
+
+    [res, TT_order, TT] = dynamic_resid_function(y3n, x, params, steady_state);
+    residuals(offset + (1:ny)) = res(eq_index);
+
+    if nargout == 2
+        jacobian = dynamic_g1_function(y3n, x, params, steady_state, dynamic_g1_sparse_rowval, dynamic_g1_sparse_colval, dynamic_g1_sparse_colptr, TT_order, TT);
+        if T == 1 % static model
+            [rows, cols, vals] = find(jacobian(eq_index, ny+(1:ny)));
+        elseif t == 1
+            [rows, cols, vals] = find(jacobian(eq_index, ny+(1:2*ny)));
+        elseif t == T
+            [rows, cols, vals] = find(jacobian(eq_index, 1:2*ny));
+            cols = cols - ny;
         else
-            [rows,cols,vals] = find(jacobian(eq_index,i_cols_j));
-            if numel(eq_index) == 1 % find() will return row vectors in this case
-                rows = rows';
-                cols = cols';
-                vals = vals';
-            end
-            iJacobian{it-maximum_lag} = [offset+rows, i_cols_J(cols), vals];
-            i_cols_J = i_cols_J + ny;
+            [rows, cols, vals] = find(jacobian(eq_index, 1:3*ny));
+            cols = cols - ny;
+        end
+        if numel(eq_index) == 1 % find() will return row vectors in this case
+            rows = rows';
+            cols = cols';
+            vals = vals';
         end
-        offset = offset + ny;
+        iJacobian{t} = [offset+rows, offset+cols, vals];
     end
-
-    i_rows = i_rows + ny;
-    i_cols = i_cols + ny;
+    offset = offset + ny;
 end
 
 if nargout == 2
diff --git a/matlab/perfect-foresight-models/solve_stacked_problem.m b/matlab/perfect-foresight-models/solve_stacked_problem.m
index cd5996343e..4ec67beacf 100644
--- a/matlab/perfect-foresight-models/solve_stacked_problem.m
+++ b/matlab/perfect-foresight-models/solve_stacked_problem.m
@@ -31,8 +31,17 @@ function [endogenousvariables, success, maxerror] = solve_stacked_problem(endoge
 % You should have received a copy of the GNU General Public License
 % along with Dynare.  If not, see <https://www.gnu.org/licenses/>.
 
-[options_, y0, yT, z, i_cols, i_cols_J1, i_cols_T, i_cols_j, i_cols_1, i_cols_0, i_cols_J0, dynamicmodel] = ...
-    initialize_stacked_problem(endogenousvariables, options_, M_, steadystate);
+if M_.maximum_lag > 0
+    y0 = endogenousvariables(:, M_.maximum_lag);
+else
+    y0 = NaN(M_.endo_nbr, 1);
+end
+if M_.maximum_lead > 0
+    yT = endogenousvariables(:, M_.maximum_lag+options_.periods+1);
+else
+    yT = NaN(M_.endo_nbr, 1);
+end
+z = endogenousvariables(:,M_.maximum_lag+(1:options_.periods));
 
 if (options_.solve_algo == 10 || options_.solve_algo == 11)% mixed complementarity problem
     [lb, ub] = feval(sprintf('%s.dynamic_complementarity_conditions', M_.fname), M_.params);
@@ -47,14 +56,16 @@ if (options_.solve_algo == 10 || options_.solve_algo == 11)% mixed complementari
         options_.mcppath.lb = repmat(lb,options_.periods,1);
         options_.mcppath.ub = repmat(ub,options_.periods,1);
     end
+    dynamic_resid_function = str2func([M_.fname,'.sparse.dynamic_resid']);
+    dynamic_g1_function = str2func([M_.fname,'.sparse.dynamic_g1']);
     [y, check, res, ~, errorcode] = dynare_solve(@perfect_foresight_mcp_problem, z(:), ...
-                                               options_.simul.maxit, options_.dynatol.f, options_.dynatol.x, ...
-                                               options_, ...
-                                               dynamicmodel, y0, yT, ...
-                                               exogenousvariables, M_.params, steadystate, ...
-                                               M_.maximum_lag, options_.periods, M_.endo_nbr, i_cols, ...
-                                               i_cols_J1, i_cols_1, i_cols_T, i_cols_j, i_cols_0, i_cols_J0, ...
-                                               M_.dynamic_mcp_equations_reordering);
+                                                 options_.simul.maxit, options_.dynatol.f, options_.dynatol.x, ...
+                                                 options_, ...
+                                                 dynamic_resid_function, dynamic_g1_function, y0, yT, ...
+                                                 exogenousvariables, M_.params, steadystate, ...
+                                                 M_.dynamic_g1_sparse_rowval, M_.dynamic_g1_sparse_colval, M_.dynamic_g1_sparse_colptr, ...
+                                                 M_.maximum_lag, options_.periods, M_.endo_nbr, ...
+                                                 M_.dynamic_mcp_equations_reordering);
     eq_to_ignore=find(isfinite(lb) | isfinite(ub));
 
 else
-- 
GitLab