xad-rs 0.2.0

Automatic differentiation library for Rust — forward/reverse mode AD, a Rust port of the C++ XAD library (https://github.com/auto-differentiation/xad)
Documentation
//! `labeled` — string-keyed wrappers over the existing AD modes.
//!
//! This module provides name-indexed wrappers around the forward AD modes
//! in the crate ([`FReal`](crate::freal::FReal), [`Dual`](crate::dual::Dual),
//! [`Dual2`](crate::dual2::Dual2)), trading a small ownership overhead
//! (one [`Arc<VarRegistry>`](std::sync::Arc) per wrapped value) for
//! ergonomic gradient readback via string labels instead of positional
//! indices. The wrappers delegate **every** arithmetic operation to the
//! underlying positional type — all derivative values are bit-identical
//! to the positional equivalents, and Criterion benchmarks (Phase 2 of
//! v0.2.0) gate the labeled layer at ≤5% runtime overhead vs positional.
//!
//! # Available with `labeled` feature
//!
//! This entire module is gated behind the `labeled` feature. The default
//! build does not link `indexmap` and exposes none of these types.
//!
//! # Feature matrix
//!
//! - `labeled` — enables [`VarRegistry`], [`LabeledFReal`], [`LabeledDual`],
//!   [`LabeledDual2`].
//! - `labeled-ndarray` — transitively enables `labeled` and adds
//!   `LabeledJacobian` with `ndarray::Array2<f64>` outputs.
//! - `labeled-hessian` — transitively enables `labeled` and `dual2-vec`;
//!   adds `LabeledHessian` and `compute_full_hessian` (Phase 4 integration).
//!
//! # The two-phase forward-mode contract
//!
//! Forward-mode labeled wrappers (`LabeledFReal<T>`, `LabeledDual`,
//! `LabeledDual2<f64>`) are constructed via [`LabeledForwardTape`], the
//! forward-mode parallel of [`LabeledTape`] for reverse mode. The tape
//! owns the [`VarRegistry`] and hands out wrappers through one of two
//! shapes — see [`forward_tape`] for the full design rationale. The
//! canonical usage pattern for `LabeledDual` is:
//!
//! ```
//! use xad_rs::labeled::{LabeledForwardScope, LabeledForwardTape};
//!
//! // 1. Build a tape and declare inputs. `declare_dual` registers the
//! //    name in the pending registry and returns an opaque handle.
//! let mut ft = LabeledForwardTape::new();
//! let spot_h   = ft.declare_dual("spot",   100.0);
//! let strike_h = ft.declare_dual("strike", 105.0);
//! let _vol_h   = ft.declare_dual("vol",      0.2);
//!
//! // 2. Freeze the tape. This consumes the tape, allocates the final
//! //    `Arc<VarRegistry>`, stamps the TLS active-registry slot, and
//! //    returns a scope object holding fully-seeded wrappers.
//! let scope: LabeledForwardScope = ft.freeze_dual();
//!
//! // 3. Retrieve wrappers via the scope's handle lookup. The returned
//! //    reference is borrowed from the scope and stays valid for the
//! //    scope's lifetime.
//! let spot   = scope.dual(spot_h);
//! let strike = scope.dual(strike_h);
//!
//! // 4. Compute. All operator impls go through Shape A — no per-op Arc
//! //    clone, no per-op registry lookup.
//! let moneyness = spot / strike;
//!
//! // 5. Read the gradient by name. The accessor reads the active
//! //    registry off the TLS slot set up by `freeze_dual`.
//! assert!((moneyness.partial("spot") - (1.0 / 105.0)).abs() < 1e-14);
//! assert!((moneyness.partial("strike") - (-100.0 / (105.0 * 105.0))).abs() < 1e-14);
//! assert_eq!(moneyness.partial("vol"), 0.0); // vol doesn't appear in moneyness
//! ```
//!
//! Once a [`VarRegistry`] has been frozen by a tape, it cannot be
//! mutated: there is no API to add names post-freeze, and user code
//! must not reach through `Arc::get_mut` to bypass this contract.
//!
//! # Cross-registry operations panic (debug-only)
//!
//! Every operator (`+`, `-`, `*`, `/`, unary `-`) between two labeled
//! forward-mode wrappers checks that both operands were constructed
//! under the **same** `LabeledForwardTape` scope, using a TLS generation
//! counter stamped at construction time (see
//! [`forward_tape::check_gen`](crate::labeled::forward_tape) and the
//! `D-02` decision block in `.planning/phases/02.2-*`). The check is
//! gated on `#[cfg(debug_assertions)]`; release builds pay zero per-op
//! cost. Mixing values from two different tapes in a debug build panics
//! with a message like:
//!
//! ```text
//! xad_rs::labeled: cross-registry forward-mode op detected
//!   (lhs tape generation = 42, rhs tape generation = 43).
//!   Both operands must come from the same LabeledForwardTape scope.
//! ```
//!
//! # Zero-overhead wrapper layout (Shape A)
//!
//! Forward-mode labeled wrappers are **Shape A**: the per-value struct
//! holds only the positional inner value. Three consequences:
//!
//! 1. **No lifetime parameters leak to user code.** Labeled values are
//!    `'static`, so they can be stored in structs, returned from
//!    functions, and passed between threads (for non-`!Send`-marked
//!    types) without lifetime annotations. The tape/scope is `!Send`;
//!    the per-value wrappers are not.
//! 2. **Zero per-op refcount cost.** Binary operators delegate directly
//!    to the positional inner type's impl — no `Arc::clone`, no
//!    `VarRegistry` lookup, no allocation.
//! 3. **`Clone` on a labeled wrapper is exactly the inner clone.** For
//!    `Copy` positional types (`Dual2<T>`) the labeled wrapper is
//!    `Clone` but not `Copy` (we require `Clone` derive since inner
//!    `Dual` has a `Vec<f64>` gradient).
//!
//! # Supported modes
//!
//! - [`LabeledFReal<T>`] — labeled single-direction forward-mode.
//! - [`LabeledDual`] — labeled multi-variable forward-mode (f64-only).
//! - [`LabeledDual2<T>`] — labeled seeded second-order forward-mode.
//! - [`LabeledAReal`] + [`LabeledTape`] — labeled reverse-mode (Phase 2).
//!   See [`areal`] for the two-phase `input()` -> `freeze()` -> `gradient()`
//!   contract and the `!Send` thread-local discipline.
//!
//! With the `labeled-ndarray` sub-feature: `LabeledJacobian` +
//! `jacobian::compute_labeled_jacobian` for `Array2<f64>`-valued labeled
//! Jacobians.

pub mod areal;
pub mod dual;
pub mod dual2;
pub mod forward_tape;
pub mod freal;
pub mod registry;

#[cfg(feature = "labeled-ndarray")]
pub mod jacobian;

#[cfg(feature = "labeled-hessian")]
pub mod hessian;

pub use areal::{LabeledAReal, LabeledTape};
pub use dual::LabeledDual;
pub use dual2::LabeledDual2;
pub use forward_tape::{Dual2Handle, DualHandle, LabeledForwardScope, LabeledForwardTape};
pub use freal::LabeledFReal;
pub use registry::VarRegistry;

#[cfg(feature = "labeled-ndarray")]
pub use jacobian::{LabeledJacobian, compute_labeled_jacobian};

#[cfg(feature = "labeled-hessian")]
pub use hessian::{LabeledHessian, compute_full_hessian};