📄 test_act.rs
/home/palash/git/iron_learn/tests/nn/test_act.rs
Language: rs • Lines: 103
#[cfg(test)]
mod tests {
    // Assuming CpuTensor is your concrete implementation for testing
    use iron_learn::nn::*;
    use iron_learn::CpuTensor;
    use iron_learn::Tensor;

    #[test]
    fn test_get_activations_sigmoid() {
        let (act, prime) = get_activations::<CpuTensor<f32>, f32>(&LayerType::Sigmoid);

        // Input tensor [0.0] -> Sigmoid(0) = 0.5
        let input = CpuTensor::new(vec![1], vec![0.0]).unwrap();
        let result = act(&input).unwrap();

        assert!((result.get_data()[0] - 0.5).abs() < 1e-6);

        // Derivative test: sigmoid_prime(output) = output * (1 - output)
        // If output is 0.5, prime is 0.5 * (1 - 0.5) = 0.25
        let prime_result = prime(&result).unwrap();
        assert!((prime_result.get_data()[0] - 0.25).abs() < 1e-6);
    }

    #[test]
    fn test_get_activations_linear() {
        let (act, prime) = get_activations::<CpuTensor<f32>, f32>(&LayerType::Linear);

        let input = CpuTensor::new(vec![2], vec![5.0, -2.0]).unwrap();

        // Linear activation should return the same values
        let result = act(&input).unwrap();
        assert_eq!(result.get_data(), vec![5.0, -2.0]);

        // Linear derivative should always return 1s regardless of input
        let prime_result = prime(&result).unwrap();
        assert_eq!(prime_result.get_data(), vec![1.0, 1.0]);
    }

    #[test]
    fn test_get_activations_tanh() {
        let (act, prime) = get_activations::<CpuTensor<f32>, f32>(&LayerType::Tanh);

        let input = CpuTensor::new(vec![1], vec![0.0]).unwrap();
        let result = act(&input).unwrap();

        // Tanh(0) = 0
        assert!((result.get_data()[0]).abs() < 1e-6);

        // Tanh prime: 1 - tanh(x)^2. Since tanh(0)=0, prime is 1 - 0 = 1
        let prime_result = prime(&result).unwrap();
        assert!((prime_result.get_data()[0] - 1.0).abs() < 1e-6);
    }

    #[test]
    fn test_softmax_single_row_logic() {
        // Input is 1 row, 3 classes
        let input = CpuTensor::<f32>::new(vec![1, 3], vec![0.0, 1.0, 2.0]).unwrap();
        let result = softmax(&input).expect("Softmax failed on single row");

        let data = result.get_data();
        let sum: f32 = data.iter().sum();

        // 1. Sum must be 1.0 (since sum() only sees this one row)
        assert!((sum - 1.0).abs() < 1e-6);

        // 2. Relative probabilities: e^2 is much larger than e^1 or e^0
        // Expected: [0.09, 0.24, 0.67] roughly
        assert!(data[2] > data[1]);
        assert!(data[1] > data[0]);
    }

    #[test]
    fn test_softmax_prime_pass_through() {
        // Verifies the identity derivative works for a single row
        let output = CpuTensor::<f32>::new(vec![1, 3], vec![0.2, 0.7, 0.1]).unwrap();
        let grad = softmax_prime(&output).expect("Softmax prime failed");

        let shape = grad.get_shape();
        let data = grad.get_data();

        assert_eq!(shape, &vec![1, 3]);
        assert!(
            data.iter().all(|&x| x == 1.0),
            "Prime must return 1.0 for pass-through"
        );
    }

    #[test]
    fn test_softmax_large_values_fail_check() {
        // This test serves as a reminder for your "Numerical Stability" future task.
        // If this panics/returns Inf, you know you still need max-subtraction.
        let input = CpuTensor::<f32>::new(vec![1, 2], vec![100.0, 100.0]).unwrap();
        let result = softmax(&input);

        if let Ok(res) = result {
            let data = res.get_data();
            assert!(
                !data[0].is_nan(),
                "Numerical overflow detected! e^100 is too large."
            );
        }
    }
}