xad-rs
Exact automatic differentiation for Rust — forward-mode, reverse-mode, first- and second-order, with named variable support for ergonomic gradient readback.
Unofficial Rust port of the C++ XAD library. Not affiliated with the upstream project.
Choosing a mode
| Type | Mode | Order | Best for |
|---|---|---|---|
FReal<T> |
Forward | 1st | 1 input direction, many outputs |
Dual |
Forward, multi-var | 1st | full gradient in one pass |
Dual2<T> |
Forward, 2nd-order | 1st + 2nd | diagonal Hessian / gamma |
AReal<T> + Tape |
Reverse (adjoint) | 1st | many inputs, scalar output |
Dual2Vec |
Forward, dense 2nd | 1st + 2nd (full) | full n x n Hessian, n < ~50 |
Reverse mode breaks even with forward around n ~ 4 inputs. For n >> 4 (e.g. 30-input swap pricer), reverse is dramatically faster.
Every mode also has a named variant (NamedDual, NamedTape, etc.)
that lets you read gradients by variable name instead of positional index.
Installation
[]
= "0.3"
MSRV: 1.85 (Rust edition 2024).
Quick start
Reverse mode
use ;
let mut tape = new;
tape.activate;
let mut x = new;
let mut y = new;
register_input;
register_input;
// f(x, y) = x^2 * y + sin(x)
let mut f = & * &y + sin;
register_output;
f.set_adjoint;
tape.compute_adjoints;
println!; // 2xy + cos(x)
println!; // x^2
Forward mode (full gradient)
use Dual;
let = ;
let f = & * &y; // x^2 * y
assert_eq!; // df/dx = 2xy
assert_eq!; // df/dy = x^2
Second-order derivatives
use Dual2;
let x = variable;
let y = x * x * x; // x^3
assert_eq!; // 3x^2
assert_eq!; // 6x
Named variables
Access derivatives by name — useful in financial models with many risk factors:
use ;
let mut ft = new;
let spot_h = ft.declare_dual;
let strike_h = ft.declare_dual;
let scope: NamedForwardScope = ft.freeze_dual;
let spot = scope.dual;
let strike = scope.dual;
let ratio = spot / strike;
assert!;
Named reverse mode returns gradients as IndexMap<String, f64>:
use NamedTape;
let mut tape = new;
let x = tape.input;
let y = tape.input;
let _registry = tape.freeze;
let f = & * &y + x.sin;
let grad = tape.gradient;
assert!;
assert!;
Jacobian and Hessian
use ;
// f: R^2 -> R^2, f(x, y) = [x*y, x + y]
let jac = compute_jacobian_rev;
// g: R^2 -> R, g(x, y) = x^2 * y + y^3
let hess = compute_hessian;
Dense full Hessian (Dual2Vec)
use Dual2Vec;
let x = variable;
let y = variable;
let f = & + &;
assert_eq!; // d2f/dx2 = 2y
assert_eq!; // d2f/dxdy = 2x
assert_eq!; // d2f/dy2 = 6y
Per-op cost is O(n^2). For n > ~50, prefer seeded Dual2<T> with n passes.
Crate structure
src/
forward/ FReal, Dual, Dual2, Dual2Vec + Named wrappers
reverse/ AReal, NamedAReal, NamedTape
ops/ compute_jacobian_*, compute_hessian, compute_full_hessian
math.rs AD-aware transcendentals (sin, exp, erf, norm_cdf, ...)
tape.rs Reverse-mode tape and thread-local active-tape slot
scalar.rs Scalar trait bound (f32, f64)
registry.rs VarRegistry — ordered name-to-index map
forward_tape.rs NamedForwardTape / NamedForwardScope setup
Examples
| Example | What it demonstrates |
|---|---|
swap_pricer.rs |
30-input IRS DV01 and gamma via reverse, Dual, and Dual2 |
fx_option.rs |
Garman-Kohlhagen FX option Greeks |
fixed_rate_bond.rs |
YTM / duration / convexity |
jacobian.rs |
4x4 Jacobian (reverse mode) |
hessian.rs |
4x4 Hessian with analytic cross-check |
Design notes
- Tape storage is thread-local. One
Tape<T>per thread;NamedTapeis!Send. - Forward mode is allocation-light.
Dualkeeps tangents in a singleVec<f64>with fused, autovectorizable loops. - Zero-alloc operator fast paths. Every
ARealbinary op uses fixed-arityTape::push_binary/push_unary— no intermediateVecper op.
Tests
165 tests covering operator correctness, transcendentals, second-order derivatives, Jacobian/Hessian helpers, named variable readback, and cross-mode consistency.
License
AGPL-3.0-or-later, matching the upstream XAD project. See LICENSE.md.
Acknowledgements
- The C++ XAD library — architectural inspiration and source of the financial examples.
num-traitsfor generic scalar plumbing.