The Dynare Preprocessor defines and parses the Dynare model specification language. It parses .mod files, runs macroprocessing directives (@#define, @#if, @#for, etc.), constructs the symbolic model representation, computes analytical derivatives (Jacobian, Hessian, and higher-order tensors), and produces simulation/estimation drivers and evaluators for MATLAB/Octave, Julia, Python, or structured JSON AST dumps.
In addition to the standalone CLI binary, it provides a high-performance native Python library (dynare_preprocessor) powered by nanobind. The Python library allows you to parse .mod files (from disk or in-memory strings), introspect model equations and symbols, and evaluate residuals, Jacobians, and higher-order derivatives directly via NumPy arrays without intermediate code generation.
Note
Project Status & Upstream Integration This repository is not an independent fork of the Dynare Preprocessor. It is developed with the blessing and active collaboration of the Dynare team, and all enhancements and packaging features will be integrated directly into the official Dynare project.
Please note that this is an early preview: the codebase is in active development, and the Python library API is subject to change as upstream integration proceeds.
- Quick Start with Pixi
- Python Library Guide & API Reference
- Installation & Packaging
- Standalone C++ CLI & Meson Build
- WebAssembly / Emscripten Packaging
- License
The repository uses Pixi to manage reproducible cross-platform development environments across Linux, macOS (Apple Silicon and Intel), and Windows.
# Install dependencies into the default development environment
pixi install
# Compile the preprocessor CLI and nanobind Python extension
pixi run compile
# Run Python pytest test suite
pixi run test
# Run all test suites via Meson (C++ CLI tests + Python tests)
pixi run test-allTo build and test the standalone dynare-preprocessor CLI without any Python or nanobind dependencies:
# Configure the minimal CLI build directory
pixi run -e cli setup-cli
# Compile the CLI binary
pixi run -e cli compile-cli
# Run CLI tests
pixi run -e cli test-cliThe Python package dynare_preprocessor provides zero-copy C++ AST evaluation using nanobind and NumPy arrays.
import numpy as np
import dynare_preprocessor as dp
# Load model from a .mod file on disk or an in-memory string
mod_text = """
var c k y;
varexo e;
parameters alpha beta delta;
alpha = 0.33;
beta = 0.99;
delta = 0.025;
model;
c + k = y + (1 - delta) * k(-1);
y = k(-1)^alpha;
1/c = beta * (1/c(+1)) * (alpha * y(+1)/k + 1 - delta);
end;
initval;
k = 10.0;
c = 1.0;
y = 1.2;
e = 0.0;
end;
"""
model = dp.DynareModel(mod_text, derivs_order=1)
# Inspect model declarations
print("Endogenous:", model.endogenous) # ['c', 'k', 'y']
print("Exogenous:", model.exogenous) # ['e']
print("Parameters:", model.parameters) # ['alpha', 'beta', 'delta']
print("Equations count:", len(model.equations))
# Extract initial values from the model context
y_ss = np.array([model.context[v] for v in model.endogenous])
e_ss = np.array([model.context[v] for v in model.exogenous])
ed_ss = np.array([model.context[v] for v in model.exogenous_det])
p_ss = np.array([model.context[v] for v in model.parameters])
# 1. Evaluate dynamic model residuals F(y_{t+1}, y_t, y_{t-1}, e_t, params)
res = model.residuals(y_ss, y_ss, y_ss, e_ss, ed_ss, p_ss)
print("Residuals shape:", res.shape) # (3,)
# 2. Evaluate structured Jacobian blocks (lead, current, lag, exo, exo_det, params)
blocks = model.jacobian_blocks(y_ss, y_ss, y_ss, e_ss, ed_ss, p_ss)
print("dF/dy_{t+1} (lead):\n", blocks.lead)
print("dF/dy_t (curr):\n", blocks.curr)
print("dF/dy_{t-1} (lag):\n", blocks.lag)
print("dF/de_t (exo):\n", blocks.exo)
# Blocks can also be unpacked directly
lead, curr, lag, exo, exo_det, params = blocks
# 3. Evaluate static (steady-state) residuals and Jacobian
s_res = model.static_residuals(y_ss, e_ss, ed_ss, p_ss)
s_jac = model.static_jacobian(y_ss, e_ss, ed_ss, p_ss)model = dp.DynareModel(
modfile_content_or_path: str,
derivs_order: int = 1,
params_derivs_order: int = 0,
strict: bool = False
)modfile_content_or_path: Either a filesystem path to a.modfile or a raw string containing the model definition.derivs_order: Maximum derivation order with respect to variables (default1).params_derivs_order: Maximum derivation order with respect to parameters (default0).strict: IfTrue, treat undeclared variables as immediate fatal parsing errors (defaultFalse).
| Attribute | Type | Description |
|---|---|---|
endogenous |
list[str] |
List of endogenous variable names |
exogenous |
list[str] |
List of exogenous shock names |
exogenous_det |
list[str] |
List of deterministic exogenous variable names. |
parameters |
list[str] |
List of parameter names |
equations |
list[str] |
Mathematical equations formatted as strings. |
context |
dict[str, float] |
Numerical values populated from assignments, initval, or steady_state_model blocks. |
covariances |
dict[tuple[str, str], float] |
Variances and covariances declared in shocks blocks. |
trajectories |
dict[str, list[tuple[int, int, float]]] |
Deterministic shock paths from surprise/shocks blocks. |
lead_lag_incidence |
list[list[int]] |
Dynare lead-lag incidence matrix across lags |
max_endo_lag |
int |
Maximum lag depth across endogenous variables. |
max_endo_lead |
int |
Maximum lead depth across endogenous variables. |
symbol_info |
list[tuple[SymbolType, int, int]] |
Maps derivation IDs to (SymbolType, symbol_id, lag). |
json_string |
str |
Complete JSON AST representation of the parsed model. |
All evaluator methods accept inputs as either contiguous 1D NumPy arrays (np.ndarray) or standard Python sequences of floats:
-
residuals(endo_future, endo_present, endo_past, exo, exo_det, params) -> np.ndarrayEvaluates dynamic model residuals$F(y_{t+1}, y_t, y_{t-1}, \epsilon_t, \epsilon_{det,t}, \theta)$ as a 1D NumPy array of shape(n_eq,). -
jacobian_blocks(endo_future, endo_present, endo_past, exo, exo_det, params) -> JacobianBlocksReturns structured analytical Jacobian blocks as 2D NumPy arrays:-
lead($n_{eq} \times n_{endo}$ ):$\partial F / \partial y_{t+1}$ -
curr($n_{eq} \times n_{endo}$ ):$\partial F / \partial y_t$ -
lag($n_{eq} \times n_{endo}$ ):$\partial F / \partial y_{t-1}$ -
exo($n_{eq} \times n_{exo}$ ):$\partial F / \partial \epsilon_t$ -
exo_det($n_{eq} \times n_{exo_det}$ ):$\partial F / \partial \epsilon_{det,t}$ -
params($n_{eq} \times n_{params}$ ):$\partial F / \partial \theta$
-
-
jacobian(endo_future, endo_present, endo_past, exo, exo_det, params) -> np.ndarrayEvaluates the combined dynamic Jacobian as a 2D NumPy array across dynamic incidence columns and shocks. -
static_residuals(endo, exo, exo_det, params) -> np.ndarrayEvaluates static (steady-state) residuals$F(y, \epsilon, \epsilon_{det}, \theta)$ as a 1D NumPy array of shape(n_eq,). -
static_jacobian(endo, exo, exo_det, params) -> np.ndarrayEvaluates static (steady-state) Jacobian$\partial F / \partial y$ as a 2D NumPy array of shape(n_eq, n_endo). -
derivatives(endo_future, endo_present, endo_past, exo, exo_det, params) -> list[list[tuple[list[int], float]]]Evaluates higher-order analytical derivatives in sparse coordinate (COO) format. The$k$ -th element contains the list of non-zero entries for the$k$ -th order tensor:([eq, var_1, ..., var_k], value).
The library provides structured C++ exceptions translated natively into Python exceptions:
import dynare_preprocessor as dp
try:
model = dp.DynareModel("var x; model; x = ; end;")
except dp.ParserException as e:
print(f"Parse error: {e.message}")
if e.location:
print(f"Location: {e.location.filename}:{e.location.line}:{e.location.column}")DynareException: Base class for all preprocessor exceptions.SourceFileException: Base class for exceptions tied to source code positions.ParserException: Syntax errors, grammar violations, or undeclared symbols.- Properties:
location(SourceLocation),line,column,filename,undeclared_variables.
- Properties:
MacroException: Macroprocessor expansion errors (@#error, invalid directives).- Properties:
location(SourceLocation),line,column,backtrace.
- Properties:
ModelSemanticException: Mathematical or semantic errors in the model definition (e.g. equation-variable count mismatch, invalid leads/lags).- Properties:
equation_number,equation_lineno,equation_tag,symbol_name,location,line. EquationException: Specific equation error.
- Properties:
StatementException: Incompatible statements or options (e.g. mixing perfect foresight with stochastic simulation).- Properties:
statement_name,option_name.
- Properties:
FileIOException: File read/write failures.- Properties:
path,action.
- Properties:
EvaluationException: Numerical evaluation errors during steady state evaluation (e.g. division by zero, domain errors).
Prebuilt packages for Linux (linux-64), macOS (osx-arm64, osx-64), Windows (win-64), and WebAssembly (emscripten-wasm32) are distributed via conda-forge and prefix.dev/econforge:
# Install via conda or mamba
conda install -c https://repo.prefix.dev/econforge -c conda-forge dynare-preprocessor-pylib
# Or add to an existing Pixi project
pixi add --channel https://repo.prefix.dev/econforge dynare-preprocessor-pylibThe Python package is PEP 517 compliant using meson-python:
# Local editable installation
pip install --no-build-isolation -e .
# Or build standalone wheels (.whl) for distribution
pixi run wheel
# The wheel will be generated in dist/
pip install dist/dynare_preprocessor-*.whlIf you are not using Pixi and have system dependencies installed (meson >= 1.3.0, ninja, modern C++20 compiler, boost, flex, bison):
meson setup build -Dbuild_cli=enabled -Dbuild_library=disabled -Dbuild_doc=false
meson compile -C build
# Run the preprocessor on a .mod file
./build/src/dynare-preprocessor example.mod
# Dump JSON AST to stdout
./build/src/dynare-preprocessor example.mod json=parse jsonstdoutmeson setup build -Dbuild_cli=enabled -Dbuild_library=enabled -Dbuild_doc=false
meson compile -C build
PYTHONPATH=build/src pytest testsThe Python library can be compiled to WebAssembly for browser runtimes such as Pyodide, JupyterLite, and Stlite.
# WebAssembly: Build emscripten-wasm32 package locally via rattler-build
pixi run build-wasm
# WebAssembly: Run tests in a headless browser (Chromium via Playwright + pytester)
pixi run test-wasm
# WebAssembly: Upload package to prefix.dev/econforge
pixi run upload-wasm
# Linux: Build packages across Python 3.11, 3.12, 3.13 variants
pixi run build-linux
# Linux: Upload packages to prefix.dev/econforge
pixi run upload-linuxInclude econforge and emscripten-forge-4x channels in your pixi.toml or environment configuration:
[workspace]
channels = [
"https://repo.prefix.dev/econforge",
"https://repo.prefix.dev/emscripten-forge-4x",
"conda-forge"
]
platforms = ["emscripten-wasm32"]
[dependencies]
dynare-preprocessor-pylib = ">=0.0.1.dev0"Or create an environment using standard Conda:
conda create -n wasm-env \
--platform=emscripten-wasm32 \
-c https://repo.prefix.dev/econforge \
-c https://repo.prefix.dev/emscripten-forge-4x \
-c conda-forge \
dynare-preprocessor-pylibMost of the source files are covered by the GNU General Public License version 3 or later. There are some exceptions; see the respective file headers.