Skip to main content

dsgelab

tests license: MIT

A general toolkit for solving DSGE models in Python. A model is declared once, as lists of state, jump and shock names and one function that returns the equilibrium residuals. The package computes the steady state, the first-order solution, impulse responses, moments, second-order risk corrections, nonlinear perfect-foresight transitions and calibration on targets from that declaration.

The package is built so that the stochastic block and the deterministic transition are the same model. One way applied work fails is a perturbation solution built on one set of equations beside a transition path solved by a hand-written fixed point on another set, similar to the first but not identical. Here both call the same residual function, so the equations behind them cannot differ.

pip install dsgelab
pip install "dsgelab[symbolic]"     # adds text equations with exact derivatives (sympy)

Requires Python 3.11 or later, numpy and scipy. Pandas is not needed.

Quick start

import dsgelab as dsg
from dsgelab.examples import rbc

model, params, guess = rbc.build()
sol = dsg.solve(model, params, guess=guess)

print(sol.summary())
irf = sol.irf("eps_a", size=0.01, periods=40)      # per cent deviations
mom = sol.moments(cov={"eps_a": 0.007})            # std, corr, autocorr

A model of your own takes three lists and a function. The parameters can be any object with attributes, here a dataclass.

import numpy as np
from dataclasses import dataclass
from dsgelab import Model, solve

@dataclass
class P:
    alpha: float = 0.36
    beta:  float = 0.99
    delta: float = 1.0
    gamma: float = 1.0
    rho:   float = 0.95

def equations(nxt, cur, eps, p):
    y  = cur.a * cur.k ** p.alpha
    y1 = nxt.a * nxt.k ** p.alpha
    return [
        nxt.k - ((1 - p.delta) * cur.k + y - cur.c),                       # resource
        cur.c ** -p.gamma - p.beta * nxt.c ** -p.gamma                     # Euler
            * (p.alpha * y1 / nxt.k + 1 - p.delta),
        np.log(nxt.a) - p.rho * np.log(cur.a) - eps.eps_a,                 # technology
    ]

model = Model(states=["k", "a"], jumps=["c"], shocks=["eps_a"], equations=equations)
sol = solve(model, P(), guess={"k": 0.2, "a": 1.0, "c": 0.36})

Variables are addressed by name, in cur for period t and nxt for t+1. When variables are read from a vector by position, swapping one index for another raises no error, and the program solves a different model from the one intended. Reading each variable by name rules that mistake out.

The user guide goes from the declaration to second order, and the site has runnable examples in English and Portuguese.

Terms

  • A state (predetermined variable) has its value for period t fixed before the innovation of period t arrives, as capital and the technology level do. A jump (non-predetermined variable), such as consumption, moves within the period in response to that innovation.
  • Klein/QZ is Klein's method for the linearised model, which orders the generalised Schur (QZ) decomposition of the matrix pair (B, A) of the canonical form below.
  • The risk correction is the second-order term that shifts the decision rules in proportion to the variance of future shocks. At first order it is zero.
  • Pruning drops, when the second-order rule is simulated, the terms of order higher than two that the recursion would otherwise accumulate, so that simulated paths do not diverge.

What is in the box

