// Simple standalone test for LogCoshLoss use std::process::exit; // Mock types for standalone testing #[derive(Debug, Clone)] struct Device; impl Device { fn cpu() -> Self { Device } } #[derive(Debug, Clone)] struct Tensor { data: Vec, shape: Vec, device: Device, } impl Tensor { fn from_data(data: Vec, shape: Vec, _device: &Device) -> Result { if data.len() != shape.iter().product::() { return Err("Shape mismatch".to_string()); } Ok(Tensor { data, shape, device: Device::cpu() }) } fn shape(&self) -> &[usize] { &self.shape } fn device(&self) -> &Device { &self.device } fn to_cpu(&self) -> Result, String> { Ok(self.data.clone()) } fn numel(&self) -> usize { self.data.len() } fn sum(&self, _dim: Option) -> Result { let sum: f32 = self.data.iter().sum(); Ok(Tensor::from_data(vec![sum], vec![], &Device::cpu())?) } fn div_scalar(&self, scalar: f32) -> Result { let result_data: Vec = self.data.iter().map(|x| x / scalar).collect(); Ok(Tensor::from_data(result_data, self.shape.clone(), &self.device)?) } } impl std::ops::Sub for &Tensor { type Output = Tensor; fn sub(self, other: &Tensor) -> Tensor { assert_eq!(self.data.len(), other.data.len()); let result_data: Vec = self.data.iter().zip(other.data.iter()) .map(|(a, b)| a - b) .collect(); Tensor::from_data(result_data, self.shape.clone(), &self.device).unwrap() } } type Result = std::result::Result; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Reduction { None, Mean, Sum, } impl Default for Reduction { fn default() -> Self { Self::Mean } } trait Loss { fn forward(&self, predictions: &Tensor, targets: &Tensor) -> Result; fn reduction(&self) -> Reduction; fn supports_backprop(&self) -> bool { true } fn name(&self) -> &'static str; } // LogCoshLoss implementation #[derive(Debug, Clone)] struct LogCoshLoss { reduction: Reduction, } impl LogCoshLoss { fn new() -> Self { Self { reduction: Reduction::Mean, } } fn with_reduction(mut self, reduction: Reduction) -> Self { self.reduction = reduction; self } fn stable_logcosh(&self, x: f32) -> f32 { let abs_x = x.abs(); if abs_x < 12.0 { x.cosh().ln() } else { abs_x - 2.0f32.ln() } } fn compute_logcosh_elementwise(&self, errors: &Tensor) -> Result { let error_data = errors.to_cpu()?; let mut result_data = Vec::new(); for &error in &error_data { let logcosh_value = self.stable_logcosh(error); result_data.push(logcosh_value); } Tensor::from_data(result_data, errors.shape().to_vec(), errors.device()) } fn apply_reduction(&self, losses: &Tensor) -> Result { match self.reduction { Reduction::None => Ok(losses.clone()), Reduction::Mean => { let sum_result = losses.sum(None)?; let numel = losses.numel() as f32; sum_result.div_scalar(numel) }, Reduction::Sum => losses.sum(None), } } } impl Loss for LogCoshLoss { fn forward(&self, predictions: &Tensor, targets: &Tensor) -> Result { // Validate input shapes if predictions.shape() != targets.shape() { return Err(format!( "Shape mismatch: predictions {:?} vs targets {:?}", predictions.shape(), targets.shape() )); } // Compute element-wise errors let errors = predictions - targets; // Compute LogCosh loss element-wise let logcosh_values = self.compute_logcosh_elementwise(&errors)?; // Apply reduction self.apply_reduction(&logcosh_values) } fn reduction(&self) -> Reduction { self.reduction } fn supports_backprop(&self) -> bool { true } fn name(&self) -> &'static str { "LogCoshLoss" } } impl Default for LogCoshLoss { fn default() -> Self { Self::new() } } fn main() { // Test LogCoshLoss implementation println!("Testing LogCoshLoss implementation..."); let device = Device::cpu(); let loss = LogCoshLoss::new(); // Test 1: Zero error println!("\nTest 1: Zero error"); let predictions = Tensor::from_data(vec![1.0, 2.0, 3.0], vec![3], &device).unwrap(); let targets = predictions.clone(); match loss.forward(&predictions, &targets) { Ok(result) => { println!("Loss value: {}", result.data[0]); if result.data[0].abs() < 1e-6 { println!("✓ Zero error test passed"); } else { println!("✗ Zero error test failed: expected ~0.0, got {}", result.data[0]); } } Err(e) => { println!("✗ Zero error test failed: {}", e); exit(1); } } // Test 2: Small error (should be approximately quadratic) println!("\nTest 2: Small error"); let predictions = Tensor::from_data(vec![0.0], vec![1], &device).unwrap(); let targets = Tensor::from_data(vec![0.1], vec![1], &device).unwrap(); match loss.forward(&predictions, &targets) { Ok(result) => { let expected_quad = 0.5 * 0.1 * 0.1; // x²/2 let actual_logcosh = 0.1f32.cosh().ln(); println!("Loss value: {}", result.data[0]); println!("Expected quadratic approximation: {}", expected_quad); println!("Actual log(cosh(0.1)): {}", actual_logcosh); if (result.data[0] - actual_logcosh).abs() < 1e-6 { println!("✓ Small error test passed"); } else { println!("✗ Small error test failed"); } } Err(e) => { println!("✗ Small error test failed: {}", e); exit(1); } } // Test 3: Large error (should be approximately linear) println!("\nTest 3: Large error"); let predictions = Tensor::from_data(vec![0.0], vec![1], &device).unwrap(); let targets = Tensor::from_data(vec![10.0], vec![1], &device).unwrap(); match loss.forward(&predictions, &targets) { Ok(result) => { let expected_linear = 10.0 - 2.0f32.ln(); // |x| - log(2) println!("Loss value: {}", result.data[0]); println!("Expected linear approximation: {}", expected_linear); if (result.data[0] - expected_linear).abs() < 1e-2 { println!("✓ Large error test passed"); } else { println!("✗ Large error test failed"); } } Err(e) => { println!("✗ Large error test failed: {}", e); exit(1); } } // Test 4: Reduction modes println!("\nTest 4: Reduction modes"); let predictions = Tensor::from_data(vec![0.0, 1.0, 2.0, 3.0], vec![4], &device).unwrap(); let targets = Tensor::from_data(vec![0.5, 0.5, 3.0, 2.0], vec![4], &device).unwrap(); let loss_mean = LogCoshLoss::new().with_reduction(Reduction::Mean); let loss_sum = LogCoshLoss::new().with_reduction(Reduction::Sum); let loss_none = LogCoshLoss::new().with_reduction(Reduction::None); let mean_result = loss_mean.forward(&predictions, &targets).unwrap(); let sum_result = loss_sum.forward(&predictions, &targets).unwrap(); let none_result = loss_none.forward(&predictions, &targets).unwrap(); println!("Mean result: {}", mean_result.data[0]); println!("Sum result: {}", sum_result.data[0]); println!("None result: {:?}", none_result.data); // Check that sum = mean * num_elements let expected_sum = mean_result.data[0] * 4.0; if (sum_result.data[0] - expected_sum).abs() < 1e-6 { println!("✓ Reduction modes test passed"); } else { println!("✗ Reduction modes test failed"); } // Test 5: Utility functions println!("\nTest 5: Utility functions"); let loss = LogCoshLoss::new(); assert!(loss.is_in_quadratic_region(0.5)); assert!(!loss.is_in_quadratic_region(2.0)); assert!(loss.is_in_linear_region(5.0)); assert!(!loss.is_in_linear_region(0.5)); println!("✓ Utility functions test passed"); println!("\n🎉 All tests passed! LogCoshLoss implementation is working correctly."); }