regit-svi 2.0.0

Arbitrage-free SVI volatility surfaces in pure Rust. Raw, Jump-Wings and SSVI parametrisations, calibration, and static-arbitrage checks. Zero dependencies.
Documentation
// Copyright 2026 Regit.io — Nicolas Koenig
// SPDX-License-Identifier: Apache-2.0

//! Crate-private numerical engines.
//!
//! Keeping these routines private lets public APIs expose validated models,
//! typed failures, and evidence instead of low-level solver state. Each engine
//! lives in its own module so its numerical contract can be reviewed in
//! isolation.

mod brent;
mod cholesky;
#[path = "levenberg_marquardt.rs"]
mod lm;
#[path = "nelder_mead.rs"]
mod simplex;

pub(crate) use brent::{BrentRoot, brent_root_with_evidence};
pub(crate) use cholesky::{solve_spd, solve_spd_3};
pub(crate) use lm::levenberg_marquardt;
pub(crate) use simplex::nelder_mead;

/// Detailed termination emitted by crate-private numerical optimizers.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OptimizerTermination {
    /// Objective and search geometry met their tolerances.
    ObjectiveConverged,
    /// The weighted gradient norm met its tolerance.
    GradientConverged,
    /// An accepted parameter step met its tolerance.
    StepConverged,
    /// No cost-reducing damped step was found.
    Stagnated,
    /// Every attempted damped normal equation was singular.
    SingularModel,
    /// A required objective or residual evaluation was non-finite.
    NonFiniteEvaluation,
    /// The configured iteration budget was exhausted.
    IterationLimit,
}

/// Converts a practical grid index or count to `f64` without an `as` cast.
///
/// The split conversion is exact for values below `2^53`, which covers every
/// grid and iteration count accepted by this crate. Larger `usize` values are
/// representable only approximately in binary64, just like a direct cast.
#[inline]
#[must_use]
pub(crate) fn index_to_f64(index: usize) -> f64 {
    let value = index as u64;
    let high = u32::try_from(value >> 32).unwrap_or(u32::MAX);
    let low = u32::try_from(value & 0xFFFF_FFFF).unwrap_or(u32::MAX);
    f64::from(high) * 4_294_967_296.0 + f64::from(low)
}

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

    #[test]
    fn index_conversion_is_exact_on_supported_counts() {
        assert!(index_to_f64(0).abs() < f64::EPSILON);
        assert!((index_to_f64(401) - 401.0).abs() < f64::EPSILON);
        assert!((index_to_f64(1_usize << 32) - 4_294_967_296.0).abs() < f64::EPSILON);
    }
}