dsgelab 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. dsgelab-0.5.0/CHANGELOG.md +235 -0
  2. dsgelab-0.5.0/CITATION.cff +23 -0
  3. dsgelab-0.5.0/LICENSE +21 -0
  4. dsgelab-0.5.0/MANIFEST.in +5 -0
  5. dsgelab-0.5.0/PKG-INFO +332 -0
  6. dsgelab-0.5.0/README.md +298 -0
  7. dsgelab-0.5.0/pyproject.toml +58 -0
  8. dsgelab-0.5.0/setup.cfg +4 -0
  9. dsgelab-0.5.0/src/dsgelab/__init__.py +129 -0
  10. dsgelab-0.5.0/src/dsgelab/bgp.py +141 -0
  11. dsgelab-0.5.0/src/dsgelab/blocks.py +304 -0
  12. dsgelab-0.5.0/src/dsgelab/calibration.py +208 -0
  13. dsgelab-0.5.0/src/dsgelab/cli.py +44 -0
  14. dsgelab-0.5.0/src/dsgelab/diagnostics.py +279 -0
  15. dsgelab-0.5.0/src/dsgelab/dynare.py +1541 -0
  16. dsgelab-0.5.0/src/dsgelab/estimate.py +276 -0
  17. dsgelab-0.5.0/src/dsgelab/estimation.py +719 -0
  18. dsgelab-0.5.0/src/dsgelab/examples/__init__.py +5 -0
  19. dsgelab-0.5.0/src/dsgelab/examples/_heutel_source.py +209 -0
  20. dsgelab-0.5.0/src/dsgelab/examples/heutel.py +210 -0
  21. dsgelab-0.5.0/src/dsgelab/examples/nk.py +55 -0
  22. dsgelab-0.5.0/src/dsgelab/examples/rbc.py +92 -0
  23. dsgelab-0.5.0/src/dsgelab/export.py +276 -0
  24. dsgelab-0.5.0/src/dsgelab/extended_path.py +355 -0
  25. dsgelab-0.5.0/src/dsgelab/foresight.py +443 -0
  26. dsgelab-0.5.0/src/dsgelab/kalman.py +609 -0
  27. dsgelab-0.5.0/src/dsgelab/klein.py +252 -0
  28. dsgelab-0.5.0/src/dsgelab/linearize.py +252 -0
  29. dsgelab-0.5.0/src/dsgelab/model.py +285 -0
  30. dsgelab-0.5.0/src/dsgelab/occbin.py +363 -0
  31. dsgelab-0.5.0/src/dsgelab/optimal.py +866 -0
  32. dsgelab-0.5.0/src/dsgelab/priors.py +168 -0
  33. dsgelab-0.5.0/src/dsgelab/projection.py +237 -0
  34. dsgelab-0.5.0/src/dsgelab/py.typed +0 -0
  35. dsgelab-0.5.0/src/dsgelab/secondorder.py +402 -0
  36. dsgelab-0.5.0/src/dsgelab/solution.py +491 -0
  37. dsgelab-0.5.0/src/dsgelab/solve.py +52 -0
  38. dsgelab-0.5.0/src/dsgelab/steady.py +366 -0
  39. dsgelab-0.5.0/src/dsgelab/symbolic.py +396 -0
  40. dsgelab-0.5.0/src/dsgelab/thirdorder.py +370 -0
  41. dsgelab-0.5.0/src/dsgelab/welfare.py +226 -0
  42. dsgelab-0.5.0/src/dsgelab.egg-info/PKG-INFO +332 -0
  43. dsgelab-0.5.0/src/dsgelab.egg-info/SOURCES.txt +71 -0
  44. dsgelab-0.5.0/src/dsgelab.egg-info/dependency_links.txt +1 -0
  45. dsgelab-0.5.0/src/dsgelab.egg-info/entry_points.txt +2 -0
  46. dsgelab-0.5.0/src/dsgelab.egg-info/requires.txt +8 -0
  47. dsgelab-0.5.0/src/dsgelab.egg-info/top_level.txt +1 -0
  48. dsgelab-0.5.0/tests/conftest.py +19 -0
  49. dsgelab-0.5.0/tests/test_assemble.py +61 -0
  50. dsgelab-0.5.0/tests/test_bgp.py +104 -0
  51. dsgelab-0.5.0/tests/test_blocks.py +76 -0
  52. dsgelab-0.5.0/tests/test_calibration.py +66 -0
  53. dsgelab-0.5.0/tests/test_diagnose.py +84 -0
  54. dsgelab-0.5.0/tests/test_dynare_import.py +151 -0
  55. dsgelab-0.5.0/tests/test_estimate.py +57 -0
  56. dsgelab-0.5.0/tests/test_estimation.py +206 -0
  57. dsgelab-0.5.0/tests/test_expectation_errors.py +130 -0
  58. dsgelab-0.5.0/tests/test_export.py +65 -0
  59. dsgelab-0.5.0/tests/test_extended_path.py +91 -0
  60. dsgelab-0.5.0/tests/test_first_order.py +85 -0
  61. dsgelab-0.5.0/tests/test_foresight.py +64 -0
  62. dsgelab-0.5.0/tests/test_heutel.py +115 -0
  63. dsgelab-0.5.0/tests/test_klein.py +88 -0
  64. dsgelab-0.5.0/tests/test_model_checks.py +59 -0
  65. dsgelab-0.5.0/tests/test_occbin.py +104 -0
  66. dsgelab-0.5.0/tests/test_optimal.py +153 -0
  67. dsgelab-0.5.0/tests/test_policy.py +161 -0
  68. dsgelab-0.5.0/tests/test_projection.py +30 -0
  69. dsgelab-0.5.0/tests/test_secondorder.py +82 -0
  70. dsgelab-0.5.0/tests/test_steady_tools.py +50 -0
  71. dsgelab-0.5.0/tests/test_symbolic.py +74 -0
  72. dsgelab-0.5.0/tests/test_thirdorder.py +105 -0
  73. dsgelab-0.5.0/validation/heutel/reference.json +7514 -0
