Omnigraph
Graph engine for context assembly. Object-storage native. Agents work with graph data the way engineers work with code: branched, typed, reviewed, merged, audited, and governed.
puremacro is an open-source empirical macroeconomics toolbox in pure Python: SVARs, local projections, Bayesian DSGE and HANK models that run on your laptop, in the browser and on an iPad.
DataJupyter Notebook · PythonGitHub @jalonso1979OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Browser-runnable empirical macroeconomics in pure Python: VAR/SVAR/LP/GARCH/inference/VFI/DSGE/narrative, with built-in validation & replication galleries (Pyodide-compatible).
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A Pyodide-compatible empirical macroeconomics toolbox: the estimator
code runs on pure numpy + scipy + pandas + matplotlib, so the numerical
core stays importable under Pyodide (iPad / juno.sh, best-effort — see
"juno.sh / iPad" below). The supported target is a local install on a
regular workstation.
puremacro 2.0 standardizes the macro API around common parameter conventions (lags, horizon, ci), frozen-dataclass result objects, rich visualization (.plot()), and direct publication export (.to_latex(), .to_typst(), .to_markdown()):
1. Local Projections (LP) & Publication-Grade ExportRead the full README ↗🇬🇧 English · 🇪🇸 Español
Workflow highlights: one model, several solution methods: ordered .mod programs, nonlinear transitions, local/global policy comparisons, and eight revised MAV notebooks. This version also includes DP models in .mod files, reproducible studies and editable HANK equations developed for 5.6.0.
Macroeconometrics and structural modeling on the core scientific-Python stack.
Estimate policy effects, compare economic models, and export results for a research note or policy brief with NumPy, SciPy, pandas and Matplotlib. A local workstation installation is the supported target; the numerical core also runs under Pyodide, with best-effort browser and tablet support.
Requires Python 3.11 or later:
python -m pip install puremacro
| Start with your task | First steps |
|---|---|
| Analyze data | Estimate a government spending multiplier in the fiscal brief, or a shock response with the quickstart; then explore data sources and research workflows. |
| Simulate policy | Declare a household model with dynamic programming, study monetary transmission, build a trade counterfactual, or read the sovereign-risk policy brief. |
| Write a policy brief | Run a tariff experiment on public data and read national and household welfare effects. |
| Learn or teach | Choose a notebook learning path or use the bilingual course and interactive labs. Use the pinned course edition below. |
Status checked 2026-11-05: 6.2.0 is published on PyPI, and pip install puremacro installs it. It succeeds 6.1.0 (published 2026-11-03) and 6.0.1 (published 2026-10-29); earlier tags 5.8.0, 5.9.0 and 6.0.0 were never published (v6.0.0 failed CI).
See the recommended tests and next milestones.
These pages track the 6.2.0 release. See the changelog for additions and fixes. Match your installed version to the documentation.
New DSGE workflows share nonlinear model equations
across robust transitions and announcements and
local/global policy comparisons. The amended bilingual
T02_A–D notebooks pin the code, helpers and models to v5.7.1. Their execution evidence covers local Python.
Since 5.5.0: read-only .mod inspection and opt-in ordered execution,
and audits of supported solved results. Inspection
checks syntax and command support without a numerical solve. Result audits
separate convergence from equation, Euler/KKT and distribution evidence, and
name unsupported or unchecked cases. Neither establishes economic validity.
Users of STEADY_STATE() in nonlinear trajectories should review the
correctness advisory and re-run affected calculations.
The bilingual course edition course-2026A-5.5.0
pins puremacro==5.5.0 and requires Python 3.12 or later. All 72 notebooks
were executed against the recorded candidate wheel
with frozen data and cached language-model responses, including the required
DP and text sections. The release record
verifies that the published wheel contains the same runtime files. Live data refreshes and live local models
remain optional and were not part of that validation. Follow the edition's
setup instructions; the library itself continues to support Python 3.11+.
puremacro uses common parameter conventions (lags, horizon, ci), frozen-dataclass result objects, visualization (.plot()), and direct publication export (.to_latex(), .to_typst(), .to_markdown()). Start with the local-projection example below; the later examples introduce structural models and larger workflows.
import numpy as np
import pandas as pd
from puremacro.lp import lp_hac
# Synthetic macro time series
rng = np.random.default_rng(42)
T = 200
shock = rng.standard_normal(T)
gdp = np.cumsum(0.7 * shock + 0.3 * rng.standard_normal(T))
df = pd.DataFrame({"gdp": gdp, "shock": shock})
# Unified API: horizon, lags, confidence level
res = lp_hac(df, y="gdp", x="shock", horizon=12, lags=4, ci=0.90)
# Instant visualization
res.plot(title="Output Response to Monetary Shock")
# Direct table export to LaTeX tabular or Typst table
print(res.to_latex())
print(res.to_typst())
Solve nonlinear DSGE models up to second order with Kim, Kim, Schaumburg & Sims (2008) pruning, cross-derivatives ($g_{xu}, g_{uu}$), risk corrections ($g_{\sigma\sigma}$), and Dynare oo_.dr parity:
from puremacro.dsge import load_mod
# 1. A Dynare .mod file: a path, or (as here) the source text itself
rbc_mod = """
var c k a;
varexo eps;
parameters alpha beta delta gamma rho;
alpha = 0.30;
beta = 0.99;
delta = 0.025;
gamma = 1.0;
rho = 0.80;
model;
c^(-gamma) = beta * c(+1)^(-gamma) * (alpha * exp(a(+1)) * k^(alpha - 1.0) + 1.0 - delta);
k = exp(a) * k(-1)^alpha - c + (1.0 - delta) * k(-1);
a = rho * a(-1) + eps;
end;
initval;
k = 38.0;
a = 0.0;
c = 2.0;
end;
shocks;
var eps; stderr 0.01;
end;
"""
model = load_mod(rbc_mod) # first-order LinearModel (load_mod(rbc_mod, order=2) goes straight to 2nd order)
# 2. Solve the 2nd-order pruned perturbation
sol = model.solve(order=2)
# 3. Access Dynare-style decision rules (oo_.dr)
print(sol.oo_dr["ghx"]) # first-order state transition
print(sol.oo_dr["ghxx"]) # second-order state curvature
print(sol.oo_dr.summary())
# 4. Analytical theoretical moments & variance decomposition
mom = sol.theoretical_moments()
print(mom.summary())
print(mom.to_latex())
Estimate a DSGE model with the No-U-Turn Sampler (NUTS). The analytic Kalman score recursion supports structural parameters in the supported first-order stationary likelihood; its structural derivative inputs may use finite differences. Numerical and user-supplied gradients are also available. See the estimation guide for gradient selection, fallback behavior and diagnostics:
# requires: numpy scipy
import numpy as np
import pandas as pd
from puremacro.dsge import load_mod, GammaPrior
# Load standard DSGE model and prepare estimation
mod_text = """
var y pi i; varexo eps_d eps_m;
parameters sigma kappa phi_pi rho_d;
sigma = 1.0; kappa = 0.1; phi_pi = 1.5; rho_d = 0.5;
model;
y = y(+1) - (1/sigma) * (i - pi(+1)) + eps_d;
pi = 0.99 * pi(+1) + kappa * y;
i = phi_pi * pi + eps_m;
end;
"""
model = load_mod(mod_text)
# Simulate synthetic observables
sim = model.stoch_simul(periods=100, seed=42)
data = pd.DataFrame({"y": sim.paths["y"], "pi": sim.paths["pi"]})
# Bayesian NUTS estimation for this first-order model
res = model.estimate(
data,
varobs=["y", "pi"],
priors={"sigma": GammaPrior(1.0, 0.2), "kappa": GammaPrior(0.1, 0.05)},
method="nuts",
n_draws=200,
burn_in=100,
n_chains=2,
seed=42,
)
print(res.summary())
Couple microeconomic heterogeneous-agent household blocks (hetagent_block; ... end;) with the built-in aggregate New Keynesian closure in Dynare .mod files using the Auclert et al. (2021) Sequence-Space Jacobian method. The model; block is parsed and kept on HANKModel.dag for inspection but is not solved: the bridge closes every household backend with its built-in Phillips curve and Taylor rule on the declared beta, kappa, phi_pi parameters and the monetary shock path magnitude * rho**t, and the shock argument only labels the result (shock_name, plus a transition_paths column holding the monetary path when the name is a declared varexo), so edit the parameters, not the equations, to change the closure:
# requires: numpy scipy
from puremacro.dsge import solve_hank_bridge
hank_mod = """
var Y C r pi i; varexo eps_m;
parameters beta gamma r_ss phi_pi kappa;
beta = 0.985; gamma = 1.0; r_ss = 0.01; phi_pi = 1.5; kappa = 0.1;
hetagent_block;
model = one_asset_hank;
n_a = 50; a_max = 30.0; borrowing_limit = 0.0; grid = hyperbolic;
end;
model;
Y = C;
pi = beta * pi(+1) + kappa * Y;
i = r_ss + phi_pi * pi + eps_m;
r = i - pi(+1);
end;
"""
# One-line transition solve for an expansionary monetary policy shock (-25 bp)
res = solve_hank_bridge(hank_mod, shock="eps_m", magnitude=-0.0025, horizon=40)
print(res.summary())
When working on an iPad or client-side Pyodide session with compute or memory constraints, seamlessly offload heavy tasks (e.g. 10,000-draw MCMC or large bootstrap SVARs) to Google Colab:
from puremacro.runtime.colab import (
generate_colab_notebook,
show_colab_offload_dialog,
load_colab_result,
)
# 1. Package the heavy task as a self-contained notebook (auth and Drive-mount cells
# included). The task's `result` variable is exported as a .pmz cartridge.
nb = generate_colab_notebook(
"""
import puremacro as pm
result = pm.dsge.estimate_sw07(n_draws=10000, n_chains=4)
""",
mount_drive=True,
save_path="sw07_offload.ipynb",
output_filename="sw07_posterior.pmz",
)
# 2. Show the upload instructions (HTML card in Juno / Jupyter, plain text in a terminal)
show_colab_offload_dialog("sw07_offload.ipynb")
# 3. When the cartridge comes back from Google Drive, load it into the local session:
# posterior = load_colab_result("sw07_posterior.pmz")
Solve continuous state-space dynamic programming models via orthogonal Chebyshev polynomial collocation or Finite Element Galerkin projection with Fischer-Burmeister NCP complementarity:
from puremacro.vfi import CollocationProblem, solve_collocation
# Continuous neoclassical growth model: u(c)=ln(c), f(k)=k^alpha, delta=1.0
alpha, beta, delta = 0.36, 0.96, 1.0
k_ss = (alpha * beta) ** (1.0 / (1.0 - alpha))
domain = (0.5 * k_ss, 1.5 * k_ss)
prob = CollocationProblem(
domain=domain,
orders=7,
method="euler",
params={"alpha": alpha, "delta": delta},
beta=beta,
)
sol = solve_collocation(prob)
print(f"Collocation converged: {sol.converged}")
print(f"Policy at steady state: {sol.policy(k_ss):.4f}")
Solve ultra-high-dimensional continuous dynamic general equilibrium models (10+ continuous state variables) using Physics-Informed Neural Networks (PINNs) trained along simulated ergodic state trajectories in pure NumPy:
from puremacro.vfi import DeepMacroModel, solve_deep_macro
# 10-country dynamic capital accumulation model (10 continuous state variables)
model = DeepMacroModel.multi_country_growth(
n_countries=10, alpha=0.36, beta=0.96, delta=0.08, gamma=2.0,
)
# Solve in pure NumPy via ergodic trajectory sampling (Maliar et al. 2021)
sol = solve_deep_macro(
model=model,
hidden_dims=(32, 32),
n_epochs=20,
batch_size=64,
trajectory_length=200,
seed=42,
verbose=False,
)
print(f"PINN converged: {sol.converged}")
print(f"Final training loss: {sol.loss_history[-1]:.4e}")
Evaluate the general equilibrium welfare, trade flow, and real wage effects of changes in bilateral trade costs and of transport infrastructure investments:
import numpy as np
from puremacro.spatial import AllenArkolakisModel
# 3-region continuous economic geography model (North, South, East)
coords = np.array([[45.0, -93.0], [30.0, -90.0], [40.7, -74.0]])
model = AllenArkolakisModel.from_coordinates(
coords,
region_names=["North", "South", "East"],
theta=4.0,
alpha=0.08,
beta=-0.35,
total_population=100.0,
)
# Simulate infrastructure investment reducing bilateral transport friction by 20%
res = model.simulate_infrastructure_shock(origin="North", destination="South", cost_reduction=0.20)
print(f"Spatial GE converged: {res.converged}")
print(f"Aggregate welfare change: {res.welfare_pct:+.4f}%")
Estimate a treatment effect with high-dimensional macro controls via Chernozhukov et al. (2018) partially linear regression: cross-fitted Robinson orthogonal scores, pure-NumPy penalized learners, and result objects that export to LaTeX, Typst and Markdown. The learners are lasso and ridge only — there is no elastic-net learner:
import numpy as np
from puremacro.causal import dml_plr
# 500 observations, 50 controls; the true treatment effect is 1.5.
rng = np.random.default_rng(0)
n, p = 500, 50
X = rng.normal(size=(n, p))
beta = np.r_[1.0, 0.5, 0.25, np.zeros(p - 3)]
D = X @ beta + rng.normal(size=n)
Y = 1.5 * D + X @ beta + rng.normal(size=n)
res = dml_plr(Y, D, X, n_folds=5, learner="lasso")
print(f"theta = {res.theta:.4f} se = {res.se:.4f}")
print(f"95% CI = [{res.ci_lower:.4f}, {res.ci_upper:.4f}]")
# theta = 1.4959 se = 0.0485
# 95% CI = [1.4009, 1.5909]
The class behind dml_plr is DoubleMLPLR. NaN / infinite inputs, more folds than
observations and arrays above two dimensions raise ValueError; a training fold with
n_train <= p + 1 emits a UserWarning rather than returning a silently unidentified
estimate. A learner passed as an instance is deep-copied per fold, so the folds never
share fitted state, and .to_latex() compiles with booktabs alone.
Solve the Achdou, Han, Lasry, Lions & Moll (2022) implicit upwind M-matrix system, then the adjoint Kolmogorov forward equation for the stationary density on the same grid, then the continuous-time Aiyagari general equilibrium:
from puremacro.vfi import solve_hjb_achdou, solve_aiyagari_continuous_hjb
# Implicit upwind HJB with two-state Poisson income switching (default lambda = 0.1),
# plus the adjoint KFE stationary density g(a, z) on the same grid.
sol = solve_hjb_achdou(r_rate=0.03, w_rate=1.0, rho_val=0.05, Na=200, a_max=20.0)
print(f"HJB iterations: {sol.n_iter} |mass - 1| = {sol.mass_residual:.1e}")
print(f"g_dist is a density of shape {sol.g_dist.shape} (mass per unit of a)")
# `converged` means |K^s - K^d| <= tol_ge, and tol_ge governs the GE loop.
ge = solve_aiyagari_continuous_hjb(Na=120, a_max=20.0, tol_ge=1e-4)
print(f"GE converged: {ge.converged} r* = {ge.r_star:.6f} K* = {ge.K_star:.4f}")
print(f"|K^s - K^d| = {abs(ge.excess_capital):.2e} <= tol_ge = 1e-4")
# HJB iterations: 9 |mass - 1| = 0.0e+00
# g_dist is a density of shape (200, 2) (mass per unit of a)
# GE converged: True r* = 0.020571 K* = 5.9977
# |K^s - K^d| = 2.41e-07 <= tol_ge = 1e-4
g_dist is a density, not node mass: on a non-uniform a_grid the node mass is
divided by the cell width, so sum_i g_i w_i = 1 with the trapezoid quadrature weights
w. The Poisson switching between the states of e_grid is new in 3.4: the 3.3.0
explicit solver carried the same two income states but no switching term, so they were
independent deterministic-income problems.
lp_iv reports the effective F-statistic with its critical values, and inverts the Anderson-Rubin set exactly instead of scanning a fixed grid — the closed-form tail limit classifies it as a bounded interval, two rays, the whole line or empty, and the endpoints are Brent roots (a closed-form quadratic when there is a single instrument):
import numpy as np
import pandas as pd
from puremacro.lp import lp_iv
from puremacro.lp.iv import mop_critical_values
# The closed form behind the MOP table: cv = chi2_{K, 1-alpha}(K / tau) / K.
cv10, cv20 = mop_critical_values(1)
print(f"K=1: 10% worst-case bias {cv10:.2f}, 20% {cv20:.2f}")
rng = np.random.default_rng(7)
T = 240
z1, z2 = rng.normal(size=T), rng.normal(size=T)
u = rng.normal(size=T)
x = 0.8 * z1 + 0.5 * z2 + u
y = np.cumsum(0.4 * x + u + rng.normal(size=T))
df = pd.DataFrame({"y": y, "x": x, "z1": z1, "z2": z2})
res = lp_iv(df, y="y", x="x", z=["z1", "z2"], horizon=3, weak_iv_robust=True)
print(res[["h", "beta", "se", "mop_f", "ar_lo", "ar_hi", "ar_set_type"]]
.round(3).to_string(index=False))
# K=1: 10% worst-case bias 23.11, 20% 15.06
# h beta se mop_f ar_lo ar_hi ar_set_type
# 0 0.347 0.123 89.978 0.095 0.546 bounded
# 1 0.155 0.187 88.555 -0.257 0.495 bounded
# 2 0.186 0.244 84.872 -0.188 0.481 bounded
# 3 0.093 0.260 81.220 -0.481 0.548 bounded
The errors are heteroskedasticity- and autocorrelation-robust (Newey-West with the
horizon's bandwidth). lp_iv has no cluster argument: neither the effective F it
reports nor the Anderson-Rubin set it inverts is cluster-robust. Only
inference.weak_iv.olea_pflueger_f(x, z, cluster=...) takes clusters, and it returns
the effective F alone — there is no cluster-robust confidence set anywhere.
Banco de México, INEGI, Banco Central do Brasil and Banco Central de Chile join the real-time providers. The catalog, the series identifiers and the vintage semantics all resolve offline, before any request is made:
from puremacro.fetch.realtime import (
available_providers, vintage_catalog, resolve_series, VINTAGE_SEMANTICS,
)
print(available_providers())
cat = vintage_catalog()
latam = cat[cat["provider"].isin(["banxico", "inegi", "bcb", "bcch"])]
print(latam.query("variable == 'cpi'")[["provider", "country", "series_id", "freq"]]
.to_string(index=False))
print(resolve_series("banxico", "MEX", "cpi"))
print(VINTAGE_SEMANTICS["bcch"][:82])
# ['alfred', 'banxico', 'bcb', 'bcch', 'bundesbank', 'ecb_rtd', 'inegi', 'oecd_stes', 'ons', 'statcan']
# provider country series_id freq
# banxico MEX SP1 M
# bcb BRA 433 M
# bcch CHL F074.IPC.IND.Z.Z.C.M M
# inegi MEX 628197 M
# SP1
# Banco Central de Chile Base de Datos Estadísticos (SIETE API). Upstream overwrites
These four sources publish only the current edition of a series, so their vintage
date is the local snapshot date — the day this machine fetched the series and stored
it. Revision history therefore accumulates only across repeated captures on different
days; it is not a publication archive like ALFRED or the ONS workbook. Catalog
entries that could not be confirmed against the live API are marked VERIFY_ONLINE.
Core econometrics
var.identify.*) — Cholesky, Blanchard-Quah,
sign restrictions (Rubio-Ramirez-Waggoner-Zha), sign + zero
restrictions (Arias-Rubio Ramirez-Waggoner), sign-robust bands
(Giacomini-Kitagawa), proxy / external instruments, max-share / news,
heteroskedasticity (Rigobon), non-Gaussian (Lanne-Meitz-Saikkonen).
All public estimators return frozen-dataclass …Result objects.lp.*) — single-country LP-HAC, LP-IV,
lag-augmented LP (Montiel Olea-Plagborg-Møller 2021), panel LP with cluster /
Driscoll-Kraay SE, state-dependent LP, smoothed LP (Barnichon-
Brownlees B-splines), asymmetric LP (Tenreyro-Thwaites), LP-GARCH-
state, LP-GARCH-in-mean, mean-group, CCE, quantile LP.inference.*) — central HAC OLS, Newey-West, Kiefer-
Vogelsang fixed-b, Driscoll-Kraay; weak-IV diagnostics (Cragg-Donald,
Kleibergen-Paap, Anderson-Rubin, Montiel Olea-Pflueger); Hansen-J /
Stock-Yogo over-id; Pesaran CD, Swamy slope-homogeneity, Quandt-
Andrews structural breaks, specification curves.oo_.dr parity (dsge.dynare).asset_pricing.*) — returns and risk (Sharpe with Lo
HAC se, drawdowns), VaR/ES with Kupiec/Christoffersen backtests, GRS and
HAC Wald time-series tests, Fama-MacBeth with Shanken se, linear SDF GMM,
Hansen-Jagannathan distance, Hansen-Singleton CCAPM, on the public GMM
engine inference.gmm; Ken French and FRED data fetched, never bundled;
see docs/asset_pricing.md. Experimental investment-research tools add lagged
portfolio backtests with costs, constrained allocation, technical indicators,
dated fundamentals and DCF, and Brock–Hommes belief dynamics; see the
English/Spanish guide.Frontier methods (2.3)
var.identify.identify_narrative_sign) — Antolín-Díaz & Rubio-Ramírez (2018) shock-sign and historical-contribution restrictions with importance weights; see docs/narrative_sign_svar.md.did.honest_did) — Rambachan & Roth (2023) smoothness and relative-magnitude sensitivity sets with fixed-length confidence intervals and breakdown values; see docs/honest_did.md.lp.smooth_lp) — Barnichon & Brownlees (2019) penalised B-spline LPs with GCV/AIC/BIC/CV smoothing selection; see docs/smooth_lp.md.models.hank_sequence_space.solve_nonlinear_transition) — Broyden transitions for large MIT shocks on genuine household Jacobians; see docs/hank_nonlinear.md.dsge.gertler_karadi) — banking frictions DSGE under Klein and OccBin; see docs/gertler_karadi.md.var.bvar_sv) — KSC mixture + FFBS Gibbs sampler, split-R̂, volatility-conditioned IRFs, hold-out log scores and fan charts; see docs/bvar_sv.md.Modern macro extensions
did.*) — Callaway-Sant'Anna, Sun-Abraham and
Borusyak-Jaravel-Spiess with panel-bootstrap SEs; Synthetic-DiD with
placebo, bootstrap or jackknife SEs (Arkhangelsky et al. 2021, §5).dynpanel.*) — Arellano-Bond, Blundell-Bond
two-step Windmeijer + Hansen-J + AR(1)/AR(2) + Roodman collapse.hfi.*) — Gertler-Karadi 2015,
Nakamura-Steinsson 2018, Jarociński-Karadi 2020.volatility.*) — SigmaObject (1:1 port of the MAV
MATLAB class with extended decomposition API), BEKK, CCC, HAR-RV,
range-based, ARCH-LM / Ljung-Box diagnostics.nowcast.*) — Kalman-DFM (Doz-Giannone-Reichlin)
with ragged-edge handling, Mariano-Murasawa MF-VAR, forecast
combinations, probabilistic scoring rules.gar.*) — quantile AR, ABG 2019 skew-t fit, NFCI-
style FCI.cycles), Phillips-Hansen FM-OLS / Stock-Watson DOLS /
Phillips-Ouliaris (cointegration_modern), PCA factors + Bai-Ng IC
(factor), MIDAS (midas), KORV (2000) system-GMM CES (korv_gmm),
synthetic control + placebo inference (synthetic_control).spectral, wavelet) — Welch PSD / cross-
spectrum / coherence (numpy.fft only); MODWT-Haar wavelet variance
decomposition.realized_vol) — realized variance, bipower
variation, Corsi HAR-RV.vfi.*, models.hank_sequence_space) — value-function iteration with
EGM, finite-horizon life-cycle, OLG, Krusell-Smith aggregate shocks,
Hopenhayn firm entry/exit, Epstein-Zin, permanent types, transition
paths, and method-of-moments estimation. In addition, full sequence-space
HANK (Auclert et al. 2021) featuring the $\mathcal{O}(T^2)$ Fake News
Algorithm (fake_news_algorithm, FakeNewsResult) and targeted fiscal
transfer simulations across wealth deciles (simulate_targeted_transfer,
FiscalTransferResult); numpy reference backend with optional
numba / mlx / cupy acceleration. See notebooks/ for a showcase suite.Continuous dynamic programming & projection engines
vfi.collocation, vfi.fem) — Chebyshev orthogonal polynomial collocation (Gauss and Lobatto extrema nodes) and Finite Element Galerkin projection with Fischer-Burmeister complementarity ($\Psi_{\text{FB}}^\epsilon(a, b) = 0$) for borrowing kinks ($k' \ge \bar{k}$); analytical Brock-Mirman neoclassical growth benchmark.vfi.splines, vfi.smolyak) — Cox-De Boor cubic B-splines, Schumaker (1983) shape-preserving quadratic splines ensuring strict monotonicity and concavity preservation ($\partial g / \partial k \ge 0$), and Smolyak (1963) sparse grid collocation scaling as $\mathcal{O}(N (\log N)^{d-1})$ for multidimensional models ($d \in [2, 6]$).vfi.dcegm) — Iskhakov, Jørgensen, Rust & Schjerning (2017) DC-EGM for models with discrete retirement/labor choices and continuous savings, featuring the fast Upper Envelope algorithm to prune sub-optimal branches, and extreme value taste shocks.vfi.continuous_distribution) — Young (2010) non-stochastic continuous density simulation preserving mass conservation to machine precision ($\sum \mu^* = 1.0 \pm 10^{-15}$), power iteration / sparse linear solvers, and Aiyagari continuous general equilibrium factor price root-finding ($K^s(r^) = K^d(r^)$).vfi.continuous_transition) — Non-linear transition paths coupling backward continuous EGM with forward time-dependent Young distribution operators ($\mu_{t+1} = T_t^* \mu_t$), solved via sequence-space Broyden Quasi-Newton.vfi.analytic_gradients) — Semi-analytic parameter sensitivities via the Implicit Function Theorem, using central-difference residual Jacobians. Supports representative-agent continuous projection solutions and the canonical continuous Aiyagari equilibrium through coupled household, stationary-distribution and market-clearing derivatives. Other heterogeneous-agent models require their own derivative implementation.vfi.deep_macro) — Physics-Informed Neural Networks in pure NumPy for ultra-high-dimensional dynamic models (10+ continuous states), bounded activations guaranteeing physical resource feasibility ($c > 0, k' > 0, c < W$), and Maliar et al. (2021) ergodic trajectory sampling.Quantitative spatial economics
spatial.allen_arkolakis) — Continuous geographic general equilibrium with bilateral iceberg trade costs, mobile labor, Marshallian agglomeration ($\alpha$), and amenity congestion ($\beta$) (AllenArkolakisModel).docs/STRUCTURAL_VALIDATION_STATUS.md states, surface by surface, the supported calculation, the acceptance contract and the remaining limits.International trade general equilibrium (puremacro.trade, puremacro 5.2)
trade.calibrate_trade_model, trade.solve_trade_equilibrium, trade.TariffScenario, trade.run_scenario) — Input-output linkages, bilateral tariffs on intermediate and final goods, and consistent producer/purchaser accounting: homogeneous factor costs, duties valued at exporter prices and rebated once, fixed foreign saving and an explicit numeraire. Any number of countries and sectors; dimensions, countries and sectors come from the table or from arguments. See docs/trade_accounting.md.trade.compute_hicksian_welfare, trade.solve_policy_equilibrium, trade.optimal_tariffs, trade.game) — Expenditure-function EV/CV with an exact endpoint attribution, audited Newton → hybrid → continuation recovery, optimal tariffs and Nash tariff games. See docs/trade_welfare.md and docs/trade_policy.md.trade.CaliendoParroModel, models.trade_policy.TradePolicySimulator) — Counterfactual tariffs without estimating unobserved fundamentals; TradePolicySimulator.from_icio(2019) calibrates to the bundled table.trade.solve_flexible_trade_equilibrium: nested CES, Stone-Geary, Armington, Atkeson-Burstein markups), the exact nested-CES block Newton (trade.ces_newton), household demand and distributional incidence (trade.household, trade.distributional), audited continuation (trade.continuation), a local-stability diagnostic (trade.stability), native MRIO tables (trade.mrio), the condensed Leontief tariff model (trade.condensed), a perfect-foresight dynamic MRIO (trade.dynamic) and optional torch/MLX solvers (trade.gpu).trade.data.load_icio_data() reads the bundled OECD ICIO 2019 table (19 G20 countries plus a rest of the world, 20 ISIC Rev.4 sections), rebuilt from the OECD file by tools/build_icio_isic.py; trade.data.synthetic_icio_table(nc, ns, seed=...) builds a synthetic table of any size. See "Bundled data and attribution" below.Monetary Policy Simulator & Browser Laboratory
models.monetary_transmission.MonetaryTransmissionSimulator) — Side-by-side HANK vs. RANK transmission engine with the Kaplan-Moll-Violante (2018) direct vs. indirect decomposition across 10 wealth deciles of a stylised calibration (a steep HANK MPC gradient against RANK's flat MPC of $1-\beta$). See docs/monetary_transmission.md.curso/site/labs/politica-monetaria-hank.html) — A static client-side lab in plain JavaScript (d3 and KaTeX from a CDN, no build step): the HANK Jacobians and MPC deciles are fixed arrays and moving a control re-solves the linear general equilibrium in the browser. Described in docs/monetary_transmission.md.Causal ML, continuous-time HJB, multi-constraint OccBin & regional real time
causal.dml) — Chernozhukov et al. (2018) partially linear regression with Neyman-orthogonal Robinson scores, $K$-fold cross-fitting and pure-NumPy penalized learners (LassoCoordinateDescent, RidgeGCV); the entry points are dml_plr and DoubleMLPLR, and the learners are lasso and ridge only. DMLResult carries .summary(), .plot(), .to_latex(), .to_typst() and .to_markdown().vfi.hjb_achdou) — Achdou, Han, Lasry, Lions & Moll (2022) implicit upwind M-matrix scheme solved with scipy.sparse.linalg.spsolve, two-state Poisson income switching (default intensity $\lambda = 0.1$, new in 3.4), and the stationary density from $A^\top g = 0$. solve_kfe_achdou returns a density — node mass divided by the cell width — normalized with trapezoid quadrature weights, so it integrates to one on non-uniform grids too. solve_aiyagari_continuous_hjb closes the loop in general equilibrium, with tol_ge governing convergence and converged meaning $|K^s - K^d| \le$ tol_ge.dsge.solve_multiconstraint_occbin) — $M \ge 2$ simultaneous occasionally binding constraints (a zero lower bound and a collateral limit at once). Shipped in 2.8.0; 3.4 extends and hardens it: the regime splice carries every differing row, result.regimes[t] is a bitmask (bit $k$ set when constraint $k$ binds, so 3 means both bind), non-convergence returns converged=False with a UserWarning naming each reason, and a constraint that cannot be paired one-to-one with its model raises ValueError.lp.iv, inference.weak_iv) — the effective $F$-statistic with critical values from the MOP closed form $\mathrm{cv} = \chi^2_{K,,1-\alpha}(K/\tau)/K$ (23.11 and 15.06 for $K = 1$ at $\tau$ = 10% and 20%), and a multi-instrument Anderson-Rubin set inverted exactly — bounded interval, two rays, the whole line, or empty — rather than read off a grid. Errors are heteroskedasticity- and autocorrelation-robust; lp_iv takes no clusters and no confidence set is cluster-robust (inference.weak_iv.olea_pflueger_f accepts a cluster argument for the effective F alone). is re-exported from .Narrative econometrics (narrative.*)
Fiscal- / labor- / uncertainty-narrative IV pipeline: canonical
NarrativeEvent / NarrativeInstrument schemas, deduplication,
keyword and manual scoring backends, panel construction, replication
loaders for canonical datasets (Romer-Romer, Mertens-Ravn). LLM
scoring backend (narrative.scoring.llm) and HTTP source modules
(narrative.sources.*) live as out-of-Pyodide side-channels.
Sources include:
puremacro.narrative.sources.iter_beige_book,
puremacro.narrative.indices.bbui).iter_erp), State of the Union (iter_sotu), and CBO reports
(iter_cbo); three matching indices erpui, sotuui, cboui.
CBO body fetches transparently fall back to the Wayback Machine
when cbo.gov returns a DataDome challenge.iter_eurlex)
and EU Parliament plenary verbatim (iter_ep_debates); two trilingual
EN/DE/FR indices eurlex_ui and ep_ui. EUR-Lex enumeration via
the public Cellar SPARQL endpoint (Wayback-routed per-act fetch
due to AWS-WAF on the live site); EP via Wayback CDX with coverage
back to Term 7 (2009-07-14).iter_bluesky_posts, bluesky_ui). Hand-curated 29-
handle seed list (BLUESKY_KNOWN_HANDLES); 12 resolved as of 2026-05-25.
Multilingual via languages=... connector kwarg; the index defaults
to monthly actor-level text aggregation (aggregate_to="actor_month")
to mitigate LUI's short-text degradation.consensus_disagreement computes
the cross-sectional mean + std over any subset of narrative indices;
CROSS_SOURCE_GROUPS documents thematic subsets.Connectors hit by WAF / bot-protection (EUR-Lex, EU Parliament, CBO) fall back
to the Wayback Machine via the shared puremacro.narrative.sources._wayback
helper. Coverage is constrained by what Wayback has snapshotted.
Local language models (puremacro.llm). The LLM features above (score_llm,
llm_prob_kernel) run on a small model on the user's own device, with no API key:
puremacro.llm.complete(prompt) picks MLX (Mac, and the iPad under the Juno app), llama.cpp,
Ollama or an OpenAI-compatible server, and sizes the model (Qwen3 1.7B, 4B or 8B, Apache-2.0) to
the device's memory. Every answer goes into a completion cache keyed by model and prompt, not by
engine, so a cache built once on a Mac answers the same calls on an iPad, on Windows, in Colab or
in the browser, where cache mode reads it with no model installed. See docs/local_llm.md.
Data pipelines (newly absorbed; see ARCHITECTURE.md)
fetch.*) — FRED / ALFRED, SDMX-CSV
(OECD, Eurostat, ECB, IMF SDMX-Central), EPU / GPR / WUI / JLN /
Fernald, OECD-MEI / QNA / Energy / FX, ILOSTAT, Yahoo, WB pink
sheet, plus per-state FRED loaders for the US subnational track.fetch.qna_long_panel) — the OECD spine extended
backwards per country by ratio-splicing archived national vintages onto it:
Spain to 1970Q1 (+100 quarters) and Japan to 1955Q2 (+155), with
provenance per series per quarter. The splice preserves the old vintage's
growth rates and reports how stable each join's ratio is, because a ratio
that drifts across the overlap means the two vintages disagree about growth
and the spliced level depends on the anchor. Seven other candidate sources
were measured to buy zero quarters and the reasons are kept in
LONG_PANEL_KNOWN_GAPS. See docs/long_panel.md.fetch.vintage_panel, fetch.realtime.*) — published
editions of a series across ten providers behind one call: the OECD STES
revisions archive (42 economies, monthly editions from 1999), ALFRED, the
Bundesbank Gerda database, the ONS real-time workbook (746 editions back to
1961), Statistics Canada's vintage tables, the ECB/EABCN database, and the
four Latin American connectors added in 3.4 (Banco de México, INEGI, Banco
Central do Brasil, Banco Central de Chile). Comes
with the revision toolkit — revision triangles, first/latest release,
r_t = y_f - y_p, and the Mankiw-Shapiro news-vs-noise test
(vintages.mankiw_shapiro). Each provider documents what its vintage date
actually means, because they disagree: the first six carry a genuine
publication or edition date, while the four Latin American sources publish
only the current edition, so their vintage date is the local snapshot date
and revision history accumulates only across repeated captures on different
days. See docs/real_time_data.md.build_panel, build_subnational_panel, build_climate_panel, build_financial_panel) — single
entry points that materialise quarterly / monthly cross-country,
US-state, climate/energy, and macro-financial panels from the fetchers,
with automated frequency rollups (M→Q, A→Q), regime tagging, SA
(X-13 / STL fallback), and missing-data tracking.fetch.emissions, fetch.energy_transition, fetch.commodities, fetch.financial) — keyless,
cached collectors for World Bank WDI and OECD SDMX greenhouse gas emissions
by sector; electricity generation by source and clean transition shares;
expanded World Bank Pink Sheet / IMF commodity benchmark price suites
(energy, metals, agriculture, fertilizers); and international financial
stability metrics (sovereign yields, policy rates, BIS credit-to-GDP gaps,
property prices, and financial conditions). See .Running away from a workstation (runtime.*, runtime.colab, pocket.*, longrun.*)
The package's headline promise is that the estimator core runs on an
iPad. These tools make the promise usable rather than merely true:
runtime reports what the machine can actually do (sockets? parquet?
threads?) and routes HTTP over the browser when there are no sockets;
runtime.colab provides seamless task offloading to Google Colab with
cloud auth and .pmz persistence when tasks exceed mobile memory;
pocket packs data into portable, self-verifying .pmz cartridges so a
panel built online opens offline; longrun runs bootstraps and chains in
resumable chunks that survive the OS suspending the app, with results
invariant to how the work was sliced. See "juno.sh / iPad" below.
DSGE sketchpad, Dynare parity & frontier engines (dsge.build, dsge.dynare, dsge.cli, dsge.occbin, dsge.bayesian, dsge.perfect_foresight)
Write equilibrium conditions as a Python function or parse native Dynare
.mod files (load_mod, parse_mod). Solves 1st- and 2nd-order approximations
with complex-step differentiation, Kim-Kim-Schaumburg-Sims (2008) pruning,
cross-derivatives ($g_{xu}, g_{uu}$), risk adjustments ($g_{\sigma\sigma}$),
Dynare oo_.dr decision rules, and analytical theoretical moments (stoch_simul).
Includes the dedicated puremacro-dynare CLI tool, Guerrieri-Iacoviello (2015)
OccBin piecewise-linear algorithm for occasionally binding constraints (ZLB),
Boucekkine-Juillard stacked Newton-Raphson relaxation for non-linear perfect
foresight, and full Bayesian MCMC estimation (Laplace Hessian covariance +
adaptive Random-Walk Metropolis-Hastings). No hand-derived matrices, no
Fortran/C++ compiler, and a numerical core importable under Pyodide. Browser
execution remains best effort; see the runtime guide for
file-format, network and compute limits.
Teaching artefacts
teaching.* is a research / teaching side-channel that intentionally
wraps statsmodels / linearmodels / arch so notebooks can compare
puremacro's pure-numpy estimators against the canonical packages. Not
covered by the Pyodide promise.
pip install puremacro
This pulls the five base dependencies (numpy, scipy, pandas, matplotlib,
requests): everything the estimators and the fetch layer need to import.
Every one of them ships with Pyodide, so the same install works in the browser.
Reading parquet or Excel files needs the optional file-format engines:
pip install "puremacro[io]" # adds pyarrow (parquet) and openpyxl (.xlsx)
The ENOE lessons of the course (08, 24), build_all, the shock atlas and the
fetch.labor* caches need [io]; code that needs a missing engine fails
immediately and names the extra. The other extras are for the optional
features listed below.
From the puremacro/ package directory (the one containing this README.md
and pyproject.toml):
pip install -e .
To run the dev parity tests, install the optional dev deps too:
pip install -e '.[dev]'
To use the narrative.sources PDF body extractor:
pip install -e '.[narrative]'
Other optional extras: [backend] (numba + Apple-Silicon mlx), [cuda]
(NVIDIA cupy), [io] (pyarrow + openpyxl: parquet and .xlsx), [data]
(yfinance / fredapi / xlrd data fetchers, plus the [io] engines), [llm]
(Anthropic-backed narrative scoring), [embeddings] (sentence-transformers
narrative scoring), [notebooks] (jupytext notebook build).
For connectors that want opt-in on-disk caching + per-host throttling,
the variants safe_get_bytes_cached and safe_get_text_cached apply
a SHA-256-keyed cache at ~/.cache/puremacro/http/. Set
PUREMACRO_HTTP_NO_CACHE=1 to bypass.
Upload the puremacro/ directory to your juno.sh workspace, then in a
notebook cell:
# requires: a Jupyter kernel (IPython magic)
%pip install ./puremacro
Every base dependency ships with Pyodide, so that command resolves its
dependencies from the Pyodide distribution without reaching PyPI; so do
%pip install puremacro and await micropip.install("puremacro"). The parquet
paths (cache, fetch.labor*, shock_atlas, build_panel) also need
pyarrow: recent Pyodide distributions (314.x, the playground's) include it,
so %pip install pyarrow works there, and older ones (0.28) do not. .xlsx
readers need openpyxl, which is pure Python but installs only where the
kernel may reach PyPI. The browser is not a supported deployment target:
teaching material assumes a local install.
puremacro.runtime answers that at run time rather than leaving you to
discover it one traceback at a time:
from puremacro import runtime
print(runtime.report())
# host : pyodide 3.12.7 (wasm32)
# device : tablet
# network : js-fetch (call runtime.enable_browser_network())
# parquet : unavailable -> use puremacro.runtime.store / pocket
# threads : no (1 cpu, unknown)
# backends : numpy
Detection is heuristic — no API tells you "this is Juno" — so every field
can be pinned with PUREMACRO_HOST, PUREMACRO_DEVICE,
PUREMACRO_SOCKETS, PUREMACRO_PARQUET or PUREMACRO_THREADS. The last
one decides whether the bootstrap engines build a thread pool at all: set
PUREMACRO_THREADS=0 and every resampling engine runs the plain loop, which
is what a browser kernel needs. When threads are unavailable, n_jobs is
ignored rather than raising.
No sockets. requests and urllib cannot open a connection under
Pyodide, so every fetch.* call fails even though the estimator core
imports perfectly. One call routes the whole existing fetch layer over
the browser's own networking:
# requires: Pyodide
from puremacro import runtime
from puremacro.fetch import fetch_xrate_monthly
runtime.enable_browser_network()
fx = fetch_xrate_monthly(["MEX"])
Endpoints must send Access-Control-Allow-Origin — some public
statistical APIs do, many WAF-fronted government sites do not. A blocked
request says so and names CORS; proxy= routes through a CORS proxy you
control.
No pyarrow. Pack the data where the network and pyarrow are, open it where they are not. A cartridge is one self-verifying file carrying its own provenance:
import numpy as np
import pandas as pd
from puremacro import pocket
# A frame to ship (in practice: the panel you just fetched on the workstation)
panel = pd.DataFrame(
{"USA": [1.0, 1.2, 1.1], "DEU": [0.8, 0.9, 1.0]},
index=pd.period_range("2025Q1", periods=3, freq="Q"),
)
# workstation
pocket.pack(panel, "g7.pmz", source="OECD QNA", vintage="2026-08-19")
# iPad, airplane mode
cart = pocket.load("g7.pmz")
panel = cart.frame() # sha256-checked on read
cart.provenance.vintage # '2026-08-19'
print(cart.summary())
Getting a file onto an iPad is often more friction than the analysis,
so a cartridge also travels as text: pocket.to_base64("g7.pmz") on
one machine, pocket.from_base64(blob, "g7.pmz") on the other.
The app gets suspended. iPadOS stops a backgrounded app, and a
four-minute bootstrap does not survive someone answering a message.
puremacro.longrun computes in chunks, persists after each, and resumes
in a later session:
import numpy as np
from puremacro import longrun
rng = np.random.default_rng(0)
Y = rng.standard_normal((120, 2)) # your VAR data
def one_draw(i):
"""One bootstrap replication: returns whatever you want the bands of."""
idx = np.random.default_rng(i).integers(0, len(Y), len(Y))
return Y[idx].mean(axis=0)
job = longrun.bootstrap(one_draw, 2000, checkpoint="irf.ckpt")
job.run(seconds=30) # 240/2000 · 12% · ~220s of compute left
job.run(seconds=30) # ... and again after the app was suspended
bands = np.percentile(job.result(), [5, 95], axis=0)
Draw i always uses default_rng([seed, i]), so a job resumed across
five sessions gives bit-identical results to one that ran straight
through — which is what makes a resumed run publishable.
Sizing the work to the device. runtime.fit(n_boot=2000) returns
what this machine should actually attempt, and
runtime.budgeted(estimator) clamps the cost arguments of a call.
Both are opt-in: no estimator default changed, so a script that runs on
your laptop produces the same numbers it always did. Only cost knobs are
clamped — horizon changes what is being estimated, so it is left alone.
Offloading heavy compute to Google Colab. When tasks exceed tablet memory
or CPU limits (such as full Bayesian MCMC chains or 10,000-draw wild bootstraps),
puremacro.runtime.colab offloads the work seamlessly:
from puremacro.runtime.colab import (
generate_colab_notebook,
show_colab_offload_dialog,
load_colab_result,
)
# 1. Package the heavy task as a self-contained notebook (auth and Drive-mount cells
# included). The task's `result` variable is exported as a .pmz cartridge.
nb = generate_colab_notebook(
"""
import puremacro as pm
result = pm.dsge.estimate_sw07(n_draws=5000, n_chains=2)
""",
mount_drive=True,
save_path="offload.ipynb",
output_filename="sw07_result.pmz",
)
# 2. Show the upload instructions (HTML card in Juno / Jupyter, plain text in a terminal)
show_colab_offload_dialog("offload.ipynb")
# 3. When the cartridge comes back from Google Drive, load it into the local session:
# posterior = load_colab_result("sw07_result.pmz")
The narrative LLM features (score_llm, llm_prob_kernel) run on a local
model — no API key, no paid API, $0. Everything else in puremacro is already
free; this closes the last paid gap.
Install an engine once (any one):
pip install "puremacro[local-llm]" # llama.cpp (any OS) + mlx-lm on any Mac; MLX itself runs only on Apple silicon
# or install Ollama (https://ollama.com) — no Python deps — then: ollama pull qwen3:4b
Then swap in a local backend (same signatures as the paid backends):
# requires: a local LLM engine (llama-cpp-python or mlx-lm) and a scored `records` corpus
from puremacro.narrative.scoring import score_llm, LocalBackend
events = score_llm(records, backend=LocalBackend("default", engine="auto"))
from puremacro.narrative.indices import llm_prob_kernel, LocalProvider
idx = llm_prob_kernel(records, provider=LocalProvider("default"),
category="economic uncertainty")
engine="auto" picks the best installed engine (Apple GPU via MLX → llama.cpp →
a running Ollama server → an OpenAI-compatible server on port 8080 or 1234; for
any other server pass base_url=). Models: "default" is the reference model
qwen3-4b; "small" (qwen3-1.7b) and "large" (qwen3-8b) are the other
rungs, and the 4.8.1 names (qwen2.5-3b-instruct, gemma2-2b, llama3.2-3b,
phi3.5) or any raw engine model id still work. Answers are cached by model and
prompt, so PUREMACRO_LLM_MODE=cache scores a corpus that was scored once
anywhere, with no engine installed. See docs/local_llm.md,
puremacro/examples/narrative_local_llm.py and the local_llm_uncertainty
notebook. (Local inference runs on desktops. MLX is selected on the iPad under Juno,
where mlx and mlx-lm import; a completion there is not yet verified on a device.
Inside the browser playground only the cache answers.)
The runtime promise is: only numpy + scipy + pandas + matplotlib
ever get imported by the estimator code that ships in the wheel.
statsmodels, linearmodels, arch and pypdf are dev-only /
extras-only or lazy-imported behind a guard.
One further package, requests, is declared as a base dependency in
pyproject.toml (five in all), because the fetch layer and the narrative
sources import it at module level, even though it never touches the
estimator path. It is pure Python and, like the other four, ships with
Pyodide. Every base dependency must: the JupyterLite playground installs
with PyPI fallback disabled, and tests/test_pyodide_compat.py enforces it.
The file-format engines are the optional [io] extra (also included in
[data] and [dev]):
pyarrow — the parquet engine pandas.read_parquet needs (cache,
fetch.labor*, shock_atlas, build_panel, and the ENOE parquet
datasets of course lessons 08 and 24). Without it, cache falls back to
the pyarrow-free .pmz store.openpyxl — the .xlsx engine pandas.read_excel needs. Eighteen
shipped modules read Excel: the EPU, WUI, JLN, LMN, Fernald, GPR and
World Bank Pink Sheet fetchers among them.Both were base dependencies until 3.3.0. They moved because neither ships
with every Pyodide distribution, so either one broke the playground's
install. The risk that made openpyxl a base dependency in the first place
(build_all swallowing each failure into a print and returning a panel
quietly missing most of its uncertainty proxies) is now handled at the
source: build_all, the shock atlas and the climate panel check for the
engines before doing any work and fail with the install command.
See ARCHITECTURE.md → "Pyodide-compatibility contract" for the full
rationale.
The regression test is tests/test_pyodide_compat.py — it walks every
shippable submodule and asserts no forbidden module lands in
sys.modules. If you add a new optional dependency, follow the
existing lazy-import pattern (see narrative.scoring.llm or
fetch._seasonal._x13_arima_analysis for the canonical examples).
First 5 minutes — offline, no data files, no API key. The quickest check that your install works (a sign-restricted SVAR on a synthetic 3-variable DGP; no network, no data, fixed seed):
python -m puremacro.examples.sign_restrictions_uhlig
Or, in Python, on a synthetic system you build in three lines:
import numpy as np
import pandas as pd
import puremacro as pm
# A small synthetic 3-variable system (no data files, no API key).
rng = np.random.default_rng(0)
T = 200
Y = rng.standard_normal((T, 3)).cumsum(0) # ndarray, shape (T, 3)
# Cholesky-identified SVAR with 90% residual-bootstrap bands.
from puremacro.var.identify import cholesky_svar
res = cholesky_svar(Y, p=2, horizon=20, n_boot=500, ci=0.90)
print(res.summary())
res.plot(target_idx=0, shock_idx=0) # 1-line IRF plot with confidence bands
print(res.to_latex(target_idx=0, shock_idx=0)) # Camera-ready LaTeX table
# Single-country LP-HAC: response of y to a synthetic shock.
panel = pd.DataFrame({"y": Y[:, 0], "shock": rng.standard_normal(T)})
from puremacro.lp import lp_hac
irf = lp_hac(panel, y="y", x="shock", horizon=20, lags=2, ci=0.90)
print(irf.summary())
irf.plot(title="Response of y to Structural Shock")
print(irf.to_latex()) # Camera-ready LaTeX table
Optional API keys are resolved centrally (none are needed for the synthetic examples above):
from puremacro import credentials
credentials.status() # see what's configured (no values leaked)
# credentials.require("fred") # raises with a signup URL if the key is missing
If you are transitioning from Stata, MATLAB/Dynare, or statsmodels:
| Task / Estimator | Stata | MATLAB / Dynare | statsmodels / linearmodels | puremacro |
|---|---|---|---|---|
| Cholesky SVAR | var y1 y2, lags(1/4) + irf create | varm / VAR Toolbox | VAR(Y).fit(4).irf(20) | var.identify.cholesky_svar(Y, p=4, horizon=20) |
| Blanchard–Quah SVAR | svar y1 y2, lreq(...) | VAR Toolbox bq_svar | SVAR(..., svar_type='B') | var.identify.bq_svar(Y, p=4, horizon=20) |
| Sign Restrictions | User plugin | Rubio-Ramírez / VAR Toolbox | — | var.identify.sign_restrictions(Y, restrictions={0: [+1, -1]}, p=4) |
| Proxy / External IV SVAR | svariv | Mertens & Ravn SVAR-IV | — | var.identify.proxy_svar(Y, p=4, instrument_series=z) |
| Local Projections (HAC) | jorda / manual OLS | Jordà (2005) code | OLS(y_h, X).fit(cov_type='HAC') | lp.lp_hac(df, y="y", x="shock", horizon=20, lags=4) |
| State-Dep LP-IV (Ramey-Zubairy) | manual 2SLS interaction | — | — | lp.lp_state_dep_iv(df, y="y", x="g", z="news", state="u") |
| Panel LP (Driscoll–Kraay) | xtscc | Panel LP toolbox | PanelOLS(..., cov_type='driscoll-kraay') | lp.panel_lp_dk(df, y="y", x="z", unit_col="id", time_col="t") |
| Dynamic Panel GMM | xtabond y x, lags(p) twostep vce(robust) noconstant | Arellano–Bond MATLAB | — | dynpanel.ab_gmm(y, panel_id, time_id, lag_dep_var=p, X_exog=X, collapse=False) |
| Staggered DiD | csdid y, ivar(id) time(t) gvar(g) | — | — | did.callaway_santanna(df, unit="id", time="t", outcome="y", treat_time="g") |
End-to-end replications of canonical papers and pedagogical showcases live under notebooks/
and puremacro/examples/:
69), ordered .mod commands, Sylvester conditioning, separable Chebyshev policies and constrained Newton–Krylov transitions (70), and the complete frontier suite uniting forward AD, sequence-space HANK, pruned higher-order OccBin, mixed-frequency nowcasting, continuous-time HJB, network/spatial general equilibrium, and self-auditing PolicyStudio counterfactual grids (71).72).73); functional and quantile distributional local projections with exact adding-up, bridge identity checks, Adaptive Tempered SMC, and nonlinear sequence-space transitions (74); Baqaee–Farhi 2nd-order CES supply-chain bottlenecks, dynamic spatial migration hat-algebra, integrated multi-region climate IAM, and self-auditing PolicyStudio interactive reporting (75); and sequence-space Ramsey policy, precautionary ZLB time iteration, SVAR counterfactuals, and practitioner macro-regime portfolio workflows (76).66), Huggett (1993) and Aiyagari (1994) tables re-solved, with numerical-error bounds, an independent solver and the Markov chain's effect (67), and third-order perturbation checked tensor by tensor against closed forms and live Dynare 7.0 (68).59), interactive Double ML IRM and DML-IV (60), trade and HANK policy simulators (61), flexible trade CGE building blocks (62), Hicksian tariff games with audited equilibrium recovery (63), spectral bounds and Keller continuation (64), and GVC cost cascades with Hicksian EV/CV attribution ().docs/quickstart.md — 2-minute quickstart covering core estimators and publication workflows.docs/autodiff_and_protocol.md — Forward DualArray automatic differentiation and the unified DifferentiableModel protocol across DSGE, HANK BlockDAG, DFM nowcast, and spatial/climate models (puremacro._autodiff, puremacro.core.protocol; Spanish twin under docs/es/).docs/smc_inference.md — Vectorized Adaptive Tempered Sequential Monte Carlo (SMC), Langevin/MALA gradient mutation, and marginal data density estimation (puremacro.inference.smc, puremacro.dsge.hank_estimate; Spanish twin under docs/es/).docs/hank_ssj_and_aggregate_risk.md — Compositional BlockDAG Sequence-Space Jacobians, state-dependent Broyden nonlinear HANK transitions, and 2nd-order aggregate risk perturbation with cross-sectional state reduction (puremacro.dsge.ssj_dag, puremacro.dsge.hank_nonlinear, puremacro.dsge.occbin_nonlinear; Spanish twin under docs/es/).docs/sovereign_default.md — Quantitative sovereign default with short- and long-term debt (Arellano, Chatterjee–Eyigungor), DualArray bond schedule gradients, Mendoza collateral-constrained sudden stops, and haircut vs. maturity-extension restructuring (puremacro.sovereign; Spanish twin under docs/es/).docs/brief_sovereign_risk.md — Sovereign-risk policy brief: illustrative comparative statics of reentry probability, income volatility and persistence in the public Arellano calibration, with 95% intervals and grid changes beside every number (python -m puremacro.examples.first_brief_sovereign; Spanish twin under docs/es/).docs/first_brief_fiscal.md — Fiscal policy brief: the government spending multiplier with and without slack on the Ramey-Zubairy historical data, with Anderson-Rubin sets for weak cells, an experiment without WWII and a concentration diagnostic (python -m puremacro.examples.first_brief_fiscal; Spanish twin under docs/es/).docs/distributional_lp.md — Functional, quantile, and distributional local projections with exact cross-sectional adding-up constraints and empirical-to-structural HANK decile IRF bridges (puremacro.lp.distributional; Spanish twin under ).__init__.py::__all__;
the top-level puremacro package re-exports __version__, build_climate_panel, and build_financial_panel.ARCHITECTURE.md § Result-
object standard). DataFrames returning named columns are exempt.X'X, non-PD
Σ, BK violations, and ill-conditioned bootstrap draws raise or warn
with a message naming the calling function and the likely cause.puremacro is MIT-licensed, but the wheel also carries a table adapted from the
OECD Inter-Country Input-Output (ICIO) tables (2023 edition, reference year
2019), so puremacro.trade calibrates without asking you for a file. The OECD
terms of use permit reuse and adaptation with attribution.
If you publish results computed from it, cite the OECD — "OECD (2023), OECD Inter-Country Input-Output Tables, http://oe.cd/icio" — not puremacro. The OECD is not affiliated with this project and does not endorse it. The MIT licence covers puremacro's code, not this third-party data.
The full notice, with the concordance, the country rule, the checksums and the
command that rebuilds the file, ships as puremacro/trade/_datafiles/SOURCES.md.
The 6.0.0 source tree includes puremacro.trade and its MRIO modules, restored in
5.2.0, plus the additions and fixes listed in the changelog.
For published distributions, check PyPI.
Release 4.8.1 is the last one with the research
studies removed in 5.x (pip install "puremacro<5"). The stability tiers
identify stable, experimental and best-effort surfaces. Structural calculations
have additional validation limits.
CI runs the default suite across three operating systems and Python 3.11–3.13,
the static Pyodide contract, public-API and version checks, minimum-Python syntax
checks, and mkdocs build --strict. Pages deployment runs on pushes to main.
Tag-triggered PyPI publication waits for CI and the slow, reference and replication
tests; complete notebook execution runs weekly and on demand. See
the release procedure. Run python tools/release_check.py locally
before tagging; the examples-gallery and real-Pyodide gates remain opt-in.
When a released version has returned a wrong number, it is recorded in
docs/ADVISORY.md, with the condition under
which the error vanishes so you can rule your own run in or out.
dpModel.solve()docs/dp.mdfetch.micro, fetch.registry) —
Census ACS PUMS and CPS, the Fed's SCF and INEGI's ENIGH with their survey
designs, so means, quantiles, ratios, Ginis and top shares come with
replicate-weight or Taylor standard errors; one table of every data source
with its key, terms and redistribution status; see docs/micro_data.md.text, narrative) — multilingual tokenizer that keeps
numbers, sparse document-term matrices with TF-IDF and country-by-period
panels, and a stored, exportable narrative corpus; see docs/text_analysis.md.la_lp_ivpuremacro.lpfetch.realtime.{banxico,inegi,bcb,bcch}) — Banco de México (SIE), INEGI (BIE), Banco Central do Brasil (SGS) and Banco Central de Chile (SIETE), with separate user and password resolution for BCCh (PASSWORD_ENV_VARS, get_password), a configurable schema-drift canary (DRIFT_POLICIES, handle_drift), use_cache=False that really bypasses the cache, and offline .pmz cartridges. These four sources publish only the current edition, so their vintage date is the local snapshot date and revision history accumulates only across repeated captures on different days. Catalog entries not yet confirmed against the live API are marked VERIFY_ONLINE.docs/data_ecosystem.mdinstruments.*) — instrument registry +
composition + external loaders (FRED API key path); backbone of the
LP-IV machinery.bartik.*) — shares, sensitivities,
Rotemberg weights, county-level EPU exposure, and shift_share_iv
(2SLS with Adão-Kolesár-Morales shock-level standard errors).spatial.*) — spatial weights (contiguity,
k-NN, distance decay, economic flows), Moran's I / Geary's C, Conley
spatial HAC and the Hsiang space-time HAC behind
panel_lp(cov_type="conley"), the cross-section models (sar, sem,
sdm, slx) with the lm_spatial_tests specification battery and
LeSage-Pace direct / indirect / total impacts (spatial_effects),
spatial panels (spatial_panel) and spatial local projections
(spatial_lp); plus spillover-robust DiD over distance rings
(did.spatial_did) and the Pesaran Global VAR (var.gvar); see
docs/spatial.md and docs/gvar.md.klems), BIS NEER
aggregator (bis_neer), G9 homogeneous-vintage splice
(long_panel), Gollin labor share (labor_share), real-time
vintages (vintages), seasonal adjustment (sa).labor_flows) and 4-state F/I/U/N transitions from ENOE microdata
for Mexico (labor_flows_enoe).| Synthetic DiD | sdid y id t d, vce(placebo) | synthdid R package | — | did.synthetic_did(df, unit="id", time="t", outcome="y", treat_time="g", se_method="placebo") |
| Factor-Augmented VAR (FAVAR) | — | BBE (2005) MATLAB | — | var.favar(panel_df, policy_series, n_factors=3, horizon=20) |
| Value Function Iteration | — | VFIToolkit ValueFnIter_Case1 | — | vfi.VFIProblem(a_grid, z_grid, P_z, return_fn, beta).solve() |
| Linear DSGE (QZ / BK) | — | Dynare stoch_simul / Klein solab | — | dsge.klein.klein_solve(A, B, C, n_pre=...) |
| DSGE from equations / .mod | — | Dynare .mod file | — | dsge.load_mod("rbc.mod") / dsge.build_dynare(eqs) |
| DSGE 2nd-Order Pruning | — | Dynare stoch_simul(order=2, pruning) | — | dsge.build_dynare(eqs, order=2) / m.solve_second_order() |
puremacro-dynare CLI | — | dynare model.mod command-line | — | puremacro-dynare model.mod --order 2 --fevd --plot |
| OccBin (ZLB / piecewise) | — | Dynare occbin_solver / Guerrieri & Iacoviello | — | dsge.solve_occbin(m_normal, m_zlb, constraint, shocks) |
| Non-Linear Perfect Foresight | — | Dynare simul (Boucekkine-Juillard) | — | dsge.solve_perfect_foresight(m, shocks=shocks, periods=100) |
| Bayesian DSGE (MCMC) | — | Dynare estimation(...) (Metropolis-Hastings) | — | dsge.estimate_dsge_bayesian(m, data, priors, n_draws=10000) |
| Sequence-Space Fake News | — | SSJ (Auclert et al. 2021) Python/Julia | — | models.fake_news_algorithm(T=40) / models.simulate_targeted_transfer(...) |
| GLS Unit Root (DF-GLS) | dfgls y, maxlag(4) | ERS (1996) code | adfuller | unit_root.dfgls_test(y, regression="ct") |
| Seasonal Adjustment | x13 y | X-13 wrapper | STL / x13 | sa.stl_sa(y) / sa.x11_sa(y) |
| Continuous Collocation / FEM | — | VFIToolkit / Miranda-Fackler | — | vfi.CollocationProblem(domain, orders=7) / vfi.FEMProblem(...) |
| Deep Macro PINNs (10+ states) | — | — | — | vfi.DeepMacroModel.multi_country_growth(...) / vfi.solve_deep_macro(...) |
| Spatial Gravity GE | — | Allen-Arkolakis replication | — | spatial.AllenArkolakisModel.from_coordinates(...) |
6556), multi-constraint OccBin coupled with DML (57), and Latin American real-time macro vintages (58).51), continuous transition dynamics under MIT shocks (52), IFT parameter sensitivities & GMM estimation (53), and 10-state Deep Macro PINNs (54).47), real-time DFM nowcasting & news decomposition (48), macroprudential GaR & systemic connectedness (49), and tri-method fiscal multipliers & sovereign DSA (50).43), HANK sequence-space bridge from .mod (44), optimal discretionary policy vs commitment & news shocks (45), and particle filtering with MS-DSGE (46).41, 42), Bloom 2009 (bloom2009.py), Mertens-Ravn narrative SVAR (svariv_mertens_ravn.py), Romer-Romer monetary narrative (romer_romer_*.py), and ~75 more.
All notebooks strictly adhere to the Pyodide contract and the 7-section pedagogical architecture in notebooks/_TEMPLATE.md.docs/es/docs/network_spatial_climate_iam.md — Baqaee–Farhi 2nd-order production network aggregation, Caliendo–Parro–Rossi-Hansberg dynamic spatial migration hat-algebra, multi-region spatial climate IAM, and self-auditing PolicyStudio interactive HTML5/SVG export (puremacro.trade.network_macro, puremacro.spatial.dynamic_hat, puremacro.climate.spatial_iam, puremacro.study.studio; Spanish twin under docs/es/).docs/vfi_continuous_projection.md — Continuous state-space dynamic programming: Chebyshev polynomial collocation and Finite Element Galerkin projection with Fischer-Burmeister borrowing constraints.docs/vfi_splines_and_sparse_grids.md — Shape-preserving cubic B-splines, Schumaker quadratic splines, and Smolyak high-dimensional sparse grids ($d \in [2, 6]$).docs/dp.md — Declarative dynamic programming (puremacro.dp): models as equations, compiled to VFI, EGM and DC-EGM, continuous-time HJB and Krusell-Smith (Spanish twin under docs/es/).docs/micro_data.md — The data-source registry and survey microdata with design-based standard errors (ACS, CPS, SCF, ENIGH), and the SCF-calibrated Aiyagari example (Spanish twin under docs/es/).docs/text_analysis.md — Narrative corpus storage and export, the multilingual tokenizer and sparse document-term matrices (Spanish twin under docs/es/).docs/dcegm.md — Discrete Choice Endogenous Grid Method (DC-EGM) with fast Upper Envelope filtering for non-convex dynamic programming.docs/vfi_continuous_equilibrium.md — Young (2010) continuous stationary wealth distributions, mass conservation, and Aiyagari general equilibrium factor market clearing.docs/vfi_continuous_transition.md — Non-linear continuous transition dynamics under unexpected MIT shocks and sequence-space Broyden solvers.docs/vfi_analytic_gradients.md — Parameter gradients and Jacobians via the Implicit Function Theorem (IFT) for structural GMM/SMM estimation, covering representative-agent projections and the canonical continuous Aiyagari equilibrium.docs/deep_macro.md — Deep Macro & Physics-Informed Neural Networks (PINNs) in pure NumPy for ultra-high-dimensional dynamic models (10+ continuous states) via ergodic sampling.docs/spatial_and_trade_ge.md — Quantitative spatial economics and gravity trade GE: Caliendo-Parro (2015) exact hat algebra with input-output linkages and tariffs, Allen-Arkolakis (2014) economic geography with labor mobility, agglomeration and congestion, and the bundled OECD ICIO table with its attribution.docs/STRUCTURAL_VALIDATION_STATUS.md — Structural solver validation status: per-surface acceptance contracts, evidence and remaining limits for the DSGE and VFI solvers (Spanish twin under docs/es/).docs/dsge_stacked_newton.md — Matrix-free stacked-time Newton-Krylov for perfect-foresight systems (puremacro.dsge.stacked_newton): preconditioners, acceptance rule, horizon comparison and what is and is not validated (Spanish twin under docs/es/).docs/trade_accounting.md — Producer/purchaser accounting for the multi-country trade model (puremacro.trade): tariff valuation, fiscal receipts, the fixed-foreign-saving closure, the numeraire and the independent two-country check (Spanish twin under docs/es/).docs/trade_welfare.md — Hicksian consumption welfare (compute_hicksian_welfare): expenditure-function EV/CV between two trade equilibria and the exact endpoint attribution to prices, factor income and fiscal transfers (Spanish twin under docs/es/).docs/trade_policy.md — Hicksian tariff policy (metric="hicksian_ev") and audited equilibrium recovery (solve_policy_equilibrium): one comparison baseline, what convergence establishes and the Newton → hybrid → continuation chain (Spanish twin under docs/es/).docs/trade_continuation.md — Audited parameter continuation and exact-target multistart (puremacro.trade.continuation) (Spanish twin under docs/es/).docs/trade_stability.md — Reduced local-stability diagnostic of a relative-factor-price tatonnement (puremacro.trade.stability, experimental) (Spanish twin under docs/es/).docs/policy_simulators.md — Macroeconomic policy simulators & interactive browser labs: Caliendo-Parro (2015) quantitative trade general equilibrium, HANK vs. RANK monetary transmission, and client-side WebAssembly laboratories (Spanish twin under docs/es/).docs/trade_household.md — Household preferences with exact expenditure-function welfare (puremacro.trade.household): fixed baskets, Cobb-Douglas, Stone-Geary and CES demand, Slutsky matrices and bridges to trade equilibria (Spanish twin under docs/es/).docs/trade_distributional.md — Distributional household incidence (puremacro.trade.distributional): exact static EV and CV of household groups at supplied prices and incomes, partial equilibrium by design (Spanish twin under docs/es/).docs/trade_ces_newton.md — Exact block Newton for nested CES technology on consistent accounting (puremacro.trade.ces_newton): exact Jacobian, tariff homotopy and an independent flow certificate (Spanish twin under docs/es/).docs/trade_condensed.md — Condensed one-factor Leontief tariff model (puremacro.trade.condensed): exact 2N condensation, raw-flow certificate and numeraire-free measures (Spanish twin under docs/es/).docs/trade_mrio.md — Native MRIO tables (puremacro.trade.mrio): OECD ICIO, FIGARO and EXIOBASE readers with provenance, the regularization contract and exact aggregation (Spanish twin under docs/es/).docs/trade_dynamic.md — Dynamic MRIO with sector-specific capital (puremacro.trade.dynamic): steady states, perfect-foresight transitions, horizon acceptance and the determinacy diagnostic (Spanish twin under docs/es/).docs/trade_gpu.md — Hardware backends for trade and spatial GE (puremacro.trade.gpu): PyTorch and Apple MLX batched Jacobians, the float64 policy and homotopy continuation (Spanish twin under docs/es/).docs/data_ecosystem.md — Global macro data ecosystem: emissions, energy transitions, commodity benchmark suites, international financial stability, and modular panel builders (build_climate_panel, build_financial_panel).docs/notebooks.md — Complete showcase catalog (notebooks 00–76), pedagogical 7-section architecture, and applied policy suites.docs/dsge_build.md — DSGE models from equations, native Dynare .mod loader, 2nd-order pruning, puremacro-dynare CLI, OccBin ZLB, non-linear relaxation, and Bayesian MCMC.docs/dsge_workflows.md, docs/dsge_nonlinear.md, docs/dsge_transitions.md, docs/dsge_global.md — Nonlinear specifications, ordered deterministic commands, continuation and announcements, global policies and local accuracy audits (Spanish mirrors under docs/es/).docs/models.md — Structural models: Sequence-Space HANK, Fake News algorithm, targeted transfers, and DMP search-and-matching.docs/monetary_transmission.md — The HANK vs. RANK monetary transmission simulator (models.monetary_transmission) and its browser lab in plain JavaScript (Spanish twin under docs/es/).docs/narrative_sign_svar.md, docs/honest_did.md, docs/smooth_lp.md, docs/hank_nonlinear.md, docs/gertler_karadi.md, docs/bvar_sv.md — the six 2.3 feature guides (each with a Spanish twin under docs/es/).docs/spatial.md — spatial weights, Moran's I / Geary's C, Conley spatial HAC (cross-section and panel local projections), the cross-section models (sar, sem, sdm, slx), the lm_spatial_tests specification battery, LeSage-Pace impacts (spatial_effects), spatial panels (spatial_panel), spatial local projections (spatial_lp), spillover-robust DiD (did.spatial_did) and shift-share IV with Adão-Kolesár-Morales errors (Spanish twin under docs/es/).docs/gvar.md — Global VAR (Pesaran-Schuermann-Weiner): country VARX* blocks, trade-weighted star variables, the stacked solve and GIRFs (var.gvar; Spanish twin under docs/es/).docs/var.md — Reduced-form VAR, SVAR identification (Cholesky, signs, narrative, proxy/IV), FAVAR, and bootstrap bands.docs/lp.md — Local Projections guide (LP-HAC, LP-IV, State-Dependent LP-IV, Panel LP, LPResult).docs/did.md — Modern Difference-in-Differences (Callaway-Sant'Anna, Sun-Abraham, Borusyak-Jaravel-Spiess, Synthetic DiD).docs/nowcast.md — GDP Nowcasting (Mixed-frequency dynamic factor models, ragged edges, news decomposition).docs/climate.md — Climate macroeconomics: Nordhaus DICE forward simulator and Social Cost of Carbon accounting.docs/forecast.md — Penalized macroeconomic forecasting: Coordinate-descent Elastic Net and Adaptive Lasso.docs/reporting.md — Publication reporting pipeline (LaTeX tabular, Typst tables, Markdown, significance stars).docs/tablet.md — Running on iPad, Juno, and WebAssembly, with Google Colab compute offloading.docs/local_llm.md — Local language models (puremacro.llm) on the iPad, Mac, Windows, Linux and Colab: engines, the Qwen3 model ladder, modes and the shippable completion cache (Spanish twin under docs/es/).docs/benchmarks.md — Performance and computational benchmarks across econometric engines.docs/national_accounts.md — OECD Quarterly National Accounts extraction, deflators, and accounting identities.docs/real_time_data.md — Real-time data vintages, revision triangles, and Mankiw-Shapiro news-vs-noise testing.docs/long_panel.md — Historical long national accounts panel (ratio-spliced Spanish and Japanese series).docs/es/ — Complete parallel documentation suite in native academic Spanish.ARCHITECTURE.md — module map, stability tiers, Pyodide contract, result-object standard.CHANGELOG.md — per-release diff, including internal-only refactors.docs/ADVISORY.md — correctness advisories: released versions that returned a wrong number.