xad-rs 0.8.1

Exact automatic differentiation for Rust — forward-mode, reverse-mode, first- and second-order, with named variable support and a unified `Real` trait for mode-agnostic numerical code
Documentation

xad-rs

Crates.io Docs.rs License: AGPL-3.0-or-later MSRV: 1.85

Exact automatic differentiation for Rust. Forward and reverse mode, first and second order, with name-keyed gradient readback for quant-finance workloads. No finite-difference error, no symbolic manipulation — just the chain rule, applied to your program as it runs.

Unofficial Rust port of the C++ XAD library. Not affiliated with the upstream project.

Documentation

  • Theory — derivations and decision-tree guidance: docs/README.md.
  • API — generated rustdoc: docs.rs/xad-rs.
  • Examples — runnable, cross-checked against analytic answers: examples/.
[dependencies]
xad-rs = "0.8"

MSRV: Rust 1.85 (edition 2024).

How it works

Write your numerical code once against the trait Real. Instantiate it at the call site against the concrete mode that matches your problem shape — f64 for no-AD, Jet1 for one directional derivative, Jet1Vec for a full gradient in one forward pass, AReal + Tape for a gradient over many inputs, Jet2/Jet2Vec for second order.

use xad_rs::prelude::*;

fn quadratic<R: Real>(x: &R) -> R {
    x.clone() * x.clone() + R::from(2.0_f64) * x.clone() + R::from(1.0_f64)
}

let v_passive = quadratic(&3.0_f64);                  // primal, no AD
let v_jet1    = quadratic(&Jet1::new(3.0_f64, 1.0));  // forward, 1st order
let v_jet2    = quadratic(&Jet2::variable(3.0_f64));  // forward, 1st + 2nd
// Reverse mode: build inside an active Tape (see below).

Pick a mode

Your problem Mode Read more
Just the value, no derivatives f64 01 — AD as a discipline
One input direction, any number of outputs Jet1<T> 02 — Forward mode & dual numbers
Full gradient in one forward pass (n small) Jet1Vec 02 — Forward mode & dual numbers
Full gradient, n ≫ 4 inputs, scalar output Tape + AReal<T> 03 — Reverse mode & taped adjoints
Gamma / diagonal Hessian along one direction Jet2<T> 04 — Second-order & k-jets
Full n × n Hessian, n ≲ 50 Jet2Vec via compute_full_hessian 04 — Second-order & k-jets
Greeks by name (delta, vega, ...) instead of by index Any mode's Named* variant 05 — Named variables & quant use cases

Reverse mode breaks even with forward around n ≈ 4 inputs. For n ≫ 4 (e.g. a 30-input swap pricer), reverse is dramatically faster.

Real is implemented for f64, AReal<f64>, Jet1<f64>, and Jet2<f64>. Jet1Vec / Jet2Vec lack the impl because the trait's From<f64> requires knowing the tangent length; use them directly when you want a full gradient or Hessian in one pass.

By mode — quick recipes

Reverse (gradient via tape)

use xad_rs::{AReal, Tape, math};

let mut tape = Tape::<f64>::new(true);
tape.activate();

let mut x = AReal::new(3.0);
let mut y = AReal::new(4.0);
AReal::register_input(std::slice::from_mut(&mut x), &mut tape);
AReal::register_input(std::slice::from_mut(&mut y), &mut tape);

// f(x, y) = x^2 * y + sin(x)
let mut f = &(&x * &x) * &y + math::ad::sin(&x);
AReal::register_output(std::slice::from_mut(&mut f), &mut tape);
f.set_adjoint(&mut tape, 1.0);
tape.compute_adjoints();

assert!((x.adjoint(&tape) - (2.0 * 3.0 * 4.0 + 3.0_f64.cos())).abs() < 1e-12);
assert_eq!(y.adjoint(&tape), 9.0);

Forward (full gradient in one pass)

use xad_rs::Jet1Vec;

let (x, y) = (Jet1Vec::variable(3.0, 0, 2), Jet1Vec::variable(4.0, 1, 2));
let f = &(&x * &x) * &y;  // x^2 * y

assert_eq!(f.partial(0), 24.0);  // df/dx = 2xy
assert_eq!(f.partial(1),  9.0);  // df/dy = x^2

Second order (gamma along one direction)

use xad_rs::Jet2;

let x = Jet2::variable(2.0_f64);
let y = x * x * x;                            // x^3
assert_eq!(y.first_derivative(),  12.0);      // 3x^2
assert_eq!(y.second_derivative(), 12.0);      // 6x

For the full dense Hessian in one pass, use Jet2Vec via compute_full_hessian (see examples/hessian.rs and the chapter 04 cost model).

Named (greeks by name)

Forward, declare-and-scope:

use xad_rs::{NamedForwardTape, NamedForwardScope};

let mut ft = NamedForwardTape::<f64>::new();
let spot_h   = ft.declare_jet1vec("spot",   100.0);
let strike_h = ft.declare_jet1vec("strike", 105.0);
let scope: NamedForwardScope<f64> = ft.into_scope();

let spot   = scope.jet1vec(spot_h);
let strike = scope.jet1vec(strike_h);
let ratio  = spot / strike;

assert!((ratio.partial("spot") - 1.0 / 105.0).abs() < 1e-14);

Reverse, with a name-keyed gradient as an IndexMap:

use xad_rs::NamedTape;

let mut tape = NamedTape::new();
let x = tape.input("x", 3.0);
let y = tape.input("y", 4.0);
let _registry = tape.freeze();

let f = &(&x * &x) * &y + x.sin();
let grad = tape.gradient(&f);

assert!((grad["x"] - (2.0 * 3.0 * 4.0 + 3.0_f64.cos())).abs() < 1e-12);
assert!((grad["y"] - 9.0).abs() < 1e-12);

Composite Jacobian / Hessian helpers

use xad_rs::{compute_jacobian_rev, compute_hessian};

// f: R^2 -> R^2, f(x, y) = [x*y, x + y]
let _jac = compute_jacobian_rev(&[3.0, 5.0], |v| {
    vec![&v[0] * &v[1], &v[0] + &v[1]]
});

// g: R^2 -> R, g(x, y) = x^2 * y + y^3
let _hess = compute_hessian(&[2.0, 3.0], |v| {
    let x2 = &v[0] * &v[0];
    let y3 = &v[1] * &v[1] * &v[1];
    x2 * &v[1] + y3
});

For the exact Hessian (machine precision, one forward pass instead of n reverse sweeps), reach for compute_full_hessian — it returns a NamedHessian with the value, gradient, and full ndarray::Array2<f64> Hessian.

Examples

Run with cargo run --release --example <name>.

Example What it demonstrates
swap_pricer.rs 30-input IRS DV01 and gamma via reverse, Jet1Vec, and Jet2
fx_option.rs Garman–Kohlhagen FX option greeks, three AD modes cross-checked against analytic
fixed_rate_bond.rs YTM, duration, convexity
jacobian.rs 4×4 Jacobian via reverse mode
hessian.rs 4×4 Hessian via compute_full_hessian with analytic cross-check
adjoint_first_order.rs Full 4-input gradient in one reverse sweep
fwd_adj_second_order.rs Forward-over-adjoint: gradient + Hessian row via Jet2Vec

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.
  • QuantLibAAD — the XAD-instrumented QuantLib build; reference for the AAD-on-quant-finance patterns the financial examples in this crate are modelled after.
  • num-traits, indexmap, and ndarray for the underlying primitives.