Skip to content

thorek1/MacroModelling.jl

Repository files navigation

MacroModelling.jl

Documentation: Documentation

codecov CI DOI Package Downloads

Author: Thore Kockerols (@thorek1)

MacroModelling.jl is a Julia package for developing and solving dynamic stochastic general equilibrium (DSGE) models.

These kinds of models describe the behaviour of a macroeconomy and are particularly suited for counterfactual analysis (economic policy evaluation) and exploring / quantifying specific mechanisms (academic research). Due to the complexity of these models, efficient numerical tools are required, as analytical solutions are often unavailable. MacroModelling.jl serves as a tool for handling the complexities involved, such as forward-looking expectations, nonlinearity, and high dimensionality.

The goal of this package is to reduce coding time and speed up model development by providing functions for working with discrete-time DSGE models. The user-friendly syntax, automatic variable declaration, and effective steady state solver facilitate fast prototyping of models. Furthermore, the package allows the user to work with nonlinear model solutions (up to third order (pruned) perturbation) and estimate the model using gradient based samplers (e.g. NUTS, or HMC). Currently, DifferentiableStateSpaceModels.jl is the only other package providing functionality to estimate using gradient based samplers but they use the start-of-period timing convention instead of the end-of-period timing convention used in most other packages. The target audience for the package includes central bankers, regulators, graduate students, and others working in academia with an interest in DSGE modelling.

As of now the package can:

  • parse a model written with user friendly syntax (variables are followed by time indices ...[2], [1], [0], [-1], [-2]..., or [x] for shocks)
  • (tries to) solve the model only knowing the model equations and parameter values (no steady state file needed; possibility to define custom steady state function)
  • calculate first, second, and third order (pruned) perturbation solutions using symbolic derivatives
  • handle occasionally binding constraints for linear and nonlinear solutions
  • calculate (generalised) impulse response functions, simulate the model, or do conditional forecasts for linear and nonlinear solutions
  • calibrate parameters using (non-stochastic) steady state relationships
  • calculate variance decompositions: unconditional decompositions at first order and at pruned second- and third-order, and conditional / forecast-error decompositions at first order. For pruned higher-order unconditional decompositions, the cross-shock interaction term can either be reported as a separate component or allocated across the individual shocks as marginal contributions (Shapley values)
  • compute shock decompositions of filtered data via the Kalman or inversion filter, including pruned second- and third-order solutions where the nonlinear interaction term can optionally be allocated across shocks via marginal contributions (Shapley values)
  • match model moments (also for pruned higher order solutions)
  • estimate the model on data (Kalman filter using first order perturbation) with gradient based samplers (e.g. NUTS, HMC), estimate nonlinear models using the inversion filter, or the filter-free joint likelihood
  • differentiate the model solution, loglikelihood (Kalman filter, inversion filter, or filter free joint loglikelihood), model moments, and steady state with respect to the parameters using forward-mode AD (ForwardDiff.jl) and reverse-mode AD (Mooncake.jl recommended; other ChainRules-compatible backends such as Zygote.jl also work via custom rrules)
  • modify a model after it has been defined — model and calibration equations can be updated, added, or removed in place (update_equations!, add_equation!, remove_equation!, and the *_calibration_equation! variants) without re-running the @model / @parameters macros. A chronological revision history is kept (get_revision_history) and the revised model can be saved to a Julia source file (write_julia_model_file); this mirrors the equation-revision workflow from TROLL

The package is not:

  • guaranteed to find the non-stochastic steady state (solving systems of nonlinear equations is an active area of research)
  • the fastest package around if there is already a fast way to find the NSSS (time to first plot is long, time to second plot (with new parameters) is very short)

For more details have a look at the documentation.

Speed

MacroModelling.jl is genuinely faster than Dynare at computing perturbation solutions, which makes it well suited for tasks where runtime matters most — in particular estimation.

For first-order solves, Dynare is most competitive when using compiled MATLAB mex files. MacroModelling.jl is still faster in that case, but the gap narrows as model size grows and the QZ decomposition starts to dominate the runtime; at that point performance is largely determined by the underlying BLAS/LAPACK rather than by the surrounding code.

For higher-order perturbation the gap widens substantially. MacroModelling.jl is typically close to an order of magnitude faster at second order, and around two orders of magnitude faster at third order (roughly 40x115x on the bundled third-order timings for Caldara_et_al_2012 and Gali_2015_chapter_3_nonlinear). Derivative construction (Jacobians and Hessians) shows even larger relative speedups — often 100x1000x — but because those operations take only microseconds in absolute terms for most models, they are not the main driver of end-to-end runtime. The speedups that actually change the user experience are in the solve steps themselves.

For full benchmarks across operating systems, CPU architectures, and models, see the Speed Benchmarks page.

