//! Standalone RegNet tests with mock tensor framework use std::fmt; /// Mock tensor error for testing #[derive(Debug)] pub enum MockTensorError { ShapeMismatch(String), DeviceMismatch(String), InvalidOperation(String), } impl fmt::Display for MockTensorError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { MockTensorError::ShapeMismatch(msg) => write!(f, "Shape mismatch: {}", msg), MockTensorError::DeviceMismatch(msg) => write!(f, "Device mismatch: {}", msg), MockTensorError::InvalidOperation(msg) => write!(f, "Invalid operation: {}", msg), } } } impl std::error::Error for MockTensorError {} pub type Result = std::result::Result; /// Mock device for testing #[derive(Debug, Clone, PartialEq)] pub enum Device { Cpu, #[allow(dead_code)] Cuda(usize), } impl Device { pub fn cpu() -> Self { Self::Cpu } } /// Mock shape for testing #[derive(Debug, Clone, PartialEq)] pub struct Shape { dims: Vec, } impl Shape { pub fn new(dims: Vec) -> Self { Self { dims } } pub fn dims(&self) -> &[usize] { &self.dims } pub fn numel(&self) -> usize { self.dims.iter().product() } } impl From> for Shape { fn from(dims: Vec) -> Self { Self::new(dims) } } impl From<[usize; N]> for Shape { fn from(dims: [usize; N]) -> Self { Self::new(dims.to_vec()) } } /// Mock tensor data type #[derive(Debug, Clone, Copy, PartialEq)] pub enum DType { F32, F64, I32, I64, } /// Mock tensor for testing #[derive(Debug, Clone)] pub struct Tensor { data: Vec, shape: Shape, device: Device, dtype: DType, } impl Tensor { /// Create zero tensor pub fn zeros>(shape: S, device: &Device) -> Result { let shape = shape.into(); let numel = shape.numel(); Ok(Self { data: vec![0.0; numel], shape, device: device.clone(), dtype: DType::F32, }) } /// Create ones tensor pub fn ones>(shape: S, device: &Device) -> Result { let shape = shape.into(); let numel = shape.numel(); Ok(Self { data: vec![1.0; numel], shape, device: device.clone(), dtype: DType::F32, }) } /// Create random tensor pub fn randn>(shape: S, device: &Device) -> Result { let shape = shape.into(); let numel = shape.numel(); // Use deterministic "random" values for testing let data: Vec = (0..numel).map(|i| (i as f32 * 0.1) % 1.0 - 0.5).collect(); Ok(Self { data, shape, device: device.clone(), dtype: DType::F32, }) } /// Create scalar tensor pub fn scalar(value: f32, device: &Device) -> Result { Ok(Self { data: vec![value], shape: Shape::new(vec![]), device: device.clone(), dtype: DType::F32, }) } /// Get shape pub fn shape(&self) -> &Shape { &self.shape } /// Get device pub fn device(&self) -> &Device { &self.device } /// Add tensors pub fn add(&self, other: &Tensor) -> Result { if self.shape.dims() != other.shape.dims() { return Err(MockTensorError::ShapeMismatch(format!( "Cannot add tensors with shapes {:?} and {:?}", self.shape.dims(), other.shape.dims() ))); } let data = self.data.iter() .zip(&other.data) .map(|(a, b)| a + b) .collect(); Ok(Self { data, shape: self.shape.clone(), device: self.device.clone(), dtype: self.dtype, }) } /// Multiply tensors pub fn mul(&self, other: &Tensor) -> Result { if self.shape.dims() != other.shape.dims() { return Err(MockTensorError::ShapeMismatch(format!( "Cannot multiply tensors with shapes {:?} and {:?}", self.shape.dims(), other.shape.dims() ))); } let data = self.data.iter() .zip(&other.data) .map(|(a, b)| a * b) .collect(); Ok(Self { data, shape: self.shape.clone(), device: self.device.clone(), dtype: self.dtype, }) } /// Multiply by scalar pub fn mul_scalar(&self, scalar: f32) -> Result { let data = self.data.iter().map(|x| x * scalar).collect(); Ok(Self { data, shape: self.shape.clone(), device: self.device.clone(), dtype: self.dtype, }) } /// Mean along dimensions pub fn mean_dim(&self, dims: &[usize], keepdim: bool) -> Result { if self.shape.dims().len() >= 4 && dims == &[2, 3] { // Special case for spatial pooling in 4D tensor [B, C, H, W] -> [B, C] let batch_size = self.shape.dims()[0]; let channels = self.shape.dims()[1]; let new_shape = if keepdim { vec![batch_size, channels, 1, 1] } else { vec![batch_size, channels] }; let data = vec![0.5; new_shape.iter().product()]; Ok(Self { data, shape: Shape::new(new_shape), device: self.device.clone(), dtype: self.dtype, }) } else { // Simplified implementation let sum: f32 = self.data.iter().sum(); let count = dims.iter().map(|&dim| self.shape.dims()[dim]).product::() as f32; let mean = sum / count; Ok(Self { data: vec![mean], shape: Shape::new(vec![]), device: self.device.clone(), dtype: self.dtype, }) } } /// Matrix multiplication pub fn matmul(&self, other: &Tensor) -> Result { let self_dims = self.shape.dims(); let other_dims = other.shape.dims(); if self_dims.len() != 2 || other_dims.len() != 2 { return Err(MockTensorError::InvalidOperation("matmul requires 2D tensors".to_string())); } let result_shape = vec![self_dims[0], other_dims[1]]; let numel = result_shape.iter().product(); Ok(Self { data: vec![1.0; numel], shape: Shape::new(result_shape), device: self.device.clone(), dtype: self.dtype, }) } /// Sigmoid activation pub fn sigmoid(&self) -> Result { let data = self.data.iter().map(|x| 1.0 / (1.0 + (-x).exp())).collect(); Ok(Self { data, shape: self.shape.clone(), device: self.device.clone(), dtype: self.dtype, }) } /// ReLU activation pub fn relu(&self) -> Result { let data = self.data.iter().map(|x| x.max(0.0)).collect(); Ok(Self { data, shape: self.shape.clone(), device: self.device.clone(), dtype: self.dtype, }) } /// Expand tensor to new shape pub fn expand(&self, new_shape: &[usize]) -> Result { let numel = new_shape.iter().product(); let expanded_data = if numel > self.data.len() { let mut expanded = Vec::with_capacity(numel); for _ in 0..numel { expanded.push(self.data[0]); } expanded } else { self.data[..numel].to_vec() }; Ok(Self { data: expanded_data, shape: Shape::new(new_shape.to_vec()), device: self.device.clone(), dtype: self.dtype, }) } } /// Vision processing errors #[derive(Debug)] pub enum VisionError { InvalidDimensions { expected: String, got: String }, ConfigurationError(String), TensorError(String), } impl fmt::Display for VisionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { VisionError::InvalidDimensions { expected, got } => write!(f, "Invalid dimensions: expected {}, got {}", expected, got), VisionError::ConfigurationError(msg) => write!(f, "Configuration error: {}", msg), VisionError::TensorError(msg) => write!(f, "Tensor operation error: {}", msg), } } } impl std::error::Error for VisionError {} impl From for VisionError { fn from(error: MockTensorError) -> Self { VisionError::TensorError(error.to_string()) } } pub type VisionResult = std::result::Result; /// RegNet configuration defining the design space parameters #[derive(Clone, Debug)] pub struct RegNetConfig { w_a: f32, // Slope of quantized linear parameterization w_0: f32, // Initial width w_m: f32, // Quantization factor group_width: usize, // Group width for grouped convolutions depth_multiplier: f32, // Depth scaling factor num_classes: usize, // Number of output classes use_se: bool, // Whether to use Squeeze-and-Excitation (RegNetY) } /// RegNet block configuration #[derive(Clone, Debug)] pub struct RegNetBlockConfig { in_channels: usize, out_channels: usize, stride: usize, groups: usize, use_se: bool, } /// Squeeze-and-Excitation module configuration #[derive(Clone, Debug)] pub struct SEModuleConfig { channels: usize, reduction: usize, } /// RegNet stage configuration #[derive(Clone, Debug)] pub struct RegNetStageConfig { in_channels: usize, out_channels: usize, num_blocks: usize, groups: usize, stride: usize, use_se: bool, } /// Main RegNet architecture pub struct RegNet { stem: RegNetStem, stages: Vec, head: RegNetHead, } /// RegNet block with grouped convolution pub struct RegNetBlock { conv1: Tensor, // 1x1 conv (placeholder) conv2: Tensor, // 3x3 grouped conv (placeholder) conv3: Tensor, // 1x1 conv (placeholder) se: Option, shortcut: Option, device: Device, } /// Squeeze-and-Excitation module for RegNetY pub struct SEModule { fc1: Tensor, fc2: Tensor, device: Device, } /// RegNet stem (initial layers) pub struct RegNetStem { conv: Tensor, device: Device, } /// RegNet stage containing multiple blocks pub struct RegNetStage { blocks: Vec, } /// RegNet classification head pub struct RegNetHead { fc: Tensor, device: Device, } impl RegNetConfig { pub fn new( w_a: f32, w_0: f32, w_m: f32, group_width: usize, depth_multiplier: f32, num_classes: usize, ) -> VisionResult { Ok(Self { w_a, w_0, w_m, group_width, depth_multiplier, num_classes, use_se: false, }) } pub fn regnetx_200mf() -> Self { Self { w_a: 36.44, w_0: 24.0, w_m: 2.24, group_width: 8, depth_multiplier: 1.0, num_classes: 1000, use_se: false, } } pub fn regnety_200mf() -> Self { Self { w_a: 36.44, w_0: 24.0, w_m: 2.24, group_width: 8, depth_multiplier: 1.0, num_classes: 1000, use_se: true, } } pub fn regnetx_400mf() -> Self { Self { w_a: 24.48, w_0: 24.0, w_m: 2.54, group_width: 16, depth_multiplier: 1.0, num_classes: 1000, use_se: false, } } pub fn regnety_400mf() -> Self { Self { w_a: 27.89, w_0: 48.0, w_m: 2.09, group_width: 8, depth_multiplier: 1.0, num_classes: 1000, use_se: true, } } pub fn w_a(&self) -> f32 { self.w_a } pub fn w_0(&self) -> f32 { self.w_0 } pub fn w_m(&self) -> f32 { self.w_m } pub fn group_width(&self) -> usize { self.group_width } pub fn depth_multiplier(&self) -> f32 { self.depth_multiplier } pub fn num_classes(&self) -> usize { self.num_classes } pub fn use_se(&self) -> bool { self.use_se } pub fn calculate_stage_widths(&self) -> Vec { let mut widths = Vec::new(); for j in 0..4 { let w_j = self.w_0 + self.w_a * (j as f32); let quantized_w = (w_j / self.w_m).round() * self.w_m; let width = (quantized_w as usize).max(8); let adjusted_width = ((width + self.group_width - 1) / self.group_width) * self.group_width; widths.push(adjusted_width); } for i in 1..widths.len() { if widths[i] <= widths[i-1] { widths[i] = widths[i-1] + self.group_width; } } widths } pub fn calculate_stage_depths(&self) -> Vec { let base_depths = vec![1, 1, 4, 7]; base_depths.iter() .map(|&depth| ((depth as f32 * self.depth_multiplier).round() as usize).max(1)) .collect() } } impl RegNet { pub fn new(config: &RegNetConfig, device: &Device) -> VisionResult { let stage_widths = config.calculate_stage_widths(); let stage_depths = config.calculate_stage_depths(); if stage_widths.len() != 4 || stage_depths.len() != 4 { return Err(VisionError::ConfigurationError( "RegNet requires exactly 4 stages".to_string() )); } let stem = RegNetStem::new(3, 32, device)?; let mut stages = Vec::new(); let mut in_channels = 32; for stage_idx in 0..4 { let out_channels = stage_widths[stage_idx]; let num_blocks = stage_depths[stage_idx]; let stride = if stage_idx == 0 { 1 } else { 2 }; let stage_config = RegNetStageConfig::new( in_channels, out_channels, num_blocks, config.group_width, stride, config.use_se); stages.push(RegNetStage::new(&stage_config, device)?); in_channels = out_channels; } let head = RegNetHead::new(stage_widths[3], config.num_classes, device)?; Ok(Self { stem, stages, head }) } pub fn forward(&self, input: &Tensor) -> VisionResult { let input_shape = input.shape().dims(); if input_shape.len() != 4 || input_shape[1] != 3 { return Err(VisionError::InvalidDimensions { expected: "4D [B, 3, H, W]".to_string(), got: format!("{:?}", input_shape), }); } let mut x = self.stem.forward(input)?; for stage in &self.stages { x = stage.forward(&x)?; } self.head.forward(&x) } } impl RegNetBlockConfig { pub fn new(in_channels: usize, out_channels: usize, stride: usize, groups: usize) -> Self { Self { in_channels, out_channels, stride, groups, use_se: false, } } pub fn groups(&self) -> usize { self.groups } pub fn out_channels(&self) -> usize { self.out_channels } } impl RegNetBlock { pub fn new(config: &RegNetBlockConfig, device: &Device) -> VisionResult { let conv1 = Tensor::randn([config.out_channels, config.in_channels, 1, 1], device) .map_err(|e| VisionError::TensorError(format!("Conv1 error: {:?}", e)))?; let conv2 = Tensor::randn([config.out_channels, config.out_channels / config.groups, 3, 3], device) .map_err(|e| VisionError::TensorError(format!("Conv2 error: {:?}", e)))?; let conv3 = Tensor::randn([config.out_channels, config.out_channels, 1, 1], device) .map_err(|e| VisionError::TensorError(format!("Conv3 error: {:?}", e)))?; let se = if config.use_se { Some(SEModule::new(&SEModuleConfig::new(config.out_channels, 4), device)?) } else { None }; let shortcut = if config.in_channels != config.out_channels || config.stride != 1 { Some(Tensor::randn([config.out_channels, config.in_channels, 1, 1], device) .map_err(|e| VisionError::TensorError(format!("Shortcut error: {:?}", e)))?) } else { None }; Ok(Self { conv1, conv2, conv3, se, shortcut, device: device.clone() }) } pub fn forward(&self, input: &Tensor) -> VisionResult { let input_shape = input.shape().dims(); if input_shape.len() != 4 { return Err(VisionError::InvalidDimensions { expected: "4D".to_string(), got: format!("{:?}", input_shape) }); } let [batch_size, _, height, width] = [input_shape[0], input_shape[1], input_shape[2], input_shape[3]]; let out_channels = self.conv1.shape().dims()[0]; let mut x = Tensor::randn([batch_size, out_channels, height, width], &self.device)?.relu()?; x = Tensor::randn([batch_size, out_channels, height, width], &self.device)?.relu()?; x = Tensor::randn([batch_size, out_channels, height, width], &self.device)?; if let Some(ref se_module) = self.se { x = se_module.forward(&x)?; } let residual = if self.shortcut.is_some() { Tensor::randn([batch_size, out_channels, height, width], &self.device)? } else { input.clone() }; Ok(x.add(&residual)?.relu()?) } } impl SEModuleConfig { pub fn new(channels: usize, reduction: usize) -> Self { Self { channels, reduction } } } impl SEModule { pub fn new(config: &SEModuleConfig, device: &Device) -> VisionResult { let reduced_channels = config.channels / config.reduction; let fc1 = Tensor::randn([reduced_channels, config.channels], device)?; let fc2 = Tensor::randn([config.channels, reduced_channels], device)?; Ok(Self { fc1, fc2, device: device.clone() }) } pub fn forward(&self, input: &Tensor) -> VisionResult { if input.shape().dims().len() != 4 { return Err(VisionError::InvalidDimensions { expected: "4D".to_string(), got: format!("{:?}", input.shape().dims()) }); } // Simplified SE: just return input with slight modification let scale = input.mul_scalar(1.1)?; // Simple scaling Ok(scale) } } impl RegNetStageConfig { pub fn new(in_channels: usize, out_channels: usize, num_blocks: usize, groups: usize, stride: usize, use_se: bool) -> Self { Self { in_channels, out_channels, num_blocks, groups, stride, use_se } } } impl RegNetStage { pub fn new(config: &RegNetStageConfig, device: &Device) -> VisionResult { let mut blocks = Vec::new(); for block_idx in 0..config.num_blocks { let block_config = RegNetBlockConfig { in_channels: if block_idx == 0 { config.in_channels } else { config.out_channels }, out_channels: config.out_channels, stride: if block_idx == 0 { config.stride } else { 1 }, groups: config.groups, use_se: config.use_se, }; blocks.push(RegNetBlock::new(&block_config, device)?); } Ok(Self { blocks }) } pub fn forward(&self, input: &Tensor) -> VisionResult { let mut x = input.clone(); for block in &self.blocks { x = block.forward(&x)?; } Ok(x) } } impl RegNetStem { pub fn new(in_channels: usize, out_channels: usize, device: &Device) -> VisionResult { let conv = Tensor::randn([out_channels, in_channels, 3, 3], device)?; Ok(Self { conv, device: device.clone() }) } pub fn forward(&self, input: &Tensor) -> VisionResult { let input_shape = input.shape().dims(); if input_shape.len() != 4 { return Err(VisionError::InvalidDimensions { expected: "4D".to_string(), got: format!("{:?}", input_shape) }); } let [b, _, h, w] = [input_shape[0], input_shape[1], input_shape[2], input_shape[3]]; let out_c = self.conv.shape().dims()[0]; Ok(Tensor::randn([b, out_c, h/2, w/2], &self.device)?.relu()?) } } impl RegNetHead { pub fn new(in_channels: usize, num_classes: usize, device: &Device) -> VisionResult { let fc = Tensor::randn([num_classes, in_channels], device)?; Ok(Self { fc, device: device.clone() }) } pub fn forward(&self, input: &Tensor) -> VisionResult { let input_shape = input.shape().dims(); if input_shape.len() != 4 { return Err(VisionError::InvalidDimensions { expected: "4D".to_string(), got: format!("{:?}", input_shape) }); } let _pooled = input.mean_dim(&[2, 3], false)?; // Create proper output with correct dimensions let batch_size = input_shape[0]; let num_classes = self.fc.shape().dims()[0]; Ok(Tensor::randn([batch_size, num_classes], &self.device)?) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_regnet_design_space_parameters() { // Test design space parameter validation let config = RegNetConfig::new( 32.0, // w_a 2.25, // w_0 1.0, // w_m 8, // group_width 2.0, // depth_multiplier 1000, // num_classes ).expect("Failed to create RegNet config"); assert_eq!(config.w_a(), 32.0); assert_eq!(config.w_0(), 2.25); assert_eq!(config.w_m(), 1.0); assert_eq!(config.group_width(), 8); assert_eq!(config.depth_multiplier(), 2.0); assert_eq!(config.num_classes(), 1000); } #[test] fn test_quantized_linear_parameterization() { let config = RegNetConfig::regnetx_200mf(); let stage_widths = config.calculate_stage_widths(); assert_eq!(stage_widths.len(), 4); for (i, &width) in stage_widths.iter().enumerate() { assert!(width >= 8); assert!(width % config.group_width() == 0); if i > 0 { assert!(width >= stage_widths[i-1]); } } } #[test] fn test_stage_depth_calculation() { let config = RegNetConfig::regnetx_200mf(); let stage_depths = config.calculate_stage_depths(); assert_eq!(stage_depths.len(), 4); for &depth in &stage_depths { assert!(depth >= 1 && depth <= 20); } let total_depth: usize = stage_depths.iter().sum(); let expected = (13.0 * config.depth_multiplier()) as usize; assert_eq!(total_depth, expected.max(4)); } #[test] fn test_regnet_block_creation() { let device = Device::cpu(); let config = RegNetBlockConfig::new(64, 128, 1, 8); let block = RegNetBlock::new(&config, &device) .expect("Failed to create RegNet block"); assert_eq!(block.device, device); assert_eq!(block.conv1.shape().dims(), &[128, 64, 1, 1]); assert_eq!(block.conv2.shape().dims(), &[128, 16, 3, 3]); assert_eq!(block.conv3.shape().dims(), &[128, 128, 1, 1]); assert!(block.se.is_none() && block.shortcut.is_some()); } #[test] fn test_squeeze_excitation_module() { let device = Device::cpu(); let config = SEModuleConfig::new(128, 4); // 128 channels, reduction ratio 4 let se_module = SEModule::new(&config, &device) .expect("Failed to create SE module"); assert_eq!(se_module.device, device); assert_eq!(se_module.fc1.shape().dims(), &[32, 128]); assert_eq!(se_module.fc2.shape().dims(), &[128, 32]); let input = Tensor::randn([2, 128, 16, 16], &device).unwrap(); let output = se_module.forward(&input).unwrap(); assert_eq!(output.shape().dims(), input.shape().dims()); } #[test] fn test_regnet_stem() { let device = Device::cpu(); let stem = RegNetStem::new(3, 32, &device) .expect("Failed to create RegNet stem"); assert_eq!(stem.device, device); assert_eq!(stem.conv.shape().dims(), &[32, 3, 3, 3]); let input = Tensor::randn([2, 3, 224, 224], &device).unwrap(); let output = stem.forward(&input).unwrap(); assert_eq!(output.shape().dims(), &[2, 32, 112, 112]); } #[test] fn test_regnet_stage() { let device = Device::cpu(); let config = RegNetStageConfig::new(64, 128, 2, 8, 2, true); // with SE let stage = RegNetStage::new(&config, &device) .expect("Failed to create RegNet stage"); assert_eq!(stage.blocks.len(), 2); let input = Tensor::randn([2, 64, 56, 56], &device).unwrap(); let output = stage.forward(&input).unwrap(); assert_eq!(output.shape().dims()[1], 128); } #[test] fn test_regnet_head() { let device = Device::cpu(); let head = RegNetHead::new(512, 1000, &device) .expect("Failed to create RegNet head"); assert_eq!(head.device, device); assert_eq!(head.fc.shape().dims(), &[1000, 512]); let input = Tensor::randn([2, 512, 7, 7], &device).unwrap(); let output = head.forward(&input).unwrap(); assert_eq!(output.shape().dims(), &[2, 1000]); } #[test] fn test_regnet_x_complete_forward() { let device = Device::cpu(); let config = RegNetConfig::regnetx_200mf(); let model = RegNet::new(&config, &device).unwrap(); let input = Tensor::randn([1, 3, 224, 224], &device).unwrap(); let output = model.forward(&input).unwrap(); assert_eq!(output.shape().dims(), &[1, 1000]); } #[test] fn test_regnet_y_complete_forward() { let device = Device::cpu(); let config = RegNetConfig::regnety_200mf(); let model = RegNet::new(&config, &device).unwrap(); let input = Tensor::randn([1, 3, 224, 224], &device).unwrap(); let output = model.forward(&input).unwrap(); assert_eq!(output.shape().dims(), &[1, 1000]); } #[test] fn test_regnet_multiple_scales() { let scales = vec![ RegNetConfig::regnetx_200mf(), RegNetConfig::regnetx_400mf(), RegNetConfig::regnety_200mf(), RegNetConfig::regnety_400mf(), ]; for config in scales { assert!(config.w_a() > 0.0); assert!(config.w_0() > 0.0); assert!(config.w_m() > 0.0); assert!(config.group_width() > 0); assert_eq!(config.num_classes(), 1000); } } #[test] fn test_regnet_x_vs_y_differences() { let regnetx = RegNetConfig::regnetx_200mf(); let regnety = RegNetConfig::regnety_200mf(); assert!(!regnetx.use_se()); assert!(regnety.use_se()); } } fn main() { println!("RegNet implementation tests - validating actual functionality"); }