From 378f405395440176dd85056d9779c31b0496ea29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Villemot?= <sebastien@dynare.org> Date: Wed, 19 Feb 2025 16:27:51 +0100 Subject: [PATCH] WIP: perfect foresight conditional forecast [skip ci] --- .../perfect_foresight_solver.m | 19 +++++- .../perfect_foresight_solver_core.m | 25 +++++++- ...foresight_with_expectation_errors_solver.m | 6 +- matlab/perfect-foresight-models/sim1.m | 36 +++++++++++- ...ked_jacobian_substitute_controlled_paths.m | 48 +++++++++++++++ scripts/dynare.el | 6 +- .../ramst_pf_controlled_paths.mod | 58 +++++++++++++++++++ 7 files changed, 185 insertions(+), 13 deletions(-) create mode 100644 matlab/perfect-foresight-models/stacked_jacobian_substitute_controlled_paths.m create mode 100644 tests/deterministic_simulations/ramst_pf_controlled_paths.mod diff --git a/matlab/perfect-foresight-models/perfect_foresight_solver.m b/matlab/perfect-foresight-models/perfect_foresight_solver.m index cc4f2d60a..f55b6e3e2 100644 --- a/matlab/perfect-foresight-models/perfect_foresight_solver.m +++ b/matlab/perfect-foresight-models/perfect_foresight_solver.m @@ -23,7 +23,7 @@ function [oo_, ts]=perfect_foresight_solver(M_, options_, oo_, no_error_if_learn % SPECIAL REQUIREMENTS % none -% Copyright © 1996-2024 Dynare Team +% Copyright © 1996-2025 Dynare Team % % This file is part of Dynare. % @@ -158,13 +158,19 @@ if completed_share == 1 if options_.simul.endval_steady oo_.steady_state = steady_state; end - % NB: no need to modify oo_.exo_simul and oo_.exo_steady_state, since we simulated 100% of the shock + % NB: no need to modify oo_.exo_simul and oo_.exo_steady_state, since we simulated 100% of the shock, except if controlled paths + if ~isempty(M_.perfect_foresight_controlled_paths) + oo_.exo_simul = exo_simul; + end if ~options_.noprint fprintf('Perfect foresight solution found.\n\n') end oo_.deterministic_simulation.status = true; elseif options_.simul.homotopy_linearization_fallback && completed_share > 0 + if ~isempty(M_.perfect_foresight_controlled_paths) + error('Option homotopy_linearization_fallback not available with perfect_foresight_controlled_paths') + end oo_.deterministic_simulation.sim1.endo_simul = endo_simul; oo_.deterministic_simulation.sim1.exo_simul = exo_simul; oo_.deterministic_simulation.sim1.steady_state = steady_state; @@ -188,6 +194,9 @@ elseif options_.simul.homotopy_linearization_fallback && completed_share > 0 oo_.deterministic_simulation.status = true; oo_.deterministic_simulation.homotopy_linearization = true; elseif options_.simul.homotopy_marginal_linearization_fallback > 0 && completed_share > options_.simul.homotopy_marginal_linearization_fallback + if ~isempty(M_.perfect_foresight_controlled_paths) + error('Option homotopy_marginal_linearization_fallback not available with perfect_foresight_controlled_paths') + end oo_.deterministic_simulation.sim1.endo_simul = endo_simul; oo_.deterministic_simulation.sim1.exo_simul = exo_simul; oo_.deterministic_simulation.sim1.steady_state = steady_state; @@ -344,7 +353,7 @@ while step > options_.simul.homotopy_min_step_size end % Solve for the paths of the endogenous variables. - [endo_simul, success, maxerror, solver_iter, per_block_status] = perfect_foresight_solver_core(endo_simul, exo_simul, steady_state, exo_steady_state, M_, options_); + [endo_simul, success, maxerror, solver_iter, per_block_status, exo_simul] = perfect_foresight_solver_core(endo_simul, exo_simul, steady_state, exo_steady_state, M_, options_); else success = false; maxerror = NaN; @@ -362,6 +371,10 @@ while step > options_.simul.homotopy_min_step_size break end + if ~isempty(M_.perfect_foresight_controlled_paths) + error('Homotopy not available with perfect_foresight_controlled_paths') + end + if iteration == 1 && ~options_.noprint fprintf('\nEntering the homotopy method iterations...\n') iter_summary_table = { sprintf('\nIter. \t | Share \t | Status \t | Max. residual\t | Duration (sec)\n'), diff --git a/matlab/perfect-foresight-models/perfect_foresight_solver_core.m b/matlab/perfect-foresight-models/perfect_foresight_solver_core.m index 05d20af69..8d38504c3 100644 --- a/matlab/perfect-foresight-models/perfect_foresight_solver_core.m +++ b/matlab/perfect-foresight-models/perfect_foresight_solver_core.m @@ -1,4 +1,4 @@ -function [y, success, maxerror, iter, per_block_status] = perfect_foresight_solver_core(y, exo_simul, steady_state, exo_steady_state, M_, options_) +function [y, success, maxerror, iter, per_block_status, exo_simul] = perfect_foresight_solver_core(y, exo_simul, steady_state, exo_steady_state, M_, options_) % Core function calling solvers for perfect foresight model % @@ -17,7 +17,7 @@ function [y, success, maxerror, iter, per_block_status] = perfect_foresight_solv % - iter [integer] Number of iterations of the underlying nonlinear solver (empty for non-iterative methods) % - per_block_status [struct] In the case of block decomposition, provides per-block solver status information (empty if no block decomposition) -% Copyright © 2015-2024 Dynare Team +% Copyright © 2015-2025 Dynare Team % % This file is part of Dynare. % @@ -55,6 +55,25 @@ if options_.linear && (isequal(options_.stack_solve_algo, 0) || isequal(options_ options_.linear_approximation = true; end +if ~isempty(M_.perfect_foresight_controlled_paths) + assert(nargout >= 6); % Ensure modified exos are used + if options_.bytecode + error('perfect_foresight_controlled_paths is not compatible with bytecode option') + end + if options_.block + error('perfect_foresight_controlled_paths is not compatible with block option') + end + if options_.linear + error('perfect_foresight_controlled_paths is not compatible with linear option') + end + if ~ismember(options_.stack_solve_algo, [0 2 3]) + error('perfect_foresight_controlled_paths is only available with stack_solve_algo equal to 0, 2 or 3') + end + if M_.maximum_endo_lead == 0 || M_.maximum_endo_lag == 0 + error('perfect_foresight_controlled_paths is not compatible with purely backward, purely forward or static models') + end +end + maxerror = []; iter = []; per_block_status = []; @@ -103,7 +122,7 @@ else end [y, success, maxerror] = sim1_linear(y, exo_simul, steady_state, exo_steady_state, M_, options_); else - [y, success, maxerror, iter] = sim1(y, exo_simul, steady_state, M_, options_); + [y, success, maxerror, iter, exo_simul] = sim1(y, exo_simul, steady_state, M_, options_); end case {1 6} if options_.linear_approximation diff --git a/matlab/perfect-foresight-models/perfect_foresight_with_expectation_errors_solver.m b/matlab/perfect-foresight-models/perfect_foresight_with_expectation_errors_solver.m index 22a07c276..c69029c9d 100644 --- a/matlab/perfect-foresight-models/perfect_foresight_with_expectation_errors_solver.m +++ b/matlab/perfect-foresight-models/perfect_foresight_with_expectation_errors_solver.m @@ -7,7 +7,7 @@ function [oo_, ts] = perfect_foresight_with_expectation_errors_solver(M_, option % OUTPUTS % oo_ [structure] storing the results -% Copyright © 2021-2024 Dynare Team +% Copyright © 2021-2025 Dynare Team % % This file is part of Dynare. % @@ -28,6 +28,10 @@ if options_.pfwee.constant_simulation_length && ~isempty(options_.simul.last_sim error('Options constant_simulation_length and last_simulation_period cannot be used together') end +if ~isempty(M_.perfect_foresight_controlled_paths) + error('perfect_foresight_with_expectation_errors_solver not available with perfect_foresight_controlled_paths') +end + [periods, first_simulation_period] = get_simulation_periods(options_); % Retrieve initial paths built by pfwee_setup diff --git a/matlab/perfect-foresight-models/sim1.m b/matlab/perfect-foresight-models/sim1.m index 55f5f6f63..3e5be836e 100644 --- a/matlab/perfect-foresight-models/sim1.m +++ b/matlab/perfect-foresight-models/sim1.m @@ -1,4 +1,4 @@ -function [endogenousvariables, success, err, iter] = sim1(endogenousvariables, exogenousvariables, steadystate, M_, options_) +function [endogenousvariables, success, err, iter, exogenousvariables] = sim1(endogenousvariables, exogenousvariables, steadystate, M_, options_) % [endogenousvariables, success, err, iter] = sim1(endogenousvariables, exogenousvariables, steadystate, M_, options_) % Performs deterministic simulations with lead or lag of one period, using % a basic Newton solver on sparse matrices. @@ -15,8 +15,10 @@ function [endogenousvariables, success, err, iter] = sim1(endogenousvariables, e % - success [logical] Whether a solution was found % - err [double] ∞-norm of the residual % - iter [integer] Number of iterations +% - exogenousvariables [double] T*M array, paths for the exogenous variables +% (may be modified if perfect_foresight_controlled_paths present) -% Copyright © 1996-2024 Dynare Team +% Copyright © 1996-2025 Dynare Team % % This file is part of Dynare. % @@ -39,6 +41,19 @@ ny = M_.endo_nbr; periods = get_simulation_periods(options_); vperiods = periods*ones(1,options_.simul.maxit); +if ~isempty(M_.perfect_foresight_controlled_paths) + for i=1:length(M_.perfect_foresight_controlled_paths) + endogenousvariables(M_.perfect_foresight_controlled_paths(i).exogenize_id, M_.perfect_foresight_controlled_paths(i).periods + M_.maximum_lag) = M_.perfect_foresight_controlled_paths(i).value; + end + + if options_.debug + error('Debugging not available with perfect_foresight_controlled_paths') + end + if options_.endogenous_terminal_period + error('The endogenous_terminal_period option not available with perfect_foresight_controlled_paths') + end +end + if M_.maximum_lag > 0 y0 = endogenousvariables(:, M_.maximum_lag); else @@ -70,6 +85,11 @@ for iter = 1:options_.simul.maxit [res, A] = perfect_foresight_problem(y, y0, yT, exogenousvariables, M_.params, steadystate, periods, M_, options_); % A is the stacked Jacobian with period x equations alongs the rows and % periods times variables (in declaration order) along the columns + + if ~isempty(M_.perfect_foresight_controlled_paths) + A = stacked_jacobian_substitute_controlled_paths(A, y, y0, yT, exogenousvariables, steadystate, periods, M_); + end + if options_.debug && iter==1 [row,col]=find(A); row=setdiff(1:periods*ny,row); @@ -136,6 +156,16 @@ for iter = 1:options_.simul.maxit end end y = y + dy; + + if ~isempty(M_.perfect_foresight_controlled_paths) + for i = 1:length(M_.perfect_foresight_controlled_paths) + p = M_.perfect_foresight_controlled_paths(i).periods; + endogenize_id = M_.perfect_foresight_controlled_paths(i).endogenize_id; + exogenize_id = M_.perfect_foresight_controlled_paths(i).exogenize_id; + y(exogenize_id+(p-1)*M_.endo_nbr) = M_.perfect_foresight_controlled_paths(i).value; + exogenousvariables(p,endogenize_id) = exogenousvariables(p,endogenize_id) + dy(exogenize_id+(p-1)*M_.endo_nbr); + end + end end endogenousvariables(:, M_.maximum_lag+(1:periods)) = reshape(y, ny, periods); @@ -278,7 +308,7 @@ end function display_critical_variables(dyy, M_, noprint) -if noprint +if noprint || ~isempty(M_.perfect_foresight_controlled_paths) return end diff --git a/matlab/perfect-foresight-models/stacked_jacobian_substitute_controlled_paths.m b/matlab/perfect-foresight-models/stacked_jacobian_substitute_controlled_paths.m new file mode 100644 index 000000000..9597c0190 --- /dev/null +++ b/matlab/perfect-foresight-models/stacked_jacobian_substitute_controlled_paths.m @@ -0,0 +1,48 @@ +function A = stacked_jacobian_substitute_controlled_paths(A, y, y0, yT, exogenousvariables, steadystate, periods, M_) + +% Copyright © 2025 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 <https://www.gnu.org/licenses/>. + +% Reorganize M_.perfect_foresight_controlled_paths period by period +flips_by_period = struct('exogenize_id', cell(periods, 1), 'endogenize_id', cell(periods, 1)); +for i=1:length(M_.perfect_foresight_controlled_paths) + for p = M_.perfect_foresight_controlled_paths(i).periods + flips_by_period(p).exogenize_id(end+1) = M_.perfect_foresight_controlled_paths(i).exogenize_id; + flips_by_period(p).endogenize_id(end+1) = M_.perfect_foresight_controlled_paths(i).endogenize_id; + end +end + +dynamic_g1 = str2func([M_.fname '.sparse.dynamic_g1']); + +for p = 1:periods + nsubst = length(flips_by_period(p).exogenize_id); + if nsubst > 0 + if p == 1 + y3n = [ y0; y(1:2*M_.endo_nbr) ]; + elseif p == periods + y3n = [ y(end-2*M_.endo_nbr+1:end); yT ]; + else + y3n = y((p-2)*M_.endo_nbr+(1:3*M_.endo_nbr)); + end + g1 = dynamic_g1(y3n, exogenousvariables(p,:), M_.params, steadystate, M_.dynamic_g1_sparse_rowval, M_.dynamic_g1_sparse_colval, M_.dynamic_g1_sparse_colptr); + + subst = [ zeros((p-1)*M_.endo_nbr, nsubst); + g1(:,3*M_.endo_nbr+flips_by_period(p).endogenize_id); + zeros((periods-p)*M_.endo_nbr, nsubst) ]; + A(:,(p-1)*M_.endo_nbr+flips_by_period(p).exogenize_id) = subst; + end +end diff --git a/scripts/dynare.el b/scripts/dynare.el index e723bf6b7..50d6e1d25 100644 --- a/scripts/dynare.el +++ b/scripts/dynare.el @@ -1,7 +1,7 @@ ;;; dynare.el --- major mode for editing Dynare mod files ;; Copyright © 2010 Yannick Kalantzis -;; Copyright © 2019-2024 Dynare Team +;; Copyright © 2019-2025 Dynare Team ;; ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by @@ -88,7 +88,7 @@ '("stderr" "values" "periods" "scales" "restriction" "exclusion" "upper_cholesky" "lower_cholesky" "equation" "bind" "relax" "error_bind" "error_relax" "add" "multiply" "target" "auxname_target_nonstationary" - "component" "growth" "auxname" "kind" "weights") + "component" "growth" "auxname" "kind" "weights" "exogenize" "endogenize") "Dynare statements-like keywords.") ;; Those keywords that makes the lexer enter the DYNARE_BLOCK start condition @@ -106,7 +106,7 @@ "conditional_forecast_paths" "svar_identification" "moment_calibration" "irf_calibration" "ramsey_constraints" "generate_irfs" "matched_moments" "occbin_constraints" "model_replace" "pac_target_info" "matched_irfs" - "matched_irfs_weights" "verbatim") + "matched_irfs_weights" "perfect_foresight_controlled_paths" "verbatim") "Dynare block keywords.")) ;; Mathematical functions and operators used in model equations (see "hand_side" in Bison file) diff --git a/tests/deterministic_simulations/ramst_pf_controlled_paths.mod b/tests/deterministic_simulations/ramst_pf_controlled_paths.mod new file mode 100644 index 000000000..d39f80ce2 --- /dev/null +++ b/tests/deterministic_simulations/ramst_pf_controlled_paths.mod @@ -0,0 +1,58 @@ +var c k y; +varexo x e_y; + +parameters alph gam delt bet aa; +alph=0.5; +gam=0.5; +delt=0.02; +bet=0.05; +aa=0.5; + + +model; +c + k - aa*x*k(-1)^alph - (1-delt)*k(-1); +c^(-gam) - (1+bet)^(-1)*(aa*alph*x(+1)*k^(alph-1) + 1 - delt)*c(+1)^(-gam); +y = 0.98*y(-1)+e_y; +end; + +initval; +x = 1; +k = ((delt+bet)/(1.0*aa*alph))^(1/(alph-1)); +c = aa*k^alph-delt*k; +y = 0; +end; + +steady; + +check; + +perfect_foresight_controlled_paths; + exogenize c; + periods 2 4; + values 1.6 1.7; + endogenize x; + + exogenize k; + periods 7; + values 13; + endogenize x; + + exogenize y; + periods 3:5; + values 0.5; + endogenize e_y; +end; + +shocks; +var x; +periods 1; +values 1.2; +end; + + + +perfect_foresight_setup(periods=200); +perfect_foresight_solver; + +rplot c; +rplot k; -- GitLab