From a36287fc3dda2a7f02429e4f786b3adf358ddd7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Villemot?= <sebastien@dynare.org>
Date: Fri, 29 Mar 2024 18:21:53 +0100
Subject: [PATCH] Convert stochastic solvers to sparse Jacobian representation

Ref. #1859
---
 matlab/AIM/dynAIMsolver1.m                    |  57 ++-----
 matlab/display_problematic_vars_Jacobian.m    |  58 ++++---
 matlab/model_diagnostics.m                    |  54 +++---
 .../dyn_first_order_solver.m                  |  15 +-
 .../dyn_second_order_solver.m                 |  47 +++---
 matlab/stochastic_solver/stochastic_solvers.m | 156 +++++++++---------
 6 files changed, 179 insertions(+), 208 deletions(-)

diff --git a/matlab/AIM/dynAIMsolver1.m b/matlab/AIM/dynAIMsolver1.m
index 78d770cd37..7c02fbf531 100644
--- a/matlab/AIM/dynAIMsolver1.m
+++ b/matlab/AIM/dynAIMsolver1.m
@@ -1,6 +1,6 @@
-function [dr,aimcode,rts]=dynAIMsolver1(jacobia_,M_,dr)
-% function [dr,aimcode]=dynAIMsolver1(jacobia_,M_,dr)
-% Maps Dynare jacobia to AIM 1st order model solver designed and developed by Gary ANderson
+function [dr, aimcode, rts] = dynAIMsolver1(g1, M_, dr)
+% function [dr, aimcode, rts] = dynAIMsolver1(g1, M_, dr)
+% Maps Dynare jacobian to AIM 1st order model solver designed and developed by Gary ANderson
 % and derives the solution for gy=dr.hgx and gu=dr.hgu from the AIM outputs
 % AIM System is given as a sum:
 % i.e. for i=-$...+&   SUM(Hi*xt+i)= £*zt, t = 0, . . . ,?
