diff --git a/matlab/perfect-foresight-models/perfect_foresight_problem.m b/matlab/perfect-foresight-models/perfect_foresight_problem.m
deleted file mode 100644
index 07220cdcef0609fbe995bab06946c7ab4a2f5349..0000000000000000000000000000000000000000
--- a/matlab/perfect-foresight-models/perfect_foresight_problem.m
+++ /dev/null
@@ -1,102 +0,0 @@
-function [residuals,JJacobian] = perfect_foresight_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)
-
-% Computes the residuals and the Jacobian matrix for a perfect foresight problem over T periods.
-%
-% 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 contemporaneous variables in M.lead_lag_incidence
-%                                in dynamic Jacobian (relevant in intermediate periods)
-% - i_cols_0            [double] indices of contemporaneous variables in M.lead_lag_incidence in dynamic
-%                                Jacobian (relevant in problems with periods=1)
-% - i_cols_J0           [double] indices of contemporaneous variables appearing in M.lead_lag_incidence (relevant in problems with periods=1)
-%
-% 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.
-
-% Copyright (C) 1996-2019 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 <http://www.gnu.org/licenses/>.
-
-
-YY = [Y0; y; YT];
-
-residuals = zeros(T*ny,1);
-if nargout == 2
-    iJacobian = cell(T,1);
-end
-
-i_rows = 1:ny;
-i_cols_J = i_cols;
-offset = 0;
-
-for it = maximum_lag+(1:T)
-    if nargout == 1
-        residuals(i_rows) = dynamic_function(YY(i_cols), exo_simul, params, steady_state, it);
-    elseif nargout == 2
-        [residuals(i_rows),jacobian] = dynamic_function(YY(i_cols), exo_simul, params, steady_state, it);
-        if T==1 && it==maximum_lag+1
-            [rows, cols, vals] = find(jacobian(:,i_cols_0));
-            iJacobian{1} = [rows, i_cols_J0(cols), vals];
-        elseif it == maximum_lag+1
-            [rows,cols,vals] = find(jacobian(:,i_cols_1));
-            iJacobian{1} = [offset+rows, i_cols_J1(cols), vals];
-        elseif it == maximum_lag+T
-            [rows,cols,vals] = find(jacobian(:,i_cols_T));
-            iJacobian{T} = [offset+rows, i_cols_J(i_cols_T(cols)), vals];
-        else
-            [rows,cols,vals] = find(jacobian(:,i_cols_j));
-            iJacobian{it-maximum_lag} = [offset+rows, i_cols_J(cols), vals];
-            i_cols_J = i_cols_J + ny;
-        end
-        offset = offset + ny;
-    end
-    i_rows = i_rows + ny;
-    i_cols = i_cols + ny;
-end
-
-if nargout == 2
-    iJacobian = cat(1,iJacobian{:});
-    JJacobian = sparse(iJacobian(:,1), iJacobian(:,2), iJacobian(:,3), T*ny, T*ny);
-end
diff --git a/matlab/perfect-foresight-models/perfect_foresight_solver.m b/matlab/perfect-foresight-models/perfect_foresight_solver.m
index 9816d65967663de18797d6efc1749f55aa6ce995..a67ad34a2bee5d14e73efe3ded6efdf6b28f38a5 100644
--- a/matlab/perfect-foresight-models/perfect_foresight_solver.m
+++ b/matlab/perfect-foresight-models/perfect_foresight_solver.m
@@ -176,26 +176,23 @@ if ~oo_.deterministic_simulation.status && ~options_.no_homotopy
 end
 
 
