Add balanced growth path support for first-order models - #304
Conversation
Allow models to be written in levels with non-stationary I(1) variables on a common balanced growth path (IRIS / semistructural QPM style), instead of hand-stationarizing into gaps and growth rates. Steady state uses the IRIS two-time-point augmentation: each variable gets a level and an additive per-period growth unknown `xᴳ`, and every steady-state equation is evaluated at two time origins so the 2N residuals pin the 2N (level, growth) unknowns. Growth-rate and cointegration identities then fall out automatically. This is auto-detected at parse time, so fully stationary models take an unchanged path. - parser: detect unit-root/non-stationary levels (cancelling level or varless collapsed law); when detected, build the 2N augmented (level+growth) SS system and classify growth symbols as SS unknowns (model_setup.jl, equation_processing.jl). - nsss solver: append growth unknowns to the solved SS vector; pass growths across solver blocks; anchor the free cointegrated trend directions to 0 via the SS Jacobian rank (zero incidence rows + dropzeros so the existing indeterminate->default path pins them); feed the residual cross-check the growth-inclusive solution. - options_and_caches: keep growth symbols out of the variable-reporting index. Verified on a random-walk-with-drift toy, a two-trend cointegration toy, and a full cointegrated reference QPM (steady levels and growths match the IRIS reference); stationary models are unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The implicit-function derivative of the non-stochastic steady state inverts the SS Jacobian ∂equations/∂(SS, pars). For levels/BGP models this augmented (level+growth) Jacobian is singular — free trend levels give zero columns and the two-time-point construction gives duplicate rows — so the LU solve failed and all parameter derivatives were silently returned as zero (e.g. ∂dx/∂g = 0 for the random-walk-with-drift toy where dx = g). Use the minimum-norm least-squares solution (pinv) for growth models in both the reverse-mode rrule (src/rrules.jl) and the forward-mode Dual path (ext/ForwardDiffExt.jl). It pins free-level sensitivities to 0 and resolves the consistent duplicate rows exactly, giving correct ∂SS/∂param through the growth unknowns. Gated on growth detection, so stationary models keep the fast LU path unchanged. Adds derivative assertions to the BGP test (∂dx/∂g = 1, ∂x/∂g = 0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a get_irf finiteness check to the random-walk-with-drift test, covering the first-order dynamic pipeline (policy function + IRFs) for a model with a unit root, mirroring the end-to-end validation on the full reference QPM. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first-order solution works in deviations from the constant anchored steady state, so levels output (get_irf/simulate with levels=true) left trending variables flat at their anchor instead of following the balanced growth path. Add the deterministic drift x_t = anchor + xᴳ·t to trending variables when returning levels: read each variable's solved growth xᴳ from the steady-state solution (bgp_growth_by_name; lead/lag auxiliaries inherit the base variable's growth) and add xᴳ·t per period in get_irf. Deviations (levels=false) are unchanged, and stationary models are unaffected (no growth symbols → no-op). Only the drift slope xᴳ is meaningful — the anchor is an arbitrary particular solution of the cointegrated level system. Verified: toy level drifts by g while its growth-rate variable stays at g and deviations carry no drift; reference QPM trending levels (l_cpi_c, l_gdp, l_rer, …) drift at exactly the IRIS reference steady_change rates while stationary levels (dl_*, ir_*) stay flat. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a "Balanced Growth Path (Models in Levels)" section to the steady-state docs explaining auto-detection, the level+growth steady state, trend anchoring, and drift in level paths, with a runnable random-walk-with-drift example. Flip the MacroModelling.jl cell in the README feature-comparison matrix to "yes (first order)". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Let users pin a trending variable's steady-state level with an in-block `x[ss] = expr` equation (an IRIS `!!`-style steady-state override), instead of relying on the automatic anchor-to-0 of each free cointegrated trend. The anchor constrains the level only; the growth stays pinned by the variable's dynamic law. - parser: extract `x[ss] = expr` equations before dynamic processing into `equations.ss_anchors` (RHS collapsed to steady-state form); register parameters referenced only in anchors so they enter the parameter vector. - nsss solver: force-anchor user-specified trend levels (then the rank pass covers any remaining free directions) and set them to the anchor expression in the indeterminate-variable step (parameters/constants resolved at solve time). Anchors are level-only and target the model's independent trends; for a linear model the dynamics are invariant to the anchor. Verified: `x[ss] = xbar` pins the level to the parameter while the growth still equals the drift, levels drift from the anchor, and stationary models / unanchored trends are unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Hi Lazar, This is really cool! I had a brief look at your PR and my only comment at this stage would be to include a more complex model, ideally a benchmark test case for this kind of problem. I will go through the codes in more detail in the coming weeks. Best, Thore |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #304 +/- ##
===========================================
- Coverage 88.05% 32.18% -55.87%
===========================================
Files 35 35
Lines 30243 29372 -871
===========================================
- Hits 26629 9453 -17176
- Misses 3614 19919 +16305 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
I will collect some more thoughts here as I go along looking at the PR. Suggestions:
using MacroModelling
@model RBC_baseline begin
c[0] ^ (-σ) = β * c[1] ^ (-σ) * (α * z[1] * (k[0] / l[1]) ^ (α - 1) + 1 - δ)
ψ * c[0] ^ σ / (1 - l[0]) = w[0]
k[0] = (1 - δ) * k[-1] + i[0]
y[0] = c[0] + i[0] + g[0]
y[0] = z[0] * k[-1] ^ α * l[0] ^ (1 - α)
w[0] = y[0] * (1 - α) / l[0]
r[0] = y[0] * α * 4 / k[-1]
z[0] = (1 - ρᶻ) + ρᶻ * z[-1] + σᶻ * ϵᶻ[x]
g[0] = (1 - ρᵍ) * ḡ + ρᵍ * g[-1] + σᵍ * ϵᵍ[x]
end
@parameters RBC_baseline begin
σᶻ = 0.066
σᵍ = .104
σ = 1
α = 1/3
i_y = 0.25
k_y = 10.4
ρᶻ = 0.97
ρᵍ = 1.01 # unit root
g_y = 0.2038
ḡ | ḡ = g_y * y[ss]
δ = i_y / k_y
β = 1 / (α / k_y + (1 - δ))
ψ | l[ss] = 1/3
end
get_solution(RBC_baseline) |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Hi Thore,
I wanted to be able to work with models with levels and BGP, so I took a stab at it.
This PR adds first-order support for such models with auto-detection in the equation parser.
I am a relative novice to open source development/contribution process and learn many things as I go (with Claude's help). Feedback would be appreciated. As a reference, I used IRIS-Toolbox implementation, so the main idea is that every variable gets a level and an additive growth unknown x^G. Since we get 2N unknowns, each steady-state equation is evaluated at two time origins (t, t+1) to double the equation count.
This PR includes:
∂SS/∂paraminverts a singular augmented Jacobian (free trend levels give zero columns; the two-time-point construction gives duplicate rows), fixed with the minimum-norm least-squares solution (pinv).get_solution/get_irfwork end-to-end for levels models.x_t = anchor + xᴳ·tis added to trending variables inget_irf/simulatewhenlevels = true. Unconditional variances of trending levels areNaN.x[ss] = exprlets one to pin a trend's level explicitly (otherwise automatic anchoring is to 0).Best,
Lazar