Skip to content

Rust port of the OG solver core (hybrid via PyO3) #1

Description

@vahid-ahmadi

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:

  1. 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.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions