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
//! The `Real` trait — the unified *active*-scalar trait that abstracts
//! over every AD mode this crate ships (reverse via [`crate::AReal`],
//! forward first-order via [`crate::Jet1`], forward second-order via
//! [`crate::Jet2`]) plus the no-AD case (plain [`f64`]).
//!
//! Conceptually, `Real` is the seam that makes one body of numerical
//! code (a pricer, a loss, an ODE step) run under every AD mode without
//! duplication: the trait abstracts the *active scalar* and the chain
//! rule lives inside its operator impls.
//!
//! See also (theory): [`docs/theory/01-automatic-differentiation.md`](https://github.com/sercanatalik/xad-rs/blob/main/docs/theory/01-automatic-differentiation.md).
//!
//! Program your numerical code against `R: Real` once; instantiate
//! against the concrete mode that matches the problem shape at the call
//! site.
//!
//! # Why `Real`?
//!
//! Aligns with the QuantLib convention (`QuantLib::Real`,
//! `QuantLibAAD::Real`, `QuantLibAdjoint::Real`), where the same name
//! denotes the user-facing scalar type regardless of whether the build
//! is plain double or AD-enabled. Same idea here: code written against
//! `Real` reads the same whether it ultimately runs over `f64`,
//! `AReal<f64>`, `Jet1<f64>`, or `Jet2<f64>`.
//!
//! The complementary trait [`crate::Passive`] is the bound on the
//! *underlying* (storage) scalar — typically `f64`. The
//! `Real::Passive` associated type ties an active type back to the
//! passive type it wraps.
//!
//! # In-crate impls (v0.5.0)
//!
//! - `impl Real for f64` (this file) — the no-AD case; methods
//!   delegate to inherent `f64::*`.
//! - `impl Real for AReal<f64>` (in `src/reverse/areal.rs`) —
//!   delegates to `math::ad::*` so operations record on the active tape.
//! - `impl Real for Jet1<f64>` (in `src/forward/jet1.rs`) — delegates
//!   to `math::fwd::*` so the tangent propagates correctly.
//! - `impl Real for Jet2<f64>` (in `src/forward/jet2.rs`) — delegates
//!   to inherent `Jet2<T>::*` methods.
//!
//! Not in v0.5.0: `Jet1Vec`/`Jet2Vec` (length-of-partial-vector
//! incompatibility with the `Default` supertrait bound) and the f32
//! flavours (`From<f64>` is lossy for f32). Tracked as follow-up
//! changes.

use crate::passive::Passive;
use std::fmt::{Debug, Display};
use std::ops::{Add, Div, Mul, Neg, Sub};

/// Unified active-scalar trait — the seam between mode-agnostic
/// numerical code and the concrete AD type it eventually runs over.
///
/// See the module-level docs for the alignment with QuantLib's `Real`
/// and the rationale for not depending on [`num_traits::Float`].
pub trait Real:
    Clone
    + Debug
    + Display
    + PartialEq
    + PartialOrd
    + From<f64>
    + From<i32>
    + Send
    + Sync
    + 'static
    + Neg<Output = Self>
    + Add<Self, Output = Self>
    + Sub<Self, Output = Self>
    + Mul<Self, Output = Self>
    + Div<Self, Output = Self>
{
    /// The underlying passive (non-AD) scalar type — typically [`f64`].
    type Passive: Passive;

    /// Project the active scalar back to its underlying passive value,
    /// stripping any AD machinery.
    fn value(&self) -> Self::Passive;

    /// Natural logarithm.
    fn ln(&self) -> Self;

    /// Exponential.
    fn exp(&self) -> Self;

    /// Square root.
    fn sqrt(&self) -> Self;

    /// Power with an arbitrary-real exponent.
    fn powf(&self, exponent: Self) -> Self;

    /// Power with an integer exponent.
    fn powi(&self, exponent: i32) -> Self;

    /// Sine.
    fn sin(&self) -> Self;

    /// Cosine.
    fn cos(&self) -> Self;
}

// ----------------------------------------------------------------------------
// impl Real for f64 — the no-AD blanket so generic code over `R: Real`
// compiles for plain `f64` without special-casing.
// ----------------------------------------------------------------------------

impl Real for f64 {
    type Passive = f64;

    #[inline]
    fn value(&self) -> f64 {
        *self
    }
    #[inline]
    fn ln(&self) -> Self {
        f64::ln(*self)
    }
    #[inline]
    fn exp(&self) -> Self {
        f64::exp(*self)
    }
    #[inline]
    fn sqrt(&self) -> Self {
        f64::sqrt(*self)
    }
    #[inline]
    fn powf(&self, exponent: Self) -> Self {
        f64::powf(*self, exponent)
    }
    #[inline]
    fn powi(&self, exponent: i32) -> Self {
        f64::powi(*self, exponent)
    }
    #[inline]
    fn sin(&self) -> Self {
        f64::sin(*self)
    }
    #[inline]
    fn cos(&self) -> Self {
        f64::cos(*self)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

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

    #[test]
    fn poly_at_three_under_f64_equals_two() {
        // (x - 1)(x - 2) at x = 3 is 2
        assert_eq!(poly(&3.0_f64), 2.0);
    }

    #[test]
    fn ln_exp_round_trips_under_f64() {
        let x: f64 = 2.5;
        let y = <f64 as Real>::ln(&x);
        let z = <f64 as Real>::exp(&y);
        assert!((z - 2.5).abs() < 1e-12);
    }

    #[test]
    fn value_is_reflexive_on_f64() {
        let x: f64 = 3.5;
        assert_eq!(<f64 as Real>::value(&x), 3.5);
    }
}