@@ -11,7 +11,7 @@ function [dr,aimcode,rts]=dynAIMsolver1(jacobia_,M_,dr)
 % where [fy'-$...  fy'i ... fy'+&]=[H-$...  Hi ... H+&] and fu'= £
 %
 % INPUTS
-%   jacobia_   [matrix]           1st order derivative of the model
+%   g1         [matrix]           sparse Jacobian of the dynamic model
 %   dr         [matlab structure] Decision rules for stochastic simulations.
 %   M_         [matlab structure] Definition of the model.
 %
@@ -46,7 +46,7 @@ function [dr,aimcode,rts]=dynAIMsolver1(jacobia_,M_,dr)
 %
 % GP July 2008
 
-% Copyright © 2008-2017 Dynare Team
+% Copyright © 2008-2024 Dynare Team
 %
 % This file is part of Dynare.
 %
@@ -64,69 +64,36 @@ function [dr,aimcode,rts]=dynAIMsolver1(jacobia_,M_,dr)
 % along with Dynare.  If not, see <https://www.gnu.org/licenses/>.
 
 aimcode=-1;
-neq= size(jacobia_,1); % no of equations
-lags=M_.maximum_endo_lag; % no of lags and leads
-leads=M_.maximum_endo_lead;
-klen=(leads+lags+1);  % total lenght
-theAIM_H=zeros(neq, neq*klen); % alloc space
-lli=M_.lead_lag_incidence';
-% "sparse" the compact jacobia into AIM H aray of matrices
-% without exogenous shoc
-theAIM_H(:,find(lli(:)))=jacobia_(:,nonzeros(lli(:)));
+neq= size(g1, 1); % no of equations
 condn  = 1.e-10;%SPAmalg uses this in zero tests
 uprbnd = 1 + 1.e-6;%allow unit roots
-                   % forward only models - AIM must have at least 1 lead and 1 lag.
-if lags ==0
-    theAIM_H =[zeros(neq) theAIM_H];
-    lags=1;
-    klen=(leads+lags+1);
-end
-% backward looking only models
-if leads ==0
-    theAIM_H =[theAIM_H zeros(neq)];
-    leads=1;
-    klen=(leads+lags+1);
-end
+
 %disp('gensysToAMA:running ama');
 try % try to run AIM
-    [bb,rts,~,~,~,~,aimcode] =...
-        SPAmalg(theAIM_H,neq, lags,leads,condn,uprbnd);
+    [bb, rts, ~, ~, ~, ~, aimcode] = SPAmalg(g1(:, 1:3*M_.endo_nbr), neq, 1, 1, condn, uprbnd);
 catch
     err = lasterror;
     disp(['Dynare AIM Solver error:' sprintf('%s; ID:%s',err.message, err.identifier)]);
     rethrow(lasterror);
 end
 if aimcode==1 %if OK
-    col_order=[];
-    for i =1:lags
-        col_order=[((i-1)*neq)+dr.order_var' col_order];
-    end
-    bb_ord= bb(dr.order_var,col_order); % derive ordered gy
-
-    % variables are present in the state space at the lag at which they
-    % appear and at all smaller lags. The are ordered from smaller to
-    % higher lag (reversed order of M_.lead_lag_incidence rows for lagged
-    % variables)
-    i_lagged_vars = flipud(cumsum(M_.lead_lag_incidence(1:M_.maximum_lag,dr.order_var),1))';
+    dr.ghx = bb(dr.order_var, dr.order_var(M_.nstatic+(1:M_.nspred)));
 
-    dr.ghx= bb_ord(:,find(i_lagged_vars(:))); % get columns reported in
-                                              % Dynare solution
     if M_.exo_nbr % if there are exogenous shocks then derive gu for the shocks:
                   %   get H0 and H+1=HM
                   %    theH0= theAIM_H (:,M_.maximum_endo_lag*neq+1: (M_.maximum_endo_lag+1)*neq);
                   %theH0= theAIM_H (:,lags*neq+1: (lags+1)*neq);
                   %    theHP= theAIM_H (:,(M_.maximum_endo_lag+1)*neq+1: (M_.maximum_endo_lag+2)*neq);
                   %theHP= theAIM_H (:,(lags+1)*neq+1: (lags+2)*neq);
-        theAIM_Psi= - jacobia_(:, size(nonzeros(lli(:)))+1:end);%
                                                                 %? = inv(H0 + H1B1)
                                                                 %phi= (theH0+theHP*sparse(bb(:,(lags-1)*neq+1:end)))\eye( neq);
                                                                 %AIM_ghu=phi*theAIM_Psi;
                                                                 %dr.ghu =AIM_ghu(dr.order_var,:); % order gu
                                                                 % Using AIM SPObstruct
-        scof = SPObstruct(theAIM_H,bb,neq,lags,leads);
-        scof1= scof(:,(lags)*neq+1:end);
+        scof = SPObstruct(g1(:, 1:3*M_.endo_nbr), bb, neq, 1, 1);
+        scof1 = scof(:, neq+1:end);
         scof1= scof1(:,dr.order_var);
-        dr.ghu =scof1\theAIM_Psi;
+        dr.ghu = -scof1 \ g1(:, 3*M_.endo_nbr+1:end);
     else
         dr.ghu = [];
     end
diff --git a/matlab/display_problematic_vars_Jacobian.m b/matlab/display_problematic_vars_Jacobian.m
index f204b421a8..5105a3b301 100644
--- a/matlab/display_problematic_vars_Jacobian.m
+++ b/matlab/display_problematic_vars_Jacobian.m
@@ -1,5 +1,5 @@
-function []=display_problematic_vars_Jacobian(problemrow,problemcol,M_,x,type,caller_string)
-% []=display_problematic_vars_Jacobian(problemrow,problemcol,M_,x,caller_string)
+function display_problematic_vars_Jacobian(problemrow, problemcol, M_, x, type, caller_string)
+% display_problematic_vars_Jacobian(problemrow,problemcol,M_,x,caller_string)
 % print the equation numbers and variables associated with problematic entries
 % of the Jacobian
 %
@@ -11,12 +11,8 @@ function []=display_problematic_vars_Jacobian(problemrow,problemcol,M_,x,type,ca
 %   type            [string] 'static' or 'dynamic' depending on the type of
 %                               Jacobian
 %   caller_string   [string] contains name of calling function for printing
-%
-% OUTPUTS
-%   none.
-%
 
-% Copyright © 2014-2023 Dynare Team
+% Copyright © 2014-2024 Dynare Team
 %
 % This file is part of Dynare.
 %
@@ -40,20 +36,24 @@ end
 initial_aux_eq_nbr=M_.ramsey_orig_endo_nbr;
 if strcmp(type,'dynamic')
     for ii=1:length(problemrow)
-        if problemcol(ii)>max(M_.lead_lag_incidence)
-            var_row=2;
-            var_index=problemcol(ii)-max(max(M_.lead_lag_incidence));
-        else
-            [var_row,var_index]=find(M_.lead_lag_incidence==problemcol(ii));
-        end
-        if var_row==2
-            type_string='';
-        elseif var_row==1
-            type_string='lag of';
-        elseif var_row==3
-            type_string='lead of';
+        if problemcol(ii) <= 3*M_.endo_nbr % Endogenous
+            endo = true;
+            var_index = mod(problemcol(ii)-1, M_.endo_nbr)+1;
+            if problemcol(ii) <= M_.endo_nbr
+                type_string='lag of';
+            elseif problemcol(ii) <= 2*M_.endo_nbr
+                type_string='';
+            else
+                type_string='lead of';
+            end
+        elseif problemcol(ii) <= 3*M_.endo_nbr+M_.exo_nbr % Exogenous
+            endo = false;
+            var_index = problemcol(ii) - 3*M_.endo_nbr;
+        else % Exogenous deterministic, ignore
+            continue
         end
-        if problemcol(ii)<=max(max(M_.lead_lag_incidence)) && var_index<=M_.orig_endo_nbr
+
+        if endo && var_index <= M_.orig_endo_nbr
             if problemrow(ii)<=initial_aux_eq_nbr
                 eq_nbr = problemrow(ii);
                 fprintf('Derivative of Auxiliary Equation %d with respect to %s Variable %s  (initial value of %s: %g) \n', ...
@@ -63,16 +63,16 @@ if strcmp(type,'dynamic')
                 fprintf('Derivative of Equation %d with respect to %s Variable %s  (initial value of %s: %g) \n', ...
                         eq_nbr, type_string, M_.endo_names{var_index}, M_.endo_names{var_index}, x(var_index));
             end
-        elseif problemcol(ii)<=max(max(M_.lead_lag_incidence)) && var_index>M_.orig_endo_nbr % auxiliary vars
-            if M_.aux_vars(1,problemcol(ii)-M_.orig_endo_nbr).type==6 %Ramsey Lagrange Multiplier
+        elseif endo && var_index > M_.orig_endo_nbr % auxiliary vars
+            if M_.aux_vars(1, var_index - M_.orig_endo_nbr).type==6 %Ramsey Lagrange Multiplier
                 if problemrow(ii)<=initial_aux_eq_nbr
                     eq_nbr = problemrow(ii);
-                    fprintf('Derivative of Auxiliary Equation %d with respect to %s of Langrange multiplier of equation %s (initial value: %g) \n', ...
-                            eq_nbr, type_string, M_.aux_vars(1,problemcol(ii)-M_.orig_endo_nbr).eq_nbr, x(problemcol(ii)));
+                    fprintf('Derivative of Auxiliary Equation %d with respect to %s of Lagrange multiplier of equation %s (initial value: %g) \n', ...
+                            eq_nbr, type_string, M_.aux_vars(1, var_index-M_.orig_endo_nbr).eq_nbr, x(var_index));
                 else
                     eq_nbr = problemrow(ii)-initial_aux_eq_nbr;
-                    fprintf('Derivative of Equation %d with respect to %s of Langrange multiplier of equation %s (initial value: %g) \n', ...
-                            eq_nbr, type_string, M_.aux_vars(1,problemcol(ii)-M_.orig_endo_nbr).eq_nbr, x(problemcol(ii)));
+                    fprintf('Derivative of Equation %d with respect to %s of Lagrange multiplier of equation %s (initial value: %g) \n', ...
+                            eq_nbr, type_string, M_.aux_vars(1, var_index-M_.orig_endo_nbr).eq_nbr, x(var_index));
                 end
             else
                 if problemrow(ii)<=initial_aux_eq_nbr
@@ -87,7 +87,7 @@ if strcmp(type,'dynamic')
                             eq_nbr, type_string, M_.endo_names{orig_var_index}, M_.endo_names{orig_var_index}, x(orig_var_index));
                 end
             end
-        elseif problemcol(ii)>max(max(M_.lead_lag_incidence)) && var_index<=M_.exo_nbr
+        else % Exogenous
             if problemrow(ii)<=initial_aux_eq_nbr
                 eq_nbr = problemrow(ii);
                 fprintf('Derivative of Auxiliary Equation %d with respect to %s shock %s \n', ...
@@ -97,8 +97,6 @@ if strcmp(type,'dynamic')
                 fprintf('Derivative of Equation %d with respect to %s shock %s \n', ...
                         eq_nbr, type_string, M_.exo_names{var_index});
             end
-        else
-            error('display_problematic_vars_Jacobian:: The error should not happen. Please contact the developers')
         end
     end
     fprintf('\n%s  The problem most often occurs, because a variable with\n', caller_string)
@@ -149,4 +147,4 @@ elseif strcmp(type, 'static')
     fprintf('%s  If you are using model-local variables (# operator), check their values as well.\n', caller_string)
 else
     error('Unknown Type')
-end
\ No newline at end of file
+end
diff --git a/matlab/model_diagnostics.m b/matlab/model_diagnostics.m
index a01947939e..66858d7600 100644
--- a/matlab/model_diagnostics.m
+++ b/matlab/model_diagnostics.m
@@ -16,7 +16,7 @@ function model_diagnostics(M_,options_,oo_)
 %   none.
 %
 
-% Copyright © 1996-2023 Dynare Team
+% Copyright © 1996-2024 Dynare Team
 %
 % This file is part of Dynare.
 %
@@ -271,51 +271,53 @@ if singularity_problem
 end
 
 %%check dynamic Jacobian
-klen = M_.maximum_lag + M_.maximum_lead + 1;
-exo_simul = [repmat(oo_.exo_steady_state',klen,1) repmat(oo_.exo_det_steady_state',klen,1)];
-iyv = M_.lead_lag_incidence';
-iyv = iyv(:);
-iyr0 = find(iyv) ;
-it_ = M_.maximum_lag + 1;
-z = repmat(dr.ys,1,klen);
+dyn_endo_ss = repmat(dr.ys, 3, 1);
 
 if options_.order == 1
     if (options_.bytecode)
         [~, loc_dr] = bytecode('dynamic','evaluate', M_, options_, z, exo_simul, ...
                                M_.params, dr.ys, 1);
-        jacobia_ = [loc_dr.g1 loc_dr.g1_x loc_dr.g1_xd];
+        % TODO: simplify the following once bytecode MEX has been updated to sparse format
+        g1 = zeros(M_.endo_nbr, 3*M_.endo_nbr+M_.exo_nbr+M_.exo_det_nbr);
+        if M_.maximum_endo_lag > 0
+            g1(:, find(M_.lead_lag_incidence(M_.maximum_endo_lag, :))) = loc_dr.g1(:, 1:M_.nspred);
+        end
+        [~,icurr] = find(M_.lead_lag_incidence(M_.maximum_endo_lag+1, :));
+        g1(:, M_.endo_nbr + icurr) = loc_dr.g1(:, M_.nspred+(1:length(icurr)));
+        if M_.maximum_endo_lead > 0
+            g1(:, 2*M_.endo_nbr + find(M_.lead_lag_incidence(M_.maximum_endo_lag+2, :))) = loc_dr.g1(:, M_.nspred+M_.endo_nbr+(1:M_.nsfwrd));
+        end
+        g1(:, 3*M_.endo_nbr+(1:M_.exo_nbr)) = loc_dr.g1_x;
+        g1(:, 3*M_.endo_nbr+M_.exo_nbr+(1:M_.exo_det_nbr)) = loc_dr.g1_xd;
+        g1 = sparse(g1);
     else
-        [~,jacobia_] = feval([M_.fname '.dynamic'],z(iyr0),exo_simul, ...
-                             M_.params, dr.ys, it_);
+        g1 = feval([M_.fname '.sparse.dynamic_g1'], dyn_endo_ss, exo, M_.params, dr.ys, ...
+                   M_.dynamic_g1_sparse_rowval, M_.dynamic_g1_sparse_colval, ...
+                   M_.dynamic_g1_sparse_colptr);
     end
 elseif options_.order >= 2
-    if (options_.bytecode)
-        [~, loc_dr] = bytecode('dynamic','evaluate', M_, options_, z, exo_simul, ...
-                               M_.params, dr.ys, 1);
-        jacobia_ = [loc_dr.g1 loc_dr.g1_x];
-    else
-        [~,jacobia_,hessian1] = feval([M_.fname '.dynamic'],z(iyr0),...
-                                      exo_simul, ...
-                                      M_.params, dr.ys, it_);
-    end
+    [g1, T_order, T] = feval([M_.fname '.sparse.dynamic_g1'], dyn_endo_ss, exo, M_.params, ...
+                             dr.ys, M_.dynamic_g1_sparse_rowval, M_.dynamic_g1_sparse_colval, ...
+                             M_.dynamic_g1_sparse_colptr);
+    g2_v = feval([M_.fname '.sparse.dynamic_g2'], dyn_endo_ss, exo, M_.params, dr.ys, T_order, T);
 end
 
-if any(any(isinf(jacobia_) | isnan(jacobia_)))
+if any(any(isinf(g1) | isnan(g1)))
     problem_dummy=1;
-    [infrow,infcol]=find(isinf(jacobia_) | isnan(jacobia_));
+    [infrow,infcol]=find(isinf(g1) | isnan(g1));
     fprintf('\nMODEL_DIAGNOSTICS: The Jacobian of the dynamic model contains Inf or NaN. The problem arises from: \n\n')
     display_problematic_vars_Jacobian(infrow,infcol,M_,dr.ys,'dynamic','MODEL_DIAGNOSTICS: ')
 end
-if any(any(~isreal(jacobia_)))
-    [imagrow,imagcol]=find(abs(imag(jacobia_))>1e-15);
+if any(any(~isreal(g1)))
+    [imagrow,imagcol]=find(abs(imag(g1))>1e-15);
     if ~isempty(imagrow)
         problem_dummy=1;
         fprintf('\nMODEL_DIAGNOSTICS: The Jacobian of the dynamic model contains imaginary parts. The problem arises from: \n\n')
         display_problematic_vars_Jacobian(imagrow,imagcol,M_,dr.ys,'dynamic','MODEL_DIAGNOSTICS: ')
     end
 end
-if exist('hessian1','var')
-    if any(any(isinf(hessian1) | isnan(hessian1)))
+if exist('g2_v','var')
+    if any(any(isinf(g2_v) | isnan(g2_v)))
         problem_dummy=1;
         fprintf('\nMODEL_DIAGNOSTICS: The Hessian of the dynamic model contains Inf or NaN.\n')
     end
diff --git a/matlab/stochastic_solver/dyn_first_order_solver.m b/matlab/stochastic_solver/dyn_first_order_solver.m
index 3ec2d2b6b3..7d39307bd9 100644
--- a/matlab/stochastic_solver/dyn_first_order_solver.m
+++ b/matlab/stochastic_solver/dyn_first_order_solver.m
@@ -1,9 +1,9 @@
-function [dr, info] = dyn_first_order_solver(jacobia, M_, dr, options_, task)
-% [dr, info] = dyn_first_order_solver(jacobia, M_, dr, options_, task)
+function [dr, info] = dyn_first_order_solver(g1, M_, dr, options_, task)
+% [dr, info] = dyn_first_order_solver(g1, M_, dr, options_, task)
 % Computes the first order reduced form of a DSGE model.
 %
 % INPUTS
-% - jacobia       [double]    matrix, the jacobian of the dynamic model.
+% - g1            [double]    sparse Jacobian of the dynamic model
 % - M_            [struct]    Matlab's structre describing the model
 % - dr            [struct]    Matlab's structure describing the reduced form model.
 % - options_      [struct]    Matlab's structure containing the current state of the options
@@ -63,6 +63,7 @@ if isempty(reorder_jacobian_columns)
     npred    = M_.npred;
     nstatic  = M_.nstatic;
     ndynamic = M_.ndynamic;
+    nspred   = M_.nspred;
     nsfwrd   = M_.nsfwrd;
 
     k1 = 1:(npred+nboth);
@@ -118,8 +119,10 @@ if isempty(reorder_jacobian_columns)
 
     [~,cols_b] = find(lead_lag_incidence(maximum_lag+1, order_var));
 
-    reorder_jacobian_columns = [nonzeros(lead_lag_incidence(:,order_var)'); ...
-                        nz+(1:exo_nbr)'];
+    reorder_jacobian_columns = [order_var(nstatic+(1:nspred));
+                                M_.endo_nbr+order_var(cols_b); ...
+                                2*M_.endo_nbr+order_var(nstatic+npred+(1:nsfwrd));
+                                3*M_.endo_nbr+(1:exo_nbr)'];
 end
 
 dr.ghx = [];
@@ -127,7 +130,7 @@ dr.ghu = [];
 
 dr.state_var = state_var;
 
-jacobia = jacobia(:,reorder_jacobian_columns);
+jacobia = full(g1(:,reorder_jacobian_columns));
 
 if nstatic > 0
     [Q, ~] = qr(jacobia(:,index_s));
diff --git a/matlab/stochastic_solver/dyn_second_order_solver.m b/matlab/stochastic_solver/dyn_second_order_solver.m
index 9a44564286..18c62f6f50 100644
--- a/matlab/stochastic_solver/dyn_second_order_solver.m
+++ b/matlab/stochastic_solver/dyn_second_order_solver.m
@@ -1,7 +1,7 @@
-function dr = dyn_second_order_solver(jacobia,hessian_mat,dr,M_,threads_BC)
+function dr = dyn_second_order_solver(g1, g2, dr, M_, threads_BC)
 
 %@info:
-%! @deftypefn {Function File} {@var{dr} =} dyn_second_order_solver (@var{jacobia},@var{hessian_mat},@var{dr},@var{M_},@var{threads_BC})
+%! @deftypefn {Function File} {@var{dr} =} dyn_second_order_solver (@var{g1}, @var{g2}, @var{dr}, @var{M_}, @var{threads_BC})
 %! @anchor{dyn_second_order_solver}
 %! @sp 1
 %! Computes the second order reduced form of the DSGE model, for details please refer to
@@ -15,10 +15,10 @@ function dr = dyn_second_order_solver(jacobia,hessian_mat,dr,M_,threads_BC)
 %! @strong{Inputs}
 %! @sp 1
 %! @table @ @var
-%! @item jacobia
-%! Matrix containing the Jacobian of the model
-%! @item hessian_mat
-%! Matrix containing the second order derivatives of the model
+%! @item g1
+%! Sparse matrix containing the Jacobian of the dynamic model
+%! @item g2
+%! Sparse matrix containing the Hessian of the dynamic model
 %! @item dr
 %! Matlab's structure describing the reduced form solution of the model.
 %! @item M_
@@ -36,7 +36,7 @@ function dr = dyn_second_order_solver(jacobia,hessian_mat,dr,M_,threads_BC)
 %! @end deftypefn
 %@eod:
 
-% Copyright © 2001-2020 Dynare Team
+% Copyright © 2001-2024 Dynare Team
 %
 % This file is part of Dynare.
 %
@@ -58,22 +58,25 @@ dr.ghuu = [];
 dr.ghxu = [];
 dr.ghs2 = [];
 
-k1 = nonzeros(M_.lead_lag_incidence(:,dr.order_var)');
-kk1 = [k1; length(k1)+(1:M_.exo_nbr+M_.exo_det_nbr)'];
-nk = size(kk1,1);
+[~,cols_b] = find(M_.lead_lag_incidence(2, dr.order_var));
+icurr = M_.endo_nbr + dr.order_var(cols_b);
+ilead = 2*M_.endo_nbr + dr.order_var(M_.nstatic+M_.npred+(1:M_.nsfwrd));
+
+kk1 = [dr.order_var(M_.nstatic+(1:M_.nspred)); icurr; ilead;
+       3*M_.endo_nbr+(1:M_.exo_nbr+M_.exo_det_nbr)'];
+nk = size(g1, 2);
 kk2 = reshape(1:nk^2,nk,nk);
-ic = [ M_.nstatic+(1:M_.nspred) ]';
 
-klag  = M_.lead_lag_incidence(1,dr.order_var); %columns are in DR order
+ic = [ M_.nstatic+(1:M_.nspred) ]';
 kcurr = M_.lead_lag_incidence(2,dr.order_var); %columns are in DR order
 klead = M_.lead_lag_incidence(3,dr.order_var); %columns are in DR order
 
 %% ghxx
 A = zeros(M_.endo_nbr,M_.endo_nbr);
-A(:,kcurr~=0) = jacobia(:,nonzeros(kcurr));
-A(:,ic) = A(:,ic) + jacobia(:,nonzeros(klead))*dr.ghx(klead~=0,:);
+A(:,kcurr~=0) = g1(:, icurr);
+A(:,ic) = A(:,ic) + g1(:, ilead)*dr.ghx(klead~=0,:);
 B = zeros(M_.endo_nbr,M_.endo_nbr);
-B(:,M_.nstatic+M_.npred+1:end) = jacobia(:,nonzeros(klead));
+B(:,M_.nstatic+M_.npred+1:end) = g1(:, ilead);
 C = dr.ghx(ic,:);
 zx = [eye(length(ic));
       dr.ghx(kcurr~=0,:);
@@ -85,14 +88,14 @@ zu = [zeros(length(ic),M_.exo_nbr);
       dr.ghx(klead~=0,:)*dr.ghu(ic,:);
       eye(M_.exo_nbr);
       zeros(M_.exo_det_nbr,M_.exo_nbr)];
-rhs = sparse_hessian_times_B_kronecker_C(hessian_mat(:,kk2(kk1,kk1)),zx,threads_BC); %hessian_mat: reordering to DR order
+rhs = sparse_hessian_times_B_kronecker_C(g2(:,kk2(kk1,kk1)), zx, threads_BC); % g2: reordering to DR order
 rhs = -rhs;
 dr.ghxx = gensylv(2,A,B,C,rhs);
 
 
 %% ghxu
 %rhs
-rhs = sparse_hessian_times_B_kronecker_C(hessian_mat(:,kk2(kk1,kk1)),zx,zu,threads_BC); %hessian_mat: reordering to DR order
+rhs = sparse_hessian_times_B_kronecker_C(g2(:,kk2(kk1,kk1)), zx, zu, threads_BC); % g2: reordering to DR order
 abcOut = A_times_B_kronecker_C(dr.ghxx, dr.ghx(ic,:), dr.ghu(ic,:));
 rhs = -rhs-B*abcOut;
 %lhs
@@ -100,7 +103,7 @@ dr.ghxu = A\rhs;
 
 %% ghuu
 %rhs
-rhs = sparse_hessian_times_B_kronecker_C(hessian_mat(:,kk2(kk1,kk1)),zu,threads_BC); %hessian_mat: reordering to DR order
+rhs = sparse_hessian_times_B_kronecker_C(g2(:,kk2(kk1,kk1)), zu, threads_BC); % g2: reordering to DR order
 B1 = A_times_B_kronecker_C(B*dr.ghxx,dr.ghu(ic,:));
 rhs = -rhs-B1;
 %lhs
@@ -111,13 +114,13 @@ dr.ghuu = A\rhs;
 O1 = zeros(M_.endo_nbr,M_.nstatic);
 O2 = zeros(M_.endo_nbr,M_.nfwrd);
 LHS = zeros(M_.endo_nbr,M_.endo_nbr);
-LHS(:,kcurr~=0) = jacobia(:,nonzeros(kcurr));
+LHS(:,kcurr~=0) = g1(:, icurr);
 RHS = zeros(M_.endo_nbr,M_.exo_nbr^2);
 E = eye(M_.endo_nbr);
-B1 = sparse_hessian_times_B_kronecker_C(hessian_mat(:,kk2(nonzeros(klead),nonzeros(klead))), dr.ghu(klead~=0,:),threads_BC); %hessian_mat:focus only on forward variables and reorder to DR order
-RHS = RHS + jacobia(:,nonzeros(klead))*dr.ghuu(klead~=0,:)+B1;
+B1 = sparse_hessian_times_B_kronecker_C(g2(:,kk2(ilead,ilead)), dr.ghu(klead~=0,:), threads_BC); % g2: focus only on forward variables and reorder to DR order
+RHS = RHS + g1(:, ilead)*dr.ghuu(klead~=0,:)+B1;
 % LHS
-LHS = LHS + jacobia(:,nonzeros(klead))*(E(klead~=0,:)+[O1(klead~=0,:) dr.ghx(klead~=0,:) O2(klead~=0,:)]);
+LHS = LHS + g1(:, ilead)*(E(klead~=0,:)+[O1(klead~=0,:) dr.ghx(klead~=0,:) O2(klead~=0,:)]);
 RHS = RHS*M_.Sigma_e(:);
 dr.fuu = RHS;
 RHS = -RHS;
diff --git a/matlab/stochastic_solver/stochastic_solvers.m b/matlab/stochastic_solver/stochastic_solvers.m
index 678810d56e..d382d34e45 100644
--- a/matlab/stochastic_solver/stochastic_solvers.m
+++ b/matlab/stochastic_solver/stochastic_solvers.m
@@ -96,96 +96,100 @@ if options_.k_order_solver
     return
 end
 
-klen = M_.maximum_lag + M_.maximum_lead + 1;
-exo_simul = [repmat(exo_steady_state',klen,1) repmat(exo_det_steady_state',klen,1)];
-iyv = M_.lead_lag_incidence';
-iyv = iyv(:);
-iyr0 = find(iyv) ;
+dyn_endo_ss = repmat(dr.ys, 3, 1);
+exo_ss = [exo_steady_state; exo_det_steady_state];
 
-it_ = M_.maximum_lag + 1;
-z = repmat(dr.ys,1,klen);
 if local_order == 1
     if (options_.bytecode)
+        klen = M_.maximum_lag + M_.maximum_lead + 1;
+        exo_simul = repmat(exo_ss', klen, 1);
+        z = repmat(dr.ys, 1, klen);
+
         [~, loc_dr] = bytecode('dynamic','evaluate', M_, options_, z, exo_simul, ...
                                M_.params, dr.ys, 1);
-        jacobia_ = [loc_dr.g1 loc_dr.g1_x loc_dr.g1_xd];
+        % TODO: simplify the following once bytecode MEX has been updated to sparse format
+        g1 = zeros(M_.endo_nbr, 3*M_.endo_nbr+M_.exo_nbr+M_.exo_det_nbr);
+        if M_.maximum_endo_lag > 0
+            g1(:, find(M_.lead_lag_incidence(M_.maximum_endo_lag, :))) = loc_dr.g1(:, 1:M_.nspred);
+        end
+        [~,icurr] = find(M_.lead_lag_incidence(M_.maximum_endo_lag+1, :));
+        g1(:, M_.endo_nbr + icurr) = loc_dr.g1(:, M_.nspred+(1:length(icurr)));
+        if M_.maximum_endo_lead > 0
+            g1(:, 2*M_.endo_nbr + find(M_.lead_lag_incidence(M_.maximum_endo_lag+2, :))) = loc_dr.g1(:, M_.nspred+M_.endo_nbr+(1:M_.nsfwrd));
+        end
+        g1(:, 3*M_.endo_nbr+(1:M_.exo_nbr)) = loc_dr.g1_x;
+        g1(:, 3*M_.endo_nbr+M_.exo_nbr+(1:M_.exo_det_nbr)) = loc_dr.g1_xd;
+        g1 = sparse(g1);
     else
-        [~,jacobia_] = feval([M_.fname '.dynamic'],z(iyr0),exo_simul, ...
-                             M_.params, dr.ys, it_);
+        g1 = feval([M_.fname '.sparse.dynamic_g1'], dyn_endo_ss, exo_ss, M_.params, dr.ys, ...
+                   M_.dynamic_g1_sparse_rowval, M_.dynamic_g1_sparse_colval, ...
+                   M_.dynamic_g1_sparse_colptr);
     end
 elseif local_order == 2
     if (options_.bytecode)
         warning('Option "bytecode" is ignored when computing perturbation solution at order = 2')
     end
-    [~,jacobia_,hessian1] = feval([M_.fname '.dynamic'],z(iyr0),...
-                                  exo_simul, ...
-                                  M_.params, dr.ys, it_);
-    [infrow, ~] = find(isinf(hessian1));
-    if options_.debug
-        if ~isempty(infrow)
+    [g1, T_order, T] = feval([M_.fname '.sparse.dynamic_g1'], dyn_endo_ss, exo_ss, M_.params, ...
+                             dr.ys, M_.dynamic_g1_sparse_rowval, M_.dynamic_g1_sparse_colval, ...
+                             M_.dynamic_g1_sparse_colptr);
+    g2_v = feval([M_.fname '.sparse.dynamic_g2'], dyn_endo_ss, exo_ss, M_.params, dr.ys, T_order, T);
+
+    g2 = build_two_dim_hessian(M_.dynamic_g2_sparse_indices, g2_v, size(g1, 1), size(g1, 2));
+
+    if any(any(isinf(g2)))
+        if options_.debug
             fprintf('\nSTOCHASTIC_SOLVER: The Hessian of the dynamic model contains Inf.\n')
             fprintf('STOCHASTIC_SOLVER: Try running model_diagnostics to find the source of the problem.\n')
-            save([M_.dname filesep 'Output' filesep M_.fname '_debug.mat'],'hessian1')
+            save([M_.dname filesep 'Output' filesep M_.fname '_debug.mat'], 'g2')
         end
-    end
-    if ~isempty(infrow)
         info(1)=11;
         return
     end
-    [nanrow, ~] = find(isnan(hessian1));
-    if options_.debug
-        if ~isempty(nanrow)
+
+    if any(any(isnan(g2)))
+        if options_.debug
             fprintf('\nSTOCHASTIC_SOLVER: The Hessian of the dynamic model contains NaN.\n')
             fprintf('STOCHASTIC_SOLVER: Try running model_diagnostics to find the source of the problem.\n')
-            save([M_.dname filesep 'Output' filesep M_.fname '_debug.mat'],'hessian1')
+            save([M_.dname filesep 'Output' filesep M_.fname '_debug.mat'], 'g2')
         end
-    end
-    if ~isempty(nanrow)
         info(1)=12;
         return
     end
 end
 
-[infrow,infcol]=find(isinf(jacobia_));
-
-if options_.debug
-    if ~isempty(infrow)
+[infrow, infcol] = find(isinf(g1));
+if ~isempty(infrow)
+    if options_.debug
         fprintf('\nSTOCHASTIC_SOLVER: The Jacobian of the dynamic model contains Inf. The problem is associated with:\n\n')
         display_problematic_vars_Jacobian(infrow,infcol,M_,dr.ys,'dynamic','STOCHASTIC_SOLVER: ')
-        save([M_.dname filesep 'Output' filesep M_.fname '_debug.mat'],'jacobia_')
+        save([M_.dname filesep 'Output' filesep M_.fname '_debug.mat'], 'g1')
     end
-end
-
-if ~isempty(infrow)
     info(1)=10;
     return
 end
 
-if ~isreal(jacobia_)
-    if max(max(abs(imag(jacobia_)))) < 1e-15
-        jacobia_ = real(jacobia_);
+if ~isreal(g1)
+    if max(max(abs(imag(g1)))) < 1e-15
+        g1 = real(g1);
     else
         if options_.debug
-            [imagrow,imagcol]=find(abs(imag(jacobia_))>1e-15);
+            [imagrow, imagcol]=find(abs(imag(g1)) > 1e-15);
             fprintf('\nMODEL_DIAGNOSTICS: The Jacobian of the dynamic model contains imaginary parts. The problem arises from: \n\n')
             display_problematic_vars_Jacobian(imagrow,imagcol,M_,dr.ys,'dynamic','STOCHASTIC_SOLVER: ')
         end
         info(1) = 6;
-        info(2) = sum(sum(imag(jacobia_).^2));
+        info(2) = sum(sum(imag(g1).^2));
         return
     end
 end
 
-[nanrow,nancol]=find(isnan(jacobia_));
-if options_.debug
-    if ~isempty(nanrow)
+[nanrow, nancol] = find(isnan(g1));
+if ~isempty(nanrow)
+    if options_.debug
         fprintf('\nSTOCHASTIC_SOLVER: The Jacobian of the dynamic model contains NaN. The problem is associated with:\n\n')
         display_problematic_vars_Jacobian(nanrow,nancol,M_,dr.ys,'dynamic','STOCHASTIC_SOLVER: ')
-        save([M_.dname filesep 'Output' filesep M_.fname '_debug.mat'],'jacobia_')
+        save([M_.dname filesep 'Output' filesep M_.fname '_debug.mat'], 'g1')
     end
-end
-
-if ~isempty(nanrow)
     info(1) = 8;
     NaN_params=find(isnan(M_.params));
     info(2:length(NaN_params)+1) =  NaN_params;
@@ -193,20 +197,14 @@ if ~isempty(nanrow)
 end
 
 nstatic = M_.nstatic;
+npred = M_.npred;
 nfwrd = M_.nfwrd;
 nspred = M_.nspred;
 nboth = M_.nboth;
 nsfwrd = M_.nsfwrd;
 order_var = dr.order_var;
 nd = M_.nspred+M_.nsfwrd;
-nz = nnz(M_.lead_lag_incidence);
-
-sdyn = M_.endo_nbr - nstatic;
-
-[~,cols_b,cols_j] = find(M_.lead_lag_incidence(M_.maximum_endo_lag+1, ...
-                                               order_var));
-b = zeros(M_.endo_nbr,M_.endo_nbr);
-b(:,cols_b) = jacobia_(:,cols_j);
+[~,icurr_dr] = find(M_.lead_lag_incidence(M_.maximum_endo_lag+1, order_var));
 
 if M_.maximum_endo_lead == 0
     % backward models: simplified code exist only at order == 1
@@ -216,9 +214,10 @@ if M_.maximum_endo_lead == 0
         else
             dr.state_var = [];
         end
-        dr.ghx = -b\jacobia_(:,1:nspred);
+        b = full(g1(:, M_.endo_nbr+order_var(icurr_dr)));
+        dr.ghx = -b \ full(g1(:, order_var(nstatic+(1:nspred))));
         if M_.exo_nbr
-            dr.ghu =  -b\jacobia_(:,nz+1:end);
+            dr.ghu =  -b \ full(g1(:, 3*M_.endo_nbr+1:end));
         end
         dr.eigval = eig(kalman_transition_matrix(dr,nstatic+(1:nspred),1:nspred));
         dr.full_rank = 1;
@@ -240,10 +239,9 @@ if M_.maximum_endo_lead == 0
 else
     % If required, use AIM solver if not check only
     if options_.aim_solver && (task == 0)
-        [dr,info] = AIM_first_order_solver(jacobia_,M_,dr,options_.qz_criterium);
-
+        [dr, info] = AIM_first_order_solver(g1, M_, dr, options_.qz_criterium);
     else  % use original Dynare solver
-        [dr,info] = dyn_first_order_solver(jacobia_,M_,dr,options_,task);
+        [dr, info] = dyn_first_order_solver(g1, M_, dr, options_, task);
         if info(1) || task
             return
         end
@@ -251,17 +249,8 @@ else
 
     if local_order > 1
         % Second order
-        dr = dyn_second_order_solver(jacobia_,hessian1,dr,M_,...
+        dr = dyn_second_order_solver(g1, g2, dr, M_, ...
                                      options_.threads.kronecker.sparse_hessian_times_B_kronecker_C);
-
-        % reordering second order derivatives, used for deterministic
-        % variables below
-        k1 = nonzeros(M_.lead_lag_incidence(:,order_var)');
-        kk = [k1; length(k1)+(1:M_.exo_nbr+M_.exo_det_nbr)'];
-        nk = size(kk,1);
-        kk1 = reshape(1:nk^2,nk,nk);
-        kk1 = kk1(kk,kk);
-        hessian1 = hessian1(:,kk1(:));
     end
 end
 
@@ -269,9 +258,9 @@ end
 %exogenous deterministic variables
 if M_.exo_det_nbr > 0
     gx = dr.gx;
-    f1 = sparse(jacobia_(:,nonzeros(M_.lead_lag_incidence(M_.maximum_endo_lag+2:end,order_var))));
-    f0 = sparse(jacobia_(:,nonzeros(M_.lead_lag_incidence(M_.maximum_endo_lag+1,order_var))));
-    fudet = sparse(jacobia_(:,nz+M_.exo_nbr+1:end));
+    f1 = g1(:,2*M_.endo_nbr + order_var(nstatic+npred+(1:nsfwrd)));
+    f0 = g1(:,M_.endo_nbr + order_var(icurr_dr));
+    fudet = g1(:,3*M_.endo_nbr+M_.exo_nbr+1:end);
     M1 = inv(f0+[zeros(M_.endo_nbr,nstatic) f1*gx zeros(M_.endo_nbr,nsfwrd-nboth)]);
     M2 = M1*f1;
     dr.ghud = cell(M_.exo_det_length,1);
@@ -281,6 +270,15 @@ if M_.exo_det_nbr > 0
     end
 
     if local_order > 1
+        % reordering second order derivatives
+        kk1 = [order_var(nstatic+(1:nspred));
+               M_.endo_nbr + order_var(icurr_dr);
+               2*M_.endo_nbr + order_var(nstatic+npred+(1:nsfwrd));
+               3*M_.endo_nbr+(1:M_.exo_nbr+M_.exo_det_nbr)'];
+        nk = size(g1, 2);
+        kk2 = reshape(1:nk^2, nk, nk);
+        g2_reordered = g2(:,kk2(kk1,kk1));
+
         lead_lag_incidence = M_.lead_lag_incidence;
         k0 = find(lead_lag_incidence(M_.maximum_endo_lag+1,order_var)');
         k1 = find(lead_lag_incidence(M_.maximum_endo_lag+2,order_var)');
@@ -291,7 +289,7 @@ if M_.exo_det_nbr > 0
         zu = [zeros(nspred,M_.exo_nbr); dr.ghu(k0,:); gx*hu; zeros(M_.exo_nbr+M_.exo_det_nbr, ...
                                                           M_.exo_nbr)];
         zud=[zeros(nspred,M_.exo_det_nbr);dr.ghud{1};gx(:,1:nspred)*hud;zeros(M_.exo_nbr,M_.exo_det_nbr);eye(M_.exo_det_nbr)];
-        R1 = hessian1*kron(zx,zud);
+        R1 = g2_reordered*kron(zx,zud);
         dr.ghxud = cell(M_.exo_det_length,1);
         kf = M_.endo_nbr-nfwrd-nboth+1:M_.endo_nbr;
         kp = nstatic+[1:nspred];
@@ -300,37 +298,37 @@ if M_.exo_det_nbr > 0
         for i = 2:M_.exo_det_length
             hudi = dr.ghud{i}(kp,:);
             zudi=[zeros(nspred,M_.exo_det_nbr);dr.ghud{i};gx(:,1:nspred)*hudi;zeros(M_.exo_nbr+M_.exo_det_nbr,M_.exo_det_nbr)];
-            R2 = hessian1*kron(zx,zudi);
+            R2 = g2_reordered*kron(zx,zudi);
             dr.ghxud{i} = -M2*(dr.ghxud{i-1}(kf,:)*kron(dr.Gy,Eud)+dr.ghxx(kf,:)*kron(dr.ghx(kp,:),dr.ghud{i}(kp,:)))-M1*R2;
         end
-        R1 = hessian1*kron(zu,zud);
+        R1 = g2_reordered*kron(zu,zud);
         dr.ghudud = cell(M_.exo_det_length,1);
         dr.ghuud{1} = -M1*(R1+f1*dr.ghxx(kf,:)*kron(dr.ghu(kp,:),dr.ghud{1}(kp,:)));
         Eud = eye(M_.exo_det_nbr);
         for i = 2:M_.exo_det_length
             hudi = dr.ghud{i}(kp,:);
             zudi=[zeros(nspred,M_.exo_det_nbr);dr.ghud{i};gx(:,1:nspred)*hudi;zeros(M_.exo_nbr+M_.exo_det_nbr,M_.exo_det_nbr)];
-            R2 = hessian1*kron(zu,zudi);
+            R2 = g2_reordered*kron(zu,zudi);
             dr.ghuud{i} = -M2*dr.ghxud{i-1}(kf,:)*kron(hu,Eud)-M1*R2;
         end
-        R1 = hessian1*kron(zud,zud);
+        R1 = g2_reordered*kron(zud,zud);
         dr.ghudud = cell(M_.exo_det_length,M_.exo_det_length);
         dr.ghudud{1,1} = -M1*R1-M2*dr.ghxx(kf,:)*kron(hud,hud);
         for i = 2:M_.exo_det_length
             hudi = dr.ghud{i}(nstatic+1:nstatic+nspred,:);
             zudi=[zeros(nspred,M_.exo_det_nbr);dr.ghud{i};gx(:,1:nspred)*hudi+dr.ghud{i-1}(kf,:);zeros(M_.exo_nbr+M_.exo_det_nbr,M_.exo_det_nbr)];
-            R2 = hessian1*kron(zudi,zudi);
+            R2 = g2_reordered*kron(zudi,zudi);
             dr.ghudud{i,i} = -M2*(dr.ghudud{i-1,i-1}(kf,:)+...
                                   2*dr.ghxud{i-1}(kf,:)*kron(hudi,Eud) ...
                                   +dr.ghxx(kf,:)*kron(hudi,hudi))-M1*R2;
-            R2 = hessian1*kron(zud,zudi);
+            R2 = g2_reordered*kron(zud,zudi);
             dr.ghudud{1,i} = -M2*(dr.ghxud{i-1}(kf,:)*kron(hud,Eud)+...
                                   dr.ghxx(kf,:)*kron(hud,hudi))...
                 -M1*R2;
             for j=2:i-1
                 hudj = dr.ghud{j}(kp,:);
                 zudj=[zeros(nspred,M_.exo_det_nbr);dr.ghud{j};gx(:,1:nspred)*hudj;zeros(M_.exo_nbr+M_.exo_det_nbr,M_.exo_det_nbr)];
-                R2 = hessian1*kron(zudj,zudi);
+                R2 = g2_reordered*kron(zudj,zudi);
                 dr.ghudud{j,i} = -M2*(dr.ghudud{j-1,i-1}(kf,:)+dr.ghxud{j-1}(kf,:)* ...
                                       kron(hudi,Eud)+dr.ghxud{i-1}(kf,:)* ...
                                       kron(hudj,Eud)+dr.ghxx(kf,:)*kron(hudj,hudi))-M1*R2;
-- 
GitLab