//! Tensor operations and utilities for ML models (Candle-free).
//!
//! With the Candle elimination, tensor operations now live in
//! `cuda_autograd::GpuTensor` and `cuda_autograd::ActivationKernels`.
//!
//! This module retains only lightweight CPU-side helper functions that
//! downstream crates use and that do not require a GPU tensor backend.

/// Tensor operation utilities (CPU-side helpers).
#[derive(Debug)]
pub struct TensorOps;

impl TensorOps {
    /// Apply softmax to a slice with numerical stability.
    ///
    /// Returns a new vector of the same length where values sum to 1.
    pub fn stable_softmax_cpu(input: &[f64]) -> Vec<f64> {
        if input.is_empty() {
            return Vec::new();
        }
        let max_val = input.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
        let exp_vals: Vec<f64> = input.iter().map(|&x| (x - max_val).exp()).collect();
        let sum_exp: f64 = exp_vals.iter().sum();
        if sum_exp == 0.0 {
            return vec![1.0 / input.len() as f64; input.len()];
        }
        exp_vals.iter().map(|&x| x / sum_exp).collect()
    }

    /// Clamp values in a slice between min and max.
    pub fn clamp_cpu(input: &[f64], min_val: f64, max_val: f64) -> Vec<f64> {
        input.iter().map(|&x| x.clamp(min_val, max_val)).collect()
    }
}

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

    #[test]
    fn test_stable_softmax_cpu() {
        let data = vec![1.0, 2.0, 3.0];
        let result = TensorOps::stable_softmax_cpu(&data);
        let sum: f64 = result.iter().sum();
        assert!((sum - 1.0).abs() < 1e-10);
        // Probabilities should be monotonically increasing
        assert!(result[0] < result[1]);
        assert!(result[1] < result[2]);
    }

    #[test]
    fn test_clamp_cpu() {
        let data = vec![-2.0, -1.0, 0.0, 1.0, 2.0];
        let result = TensorOps::clamp_cpu(&data, -1.0, 1.0);
        for val in &result {
            assert!(*val >= -1.0 && *val <= 1.0);
        }
    }
}
