use crate::reduction::{ImplicitProjection, compute_jl_dimension, project_matrix};
use smartcore::linalg::basic::{
arrays::{Array, Array2},
matrix::DenseMatrix,
};
#[test]
fn test_implicit_projection_creates() {
let proj = ImplicitProjection::new(100, 10, Some(42));
assert_eq!(proj.original_dim, 100);
assert_eq!(proj.reduced_dim, 10);
assert!(proj.seed > 0);
}
#[test]
fn test_implicit_projection_dimensions() {
let proj = ImplicitProjection::new(50, 8, Some(42));
let query = vec![0.5; 50];
let projected = proj.project(&query);
assert_eq!(projected.len(), 8);
assert!(projected.iter().all(|&x| x.is_finite()));
}
#[test]
fn test_implicit_projection_deterministic() {
let proj = ImplicitProjection::new(30, 5, Some(42));
let query = vec![1.0; 30];
let result1 = proj.project(&query);
let result2 = proj.project(&query);
assert_eq!(result1, result2);
}
#[test]
fn test_implicit_projection_different_seeds() {
let proj1 = ImplicitProjection::new(20, 5, None);
let proj2 = ImplicitProjection::new(20, 5, None);
assert_ne!(proj1.seed, proj2.seed);
let query = vec![1.0; 20];
let result1 = proj1.project(&query);
let result2 = proj2.project(&query);
assert_ne!(result1, result2);
}
#[test]
fn test_implicit_projection_zero_vector() {
let proj = ImplicitProjection::new(40, 10, Some(42));
let query = vec![0.0; 40];
let projected = proj.project(&query);
assert_eq!(projected.len(), 10);
assert!(projected.iter().all(|&x| x.abs() < 1e-10));
}
#[test]
fn test_implicit_projection_linearity() {
let proj = ImplicitProjection::new(25, 6, Some(42));
let query = vec![1.0; 25];
let scaled_query: Vec<f64> = query.iter().map(|x| x * 2.0).collect();
let proj1 = proj.project(&query);
let proj2 = proj.project(&scaled_query);
for i in 0..proj1.len() {
let expected = proj1[i] * 2.0;
let actual = proj2[i];
assert!(
(expected - actual).abs() < 1e-9,
"Linearity violation at {}: expected {}, got {}",
i,
expected,
actual
);
}
}
#[test]
fn test_implicit_projection_preserves_scale() {
let proj = ImplicitProjection::new(50, 15, Some(42));
let query = vec![1.0; 50];
let projected = proj.project(&query);
let orig_norm: f64 = query.iter().map(|x| x * x).sum::<f64>().sqrt();
let proj_norm: f64 = projected.iter().map(|x| x * x).sum::<f64>().sqrt();
let ratio = proj_norm / orig_norm;
assert!(ratio > 0.5 && ratio < 2.0);
}
#[test]
fn test_implicit_projection_non_trivial() {
let proj = ImplicitProjection::new(30, 8, Some(42));
let query = vec![1.0; 30];
let projected = proj.project(&query);
let has_nonzero = projected.iter().any(|&x| x.abs() > 1e-10);
assert!(has_nonzero);
}
#[test]
fn test_project_matrix_dimensions() {
let data = vec![1.0; 60]; let matrix = DenseMatrix::from_iterator(data.into_iter(), 3, 20, 1);
let proj = ImplicitProjection::new(20, 5, Some(42));
let projected = project_matrix(&matrix, &proj);
assert_eq!(projected.shape(), (3, 5));
}
#[test]
fn test_project_matrix_preserves_rows() {
let data = vec![0.5; 100]; let matrix = DenseMatrix::from_iterator(data.into_iter(), 10, 10, 1);
let proj = ImplicitProjection::new(10, 3, Some(42));
let projected = project_matrix(&matrix, &proj);
assert_eq!(projected.shape().0, 10);
assert_eq!(projected.shape().1, 3);
}
#[test]
fn test_project_matrix_zero_matrix() {
let data = vec![0.0; 80]; let matrix = DenseMatrix::from_iterator(data.into_iter(), 4, 20, 0);
let proj = ImplicitProjection::new(20, 6, Some(42));
let projected = project_matrix(&matrix, &proj);
for i in 0..projected.shape().0 {
for j in 0..projected.shape().1 {
assert!(projected.get((i, j)).abs() < 1e-10);
}
}
}
#[test]
fn test_project_matrix_different_rows_different_projections() {
let data = vec![1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0];
let matrix = DenseMatrix::from_iterator(data.into_iter(), 3, 4, 0);
let proj = ImplicitProjection::new(4, 2, Some(42));
let projected = project_matrix(&matrix, &proj);
let row0: Vec<f64> = (0..2).map(|j| *projected.get((0, j))).collect();
let row1: Vec<f64> = (0..2).map(|j| *projected.get((1, j))).collect();
let row2: Vec<f64> = (0..2).map(|j| *projected.get((2, j))).collect();
assert_ne!(row0, row1);
assert_ne!(row1, row2);
}
#[test]
fn test_jl_dimension_preserves_low_dims() {
assert_eq!(compute_jl_dimension(100, 16, 0.3), 16);
assert_eq!(compute_jl_dimension(1000, 8, 0.1), 8);
assert_eq!(compute_jl_dimension(50, 31, 0.2), 31);
assert_eq!(compute_jl_dimension(10, 1, 0.5), 1);
}
#[test]
fn test_jl_dimension_never_expands() {
let n = 10;
let epsilon = 0.3;
assert_eq!(compute_jl_dimension(n, 100, epsilon), 100);
assert_eq!(compute_jl_dimension(n, 50, epsilon), 50);
let dim = compute_jl_dimension(100, 200, 0.5);
assert!(dim >= 148 && dim <= 149); }
#[test]
fn test_jl_dimension_minimum_bound() {
let n = 2;
let epsilon = 0.9;
assert_eq!(compute_jl_dimension(n, 1000, epsilon), 32);
assert_eq!(compute_jl_dimension(n, 20, epsilon), 20);
}
#[test]
fn test_jl_dimension_standard_regime() {
let n = 1000;
let epsilon = 0.1;
let original_dim = 512;
let dim = compute_jl_dimension(n, original_dim, epsilon);
assert_eq!(dim, 512);
let n2 = 100;
let original_dim2 = 2000;
let dim2 = compute_jl_dimension(n2, original_dim2, 0.2);
assert!(dim2 >= 921 && dim2 <= 923); }
#[test]
fn test_jl_dimension_high_dim_buffer_mild_compression() {
let n = 500; let epsilon = 0.3;
let original_dim = 3000;
let dim = compute_jl_dimension(n, original_dim, epsilon);
let jl_bound = (8.0 * (n as f64).ln() / (epsilon * epsilon)).ceil() as usize;
let expected = ((jl_bound as f64) * 1.2).ceil() as usize;
assert!(dim >= expected - 2 && dim <= expected + 2);
assert!(dim < original_dim);
}
#[test]
fn test_jl_dimension_high_dim_buffer_moderate_compression() {
let n = 100;
let epsilon = 0.3;
let original_dim = 100_000;
let dim = compute_jl_dimension(n, original_dim, epsilon);
let jl_bound = (8.0 * (n as f64).ln() / (epsilon * epsilon)).ceil() as usize;
let compression = original_dim as f64 / jl_bound as f64;
let buffer = if compression >= 100.0 { 2.0 } else { 1.5 };
let expected = ((jl_bound as f64) * buffer).ceil() as usize;
assert!(dim >= expected - 2 && dim <= expected + 2);
assert!(dim < original_dim);
}
#[test]
fn test_jl_dimension_high_dim_buffer_severe_compression() {
let n = 10;
let epsilon = 0.3;
let original_dim = 50_000;
let dim = compute_jl_dimension(n, original_dim, epsilon);
let jl_bound = (8.0 * (n as f64).ln() / (epsilon * epsilon)).ceil() as usize;
let expected = ((jl_bound as f64) * 2.0).ceil() as usize;
assert!(dim >= expected - 2 && dim <= expected + 2);
assert!(dim < original_dim);
}
#[test]
fn test_jl_dimension_high_dim_buffer_caps_at_original() {
let n = 10_000;
let epsilon = 0.3;
let original_dim = 5_000;
let dim = compute_jl_dimension(n, original_dim, epsilon);
let jl_bound = (8.0 * (n as f64).ln() / (epsilon * epsilon)).ceil() as usize;
let buffered = ((jl_bound as f64) * 1.2).ceil() as usize;
assert_eq!(dim, buffered);
assert!(dim < original_dim); }
#[test]
fn test_jl_dimension_buffer_actually_hits_cap() {
let n = 5_000;
let epsilon = 0.3;
let original_dim = 3_000;
let dim = compute_jl_dimension(n, original_dim, epsilon);
let jl_bound = (8.0 * (n as f64).ln() / (epsilon * epsilon)).ceil() as usize;
let buffered = ((jl_bound as f64) * 1.2).ceil() as usize;
if buffered <= original_dim {
assert_eq!(dim, buffered);
} else {
assert_eq!(dim, original_dim);
}
}
#[test]
fn test_jl_dimension_grows_with_n() {
let epsilon = 0.2;
let original_dim = 10_000;
let dim_100 = compute_jl_dimension(100, original_dim, epsilon);
let dim_1000 = compute_jl_dimension(1000, original_dim, epsilon);
let dim_10000 = compute_jl_dimension(10000, original_dim, epsilon);
assert!(dim_1000 > dim_100);
assert!(dim_10000 > dim_1000);
}
#[test]
fn test_jl_dimension_inversely_proportional_epsilon() {
let n = 5000;
let original_dim = 10_000;
let dim_05 = compute_jl_dimension(n, original_dim, 0.5);
let dim_02 = compute_jl_dimension(n, original_dim, 0.2);
let dim_01 = compute_jl_dimension(n, original_dim, 0.1);
assert!(dim_02 > dim_05);
assert!(dim_01 > dim_02);
}
#[test]
fn test_jl_dimension_dorothea_scenario() {
let n_clusters = 17;
let original_dim = 100_000;
let epsilon = 0.3;
let dim = compute_jl_dimension(n_clusters, original_dim, epsilon);
assert!(dim >= 480 && dim <= 520); assert!(dim < 1000);
}
#[test]
fn test_jl_dimension_moderate_compression() {
let n = 100;
let epsilon = 0.3;
let dim_5k = compute_jl_dimension(n, 5000, epsilon);
assert!(dim_5k >= 600 && dim_5k <= 630);
let dim_50k = compute_jl_dimension(n, 50000, epsilon);
assert!(dim_50k >= 800 && dim_50k <= 840);
}
#[test]
fn test_jl_dimension_reasonable_range() {
let test_cases = vec![
(100, 384, 0.2), (200, 1536, 0.3), (50, 100_000, 0.3), (1000, 768, 0.15), ];
for (n, original_dim, eps) in test_cases {
let dim = compute_jl_dimension(n, original_dim, eps);
assert!(dim >= 32 || dim == original_dim); assert!(dim <= original_dim);
assert!(
dim < 10_000,
"Result too large: {} for n={}, F={}",
dim,
n,
original_dim
);
}
}
#[test]
fn test_jl_dimension_formula_correctness_standard() {
let n = 1000;
let epsilon = 0.15;
let original_dim = 1500;
let dim = compute_jl_dimension(n, original_dim, epsilon);
let expected_jl = (8.0 * (n as f64).ln() / (epsilon * epsilon)).ceil() as usize;
let expected = expected_jl.clamp(32, original_dim);
assert_eq!(dim, expected);
}
#[test]
fn test_jl_dimension_edge_case_single_point() {
let n = 1;
let epsilon = 0.1;
assert_eq!(compute_jl_dimension(n, 100, epsilon), 32);
assert_eq!(compute_jl_dimension(n, 10, epsilon), 10); }
#[test]
fn test_jl_dimension_buffer_factor_application() {
let epsilon = 0.3;
let n1 = 800; let original_1 = 5500; let dim1 = compute_jl_dimension(n1, original_1, epsilon);
let jl1 = (8.0 * (n1 as f64).ln() / (epsilon * epsilon)).ceil() as usize;
let expected1 = ((jl1 as f64) * 1.2).ceil() as usize;
assert!(
dim1 >= expected1 - 5 && dim1 <= expected1 + 5,
"Tier 1 failed: expected ~{}, got {}",
expected1,
dim1
);
let n2 = 100; let original_2 = 20_000; let dim2 = compute_jl_dimension(n2, original_2, epsilon);
let jl2 = (8.0 * (n2 as f64).ln() / (epsilon * epsilon)).ceil() as usize;
let expected2 = ((jl2 as f64) * 1.5).ceil() as usize;
assert!(
dim2 >= expected2 - 5 && dim2 <= expected2 + 5,
"Tier 2 failed: expected ~{}, got {}",
expected2,
dim2
);
let n3 = 50; let original_3 = 50_000; let dim3 = compute_jl_dimension(n3, original_3, epsilon);
let jl3 = (8.0 * (n3 as f64).ln() / (epsilon * epsilon)).ceil() as usize;
let expected3 = ((jl3 as f64) * 2.0).ceil() as usize;
assert!(
dim3 >= expected3 - 5 && dim3 <= expected3 + 5,
"Tier 3 failed: expected ~{}, got {}",
expected3,
dim3
);
}
#[test]
fn test_jl_dimension_boundary_2048() {
let n = 100;
let epsilon = 0.3;
let dim_2048 = compute_jl_dimension(n, 2048, epsilon);
let jl_bound = (8.0 * (n as f64).ln() / (epsilon * epsilon)).ceil() as usize;
assert_eq!(dim_2048, jl_bound.clamp(32, 2048));
let dim_2049 = compute_jl_dimension(n, 2049, epsilon);
let buffered = ((jl_bound as f64) * 1.2).ceil() as usize;
assert_eq!(dim_2049, buffered.clamp(32, 2049));
}
#[test]
fn test_jl_dimension_consistency() {
let n = 500;
let original_dim = 5000;
let epsilon = 0.2;
let dim1 = compute_jl_dimension(n, original_dim, epsilon);
let dim2 = compute_jl_dimension(n, original_dim, epsilon);
assert_eq!(dim1, dim2, "Function should be deterministic");
}
#[test]
fn test_jl_dimension_edge_cases() {
assert_eq!(compute_jl_dimension(100, 16, 0.3), 16);
assert_eq!(compute_jl_dimension(10, 50, 0.3), 50);
let result = compute_jl_dimension(100, 100_000, 0.3);
assert!(result >= 800 && result <= 850);
assert!(result < 100_000);
let d1 = compute_jl_dimension(10, 10_000, 0.3);
let d2 = compute_jl_dimension(100, 10_000, 0.3);
assert!(d2 > d1);
}
#[test]
fn test_full_pipeline_implicit_projection() {
let n_samples = 20;
let orig_dim = 100;
let reduced_dim = 15;
let data: Vec<f64> = (0..n_samples * orig_dim)
.map(|i| (i as f64) * 0.01)
.collect();
let matrix = DenseMatrix::from_iterator(data.into_iter(), n_samples, orig_dim, 0);
let proj = ImplicitProjection::new(orig_dim, reduced_dim, Some(42));
let projected = project_matrix(&matrix, &proj);
assert_eq!(projected.shape(), (n_samples, reduced_dim));
for i in 0..projected.shape().0 {
for j in 0..projected.shape().1 {
assert!(projected.get((i, j)).is_finite());
}
}
}
#[test]
fn test_memory_efficiency() {
let proj = ImplicitProjection::new(1000, 100, Some(42));
let query = vec![1.0; 1000];
let projected = proj.project(&query);
assert_eq!(projected.len(), 100);
assert_eq!(std::mem::size_of::<ImplicitProjection>(), 24); }