@@ -0,0 +1,235 @@
1
+ # Changelog
2
+
3
+ All notable changes to `dsgelab`. Versions follow [semantic versioning](https://semver.org/);
4
+ before 1.0, a minor version may add to the API and a patch version only fixes bugs.
5
+
6
+ ## 0.5.0 — 2026-10-18
7
+
8
+ The roadmap's milestone 0.5, the public release. No new solver features.
9
+
10
+ ### Added
11
+
12
+ - `py.typed`: the package declares its type annotations (PEP 561).
13
+ - CI tests the built wheel on Linux, macOS and Windows with Python 3.11 to 3.14, and
14
+ installs the sdist and tests it from its own tree. The site examples run in CI, and
15
+ `twine check --strict` passes on both artefacts. `DSGELAB_TEST_INSTALLED=1` makes the
16
+ suite import the installed package instead of `src/`.
17
+ - `validation/run_all.sh` runs the whole external validation and summarises which
18
+ comparisons passed. `validation/setup_octave_packages.sh` installs the Octave packages
19
+ at the versions the comparison used, checked by sha256, and `validation/macromodelling/`
20
+ is a Julia project with MacroModelling.jl pinned. A weekly CI job runs all three.
21
+ - A release workflow that publishes by trusted publishing, to TestPyPI first, tests what
22
+ TestPyPI serves, and only then publishes to PyPI.
23
+ - `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, and issue and pull-request templates. The
24
+ README has a section of known limitations.
25
+ - `.github/workflows/pages.yml` publishes the site to GitHub Pages, pointing the links to
26
+ `docs/` at the files on GitHub. It is skipped while the repository is private.
27
+ - The site in English, with the Portuguese version under `site/pt-br/`. The examples of
28
+ both versions run.
29
+ - `validation/heutel/fetch.sh` downloads Heutel's MATLAB files from the public copy at
30
+ Georgia State University's repository and checks them; the six `.m` files are
31
+ byte-identical to the ones the port was validated on.
32
+ - `dsgelab.examples.heutel.fetch()` downloads the same public copy, checks every file
33
+ by SHA-256, and writes the translated algebra of the port to a cache directory
34
+ (`~/.cache/dsgelab/heutel`, or `DSGELAB_HEUTEL_DIR`). Without it, `heutel.solve`
35
+ raises `HeutelSourceMissing` naming the call to make, and the Heutel tests are
36
+ skipped.
37
+
38
+ ### Changed
39
+
40
+ - The validation scripts print their reports in English (`s.s.` for steady state,
41
+ `moments`, `max relative deviation`, ...), with the same columns and numbers.
42
+ `compare_estimation.py --no-mh` and `compare_policy.py --no-dynare` are the new flag
43
+ names, and `--sem-mh` and `--sem-dynare` still work.
44
+ - The roadmap is in English (`ROADMAP.md`) and in Portuguese (`ROADMAP.pt-BR.md`).
45
+ - Examples, documentation, tests and validation scripts import the package as
46
+ `import dsgelab as dsg`. The former alias `dk` came from the old name, dsgekit.
47
+ - The site links to cgelab, whose `cgelab.bridge` uses dsgelab for the intertemporal part of
48
+ a model.
49
+ - The one comparison made on a model that is not public (the applied DSGE of an unpublished
50
+ paper) is marked on the site as not reproducible from the repository.
51
+ - The translated algebra of the Heutel port (`dsgelab/examples/_heutel_algebra.py`) is
52
+ no longer distributed, because it is a translation of Heutel's MATLAB files, which
53
+ carry no licence. `heutel.fetch()` generates it on the user's machine, and
54
+ `validation/heutel/translate.py` is gone.
55
+ - The calibration example on the site and in the user guide is replaced by one whose
56
+ targets identify the free parameters (`target_jacobian` reports `identified=True`). The
57
+ former example had a target that held identically, and `calibrate` returned different
58
+ answers from different starting points.
59
+ - The validation figures quoted on the site match the current run of
60
+ `validation/run_all.sh`. The README, `CONTRIBUTING.md`, the roadmap and the API
61
+ reference are corrected where they described the checks, the validation layers or the
62
+ API inaccurately.
63
+ - Python 3.11 or later is required. Python 3.10 is dropped, because SciPy 1.15.3, the
64
+ last release for it, does not load on macOS 27.
65
+ - Error messages, the labels of derived first-order conditions ("FOC for control c",
66
+ "Euler equation for state k", "constraint i: ...") and the header comments of files
67
+ written by `to_dynare` and `to_macromodelling` are in English. The exported model text
68
+ is unchanged.
69
+ - The external validation runs on Dynare 7.2, with Octave 11.3.0 and the Octave packages
70
+ statistics 2.0.0 and datatypes 1.5.0. Every comparison prints the same numbers as on
71
+ Dynare 7.1, and the Heutel errors move in their last digit.
72
+
73
+ ### Fixed
74
+
75
+ - `SecondOrderSolution.simulate`, `mean_correction` and `ergodic_mean`, `ThirdOrderSolution.simulate`
76
+ and `solve_third_order`, when given a covariance other than the one the solution was computed at,
77
+ combined risk terms (`g_ss`, `h_ss`) of the old covariance with shocks or a state covariance of the
78
+ new one. On the RBC with the standard deviation doubled, the mean correction of capital came out
79
+ 5.8e-05 instead of 2.4e-03. They now solve again at the covariance asked for, through the new
80
+ `SecondOrderSolution.at_covariance`.
81
+ - The sdist now carries `validation/heutel/reference.json`, which its own test suite
82
+ reads.
83
+ - `validation/compare_diagnostics.py` honours `OCTAVE` and `DYNARE_MATLAB` and looks in
84
+ the same places as the other scripts, instead of only in Homebrew's.
85
+ - `modelo.mod` and `modelo.jl`, written by a site example, had been committed at the
86
+ repository root; they are gone, and `tools/check_site_examples.py` now runs the
87
+ examples in a temporary directory.
88
+ - Package metadata uses the SPDX license expression (`license = "MIT"`), which
89
+ setuptools will require from 2027.
90
+
91
+ ## 0.4.0 — 2026-10-11
92
+
93
+ The roadmap's milestone 0.4: nonlinearities and the declaration of the model. Each item is
94
+ validated against Dynare 7.1 on Dynare's own test files, against MacroModelling.jl, or
95
+ against a closed form.
96
+
97
+ ### Added
98
+
99
+ - `OccbinModel`, `occbin_simulate`, `OccbinResult`: occasionally binding constraints by
100
+ the piecewise-linear method of Guerrieri and Iacoviello (2015), as in Dynare's
101
+ `+occbin`, with any number of constraints, time-varying rules found backwards, Dynare's
102
+ regime update with `curb_retrench`, surprise shocks with re-planning, and the horizon
103
+ extended while a constraint binds at its end. `from_mod` reads `bind=`/`relax=` equation tags,
104
+ `occbin_constraints`, `shocks(surprise)` and the `occbin_setup`/`occbin_solver`
105
+ options; `ModFile.occbin_simulate` runs them. Against Dynare's `borrcon.mod` (one
106
+ constraint) and `dynrbc.mod` (two) to 2e-15, with identical regime histories.
107
+ - `extended_path`, `quadrature_nodes`, `ExtendedPath`: the extended path (Fair and
108
+ Taylor, 1983) and the stochastic extended path (Adjemian and Juillard) of any order,
109
+ on Dynare's sparse or full tree of Gauss–Hermite nodes, with the hybrid second-order
110
+ risk correction, and `ModFile.extended_path`. Against Dynare's `burnside.mod` in seven
111
+ variants to 1.1e-11, and `linearmodel0.mod` to 2e-15.
112
+ - Declared trends in `from_mod`: `trend_var`, `log_trend_var`, `var(deflator=)` and
113
+ `var(log_deflator=)` are detrended as Dynare does, and an equation that still depends
114
+ on the level of a trend is refused (Dynare's balanced-growth test, checked at several
115
+ points). `fs2000_nonstationary.mod` from Dynare's tests joins `compare_import`.
116
+ - `balanced_growth`, `BalancedGrowth`, `ModFile.balanced_growth`: Dynare's `bgp.write`
117
+ problem, solved by Levenberg–Marquardt with restarts away from degenerate paths. The
118
+ residual matches Dynare's generated `bgpfun` to 2.2e-16 on its three tests.
119
+ - `diagnose`, Dynare's `model_diagnostics`. It reports variables absent at the current
120
+ date, collinear relations of the static Jacobian with their variables and equations,
121
+ redundant equations of the dynamic Jacobian, and unit roots, and gives the same report
122
+ as Dynare on a model broken on purpose.
123
+ - `analytic_steady_state`, `SteadyStateError`, `from_equations(steady_state_model=)` and
124
+ `ModFile.steady_state(analytic=True)`. A closed-form steady state is used only after it
125
+ zeroes the model's residuals, and refused otherwise, as Dynare refuses it.
126
+ - Calibration equations inside the model, MacroModelling's `k[ss]/q[ss] = 2.5 | β`:
127
+ `from_equations(calibration_equations=)` and `steady_state(calibrate=)`. Against
128
+ MacroModelling.jl to 4e-15.
129
+ - `steady_state(homotopy=)` and `homotopy_setup` in `from_mod`: continuation from a
130
+ parameter value where the model is easy, with adaptive steps.
131
+ - `assemble`: several agents' optimisation problems and the closing equations in one
132
+ `Model`, refusing a variable chosen twice and renaming clashing multipliers.
133
+ - `from_equations` accepts lags and leads of any order, each carried by an auxiliary
134
+ listed in `model.auxiliary`; a guess without the auxiliaries gets them from their
135
+ variables.
136
+
137
+ ### Changed
138
+
139
+ - `perfect_foresight(jacobian="auto")` is the default: the stacked Jacobian is assembled
140
+ sparse from the model's exact derivatives when it has them (forward differences
141
+ otherwise); ex-post states use them too.
142
+ - `klein` uses the real ordered QZ by default, the complex one as a fallback: SciPy 1.18's
143
+ complex reordering loses up to 1e-8 on a pencil with a zero root and refuses one with
144
+ several infinite roots.
145
+ - Moments and variance decompositions of a model with a unit root mask the
146
+ nonstationary entries (`nan`) instead of failing; the Lyapunov equation is solved on
147
+ the stationary block of an ordered Schur form.
148
+ - `from_mod` refuses a name declared twice.
149
+
150
+ ## 0.3.0 — 2026-10-11
151
+
152
+ The first version that carries the roadmap's milestones 0.2 (optimal policy, welfare,
153
+ perfect foresight with news) and 0.3 (estimation, smoothing, decomposition,
154
+ forecasting), each item validated against Dynare 7.1 on Dynare's own files.
155
+
156
+ ### Changed
157
+
158
+ - Renamed from `dsgekit` to `dsgelab`: `dsgekit` is taken on PyPI by an unrelated
159
+ project. The import name, distribution name, CLI entry point and URLs move together.
160
+
161
+ ### Added
162
+
163
+ - `state_space`, `kalman_filter`, `kalman_smoother`: the state space of a first-order
164
+ solution, with observables in levels or logs (Dynare's `loglinear` with `logdata`),
165
+ measurement errors and missing data; the filter starts stationary roots at their
166
+ unconditional law and unit roots with the exact diffuse initialisation (Koopman and Durbin, 2000, unit
167
+ roots first in the Schur form, as Dynare does). The likelihood matches Dynare's on
168
+ `fs2000.mod` to 6e-12 and its `diffuse_filter` to 2.5e-12, and the smoother matches to
169
+ 1e-14.
170
+ - `EstimationProblem`, `posterior_mode`, `maximum_likelihood`, `metropolis_hastings`,
171
+ `smc`, `prior_predictive`, `model_probabilities`, `posterior_forecast`, `rhat`,
172
+ `effective_sample_size`: estimation, with Dynare's priors by mean and standard
173
+ deviation (`prior`), the Laplace and modified-harmonic-mean marginal likelihoods,
174
+ and `ModFile.estimation_problem` reading `estimated_params`, `varobs` and the options
175
+ of `estimation(...)`. Validated on Dynare's `fs2000.mod`: prior density to 4e-13, a
176
+ mode at least as high as Dynare's, Laplace by the same formula to 2e-13, and posterior
177
+ means within 3 Monte Carlo standard errors of Dynare's (largest 2.9).
178
+ - `shock_decomposition`, `realtime_shock_decomposition`, `forecast`: against Dynare's
179
+ commands of the same names to 5e-15 (forecast bands to 6e-9).
180
+ - `FirstOrderSolution.conditional_forecast(..., controlled=, levels=)`: Dynare's
181
+ `conditional_forecast` with `controlled_varexo`, to 1.6e-13 on its own test file.
182
+ - `perfect_foresight_with_expectation_errors` and `News`: agents re-plan at each date of
183
+ news from the states they have reached; constant or shrinking horizon. Validated
184
+ against Dynare's `pfwee.mod` and `pfwee_constant_sim_length.mod` to 3e-12.
185
+ - `perfect_foresight(..., shocks=)` validated against Dynare's `rbc_det1`–`3`
186
+ (anticipated shocks, initial conditions) to 1e-13.
187
+ - `from_mod`: exogenous variables carry their level (`EXO_SS_e`, from `initval`), may
188
+ appear one period ahead, and the deterministic `shocks` block and `histval` are read;
189
+ `ModFile.deterministic`, `ModFile.news`, `ModFile.initial_states` and
190
+ `read_expectation_errors_csv` turn them into arguments for the solvers.
191
+ - `osr(..., objective=, discount=)`: an optimal simple rule on welfare (minus
192
+ unconditional welfare at second order), Dynare's `osr` with `planner_objective`.
193
+ - `discretionary_policy` and `DiscretionSolution`: optimal policy under discretion
194
+ (Dennis, 2007, as Dynare implements it), with IRFs, moments and welfare. Validated
195
+ against Galí's closed form and Dynare's `tests/discretionary_policy/dennis_1.mod`.
196
+ - `osr` and `OSRResult`: optimal simple rules on a quadratic loss in unconditional
197
+ moments, Dynare's `optim_weights` convention. On Dynare's `osr_example.mod` the loss
198
+ matches Dynare's at its optimum to 2.6e-16, and the optimiser finds a lower one, which
199
+ Dynare's own objective confirms.
200
+ - `welfare`, `welfare_model`, `consumption_equivalent`: second-order welfare,
201
+ conditional and unconditional, and for Ramsey models with the lagged multipliers at
202
+ zero. Matches Dynare's `evaluate_planner_objective` to 1.4e-12 for Ramsey and to
203
+ 6.1e-10 under discretion.
204
+ - `from_mod` reads `planner_objective`, `ramsey_model`, `discretionary_policy`,
205
+ `osr_params`, `optim_weights` and `osr_params_bounds`; `ramsey_model` and
206
+ `discretionary_policy` accept the resulting `ModFile`. `PrivateBlock` holds a
207
+ private sector with instruments left open.
208
+ - `ramsey_model`, `ramsey_steady_state`, `ramsey_multipliers`: the Ramsey planner under
209
+ commitment, timeless perspective, derived symbolically from the private-sector
210
+ equations and a period objective. Validated against Galí's targeting rule and closed
211
+ form, and against Dynare's `ramsey_model` on three models
212
+ (`validation/compare_ramsey.py`).
213
+ - `from_mod`, `ModFile`: read a Dynare `.mod` file, with macros, model-local variables,
214
+ `steady_state()`, and leads and lags of any order. Validated on eight `.mod` files read
215
+ unchanged, five of them from Dynare's `examples/` (`validation/compare_import.py`).
216
+ - `solve(..., qz_criterium=)` and `klein(..., qz_criterium=)`: Dynare's convention that
217
+ admits a unit root.
218
+ - `perfect_foresight(..., shocks=)`: a path of innovations, anticipated from the start.
219
+ - `SecondOrderSolution.irf`: the generalised impulse response of the pruned rule.
220
+ - `FirstOrderSolution.historical_decomposition` and `conditional_forecast`.
221
+ - `symbolic.parse_system` and `symbolic.model_from_expressions`: the symbolic front end
222
+ split in two, so that derived equations reach the model builder without being printed
223
+ and re-parsed.
224
+
225
+ ## 0.2.0
226
+
227
+ - Third-order risk slopes, linear estimation (Kalman filter, posterior mode,
228
+ random-walk Metropolis) and Chebyshev projection.
229
+ - Standalone package, separate from the research repository.
230
+
231
+ ## 0.1.0
232
+
233
+ - Steady state, first-order (Klein/QZ) and second-order perturbation, perfect foresight,
234
+ calibration on targets, symbolic front end, first-order-condition derivation,
235
+ exporters to Dynare and MacroModelling.jl.
@@ -0,0 +1,23 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software in a publication, please cite it as below."
3
+ type: software
4
+ title: "dsgelab: a general toolkit for solving DSGE models in Python"
5
+ abstract: >-
6
+ Steady state, Klein/QZ first-order solution, second-order perturbation and a partial third order (the cubic block and the risk slope), Chebyshev projection, perfect foresight with expectation errors, optimal policy (Ramsey, discretion, optimal simple rules) and welfare, Kalman filtering and smoothing, Bayesian estimation, shock decompositions and forecasts, reading Dynare .mod files, and export to Dynare and MacroModelling.jl. Validated against Dynare, MacroModelling.jl and closed-form solutions.
7
+ authors:
8
+ - family-names: "Moreira"
9
+ given-names: "Gustavo Hoffmann"
10
+ email: "gustavo@gakusai.net"
11
+ orcid: "https://orcid.org/0000-0003-0827-2521"
12
+ version: "0.5.0"
13
+ date-released: "2026-10-18"
14
+ license: MIT
15
+ # The DOI is filled in with the first release archived on Zenodo, which makes the citation stable,
16
+ # since a repository URL is not an archivable reference.
17
+ repository-code: "https://github.com/GustavoHM/dsgelab"
18
+ # doi: "10.5281/zenodo.XXXXXXX"
19
+ keywords:
20
+ - "DSGE"
21
+ - "perturbation"
22
+ - "rational expectations"
23
+ - "Python"
dsgelab-0.5.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gustavo Hoffmann Moreira
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ # The sdist carries the test suite, and test_heutel.py reads its reference
2
+ # values from validation/. Nothing else under validation/ is needed to test.
3
+ include CHANGELOG.md CITATION.cff LICENSE README.md
4
+ recursive-include tests *.py
5
+ include validation/heutel/reference.json
dsgelab-0.5.0/PKG-INFO ADDED
@@ -0,0 +1,332 @@
1
+ Metadata-Version: 2.4
2
+ Name: dsgelab
3
+ Version: 0.5.0
4
+ Summary: Solve, simulate and estimate DSGE models in Python: perturbation to second order (and a partial third order), perfect foresight, OccBin, optimal policy, Bayesian estimation and Dynare .mod import, validated against Dynare.
5
+ Author-email: Gustavo Hoffmann Moreira <gustavo@gakusai.net>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/GustavoHM/dsgelab
8
+ Project-URL: Documentation, https://github.com/GustavoHM/dsgelab/tree/main/docs
9
+ Project-URL: Repository, https://github.com/GustavoHM/dsgelab
10
+ Project-URL: Issues, https://github.com/GustavoHM/dsgelab/issues
11
+ Keywords: dsge,perturbation,rational expectations,klein,macroeconomics
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
22
+ Classifier: Intended Audience :: Science/Research
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.11
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: numpy>=1.24
28
+ Requires-Dist: scipy>=1.10
29
+ Provides-Extra: symbolic
30
+ Requires-Dist: sympy>=1.12; extra == "symbolic"
31
+ Provides-Extra: test
32
+ Requires-Dist: pytest>=7; extra == "test"
33
+ Dynamic: license-file
34
+
35
+ # dsgelab
36
+
37
+ [![tests](https://github.com/GustavoHM/dsgelab/actions/workflows/tests.yml/badge.svg)](https://github.com/GustavoHM/dsgelab/actions/workflows/tests.yml)
38
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/GustavoHM/dsgelab/blob/main/LICENSE)
39
+
40
+ A general toolkit for solving DSGE models in Python. A model is declared once, as lists of
41
+ state, jump and shock names and one function that returns the equilibrium residuals. The
42
+ package computes the steady state, the first-order solution, impulse responses, moments,
43
+ second-order risk corrections, nonlinear perfect-foresight transitions and calibration on
44
+ targets from that declaration.
45
+
46
+ The package is built so that the stochastic block and the deterministic transition are the
47
+ *same model*. One way applied work fails is a perturbation solution built on one set of
48
+ equations beside a transition path solved by a hand-written fixed point on another set, similar
49
+ to the first but not identical. Here both call the same residual function, so the equations
50
+ behind them cannot differ.
51
+
52
+ ```
53
+ pip install dsgelab
54
+ pip install "dsgelab[symbolic]" # adds text equations with exact derivatives (sympy)
55
+ ```
56
+
57
+ Requires Python 3.11 or later, numpy and scipy. Pandas is not needed.
58
+
59
+ ## Quick start
60
+
61
+ ```python
62
+ import dsgelab as dsg
63
+ from dsgelab.examples import rbc
64
+
65
+ model, params, guess = rbc.build()
66
+ sol = dsg.solve(model, params, guess=guess)
67
+
68
+ print(sol.summary())
69
+ irf = sol.irf("eps_a", size=0.01, periods=40) # per cent deviations
70
+ mom = sol.moments(cov={"eps_a": 0.007}) # std, corr, autocorr
71
+ ```
72
+
73
+ A model of your own takes three lists and a function. The parameters can be any object with
74
+ attributes, here a dataclass.
75
+
76
+ ```python
77
+ import numpy as np
78
+ from dataclasses import dataclass
79
+ from dsgelab import Model, solve
80
+
81
+ @dataclass
82
+ class P:
83
+ alpha: float = 0.36
84
+ beta: float = 0.99
85
+ delta: float = 1.0
86
+ gamma: float = 1.0
87
+ rho: float = 0.95
88
+
89
+ def equations(nxt, cur, eps, p):
90
+ y = cur.a * cur.k ** p.alpha
91
+ y1 = nxt.a * nxt.k ** p.alpha
92
+ return [
93
+ nxt.k - ((1 - p.delta) * cur.k + y - cur.c), # resource
94
+ cur.c ** -p.gamma - p.beta * nxt.c ** -p.gamma # Euler
95
+ * (p.alpha * y1 / nxt.k + 1 - p.delta),
96
+ np.log(nxt.a) - p.rho * np.log(cur.a) - eps.eps_a, # technology
97
+ ]
98
+
99
+ model = Model(states=["k", "a"], jumps=["c"], shocks=["eps_a"], equations=equations)
100
+ sol = solve(model, P(), guess={"k": 0.2, "a": 1.0, "c": 0.36})
101
+ ```
102
+
103
+ Variables are addressed by *name*, in `cur` for period `t` and `nxt` for `t+1`. When
104
+ variables are read from a vector by position, swapping one index for another raises no error,
105
+ and the program solves a different model from the one intended. Reading each variable by name
106
+ rules that mistake out.
107
+
108
+ The [user guide](https://github.com/GustavoHM/dsgelab/blob/main/docs/guide.md) goes from the
109
+ declaration to second order, and the [site](https://gustavohm.github.io/dsgelab/) has runnable
110
+ examples in English and Portuguese.
111
+
112
+ ## Terms
113
+
114
+ - A *state* (predetermined variable) has its value for period `t` fixed before the innovation
115
+ of period `t` arrives, as capital and the technology level do. A *jump* (non-predetermined
116
+ variable), such as consumption, moves within the period in response to that innovation.
117
+ - *Klein/QZ* is Klein's method for the linearised model, which orders the generalised Schur
118
+ (QZ) decomposition of the matrix pair `(B, A)` of the canonical form below.
119
+ - The *risk correction* is the second-order term that shifts the decision rules in proportion
120
+ to the variance of future shocks. At first order it is zero.
121
+ - *Pruning* drops, when the second-order rule is simulated, the terms of order higher than two
122
+ that the recursion would otherwise accumulate, so that simulated paths do not diverge.
123
+
124
+ ## What is in the box
125
+
126
+ | Capability | Entry point |
127
+ |---|---|
128
+ | Steady state, with positivity and outer anchors | `steady_state` |
129
+ | First-order solution (Klein/QZ, Blanchard–Kahn enforced) | `solve` |
130
+ | Impulse responses, simulation, moments, variance decomposition | `FirstOrderSolution` |
131
+ | Second-order perturbation and risk corrections | `solve_second_order` |
132
+ | Third-order perturbation, partial (the cubic block and the risk slope) | `solve_third_order` |
133
+ | Global solution by Chebyshev collocation, up to three states | `solve_projection` |
134
+ | Nonlinear perfect-foresight transitions, announced policy and shock paths | `perfect_foresight` |
135
+ | Perfect foresight with expectation errors (news, surprises) | `perfect_foresight_with_expectation_errors` |
136
+ | Ramsey optimal policy under commitment (timeless), validated against Dynare | `ramsey_model` |
137
+ | Optimal policy under discretion; optimal simple rules, on moments or on welfare | `discretionary_policy`, `osr` |
138
+ | Kalman filter (exact diffuse start) and smoother; measurement error; missing data | `state_space`, `kalman_filter`, `kalman_smoother` |
139
+ | Bayesian estimation: mode, Laplace, Metropolis-Hastings, SMC, R-hat, ESS, marginal likelihood; maximum likelihood | `EstimationProblem`, `posterior_mode`, `metropolis_hastings`, `smc` |
140
+ | Shock decomposition (also real-time), forecasts with bands, conditional forecasts | `shock_decomposition`, `forecast`, the method `FirstOrderSolution.conditional_forecast` |
141
+ | Welfare at second order, conditional and unconditional; consumption equivalent | `welfare`, `consumption_equivalent` |
142
+ | Occasionally binding constraints (OccBin), any number of them, surprise shocks | `OccbinModel`, `occbin_simulate` |
143
+ | Extended path, deterministic and stochastic (sparse or full tree, hybrid correction) | `extended_path` |
144
+ | Models in levels: declared trends with Dynare's balanced-growth check; the growth path | `from_mod`, `balanced_growth` |
145
+ | Read a Dynare `.mod` file, macros and leads/lags included | `from_mod` |
146
+ | Export a model to Dynare and to MacroModelling.jl | `to_dynare`, `to_macromodelling` |
147
+ | Calibration on targets, with an identification diagnostic; calibration equations inside the model | `calibrate`, `target_jacobian`, `from_equations(calibration_equations=)` |
148
+ | Closed-form steady states, checked before use; homotopy for hard steady states | `analytic_steady_state`, `steady_state(homotopy=)` |
149
+ | Model diagnostics (Dynare's `model_diagnostics`) and independent checks | `diagnose`, `dsgelab.diagnostics` |
150
+ | Text equations with exact analytic derivatives, lags and leads of any order | `dsgelab.symbolic` |
151
+ | Several agents' optimisation problems in one equilibrium | `assemble` |
152
+
153
+ ## Timing convention
154
+
155
+ `eps` is the innovation dated `t+1`, unknown at `t`, with `E_t eps_{t+1} = 0`. An AR(1) is
156
+ written as the residual
157
+
158
+ ```python
159
+ def equations(nxt, cur, eps, p):
160
+ return [nxt.a - p.rho * cur.a - eps.eps_a]
161
+ ```
162
+
163
+ and the resulting law of motion is `k_{t+1} = P k_t + Q eps_{t+1}`. Treating the innovation as
164
+ known at `t` instead makes the model deterministic within the period, and every second-order
165
+ risk correction then comes out zero without a warning. Of the package's conventions, this is
166
+ the only one it cannot check in a user's model.
167
+
168
+ Two checks run on every model without being requested. `solve` raises `BlanchardKahnError`
169
+ when the Blanchard–Kahn condition fails, and the steady state is accepted only when it zeroes
170
+ the residuals. Three others take one call each by the user. `check_identity` tests an
171
+ identity such as Walras's law, `sol.accuracy()` reports the order of accuracy of a solution,
172
+ and `target_jacobian` checks whether calibration targets identify the free parameters, which
173
+ `calibrate` does not check by itself.
174
+
175
+ ## Canonical form
176
+
177
+ ```
178
+ A E_t x_{t+1} = B x_t + C eps_{t+1}, x_t = [k_t ; u_t]
179
+ u_t = F k_t
180
+ k_{t+1} = P k_t + Q eps_{t+1}
181
+ ```
182
+
183
+ with `k` the predetermined variables (states) and `u` the non-predetermined ones (jumps). The
184
+ Blanchard–Kahn condition requires the number of stable generalised eigenvalues of the matrix
185
+ pair `(B, A)` to equal the number of states. When it fails, the equilibrium is indeterminate
186
+ or does not exist, and `solve` raises `BlanchardKahnError` instead of returning `F` and `P`.
187
+
188
+ ## Documentation
189
+
190
+ - [`docs/whitepaper.md`](https://github.com/GustavoHM/dsgelab/blob/main/docs/whitepaper.md), the mathematics the solvers implement
191
+ - [`docs/guide.md`](https://github.com/GustavoHM/dsgelab/blob/main/docs/guide.md), the user guide, from the declaration to second order
192
+ - [`docs/api.md`](https://github.com/GustavoHM/dsgelab/blob/main/docs/api.md), the API reference
193
+ - [`docs/design-notes.md`](https://github.com/GustavoHM/dsgelab/blob/main/docs/design-notes.md), the numerical decisions and the errors each one avoids
194
+ - [`docs/from-scripts.md`](https://github.com/GustavoHM/dsgelab/blob/main/docs/from-scripts.md), porting an existing research script
195
+ - [`docs/comparison.md`](https://github.com/GustavoHM/dsgelab/blob/main/docs/comparison.md), how this package compares with the existing ones, and what it lacks
196
+ - [`ROADMAP.md`](https://github.com/GustavoHM/dsgelab/blob/main/ROADMAP.md), what is still missing, and when it is planned
197
+ - [`CONTRIBUTING.md`](https://github.com/GustavoHM/dsgelab/blob/main/CONTRIBUTING.md), how to report a problem and what a contribution needs
198
+ - [The site](https://gustavohm.github.io/dsgelab/), with runnable examples in English and Portuguese (source in [`site/`](https://github.com/GustavoHM/dsgelab/tree/main/site))
199
+
200
+ Questions and bug reports go to the [issue tracker](https://github.com/GustavoHM/dsgelab/issues).
201
+
202
+ ## Heutel environmental-policy example
203
+
204
+ [`docs/heutel.md`](https://github.com/GustavoHM/dsgelab/blob/main/docs/heutel.md) documents the
205
+ first-order port of Heutel's (2012) MATLAB replication scripts, including the optimal tax and
206
+ the tax and quota rules with lagged information. The port is checked against the original
207
+ algebra, run in Octave, and against impulse responses and moments solved independently in
208
+ Dynare.
209
+
210
+ ```python
211
+ from dsgelab.examples import heutel
212
+ heutel.fetch() # once per machine; needs an internet connection
213
+ sol = heutel.solve("tax")
214
+ irf = sol.irf("eps_a", size=0.01, periods=100)
215
+ ```
216
+
217
+ The planner script's sensitivity calibration fails the admissible steady-state check, so the
218
+ Table 2 baseline has to be requested explicitly, with `heutel.solve("planner", heutel.Params())`.
219
+ Without it, `heutel.solve("planner")` raises `heutel.SteadyStateError`. The documentation lists
220
+ the differences between the supplied code and the published paper.
221
+
222
+ The port's scalar algebra is translated from Heutel's MATLAB files, which carry no licence, so
223
+ it is not distributed with the package. `heutel.fetch()` downloads the public copy from Georgia
224
+ State University's repository, checks each file against the SHA-256 of the files the port was
225
+ validated on, and writes the translation to `~/.cache/dsgelab/heutel` (or to
226
+ `DSGELAB_HEUTEL_DIR`).
227
+
228
+ ## Verification
229
+
230
+ Each feature is validated against an independent reference where one exists, which may be
231
+ another implementation, a closed form or a published result. A few rest on internal tests
232
+ only, namely autocorrelations, variance decomposition, pruning, the second-order generalised
233
+ impulse response, announced parameter paths and the Blanchard–Kahn check, and the export to
234
+ MacroModelling.jl has no round trip. The checks come in three layers.
235
+
236
+ The first layer is the test suite (`pytest`), which compares the output with answers known
237
+ analytically. It has 148 tests. Of these, 65 need the `symbolic` extra (sympy) and 15 the
238
+ Heutel files from `heutel.fetch()`, and without those files a run gives 137 passed and 11
239
+ skipped. Among the closed forms it checks are the following.
240
+
241
+ - The RBC with log utility and full depreciation has the solution `P = [[α, 1], [0, ρ]]` and
242
+ `F = [α, 1]`.
243
+ - The New Keynesian example is determinate under a Taylor rule with `φπ = 1.5` and
244
+ indeterminate with `φπ = 0.8`, where `solve` raises `BlanchardKahnError`.
245
+ - On a lognormal AR(1), the second-order coefficient `h_ss` equals `σ²`, so the risk
246
+ correction in the decision rule, `½·h_ss = σ²/2`, is the Jensen term.
247
+ - The perfect-foresight transition reproduces the closed-form path `k_{t+1} = αβ k_t^α`.
248
+ - On two independent AR(1) processes and one forward-looking equation, the shock loading is
249
+ the identity. The textbook formula read off the QZ treats every equation as holding ex post
250
+ and gives a dense loading here, which is why `klein` solves for the loading on the rows that
251
+ hold ex post
252
+ ([design notes](https://github.com/GustavoHM/dsgelab/blob/main/docs/design-notes.md#the-shock-loading-is-not-read-off-the-qz)).
253
+ - Halving the displacement in `accuracy()` divides the first-order residual by about four and
254
+ the second-order residual by about eight.
255
+
256
+ The suite runs from a clone of the repository.
257
+
258
+ ```
259
+ git clone https://github.com/GustavoHM/dsgelab && cd dsgelab
260
+ pip install -e ".[symbolic,test]" && pytest
261
+ ```
262
+
263
+ The second layer, in [`validation/`](https://github.com/GustavoHM/dsgelab/blob/main/validation/README.md),
264
+ runs Dynare's own example and test `.mod` files in Octave with Dynare, and one calibration in
265
+ MacroModelling.jl. Each result is compared with `dsgelab`'s, for steady states, impulse
266
+ responses, moments, optimal policy, perfect foresight, estimation, OccBin, the extended path,
267
+ balanced growth and diagnostics. The same folder checks the port of Heutel (2012) against
268
+ Heutel's own code. `bash validation/run_all.sh` runs all of it, and CI runs it weekly.
269
+
270
+ The third layer is one comparison on an applied model from an unpublished paper by the author.
271
+ That model is not public, so the comparison cannot be rerun from the repository. Of the three
272
+ layers, the second is the one in which both the models and the reference software come from
273
+ other authors.
274
+
275
+ ## Known limitations
276
+
277
+ This section lists what the package does not do yet. The
278
+ [roadmap](https://github.com/GustavoHM/dsgelab/blob/main/ROADMAP.md) says when each item is
279
+ planned.
280
+
281
+ - **Third order is partial.** The cubic block and the risk slope (`g_ssk` and `h_ssk`) are
282
+ implemented. Dynare's full decision-rule tensor, with the innovation in every monomial, and
283
+ the stochastic steady state are planned for 0.7. The tensors are dense, so their size grows
284
+ with the cube of the number of variables and shocks, and sparse tensors for large models are
285
+ planned for the same version.
286
+ - **Estimation is linear.** The likelihood comes from the Kalman filter on the first-order
287
+ solution. There is no particle or inversion filter, so no estimation at second or third
288
+ order, and no gradient-based samplers (HMC/NUTS).
289
+ - **No identification analysis, global sensitivity, or method of moments** (Dynare's
290
+ `+identification`, `+gsa`, `+mom`), and no HP- or band-pass-filtered theoretical moments.
291
+ - **Global methods are limited** to Chebyshev collocation with up to three states. There is no
292
+ value or time iteration, endogenous grid, sparse grid, or Tauchen/Rouwenhorst discretisation.
293
+ - **No Markov regime switching, heterogeneous agents, BVAR or DSGE-VAR.**
294
+ - **No plotting.** Results are arrays and dictionaries, and plotting is left to the user.
295
+ - **The extended path's hybrid correction stops at second order.**
296
+ - **Trends can be declared only in a `.mod` file.** `from_equations` does not accept them yet.
297
+ - **`from_mod` reads the model and ignores the script.** Commands such as `stoch_simul`,
298
+ `estimation` or `identification` are skipped, and their options are not applied. In the
299
+ model block, the constructs the reader cannot translate (`@#include`, `@#for`, `varexo_det`,
300
+ leads of a shock, `expectation`, `diff`, `adl`, external functions) are refused with an
301
+ error that quotes the line containing them. Coverage is validated on the Dynare examples and
302
+ tests listed in `validation/`.
303
+
304
+ ## Related package
305
+
306
+ [cgelab](https://github.com/GustavoHM/cgelab), by the same author, solves computable general
307
+ equilibrium models. Its module `cgelab.bridge` uses dsgelab for the intertemporal part of a
308
+ model.
309
+
310
+ ## Citing
311
+
312
+ If the package contributed to a published result, cite it as any other methodological input,
313
+ with the version, because a number that reproduces only under one version cannot be reproduced
314
+ without knowing which. [`CITATION.cff`](https://github.com/GustavoHM/dsgelab/blob/main/CITATION.cff)
315
+ holds the same metadata in the format GitHub reads.
316
+
317
+ Moreira, G. H. (2026). dsgelab: A general toolkit for solving DSGE models in Python (Version
318
+ 0.5.0) [Computer software]. https://github.com/GustavoHM/dsgelab
319
+
320
+ ```bibtex
321
+ @software{dsgelab_2026,
322
+ author = {Moreira, Gustavo Hoffmann},
323
+ title = {{dsgelab}: a general toolkit for solving DSGE models in Python},
324
+ year = {2026},
325
+ version = {0.5.0},
326
+ license = {MIT},
327
+ url = {https://github.com/GustavoHM/dsgelab},
328
+ note = {Python package}
329
+ }
330
+ ```
331
+
332
+ A DOI will be added with the first release archived on Zenodo.