Goal
Port the numerical solver core of OG-UK/OG-Core to Rust for speed and
dependency-light embedding, while keeping the PolicyEngine layer in Python. Not
a full rewrite — a hybrid where a Rust crate solves the equilibrium and is
exposed to Python via PyO3, dropped in behind the existing
solve_steady_state / run_transition_path API so nothing upstream changes.
Why hybrid, not a full port
OG-UK is three layers; only one is worth porting:
- PolicyEngine layer (reform → microdata → tax functions) — a large rules
engine with its own ecosystem. Reimplementing is a multi-year project on its
own. Stays Python.
- Numerical core of OG-Core (household Euler equations, firm FOCs, market
clearing, steady-state root-find, TPI iteration) — well-defined math, dense
NumPy/scipy, and where all the compute time goes. The real candidate.
- Calibration / data plumbing (ONS/OBR fetch, demographics, output
mapping) — runs in seconds. Portable but pointless.
Approach
Rust crate macromod-core using ndarray + a Levenberg–Marquardt / Broyden
solver, exposed to Python via PyO3, wired in behind the current public API.
Phases
| phase |
scope |
estimate |
| 1 |
Single-sector steady-state solver + PyO3 bindings + golden-file parity tests vs the Python version |
3–5 working days |
| 2 |
Transition path (60-period backward/forward iteration) |
3–5 days |
| 3 |
Multi-sector (M=8, needs a robust LM solver), age-specific tax-function interpolation |
1–2 weeks |
Roughly 3–5 weeks calendar time with review between phases.
Main cost: numerical parity, not code
Each phase must match Python output within tolerance across a battery of
reforms, and each comparison is a real solve (minutes). Because scipy's and a
Rust root-finder's step paths differ in floating point, we verify economics
(same equilibrium within tolerance), not bit-identical output.
Payoff
- 5–20× faster solver; no scipy/dask fragility.
- Makes an interactive remote MCP server feasible.
- A fast, dependency-light engine PolicyEngine can embed anywhere — including
WASM in the browser (in-page scoring on policyengine.org).
Alternative considered
If the goal is only "make it faster," JAX or tightening existing numba paths
captures much of the win for far less effort. Rust is the right call if the aim
is a portable, embeddable engine.
First step
Prototype phase 1: a macromod-core crate solving the single-sector steady
state with parity tests against Python.
Goal
Port the numerical solver core of OG-UK/OG-Core to Rust for speed and
dependency-light embedding, while keeping the PolicyEngine layer in Python. Not
a full rewrite — a hybrid where a Rust crate solves the equilibrium and is
exposed to Python via PyO3, dropped in behind the existing
solve_steady_state/run_transition_pathAPI so nothing upstream changes.Why hybrid, not a full port
OG-UK is three layers; only one is worth porting:
engine with its own ecosystem. Reimplementing is a multi-year project on its
own. Stays Python.
clearing, steady-state root-find, TPI iteration) — well-defined math, dense
NumPy/scipy, and where all the compute time goes. The real candidate.
mapping) — runs in seconds. Portable but pointless.
Approach
Rust crate
macromod-coreusingndarray+ a Levenberg–Marquardt / Broydensolver, exposed to Python via PyO3, wired in behind the current public API.
Phases
Roughly 3–5 weeks calendar time with review between phases.
Main cost: numerical parity, not code
Each phase must match Python output within tolerance across a battery of
reforms, and each comparison is a real solve (minutes). Because scipy's and a
Rust root-finder's step paths differ in floating point, we verify economics
(same equilibrium within tolerance), not bit-identical output.
Payoff
WASM in the browser (in-page scoring on policyengine.org).
Alternative considered
If the goal is only "make it faster," JAX or tightening existing numba paths
captures much of the win for far less effort. Rust is the right call if the aim
is a portable, embeddable engine.
First step
Prototype phase 1: a
macromod-corecrate solving the single-sector steadystate with parity tests against Python.