gradcheck 0.1.0

Finite-difference gradient checking for Rust ML frameworks. Verifies an autodiff engine against an independent numerical oracle, with a negative control that must fail.
// gradcheck — finite-difference gradient checking for Rust ML frameworks.
// Copyright (c) 2026 Henos D <henosd19@gmail.com> (GitHub: @4ktLuffy)
// Repository: https://github.com/4ktLuffy/gradcheck
// SPDX-License-Identifier: MIT OR Apache-2.0

//! The framework abstraction.
//!
//! Implement [`Backend`] once for a framework and every check in this crate works against it.
//! The trait is deliberately small: build a tensor, read it back, and evaluate a function
//! either forward-only or with its analytic gradient.

/// A machine-learning framework that `gradcheck` can verify.
///
/// The core of this crate never touches framework types directly — it only speaks `f64`
/// slices and shapes. Adapters convert at the boundary. That is what keeps the numerical
/// oracle independent of the autodiff engine under test.
///
/// # Implementing for a new framework
///
/// ```ignore
/// struct MyBackend;
///
/// impl Backend for MyBackend {
///     type Tensor = my_framework::Tensor;
///
///     fn from_slice(data: &[f64], shape: &[usize]) -> Self::Tensor { /* ... */ }
///     fn to_vec(t: &Self::Tensor) -> Vec<f64> { /* ... */ }
///     fn forward_sum(f: &dyn Fn(Self::Tensor) -> Self::Tensor, x: &Self::Tensor) -> f64 { /* ... */ }
///     fn analytic_grad(f: &dyn Fn(Self::Tensor) -> Self::Tensor, x: &Self::Tensor) -> Vec<f64> { /* ... */ }
/// }
/// ```
pub trait Backend {
    /// The framework's tensor type.
    type Tensor: Clone;

    /// Human-readable name, used in reports (e.g. `"burn/ndarray"`).
    fn name() -> &'static str;

    /// Build a tensor from row-major data and a shape.
    fn from_slice(data: &[f64], shape: &[usize]) -> Self::Tensor;

    /// Read a tensor back as row-major `f64`.
    fn to_vec(t: &Self::Tensor) -> Vec<f64>;

    /// Evaluate `sum(f(x))` as a scalar, WITHOUT autodiff.
    ///
    /// This is the forward pass the numerical oracle differentiates. It must not depend on
    /// the framework's gradient machinery — otherwise the "independent" reference shares a
    /// bug with the code under test, and the whole method is worthless.
    fn forward_sum(f: &dyn Fn(Self::Tensor) -> Self::Tensor, x: &Self::Tensor) -> f64;

    /// Compute the framework's analytic gradient of `sum(f(x))` with respect to `x`.
    ///
    /// Returned row-major, same length as the input.
    fn analytic_grad(f: &dyn Fn(Self::Tensor) -> Self::Tensor, x: &Self::Tensor) -> Vec<f64>;
}