use ruvector_cnn::{CnnEmbedder, EmbeddingConfig, EmbeddingExtractor};
#[test]
fn test_cnn_embedder_creation() {
let embedder = CnnEmbedder::new(EmbeddingConfig::default())
.expect("Failed to create embedder");
assert_eq!(embedder.embedding_dim(), 512);
assert_eq!(embedder.input_size(), 224);
}
#[test]
fn test_cnn_embedder_v3_small() {
let embedder = CnnEmbedder::new_v3_small()
.expect("Failed to create V3 Small embedder");
assert_eq!(embedder.embedding_dim(), 576);
}
#[test]
fn test_cnn_embedder_v3_large() {
let embedder = CnnEmbedder::new_v3_large()
.expect("Failed to create V3 Large embedder");
assert_eq!(embedder.embedding_dim(), 960);
}
#[test]
fn test_embedding_config_default() {
let config = EmbeddingConfig::default();
assert_eq!(config.input_size, 224);
assert_eq!(config.embedding_dim, 512);
assert!(config.normalize);
assert!(!config.quantized);
}
#[test]
fn test_image_to_embedding_pipeline() {
let config = EmbeddingConfig {
input_size: 64,
embedding_dim: 128,
normalize: true,
quantized: false,
};
let embedder = CnnEmbedder::new(config).expect("Failed to create embedder");
let image: Vec<u8> = (0..(64 * 64 * 4))
.map(|i| (i % 256) as u8)
.collect();
let embedding = embedder.extract(&image, 64, 64).expect("Extraction failed");
assert_eq!(embedding.len(), 128, "Embedding dimension mismatch");
assert!(embedding.iter().all(|x| x.is_finite()), "Embedding contains non-finite values");
}
#[test]
fn test_image_to_embedding_normalized() {
let config = EmbeddingConfig {
input_size: 32,
embedding_dim: 64,
normalize: true,
quantized: false,
};
let embedder = CnnEmbedder::new(config).expect("Failed to create embedder");
let image: Vec<u8> = vec![128; 32 * 32 * 4];
let embedding = embedder.extract(&image, 32, 32).expect("Extraction failed");
let norm: f32 = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
assert!(
(norm - 1.0).abs() < 1e-4 || norm < 1e-10,
"Expected normalized embedding, got norm={}",
norm
);
}
#[test]
fn test_different_image_sizes() {
let embedder = CnnEmbedder::new(EmbeddingConfig {
input_size: 64,
embedding_dim: 32,
normalize: true,
quantized: false,
}).expect("Failed to create embedder");
let image_64 = vec![128u8; 64 * 64 * 4];
let emb = embedder.extract(&image_64, 64, 64).expect("Failed");
assert_eq!(emb.len(), 32);
}
#[test]
fn test_grayscale_vs_color_images() {
let embedder = CnnEmbedder::new(EmbeddingConfig {
input_size: 32,
embedding_dim: 16,
normalize: false,
quantized: false,
}).expect("Failed to create embedder");
let gray_image: Vec<u8> = vec![128; 32 * 32 * 4];
let color_image: Vec<u8> = (0..(32 * 32 * 4))
.map(|i| ((i * 37) % 256) as u8)
.collect();
let emb_gray = embedder.extract(&gray_image, 32, 32).expect("Failed");
let emb_color = embedder.extract(&color_image, 32, 32).expect("Failed");
assert_eq!(emb_gray.len(), 16);
assert_eq!(emb_color.len(), 16);
let diff_count = emb_gray.iter().zip(emb_color.iter())
.filter(|(a, b)| (*a - *b).abs() > 1e-10)
.count();
assert!(diff_count > 0, "Different images should produce different embeddings");
}
#[test]
fn test_similar_images_similar_embeddings() {
let embedder = CnnEmbedder::new(EmbeddingConfig {
input_size: 32,
embedding_dim: 32,
normalize: true,
quantized: false,
}).expect("Failed to create embedder");
let image1: Vec<u8> = vec![128; 32 * 32 * 4];
let image2: Vec<u8> = vec![130; 32 * 32 * 4];
let image3: Vec<u8> = (0..(32 * 32 * 4))
.map(|i| ((i * 37) % 256) as u8)
.collect();
let emb1 = embedder.extract(&image1, 32, 32).expect("Failed");
let emb2 = embedder.extract(&image2, 32, 32).expect("Failed");
let emb3 = embedder.extract(&image3, 32, 32).expect("Failed");
let sim_12: f32 = emb1.iter().zip(emb2.iter()).map(|(a, b)| a * b).sum();
let sim_13: f32 = emb1.iter().zip(emb3.iter()).map(|(a, b)| a * b).sum();
assert!(sim_12.is_finite());
assert!(sim_13.is_finite());
}
#[test]
fn test_embedding_extractor_trait() {
let embedder = CnnEmbedder::new(EmbeddingConfig {
input_size: 32,
embedding_dim: 64,
normalize: true,
quantized: false,
}).expect("Failed to create embedder");
assert_eq!(embedder.embedding_dim(), 64);
let image = vec![128u8; 32 * 32 * 4];
let embedding = EmbeddingExtractor::extract(&embedder, &image, 32, 32)
.expect("Trait extraction failed");
assert_eq!(embedding.len(), 64);
}
#[test]
fn test_invalid_image_dimensions() {
let embedder = CnnEmbedder::new(EmbeddingConfig {
input_size: 64,
embedding_dim: 32,
normalize: true,
quantized: false,
}).expect("Failed to create embedder");
let image: Vec<u8> = vec![128; 100];
let result = embedder.extract(&image, 64, 64);
assert!(result.is_err(), "Should fail with mismatched dimensions");
}
#[test]
fn test_zero_dimension_image() {
use std::panic;
let embedder = CnnEmbedder::new(EmbeddingConfig::default())
.expect("Failed to create embedder");
let image: Vec<u8> = vec![];
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
embedder.extract(&image, 0, 0)
}));
let failed = result.is_err() || result.map(|r| r.is_err()).unwrap_or(false);
assert!(failed, "Should fail with zero dimensions (either panic or error)");
}
#[test]
fn test_extraction_deterministic() {
let embedder = CnnEmbedder::new(EmbeddingConfig {
input_size: 32,
embedding_dim: 16,
normalize: true,
quantized: false,
}).expect("Failed to create embedder");
let image = vec![128u8; 32 * 32 * 4];
let emb1 = embedder.extract(&image, 32, 32).expect("Failed");
let emb2 = embedder.extract(&image, 32, 32).expect("Failed");
for (v1, v2) in emb1.iter().zip(emb2.iter()) {
assert_eq!(v1, v2, "Extraction should be deterministic");
}
}
#[test]
fn test_concurrent_extraction() {
use std::sync::Arc;
use std::thread;
let embedder = Arc::new(
CnnEmbedder::new(EmbeddingConfig {
input_size: 32,
embedding_dim: 16,
normalize: true,
quantized: false,
}).expect("Failed to create embedder")
);
let handles: Vec<_> = (0..4)
.map(|i| {
let embedder = Arc::clone(&embedder);
thread::spawn(move || {
let image: Vec<u8> = vec![(i * 50) as u8; 32 * 32 * 4];
embedder.extract(&image, 32, 32)
})
})
.collect();
let results: Vec<_> = handles.into_iter()
.map(|h| h.join().expect("Thread panicked"))
.collect();
for result in results {
assert!(result.is_ok());
let embedding = result.unwrap();
assert_eq!(embedding.len(), 16);
}
}
#[test]
fn test_multiple_extractions_no_leak() {
let embedder = CnnEmbedder::new(EmbeddingConfig {
input_size: 32,
embedding_dim: 16,
normalize: true,
quantized: false,
}).expect("Failed to create embedder");
for i in 0..100 {
let image: Vec<u8> = vec![(i % 256) as u8; 32 * 32 * 4];
let _embedding = embedder.extract(&image, 32, 32).expect("Failed");
}
assert!(true);
}
#[test]
fn test_embedder_with_infonce() {
use ruvector_cnn::contrastive::InfoNCELoss;
let embedder = CnnEmbedder::new(EmbeddingConfig {
input_size: 16,
embedding_dim: 8,
normalize: true,
quantized: false,
}).expect("Failed to create embedder");
let images: Vec<Vec<u8>> = (0..4)
.map(|i| vec![(i * 50) as u8; 16 * 16 * 4])
.collect();
let embeddings: Vec<Vec<f64>> = images.iter()
.map(|img| {
let emb = embedder.extract(img, 16, 16).expect("Failed");
emb.into_iter().map(|x| x as f64).collect()
})
.collect();
let loss_fn = InfoNCELoss::new(0.07);
let loss = loss_fn.forward(&embeddings, 2);
assert!(loss > 0.0, "Loss should be positive");
assert!(loss.is_finite(), "Loss should be finite");
}
#[test]
fn test_embedder_with_triplet() {
use ruvector_cnn::contrastive::TripletLoss;
let embedder = CnnEmbedder::new(EmbeddingConfig {
input_size: 16,
embedding_dim: 8,
normalize: true,
quantized: false,
}).expect("Failed to create embedder");
let anchor_img = vec![128u8; 16 * 16 * 4];
let positive_img = vec![130u8; 16 * 16 * 4]; let negative_img: Vec<u8> = (0..(16 * 16 * 4))
.map(|i| ((i * 37) % 256) as u8)
.collect();
let anchor: Vec<f64> = embedder.extract(&anchor_img, 16, 16)
.expect("Failed").into_iter().map(|x| x as f64).collect();
let positive: Vec<f64> = embedder.extract(&positive_img, 16, 16)
.expect("Failed").into_iter().map(|x| x as f64).collect();
let negative: Vec<f64> = embedder.extract(&negative_img, 16, 16)
.expect("Failed").into_iter().map(|x| x as f64).collect();
let loss_fn = TripletLoss::new(0.5);
let loss = loss_fn.forward(&anchor, &positive, &negative);
assert!(loss >= 0.0, "Loss should be non-negative");
assert!(loss.is_finite(), "Loss should be finite");
}
#[test]
fn test_simd_functions_available() {
use ruvector_cnn::simd;
let a = vec![1.0f32, 2.0, 3.0, 4.0];
let b = vec![4.0f32, 3.0, 2.0, 1.0];
let dot = simd::dot_product_simd(&a, &b);
assert!((dot - 20.0).abs() < 1e-5, "Expected dot product to be 20.0, got {}", dot);
}
#[test]
fn test_simd_relu() {
use ruvector_cnn::simd;
let input = vec![-1.0f32, 0.0, 1.0, 2.0, -0.5, 0.5];
let mut output = vec![0.0f32; input.len()];
simd::relu_simd(&input, &mut output);
assert_eq!(output, vec![0.0, 0.0, 1.0, 2.0, 0.0, 0.5]);
}
#[test]
fn test_simd_relu6() {
use ruvector_cnn::simd;
let input = vec![-1.0f32, 0.0, 1.0, 5.0, 7.0, 10.0];
let mut output = vec![0.0f32; input.len()];
simd::relu6_simd(&input, &mut output);
assert_eq!(output, vec![0.0, 0.0, 1.0, 5.0, 6.0, 6.0]);
}
#[test]
fn test_layers_module_available() {
use ruvector_cnn::layers::{conv2d_3x3, batch_norm, relu, relu6, hard_swish, global_avg_pool};
let input = vec![0.5f32; 3 * 8 * 8]; let weights = vec![0.1f32; 3 * 3 * 3 * 16];
let conv_out = conv2d_3x3(&input, &weights, 3, 16, 8, 8);
assert!(!conv_out.is_empty());
let bn_out = batch_norm(
&conv_out,
&vec![1.0f32; 16], &vec![0.0f32; 16], &vec![0.0f32; 16], &vec![1.0f32; 16], 1e-5,
);
assert_eq!(bn_out.len(), conv_out.len());
let relu_out = relu(&bn_out);
assert_eq!(relu_out.len(), bn_out.len());
let relu6_out = relu6(&bn_out);
assert_eq!(relu6_out.len(), bn_out.len());
let hardswish_out = hard_swish(&bn_out);
assert_eq!(hardswish_out.len(), bn_out.len());
let pooled = global_avg_pool(&relu_out, 16);
assert_eq!(pooled.len(), 16);
}
#[test]
fn test_tensor_creation() {
use ruvector_cnn::Tensor;
let tensor = Tensor::zeros(&[2, 3, 4, 5]);
assert_eq!(tensor.shape(), &[2, 3, 4, 5]);
assert_eq!(tensor.numel(), 2 * 3 * 4 * 5);
assert!(tensor.data().iter().all(|&x| x == 0.0));
}
#[test]
fn test_tensor_ones() {
use ruvector_cnn::Tensor;
let tensor = Tensor::ones(&[4, 4, 4, 4]);
assert_eq!(tensor.numel(), 256);
assert!(tensor.data().iter().all(|&x| x == 1.0));
}
#[test]
fn test_tensor_from_data() {
use ruvector_cnn::Tensor;
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let tensor = Tensor::from_data(data.clone(), &[2, 3]).expect("Failed to create tensor");
assert_eq!(tensor.shape(), &[2, 3]);
assert_eq!(tensor.data(), &data);
}
#[test]
fn test_tensor_from_data_mismatch() {
use ruvector_cnn::Tensor;
let data = vec![1.0, 2.0, 3.0, 4.0];
let result = Tensor::from_data(data, &[2, 3]);
assert!(result.is_err());
}