use rtx_diffuse::{DiffusionScheduler, NoiseGenerator, NoiseSchedule, Result, SchedulerType}; use rtx_tensor::Tensor; #[test] fn test_scheduler_creation() -> Result<()> { let noise_gen = NoiseGenerator::new( NoiseSchedule::Linear { beta_start: 0.0001, beta_end: 0.02, }, 1000, Some(42), )?; // Test DDIM scheduler let ddim_scheduler = DiffusionScheduler::new(SchedulerType::DDIM { eta: 0.0 }, noise_gen.clone(), 50)?; assert_eq!(ddim_scheduler.timesteps().len(), 50); // Test DPM++ scheduler let dpm_scheduler = DiffusionScheduler::new(SchedulerType::DPMPlusPlus, noise_gen.clone(), 20)?; assert_eq!(dpm_scheduler.timesteps().len(), 20); // Test Euler Ancestral let euler_scheduler = DiffusionScheduler::new(SchedulerType::EulerAncestral, noise_gen.clone(), 25)?; assert_eq!(euler_scheduler.timesteps().len(), 25); // Test DDPM scheduler let ddpm_scheduler = DiffusionScheduler::new(SchedulerType::DDPM, noise_gen, 30)?; assert_eq!(ddpm_scheduler.timesteps().len(), 30); Ok(()) } #[test] #[ignore = "Pre-existing timestep ordering assertion failure"] fn test_timestep_scheduling() -> Result<()> { let noise_gen = NoiseGenerator::new( NoiseSchedule::Linear { beta_start: 0.0001, beta_end: 0.02, }, 1000, Some(42), )?; let scheduler = DiffusionScheduler::new(SchedulerType::DDIM { eta: 0.0 }, noise_gen, 10)?; let timesteps = scheduler.timesteps(); assert_eq!(timesteps.len(), 10); // Timesteps should be in descending order for i in 1..timesteps.len() { assert!( timesteps[i - 1] > timesteps[i], "Timesteps should be descending: {} > {}", timesteps[i - 1], timesteps[i] ); } // First timestep should be high, last should be low assert!(timesteps[0] > 800, "First timestep should be high"); assert!( timesteps[timesteps.len() - 1] < 200, "Last timestep should be low" ); Ok(()) } #[test] fn test_ddim_step() -> Result<()> { let noise_gen = NoiseGenerator::new( NoiseSchedule::Linear { beta_start: 0.0001, beta_end: 0.02, }, 1000, Some(42), )?; let scheduler = DiffusionScheduler::new( SchedulerType::DDIM { eta: 0.0 }, // Deterministic DDIM noise_gen, 50, )?; // Create sample and model output let sample_data = vec![0.5; 2 * 3 * 8 * 8]; let sample = Tensor::new(sample_data, vec![2, 3, 8, 8])?; let model_output_data = vec![0.1; 2 * 3 * 8 * 8]; let model_output = Tensor::new(model_output_data, vec![2, 3, 8, 8])?; let timestep = scheduler.timesteps()[0]; let result = scheduler.step(&model_output, timestep, &sample, None)?; // Result should have same shape as input assert_eq!(result.shape().dims(), sample.shape().dims()); // Values should change (denoising step) let sample_data = sample.data()?; let result_data = result.data()?; assert_ne!( sample_data[0], result_data[0], "DDIM step should change the sample" ); Ok(()) } #[test] #[ignore = "Pre-existing DPM++ step assertion failure"] fn test_dpm_plusplus_step() -> Result<()> { let noise_gen = NoiseGenerator::new(NoiseSchedule::Cosine { s: 0.008 }, 1000, Some(42))?; let scheduler = DiffusionScheduler::new(SchedulerType::DPMPlusPlus, noise_gen, 25)?; let sample_data = vec![0.3; 1 * 4 * 16 * 16]; let sample = Tensor::new(sample_data, vec![1, 4, 16, 16])?; let model_output_data = vec![-0.1; 1 * 4 * 16 * 16]; let model_output = Tensor::new(model_output_data, vec![1, 4, 16, 16])?; let timestep = scheduler.timesteps()[10]; // Middle timestep let result = scheduler.step(&model_output, timestep, &sample, None)?; assert_eq!(result.shape().dims(), sample.shape().dims()); // Check that denoising occurred let sample_data = sample.data()?; let result_data = result.data()?; let diff = (sample_data[0] - result_data[0]).abs(); assert!( diff > 0.001, "DPM++ step should meaningfully change the sample" ); Ok(()) } #[test] fn test_euler_ancestral_step() -> Result<()> { let noise_gen = NoiseGenerator::new( NoiseSchedule::ScaledLinear { beta_start: 0.00085, beta_end: 0.012, }, 1000, Some(42), )?; let scheduler = DiffusionScheduler::new(SchedulerType::EulerAncestral, noise_gen, 40)?; let sample_data = vec![0.7; 1 * 3 * 32 * 32]; let sample = Tensor::new(sample_data, vec![1, 3, 32, 32])?; let model_output_data = vec![0.2; 1 * 3 * 32 * 32]; let model_output = Tensor::new(model_output_data, vec![1, 3, 32, 32])?; let timestep = scheduler.timesteps()[5]; let result = scheduler.step(&model_output, timestep, &sample, None)?; assert_eq!(result.shape().dims(), sample.shape().dims()); Ok(()) } #[test] fn test_ddpm_step() -> Result<()> { let noise_gen = NoiseGenerator::new( NoiseSchedule::Linear { beta_start: 0.0001, beta_end: 0.02, }, 1000, Some(42), )?; let scheduler = DiffusionScheduler::new(SchedulerType::DDPM, noise_gen, 100)?; let sample_data = vec![1.0; 2 * 4 * 8 * 8]; let sample = Tensor::new(sample_data, vec![2, 4, 8, 8])?; let model_output_data = vec![0.5; 2 * 4 * 8 * 8]; let model_output = Tensor::new(model_output_data, vec![2, 4, 8, 8])?; let timestep = scheduler.timesteps()[50]; let result = scheduler.step(&model_output, timestep, &sample, None)?; assert_eq!(result.shape().dims(), sample.shape().dims()); // DDPM should predict x0 and sample from posterior let result_data = result.data()?; assert!(result_data[0].is_finite(), "DDPM result should be finite"); Ok(()) } #[test] fn test_scale_model_input() -> Result<()> { let noise_gen = NoiseGenerator::new( NoiseSchedule::Linear { beta_start: 0.0001, beta_end: 0.02, }, 1000, Some(42), )?; let scheduler = DiffusionScheduler::new(SchedulerType::DDIM { eta: 0.5 }, noise_gen, 50)?; let sample_data = vec![0.5; 1 * 3 * 16 * 16]; let sample = Tensor::new(sample_data, vec![1, 3, 16, 16])?; let timestep = scheduler.timesteps()[0]; let scaled = scheduler.scale_model_input(&sample, timestep)?; // For most schedulers, scaling is identity assert_eq!(scaled.shape().dims(), sample.shape().dims()); let sample_data = sample.data()?; let scaled_data = scaled.data()?; for (a, b) in sample_data.iter().zip(scaled_data.iter()) { assert!((a - b).abs() < 1e-6, "Default scaling should be identity"); } Ok(()) } #[test] fn test_add_noise_through_scheduler() -> Result<()> { let noise_gen = NoiseGenerator::new( NoiseSchedule::Linear { beta_start: 0.0001, beta_end: 0.02, }, 1000, Some(42), )?; let scheduler = DiffusionScheduler::new(SchedulerType::DDIM { eta: 0.0 }, noise_gen, 50)?; let original_data = vec![1.0; 2 * 3 * 4 * 4]; let original = Tensor::new(original_data, vec![2, 3, 4, 4])?; let noise_data = vec![0.0; 2 * 3 * 4 * 4]; let noise = Tensor::new(noise_data, vec![2, 3, 4, 4])?; // Test adding noise at different timesteps let noisy_0 = scheduler.add_noise(&original, &noise, 0)?; let noisy_500 = scheduler.add_noise(&original, &noise, 500)?; let noisy_999 = scheduler.add_noise(&original, &noise, 999)?; assert_eq!(noisy_0.shape().dims(), original.shape().dims()); assert_eq!(noisy_500.shape().dims(), original.shape().dims()); assert_eq!(noisy_999.shape().dims(), original.shape().dims()); Ok(()) } #[test] fn test_get_variance_through_scheduler() -> Result<()> { let noise_gen = NoiseGenerator::new(NoiseSchedule::Cosine { s: 0.008 }, 1000, Some(42))?; let scheduler = DiffusionScheduler::new(SchedulerType::DPMPlusPlus, noise_gen, 30)?; // Test variance extraction let var_0 = scheduler.get_variance(0)?; let var_500 = scheduler.get_variance(500)?; assert_eq!(var_0, 0.0); assert!(var_500 > 0.0); Ok(()) } #[test] fn test_invalid_scheduler_parameters() { let noise_gen = NoiseGenerator::new( NoiseSchedule::Linear { beta_start: 0.0001, beta_end: 0.02, }, 1000, Some(42), ) .unwrap(); // Zero inference steps should fail let result = DiffusionScheduler::new(SchedulerType::DDIM { eta: 0.0 }, noise_gen.clone(), 0); assert!(result.is_err()); // Too many inference steps should fail let result = DiffusionScheduler::new( SchedulerType::DDIM { eta: 0.0 }, noise_gen, 1500, // > num_train_timesteps ); assert!(result.is_err()); } #[test] fn test_scheduler_consistency() -> Result<()> { let noise_gen = NoiseGenerator::new( NoiseSchedule::Linear { beta_start: 0.0001, beta_end: 0.02, }, 1000, Some(42), )?; let scheduler = DiffusionScheduler::new(SchedulerType::DDIM { eta: 0.0 }, noise_gen, 20)?; let sample_data = vec![0.5; 1 * 3 * 8 * 8]; let sample = Tensor::new(sample_data, vec![1, 3, 8, 8])?; let model_output_data = vec![0.1; 1 * 3 * 8 * 8]; let model_output = Tensor::new(model_output_data, vec![1, 3, 8, 8])?; // Multiple steps with same input should be deterministic let timestep = scheduler.timesteps()[5]; let result1 = scheduler.step(&model_output, timestep, &sample, None)?; let result2 = scheduler.step(&model_output, timestep, &sample, None)?; let data1 = result1.data()?; let data2 = result2.data()?; for (a, b) in data1.iter().zip(data2.iter()) { assert!( (a - b).abs() < 1e-6, "Same inputs should produce identical results" ); } Ok(()) }