Wall-clock user experience is a different story. MacroModelling.jl inherits Julia's just-in-time compilation cost: time-to-first-output can take a minute or more on a small model while the relevant functions are compiled. Once compiled, every subsequent call is fast. Dynare, being built on a compiled host - MATLAB, has the edge on the first call, but this advantage erodes as model size grows and disappears entirely as soon as functions are reused.

In practice: if the goal is a single output from a model run once, Dynare will deliver it sooner. For iterative work — changing parameters or equations interactively, running estimation, or anything that calls the solvers many times — MacroModelling.jl is the better fit, thanks to its interactive design, the speed of precompiled functions, and its compatibility with Julia's rich ecosystem of gradient-based samplers.

Getting started

Installation

MacroModelling.jl requires julia version 1.10 or higher and an IDE is recommended (e.g. VS Code with the julia extension).

Once set up MacroModelling.jl can be installed (and StatsPlots in order to plot) by typing the following in the Julia REPL:

using Pkg; Pkg.add(["MacroModelling", "StatsPlots"])

Optional extensions

MacroModelling.jl uses Julia's package extension mechanism to provide additional functionality when certain packages are loaded. Install the ones relevant for the intended workflow:

using Pkg; Pkg.add(["Turing", "Mooncake"])         # Bayesian estimation with gradient-based samplers
using Pkg; Pkg.add("ForwardDiff")                   # Forward-mode AD for derivatives of loglikelihood, solutions, IRFs, and moments
using Pkg; Pkg.add("Optim")                         # LBFGS for conditional forecasts; SAMIN for steady state solver tuning

Automatic differentiation backends:

  • Mooncake.jl (reverse-mode) is the recommended backend for gradient-based estimation with Turing.jl (NUTS, HMC). Custom ChainRules rrule definitions ensure efficient reverse-mode differentiation through all solvers and filters. Other ChainRules-compatible backends (e.g. Zygote.jl) also work through these same rrules.
  • ForwardDiff.jl (forward-mode) is supported via a package extension and provides ForwardDiff.jacobian / ForwardDiff.gradient compatibility for get_solution, get_irf, and get_statistics (steady state, mean, variance, standard deviation, covariance, correlation, autocorrelation) across all perturbation orders (first, second, third, and pruned variants), as well as the different likelihood estimators via get_loglikelihood (Kalman filter, inversion filter, and filter free).

Example

See below an implementation of a simple RBC model. You can find more detailed tutorials in the documentation.

using MacroModelling
import StatsPlots

@model RBC begin
    1  /  c[0] =/  c[1]) ** exp(z[1]) * k[0]^- 1) + (1 - δ))
    c[0] + k[0] = (1 - δ) * k[-1] + q[0]
    q[0] = exp(z[0]) * k[-1]^α
    z[0] = ρ * z[-1] + std_z * eps_z[x]
end;

@parameters RBC begin
    std_z = 0.01
    ρ = 0.2
    δ = 0.02
    α = 0.5
    β = 0.95
end;

plot_irf(RBC)

RBC IRF

Custom steady state function

By default, the package automatically solves for the non-stochastic steady state. For complex models, you can provide a custom steady state function to ensure efficient computation or in case the automatic solver fails:

using MacroModelling

@model RBC begin
    1  /  c[0] =/  c[1]) ** exp(z[1]) * k[0]^- 1) + (1 - δ))
    c[0] + k[0] = (1 - δ) * k[-1] + q[0]
    q[0] = exp(z[0]) * k[-1]^α
    z[0] = ρ * z[-1] + std_z * eps_z[x]
end;

function rbc_steady_state!(ss, params)
    std_z, rho, delta, alpha, beta = params

    k_ss = ((1 / beta - 1 + delta) / alpha)^(1 / (alpha - 1))
    q_ss = k_ss^alpha
    c_ss = q_ss - delta * k_ss
    z_ss = 0.0

    ss[1] = c_ss
    ss[2] = k_ss
    ss[3] = q_ss
    ss[4] = z_ss
end

@parameters RBC steady_state_function = rbc_steady_state! begin
    std_z = 0.01
    ρ = 0.2
    δ = 0.02
    α = 0.5
    β = 0.95
end;

Delayed parameter definition

Parameters do not need to be defined upfront in the @parameters block. You can define the model first and provide parameter values later when calling functions. This is useful for loading parameter values from external sources (e.g., CSV files).

using MacroModelling
import StatsPlots

@model RBC_delayed begin
    1  /  c[0] =/  c[1]) ** exp(z[1]) * k[0]^- 1) + (1 - δ))

    c[0] + k[0] = (1 - δ) * k[-1] + q[0]

    q[0] = exp(z[0]) * k[-1]^α

    z[0] = ρ * z[-1] + std_z * eps_z[x]