-if ~isreal(oo_.endo_simul(:)) % can only happen without bytecode
-    y0 = real(oo_.endo_simul(:,1));
-    yT = real(oo_.endo_simul(:,periods+2));
-    yy  = real(oo_.endo_simul(:,2:periods+1));
-    illi = M_.lead_lag_incidence';
-    [i_cols,~,i_cols_j] = find(illi(:));
-    illi = illi(:,2:3);
-    [i_cols_J1,~,i_cols_1] = find(illi(:));
-    i_cols_T = nonzeros(M_.lead_lag_incidence(1:2,:)');
-    if periods==1
-        i_cols_0 = nonzeros(M_.lead_lag_incidence(2,:)');
-        i_cols_J0 = find(M_.lead_lag_incidence(2,:)');
+if ~isreal(oo_.endo_simul(:)) % cannot happen with bytecode or the perfect_foresight_problem DLL
+    if M_.maximum_lag > 0
+        y0 = real(oo_.endo_simul(:, M_.maximum_lag));
     else
-        i_cols_0 = [];
-        i_cols_J0 = [];
+        y0 = NaN(ny, 1);
     end
-    residuals = perfect_foresight_problem(yy(:),str2func([M_.fname '.dynamic']), y0, yT, ...
-                                          oo_.exo_simul,M_.params,oo_.steady_state, ...
-                                          M_.maximum_lag, periods, M_.endo_nbr, i_cols, ...
-                                          i_cols_J1, i_cols_1, i_cols_T, i_cols_j, i_cols_0, i_cols_J0);
+    if M_.maximum_lead > 0
+        yT = real(oo_.endo_simul(:, M_.maximum_lag+periods+1));
+    else
+        yT = NaN(ny, 1);
+    end
+    yy = real(oo_.endo_simul(:,M_.maximum_lag+(1:periods)));
+    model_dynamic_g1_nz = str2func([M_.fname,'.dynamic_g1_nz']);
+    [nzij_pred, nzij_current, nzij_fwrd] = model_dynamic_g1_nz();
+
+    residuals = perfect_foresight_problem(yy(:), M_.fname, sum(M_.dynamic_tmp_nbr(1:2)), y0, yT, oo_.exo_simul, M_.params, oo_.steady_state, periods, M_.endo_nbr, M_.maximum_lag, M_.maximum_endo_lag, M_.lead_lag_incidence, nzij_pred, nzij_current, nzij_fwrd, M_.has_external_function, options_.use_dll);
+
     if max(abs(residuals))< options_.dynatol.f
         oo_.deterministic_simulation.status = 1;
         oo_.endo_simul=real(oo_.endo_simul);
diff --git a/matlab/perfect-foresight-models/perfect_foresight_solver_core.m b/matlab/perfect-foresight-models/perfect_foresight_solver_core.m
index 24464e8f1996eb1affc9099eb983c41b745d5143..76a6e2d2f55352fd9441c2277b2b636158b1a073 100644
--- a/matlab/perfect-foresight-models/perfect_foresight_solver_core.m
+++ b/matlab/perfect-foresight-models/perfect_foresight_solver_core.m
@@ -120,31 +120,27 @@ else
 end
 
 if nargout>1
-    y0 = oo_.endo_simul(:,1);
-    yT = oo_.endo_simul(:,periods+2);
-    yy  = oo_.endo_simul(:,2:periods+1);
-    illi = M_.lead_lag_incidence';
-    [i_cols,~,i_cols_j] = find(illi(:));
-    illi = illi(:,2:3);
-    [i_cols_J1,~,i_cols_1] = find(illi(:));
-    i_cols_T = nonzeros(M_.lead_lag_incidence(1:2,:)');
-    if periods==1
-        i_cols_0 = nonzeros(M_.lead_lag_incidence(2,:)');
-        i_cols_J0 = find(M_.lead_lag_incidence(2,:)');
-    else
-        i_cols_0 = [];
-        i_cols_J0 = [];
-    end
     if options_.block && ~options_.bytecode
         maxerror = oo_.deterministic_simulation.error;
     else
         if options_.bytecode
             [chck, residuals, ~]= bytecode('dynamic','evaluate', oo_.endo_simul, oo_.exo_simul, M_.params, oo_.steady_state, 1);
         else
-            residuals = perfect_foresight_problem(yy(:),str2func([M_.fname '.dynamic']), y0, yT, ...
-                                                  oo_.exo_simul,M_.params,oo_.steady_state, ...
-                                                  M_.maximum_lag, periods,M_.endo_nbr,i_cols, ...
-                                                  i_cols_J1, i_cols_1, i_cols_T, i_cols_j, i_cols_0, i_cols_J0);
+            if M_.maximum_lag > 0
+                y0 = oo_.endo_simul(:, M_.maximum_lag);
+            else
+                y0 = NaN(ny, 1);
+            end
+            if M_.maximum_lead > 0
+                yT = oo_.endo_simul(:, M_.maximum_lag+periods+1);
+            else
+                yT = NaN(ny, 1);
+            end
+            yy = oo_.endo_simul(:,M_.maximum_lag+(1:periods));
+            model_dynamic_g1_nz = str2func([M_.fname,'.dynamic_g1_nz']);
+            [nzij_pred, nzij_current, nzij_fwrd] = model_dynamic_g1_nz();
+
+            residuals = perfect_foresight_problem(yy(:), M_.fname, sum(M_.dynamic_tmp_nbr(1:2)), y0, yT, oo_.exo_simul, M_.params, oo_.steady_state, periods, M_.endo_nbr, M_.maximum_lag, M_.maximum_endo_lag, M_.lead_lag_incidence, nzij_pred, nzij_current, nzij_fwrd, M_.has_external_function, options_.use_dll);
         end
         maxerror = max(max(abs(residuals)));
     end
diff --git a/matlab/perfect-foresight-models/private/initialize_stacked_problem.m b/matlab/perfect-foresight-models/private/initialize_stacked_problem.m
index 55290fc9f97c2e3a8e13af44c867725caa3eefd3..5677add41d9678b82ea0c2d240cf737f3c839b6c 100644
--- a/matlab/perfect-foresight-models/private/initialize_stacked_problem.m
+++ b/matlab/perfect-foresight-models/private/initialize_stacked_problem.m
@@ -70,8 +70,16 @@ elseif (options.solve_algo == 11)
     end
 end
 
-y0 = endogenousvariables(:,M.maximum_lag);
-yT = endogenousvariables(:,M.maximum_lag+periods+1);
+if M.maximum_lag > 0
+    y0 = endogenousvariables(:, M.maximum_lag);
+else
+    y0 = NaN(ny, 1);
+end
+if M.maximum_lead > 0
+    yT = endogenousvariables(:, M.maximum_lag+periods+1);
+else
+    yT = NaN(ny, 1);
+end
 z = endogenousvariables(:,M.maximum_lag+(1:periods));
 illi = M.lead_lag_incidence';
 [i_cols,~,i_cols_j] = find(illi(:));
@@ -85,4 +93,4 @@ else
     i_cols_0 = [];
     i_cols_J0 = [];
 end
-dynamicmodel = str2func([M.fname,'.dynamic']);
\ No newline at end of file
+dynamicmodel = str2func([M.fname,'.dynamic']);
diff --git a/matlab/perfect-foresight-models/sim1.m b/matlab/perfect-foresight-models/sim1.m
index 1453875a7875427353961a512ea6032ed0bd3c2a..1379ade92c0b1c57333de4dea992d84e02d1c6ad 100644
--- a/matlab/perfect-foresight-models/sim1.m
+++ b/matlab/perfect-foresight-models/sim1.m
@@ -1,21 +1,17 @@
 function [endogenousvariables, info] = sim1(endogenousvariables, exogenousvariables, steadystate, M, options)
-
-% Performs deterministic simulations with lead or lag on one period. Uses sparse matrices.
+% Performs deterministic simulations with lead or lag of one period, using
+% a basic Newton solver on sparse matrices.
+% Uses perfect_foresight_problem DLL to construct the stacked problem.
 %
 % INPUTS
-%   - endogenousvariables [double] N*T array, paths for the endogenous variables (initial guess).
+%   - endogenousvariables [double] N*(T+M.maximum_lag+M.maximum_lead) array, paths for the endogenous variables (initial condition + initial guess + terminal condition).
 %   - exogenousvariables  [double] T*M array, paths for the exogenous variables.
 %   - steadystate       [double] N*1 array, steady state for the endogenous variables.
 %   - M                   [struct] contains a description of the model.
 %   - options             [struct] contains various options.
 % OUTPUTS
-%   - endogenousvariables [double] N*T array, paths for the endogenous variables (solution of the perfect foresight model).
+%   - endogenousvariables [double] N*(T+M.maximum_lag+M.maximum_lead) array, paths for the endogenous variables (solution of the perfect foresight model).
 %   - info                [struct] contains informations about the results.
-% ALGORITHM
-%   ...
-%
-% SPECIAL REQUIREMENTS
-%   None.
 
 % Copyright (C) 1996-2019 Dynare Team
 %
@@ -36,38 +32,25 @@ function [endogenousvariables, info] = sim1(endogenousvariables, exogenousvariab
 
 verbose = options.verbosity;
 
-endogenous_terminal_period = options.endogenous_terminal_period;
-vperiods = options.periods*ones(1,options.simul.maxit);
-azero = options.dynatol.f/1e7;
-
-lead_lag_incidence = M.lead_lag_incidence;
-
 ny = M.endo_nbr;
+periods = options.periods;
+vperiods = periods*ones(1,options.simul.maxit);
 
-maximum_lag = M.maximum_lag;
-max_lag = M.maximum_endo_lag;
-
-nyp = nnz(lead_lag_incidence(1,:)) ;
-ny0 = nnz(lead_lag_incidence(2,:)) ;
-nyf = nnz(lead_lag_incidence(3,:)) ;
-
-nd = nyp+ny0+nyf;
-stop = 0 ;
+if M.maximum_lag > 0
+    y0 = endogenousvariables(:, M.maximum_lag);
+else
+    y0 = NaN(ny, 1);
+end
 
-periods = options.periods;
-params = M.params;
+if M.maximum_lead > 0
+    yT = endogenousvariables(:, M.maximum_lag+periods+1);
+else
+    yT = NaN(ny, 1);
+end
 
-i_cols_1 = nonzeros(lead_lag_incidence(2:3,:)');
-i_cols_A1 = find(lead_lag_incidence(2:3,:)');
-i_cols_A1 = i_cols_A1(:);
-i_cols_T = nonzeros(lead_lag_incidence(1:2,:)');
-i_cols_0 = nonzeros(lead_lag_incidence(2,:)');
-i_cols_A0 = find(lead_lag_incidence(2,:)');
-i_cols_A0 = i_cols_A0(:);
-i_cols_j = (1:nd)';
-i_upd = maximum_lag*ny+(1:periods*ny);
+y = reshape(endogenousvariables(:, M.maximum_lag+(1:periods)), ny*periods, 1);
 
-Y = endogenousvariables(:);
+stop = false;
 
 if verbose
     skipline()
@@ -76,121 +59,78 @@ if verbose
     skipline()
 end
 
-model_dynamic = str2func([M.fname,'.dynamic']);
-
-res = zeros(periods*ny,1);
-
-o_periods = periods;
-
-if endogenous_terminal_period
-    ZERO = zeros(length(i_upd),1);
-end
+model_dynamic_g1_nz = str2func([M.fname,'.dynamic_g1_nz']);
+[nzij_pred, nzij_current, nzij_fwrd] = model_dynamic_g1_nz();
 
-h1 = clock ;
-iA = zeros(periods*M.NNZDerivatives(1),3);
+h1 = clock;
 
 for iter = 1:options.simul.maxit
-    h2 = clock ;
-    i_rows = (1:ny)';
-    i_cols_A = find(lead_lag_incidence');
-    i_cols_A = i_cols_A(:);
-    i_cols = i_cols_A+(maximum_lag-1)*ny;
-    m = 0;
-    for it = (maximum_lag+1):(maximum_lag+periods)
-        [d1,jacobian] = model_dynamic(Y(i_cols), exogenousvariables, params, steadystate,it);
-        if periods==1 && it==maximum_lag+1
-            [r,c,v] = find(jacobian(:,i_cols_0));
-            iA((1:length(v))+m,:) = [i_rows(r(:)),i_cols_A0(c(:)),v(:)];
-        elseif it == maximum_lag+periods
-            [r,c,v] = find(jacobian(:,i_cols_T));
-            iA((1:length(v))+m,:) = [i_rows(r(:)),i_cols_A(i_cols_T(c(:))),v(:)];
-        elseif it == maximum_lag+1
-            [r,c,v] = find(jacobian(:,i_cols_1));
-            iA((1:length(v))+m,:) = [i_rows(r(:)),i_cols_A1(c(:)),v(:)];
-        else
-            [r,c,v] = find(jacobian(:,i_cols_j));
-            iA((1:length(v))+m,:) = [i_rows(r(:)),i_cols_A(c(:)),v(:)];
-        end
-        m = m + length(v);
-        res(i_rows) = d1;
-        if endogenous_terminal_period && iter>1
-            dr = max(abs(d1));
-            if dr<azero
-                vperiods(iter) = it;
-                periods = it-maximum_lag+1;
+    h2 = clock;
+
+    [res, A] = perfect_foresight_problem(y, M.fname, sum(M.dynamic_tmp_nbr(1:2)), y0, yT, exogenousvariables, M.params, steadystate, periods, ny, M.maximum_lag, M.maximum_endo_lag, M.lead_lag_incidence, nzij_pred, nzij_current, nzij_fwrd, M.has_external_function, options.use_dll);
+
+    if options.endogenous_terminal_period && iter > 1
+        for it = 1:periods
+            if max(abs(res((it-1)*ny+(1:ny)))) < options.dynatol.f/1e7
+                if it < periods
+                    res = res(1:(it*ny));
+                    A = A(1:(it*ny), 1:(it*ny));
+                    yT = y(it*ny+(1:ny));
+                    endogenousvariables(:, M.maximum_lag+((it+1):periods)) = reshape(y(it*ny+(1:(ny*(periods-it)))), ny, periods-it);
+                    y = y(1:(it*ny));
+                    periods = it;
+                end
                 break
             end
         end
-        i_rows = i_rows + ny;
-        i_cols = i_cols + ny;
-        if it > maximum_lag+1
-            i_cols_A = i_cols_A + ny;
-        end
+        vperiods(iter) = periods;
     end
+
     err = max(abs(res));
     if options.debug
         fprintf('\nLargest absolute residual at iteration %d: %10.3f\n',iter,err);
-        if any(isnan(res)) || any(isinf(res)) || any(isnan(Y)) || any(isinf(Y))
+        if any(isnan(res)) || any(isinf(res)) || any(any(isnan(endogenousvariables))) || any(any(isinf(endogenousvariables)))
             fprintf('\nWARNING: NaN or Inf detected in the residuals or endogenous variables.\n');
         end
-        if ~isreal(res) || ~isreal(Y)
-            fprintf('\nWARNING: Imaginary parts detected in the residuals or endogenous variables.\n');
-        end
         skipline()
     end
     if verbose
-        str = sprintf('Iter: %s,\t err. = %s, \t time = %s',num2str(iter),num2str(err), num2str(etime(clock,h2)));
-        disp(str);
+        fprintf('Iter: %d,\t err. = %g,\t time = %g\n', iter, err, etime(clock,h2));
     end
     if err < options.dynatol.f
-        stop = 1 ;
+        stop = true;
         break
     end
-    iA = iA(1:m,:);
-    A = sparse(iA(:,1),iA(:,2),iA(:,3),periods*ny,periods*ny);
-    if endogenous_terminal_period && iter>1
-        dy = ZERO;
-        if options.simul.robust_lin_solve
-            dy(1:i_rows(end)) = -lin_solve_robust( A(1:i_rows(end),1:i_rows(end)), res(1:i_rows(end)),verbose );
-        else
-            dy(1:i_rows(end)) = -lin_solve( A(1:i_rows(end),1:i_rows(end)), res(1:i_rows(end)), verbose );
-        end
+    if options.simul.robust_lin_solve
+        dy = -lin_solve_robust(A, res, verbose);
     else
-        if options.simul.robust_lin_solve
-            dy = -lin_solve_robust( A, res, verbose );
-        else
-            dy = -lin_solve( A, res, verbose );
-        end
+        dy = -lin_solve(A, res, verbose);
     end
-    if any(~isreal(dy)) || any(isnan(dy)) || any(isinf(dy))
+    if any(isnan(dy)) || any(isinf(dy))
         if verbose
             display_critical_variables(reshape(dy,[ny periods])', M);
         end
     end
-    Y(i_upd) = Y(i_upd) + dy;
+    y = y + dy;
 end
 
-if endogenous_terminal_period
-    err = evaluate_max_dynamic_residual(model_dynamic, Y, exogenousvariables, params, steadystate, o_periods, ny, max_lag, lead_lag_incidence);
-    periods = o_periods;
-end
+endogenousvariables(:, M.maximum_lag+(1:periods)) = reshape(y, ny, periods);
 
+if options.endogenous_terminal_period
+    periods = options.periods;
+    err = evaluate_max_dynamic_residual(str2func([M.fname,'.dynamic']), endogenousvariables, exogenousvariables, M.params, steadystate, periods, ny, M.maximum_endo_lag, M.lead_lag_incidence);
+end
 
 if stop
-    if any(isnan(res)) || any(isinf(res)) || any(isnan(Y)) || any(isinf(Y)) || ~isreal(res) || ~isreal(Y)
+    if any(any(isnan(endogenousvariables))) || any(any(isinf(endogenousvariables)))
         info.status = false;% NaN or Inf occurred
         info.error = err;
         info.iterations = iter;
         info.periods = vperiods(1:iter);
-        endogenousvariables = reshape(Y,ny,periods+maximum_lag+M.maximum_lead);
         if verbose
             skipline()
-            disp(sprintf('Total time of simulation: %s.', num2str(etime(clock,h1))))
-            if ~isreal(res) || ~isreal(Y)
-                disp('Simulation terminated with imaginary parts in the residuals or endogenous variables.')
-            else
-                disp('Simulation terminated with NaN or Inf in the residuals or endogenous variables.')
-            end
+            fprintf('Total time of simulation: %g.\n', etime(clock,h1))
+            disp('Simulation terminated with NaN or Inf in the residuals or endogenous variables.')
             display_critical_variables(reshape(dy,[ny periods])', M);
             disp('There is most likely something wrong with your model. Try model_diagnostics or another simulation method.')
             printline(105)
@@ -198,19 +138,18 @@ if stop
     else
         if verbose
             skipline();
-            disp(sprintf('Total time of simulation: %s', num2str(etime(clock,h1))))
+            fprintf('Total time of simulation: %g.\n', etime(clock,h1))
             printline(56)
         end
         info.status = true;% Convergency obtained.
         info.error = err;
         info.iterations = iter;
         info.periods = vperiods(1:iter);
-        endogenousvariables = reshape(Y,ny,periods+maximum_lag+M.maximum_lead);
     end
 elseif ~stop
     if verbose
         skipline();
-        disp(sprintf('Total time of simulation: %s.', num2str(etime(clock,h1))))
+        fprintf('Total time of simulation: %g.\n', etime(clock,h1))
         disp('Maximum number of iterations is reached (modify option maxit).')
         printline(62)
     end
@@ -224,21 +163,21 @@ if verbose
     skipline();
 end
 
-function x = lin_solve( A, b,verbose)
-if norm( b ) < sqrt( eps ) % then x = 0 is a solution
+function x = lin_solve(A, b, verbose)
+if norm(b) < sqrt(eps) % then x = 0 is a solution
     x = 0;
     return
 end
 
 x = A\b;
-x( ~isfinite( x ) ) = 0;
-relres = norm( b - A * x ) / norm( b );
+x(~isfinite(x)) = 0;
+relres = norm(b - A*x) / norm(b);
 if relres > 1e-6 && verbose
-    fprintf( 'WARNING : Failed to find a solution to the linear system.\n' );
+    fprintf('WARNING : Failed to find a solution to the linear system.\n');
 end
 
-function [ x, flag, relres ] = lin_solve_robust( A, b , verbose)
-if norm( b ) < sqrt( eps ) % then x = 0 is a solution
+function [ x, flag, relres ] = lin_solve_robust(A, b ,verbose)
+if norm(b) < sqrt(eps) % then x = 0 is a solution
     x = 0;
     flag = 0;
     relres = 0;
@@ -246,8 +185,8 @@ if norm( b ) < sqrt( eps ) % then x = 0 is a solution
 end
 
 x = A\b;
-x( ~isfinite( x ) ) = 0;
-[ x, flag, relres ] = bicgstab( A, b, [], [], [], [], x ); % returns immediately if x is a solution
+x(~isfinite(x)) = 0;
+[ x, flag, relres ] = bicgstab(A, b, [], [], [], [], x); % returns immediately if x is a solution
 if flag == 0
     return
 end
@@ -255,38 +194,38 @@ end
 disp( relres );
 
 if verbose
-    fprintf( 'Initial bicgstab failed, trying alternative start point.\n' );
+    fprintf('Initial bicgstab failed, trying alternative start point.\n');
 end
 old_x = x;
 old_relres = relres;
-[ x, flag, relres ] = bicgstab( A, b );
+[ x, flag, relres ] = bicgstab(A, b);
 if flag == 0
     return
 end
 
 if verbose
-    fprintf( 'Alternative start point also failed with bicgstab, trying gmres.\n' );
+    fprintf('Alternative start point also failed with bicgstab, trying gmres.\n');
 end
 if old_relres < relres
     x = old_x;
 end
-[ x, flag, relres ] = gmres( A, b, [], [], [], [], [], x );
+[ x, flag, relres ] = gmres(A, b, [], [], [], [], [], x);
 if flag == 0
     return
 end
 
 if verbose
-    fprintf( 'Initial gmres failed, trying alternative start point.\n' );
+    fprintf('Initial gmres failed, trying alternative start point.\n');
 end
 old_x = x;
 old_relres = relres;
-[ x, flag, relres ] = gmres( A, b );
+[ x, flag, relres ] = gmres(A, b);
 if flag == 0
     return
 end
 
 if verbose
-    fprintf( 'Alternative start point also failed with gmres, using the (SLOW) Moore-Penrose Pseudo-Inverse.\n' );
+    fprintf('Alternative start point also failed with gmres, using the (SLOW) Moore-Penrose Pseudo-Inverse.\n');
 end
 if old_relres < relres
     x = old_x;
@@ -294,15 +233,15 @@ if old_relres < relres
 end
 old_x = x;
 old_relres = relres;
-x = pinv( full( A ) ) * b;
-relres = norm( b - A * x ) / norm( b );
+x = pinv(full(A)) * b;
+relres = norm(b - A*x) / norm(b);
 if old_relres < relres
     x = old_x;
     relres = old_relres;
 end
 flag = relres > 1e-6;
 if flag ~= 0 && verbose
-    fprintf( 'WARNING : Failed to find a solution to the linear system\n' );
+    fprintf('WARNING : Failed to find a solution to the linear system\n');
 end
 
 function display_critical_variables(dyy, M)
@@ -321,10 +260,3 @@ if any(isinf(dyy))
     fprintf('%s, ', endo_names{:}),
     fprintf('\n'),
 end
-if any(~isreal(dyy))
-    indx = find(any(~isreal(dyy)));
-    endo_names = M.endo_names(indx);
-    disp('Last iteration provided complex number for the following variables:')
-    fprintf('%s, ', endo_names{:}),
-    fprintf('\n'),
-end
diff --git a/matlab/perfect-foresight-models/solve_stacked_problem.m b/matlab/perfect-foresight-models/solve_stacked_problem.m
index 6290d41765d93bfa34b04655f36a3997a769ad30..797b23f9dadb35a2d9d29df9a28ac1bd09b79450 100644
--- a/matlab/perfect-foresight-models/solve_stacked_problem.m
+++ b/matlab/perfect-foresight-models/solve_stacked_problem.m
@@ -53,11 +53,10 @@ if (options.solve_algo == 10 || options.solve_algo == 11)% mixed complementarity
                               i_cols_J1, i_cols_1, i_cols_T, i_cols_j, i_cols_0, i_cols_J0, ...
                               eq_index);
 else
-    [y, check] = dynare_solve(@perfect_foresight_problem,z(:),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);
+    model_dynamic_g1_nz = str2func([M.fname,'.dynamic_g1_nz']);
+    [nzij_pred, nzij_current, nzij_fwrd] = model_dynamic_g1_nz();
+
+    [y, check] = dynare_solve(@perfect_foresight_problem,z(:), options, M.fname, sum(M.dynamic_tmp_nbr(1:2)), y0, yT, exogenousvariables, M.params, steadystate, options.periods, M.endo_nbr, M.maximum_lag, M.maximum_endo_lag, M.lead_lag_incidence, nzij_pred, nzij_current, nzij_fwrd, M.has_external_function, options.use_dll);
 end
 
 if all(imag(y)<.1*options.dynatol.x)
diff --git a/mex/build/matlab/Makefile.am b/mex/build/matlab/Makefile.am
index 0f722508266b7d86cb0ff9bb037202b6aa9bfb65..8d555f1adcd7edb408258c8670464bf720635681 100644
--- a/mex/build/matlab/Makefile.am
+++ b/mex/build/matlab/Makefile.am
@@ -1,7 +1,7 @@
 ACLOCAL_AMFLAGS = -I ../../../m4
 
 if DO_SOMETHING
-SUBDIRS = mjdgges kronecker bytecode block_kalman_filter sobol local_state_space_iterations
+SUBDIRS = mjdgges kronecker bytecode block_kalman_filter sobol local_state_space_iterations perfect_foresight_problem
 
 # libdynare++ must come before gensylv, k_order_perturbation, dynare_simul_
 if HAVE_MATIO
diff --git a/mex/build/matlab/configure.ac b/mex/build/matlab/configure.ac
index eb8f8b7b0332a3f268f15c618d6a090d2099cf3d..906aeee2e7a0ddab38ec70cd2f6436a43f761546 100644
--- a/mex/build/matlab/configure.ac
+++ b/mex/build/matlab/configure.ac
@@ -172,6 +172,7 @@ AC_CONFIG_FILES([Makefile
                  ms_sbvar/Makefile
                  block_kalman_filter/Makefile
 	         sobol/Makefile
-		 local_state_space_iterations/Makefile])
+		 local_state_space_iterations/Makefile
+                 perfect_foresight_problem/Makefile])
 
 AC_OUTPUT
diff --git a/mex/build/matlab/perfect_foresight_problem/Makefile.am b/mex/build/matlab/perfect_foresight_problem/Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..d2ad7ff72c2f88a0e4c0a12cafcb7371769cc07e
--- /dev/null
+++ b/mex/build/matlab/perfect_foresight_problem/Makefile.am
@@ -0,0 +1,2 @@
+include ../mex.am
+include ../../perfect_foresight_problem.am
diff --git a/mex/build/octave/Makefile.am b/mex/build/octave/Makefile.am
index 704a86bf0850ce10c54d4b04f739a754f2be5a12..4703f18c61225408b483b4fa76cbe4910dae4028 100644
--- a/mex/build/octave/Makefile.am
+++ b/mex/build/octave/Makefile.am
@@ -1,7 +1,7 @@
 ACLOCAL_AMFLAGS = -I ../../../m4
 
 if DO_SOMETHING
-SUBDIRS = mjdgges kronecker bytecode block_kalman_filter sobol local_state_space_iterations
+SUBDIRS = mjdgges kronecker bytecode block_kalman_filter sobol local_state_space_iterations perfect_foresight_problem
 
 # libdynare++ must come before gensylv, k_order_perturbation, dynare_simul_
 if HAVE_MATIO
diff --git a/mex/build/octave/configure.ac b/mex/build/octave/configure.ac
index 478b9b22f739337250d347cb288b6bff1e6b11ba..7bb01f7047edbbb676450a54eee64dd7de1cacf6 100644
--- a/mex/build/octave/configure.ac
+++ b/mex/build/octave/configure.ac
@@ -1,6 +1,6 @@
 dnl Process this file with autoconf to produce a configure script.
 
-dnl Copyright © 2009-2017 Dynare Team
+dnl Copyright © 2009-2019 Dynare Team
 dnl
 dnl This file is part of Dynare.
 dnl
@@ -135,6 +135,7 @@ AC_CONFIG_FILES([Makefile
                  ms_sbvar/Makefile
                  block_kalman_filter/Makefile
 		 sobol/Makefile
-		 local_state_space_iterations/Makefile])
+		 local_state_space_iterations/Makefile
+                 perfect_foresight_problem/Makefile])
 
 AC_OUTPUT
diff --git a/mex/build/octave/perfect_foresight_problem/Makefile.am b/mex/build/octave/perfect_foresight_problem/Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..65ceb979fa5bde26c66cefb3a0c29d8988100b15
--- /dev/null
+++ b/mex/build/octave/perfect_foresight_problem/Makefile.am
@@ -0,0 +1,3 @@
+EXEEXT = .mex
+include ../mex.am
+include ../../perfect_foresight_problem.am
diff --git a/mex/build/perfect_foresight_problem.am b/mex/build/perfect_foresight_problem.am
new file mode 100644
index 0000000000000000000000000000000000000000..e295c7e25257115040eca56a1c8541512a57d522
--- /dev/null
+++ b/mex/build/perfect_foresight_problem.am
@@ -0,0 +1,15 @@
+mex_PROGRAMS = perfect_foresight_problem
+
+TOPDIR = $(top_srcdir)/../../sources/perfect_foresight_problem
+
+nodist_perfect_foresight_problem_SOURCES = perfect_foresight_problem.cc DynamicModelCaller.cc
+
+perfect_foresight_problem_CPPFLAGS = $(AM_CPPFLAGS) -I$(TOPDIR)
+perfect_foresight_problem_CXXFLAGS = $(AM_CXXFLAGS) -fopenmp
+perfect_foresight_problem_LDADD = $(LIBADD_DLOPEN)
+
+BUILT_SOURCES = $(nodist_perfect_foresight_problem_SOURCES)
+CLEANFILES = $(nodist_perfect_foresight_problem_SOURCES)
+
+%.cc: $(TOPDIR)/%.cc
+	$(LN_S) -f $< $@
diff --git a/mex/sources/Makefile.am b/mex/sources/Makefile.am
index 654100d3ab78f1c431c2bd4e53dbe04627610ad4..6b469e1cfd4c208fddc143b1d52d47e9491f8853 100644
--- a/mex/sources/Makefile.am
+++ b/mex/sources/Makefile.am
@@ -13,7 +13,8 @@ EXTRA_DIST = \
 	sobol \
 	local_state_space_iterations \
 	gensylv \
-	dynare_simul_
+	dynare_simul_ \
+	perfect_foresight_problem
 
 clean-local:
 	rm -rf `find mex/sources -name *.o`
diff --git a/mex/sources/perfect_foresight_problem/DynamicModelCaller.cc b/mex/sources/perfect_foresight_problem/DynamicModelCaller.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2a59e0937e06f75e3ded44126996960b87d49c32
--- /dev/null
+++ b/mex/sources/perfect_foresight_problem/DynamicModelCaller.cc
@@ -0,0 +1,215 @@
+/*
+ * Copyright © 2019 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 <http://www.gnu.org/licenses/>.
+ */
+
+#if defined(_WIN32) || defined(__CYGWIN32__)
+# ifndef NOMINMAX
+#  define NOMINMAX // Do not define "min" and "max" macros
+# endif
+# include <windows.h>
+#else
+# include <dlfcn.h> // unix/linux DLL (.so) handling routines
+#endif
+
+#include <dynmex.h>
+
+#include "DynamicModelCaller.hh"
+
+#include <algorithm>
+
+std::string DynamicModelCaller::error_msg;
+
+void *DynamicModelDllCaller::dynamic_mex{nullptr};
+DynamicModelDllCaller::dynamic_tt_fct DynamicModelDllCaller::residual_tt_fct{nullptr}, DynamicModelDllCaller::g1_tt_fct{nullptr};
+DynamicModelDllCaller::dynamic_deriv_fct DynamicModelDllCaller::residual_fct{nullptr}, DynamicModelDllCaller::g1_fct{nullptr};
+
+void
+DynamicModelDllCaller::load_dll(const std::string &basename)
+{
+  // Load symbols from dynamic MEX
+  std::string mex_name;
+#if !defined(__CYGWIN32__) && !defined(_WIN32)
+  mex_name = "./";
+#endif
+  mex_name += "+" + basename + "/dynamic" + MEXEXT;
+#if !defined(__CYGWIN32__) && !defined(_WIN32)
+  dynamic_mex = dlopen(mex_name.c_str(), RTLD_NOW);
+#else
+  dynamic_mex = LoadLibrary(mex_name.c_str());
+#endif
+  if (!dynamic_mex)
+    mexErrMsgTxt("Can't load dynamic MEX file");
+
+#if !defined(__CYGWIN32__) && !defined(_WIN32)
+  residual_tt_fct = reinterpret_cast<dynamic_tt_fct>(dlsym(dynamic_mex, "dynamic_resid_tt"));
+  residual_fct = reinterpret_cast<dynamic_deriv_fct>(dlsym(dynamic_mex, "dynamic_resid"));
+  g1_tt_fct = reinterpret_cast<dynamic_tt_fct>(dlsym(dynamic_mex, "dynamic_g1_tt"));
+  g1_fct = reinterpret_cast<dynamic_deriv_fct>(dlsym(dynamic_mex, "dynamic_g1"));
+#else
+  residual_tt_fct = reinterpret_cast<dynamic_tt_fct>(GetProcAddress(dynamic_mex, "dynamic_resid_tt"));
+  residual_fct = reinterpret_cast<dynamic_deriv_fct>(GetProcAddress(dynamic_mex, "dynamic_resid"));
+  g1_tt_fct = reinterpret_cast<dynamic_tt_fct>(GetProcAddress(dynamic_mex, "dynamic_g1_tt"));
+  g1_fct = reinterpret_cast<dynamic_deriv_fct>(GetProcAddress(dynamic_mex, "dynamic_g1"));
+#endif
+  if (!residual_tt_fct || !residual_fct || !g1_tt_fct || !g1_fct)
+    mexErrMsgTxt("Can't load functions in dynamic MEX file");
+}
+
+void
+DynamicModelDllCaller::unload_dll()
+{
+#if !defined(__CYGWIN32__) && !defined(_WIN32)
+  dlclose(dynamic_mex);
+#else
+  FreeLibrary(dynamic_mex);
+#endif
+}
+
+DynamicModelDllCaller::DynamicModelDllCaller(size_t ntt, mwIndex nx, mwIndex ny, size_t ndynvars, const double *x_arg, size_t nb_row_x_arg, const double *params_arg, const double *steady_state_arg, bool compute_jacobian_arg) :
+  DynamicModelCaller{compute_jacobian_arg},
+  nb_row_x{nb_row_x_arg}, x{x_arg}, params{params_arg}, steady_state{steady_state_arg}
+{
+  tt = std::make_unique<double[]>(ntt);
+  y_p = std::make_unique<double[]>(ndynvars);
+  if (compute_jacobian)
+    jacobian_p = std::make_unique<double[]>((ndynvars+nx)*ny);
+}
+
+void
+DynamicModelDllCaller::eval(int it, double *resid)
+{
+  residual_tt_fct(y_p.get(), x, nb_row_x, params, steady_state, it, tt.get());
+  residual_fct(y_p.get(), x, nb_row_x, params, steady_state, it, tt.get(), resid);
+  if (compute_jacobian)
+    {
+      g1_tt_fct(y_p.get(), x, nb_row_x, params, steady_state, it, tt.get());
+      g1_fct(y_p.get(), x, nb_row_x, params, steady_state, it, tt.get(), jacobian_p.get());
+    }
+}
+
+DynamicModelMatlabCaller::DynamicModelMatlabCaller(std::string basename_arg, size_t ntt, size_t ndynvars, const mxArray *x_mx_arg, const mxArray *params_mx_arg, const mxArray *steady_state_mx_arg, bool compute_jacobian_arg) :
+  DynamicModelCaller{compute_jacobian_arg},
+  basename{std::move(basename_arg)},
+  T_mx{mxCreateDoubleMatrix(ntt, 1, mxREAL)},
+  y_mx{mxCreateDoubleMatrix(ndynvars, 1, mxREAL)},
+  it_mx{mxCreateDoubleScalar(0)},
+  T_flag_mx{mxCreateLogicalScalar(false)},
+  jacobian_mx{nullptr},
+  x_mx{mxDuplicateArray(x_mx_arg)},
+  params_mx{mxDuplicateArray(params_mx_arg)},
+  steady_state_mx{mxDuplicateArray(steady_state_mx_arg)}
+{
+}
+
+DynamicModelMatlabCaller::~DynamicModelMatlabCaller()
+{
+  mxDestroyArray(T_mx);
+  mxDestroyArray(y_mx);
+  mxDestroyArray(it_mx);
+  mxDestroyArray(T_flag_mx);
+  if (jacobian_mx)
+    mxDestroyArray(jacobian_mx);
+  mxDestroyArray(x_mx);
+  mxDestroyArray(params_mx);
+  mxDestroyArray(steady_state_mx);
+}
+
+void
+DynamicModelMatlabCaller::eval(int it, double *resid)
+{
+  *mxGetPr(it_mx) = it + 1;
+
+  {
+    // Compute temporary terms
+    std::string funcname = basename + (compute_jacobian ? ".dynamic_g1_tt" : ".dynamic_resid_tt");
+    mxArray *plhs[1], *prhs[] = { T_mx, y_mx, x_mx, params_mx, steady_state_mx, it_mx };
+
+    mxArray *exception = mexCallMATLABWithTrap(1, plhs, 6, prhs, funcname.c_str());
+    if (exception)
+      error_msg = std::string{"An error occurred when calling "} + funcname;
+
+    mxDestroyArray(T_mx);
+    T_mx = plhs[0];
+  }
+
+  {
+    // Compute residuals
+    std::string funcname = basename + ".dynamic_resid";
+    mxArray *plhs[1], *prhs[] = { T_mx, y_mx, x_mx, params_mx, steady_state_mx, it_mx, T_flag_mx };
+
+    mxArray *exception = mexCallMATLABWithTrap(1, plhs, 7, prhs, funcname.c_str());
+    if (exception)
+      error_msg = std::string{"An error occurred when calling "} + funcname;
+
+    if (mxIsComplex(plhs[0]))
+      plhs[0] = cmplxToReal(plhs[0]);
+
+    std::copy_n(mxGetPr(plhs[0]), mxGetNumberOfElements(plhs[0]), resid);
+    mxDestroyArray(plhs[0]);
+  }
+
+  if (compute_jacobian)
+    {
+      // Compute Jacobian
+      std::string funcname = basename + ".dynamic_g1";
+      mxArray *plhs[1], *prhs[] = { T_mx, y_mx, x_mx, params_mx, steady_state_mx, it_mx, T_flag_mx };
+
+      mxArray *exception = mexCallMATLABWithTrap(1, plhs, 7, prhs, funcname.c_str());
+      if (exception)
+        error_msg = std::string{"An error occurred when calling "} + funcname;
+
+      if (jacobian_mx)
+        {
+          mxDestroyArray(jacobian_mx);
+          jacobian_mx = nullptr;
+        }
+
+      if (mxIsComplex(plhs[0]))
+        jacobian_mx = cmplxToReal(plhs[0]);
+      else
+        jacobian_mx = plhs[0];
+    }
+}
+
+mxArray *
+DynamicModelMatlabCaller::cmplxToReal(mxArray *cmplx_mx)
+{
+  mxArray *real_mx = mxCreateDoubleMatrix(mxGetM(cmplx_mx), mxGetN(cmplx_mx), mxREAL);
+
+#if MX_HAS_INTERLEAVED_COMPLEX
+  mxComplexDouble *cmplx = mxGetComplexDoubles(cmplx_mx);
+#else
+  double *cmplx_real = mxGetPr(cmplx_mx);
+  double *cmplx_imag = mxGetPi(cmplx_mx);
+#endif
+  double *real = mxGetPr(real_mx);
+
+  for (size_t i = 0; i < mxGetNumberOfElements(cmplx_mx); i++)
+#if MX_HAS_INTERLEAVED_COMPLEX
+    if (cmplx[i].imag == 0.0)
+      real[i] = cmplx[i].real;
+#else
+    if (cmplx_imag[i] == 0.0)
+      real[i] = cmplx_real[i];
+#endif
+    else
+      real[i] = std::numeric_limits<double>::quiet_NaN();
+
+  mxDestroyArray(cmplx_mx);
+  return real_mx;
+}
diff --git a/mex/sources/perfect_foresight_problem/DynamicModelCaller.hh b/mex/sources/perfect_foresight_problem/DynamicModelCaller.hh
new file mode 100644
index 0000000000000000000000000000000000000000..8c64938fdb6faf8a420cb62dfa9b5d5981a237c0
--- /dev/null
+++ b/mex/sources/perfect_foresight_problem/DynamicModelCaller.hh
@@ -0,0 +1,83 @@
+/*
+ * Copyright © 2019 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <string>
+#include <memory>
+#include <limits>
+
+class DynamicModelCaller
+{
+public:
+  const bool compute_jacobian;
+
+  // Used to store error messages (as exceptions cannot cross the OpenMP boundary)
+  static std::string error_msg;
+
+  DynamicModelCaller(bool compute_jacobian_arg) : compute_jacobian{compute_jacobian_arg} {};
+  virtual ~DynamicModelCaller() = default;
+  virtual double &y(size_t i) const = 0;
+  virtual double jacobian(size_t i) const = 0;
+  virtual void eval(int it, double *resid) = 0;
+};
+
+class DynamicModelDllCaller : public DynamicModelCaller
+{
+private:
+  static void *dynamic_mex;
+  using dynamic_tt_fct = void (*)(const double *y, const double *x, int nb_row_x, const double *params, const double *steady_state, int it_, double *T);
+  using dynamic_deriv_fct = void (*) (const double *y, const double *x, int nb_row_x, const double *params, const double *steady_state, int it_, const double *T, double *deriv);
+  static dynamic_tt_fct residual_tt_fct, g1_tt_fct;
+  static dynamic_deriv_fct residual_fct, g1_fct;
+  size_t nb_row_x;
+  const double *x, *params, *steady_state;
+  std::unique_ptr<double[]> tt, y_p, jacobian_p;
+
+public:
+  DynamicModelDllCaller(size_t ntt, mwIndex nx, mwIndex ny, size_t ndynvars, const double *x_arg, size_t nb_row_x_arg, const double *params_arg, const double *steady_state_arg, bool compute_jacobian_arg);
+  virtual ~DynamicModelDllCaller() = default;
+  double &y(size_t i) const override { return y_p[i]; };
+  double jacobian(size_t i) const override { return jacobian_p[i]; };
+  void eval(int it, double *resid) override;
+  static void load_dll(const std::string &basename);
+  static void unload_dll();
+};
+
+class DynamicModelMatlabCaller : public DynamicModelCaller
+{
+private:
+  std::string basename;
+  mxArray *T_mx, *y_mx, *it_mx, *T_flag_mx, *jacobian_mx, *x_mx, *params_mx, *steady_state_mx;
+  /* Given a complex matrix, returns a real matrix of same size.
+     Real elements of the original matrix are copied as-is to the new one.
+     Complex elements are replaced by NaNs.
+     Destroys the original matrix. */
+  static mxArray *cmplxToReal(mxArray *m);
+public:
+  DynamicModelMatlabCaller(std::string basename_arg, size_t ntt, size_t ndynvars, const mxArray *x_mx_arg, const mxArray *params_mx_arg, const mxArray *steady_state_mx_arg, bool compute_jacobian_arg);
+  ~DynamicModelMatlabCaller() override;
+  double &y(size_t i) const override { return mxGetPr(y_mx)[i]; };
+  double jacobian(size_t i) const override { return jacobian_mx ? mxGetPr(jacobian_mx)[i] : std::numeric_limits<double>::quiet_NaN(); };
+  void eval(int it, double *resid) override;
+  class Exception {
+  public:
+    const std::string msg;
+    Exception(std::string msg_arg) : msg{std::move(msg_arg)} {};
+  };
+};
+
diff --git a/mex/sources/perfect_foresight_problem/perfect_foresight_problem.cc b/mex/sources/perfect_foresight_problem/perfect_foresight_problem.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f743c92b023fe6b90883e1d7c79fb7bd442d9cf7
--- /dev/null
+++ b/mex/sources/perfect_foresight_problem/perfect_foresight_problem.cc
@@ -0,0 +1,277 @@
+/*
+ * Copyright © 2019 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <string>
+#include <memory>
+#include <algorithm>
+
+#include <dynmex.h>
+
+#include "DynamicModelCaller.hh"
+
+void
+mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
+{
+  if (nlhs < 1 || nlhs > 2 || nrhs != 18)
+    mexErrMsgTxt("Must have 18 input arguments and 1 or 2 output arguments");
+  bool compute_jacobian = nlhs == 2;
+
+  // Give explicit names to input arguments
+  const mxArray *y_mx = prhs[0];
+  const mxArray *basename_mx = prhs[1];
+  const mxArray *ntt_mx = prhs[2];
+  const mxArray *y0_mx = prhs[3];
+  const mxArray *yT_mx = prhs[4];
+  const mxArray *exo_path_mx = prhs[5];
+  const mxArray *params_mx = prhs[6];
+  const mxArray *steady_state_mx = prhs[7];
+  const mxArray *periods_mx = prhs[8];
+  const mxArray *ny_mx = prhs[9];
+  const mxArray *maximum_lag_mx = prhs[10];
+  const mxArray *maximum_endo_lag_mx = prhs[11];
+  const mxArray *lead_lag_incidence_mx = prhs[12];
+  const mxArray *nzij_pred_mx = prhs[13];
+  const mxArray *nzij_current_mx = prhs[14];
+  const mxArray *nzij_fwrd_mx = prhs[15];
+  const mxArray *has_external_function_mx = prhs[16];
+  const mxArray *use_dll_mx = prhs[17];
+
+  // Check input and map it to local variables
+  if (!(mxIsChar(basename_mx) && mxGetM(basename_mx) == 1))
+    mexErrMsgTxt("basename should be a character string");
+  char *basename = mxArrayToString(basename_mx);
+
+  if (!(mxIsScalar(ntt_mx) && mxIsNumeric(ntt_mx)))
+    mexErrMsgTxt("ntt should be a numeric scalar");
+  size_t ntt = mxGetScalar(ntt_mx);
+
+  if (!(mxIsScalar(periods_mx) && mxIsNumeric(periods_mx)))
+    mexErrMsgTxt("periods should be a numeric scalar");
+  mwIndex periods = static_cast<mwIndex>(mxGetScalar(periods_mx));
+
+  if (!(mxIsScalar(ny_mx) && mxIsNumeric(ny_mx)))
+    mexErrMsgTxt("ny should be a numeric scalar");
+  mwIndex ny = static_cast<mwIndex>(mxGetScalar(ny_mx));
+
+  if (!(mxIsScalar(maximum_lag_mx) && mxIsNumeric(maximum_lag_mx)))
+    mexErrMsgTxt("maximum_lag should be a numeric scalar");
+  mwIndex maximum_lag = static_cast<mwIndex>(mxGetScalar(maximum_lag_mx));
+
+  if (!(mxIsScalar(maximum_endo_lag_mx) && mxIsNumeric(maximum_endo_lag_mx)))
+    mexErrMsgTxt("maximum_endo_lag should be a numeric scalar");
+  mwIndex maximum_endo_lag = static_cast<mwIndex>(mxGetScalar(maximum_endo_lag_mx));
+
+  if (!(mxIsDouble(y_mx) && mxGetM(y_mx) == static_cast<size_t>(ny*periods) && mxGetN(y_mx) == 1))
+    mexErrMsgTxt("y should be a double precision column-vector of ny*periods elements");
+  const double *y = mxGetPr(y_mx);
+
+  if (!(mxIsDouble(y0_mx) && mxGetM(y0_mx) == static_cast<size_t>(ny) && mxGetN(y0_mx) == 1))
+    mexErrMsgTxt("y0 should be a double precision column-vector of ny elements");
+  const double *y0 = mxGetPr(y0_mx);
+
+  if (!(mxIsDouble(yT_mx) && mxGetM(yT_mx) == static_cast<size_t>(ny) && mxGetN(yT_mx) == 1))
+    mexErrMsgTxt("yT should be a double precision column-vector of ny elements");
+  const double *yT = mxGetPr(yT_mx);
+
+  if (!(mxIsDouble(exo_path_mx) && mxGetM(exo_path_mx) >= static_cast<size_t>(periods+maximum_lag)))
+    mexErrMsgTxt("exo_path should be a double precision matrix with at least periods+maximum_lag rows");
+  mwIndex nx = static_cast<mwIndex>(mxGetN(exo_path_mx));
+  size_t nb_row_x = mxGetM(exo_path_mx);
+  const double *exo_path = mxGetPr(exo_path_mx);
+
+  if (!(mxIsDouble(params_mx) && mxGetN(params_mx) == 1))
+    mexErrMsgTxt("params should be a double precision column-vector");
+  const double *params = mxGetPr(params_mx);
+
+  if (!(mxIsDouble(steady_state_mx) && mxGetN(steady_state_mx) == 1))
+    mexErrMsgTxt("steady_state should be a double precision column-vector");
+  const double *steady_state = mxGetPr(steady_state_mx);
+
+  if (!(mxIsDouble(lead_lag_incidence_mx) && mxGetM(lead_lag_incidence_mx) == static_cast<size_t>(2+maximum_endo_lag)
+        && mxGetN(lead_lag_incidence_mx) == static_cast<size_t>(ny)))
+    mexErrMsgTxt("lead_lag_incidence should be a double precision matrix with 2+maximum_endo_lag rows and endo_nbr columns");
+  const double *lead_lag_incidence = mxGetPr(lead_lag_incidence_mx);
+
+  if (!(mxIsInt32(nzij_pred_mx) && mxGetN(nzij_pred_mx) == 2))
+    mexErrMsgTxt("nzij_pred should be an int32 matrix with 2 columns");
+  size_t nnz_pred = mxGetM(nzij_pred_mx);
+  const int32_T *nzij_pred = static_cast<const int32_T *>(mxGetData(nzij_pred_mx));
+
+  if (!(mxIsInt32(nzij_current_mx) && mxGetN(nzij_current_mx) == 2))
+    mexErrMsgTxt("nzij_current should be an int32 matrix with 2 columns");
+  size_t nnz_current = mxGetM(nzij_current_mx);
+  const int32_T *nzij_current = static_cast<const int32_T *>(mxGetData(nzij_current_mx));
+
+  if (!(mxIsInt32(nzij_fwrd_mx) && mxGetN(nzij_fwrd_mx) == 2))
+    mexErrMsgTxt("nzij_fwrd should be an int32 matrix with 2 columns");
+  size_t nnz_fwrd = mxGetM(nzij_fwrd_mx);
+  const int32_T *nzij_fwrd = static_cast<const int32_T *>(mxGetData(nzij_fwrd_mx));
+
+  if (!(mxIsLogicalScalar(has_external_function_mx)))
+    mexErrMsgTxt("has_external_function should be a logical scalar");
+  bool has_external_function = static_cast<bool>(mxGetScalar(has_external_function_mx));
+
+  if (!(mxIsLogicalScalar(use_dll_mx)))
+    mexErrMsgTxt("use_dll should be a logical scalar");
+  bool use_dll = static_cast<bool>(mxGetScalar(use_dll_mx));
+
+  // Allocate output matrices
+  plhs[0] = mxCreateDoubleMatrix(periods*ny, 1, mxREAL);
+  double *stacked_residual = mxGetPr(plhs[0]);
+
+  mwIndex nzmax = periods*nnz_current+(periods-1)*(nnz_pred+nnz_fwrd);
+
+  double *stacked_jacobian = nullptr;
+  mwIndex *ir = nullptr, *jc = nullptr;
+  if (compute_jacobian)
+    {
+      plhs[1] = mxCreateSparse(periods*ny, periods*ny, nzmax, mxREAL);
+      stacked_jacobian = mxGetPr(plhs[1]);
+      ir = mxGetIr(plhs[1]);
+      jc = mxGetJc(plhs[1]);
+
+      /* Create the index vectors (IR, JC) of the sparse stacked jacobian. This
+         makes parallelization across periods possible when evaluating the model,
+         since all indices are known ex ante. */
+      mwIndex k = 0;
+      jc[0] = 0;
+      for (mwIndex T = 0; T < periods; T++)
+        {
+          size_t row_pred = 0, row_current = 0, row_fwrd = 0;
+          for (int32_T j = 0; j < static_cast<int32_T>(ny); j++)
+            {
+              if (T != 0)
+                while (row_fwrd < nnz_fwrd && nzij_fwrd[row_fwrd+nnz_fwrd]-1 == j)
+                  ir[k++] = (T-1)*ny + nzij_fwrd[row_fwrd++]-1;
+              while (row_current < nnz_current && nzij_current[row_current+nnz_current]-1 == j)
+                ir[k++] = T*ny + nzij_current[row_current++]-1;
+              if (T != periods-1)
+                while (row_pred < nnz_pred && nzij_pred[row_pred+nnz_pred]-1 == j)
+                  ir[k++] = (T+1)*ny + nzij_pred[row_pred++]-1;
+              jc[T*ny+j+1] = k;
+            }
+        }
+    }
+
+  size_t ndynvars = static_cast<size_t>(*std::max_element(lead_lag_incidence, lead_lag_incidence+(maximum_endo_lag+2)*ny));
+
+  if (use_dll)
+    DynamicModelDllCaller::load_dll(basename);
+
+  DynamicModelCaller::error_msg.clear();
+
+  /* Parallelize the main loop, if use_dll and no external function (to avoid
+     parallel calls to MATLAB) */
+#pragma omp parallel if (use_dll && !has_external_function)
+  {
+    // Allocate (thread-private) model evaluator (which allocates space for temporaries)
+    std::unique_ptr<DynamicModelCaller> m;
+    if (use_dll)
+      m = std::make_unique<DynamicModelDllCaller>(ntt, nx, ny, ndynvars, exo_path, nb_row_x, params, steady_state, compute_jacobian);
+    else
+      m = std::make_unique<DynamicModelMatlabCaller>(basename, ntt, ndynvars, exo_path_mx, params_mx, steady_state_mx, compute_jacobian);
+
+    // Main computing loop
+#pragma omp for
+    for (mwIndex T = 0; T < periods; T++)
+      {
+        // Fill vector of dynamic variables
+        for (mwIndex j = 0; j < maximum_endo_lag+2; j++)
+          for (mwIndex i = 0; i < ny; i++)
+            {
+              int idx = static_cast<int>(lead_lag_incidence[j+i*(2+maximum_endo_lag)])-1;
+              if (idx != -1)
+                {
+                  if (T+j == maximum_endo_lag-1)
+                    m->y(idx) = y0[i];
+                  else if (T+j == maximum_endo_lag+periods)
+                    m->y(idx) = yT[i];
+                  else
+                    m->y(idx) = y[i+(T+j-maximum_endo_lag)*ny];
+                }
+            }
+
+        // Compute the residual and Jacobian, and fill the stacked residual
+        m->eval(T+maximum_lag, stacked_residual+T*ny);
+
+        if (compute_jacobian)
+          {
+            // Fill the stacked jacobian
+            for (mwIndex col = T > maximum_endo_lag ? (T-maximum_endo_lag)*ny : 0; // We can't use std::max() here, because mwIndex is unsigned under MATLAB
+                 col < std::min(periods*ny, (T+2)*ny); col++)
+              {
+                mwIndex k = jc[col];
+                while (k < jc[col+1])
+                  {
+                    if (ir[k] < T*ny)
+                      {
+                        k++;
+                        continue;
+                      }
+                    if (ir[k] >= (T+1)*ny)
+                      break;
+
+                    mwIndex eq = ir[k]-T*ny;
+                    mwIndex lli_row = col/ny-(T-maximum_endo_lag); // 0, 1 or 2
+                    mwIndex lli_col = col%ny;
+                    mwIndex dynvar = static_cast<mwIndex>(lead_lag_incidence[lli_row+lli_col*(2+maximum_endo_lag)])-1;
+                    stacked_jacobian[k] = m->jacobian(eq+dynvar*ny);
+                    k++;
+                  }
+              }
+          }
+      }
+  }
+
+  /* Mimic a try/catch using a global string, since exceptions are not allowed
+     to cross OpenMP boundary */
+  if (!DynamicModelCaller::error_msg.empty())
+    mexErrMsgTxt(DynamicModelCaller::error_msg.c_str());
+
+  if (compute_jacobian)
+    {
+      /* The stacked jacobian so far constructed can still reference some zero
+         elements, because some expressions that are symbolically different from
+         zero may still evaluate to zero for some endogenous/parameter values. The
+         following code further compresses the Jacobian by removing the references
+         to those extra zeros. This makes a significant speed difference when
+         inversing the Jacobian for some large models. */
+      mwIndex k_orig = 0, k_new = 0;
+      for (mwIndex col = 0; col < periods*ny; col++)
+        {
+          while (k_orig < jc[col+1])
+            {
+              if (stacked_jacobian[k_orig] != 0.0)
+                {
+                  if (k_new != k_orig)
+                    {
+                      stacked_jacobian[k_new] = stacked_jacobian[k_orig];
+                      ir[k_new] = ir[k_orig];
+                    }
+                  k_new++;
+                }
+              k_orig++;
+            }
+          jc[col+1] = k_new;
+        }
+    }
+
+  if (use_dll)
+    DynamicModelDllCaller::unload_dll();
+}