Perfect foresight homotopy: make marginal linearization more robust

For marginal linearization, an extra simulation is needed. This extra
simulation is first computed using the first simulation as guess value. If that
does not work, the present commits adds an additional attempt for computing
that extra simulation, using a full homotopy loop from the beginning.

As a consequence, the present commits puts the homotopy loop in a dedicated
function.
kalman-mex
Sébastien Villemot 2023-10-13 15:31:44 -04:00
parent 2ae485705e
commit 4875554a39
No known key found for this signature in database
GPG Key ID: 2CECE9350ECEBE4A
1 changed files with 127 additions and 121 deletions

View File

@ -125,13 +125,125 @@ end
exoorig = oo_.exo_simul;
endoorig = oo_.endo_simul;
% Initialize paths for endogenous (and final steady state that may be modified during homotopy)
endo_simul = oo_.endo_simul;
steady_state = oo_.steady_state;
exo_steady_state = oo_.exo_steady_state;
% Perform the homotopy loop
[completed_share, endo_simul, exo_simul, steady_state, exo_steady_state, iteration, maxerror, solver_iter, per_block_status] = homotopy_loop(options_.simul.homotopy_max_completion_share, endoorig, exoorig, endobase, exobase, initperiods, simperiods, lastperiods, recompute_final_steady_state, oo_.endo_simul, oo_.steady_state, oo_.exo_steady_state);
current_share = 0; % Share of shock successfully completed so far
step = min(options_.simul.homotopy_initial_step_size, options_.simul.homotopy_max_completion_share);
% Do linearization if needed and requested, and put results and solver status information in oo_
if completed_share == 1
oo_.endo_simul = endo_simul;
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
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
oo_.endo_simul = endobase + (endo_simul - endobase)/completed_share;
if options_.simul.endval_steady
% The following is needed for the STEADY_STATE() operator to work properly,
% and thus must come before computing the maximum error.
% This is not a true steady state, but it is the closest we can get to
oo_.steady_state = oo_.endo_simul(:, end);
end
% NB: no need to modify oo_.exo_simul and oo_.exo_steady_state, since we simulated 100% of the shock (although with an approximation)
maxerror = recompute_maxerror(oo_.endo_simul, oo_.exo_simul, oo_.steady_state, M_, options_);
if ~options_.noprint
fprintf('Perfect foresight solution found for %.1f%% of the shock, then extrapolation was performed using linearization\n\n', completed_share*100)
end
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
% Now compute extra simulation. First try using the first simulation as guess value.
extra_share = completed_share - options_.simul.homotopy_marginal_linearization_fallback;
extra_simul_time_counter = tic;
[extra_success, extra_endo_simul, extra_exo_simul, extra_steady_state, extra_exo_steady_state] = create_scenario(extra_share, endoorig, exoorig, endobase, exobase, initperiods, lastperiods, recompute_final_steady_state, endo_simul, steady_state, exo_steady_state);
if extra_success
[extra_endo_simul, extra_success] = perfect_foresight_solver_core(extra_endo_simul, extra_exo_simul, extra_steady_state, extra_exo_steady_state, M_, options_);
end
if ~extra_success
fprintf('The extra simulation for %.1f%% of the shock did not run when using the first simulation as a guess value. Now trying a full homotopy loop to get that extra simulation working\n\n', extra_share)
[extra_completed_share, extra_endo_simul] = homotopy_loop(extra_share, endoorig, exoorig, endobase, exobase, initperiods, simperiods, lastperiods, recompute_final_steady_state, oo_.endo_simul, oo_.steady_state, oo_.exo_steady_state);
extra_success = (extra_completed_share == extra_share);
end
extra_simul_time_elapsed = toc(extra_simul_time_counter);
if extra_success
oo_.endo_simul = endo_simul + (endo_simul - extra_endo_simul)*(1-completed_share)/options_.simul.homotopy_marginal_linearization_fallback;
if options_.simul.endval_steady
% The following is needed for the STEADY_STATE() operator to work properly,
% and thus must come before computing the maximum error.
% This is not a true steady state, but it is the closest we can get to
oo_.steady_state = oo_.endo_simul(:, end);
end
% NB: no need to modify oo_.exo_simul and oo_.exo_steady_state, since we simulated 100% of the shock (although with an approximation)
maxerror = recompute_maxerror(oo_.endo_simul, oo_.exo_simul, oo_.steady_state, M_, options_);
if ~options_.noprint
fprintf('Perfect foresight solution found for %.1f%% of the shock, then extrapolation was performed using marginal linearization (extra simulation took %.1f seconds)\n\n', completed_share*100, extra_simul_time_elapsed)
end
oo_.deterministic_simulation.homotopy_marginal_linearization = true;
else
% Set oo_ values to the partial simulation
oo_.endo_simul = endo_simul;
oo_.exo_simul = exo_simul;
oo_.steady_state = steady_state;
oo_.exo_steady_state = exo_steady_state;
fprintf('perfect_foresight_solver: marginal linearization failed, unable to find solution for %.1f%% of the shock (extra simulation took %.1f seconds). Try to modify the value of homotopy_marginal_linearization_fallback option\n\n', extra_share*100, extra_simul_time_elapsed)
end
oo_.deterministic_simulation.status = extra_success;
else
% Set oo_ values to the partial simulation
oo_.endo_simul = endo_simul;
oo_.exo_simul = exo_simul;
oo_.steady_state = steady_state;
oo_.exo_steady_state = exo_steady_state;
fprintf('Failed to solve perfect foresight model\n\n')
oo_.deterministic_simulation.status = false;
end
oo_.deterministic_simulation.error = maxerror;
oo_.deterministic_simulation.homotopy_completion_share = completed_share;
oo_.deterministic_simulation.homotopy_iterations = iteration;
if ~isempty(solver_iter)
oo_.deterministic_simulation.iterations = solver_iter;
end
if ~isempty(per_block_status)
oo_.deterministic_simulation.block = per_block_status;
end
dyn2vec(M_, oo_, options_);
if isfield(oo_, 'initval_series') && ~isempty(oo_.initval_series)
initial_period = oo_.initval_series.dates(1)+(M_.orig_maximum_lag-1);
elseif ~isdates(options_.initial_period) && isnan(options_.initial_period)
initial_period = dates(1,1);
else
initial_period = options_.initial_period;
end
ts = dseries([transpose(oo_.endo_simul(1:M_.orig_endo_nbr,:)), oo_.exo_simul], initial_period, [M_.endo_names(1:M_.orig_endo_nbr); M_.exo_names]);
if isfield(oo_, 'initval_series') && ~isempty(oo_.initval_series)
names = ts.name;
ts = merge(oo_.initval_series{names{:}}, ts);
end
assignin('base', 'Simulated_time_series', ts);
oo_.gui.ran_perfect_foresight = oo_.deterministic_simulation.status;
function [completed_share, endo_simul, exo_simul, steady_state, exo_steady_state, iteration, maxerror, solver_iter, per_block_status] = homotopy_loop(max_share, endoorig, exoorig, endobase, exobase, initperiods, simperiods, lastperiods, recompute_final_steady_state, endo_simul, steady_state, exo_steady_state)
global M_ options_
completed_share = 0; % Share of shock successfully completed so far
step = min(options_.simul.homotopy_initial_step_size, max_share);
success_counter = 0;
iteration = 0;
@ -141,11 +253,11 @@ while step > options_.simul.homotopy_min_step_size
saved_endo_simul = endo_simul;
new_share = current_share + step; % Try this share, and see if it succeeds
new_share = completed_share + step; % Try this share, and see if it succeeds
if new_share > options_.simul.homotopy_max_completion_share
new_share = options_.simul.homotopy_max_completion_share; % Don't go beyond target point
step = new_share - current_share;
if new_share > max_share
new_share = max_share; % Don't go beyond target point
step = new_share - completed_share;
end
iter_time_counter = tic;
@ -158,7 +270,7 @@ while step > options_.simul.homotopy_min_step_size
% does not make much sense). Afterwards, until a converging iteration has been obtained,
% use the rescaled terminal condition (or, if there is no lead, the base
% scenario / initial steady state).
if current_share == 0
if completed_share == 0
if iteration == 1 && new_share == 1
endo_simul(:, simperiods) = endoorig(:, simperiods);
elseif M_.maximum_lead > 0
@ -182,7 +294,7 @@ while step > options_.simul.homotopy_min_step_size
if options_.no_homotopy || (iteration == 1 && success && new_share == 1)
% Skip homotopy
if success
current_share = new_share;
completed_share = new_share;
end
break
end
@ -198,8 +310,8 @@ while step > options_.simul.homotopy_min_step_size
if ~options_.noprint
iter_summary_table{end+1} = sprintf('%i \t | %1.5f \t | %s \t | %e \t | %.1f\n', iteration, new_share, 'succeeded', maxerror, iter_time_elapsed);
end
current_share = new_share;
if current_share >= options_.simul.homotopy_max_completion_share
completed_share = new_share;
if completed_share >= max_share
% Print the iterations summary table for the last time, to show convergence
fprintf('%s', iter_summary_table{:})
break
@ -237,119 +349,13 @@ if ~isreal(endo_simul(:)) % cannot happen with bytecode or the perfect_foresight
endo_simul = real_simul;
maxerror = real_maxerror;
else
current_share = 0;
completed_share = 0;
disp('Simulation terminated with imaginary parts in the residuals or endogenous variables.')
end
end
fprintf('\n')
% Do linearization if needed and requested, and put results and solver status information in oo_
if current_share == 1
oo_.endo_simul = endo_simul;
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
if ~options_.noprint
fprintf('Perfect foresight solution found.\n\n')
end
oo_.deterministic_simulation.status = true;
elseif options_.simul.homotopy_linearization_fallback && current_share > 0
oo_.endo_simul = endobase + (endo_simul - endobase)/current_share;
if options_.simul.endval_steady
% The following is needed for the STEADY_STATE() operator to work properly,
% and thus must come before computing the maximum error.
% This is not a true steady state, but it is the closest we can get to
oo_.steady_state = oo_.endo_simul(:, end);
end
% NB: no need to modify oo_.exo_simul and oo_.exo_steady_state, since we simulated 100% of the shock (although with an approximation)
maxerror = recompute_maxerror(oo_.endo_simul, oo_.exo_simul, oo_.steady_state, M_, options_);
if ~options_.noprint
fprintf('Perfect foresight solution found for %.1f%% of the shock, then extrapolation was performed using linearization\n\n', current_share*100)
end
oo_.deterministic_simulation.status = true;
oo_.deterministic_simulation.homotopy_linearization = true;
elseif options_.simul.homotopy_marginal_linearization_fallback > 0 && current_share > options_.simul.homotopy_marginal_linearization_fallback
% Now compute extra simulation
extra_share = current_share - options_.simul.homotopy_marginal_linearization_fallback;
extra_simul_time_counter = tic;
[extra_success, extra_endo_simul, extra_exo_simul, extra_steady_state, extra_exo_steady_state] = create_scenario(extra_share, endoorig, exoorig, endobase, exobase, initperiods, lastperiods, recompute_final_steady_state, endo_simul, steady_state, exo_steady_state);
if extra_success
[extra_endo_simul, extra_success] = perfect_foresight_solver_core(extra_endo_simul, extra_exo_simul, extra_steady_state, extra_exo_steady_state, M_, options_);
end
extra_simul_time_elapsed = toc(extra_simul_time_counter);
if extra_success
oo_.endo_simul = endo_simul + (endo_simul - extra_endo_simul)*(1-current_share)/options_.simul.homotopy_marginal_linearization_fallback;
if options_.simul.endval_steady
% The following is needed for the STEADY_STATE() operator to work properly,
% and thus must come before computing the maximum error.
% This is not a true steady state, but it is the closest we can get to
oo_.steady_state = oo_.endo_simul(:, end);
end
% NB: no need to modify oo_.exo_simul and oo_.exo_steady_state, since we simulated 100% of the shock (although with an approximation)
maxerror = recompute_maxerror(oo_.endo_simul, oo_.exo_simul, oo_.steady_state, M_, options_);
if ~options_.noprint
fprintf('Perfect foresight solution found for %.1f%% of the shock, then extrapolation was performed using marginal linearization (extra simulation took %.1f seconds)\n\n', current_share*100, extra_simul_time_elapsed)
end
oo_.deterministic_simulation.homotopy_marginal_linearization = true;
else
% Set oo_ values to the partial simulation
oo_.endo_simul = endo_simul;
oo_.exo_simul = exo_simul;
oo_.steady_state = steady_state;
oo_.exo_steady_state = exo_steady_state;
fprintf('perfect_foresight_solver: marginal linearization failed, unable to find solution for %.1f%% of the shock (extra simulation took %.1f seconds). Try to modify the value of homotopy_marginal_linearization_fallback option\n\n', extra_share*100, extra_simul_time_elapsed)
end
oo_.deterministic_simulation.status = extra_success;
else
% Set oo_ values to the partial simulation
oo_.endo_simul = endo_simul;
oo_.exo_simul = exo_simul;
oo_.steady_state = steady_state;
oo_.exo_steady_state = exo_steady_state;
fprintf('Failed to solve perfect foresight model\n\n')
oo_.deterministic_simulation.status = false;
end
oo_.deterministic_simulation.error = maxerror;
oo_.deterministic_simulation.homotopy_completion_share = current_share;
oo_.deterministic_simulation.homotopy_iterations = iteration;
if ~isempty(solver_iter)
oo_.deterministic_simulation.iterations = solver_iter;
end
if ~isempty(per_block_status)
oo_.deterministic_simulation.block = per_block_status;
end
dyn2vec(M_, oo_, options_);
if isfield(oo_, 'initval_series') && ~isempty(oo_.initval_series)
initial_period = oo_.initval_series.dates(1)+(M_.orig_maximum_lag-1);
elseif ~isdates(options_.initial_period) && isnan(options_.initial_period)
initial_period = dates(1,1);
else
initial_period = options_.initial_period;
end
ts = dseries([transpose(oo_.endo_simul(1:M_.orig_endo_nbr,:)), oo_.exo_simul], initial_period, [M_.endo_names(1:M_.orig_endo_nbr); M_.exo_names]);
if isfield(oo_, 'initval_series') && ~isempty(oo_.initval_series)
names = ts.name;
ts = merge(oo_.initval_series{names{:}}, ts);
end
assignin('base', 'Simulated_time_series', ts);
if success
oo_.gui.ran_perfect_foresight = true;
end
function [steady_success, endo_simul, exo_simul, steady_state, exo_steady_state] = create_scenario(share, endoorig, exoorig, endobase, exobase, initperiods, lastperiods, recompute_final_steady_state, endo_simul, steady_state, exo_steady_state)
% For a given share, comutes the exogenous path and also the initial and