Capability Entry point
Steady state, with positivity and outer anchors steady_state
First-order solution (Klein/QZ, Blanchard–Kahn enforced) solve
Impulse responses, simulation, moments, variance decomposition FirstOrderSolution
Second-order perturbation and risk corrections solve_second_order
Third-order perturbation, partial (the cubic block and the risk slope) solve_third_order
Global solution by Chebyshev collocation, up to three states solve_projection
Nonlinear perfect-foresight transitions, announced policy and shock paths perfect_foresight
Perfect foresight with expectation errors (news, surprises) perfect_foresight_with_expectation_errors
Ramsey optimal policy under commitment (timeless), validated against Dynare ramsey_model
Optimal policy under discretion; optimal simple rules, on moments or on welfare discretionary_policy, osr
Kalman filter (exact diffuse start) and smoother; measurement error; missing data state_space, kalman_filter, kalman_smoother
Bayesian estimation: mode, Laplace, Metropolis-Hastings, SMC, R-hat, ESS, marginal likelihood; maximum likelihood EstimationProblem, posterior_mode, metropolis_hastings, smc
Shock decomposition (also real-time), forecasts with bands, conditional forecasts shock_decomposition, forecast, the method FirstOrderSolution.conditional_forecast
Welfare at second order, conditional and unconditional; consumption equivalent welfare, consumption_equivalent
Occasionally binding constraints (OccBin), any number of them, surprise shocks OccbinModel, occbin_simulate
Extended path, deterministic and stochastic (sparse or full tree, hybrid correction) extended_path
Models in levels: declared trends with Dynare's balanced-growth check; the growth path from_mod, balanced_growth
Read a Dynare .mod file, macros and leads/lags included from_mod
Export a model to Dynare and to MacroModelling.jl to_dynare, to_macromodelling
Calibration on targets, with an identification diagnostic; calibration equations inside the model calibrate, target_jacobian, from_equations(calibration_equations=)
Closed-form steady states, checked before use; homotopy for hard steady states analytic_steady_state, steady_state(homotopy=)
Model diagnostics (Dynare's model_diagnostics) and independent checks diagnose, dsgelab.diagnostics
Text equations with exact analytic derivatives, lags and leads of any order dsgelab.symbolic
Several agents' optimisation problems in one equilibrium assemble

Timing convention

eps is the innovation dated t+1, unknown at t, with E_t eps_{t+1} = 0. An AR(1) is written as the residual

def equations(nxt, cur, eps, p):
    return [nxt.a - p.rho * cur.a - eps.eps_a]

and the resulting law of motion is k_{t+1} = P k_t + Q eps_{t+1}. Treating the innovation as known at t instead makes the model deterministic within the period, and every second-order risk correction then comes out zero without a warning. Of the package's conventions, this is the only one it cannot check in a user's model.

Two checks run on every model without being requested. solve raises BlanchardKahnError when the Blanchard–Kahn condition fails, and the steady state is accepted only when it zeroes the residuals. Three others take one call each by the user. check_identity tests an identity such as Walras's law, sol.accuracy() reports the order of accuracy of a solution, and target_jacobian checks whether calibration targets identify the free parameters, which calibrate does not check by itself.

Canonical form

A E_t x_{t+1} = B x_t + C eps_{t+1},   x_t = [k_t ; u_t]
u_t     = F k_t
k_{t+1} = P k_t + Q eps_{t+1}

with k the predetermined variables (states) and u the non-predetermined ones (jumps). The Blanchard–Kahn condition requires the number of stable generalised eigenvalues of the matrix pair (B, A) to equal the number of states. When it fails, the equilibrium is indeterminate or does not exist, and solve raises BlanchardKahnError instead of returning F and P.

Documentation

Questions and bug reports go to the issue tracker.

Heutel environmental-policy example

docs/heutel.md documents the first-order port of Heutel's (2012) MATLAB replication scripts, including the optimal tax and the tax and quota rules with lagged information. The port is checked against the original algebra, run in Octave, and against impulse responses and moments solved independently in Dynare.

from dsgelab.examples import heutel
heutel.fetch()          # once per machine; needs an internet connection
sol = heutel.solve("tax")
irf = sol.irf("eps_a", size=0.01, periods=100)

The planner script's sensitivity calibration fails the admissible steady-state check, so the Table 2 baseline has to be requested explicitly, with heutel.solve("planner", heutel.Params()). Without it, heutel.solve("planner") raises heutel.SteadyStateError. The documentation lists the differences between the supplied code and the published paper.

The port's scalar algebra is translated from Heutel's MATLAB files, which carry no licence, so it is not distributed with the package. heutel.fetch() downloads the public copy from Georgia State University's repository, checks each file against the SHA-256 of the files the port was validated on, and writes the translation to ~/.cache/dsgelab/heutel (or to DSGELAB_HEUTEL_DIR).

Verification

Each feature is validated against an independent reference where one exists, which may be another implementation, a closed form or a published result. A few rest on internal tests only, namely autocorrelations, variance decomposition, pruning, the second-order generalised impulse response, announced parameter paths and the Blanchard–Kahn check, and the export to MacroModelling.jl has no round trip. The checks come in three layers.

The first layer is the test suite (pytest), which compares the output with answers known analytically. It has 148 tests. Of these, 65 need the symbolic extra (sympy) and 15 the Heutel files from heutel.fetch(), and without those files a run gives 137 passed and 11 skipped. Among the closed forms it checks are the following.

  • The RBC with log utility and full depreciation has the solution P = [[α, 1], [0, ρ]] and F = [α, 1].
  • The New Keynesian example is determinate under a Taylor rule with φπ = 1.5 and indeterminate with φπ = 0.8, where solve raises BlanchardKahnError.
  • On a lognormal AR(1), the second-order coefficient h_ss equals σ², so the risk correction in the decision rule, ½·h_ss = σ²/2, is the Jensen term.
  • The perfect-foresight transition reproduces the closed-form path k_{t+1} = αβ k_t^α.
  • On two independent AR(1) processes and one forward-looking equation, the shock loading is the identity. The textbook formula read off the QZ treats every equation as holding ex post and gives a dense loading here, which is why klein solves for the loading on the rows that hold ex post (design notes).
  • Halving the displacement in accuracy() divides the first-order residual by about four and the second-order residual by about eight.

The suite runs from a clone of the repository.

git clone https://github.com/GustavoHM/dsgelab && cd dsgelab
pip install -e ".[symbolic,test]" && pytest

The second layer, in validation/, runs Dynare's own example and test .mod files in Octave with Dynare, and one calibration in MacroModelling.jl. Each result is compared with dsgelab's, for steady states, impulse responses, moments, optimal policy, perfect foresight, estimation, OccBin, the extended path, balanced growth and diagnostics. The same folder checks the port of Heutel (2012) against Heutel's own code. bash validation/run_all.sh runs all of it, and CI runs it weekly.

The third layer is one comparison on an applied model from an unpublished paper by the author. That model is not public, so the comparison cannot be rerun from the repository. Of the three layers, the second is the one in which both the models and the reference software come from other authors.

Known limitations

This section lists what the package does not do yet. The roadmap says when each item is planned.

  • Third order is partial. The cubic block and the risk slope (g_ssk and h_ssk) are implemented. Dynare's full decision-rule tensor, with the innovation in every monomial, and the stochastic steady state are planned for 0.7. The tensors are dense, so their size grows with the cube of the number of variables and shocks, and sparse tensors for large models are planned for the same version.
  • Estimation is linear. The likelihood comes from the Kalman filter on the first-order solution. There is no particle or inversion filter, so no estimation at second or third order, and no gradient-based samplers (HMC/NUTS).
  • No identification analysis, global sensitivity, or method of moments (Dynare's +identification, +gsa, +mom), and no HP- or band-pass-filtered theoretical moments.
  • Global methods are limited to Chebyshev collocation with up to three states. There is no value or time iteration, endogenous grid, sparse grid, or Tauchen/Rouwenhorst discretisation.
  • No Markov regime switching, heterogeneous agents, BVAR or DSGE-VAR.
  • No plotting. Results are arrays and dictionaries, and plotting is left to the user.
  • The extended path's hybrid correction stops at second order.
  • Trends can be declared only in a .mod file. from_equations does not accept them yet.
  • from_mod reads the model and ignores the script. Commands such as stoch_simul, estimation or identification are skipped, and their options are not applied. In the model block, the constructs the reader cannot translate (@#include, @#for, varexo_det, leads of a shock, expectation, diff, adl, external functions) are refused with an error that quotes the line containing them. Coverage is validated on the Dynare examples and tests listed in validation/.

cgelab, by the same author, solves computable general equilibrium models. Its module cgelab.bridge uses dsgelab for the intertemporal part of a model.

Citing

If the package contributed to a published result, cite it as any other methodological input, with the version, because a number that reproduces only under one version cannot be reproduced without knowing which. CITATION.cff holds the same metadata in the format GitHub reads.

Moreira, G. H. (2026). dsgelab: A general toolkit for solving DSGE models in Python (Version 0.5.0) [Computer software]. https://github.com/GustavoHM/dsgelab

@software{dsgelab_2026,
  author  = {Moreira, Gustavo Hoffmann},
  title   = {{dsgelab}: a general toolkit for solving DSGE models in Python},
  year    = {2026},
  version = {0.5.0},
  license = {MIT},
  url     = {https://github.com/GustavoHM/dsgelab},
  note    = {Python package}
}

A DOI will be added with the first release archived on Zenodo.

Metadata

Release files for dsgelab 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dsgelab 0.5.0
File Size Uploaded
dsgelab-0.5.0.tar.gz 238.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dsgelab 0.5.0
File Interpreter ABI Platform
dsgelab-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 405.5 kB

Release files / dsgelab-0.5.0.tar.gz

Download URL dsgelab-0.5.0.tar.gz
Size 238.2 kB
Tags Source
SHA-256 checksum
How to use checksums
3f4ce1fca3330444c6de779517feade662dcfb9c95b3846b03d8237adebfc75d
BLAKE2b-256 checksum
How to use checksums
ea7fbbc16a9a3b7e4587dbac2fb1680e6d18ef830bd6240f40d95d43ff23be6b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / dsgelab-0.5.0-py3-none-any.whl

Download URL dsgelab-0.5.0-py3-none-any.whl
Size 167.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
930298f77ab4399855136e61d6ee0e926e31d58e74f74012318108ddaedb04f9
BLAKE2b-256 checksum
How to use checksums
9fac9ee6fa80ede3dab306d1ce8382136da0233cd4c418732089278875b90696
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page