Continuous-time DSGE toolbox in the spirit of Dynare. A Python toolbox for solving continuous-time macroeconomic models, with perfect-foresight deterministic transitions, multi-revelation surprises, and permanent parameter changes.
Status: pre-alpha (v0.0.3, 2026-06-16). The pipeline runs end to end
— a .mod file solves to a path — and a reference manual ships with the
source, but interfaces may still change. See CHANGELOG.md
for what is in this release.
- Continuous-time perfect-foresight solver: stacked collocation (Crank–Nicolson) + Newton on a sparse two-point boundary value problem.
- Multi-segment information structures: anticipated, permanent, and MIT-style surprise shocks, stitched by state continuity.
- Macroprocessor compatible with Dynare's
@#define,@#if,@#for,@#include(plus function macros, comprehensions, and a real-math library). - Symbolic model representation, sparsity-aware Jacobians and C codegen via CasADi; sparse linear solve via SciPy.
- A CLI (
continuo model.mod) and a programmatic Python API (import continuo).
Deferred: discretisation schemes beyond Crank–Nicolson (Radau, Lobatto-IIIA), adaptive meshes, and HDF5/parquet output.
Continuo is pure Python (≥ 3.13). Install the latest release without cloning — the wheel is attached to every GitHub release:
pip install https://github.com/stepan-a/continuo/releases/download/v0.0.3/continuo-0.0.3-py3-none-any.whlWith the optional extras for the Solution conversion methods
(to_dataframe, to_xarray, HDF5 output):
pip install "continuo[pandas,xarray,hdf5] @ https://github.com/stepan-a/continuo/releases/download/v0.0.3/continuo-0.0.3-py3-none-any.whl"To hack on the source, clone and install editable:
git clone https://github.com/stepan-a/continuo
cd continuo
pip install -e ".[dev]"A small model, rbc.mod — a one-sector growth model with an anticipated
rise in productivity z at t = 5, starting 20% below the steady state:
var(state) K;
var(jump) C;
var Y;
varexo z;
parameters alpha, delta, rho;
alpha = 0.33; delta = 0.1; rho = 0.05;
model;
diff(K) = Y - C - delta * K; // capital accumulation
diff(C) = C * (alpha * Y / K - delta - rho); // Euler equation
Y = z * K^alpha; // production
end;
steady_state_model;
K = (alpha * z / (rho + delta))^(1 / (1 - alpha));
Y = z * K^alpha;
C = Y - delta * K;
end;
initval; K = 0.8 * steady_state(K); end; // start below the steady state
shocks; var z; path = if(t >= 5, 1.1, 1.0); end;
simulate(T=50, N=250);
From Python:
import continuo
model = continuo.parse("rbc.mod")
sol = model.simul() # reads the simulate command
sol.t # time grid (numpy array)
sol["C"] # the consumption path
sol.K # attribute-style access
model.steady_state(exogenous={"z": 1.1}) # steady state under the new TFP
sol.to_dataframe() # pandas DataFrame (optional extra)From the command line (writes rbc.csv):
continuo rbc.mod
continuo rbc.mod -o out.csv -T 100 -N 500 # override output / horizon / gridThe Newton solve runs on a pluggable linear backend (see the
Linear solvers manual page).
The table below compares the available backends across the example models;
regenerate it with python examples/benchmark_solvers.py --write.
Wall-clock per solve (median, ms)
| Model | n | superlu | klu | klu-nobtf | umfpack | pardiso |
|---|---|---|---|---|---|---|
| cagan | 201 | 18.2 | 18.4 | 19.7 | 18.3 | 261.0 |
| dornbusch | 903 | 23.3 | 23.8 | 23.4 | 23.6 | 261.1 |
| goodwin | 4802 | 133.8 | 122.6 | 122.3 | 133.0 | 375.2 |
| nk | 1503 | 79.5 | 82.5 | 79.8 | 79.2 | 318.0 |
| nk-nonlinear | 3005 | 113.1 | 109.6 | 113.1 | 113.4 | 360.6 |
| rbc | 1004 | 28.0 | 26.0 | 27.0 | 26.2 | 255.2 |
| solow | 602 | 25.7 | 24.2 | 25.5 | 25.4 | 268.0 |
| tobinq | 903 | 28.2 | 28.3 | 27.8 | 28.4 | 273.1 |
Isolated linear solve — refactor + solve, warm (µs)
| Model | n | superlu | klu | klu-nobtf | umfpack | pardiso |
|---|---|---|---|---|---|---|
| cagan | 201 | 44.7 | 11.3 | 11.0 | 18.5 | 459.4 |
| dornbusch | 903 | 160.4 | 25.6 | 31.6 | 255.5 | 1004 |
| goodwin | 4802 | 834.9 | 108.1 | 98.9 | 1299 | 2999 |
| nk | 1503 | 275.0 | 42.2 | 45.9 | 447.0 | 2583 |
| nk-nonlinear | 3005 | 476.2 | 62.4 | 129.9 | 868.8 | 2051 |
| rbc | 1004 | 294.5 | 29.7 | 47.2 | 284.8 | 1218 |
| solow | 602 | 117.1 | 20.4 | 18.3 | 136.3 | 530.9 |
| tobinq | 903 | 135.6 | 24.8 | 25.7 | 184.0 | 982.0 |
Peak resident memory (MiB)
| Model | n | superlu | klu | klu-nobtf | umfpack | pardiso |
|---|---|---|---|---|---|---|
| cagan | 201 | 102 | 100 | 100 | 101 | 139 |
| dornbusch | 903 | 103 | 101 | 101 | 102 | 142 |
| goodwin | 4802 | 116 | 113 | 112 | 114 | 169 |
| nk | 1503 | 105 | 102 | 103 | 104 | 146 |
| nk-nonlinear | 3005 | 112 | 108 | 109 | 110 | 158 |
| rbc | 1004 | 105 | 103 | 102 | 103 | 144 |
| solow | 602 | 103 | 101 | 101 | 101 | 141 |
| tobinq | 903 | 105 | 102 | 103 | 103 | 142 |
Reading these: the wall-clock above is end-to-end Model.simul(), dominated by the (solver-independent) CasADi build and residual/Jacobian evaluation — so on these small models the linear backend barely moves it. KLU's edge is in the linear solve itself: the isolated refactor + solve table (the warm per-Newton-step cost) shows it ~4–10× faster than SuperLU, a gap that grows with problem size and Newton iterations. PARDISO is far slower here only because MKL oversubscribes threads on these tiny systems — reserve it for large models. See the Linear solvers manual page for the full isolated tables.
Median of 5 runs of end-to-end Model.simul() (includes the CasADi build). Wall-clock in milliseconds; peak resident memory in MiB (whole process — the Python/CasADi/SciPy baseline dominates, and PARDISO loads MKL). Measured 2026-06-19 on AMD Ryzen AI 9 HX 370 w/ Radeon 890M, 24 cores, Python 3.13.14.
The tests live in tests/ and are driven by pytest. The repository is
configured for nox, which builds an isolated
virtual environment for each task — mirroring how CI runs. Install nox once
(globally, not into the project):
pipx install nox # recommended; provides a `nox` command
# or: pip install --user nox
# or, on Debian/Ubuntu: apt install python3-nox (then invoke as `python3 -m nox`)The Debian python3-nox package does not install a nox launcher on your
PATH; if you used it, replace nox with python3 -m nox below. Then run
the sessions:
nox # default sessions: lint + tests on the current Python
nox -s tests # tests across Python 3.13 and 3.14
nox -s lint # ruff check + format check (as in CI)
nox -s fix # apply ruff autofixes and reformat in place
nox -s coverage # tests with a coverage report
nox -l # list all sessionsArguments after -- are forwarded to pytest, so you can narrow a run:
nox -s tests -- tests/parser # only the parser tests
nox -s tests -- -k shocks # only tests whose name matches "shocks"The matrix in nox -s tests needs the corresponding interpreters installed;
run a single one with e.g. nox -s tests-3.13.
You can also run the tools directly. Install the package with its development dependencies in editable mode, ideally inside a virtual environment:
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"Then, from the repository root:
pytest # full suite (tests/ is discovered automatically)
pytest -q --tb=short # quiet output, short tracebacks (as in CI)
pytest tests/parser # only the parser tests
pytest -k shocks # only tests whose name matches "shocks"
pytest --cov=continuo # with a coverage report
ruff check src tests # lint (as in CI)
ruff format --check src tests # formatting check (as in CI)Released into the public domain under the Unlicense.
See LICENSE.