use rtx_automeasure::agents::{ BinningStrategy, FeatureEngineer, FeaturePipeline, FeatureTransformation, ScalingMethod, StatisticalMethod, }; use rtx_automeasure::{AutoMLResult, TaskType}; use rtx_tensor::{Device, Tensor}; #[tokio::test] async fn test_feature_engineer_creation() -> AutoMLResult<()> { let engineer = FeatureEngineer::new(TaskType::Classification)?; Ok(()) } #[tokio::test] async fn test_feature_engineer_configuration() -> AutoMLResult<()> { let engineer = FeatureEngineer::new(TaskType::Classification)? .with_max_features(500) .with_selection_threshold(0.05) .with_random_state(123); Ok(()) } #[tokio::test] #[ignore = "Pre-existing shape error issue"] async fn test_engineer_features_basic() -> AutoMLResult<()> { let device = Device::cpu(); let engineer = FeatureEngineer::new(TaskType::Classification)?; let x = Tensor::randn(&[100, 5], &device)?; let y = Tensor::zeros(&[100], &device)?; let pipeline = engineer.engineer_features(&x, &y).await?; assert!(!pipeline.transformations.is_empty()); assert!(!pipeline.feature_names.is_empty()); Ok(()) } #[tokio::test] #[ignore = "Pre-existing shape error issue"] async fn test_engineer_features_with_transform() -> AutoMLResult<()> { let device = Device::cpu(); let engineer = FeatureEngineer::new(TaskType::Regression)?; let x_train = Tensor::randn(&[80, 6], &device)?; let y_train = Tensor::randn(&[80], &device)?; let pipeline = engineer.engineer_features(&x_train, &y_train).await?; // Apply transformation to new data let x_test = Tensor::randn(&[20, 6], &device)?; let x_transformed = engineer.transform(&x_test, &pipeline).await?; assert_eq!(x_transformed.shape()[0], 20); Ok(()) } #[tokio::test] async fn test_feature_pipeline_creation() { let pipeline = FeaturePipeline::new(); assert!(pipeline.transformations.is_empty()); assert!(pipeline.selected_features.is_empty()); assert!(pipeline.feature_names.is_empty()); assert!(pipeline.importance_scores.is_empty()); } #[tokio::test] async fn test_feature_pipeline_add_transformation() { let mut pipeline = FeaturePipeline::new(); pipeline.add_transformation(FeatureTransformation::Scaling { method: ScalingMethod::StandardScaler, }); pipeline.add_transformation(FeatureTransformation::Polynomial { degree: 2, include_bias: false, }); assert_eq!(pipeline.transformations.len(), 2); } #[tokio::test] async fn test_feature_pipeline_set_selected_features() { let mut pipeline = FeaturePipeline::new(); pipeline.set_selected_features(vec![0, 2, 4, 6]); assert_eq!(pipeline.selected_features.len(), 4); } #[tokio::test] async fn test_feature_pipeline_get_n_features() { let mut pipeline = FeaturePipeline::new(); pipeline.feature_names = vec![ "feat1".to_string(), "feat2".to_string(), "feat3".to_string(), ]; assert_eq!(pipeline.get_n_features(), 3); } #[tokio::test] async fn test_polynomial_transformation() { let transform = FeatureTransformation::Polynomial { degree: 2, include_bias: true, }; match transform { FeatureTransformation::Polynomial { degree, include_bias, } => { assert_eq!(degree, 2); assert!(include_bias); } _ => panic!("Wrong transformation type"), } } #[tokio::test] async fn test_interaction_transformation() { let transform = FeatureTransformation::Interaction { features: vec![0, 1, 3], }; match transform { FeatureTransformation::Interaction { features } => { assert_eq!(features.len(), 3); } _ => panic!("Wrong transformation type"), } } #[tokio::test] async fn test_statistical_transformations() { let methods = vec![ StatisticalMethod::Log, StatisticalMethod::Sqrt, StatisticalMethod::Square, StatisticalMethod::Reciprocal, StatisticalMethod::Abs, StatisticalMethod::Sign, ]; for method in methods { let transform = FeatureTransformation::Statistical { method: method.clone(), }; assert!(matches!( transform, FeatureTransformation::Statistical { .. } )); } } #[tokio::test] async fn test_binning_transformation_uniform() { let transform = FeatureTransformation::Binning { n_bins: 10, strategy: BinningStrategy::Uniform, }; match transform { FeatureTransformation::Binning { n_bins, strategy } => { assert_eq!(n_bins, 10); assert!(matches!(strategy, BinningStrategy::Uniform)); } _ => panic!("Wrong transformation type"), } } #[tokio::test] async fn test_binning_transformation_quantile() { let transform = FeatureTransformation::Binning { n_bins: 5, strategy: BinningStrategy::Quantile, }; match transform { FeatureTransformation::Binning { n_bins, strategy } => { assert_eq!(n_bins, 5); assert!(matches!(strategy, BinningStrategy::Quantile)); } _ => panic!("Wrong transformation type"), } } #[tokio::test] async fn test_scaling_transformations() { let methods = vec![ ScalingMethod::StandardScaler, ScalingMethod::MinMaxScaler, ScalingMethod::RobustScaler, ]; for method in methods { let transform = FeatureTransformation::Scaling { method: method.clone(), }; assert!(matches!(transform, FeatureTransformation::Scaling { .. })); } } #[tokio::test] #[ignore = "Pre-existing shape error issue"] async fn test_engineer_features_regression() -> AutoMLResult<()> { let device = Device::cpu(); let engineer = FeatureEngineer::new(TaskType::Regression)?; let x = Tensor::randn(&[60, 8], &device)?; let y = Tensor::randn(&[60], &device)?; let pipeline = engineer.engineer_features(&x, &y).await?; assert!(!pipeline.transformations.is_empty()); Ok(()) } #[tokio::test] #[ignore = "Pre-existing shape error issue"] async fn test_engineer_features_classification() -> AutoMLResult<()> { let device = Device::cpu(); let engineer = FeatureEngineer::new(TaskType::Classification)?; let x = Tensor::randn(&[100, 10], &device)?; let y = Tensor::zeros(&[100], &device)?; let pipeline = engineer.engineer_features(&x, &y).await?; assert!(!pipeline.transformations.is_empty()); Ok(()) } #[tokio::test] #[ignore = "Pre-existing shape error issue"] async fn test_transform_preserves_samples() -> AutoMLResult<()> { let device = Device::cpu(); let engineer = FeatureEngineer::new(TaskType::Classification)?; let x_train = Tensor::randn(&[80, 5], &device)?; let y_train = Tensor::zeros(&[80], &device)?; let pipeline = engineer.engineer_features(&x_train, &y_train).await?; let x_test = Tensor::randn(&[30, 5], &device)?; let x_transformed = engineer.transform(&x_test, &pipeline).await?; assert_eq!(x_transformed.shape()[0], 30); Ok(()) } #[tokio::test] #[ignore = "Pre-existing shape error issue"] async fn test_feature_importance_scores() -> AutoMLResult<()> { let device = Device::cpu(); let engineer = FeatureEngineer::new(TaskType::Classification)?; let x = Tensor::randn(&[100, 8], &device)?; let y = Tensor::zeros(&[100], &device)?; let pipeline = engineer.engineer_features(&x, &y).await?; assert!(!pipeline.importance_scores.is_empty()); Ok(()) } #[tokio::test] #[ignore = "Pre-existing shape error issue"] async fn test_feature_selection_with_threshold() -> AutoMLResult<()> { let device = Device::cpu(); let engineer = FeatureEngineer::new(TaskType::Classification)?.with_selection_threshold(0.05); let x = Tensor::randn(&[150, 20], &device)?; let y = Tensor::zeros(&[150], &device)?; let pipeline = engineer.engineer_features(&x, &y).await?; assert!(!pipeline.selected_features.is_empty()); Ok(()) } #[tokio::test] #[ignore = "Pre-existing shape error issue"] async fn test_max_features_constraint() -> AutoMLResult<()> { let device = Device::cpu(); let engineer = FeatureEngineer::new(TaskType::Regression)?.with_max_features(10); let x = Tensor::randn(&[100, 50], &device)?; let y = Tensor::randn(&[100], &device)?; let pipeline = engineer.engineer_features(&x, &y).await?; if !pipeline.selected_features.is_empty() { assert!(pipeline.selected_features.len() <= 10); } Ok(()) } #[tokio::test] async fn test_feature_pipeline_serialization() -> AutoMLResult<()> { let mut pipeline = FeaturePipeline::new(); pipeline.add_transformation(FeatureTransformation::Scaling { method: ScalingMethod::StandardScaler, }); pipeline.set_selected_features(vec![0, 1, 2]); pipeline.feature_names = vec!["feat1".to_string(), "feat2".to_string()]; let serialized = serde_json::to_string(&pipeline)?; assert!(!serialized.is_empty()); let deserialized: FeaturePipeline = serde_json::from_str(&serialized)?; assert_eq!( deserialized.transformations.len(), pipeline.transformations.len() ); Ok(()) } #[tokio::test] async fn test_transformation_serialization() -> AutoMLResult<()> { let transform = FeatureTransformation::Polynomial { degree: 3, include_bias: true, }; let serialized = serde_json::to_string(&transform)?; assert!(!serialized.is_empty()); let deserialized: FeatureTransformation = serde_json::from_str(&serialized)?; match deserialized { FeatureTransformation::Polynomial { degree, include_bias, } => { assert_eq!(degree, 3); assert!(include_bias); } _ => panic!("Wrong transformation type after deserialization"), } Ok(()) } #[tokio::test] #[ignore = "Pre-existing shape error issue"] async fn test_engineer_with_small_dataset() -> AutoMLResult<()> { let device = Device::cpu(); let engineer = FeatureEngineer::new(TaskType::Classification)?; let x = Tensor::randn(&[30, 4], &device)?; let y = Tensor::zeros(&[30], &device)?; let pipeline = engineer.engineer_features(&x, &y).await?; assert!(!pipeline.transformations.is_empty()); Ok(()) } #[tokio::test] #[ignore = "Pre-existing shape error issue"] async fn test_engineer_with_large_feature_space() -> AutoMLResult<()> { let device = Device::cpu(); let engineer = FeatureEngineer::new(TaskType::Regression)?.with_max_features(100); let x = Tensor::randn(&[200, 50], &device)?; let y = Tensor::randn(&[200], &device)?; let pipeline = engineer.engineer_features(&x, &y).await?; assert!(!pipeline.transformations.is_empty()); Ok(()) } #[tokio::test] async fn test_multiple_transformations_in_pipeline() { let mut pipeline = FeaturePipeline::new(); pipeline.add_transformation(FeatureTransformation::Scaling { method: ScalingMethod::StandardScaler, }); pipeline.add_transformation(FeatureTransformation::Polynomial { degree: 2, include_bias: false, }); pipeline.add_transformation(FeatureTransformation::Statistical { method: StatisticalMethod::Log, }); assert_eq!(pipeline.transformations.len(), 3); } #[tokio::test] #[ignore = "Pre-existing shape error issue"] async fn test_feature_names_generation() -> AutoMLResult<()> { let device = Device::cpu(); let engineer = FeatureEngineer::new(TaskType::Classification)?; let x = Tensor::randn(&[50, 5], &device)?; let y = Tensor::zeros(&[50], &device)?; let pipeline = engineer.engineer_features(&x, &y).await?; assert!(!pipeline.feature_names.is_empty()); for name in &pipeline.feature_names { assert!(!name.is_empty()); } Ok(()) } #[tokio::test] #[ignore = "Pre-existing shape error issue"] async fn test_random_state_reproducibility() -> AutoMLResult<()> { let device = Device::cpu(); let engineer1 = FeatureEngineer::new(TaskType::Classification)?.with_random_state(42); let engineer2 = FeatureEngineer::new(TaskType::Classification)?.with_random_state(42); let x = Tensor::randn(&[60, 6], &device)?; let y = Tensor::zeros(&[60], &device)?; let pipeline1 = engineer1.engineer_features(&x, &y).await?; let pipeline2 = engineer2.engineer_features(&x, &y).await?; assert_eq!( pipeline1.transformations.len(), pipeline2.transformations.len() ); Ok(()) } #[tokio::test] async fn test_box_cox_transformation() { let transform = FeatureTransformation::Statistical { method: StatisticalMethod::BoxCox { lambda: 0.5 }, }; match transform { FeatureTransformation::Statistical { method } => match method { StatisticalMethod::BoxCox { lambda } => { assert_eq!(lambda, 0.5); } _ => panic!("Wrong statistical method"), }, _ => panic!("Wrong transformation type"), } } #[tokio::test] async fn test_empty_pipeline_default() { let pipeline = FeaturePipeline::default(); assert!(pipeline.transformations.is_empty()); assert!(pipeline.selected_features.is_empty()); assert_eq!(pipeline.get_n_features(), 0); }