#![cfg(feature = "machine_learning")]
use ndarray::{Array2, arr2};
use rustyml::error::ModelError;
use rustyml::machine_learning::isolation_forest::IsolationForest;
#[test]
fn test_new() {
let forest = IsolationForest::new(50, 128, Some(6), Some(42)).unwrap();
assert_eq!(forest.get_n_estimators(), 50);
assert_eq!(forest.get_max_samples(), 128);
assert_eq!(forest.get_max_depth(), 6);
assert_eq!(forest.get_random_state(), Some(42));
assert_eq!(forest.get_n_features(), 0);
}
#[test]
fn test_default() {
let forest = IsolationForest::default();
assert_eq!(forest.get_n_estimators(), 100);
assert_eq!(forest.get_max_samples(), 256);
assert_eq!(forest.get_max_depth(), 8);
assert_eq!(forest.get_random_state(), None);
assert!(forest.get_trees().is_none());
}
#[test]
fn test_new_with_auto_max_depth() {
let forest = IsolationForest::new(10, 64, None, None).unwrap();
assert_eq!(forest.get_max_depth(), 6);
let forest2 = IsolationForest::new(10, 100, None, None).unwrap();
assert_eq!(forest2.get_max_depth(), 7);
}
#[test]
fn test_fit_valid_data() {
let mut forest = IsolationForest::new(10, 50, Some(3), Some(42)).unwrap();
let x = arr2(&[
[1.0, 2.0],
[2.0, 3.0],
[3.0, 4.0],
[100.0, 200.0], ]);
let result = forest.fit(&x.view());
assert!(result.is_ok());
assert_eq!(forest.get_n_features(), 2);
assert!(forest.get_trees().is_some());
assert_eq!(forest.get_trees().as_ref().unwrap().len(), 10);
}
#[test]
fn test_fit_empty_data() {
let mut forest = IsolationForest::default();
let x = Array2::<f64>::zeros((0, 2));
let result = forest.fit(&x.view());
assert!(matches!(result, Err(ModelError::InputValidationError(_))));
}
#[test]
fn test_fit_nan_data() {
let mut forest = IsolationForest::default();
let x = arr2(&[[1.0, 2.0], [f64::NAN, 3.0], [4.0, 5.0]]);
let result = forest.fit(&x.view());
assert!(matches!(result, Err(ModelError::InputValidationError(_))));
}
#[test]
fn test_fit_infinite_data() {
let mut forest = IsolationForest::default();
let x = arr2(&[[1.0, 2.0], [f64::INFINITY, 3.0], [4.0, 5.0]]);
let result = forest.fit(&x.view());
assert!(matches!(result, Err(ModelError::InputValidationError(_))));
}
#[test]
fn test_anomaly_score() {
let mut forest = IsolationForest::new(10, 50, Some(3), Some(42)).unwrap();
let x_train = arr2(&[[1.0, 1.0], [1.1, 0.9], [0.9, 1.1], [1.0, 0.8], [0.8, 1.0]]);
forest.fit(&x_train.view()).unwrap();
let normal_point = [1.0, 1.0];
let normal_score = forest.anomaly_score(&normal_point).unwrap();
assert!(normal_score >= 0.0 && normal_score <= 1.0);
let outlier_point = [10.0, 10.0];
let outlier_score = forest.anomaly_score(&outlier_point).unwrap();
assert!(outlier_score >= 0.0 && outlier_score <= 1.0);
}
#[test]
fn test_anomaly_score_not_fitted() {
let forest = IsolationForest::default();
let sample = [1.0, 2.0];
let result = forest.anomaly_score(&sample);
assert!(matches!(result, Err(ModelError::NotFitted)));
}
#[test]
fn test_anomaly_score_dimension_mismatch() {
let mut forest = IsolationForest::new(5, 10, Some(2), Some(42)).unwrap();
let x_train = arr2(&[[1.0, 2.0], [2.0, 3.0]]);
forest.fit(&x_train.view()).unwrap();
let wrong_sample = [1.0, 2.0, 3.0]; let result = forest.anomaly_score(&wrong_sample);
assert!(matches!(result, Err(ModelError::InputValidationError(_))));
}
#[test]
fn test_predict() {
let mut forest = IsolationForest::new(20, 50, Some(4), Some(42)).unwrap();
let x_train = arr2(&[
[0.0, 0.0],
[0.1, 0.1],
[0.0, 0.2],
[0.2, 0.0],
[-0.1, 0.1],
[0.1, -0.1],
]);
forest.fit(&x_train.view()).unwrap();
let x_test = arr2(&[
[0.05, 0.05], [5.0, 5.0], [0.0, 0.0], ]);
let scores = forest.predict(&x_test.view()).unwrap();
assert_eq!(scores.len(), 3);
for &score in scores.iter() {
assert!(score >= 0.0 && score <= 1.0);
}
}
#[test]
fn test_predict_not_fitted() {
let forest = IsolationForest::default();
let x_test = arr2(&[[1.0, 2.0], [3.0, 4.0]]);
let result = forest.predict(&x_test.view());
assert!(matches!(result, Err(ModelError::NotFitted)));
}
#[test]
fn test_predict_empty_data() {
let mut forest = IsolationForest::new(5, 10, Some(2), Some(42)).unwrap();
let x_train = arr2(&[[1.0, 2.0], [3.0, 4.0]]);
forest.fit(&x_train.view()).unwrap();
let x_test = Array2::<f64>::zeros((0, 2));
let result = forest.predict(&x_test.view());
assert!(matches!(result, Err(ModelError::InputValidationError(_))));
}
#[test]
fn test_predict_dimension_mismatch() {
let mut forest = IsolationForest::new(5, 10, Some(2), Some(42)).unwrap();
let x_train = arr2(&[[1.0, 2.0], [3.0, 4.0]]);
forest.fit(&x_train.view()).unwrap();
let x_test = arr2(&[[1.0, 2.0, 3.0]]); let result = forest.predict(&x_test.view());
assert!(matches!(result, Err(ModelError::InputValidationError(_))));
}
#[test]
fn test_predict_nan_data() {
let mut forest = IsolationForest::new(5, 10, Some(2), Some(42)).unwrap();
let x_train = arr2(&[[1.0, 2.0], [3.0, 4.0]]);
forest.fit(&x_train.view()).unwrap();
let x_test = arr2(&[[1.0, f64::NAN]]);
let result = forest.predict(&x_test.view());
assert!(matches!(result, Err(ModelError::InputValidationError(_))));
}
#[test]
fn test_fit_predict() {
let mut forest = IsolationForest::new(15, 30, Some(3), Some(123)).unwrap();
let x = arr2(&[
[1.0, 1.0],
[1.2, 0.8],
[0.8, 1.2],
[1.1, 0.9],
[10.0, 10.0], ]);
let scores = forest.fit_predict(&x.view()).unwrap();
assert_eq!(scores.len(), 5);
for &score in scores.iter() {
assert!(score >= 0.0 && score <= 1.0);
}
assert_eq!(forest.get_n_features(), 2);
assert!(forest.get_trees().is_some());
}
#[test]
fn test_single_feature() {
let mut forest = IsolationForest::new(10, 20, Some(3), Some(42)).unwrap();
let x = arr2(&[
[1.0],
[1.1],
[0.9],
[1.05],
[10.0], ]);
let result = forest.fit(&x.view());
assert!(result.is_ok());
let scores = forest.predict(&x.view()).unwrap();
assert_eq!(scores.len(), 5);
for &score in scores.iter() {
assert!(score >= 0.0 && score <= 1.0);
}
}
#[test]
fn test_reproducibility() {
let x = arr2(&[[1.0, 2.0], [2.0, 1.0], [1.5, 1.5], [10.0, 10.0]]);
let mut forest1 = IsolationForest::new(10, 20, Some(3), Some(42)).unwrap();
let mut forest2 = IsolationForest::new(10, 20, Some(3), Some(42)).unwrap();
let scores1 = forest1.fit_predict(&x.view()).unwrap();
let scores2 = forest2.fit_predict(&x.view()).unwrap();
for i in 0..scores1.len() {
assert!((scores1[i] - scores2[i]).abs() < 1e-10);
}
}
#[test]
fn test_different_n_estimators() {
let x = arr2(&[[1.0, 2.0], [2.0, 1.0], [1.5, 1.5], [0.5, 2.5]]);
for n_estimators in [1, 5, 10, 50] {
let mut forest = IsolationForest::new(n_estimators, 10, Some(2), Some(42)).unwrap();
let result = forest.fit(&x.view());
assert!(result.is_ok());
assert_eq!(forest.get_trees().as_ref().unwrap().len(), n_estimators);
}
}
#[test]
fn test_different_max_samples() {
let x = arr2(&[
[1.0, 2.0],
[2.0, 1.0],
[1.5, 1.5],
[0.5, 2.5],
[2.5, 0.5],
[1.8, 1.2],
]);
for max_samples in [2, 4, 6, 10] {
let mut forest = IsolationForest::new(5, max_samples, Some(3), Some(42)).unwrap();
let result = forest.fit(&x.view());
assert!(result.is_ok());
assert_eq!(forest.get_max_samples(), max_samples);
}
}
#[test]
fn test_path_length_consistency() {
let mut forest = IsolationForest::new(1, 10, Some(3), Some(42)).unwrap(); let x = arr2(&[[1.0, 1.0], [2.0, 2.0], [3.0, 3.0]]);
forest.fit(&x.view()).unwrap();
let sample = [1.5, 1.5];
let score1 = forest.anomaly_score(&sample).unwrap();
let score2 = forest.anomaly_score(&sample).unwrap();
assert_eq!(score1, score2);
}
#[test]
fn test_larger_dataset() {
let mut forest = IsolationForest::new(20, 100, Some(5), Some(42)).unwrap();
let mut data = Vec::new();
for i in 0..100 {
data.push((i as f64 * 0.01 - 1.0).sin());
data.push((i as f64 * 0.01 - 1.0).cos());
}
data.extend_from_slice(&[10.0, 10.0, -10.0, -10.0]);
let x = Array2::from_shape_vec((102, 2), data).unwrap();
let result = forest.fit(&x.view());
assert!(result.is_ok());
let scores = forest.predict(&x.view()).unwrap();
assert_eq!(scores.len(), 102);
for &score in scores.iter() {
assert!(score >= 0.0 && score <= 1.0);
assert!(!score.is_nan());
}
}
#[test]
fn test_identical_points() {
let mut forest = IsolationForest::new(10, 20, Some(3), Some(42)).unwrap();
let x = arr2(&[[1.0, 2.0], [1.0, 2.0], [1.0, 2.0], [1.0, 2.0]]);
let result = forest.fit(&x.view());
assert!(result.is_ok());
let scores = forest.predict(&x.view()).unwrap();
assert_eq!(scores.len(), 4);
let first_score = scores[0];
for &score in scores.iter() {
assert!((score - first_score).abs() < 1e-10);
assert!(score >= 0.0 && score <= 1.0);
}
}
#[test]
fn test_parallel_consistency() {
let x = arr2(&[
[1.0, 2.0],
[2.0, 3.0],
[3.0, 4.0],
[4.0, 5.0],
[100.0, 200.0],
]);
let mut forest = IsolationForest::new(10, 20, Some(4), Some(42)).unwrap();
forest.fit(&x.view()).unwrap();
let scores1 = forest.predict(&x.view()).unwrap();
let scores2 = forest.predict(&x.view()).unwrap();
for i in 0..scores1.len() {
assert_eq!(scores1[i], scores2[i]);
}
}
#[test]
fn test_memory_efficiency() {
let mut forest = IsolationForest::new(5, 10, Some(3), Some(42)).unwrap();
let x_train = arr2(&[[1.0, 2.0], [2.0, 1.0], [1.5, 1.5]]);
forest.fit(&x_train.view()).unwrap();
for i in 0..100 {
let test_point = [i as f64 * 0.1, i as f64 * 0.1];
let score = forest.anomaly_score(&test_point).unwrap();
assert!(score >= 0.0 && score <= 1.0);
}
}