ddx-core
Engine-neutral symbolic differentiation of SQL scalar expressions — the v1
core of ddx, "autograd for composable
databases." Write calculus directly in SQL and let the engine evaluate the
derivative per row (the relational equivalent of jax.vmap(jax.grad(f))):
SELECT i, grad(x * y, x) AS dfdx, grad(x * y, y) AS dfdy FROM g
grad/jvp are markers, not row functions: they carry a differentiation
request through parsing and are always rewritten away before execution.
use Ddx;
use GenericDialect;
let ddx = new;
let out = ddx.rewrite_sql?;
assert_eq!;
The engine differentiates sqlparser::ast::Expr
directly — the AST is the IR, there is no bespoke representation. The primary
dependency is sqlparser, re-exported as ddx_core::sqlparser
so downstream adapters cannot link a mismatched version.
What it supports
+ - * /; the unary chain rule for the trig / inverse-trig / exp / log /
hyperbolic set plus abs; power with a constant base or exponent;
higher-order via nesting; through-aggregate via linearity
(AVG(grad(loss, theta))). Custom unary rules are registrable
(ddx.register("myfn", rule) — the rule supplies f'(u), the engine applies
the chain rule). Anything else is a typed DiffError, never a silently-wrong
number.
Scalar vjp is deliberately not here: the name is reserved for the
query-level reverse-mode operation in ddx-ad (design.md §3.6, §4).
Correctness properties worth knowing
- Identifier folding is per-dialect (
Ddx::for_datafusion()vsDdx::for_duckdb()): unquoted identifiers always fold case; DuckDB folds quoted ones too.grad(Temp*Temp, temp)matches. - Ambiguity is a hard error, not a guess: a
wrtthat can't be pinned syntactically (grad(a.x*b.x, x)) errors rather than differentiating the wrong column. divforces floating-point division (CAST(<numerator> AS DOUBLE)), so integer columns don't silently truncate the derivative.- 0/1-folding follows JAX's
Zero-tangent convention and differs from unfolded SQL only on NULL-bearing rows (documented, tested).
See ../../docs/design.md §3 for the full rationale and
the decision log (F#/G#) behind each of these.
Status
M0 (the scalar core) — implemented. The per-engine adapters (ddx-datafusion,
ddx-duckdb), the Python wheel (ddxdb), and the query-level v2 engine
(ddx-ad) are later milestones; see the design doc's §8.
Licensed under Apache-2.0.