|
1 | | -using Revise |
| 1 | +""" |
| 2 | +Test script for solving variables across stochastic shocks using the dynamic equations function. |
| 3 | +
|
| 4 | +This script demonstrates: |
| 5 | +1. Loading a model and computing steady states |
| 6 | +2. Generating Sobol quasi-random sequences for shocks |
| 7 | +3. Evaluating dynamic equations across multiple shock realizations |
| 8 | +4. Solving for variables (past, present, future) using NonlinearSolve.jl |
| 9 | +""" |
| 10 | + |
2 | 11 | using MacroModelling |
3 | 12 | using QuasiMonteCarlo |
4 | | -using Optimization |
5 | | -# using OptimizationOptimJL |
6 | | -using OptimizationNLopt |
7 | | -import SpecialFunctions: erfinv |
| 13 | +using NonlinearSolve |
| 14 | +using SpecialFunctions |
8 | 15 | using Statistics |
9 | | -# using Zygote |
10 | 16 |
|
11 | 17 | # Define a simple RBC model |
12 | | -# @model RBC begin |
13 | | -# 1 / c[0] = (β / c[1]) * (α * exp(z[1]) * k[0]^(α - 1) + (1 - δ)) |
14 | | -# c[0] + k[0] = (1 - δ) * k[-1] + q[0] |
15 | | -# q[0] = exp(z[0]) * k[-1]^α |
16 | | -# z[0] = ρ * z[-1] + std_z * eps_z[x] |
17 | | -# end |
18 | | - |
19 | | -# @parameters RBC begin |
20 | | -# std_z = 0.01 |
21 | | -# ρ = 0.2 |
22 | | -# δ = 0.02 |
23 | | -# α = 0.5 |
24 | | -# β = 0.95 |
25 | | -# end |
26 | | - |
27 | | -include("./models/Gali_2015_chapter_3_nonlinear.jl") |
28 | | - |
29 | | -m = Gali_2015_chapter_3_nonlinear |
30 | | -# m.dyn_equations[8] |
| 18 | +@model RBC begin |
| 19 | + 1 / c[0] = (β / c[1]) * (α * exp(z[1]) * k[0]^(α - 1) + (1 - δ)) |
| 20 | + c[0] + k[0] = (1 - δ) * k[-1] + q[0] |
| 21 | + q[0] = exp(z[0]) * k[-1]^α |
| 22 | + z[0] = ρ * z[-1] + std_z * eps_z[x] |
| 23 | +end |
| 24 | + |
| 25 | +@parameters RBC begin |
| 26 | + std_z = 0.01 |
| 27 | + ρ = 0.2 |
| 28 | + δ = 0.02 |
| 29 | + α = 0.5 |
| 30 | + β = 0.95 |
| 31 | +end |
| 32 | + |
| 33 | +println("Solving model...") |
| 34 | +solve!(RBC) |
| 35 | + |
31 | 36 | # Get steady states |
32 | | -SS_non_stochastic = SS(m, derivatives = false) |> collect |
33 | | -SS_stochastic = SSS(m, derivatives = false) |> collect |
| 37 | +println("Computing steady states...") |
| 38 | +SS_non_stochastic = get_steady_state(RBC, stochastic = false) |
| 39 | +SS_stochastic = get_steady_state(RBC, stochastic = true) |
| 40 | + |
| 41 | +println("Non-stochastic steady state:") |
| 42 | +println(SS_non_stochastic) |
| 43 | +println("\nStochastic steady state:") |
| 44 | +println(SS_stochastic) |
| 45 | + |
| 46 | +# Get auxiliary indices for the steady state input |
| 47 | +aux_idx = RBC.solution.perturbation.auxiliary_indices |
| 48 | +SS_for_func = SS_non_stochastic[aux_idx.dyn_ss_idx] |
| 49 | + |
| 50 | +# Number of variables and shocks |
| 51 | +n_vars = length(RBC.var) |
| 52 | +n_shocks = length(RBC.exo) |
| 53 | +n_dyn_eqs = length(RBC.dyn_equations) |
| 54 | +n_calib_eqs = length(RBC.calibration_equations) |
| 55 | +n_total_eqs = n_dyn_eqs + n_calib_eqs |
| 56 | + |
| 57 | +println("\nModel dimensions:") |
| 58 | +println(" Variables: ", n_vars) |
| 59 | +println(" Shocks: ", n_shocks) |
| 60 | +println(" Dynamic equations: ", n_dyn_eqs) |
| 61 | +println(" Calibration equations: ", n_calib_eqs) |
| 62 | +println(" Total equations: ", n_total_eqs) |
34 | 63 |
|
35 | | -# SS_for_func = SS_non_stochastic # |
| 64 | +# Generate Sobol sequence for shocks |
| 65 | +n_draws = 100 |
| 66 | +println("\nGenerating ", n_draws, " Sobol draws for shocks...") |
36 | 67 |
|
| 68 | +# Generate Sobol sequence in [0,1] |
| 69 | +shock_draws_uniform = QuasiMonteCarlo.sample(n_draws, n_shocks, SobolSample()) |
37 | 70 |
|
| 71 | +# Convert to normal distribution using inverse error function |
| 72 | +normal_draws = @. sqrt(2) * erfinv(2 * shock_draws_uniform - 1) |
38 | 73 |
|
39 | | -# histogram(vec(shock_draws), bins=30, title="Histogram of Uniform [0,1] Sobol Draws", xlabel="Value", ylabel="Frequency") |
| 74 | +# Standardize to zero mean and unit variance |
| 75 | +normal_draws .-= mean(normal_draws, dims=2) |
| 76 | +normal_draws ./= Statistics.std(normal_draws, dims=2) |
40 | 77 |
|
41 | | -# histogram(vec(@. sqrt(2) * erfinv(2 * shock_draws - 1)), bins=30, title="Histogram of Normal Sobol Draws", xlabel="Value", ylabel="Frequency") |
| 78 | +println("Shock draws shape: ", size(normal_draws)) |
| 79 | +println("Shock draws mean: ", mean(normal_draws, dims=2)) |
| 80 | +println("Shock draws std: ", Statistics.std(normal_draws, dims=2)) |
42 | 81 |
|
43 | 82 | # Calibration parameters |
44 | | -calib_params = zeros(length(m.calibration_equations_parameters)) |
| 83 | +calib_params = zeros(length(RBC.calibration_equations_parameters)) |
45 | 84 |
|
46 | | -# Define objective function: sum of squared residuals across all shock draws |
47 | | -function objective(vars_flat, shock_draws, model, SS_non_stochastic, calib_params) |
48 | | - # n_vars = length(model.var) |
| 85 | +# Define residual function that returns per-equation average residuals |
| 86 | +# This is compatible with NonlinearSolve.jl |
| 87 | +function residual_function!(residual_avg, vars_flat, p) |
| 88 | + shock_draws, model, SS_for_func, calib_params = p |
| 89 | + |
49 | 90 | n_draws = size(shock_draws, 2) |
50 | | - |
| 91 | + n_vars = length(model.var) |
| 92 | + |
51 | 93 | # Unpack variables: [past; present; future] |
52 | | - past = vars_flat#[1:n_vars] |
53 | | - present = vars_flat#[n_vars+1:2*n_vars] |
54 | | - future = vars_flat#[2*n_vars+1:3*n_vars] |
| 94 | + past = vars_flat[1:n_vars] |
| 95 | + present = vars_flat[n_vars+1:2*n_vars] |
| 96 | + future = vars_flat[2*n_vars+1:3*n_vars] |
55 | 97 |
|
56 | 98 | n_eqs = length(model.dyn_equations) + length(model.calibration_equations) |
57 | | - |
58 | | - residual = zeros(eltype(vars_flat), n_eqs) |
59 | | - |
60 | | - # Sum squared residuals across all shock draws |
61 | | - total_loss = 0.0 |
| 99 | + residual_temp = zeros(n_eqs) |
| 100 | + |
| 101 | + # Initialize average residuals to zero |
| 102 | + fill!(residual_avg, 0.0) |
| 103 | + |
| 104 | + # Sum residuals across all shock draws |
62 | 105 | for i in 1:n_draws |
63 | 106 | shocks = shock_draws[:, i] |
64 | 107 |
|
65 | 108 | # Evaluate dynamic equations |
66 | | - get_dynamic_residuals(residual, model.parameter_values, calib_params, |
67 | | - past, present, future, SS_non_stochastic, shocks, model) |
| 109 | + get_dynamic_residuals(residual_temp, model.parameter_values, calib_params, |
| 110 | + past, present, future, SS_for_func, shocks, model) |
68 | 111 |
|
69 | | - # Add squared residuals |
70 | | - total_loss += sum(residual.^2) |
| 112 | + # Accumulate residuals |
| 113 | + residual_avg .+= residual_temp |
71 | 114 | end |
72 | 115 |
|
73 | | - return total_loss / n_draws # Average loss |
| 116 | + # Average over draws |
| 117 | + residual_avg ./= n_draws |
| 118 | + |
| 119 | + return nothing |
74 | 120 | end |
75 | 121 |
|
76 | | -n_eqs = length(m.dyn_equations) + length(m.calibration_equations) |
77 | | -residual = zeros(n_eqs) |
78 | | - |
79 | | -initial_vars = SS_stochastic # vcat(SS_stochastic, SS_stochastic, SS_stochastic) |
80 | | - |
81 | | - |
82 | | - |
83 | | -n_shocks = length(m.exo) |
84 | | - |
85 | | -# Generate Sobol sequence for shocks |
86 | | -n_draws = 1000 |
87 | | - |
88 | | - |
89 | | -# Generate Sobol sequence |
90 | | -shock_draws = QuasiMonteCarlo.sample(n_draws, n_shocks, SobolSample()) |
91 | | - |
92 | | -normal_draws = @. sqrt(2) * erfinv(2 * shock_draws - 1) |
93 | | - |
94 | | -normal_draws .-= mean(normal_draws, dims=2) |
95 | | -normal_draws ./= Statistics.std(normal_draws, dims=2) |
96 | | - |
97 | | -mean(normal_draws, dims=2) |
98 | | -Statistics.std(normal_draws, dims=2) |
99 | | - |
100 | | - |
101 | | -objective(initial_vars, normal_draws, m, SS_non_stochastic, calib_params) |
102 | | - |
103 | | -# Set up optimization problem |
104 | | -optf = OptimizationFunction((x, p) -> objective(x, normal_draws, m, SS_non_stochastic, calib_params), AutoForwardDiff()) |
105 | | -prob = OptimizationProblem(optf, SS_stochastic) |
106 | | - |
107 | | -# Solve using BFGS |
108 | | -sol = solve(prob, NLopt.LD_LBFGS()) |
109 | | -# solPR = solve(prob, NLopt.LN_PRAXIS()) |
110 | | -solNM = solve(prob, NLopt.LN_NELDERMEAD()) |
111 | | -# solBO = solve(prob, NLopt.LN_BOBYQA()) |
112 | | -# solCO = solve(prob, NLopt.LN_COBYLA()) |
| 122 | +# Initial guess: use stochastic steady state for all time periods |
| 123 | +println("\nSetting up nonlinear problem...") |
| 124 | +initial_vars = vcat(SS_stochastic, SS_stochastic, SS_stochastic) |
113 | 125 |
|
114 | | -objective!(residual, sol.u, shock_draws, m, SS_non_stochastic, calib_params) |
115 | | -objective!(residual, initial_vars, shock_draws, m, SS_non_stochastic, calib_params) |
| 126 | +# Package parameters for the residual function |
| 127 | +params = (normal_draws, RBC, SS_for_func, calib_params) |
116 | 128 |
|
| 129 | +# Test initial residuals |
| 130 | +residual_test = zeros(n_total_eqs) |
| 131 | +residual_function!(residual_test, initial_vars, params) |
| 132 | +println("Initial residual norm: ", norm(residual_test)) |
| 133 | +println("Initial max|residual|: ", maximum(abs.(residual_test))) |
117 | 134 |
|
118 | | -ststst = SSS(m, derivatives = false, algorithm = :third_order) |
| 135 | +# Set up nonlinear problem |
| 136 | +println("\nSolving nonlinear system...") |
| 137 | +println("This may take a moment...") |
119 | 138 |
|
120 | | -ststst .= sol.u |
| 139 | +prob = NonlinearProblem(residual_function!, initial_vars, params) |
121 | 140 |
|
| 141 | +# Solve using Newton-Raphson with automatic differentiation |
| 142 | +sol = solve(prob, NewtonRaphson(; autodiff = AutoFiniteDiff())) |
122 | 143 |
|
123 | | -ststst_orig = SSS(m, derivatives = false, algorithm = :third_order) |
| 144 | +println("\nSolution complete!") |
| 145 | +println("Solution return code: ", sol.retcode) |
| 146 | +println("Final residual norm: ", norm(sol.resid)) |
| 147 | +println("Final max|residual|: ", maximum(abs.(sol.resid))) |
124 | 148 |
|
| 149 | +# Extract solved variables |
| 150 | +solved_past = sol.u[1:n_vars] |
| 151 | +solved_present = sol.u[n_vars+1:2*n_vars] |
| 152 | +solved_future = sol.u[2*n_vars+1:3*n_vars] |
125 | 153 |
|
126 | | -println("\nOptimization complete!") |
127 | | -println("Final objective value: ", sol.objective) |
128 | | -println("Initial objective value: ", objective(initial_vars, shock_draws, RBC, SS_for_func, calib_params)) |
129 | | -println("Improvement: ", (1 - sol.objective / objective(initial_vars, shock_draws, RBC, SS_for_func, calib_params)) * 100, "%") |
130 | | - |
131 | | -# Extract optimized variables |
132 | | -opt_past = sol.u[1:n_vars] |
133 | | -opt_present = sol.u[n_vars+1:2*n_vars] |
134 | | -opt_future = sol.u[2*n_vars+1:3*n_vars] |
135 | | - |
136 | | -println("\nOptimized variables:") |
137 | | -println("Past: ", opt_past) |
138 | | -println("Present: ", opt_present) |
139 | | -println("Future: ", opt_future) |
| 154 | +println("\nSolved variables:") |
| 155 | +println("Past: ", solved_past) |
| 156 | +println("Present: ", solved_present) |
| 157 | +println("Future: ", solved_future) |
140 | 158 |
|
141 | 159 | println("\nComparison with steady states:") |
142 | 160 | for (i, var) in enumerate(RBC.var) |
143 | 161 | println(" ", var, ":") |
144 | 162 | println(" Non-stochastic SS: ", SS_non_stochastic[i]) |
145 | 163 | println(" Stochastic SS: ", SS_stochastic[i]) |
146 | | - println(" Optimized (past): ", opt_past[i]) |
147 | | - println(" Optimized (pres): ", opt_present[i]) |
148 | | - println(" Optimized (fut): ", opt_future[i]) |
| 164 | + println(" Solved (past): ", solved_past[i]) |
| 165 | + println(" Solved (present): ", solved_present[i]) |
| 166 | + println(" Solved (future): ", solved_future[i]) |
149 | 167 | end |
150 | 168 |
|
| 169 | +# Evaluate residuals at the solution for a few shock draws |
| 170 | +println("\nPer-draw residuals at solution for first 5 shock draws:") |
| 171 | +residual = zeros(n_total_eqs) |
| 172 | +for i in 1:min(5, n_draws) |
| 173 | + shocks = normal_draws[:, i] |
| 174 | + get_dynamic_residuals(residual, RBC.parameter_values, calib_params, |
| 175 | + solved_past, solved_present, solved_future, SS_for_func, shocks, RBC) |
| 176 | + println("Draw ", i, ": max|residual| = ", maximum(abs.(residual)), |
| 177 | + ", mean|residual| = ", sum(abs.(residual))/length(residual)) |
| 178 | +end |
| 179 | + |
| 180 | +println("\nScript completed successfully!") |
| 181 | + |
151 | 182 | # Evaluate residuals at the optimal point for a few shock draws |
152 | 183 | println("\nResiduals at optimal point for first 5 shock draws:") |
153 | 184 | residual = zeros(n_total_eqs) |
|
0 commit comments