end;

@parameters RBC_delayed begin
end;

plot_irf(RBC_delayed, parameters = (:std_z => 0.01,  => 0.2,  => 0.02,  => 0.5,  => 0.95))

RBC IRF

Note: Calibration equations (using | syntax) and parameters defined as functions of other parameters must be declared in the @parameters block.

Steady state options:

  • Automatic solver (default) from the model equations.
  • Custom steady state function.

Parameter values can also be supplied later (delayed parameter definition) as illustrated above.

See the documentation for more details on the steady state.

Modifying a model after definition

Model and calibration equations can be edited in place after the @model / @parameters block has been evaluated, without having to re-declare the model. The update_equations!, add_equation!, and remove_equation! functions (plus their *_calibration_equation! counterparts) mutate the model object, invalidate cached solver results, and re-solve the non-stochastic steady state. Every change is appended to a revision log exposed via get_revision_history.

using MacroModelling

@model RBC begin
    1  /  c[0] =/  c[1]) ** exp(z[1]) * k[0]^- 1) + (1 - δ))
    c[0] + k[0] = (1 - δ) * k[-1] + q[0]
    q[0] = exp(z[0]) * k[-1]^α
    z[0] = ρ * z[-1] + std_z * eps_z[x]
end;

@parameters RBC begin
    std_z = 0.01
    ρ = 0.2
    δ = 0.02
    α = 0.5
    β = 0.95
end;

# Replace the technology-shock process with a more persistent one
update_equations!(RBC, :(z[0] = ρ * z[-1] + std_z * eps_z[x]),
                       :(z[0] = 0.9 * z[-1] + std_z * eps_z[x]))

# Append an auxiliary equation defining log output
add_equation!(RBC, :(log_q[0] = log(q[0])))

# Drop it again
remove_equation!(RBC, :(log_q[0] = log(q[0])))

# Inspect what has been changed
get_revision_history(RBC)

See the how-to guide on modifying models for details, and calibration-equation examples.

Models

The package contains the following models in the models folder:

Comparison with other packages

MacroModelling.jl dynare DSGE.jl dolo.py SolveDSGE.jl DifferentiableStateSpaceModels.jl StateSpaceEcon.jl IRIS RISE NBTOOLBOX gEcon GDSGE Taylor Projection
Host language julia MATLAB julia Python julia julia julia MATLAB MATLAB MATLAB R MATLAB MATLAB
Non-stochastic steady state solver symbolic or numerical solver of independent blocks; symbolic removal of variables redundant in steady state; inclusion of calibration equations in problem; custom steady state function can be provided numerical solver of independent blocks or user-supplied values/functions numerical solver of independent blocks or user-supplied values/functions numerical solver numerical solver or user supplied values/equations numerical solver of independent blocks or user-supplied values/functions numerical solver of independent blocks or user-supplied values/functions numerical solver of independent blocks or user-supplied values/functions user-supplied steady state file or numerical solver numerical solver; inclusion of calibration equations in problem
Automatic declaration of variables and parameters yes
Derivatives wrt parameters yes yes
Perturbation solution order 1, 2, 3 k 1 1, 2, 3 1, 2, 3 1, 2 1 1 1 to 5 1 1 1 to 5
Pruning yes yes yes yes
Automatic derivation of first order conditions yes
Occasionally binding constraints yes yes yes yes yes yes yes
Global solution yes yes yes
Estimation yes yes yes yes yes yes yes yes
Balanced growth path yes yes yes yes yes yes
Model input macro (julia) text file text file text file text file macro (julia) module (julia) text file text file text file text file text file text file
Timing convention end-of-period end-of-period end-of-period start-of-period start-of-period end-of-period end-of-period end-of-period end-of-period end-of-period start-of-period start-of-period

Bibliography

Andreasen, M. M., Fernández-Villaverde, J., and Rubio-Ramírez, J. F. (2018), "The pruned state-space system for non-linear DSGE models: Theory and empirical applications.", The Review of Economic Studies, 85.1, p. 1-49.

Durbin, J, and Koopman, S. J. (2012), "Time Series Analysis by State Space Methods, 2nd edn", Oxford University Press.

Levintal, O., (2017), "Fifth-Order Perturbation Solution to DSGE models", Journal of Economic Dynamics and Control, 80, p. 1---16.

Villemot, S., (2011), "Solving rational expectations models at first order: what Dynare does", Dynare Working Papers 2, CEPREMAP.

Questions and Contributions

If there are questions regarding the usage or unexpected behaviour is encountered please file an issue.

Contributions are very welcome, as are feature requests and suggestions. Please open an issue or even better become a contributor and create a pull request.

Packages

 
 
 

Contributors

Languages