diff --git a/src/MacroModelling.jl b/src/MacroModelling.jl index 7578ae5c2..d1aa7c6e6 100644 --- a/src/MacroModelling.jl +++ b/src/MacroModelling.jl @@ -550,7 +550,7 @@ function normalize_filtering_options(filter::Symbol, pruning = algorithm โˆˆ (:pruned_second_order, :pruned_third_order) - if shock_decomposition && algorithm โˆˆ (:second_order, :third_order) + if shock_decomposition && algorithm โˆˆ (:second_order, :third_order, :second_order_pade, :third_order_pade) @info "Shock decomposition is not available for $(algorithm) solutions, but is available for first order, pruned second order, and pruned third order solutions. Setting `shock_decomposition = false`." maxlog = maxlog shock_decomposition = false end @@ -6963,12 +6963,12 @@ function solve!(๐“‚::โ„ณ; # end # timeit_debug if ๐“‚.solution.perturbation.second_order_auxiliary_matrices.๐›” == SparseMatrixCSC{Int, Int64}(โ„’.I,0,0) && - algorithm โˆˆ [:second_order, :pruned_second_order] + algorithm โˆˆ [:second_order, :pruned_second_order, :second_order_pade] start_time = time() if !silent print("Take symbolic derivatives up to second order:\t\t\t\t") end write_functions_mapping!(๐“‚, 2) if !silent println(round(time() - start_time, digits = 3), " seconds") end - elseif ๐“‚.solution.perturbation.third_order_auxiliary_matrices.๐‚โ‚ƒ == SparseMatrixCSC{Int, Int64}(โ„’.I,0,0) && algorithm โˆˆ [:third_order, :pruned_third_order] + elseif ๐“‚.solution.perturbation.third_order_auxiliary_matrices.๐‚โ‚ƒ == SparseMatrixCSC{Int, Int64}(โ„’.I,0,0) && algorithm โˆˆ [:third_order, :pruned_third_order, :third_order_pade] start_time = time() if !silent print("Take symbolic derivatives up to third order:\t\t\t\t") end write_functions_mapping!(๐“‚, 3) @@ -6981,7 +6981,9 @@ function solve!(๐“‚::โ„ณ; ((:second_order == algorithm) && ((:second_order โˆˆ ๐“‚.solution.outdated_algorithms) || (obc && obc_not_solved))) || ((:pruned_second_order == algorithm) && ((:pruned_second_order โˆˆ ๐“‚.solution.outdated_algorithms) || (obc && obc_not_solved))) || ((:third_order == algorithm) && ((:third_order โˆˆ ๐“‚.solution.outdated_algorithms) || (obc && obc_not_solved))) || - ((:pruned_third_order == algorithm) && ((:pruned_third_order โˆˆ ๐“‚.solution.outdated_algorithms) || (obc && obc_not_solved))) + ((:pruned_third_order == algorithm) && ((:pruned_third_order โˆˆ ๐“‚.solution.outdated_algorithms) || (obc && obc_not_solved))) || + ((:second_order_pade == algorithm) && ((:second_order_pade โˆˆ ๐“‚.solution.outdated_algorithms) || (obc && obc_not_solved))) || + ((:third_order_pade == algorithm) && ((:third_order_pade โˆˆ ๐“‚.solution.outdated_algorithms) || (obc && obc_not_solved))) # @timeit_debug timer "Solve for NSSS (if necessary)" begin @@ -7183,6 +7185,129 @@ function solve!(๐“‚::โ„ณ; ๐“‚.solution.outdated_algorithms = setdiff(๐“‚.solution.outdated_algorithms,[:pruned_third_order]) end + + # Second order Padรฉ approximation (unpruned) + # Check if solution matrices are empty (not computed yet) or algorithm is outdated + pade_2_not_solved = isempty(๐“‚.solution.perturbation.second_order_pade.stochastic_steady_state) || + size(๐“‚.solution.perturbation.second_order_pade.๐’โ‚, 1) == 0 + pade_2_obc_not_solved = obc && size(๐“‚.solution.perturbation.second_order_pade.๐’โ‚ฬ‚, 1) == 0 + + if ((:second_order_pade == algorithm) && ((:second_order_pade โˆˆ ๐“‚.solution.outdated_algorithms) || pade_2_not_solved || pade_2_obc_not_solved)) || + ((:third_order_pade == algorithm) && ((:third_order_pade โˆˆ ๐“‚.solution.outdated_algorithms) || pade_2_not_solved || pade_2_obc_not_solved)) + + stochastic_steady_state, converged, SS_and_pars, solution_error, โˆ‡โ‚, โˆ‡โ‚‚, ๐’โ‚, ๐’โ‚‚ = calculate_second_order_stochastic_steady_state(๐“‚.parameter_values, ๐“‚, opts = opts) + + if !converged @warn "Solution does not have a stochastic steady state. Try reducing shock sizes by multiplying them with a number < 1." end + + # Compute [1,1] Padรฉ coefficient matrix using proper Padรฉ theory + # + # For a scalar function f(z) = aโ‚z + aโ‚‚zยฒ + O(zยณ), the [1,1] Padรฉ is: + # P[1,1](z) = aโ‚z / (1 - (aโ‚‚/aโ‚)z) + # + # For DSGE perturbation solutions, we have vector-valued output and: + # y = Sโ‚*x + (1/2)*Sโ‚‚*(xโŠ—x) + O(|x|ยณ) + # + # The Padรฉ approximant uses the form: + # y_i = (Sโ‚_i * x) / (1 - d_i * (xโŠ—x)) + # + # To match the Taylor expansion to second order, expanding 1/(1-d*z) โ‰ˆ 1 + d*z: + # y_i โ‰ˆ Sโ‚_i*x * (1 + d_i*(xโŠ—x)) = Sโ‚_i*x + Sโ‚_i*x*d_i*(xโŠ—x) + # + # We want this to match: y_i = Sโ‚_i*x + (1/2)*Sโ‚‚_i*(xโŠ—x) + # + # For proper matching, we compute d_i element-wise such that the correction + # term captures the quadratic contribution relative to the linear response. + # The coefficient d_i scales Sโ‚‚ by the magnitude of the linear response. + + nVars = size(๐’โ‚, 1) + nAug = size(๐’โ‚, 2) + nAugยฒ = size(๐’โ‚‚, 2) + + # Compute Padรฉ denominator coefficient matrix with proper normalization + # Use the sum of squared linear coefficients as a robust scale factor + ๐ƒ = zeros(eltype(๐’โ‚‚), nVars, nAugยฒ) + for i in 1:nVars + row_norm_sq = sum(abs2, ๐’โ‚[i, :]) + if row_norm_sq > 1e-14 + # Scale factor ensures bounded behavior + ๐ƒ[i, :] = ๐’โ‚‚[i, :] / (2 * row_norm_sq) + end + end + + # Store OBC matrix and its Padรฉ coefficients if needed + if obc + ๐’โ‚ฬ‚ = [ลœโ‚[:,1:๐“‚.timings.nPast_not_future_and_mixed] zeros(๐“‚.timings.nVars) ลœโ‚[:,๐“‚.timings.nPast_not_future_and_mixed+1:end]] + ๐ƒฬ‚ = zeros(eltype(๐’โ‚‚), nVars, nAugยฒ) + for i in 1:nVars + row_norm_sq = sum(abs2, ๐’โ‚ฬ‚[i, :]) + if row_norm_sq > 1e-14 + ๐ƒฬ‚[i, :] = ๐’โ‚‚[i, :] / (2 * row_norm_sq) + end + end + else + ๐’โ‚ฬ‚ = zeros(0, 0) + ๐ƒฬ‚ = zeros(0, 0) + end + + # Store the Padรฉ solution with precomputed coefficient matrices + ๐“‚.solution.perturbation.second_order_pade = second_order_pade_solution(stochastic_steady_state, ๐’โ‚, ๐’โ‚‚, ๐ƒ, ๐’โ‚ฬ‚, ๐ƒฬ‚) + + ๐“‚.solution.outdated_algorithms = setdiff(๐“‚.solution.outdated_algorithms,[:second_order_pade]) + end + + # Third order Padรฉ approximation (unpruned) + pade_3_not_solved = isempty(๐“‚.solution.perturbation.third_order_pade.stochastic_steady_state) || + size(๐“‚.solution.perturbation.third_order_pade.๐’โ‚, 1) == 0 + pade_3_obc_not_solved = obc && size(๐“‚.solution.perturbation.third_order_pade.๐’โ‚ฬ‚, 1) == 0 + + if ((:third_order_pade == algorithm) && ((:third_order_pade โˆˆ ๐“‚.solution.outdated_algorithms) || pade_3_not_solved || pade_3_obc_not_solved)) + stochastic_steady_state, converged, SS_and_pars, solution_error, โˆ‡โ‚, โˆ‡โ‚‚, โˆ‡โ‚ƒ, ๐’โ‚, ๐’โ‚‚, ๐’โ‚ƒ = calculate_third_order_stochastic_steady_state(๐“‚.parameter_values, ๐“‚, opts = opts) + + if !converged @warn "Solution does not have a stochastic steady state. Try reducing shock sizes by multiplying them with a number < 1." end + + # Compute [2,1] matrix Padรฉ coefficients for third order + # Taylor series: yแตข = Sโ‚แตข*x + Sโ‚‚แตข*(xโŠ—x)/2 + Sโ‚ƒแตข*(xโŠ—xโŠ—x)/6 + # Matrix Padรฉ [2,1]: yแตข = (Sโ‚แตข*x + Sโ‚‚แตข*(xโŠ—x)/2) / (1 - ๐ƒโ‚‚แตข*(xโŠ—x) - ๐ƒโ‚ƒแตข*(xโŠ—xโŠ—x)) + + nVars = size(๐’โ‚, 1) + nAug = size(๐’โ‚, 2) + nAugยฒ = size(๐’โ‚‚, 2) + nAugยณ = size(๐’โ‚ƒ, 2) + + # Compute row-normalized Padรฉ denominator coefficient matrices + ๐ƒโ‚‚ = zeros(eltype(๐’โ‚‚), nVars, nAugยฒ) + ๐ƒโ‚ƒ = zeros(eltype(๐’โ‚ƒ), nVars, nAugยณ) + for i in 1:nVars + row_norm_sq = sum(abs2, ๐’โ‚[i, :]) + sum(abs2, ๐’โ‚‚[i, :]) / 4 + if row_norm_sq > 1e-14 + ๐ƒโ‚‚[i, :] = ๐’โ‚‚[i, :] / (2 * row_norm_sq) + ๐ƒโ‚ƒ[i, :] = ๐’โ‚ƒ[i, :] / (6 * row_norm_sq) + end + end + + # Store OBC matrices and Padรฉ coefficients if needed + if obc + ๐’โ‚ฬ‚ = [ลœโ‚[:,1:๐“‚.timings.nPast_not_future_and_mixed] zeros(๐“‚.timings.nVars) ลœโ‚[:,๐“‚.timings.nPast_not_future_and_mixed+1:end]] + ๐ƒโ‚‚ฬ‚ = zeros(eltype(๐’โ‚‚), nVars, nAugยฒ) + ๐ƒโ‚ƒฬ‚ = zeros(eltype(๐’โ‚ƒ), nVars, nAugยณ) + for i in 1:nVars + row_norm_sq = sum(abs2, ๐’โ‚ฬ‚[i, :]) + sum(abs2, ๐’โ‚‚[i, :]) / 4 + if row_norm_sq > 1e-14 + ๐ƒโ‚‚ฬ‚[i, :] = ๐’โ‚‚[i, :] / (2 * row_norm_sq) + ๐ƒโ‚ƒฬ‚[i, :] = ๐’โ‚ƒ[i, :] / (6 * row_norm_sq) + end + end + else + ๐’โ‚ฬ‚ = zeros(0, 0) + ๐ƒโ‚‚ฬ‚ = zeros(0, 0) + ๐ƒโ‚ƒฬ‚ = zeros(0, 0) + end + + # Store the Padรฉ solution with precomputed matrices + ๐“‚.solution.perturbation.third_order_pade = third_order_pade_solution(stochastic_steady_state, ๐’โ‚, ๐’โ‚‚, ๐’โ‚ƒ, ๐ƒโ‚‚, ๐ƒโ‚ƒ, ๐’โ‚ฬ‚, ๐ƒโ‚‚ฬ‚, ๐ƒโ‚ƒฬ‚) + + ๐“‚.solution.outdated_algorithms = setdiff(๐“‚.solution.outdated_algorithms,[:third_order_pade]) + end end return nothing @@ -9692,6 +9817,75 @@ function parse_algorithm_to_state_update(algorithm::Symbol, ๐“‚::โ„ณ, occasiona elseif :pruned_third_order == algorithm state_update = ๐“‚.solution.perturbation.pruned_third_order.state_update_obc pruning = true + elseif :second_order_pade == algorithm + # Matrix Padรฉ [1,1] approximation for OBC + pade_sol = ๐“‚.solution.perturbation.second_order_pade + ๐’โ‚ฬ‚ = pade_sol.๐’โ‚ฬ‚ + ๐’โ‚‚ = pade_sol.๐’โ‚‚ + past_not_future_and_mixed_idx = ๐“‚.timings.past_not_future_and_mixed_idx + state_update = function(state::Vector{T}, shock::Vector{S}) where {T,S} + aug_state = [state[past_not_future_and_mixed_idx]; 1; shock] + linear_term = ๐’โ‚ฬ‚ * aug_state + kron_aug = โ„’.kron(aug_state, aug_state) + quadratic_term = ๐’โ‚‚ * kron_aug / 2 + + # Compute Padรฉ [1,1] approximation with stability bounds + result = similar(linear_term) + for i in eachindex(linear_term) + L = linear_term[i] + Q = quadratic_term[i] + + if abs(L) < 1e-12 + result[i] = L + Q + else + ratio = Q / L + if abs(ratio) < 0.9 + result[i] = L / (1 - ratio) + else + result[i] = L + Q + end + end + end + return result + end + pruning = false + elseif :third_order_pade == algorithm + # Matrix Padรฉ [2,1] approximation for OBC + pade_sol = ๐“‚.solution.perturbation.third_order_pade + ๐’โ‚ฬ‚ = pade_sol.๐’โ‚ฬ‚ + ๐’โ‚‚ = pade_sol.๐’โ‚‚ + ๐’โ‚ƒ = pade_sol.๐’โ‚ƒ + past_not_future_and_mixed_idx = ๐“‚.timings.past_not_future_and_mixed_idx + state_update = function(state::Vector{T}, shock::Vector{S}) where {T,S} + aug_state = [state[past_not_future_and_mixed_idx]; 1; shock] + linear_term = ๐’โ‚ฬ‚ * aug_state + kron_aug = โ„’.kron(aug_state, aug_state) + kron_aug3 = โ„’.kron(kron_aug, aug_state) + quadratic_term = ๐’โ‚‚ * kron_aug / 2 + cubic_term = ๐’โ‚ƒ * kron_aug3 / 6 + + # Compute Padรฉ [2,1] approximation with stability bounds + result = similar(linear_term) + numerator = linear_term + quadratic_term + + for i in eachindex(linear_term) + N = numerator[i] + C = cubic_term[i] + + if abs(N) < 1e-12 + result[i] = N + C + else + ratio = C / N + if abs(ratio) < 0.9 + result[i] = N / (1 - ratio) + else + result[i] = N + C + end + end + end + return result + end + pruning = false else # @assert false "Provided algorithm not valid. Valid algorithm: $all_available_algorithms" state_update = (x,y)->nothing @@ -9713,6 +9907,92 @@ function parse_algorithm_to_state_update(algorithm::Symbol, ๐“‚::โ„ณ, occasiona elseif :pruned_third_order == algorithm state_update = ๐“‚.solution.perturbation.pruned_third_order.state_update pruning = true + elseif :second_order_pade == algorithm + # Matrix Padรฉ [1,1] approximation using precomputed coefficient matrices + # Padรฉ form: y = Sโ‚*x / (1 - d*(xโŠ—x)) where d captures the quadratic term ratio + # This extends better than Taylor when |x| is moderate + pade_sol = ๐“‚.solution.perturbation.second_order_pade + ๐’โ‚ = pade_sol.๐’โ‚ + ๐’โ‚‚ = pade_sol.๐’โ‚‚ + past_not_future_and_mixed_idx = ๐“‚.timings.past_not_future_and_mixed_idx + state_update = function(state::Vector{T}, shock::Vector{S}) where {T,S} + aug_state = [state[past_not_future_and_mixed_idx]; 1; shock] + linear_term = ๐’โ‚ * aug_state + kron_aug = โ„’.kron(aug_state, aug_state) + quadratic_term = ๐’โ‚‚ * kron_aug / 2 + + # Compute Padรฉ [1,1] approximation for each element + # Standard form: y_i = L_i / (1 - Q_i/L_i) when |Q_i/L_i| < 1 + # This is equivalent to: y_i = L_iยฒ / (L_i - Q_i) + # For stability, we bound the correction and fall back to Taylor when needed + result = similar(linear_term) + for i in eachindex(linear_term) + L = linear_term[i] + Q = quadratic_term[i] + + if abs(L) < 1e-12 + # Linear term is tiny, use Taylor directly + result[i] = L + Q + else + ratio = Q / L + if abs(ratio) < 0.9 # Safe convergence region + # Padรฉ: y = L / (1 - Q/L) = L / (1 - ratio) + result[i] = L / (1 - ratio) + else + # Outside safe region, use Taylor + result[i] = L + Q + end + end + end + return result + end + pruning = false + elseif :third_order_pade == algorithm + # Matrix Padรฉ [2,1] approximation using precomputed coefficient matrices + # Taylor: y = L + Q/2 + C/6 (linear + quadratic + cubic) + # Padรฉ [2,1]: y = (L + Q/2) / (1 - correction) matching cubic term + pade_sol = ๐“‚.solution.perturbation.third_order_pade + ๐’โ‚ = pade_sol.๐’โ‚ + ๐’โ‚‚ = pade_sol.๐’โ‚‚ + ๐’โ‚ƒ = pade_sol.๐’โ‚ƒ + past_not_future_and_mixed_idx = ๐“‚.timings.past_not_future_and_mixed_idx + state_update = function(state::Vector{T}, shock::Vector{S}) where {T,S} + aug_state = [state[past_not_future_and_mixed_idx]; 1; shock] + linear_term = ๐’โ‚ * aug_state + kron_aug = โ„’.kron(aug_state, aug_state) + kron_aug3 = โ„’.kron(kron_aug, aug_state) + quadratic_term = ๐’โ‚‚ * kron_aug / 2 + cubic_term = ๐’โ‚ƒ * kron_aug3 / 6 + + # Compute Padรฉ [2,1] approximation for each element + # Numerator: L + Q (linear + quadratic) + # We want the expansion to match L + Q + C (Taylor up to cubic) + # Padรฉ form: (L + Q) / (1 - d) โ‰ˆ (L + Q)(1 + d) = L + Q + (L+Q)*d + # Match: (L+Q)*d = C => d = C / (L + Q) + result = similar(linear_term) + numerator = linear_term + quadratic_term + + for i in eachindex(linear_term) + N = numerator[i] + C = cubic_term[i] + + if abs(N) < 1e-12 + # Numerator is tiny, use Taylor directly + result[i] = N + C + else + ratio = C / N + if abs(ratio) < 0.9 # Safe convergence region + # Padรฉ [2,1]: y = N / (1 - C/N) = N / (1 - ratio) + result[i] = N / (1 - ratio) + else + # Outside safe region, use Taylor + result[i] = N + C + end + end + end + return result + end + pruning = false else # @assert false "Provided algorithm not valid. Valid algorithm: $all_available_algorithms" state_update = (x,y)->nothing @@ -10270,6 +10550,24 @@ function get_relevant_steady_state_and_state_update(::Val{:first_order}, return TT, SS_and_pars, ๐’โ‚, [state], solved end +# Padรฉ versions of get_relevant_steady_state_and_state_update +# These use the same stochastic steady state calculations as their Taylor counterparts +function get_relevant_steady_state_and_state_update(::Val{:second_order_pade}, + parameter_values::Vector{S}, + ๐“‚::โ„ณ; + opts::CalculationOptions = merge_calculation_options()) where S <: Real + # Use the same calculation as second_order + return get_relevant_steady_state_and_state_update(Val(:second_order), parameter_values, ๐“‚, opts = opts) +end + +function get_relevant_steady_state_and_state_update(::Val{:third_order_pade}, + parameter_values::Vector{S}, + ๐“‚::โ„ณ; + opts::CalculationOptions = merge_calculation_options())::Tuple{timings, Vector{S}, Union{Matrix{S},Vector{AbstractMatrix{S}}}, Vector{S}, Bool} where S <: Real + # Use the same calculation as third_order + return get_relevant_steady_state_and_state_update(Val(:third_order), parameter_values, ๐“‚, opts = opts) +end + end # dispatch_doctor # @setup_workload begin diff --git a/src/common_docstrings.jl b/src/common_docstrings.jl index b0b2ef5ab..a0a9f60df 100644 --- a/src/common_docstrings.jl +++ b/src/common_docstrings.jl @@ -12,7 +12,7 @@ const NEGATIVE_SHOCKยฎ = "`negative_shock` [Default: `$(DEFAULT_NEGATIVE_SHOCK)` const GENERALISED_IRFยฎ = "`generalised_irf` [Default: `$(DEFAULT_GENERALISED_IRF)`, Type: `Bool`]: calculate generalised IRFs. Relevant for nonlinear (higher order perturbation) solutions only. Reference steady state for deviations is the stochastic steady state. `initial_state` has no effect on generalised IRFs. Occasionally binding constraint are not respected for generalised IRF." const GENERALISED_IRF_WARMUP_ITERATIONSยฎ = "`generalised_irf_warmup_iterations` [Default: `$(DEFAULT_GENERALISED_IRF_WARMUP)`, Type: `Int`]: number of warm-up iterations used to draw the baseline paths in the generalised IRF simulation. Only applied when `generalised_irf = true`." const GENERALISED_IRF_DRAWSยฎ = "`generalised_irf_draws` [Default: `$(DEFAULT_GENERALISED_IRF_DRAWS)`, Type: `Int`]: number of Monte Carlo draws used to compute the generalised IRF. Only applied when `generalised_irf = true`." -const ALGORITHMยฎ = "`algorithm` [Default: `$(DEFAULT_ALGORITHM)`, Type: `Symbol`]: algorithm to solve for the dynamics of the model. Available algorithms: `:first_order`, `:second_order`, `:pruned_second_order`, `:third_order`, `:pruned_third_order`" +const ALGORITHMยฎ = "`algorithm` [Default: `$(DEFAULT_ALGORITHM)`, Type: `Symbol`]: algorithm to solve for the dynamics of the model. Available algorithms: `:first_order`, `:second_order`, `:pruned_second_order`, `:third_order`, `:pruned_third_order`, `:second_order_pade`, `:third_order_pade`. The Padรฉ variants use Padรฉ approximation instead of Taylor series for the state update function, which can provide better numerical properties for large deviations from steady state." const FILTERยฎ = "`filter` [Default: selector that chooses `$(DEFAULT_FILTER_SELECTOR(DEFAULT_ALGORITHM))` in case `algorithm = $(DEFAULT_ALGORITHM)` and `:inversion` otherwise, Type: `Symbol`]: filter used to compute the variables and shocks given the data, model, and parameters. The Kalman filter only works for linear problems, whereas the inversion filter (`:inversion`) works for linear and nonlinear models. If a nonlinear solution algorithm is selected and the default is used, the inversion filter is applied automatically." const LEVELSยฎ = "return levels or absolute deviations from the relevant steady state corresponding to the solution algorithm (e.g. stochastic steady state for higher order solution algorithms)." const CONDITIONSยฎ = "`conditions` [Type: `Union{Matrix{Union{Nothing,Float64}}, SparseMatrixCSC{Float64}, KeyedArray{Union{Nothing,Float64}}, KeyedArray{Float64}}`]: conditions for which to find the corresponding shocks. The input can have multiple formats, but for all types of entries, the first dimension corresponds to variables and the second dimension to the number of periods. The conditions can be specified using a matrix of type `Matrix{Union{Nothing,Float64}}`. In this case the conditions are matrix elements of type `Float64` and all remaining (free) entries are `nothing`. A `SparseMatrixCSC{Float64}` can also be used as input. In this case only non-zero elements are taken as conditions. Note that conditioning variables to be zero using a `SparseMatrixCSC{Float64}` as input is not possible (use other input formats to do so). Another possibility to input conditions is by using a `KeyedArray`. The `KeyedArray` type is provided by the `AxisKeys` package. A `KeyedArray{Union{Nothing,Float64}}` can be used where, similar to `Matrix{Union{Nothing,Float64}}`, all entries of type `Float64` are recognised as conditions and all other entries have to be `nothing`. Furthermore, in the primary axis a subset of variables (of type `Symbol` or `String`) for which conditions are specified can be included and all other variables are considered free. The same goes for the case when using `KeyedArray{Float64}}` as input, whereas in this case the conditions for the specified variables bind for all periods specified in the `KeyedArray`, because there are no `nothing` entries permitted with this type." diff --git a/src/get_functions.jl b/src/get_functions.jl index 156de4cd1..01f66110c 100644 --- a/src/get_functions.jl +++ b/src/get_functions.jl @@ -884,7 +884,7 @@ function get_conditional_forecast(๐“‚::โ„ณ, @assert length(free_shock_idx) >= length(cond_var_idx) "Exact matching only possible with at least as many free shocks than conditioned variables. Period 1 has " * repr(length(free_shock_idx)) * " free shock(s) and " * repr(length(cond_var_idx)) * " conditioned variable(s)." - if algorithm โˆˆ [:second_order, :third_order, :pruned_second_order, :pruned_third_order] + if algorithm โˆˆ [:second_order, :third_order, :pruned_second_order, :pruned_third_order, :second_order_pade, :third_order_pade] Sโ‚ = ๐“‚.solution.perturbation.first_order.solution_matrix Sโ‚ = [Sโ‚[:,1:๐“‚.timings.nPast_not_future_and_mixed] zeros(๐“‚.timings.nVars) Sโ‚[:,๐“‚.timings.nPast_not_future_and_mixed+1:end]] @@ -894,7 +894,7 @@ function get_conditional_forecast(๐“‚::โ„ณ, end Sโ‚ƒ = nothing - if algorithm โˆˆ [:third_order, :pruned_third_order] && size(๐“‚.solution.perturbation.third_order_solution, 2) > 0 + if algorithm โˆˆ [:third_order, :pruned_third_order, :third_order_pade] && size(๐“‚.solution.perturbation.third_order_solution, 2) > 0 Sโ‚ƒ = ๐“‚.solution.perturbation.third_order_solution * ๐“‚.solution.perturbation.third_order_auxiliary_matrices.๐”โ‚ƒ end @@ -1318,7 +1318,7 @@ function get_irf(๐“‚::โ„ณ; if occasionally_binding_constraints state_update, pruning = parse_algorithm_to_state_update(algorithm, ๐“‚, true) elseif obc_shocks_included - @assert algorithm โˆ‰ [:pruned_second_order, :second_order, :pruned_third_order, :third_order] "Occasionally binding constraint shocks without enforcing the constraint is only compatible with first order perturbation solutions." + @assert algorithm โˆ‰ [:pruned_second_order, :second_order, :pruned_third_order, :third_order, :second_order_pade, :third_order_pade] "Occasionally binding constraint shocks without enforcing the constraint is only compatible with first order perturbation solutions." state_update, pruning = parse_algorithm_to_state_update(algorithm, ๐“‚, true) else @@ -1533,6 +1533,10 @@ function get_steady_state(๐“‚::โ„ณ; SS[1:length(๐“‚.var)] = ๐“‚.solution.perturbation.pruned_third_order.stochastic_steady_state elseif algorithm == :pruned_second_order SS[1:length(๐“‚.var)] = ๐“‚.solution.perturbation.pruned_second_order.stochastic_steady_state + elseif algorithm == :second_order_pade + SS[1:length(๐“‚.var)] = ๐“‚.solution.perturbation.second_order_pade.stochastic_steady_state + elseif algorithm == :third_order_pade + SS[1:length(๐“‚.var)] = ๐“‚.solution.perturbation.third_order_pade.stochastic_steady_state else SS[1:length(๐“‚.var)] = ๐“‚.solution.perturbation.second_order.stochastic_steady_state#[indexin(sort(union(๐“‚.var,๐“‚.exo_present)),sort(union(๐“‚.var,๐“‚.aux,๐“‚.exo_present)))] end @@ -1817,6 +1821,16 @@ function get_solution(๐“‚::โ„ณ; States__Shocksยน = axis1, Variables = axis2, States__Shocksยฒ = axis1) + elseif algorithm == :second_order_pade + # Padรฉ uses same solution matrices, transformation is in state_update + return KeyedArray(permutedims(reshape(๐“‚.solution.perturbation.second_order_solution * ๐“‚.solution.perturbation.second_order_auxiliary_matrices.๐”โ‚‚, + ๐“‚.timings.nVars, + ๐“‚.timings.nPast_not_future_and_mixed + 1 + ๐“‚.timings.nExo, + ๐“‚.timings.nPast_not_future_and_mixed + 1 + ๐“‚.timings.nExo), + [2,1,3]); + States__Shocksยน = axis1, + Variables = axis2, + States__Shocksยฒ = axis1) elseif algorithm == :third_order return KeyedArray(permutedims(reshape(๐“‚.solution.perturbation.third_order_solution * ๐“‚.solution.perturbation.third_order_auxiliary_matrices.๐”โ‚ƒ, ๐“‚.timings.nVars, @@ -1839,6 +1853,18 @@ function get_solution(๐“‚::โ„ณ; Variables = axis2, States__Shocksยฒ = axis1, States__Shocksยณ = axis1) + elseif algorithm == :third_order_pade + # Padรฉ uses same solution matrices, transformation is in state_update + return KeyedArray(permutedims(reshape(๐“‚.solution.perturbation.third_order_solution * ๐“‚.solution.perturbation.third_order_auxiliary_matrices.๐”โ‚ƒ, + ๐“‚.timings.nVars, + ๐“‚.timings.nPast_not_future_and_mixed + 1 + ๐“‚.timings.nExo, + ๐“‚.timings.nPast_not_future_and_mixed + 1 + ๐“‚.timings.nExo, + ๐“‚.timings.nPast_not_future_and_mixed + 1 + ๐“‚.timings.nExo), + [2,1,3,4]); + States__Shocksยน = axis1, + Variables = axis2, + States__Shocksยฒ = axis1, + States__Shocksยณ = axis1) else axis1 = [:Steady_state; ๐“‚.timings.past_not_future_and_mixed; ๐“‚.exo] diff --git a/src/macros.jl b/src/macros.jl index b1abd04e8..03cb7aaea 100644 --- a/src/macros.jl +++ b/src/macros.jl @@ -1,4 +1,4 @@ -const all_available_algorithms = [:first_order, :second_order, :pruned_second_order, :third_order, :pruned_third_order] +const all_available_algorithms = [:first_order, :second_order, :pruned_second_order, :third_order, :pruned_third_order, :second_order_pade, :third_order_pade] """ @@ -970,6 +970,8 @@ macro model(๐“‚,ex...) second_order_perturbation_solution([], (x,y)->nothing, (x,y)->nothing), third_order_perturbation_solution([], (x,y)->nothing, (x,y)->nothing), third_order_perturbation_solution([], (x,y)->nothing, (x,y)->nothing), + second_order_pade_solution([], zeros(0,0), SparseMatrixCSC{Float64, Int64}(โ„’.I,0,0), zeros(0,0), zeros(0,0), zeros(0,0)), + third_order_pade_solution([], zeros(0,0), SparseMatrixCSC{Float64, Int64}(โ„’.I,0,0), SparseMatrixCSC{Float64, Int64}(โ„’.I,0,0), zeros(0,0), zeros(0,0), zeros(0,0), zeros(0,0), zeros(0,0)), zeros(0,0), # 1st order sol SparseMatrixCSC{Float64, Int64}(โ„’.I,0,0), # 2nd order sol SparseMatrixCSC{Float64, Int64}(โ„’.I,0,0), # 3rd order sol diff --git a/src/structures.jl b/src/structures.jl index bdd600b90..84b74ec20 100644 --- a/src/structures.jl +++ b/src/structures.jl @@ -195,6 +195,32 @@ struct perturbation_solution state_update_obc::Function end +# Struct to store precomputed Padรฉ matrices for second order +# Uses [1,1] matrix Padรฉ approximation: y โ‰ˆ ๐*x * (I - ๐ƒ*kron(x,x))โปยน +# Precomputes the Padรฉ coefficient matrices from Taylor series coefficients +struct second_order_pade_solution + stochastic_steady_state::Vector{Float64} + ๐’โ‚::AbstractMatrix{Float64} # First order solution matrix (numerator coefficient) + ๐’โ‚‚::AbstractMatrix{Float64} # Second order solution matrix + ๐ƒ::AbstractMatrix{Float64} # Precomputed Padรฉ denominator coefficient matrix + ๐’โ‚ฬ‚::AbstractMatrix{Float64} # First order solution matrix for OBC (zeros if not needed) + ๐ƒฬ‚::AbstractMatrix{Float64} # Padรฉ denominator for OBC (zeros if not needed) +end + +# Struct to store precomputed Padรฉ matrices for third order +# Uses [2,1] matrix Padรฉ approximation +struct third_order_pade_solution + stochastic_steady_state::Vector{Float64} + ๐’โ‚::AbstractMatrix{Float64} # First order solution matrix + ๐’โ‚‚::AbstractMatrix{Float64} # Second order solution matrix + ๐’โ‚ƒ::AbstractMatrix{Float64} # Third order solution matrix + ๐ƒโ‚‚::AbstractMatrix{Float64} # Padรฉ denominator coefficient for quadratic term + ๐ƒโ‚ƒ::AbstractMatrix{Float64} # Padรฉ denominator coefficient for cubic term + ๐’โ‚ฬ‚::AbstractMatrix{Float64} # First order solution matrix for OBC (zeros if not needed) + ๐ƒโ‚‚ฬ‚::AbstractMatrix{Float64} # Padรฉ denominator for OBC quadratic (zeros if not needed) + ๐ƒโ‚ƒฬ‚::AbstractMatrix{Float64} # Padรฉ denominator for OBC cubic (zeros if not needed) +end + struct second_order_perturbation_solution # solution_matrix::SparseMatrixCSC{Float64} stochastic_steady_state::Vector{Float64} @@ -216,6 +242,8 @@ mutable struct perturbation pruned_second_order::second_order_perturbation_solution third_order::third_order_perturbation_solution pruned_third_order::third_order_perturbation_solution + second_order_pade::second_order_pade_solution + third_order_pade::third_order_pade_solution qme_solution::Matrix{Float64} second_order_solution::AbstractMatrix{Float64} third_order_solution::AbstractMatrix{Float64}