One expression. Nine runtimes.
MathIR takes a single mathematical expression, parses it into an abstract-syntax tree, and emits idiomatic code in nine target languages. Change the tree — all nine outputs update in lockstep.
The problem
Every pricing library eventually faces the same issue: the math needs to run in several places at once. A C# server. A Rust core. A JavaScript harness in the browser. A CUDA kernel. An Excel LAMBDA on a trader's desk. Hand-writing each version is the obvious path and the wrong one — every bug has to be caught nine times, every refinement applied nine times, and the nine implementations drift apart the moment anyone is tired on a Friday afternoon.
The escape hatch is to express the math once as a data structure, then let a family of emitters generate each target language from that single source. That data structure is an abstract-syntax tree, and in this project it's called MathIR.
A worked example
Take the first equation everyone learns in physics — distance under constant acceleration:
Where s is distance travelled, u is initial velocity, t is time, and a is (constant) acceleration. Four variables and a square. Enough structure to show real tree depth without being intimidating.
Parsed as a tree
MathIR parses that single expression into a tree of Binary
(operator), Var (variable), and LitF64
(literal) nodes. The square t2 is represented as
t * t — the IR does not ship a dedicated Pow node
because every target language inlines small integer powers as repeated
multiplication anyway.
Binary("+", "*")
Variable — Var("u")
Literal — LitF64(0.5)
Read it top-down: the root is the + at the top combining
two products. The left product is simply u * t. The right
product is 0.5 * (a * (t * t)) — the outer multiplier
0.5, then the acceleration, then t squared written
explicitly as t * t.
That's the entire structure. No hidden state, no implicit coercions, no operator-precedence magic — the tree is the precedence.
Nine emissions, same tree
Feed that AST through nine emitters and out come nine idiomatic functions. Each one produces what a competent developer in that language would write by hand — no LLM signatures, no foreign syntax tells, no redundant parentheses. Use the language pill above to pick one; the matching card lifts so it's easy to compare against the tree.
pub fn suvat(u: f64, t: f64, a: f64) -> f64 {
u * t + 0.5 * a * t * t
}double suvat(double u, double t, double a) {
return u * t + 0.5 * a * t * t;
}public static double Suvat(
double u, double t, double a)
=> u * t + 0.5 * a * t * t;func suvat(u, t, a float64) float64 {
return u*t + 0.5*a*t*t
}function suvat(u, t, a) {
return u * t + 0.5 * a * t * t;
}function suvat(
u: number, t: number, a: number,
): number {
return u * t + 0.5 * a * t * t;
}def suvat(u: float, t: float, a: float) -> float:
return u * t + 0.5 * a * t * t__device__ double suvat(
double u, double t, double a) {
return u * t + 0.5 * a * t * t;
}SUVAT = LAMBDA(u, t, a,
u*t + 0.5*a*t*t)That's it
Every snippet above was generated by an EmitterLang
class walking the same AST. No snippet was hand-written; no snippet was
hand-edited. If the source expression changes — a new coefficient,
a new variable, a different operator tree — all nine files
regenerate together, and any downstream test that compares the lanes
catches discrepancies automatically.
The reflexlibs.ai pricing catalogue — Black-76, SABR, Heston
Normal — is built the same way. A harder tree, but the same
machinery: one author writes the math; a family of emitters produces the
production code; a triumvirate harness runs the same vectors through
every lane and demands agreement to 1e-12.
SUVAT is the smallest possible version of that story. Pricing Greeks are just a deeper tree.