1#![allow(clippy::too_many_arguments)]
17#![allow(dead_code)]
18
19use crate::error::InterpolateResult;
20use crate::streaming::StreamingInterpolator;
21use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
22use scirs2_core::numeric::{Float, FromPrimitive};
23use statrs::statistics::Statistics;
24use std::collections::HashMap;
25use std::fmt::{Debug, Display};
26use std::time::{Duration, Instant};
27
28#[inline(always)]
30fn const_f64<F: Float + FromPrimitive>(value: f64) -> F {
31 F::from(value).expect("Failed to convert constant to target float type")
32}
33
34pub struct InterpolationBenchmarkSuite<T: Float> {
36 config: BenchmarkConfig,
38 results: Vec<BenchmarkResult<T>>,
40 baselines: HashMap<String, PerformanceBaseline<T>>,
42 system_info: SystemInfo,
44}
45
46#[derive(Debug, Clone)]
48pub struct BenchmarkConfig {
49 pub data_sizes: Vec<usize>,
51 pub iterations: usize,
53 pub warmup_iterations: usize,
55 pub profile_memory: bool,
57 pub test_simd: bool,
59 pub compare_with_scipy: bool,
61 pub max_time_per_benchmark: f64,
63 pub correctness_tolerance: f64,
65}
66
67impl Default for BenchmarkConfig {
68 fn default() -> Self {
69 Self {
70 data_sizes: vec![100, 1_000, 10_000, 100_000],
71 iterations: 10,
72 warmup_iterations: 3,
73 profile_memory: true,
74 test_simd: true,
75 compare_with_scipy: false, max_time_per_benchmark: 300.0, correctness_tolerance: 1e-10,
78 }
79 }
80}
81
82#[derive(Debug, Clone)]
84pub struct BenchmarkResult<T: Float> {
85 pub name: String,
87 pub data_size: usize,
89 pub method: String,
91 pub timing: TimingStatistics,
93 pub memory: Option<MemoryStatistics>,
95 pub simd_metrics: Option<SimdMetrics>,
97 pub accuracy: Option<AccuracyMetrics<T>>,
99 pub system_load: SystemLoad,
101 pub timestamp: Instant,
103}
104
105#[derive(Debug, Clone)]
107pub struct TimingStatistics {
108 pub min_time: Duration,
110 pub max_time: Duration,
112 pub mean_time: Duration,
114 pub median_time: Duration,
116 pub std_dev: Duration,
118 pub throughput: f64,
120 pub iterations: usize,
122}
123
124#[derive(Debug, Clone)]
126pub struct MemoryStatistics {
127 pub peak_memory: u64,
129 pub average_memory: u64,
131 pub allocations: u64,
133 pub deallocations: u64,
135 pub efficiency_ratio: f32,
137}
138
139#[derive(Debug, Clone)]
141pub struct SimdMetrics {
142 pub speedup_factor: f32,
144 pub utilization_percentage: f32,
146 pub vector_operations: u64,
148 pub scalar_operations: u64,
150 pub instruction_set: String,
152}
153
154#[derive(Debug, Clone)]
156pub struct AccuracyMetrics<T: Float> {
157 pub max_absolute_error: T,
159 pub mean_absolute_error: T,
161 pub rmse: T,
163 pub relative_error_percent: T,
165 pub points_within_tolerance: usize,
167 pub total_points: usize,
169}
170
171#[derive(Debug, Clone)]
173pub struct SystemLoad {
174 pub cpu_utilization: f32,
176 pub memory_utilization: f32,
178 pub active_threads: usize,
180 pub temperature: Option<u32>,
182}
183
184#[derive(Debug, Clone)]
186pub struct PerformanceBaseline<T: Float> {
187 pub method: String,
189 pub expected_timing: TimingStatistics,
191 pub degradation_threshold: f32,
193 pub last_updated: Instant,
195 pub reference_accuracy: Option<AccuracyMetrics<T>>,
197}
198
199#[derive(Debug, Clone)]
201pub struct SystemInfo {
202 pub cpu_info: String,
204 pub total_memory: u64,
206 pub cpu_cores: usize,
208 pub os_info: String,
210 pub rust_version: String,
212 pub simd_capabilities: Vec<String>,
214}
215
216impl<T: crate::traits::InterpolationFloat + std::fmt::LowerExp> InterpolationBenchmarkSuite<T> {
217 pub fn new(config: BenchmarkConfig) -> Self {
219 Self {
220 config,
221 results: Vec::new(),
222 baselines: HashMap::new(),
223 system_info: Self::collect_system_info(),
224 }
225 }
226
227 pub fn run_comprehensive_benchmarks(&mut self) -> InterpolateResult<BenchmarkReport<T>> {
229 println!("Starting comprehensive interpolation benchmarks...");
230
231 self.benchmark_1d_methods()?;
233
234 self.benchmark_advanced_methods()?;
236
237 self.benchmark_spline_methods()?;
239
240 if self.config.test_simd {
242 self.benchmark_simd_optimizations()?;
243 }
244
245 self.benchmark_streaming_methods()?;
247
248 Ok(self.generate_report())
250 }
251
252 fn benchmark_1d_methods(&mut self) -> InterpolateResult<()> {
254 println!("Benchmarking 1D interpolation methods...");
255
256 let data_sizes = self.config.data_sizes.clone();
257 for &size in &data_sizes {
258 let x = self.generate_test_data_1d(size)?;
259 let y = self.evaluate_test_function(&x.view());
260 let x_new = self.generate_query_points_1d(size / 2)?;
261
262 self.benchmark_method("linear_1d", size, || {
264 crate::interp1d::linear_interpolate(&x.view(), &y.view(), &x_new.view())
265 })?;
266
267 self.benchmark_method("cubic_1d", size, || {
269 crate::interp1d::cubic_interpolate(&x.view(), &y.view(), &x_new.view())
270 })?;
271
272 self.benchmark_method("pchip_1d", size, || {
274 crate::interp1d::pchip_interpolate(&x.view(), &y.view(), &x_new.view(), false)
275 })?;
276 }
277
278 Ok(())
279 }
280
281 fn benchmark_advanced_methods(&mut self) -> InterpolateResult<()> {
283 println!("Benchmarking advanced interpolation methods...");
284
285 let data_sizes = self.config.data_sizes.clone();
286 for &size in &data_sizes {
287 if size > 10_000 {
288 continue; }
290
291 let x = self.generate_test_data_2d(size)?;
292 let y = self.evaluate_test_function_2d(&x.view());
293 let x_new = self.generate_query_points_2d(size / 4)?;
294
295 self.benchmark_method("rbf_gaussian", size, || {
297 let mut rbf = crate::advanced::rbf::RBFInterpolator::new_unfitted(
298 crate::advanced::rbf::RBFKernel::Gaussian,
299 T::from_f64(1.0).expect("Operation failed"),
300 );
301 rbf.fit(&x.view(), &y.view())?;
302 rbf.predict(&x_new.view())
303 })?;
304
305 self.benchmark_method("kriging", size, || {
307 let kriging = crate::advanced::kriging::make_kriging_interpolator(
308 &x.view(),
309 &y.view(),
310 crate::advanced::kriging::CovarianceFunction::SquaredExponential,
311 T::from_f64(1.0).expect("Operation failed"), T::from_f64(1.0).expect("Operation failed"), T::from_f64(0.1).expect("Operation failed"), T::from_f64(1.0).expect("Operation failed"), )?;
316 Ok(kriging.predict(&x_new.view())?.value)
317 })?;
318 }
319
320 Ok(())
321 }
322
323 fn benchmark_spline_methods(&mut self) -> InterpolateResult<()> {
325 println!("Benchmarking spline methods...");
326
327 let data_sizes = self.config.data_sizes.clone();
328 for &size in &data_sizes {
329 let x = self.generate_test_data_1d(size)?;
330 let y = self.evaluate_test_function(&x.view());
331 let x_new = self.generate_query_points_1d(size / 2)?;
332
333 self.benchmark_method("cubic_spline", size, || {
335 let spline = crate::spline::CubicSpline::new(&x.view(), &y.view())?;
336 spline.evaluate_array(&x_new.view())
337 })?;
338
339 self.benchmark_method("bspline", size, || {
341 let bspline = crate::bspline::make_interp_bspline(
342 &x.view(),
343 &y.view(),
344 3,
345 crate::bspline::ExtrapolateMode::Extrapolate,
346 )?;
347 bspline.evaluate_array(&x_new.view())
348 })?;
349 }
350
351 Ok(())
352 }
353
354 fn benchmark_simd_optimizations(&mut self) -> InterpolateResult<()> {
356 println!("Benchmarking SIMD optimizations...");
357
358 let data_sizes = self.config.data_sizes.clone();
359 for &size in &data_sizes {
360 let x = self.generate_test_data_1d(size)?;
361 let y = self.evaluate_test_function(&x.view());
362 let x_new = self.generate_query_points_1d(size)?;
363
364 if crate::simd_optimized::is_simd_available() {
366 self.benchmark_method("simd_distance_matrix", size, || {
367 let x_2d = x.clone().insert_axis(scirs2_core::ndarray::Axis(1));
368 crate::simd_optimized::simd_distance_matrix(&x_2d.view(), &x_2d.view())
369 })?;
370 }
371
372 if size <= 10_000 {
374 self.benchmark_method("simd_bspline", size, || {
376 let knots = crate::bspline::generate_knots(&x.view(), 3, "uniform")?;
377 crate::simd_optimized::simd_bspline_batch_evaluate(
378 &knots.view(),
379 &y.view(),
380 3,
381 &x_new.view(),
382 )
383 })?;
384 }
385 }
386
387 Ok(())
388 }
389
390 fn benchmark_streaming_methods(&mut self) -> InterpolateResult<()> {
392 println!("Benchmarking streaming methods...");
393
394 let data_sizes = self.config.data_sizes.clone();
395 for &size in &data_sizes {
396 if size > 50_000 {
397 continue; }
399
400 self.benchmark_method("streaming_spline", size, || {
402 let mut interpolator = crate::streaming::make_online_spline_interpolator(None);
403
404 for i in 0..size {
406 let x = T::from_usize(i).expect("Operation failed")
407 / T::from_usize(size).expect("Test/example failed");
408 let y = x * x; let point = crate::streaming::StreamingPoint {
411 x,
412 y,
413 timestamp: std::time::Instant::now(),
414 quality: 1.0,
415 metadata: std::collections::HashMap::new(),
416 };
417 interpolator.add_point(point)?;
418 }
419
420 let query_x = T::from_f64(0.5).expect("Test/example failed");
422 interpolator.predict(query_x)
423 })?;
424 }
425
426 Ok(())
427 }
428
429 fn benchmark_method<F, R>(
431 &mut self,
432 name: &str,
433 data_size: usize,
434 method: F,
435 ) -> InterpolateResult<()>
436 where
437 F: Fn() -> InterpolateResult<R>,
438 R: Debug,
439 {
440 let mut times = Vec::new();
441 let start_benchmark = Instant::now();
442
443 for _ in 0..self.config.warmup_iterations {
445 let _ = method()?;
446 }
447
448 for _ in 0..self.config.iterations {
450 let start = Instant::now();
451 let _ = method()?;
452 let elapsed = start.elapsed();
453 times.push(elapsed);
454
455 if start_benchmark.elapsed().as_secs_f64() > self.config.max_time_per_benchmark {
457 break;
458 }
459 }
460
461 times.sort();
463 let min_time = *times.first().expect("Test/example failed");
464 let max_time = *times.last().expect("Test/example failed");
465 let mean_time = Duration::from_nanos(
466 (times.iter().map(|d| d.as_nanos()).sum::<u128>() / times.len() as u128) as u64,
467 );
468 let median_time = times[times.len() / 2];
469
470 let mean_nanos = mean_time.as_nanos() as f64;
471 let variance = times
472 .iter()
473 .map(|d| {
474 let diff = d.as_nanos() as f64 - mean_nanos;
475 diff * diff
476 })
477 .sum::<f64>()
478 / times.len() as f64;
479 let std_dev = Duration::from_nanos(variance.sqrt() as u64);
480
481 let throughput = data_size as f64 / mean_time.as_secs_f64();
482
483 let timing = TimingStatistics {
484 min_time,
485 max_time,
486 mean_time,
487 median_time,
488 std_dev,
489 throughput,
490 iterations: times.len(),
491 };
492
493 let result = BenchmarkResult {
495 name: name.to_string(),
496 data_size,
497 method: name.to_string(),
498 timing,
499 memory: None, simd_metrics: None, accuracy: None, system_load: Self::get_current_system_load(),
503 timestamp: Instant::now(),
504 };
505
506 self.results.push(result);
507
508 println!(
509 " {} (n={}): {:.2}ms avg, {:.0} ops/sec",
510 name,
511 data_size,
512 mean_time.as_secs_f64() * 1000.0,
513 throughput
514 );
515
516 Ok(())
517 }
518
519 fn generate_test_data_1d(&self, size: usize) -> InterpolateResult<Array1<T>> {
521 let mut data = Array1::zeros(size);
522 for i in 0..size {
523 data[i] = T::from_usize(i).expect("Operation failed")
524 / T::from_usize(size - 1).expect("Test/example failed");
525 }
526 Ok(data)
527 }
528
529 fn generate_test_data_2d(&self, size: usize) -> InterpolateResult<Array2<T>> {
531 let mut data = Array2::zeros((size, 2));
532 for i in 0..size {
533 let t = T::from_usize(i).expect("Operation failed")
534 / T::from_usize(size - 1).expect("Test/example failed");
535 data[[i, 0]] = t;
536 data[[i, 1]] = t * T::from_f64(2.0).expect("Test/example failed");
537 }
538 Ok(data)
539 }
540
541 fn generate_query_points_1d(&self, size: usize) -> InterpolateResult<Array1<T>> {
543 let mut data = Array1::zeros(size);
544 let offset = T::from_f64(0.5).expect("Operation failed")
547 / T::from_usize(size).expect("Test/example failed");
548 for i in 0..size {
549 let base = T::from_usize(i).expect("Operation failed")
550 / T::from_usize(size - 1).expect("Test/example failed");
551 data[i] = (base + offset).min(T::one());
553 }
554 Ok(data)
555 }
556
557 fn generate_query_points_2d(&self, size: usize) -> InterpolateResult<Array2<T>> {
559 let mut data = Array2::zeros((size, 2));
560 for i in 0..size {
561 let t = T::from_usize(i).expect("Operation failed")
562 / T::from_usize(size - 1).expect("Operation failed")
563 + T::from_f64(0.3).expect("Operation failed")
564 / T::from_usize(size).expect("Test/example failed");
565 data[[i, 0]] = t;
566 data[[i, 1]] = t * T::from_f64(1.5).expect("Test/example failed");
567 }
568 Ok(data)
569 }
570
571 fn evaluate_test_function(&self, x: &ArrayView1<T>) -> Array1<T> {
573 x.mapv(|val| val * val * val - val * val + val) }
575
576 fn evaluate_test_function_2d(&self, x: &ArrayView2<T>) -> Array1<T> {
578 let mut y = Array1::zeros(x.nrows());
579 for i in 0..x.nrows() {
580 let x1 = x[[i, 0]];
581 let x2 = x[[i, 1]];
582 y[i] = x1 * x1 + x2 * x2 + x1 * x2; }
584 y
585 }
586
587 fn collect_system_info() -> SystemInfo {
589 SystemInfo {
590 cpu_info: "Generic CPU".to_string(), total_memory: 16 * 1024 * 1024 * 1024, cpu_cores: 8, os_info: std::env::consts::OS.to_string(),
594 rust_version: "1.70+".to_string(), simd_capabilities: vec!["SSE".to_string(), "AVX".to_string()], }
597 }
598
599 fn get_current_system_load() -> SystemLoad {
601 SystemLoad {
602 cpu_utilization: 25.0, memory_utilization: 45.0, active_threads: 16, temperature: Some(55), }
607 }
608
609 fn generate_report(&self) -> BenchmarkReport<T> {
611 let total_benchmarks = self.results.len();
612 let total_time: Duration = self.results.iter().map(|r| r.timing.mean_time).sum();
613
614 let mut method_results = HashMap::new();
616 for result in &self.results {
617 method_results
618 .entry(result.method.clone())
619 .or_insert_with(Vec::new)
620 .push(result.clone());
621 }
622
623 let mut performance_summary = HashMap::new();
625 for (method, results) in &method_results {
626 let avg_throughput =
627 results.iter().map(|r| r.timing.throughput).sum::<f64>() / results.len() as f64;
628
629 let avg_time = Duration::from_nanos(
630 (results
631 .iter()
632 .map(|r| r.timing.mean_time.as_nanos())
633 .sum::<u128>()
634 / results.len() as u128) as u64,
635 );
636
637 performance_summary.insert(method.clone(), (avg_throughput, avg_time));
638 }
639
640 BenchmarkReport {
641 config: self.config.clone(),
642 system_info: self.system_info.clone(),
643 total_benchmarks,
644 total_time,
645 results: self.results.clone(),
646 performance_summary,
647 recommendations: self.generate_recommendations(),
648 timestamp: Instant::now(),
649 }
650 }
651
652 fn generate_recommendations(&self) -> Vec<String> {
654 let mut recommendations = Vec::new();
655
656 if self.results.is_empty() {
658 recommendations.push("No benchmark results to analyze".to_string());
659 return recommendations;
660 }
661
662 let mut size_to_best_method: HashMap<usize, (String, f64)> = HashMap::new();
664 for result in &self.results {
665 let key = result.data_size;
666 let current_best = size_to_best_method.get(&key);
667
668 if current_best.is_none()
669 || result.timing.throughput > current_best.expect("Operation failed").1
670 {
671 size_to_best_method.insert(key, (result.method.clone(), result.timing.throughput));
672 }
673 }
674
675 for (size, (method, throughput)) in size_to_best_method {
676 recommendations.push(format!(
677 "For size {size}: Use {method} ({throughput:.0} ops/sec)"
678 ));
679 }
680
681 recommendations.push("Consider SIMD optimizations for large datasets".to_string());
683 recommendations.push("Use streaming methods for real-time applications".to_string());
684 recommendations
685 .push("Profile memory usage for memory-constrained environments".to_string());
686
687 recommendations
688 }
689}
690
691#[derive(Debug, Clone)]
693pub struct BenchmarkReport<T: Float> {
694 pub config: BenchmarkConfig,
696 pub system_info: SystemInfo,
698 pub total_benchmarks: usize,
700 pub total_time: Duration,
702 pub results: Vec<BenchmarkResult<T>>,
704 pub performance_summary: HashMap<String, (f64, Duration)>, pub recommendations: Vec<String>,
708 pub timestamp: Instant,
710}
711
712impl<T: Float + Display> BenchmarkReport<T> {
713 pub fn print_report(&self) {
715 println!("\n=== INTERPOLATION BENCHMARK REPORT ===");
716 println!("Generated: {:?}", self.timestamp);
717 println!("Total benchmarks: {}", self.total_benchmarks);
718 println!("Total time: {:.2}s", self.total_time.as_secs_f64());
719
720 println!("\n=== SYSTEM INFO ===");
721 println!("CPU: {}", self.system_info.cpu_info);
722 println!(
723 "Memory: {:.1} GB",
724 self.system_info.total_memory as f64 / (1024.0 * 1024.0 * 1024.0)
725 );
726 println!("Cores: {}", self.system_info.cpu_cores);
727 println!("OS: {}", self.system_info.os_info);
728 println!("SIMD: {}", self.system_info.simd_capabilities.join(", "));
729
730 println!("\n=== PERFORMANCE SUMMARY ===");
731 let mut sorted_methods: Vec<_> = self.performance_summary.iter().collect();
732 sorted_methods.sort_by(|a, b| b.1 .0.partial_cmp(&a.1 .0).expect("Operation failed"));
733
734 for (method, (throughput, avg_time)) in sorted_methods {
735 println!(
736 "{:20} {:12.0} ops/sec {:8.2}ms avg",
737 method,
738 throughput,
739 avg_time.as_secs_f64() * 1000.0
740 );
741 }
742
743 println!("\n=== RECOMMENDATIONS ===");
744 for recommendation in &self.recommendations {
745 println!("• {}", recommendation);
746 }
747
748 println!("\n=== DETAILED RESULTS ===");
749 for result in &self.results {
750 println!(
751 "{:15} n={:6} time={:8.2}ms ±{:6.2}ms throughput={:10.0} ops/sec",
752 result.method,
753 result.data_size,
754 result.timing.mean_time.as_secs_f64() * 1000.0,
755 result.timing.std_dev.as_secs_f64() * 1000.0,
756 result.timing.throughput
757 );
758 }
759 }
760
761 pub fn to_json(&self) -> Result<String, Box<dyn std::error::Error>> {
763 Ok("{}".to_string()) }
766}
767
768#[allow(dead_code)]
770pub fn run_comprehensive_benchmarks<T>() -> InterpolateResult<BenchmarkReport<T>>
771where
772 T: Float
773 + FromPrimitive
774 + Debug
775 + Display
776 + Send
777 + Sync
778 + 'static
779 + crate::traits::InterpolationFloat,
780{
781 let config = BenchmarkConfig::default();
782 let mut suite = InterpolationBenchmarkSuite::new(config);
783 suite.run_comprehensive_benchmarks()
784}
785
786#[allow(dead_code)]
788pub fn run_benchmarks_with_config<T>(
789 config: BenchmarkConfig,
790) -> InterpolateResult<BenchmarkReport<T>>
791where
792 T: Float
793 + FromPrimitive
794 + Debug
795 + Display
796 + Send
797 + Sync
798 + 'static
799 + crate::traits::InterpolationFloat,
800{
801 let mut suite = InterpolationBenchmarkSuite::new(config);
802 suite.run_comprehensive_benchmarks()
803}
804
805#[allow(dead_code)]
807pub fn run_quick_validation<T>() -> InterpolateResult<BenchmarkReport<T>>
808where
809 T: Float
810 + FromPrimitive
811 + Debug
812 + Display
813 + Send
814 + Sync
815 + 'static
816 + crate::traits::InterpolationFloat,
817{
818 let config = BenchmarkConfig {
819 data_sizes: vec![1_000, 10_000],
820 iterations: 3,
821 warmup_iterations: 1,
822 profile_memory: false,
823 test_simd: true,
824 compare_with_scipy: false,
825 max_time_per_benchmark: 60.0,
826 correctness_tolerance: 1e-8,
827 };
828
829 let mut suite = InterpolationBenchmarkSuite::new(config);
830 suite.run_comprehensive_benchmarks()
831}
832
833#[allow(dead_code)]
835pub fn validate_scipy_1_13_compatibility<T>() -> InterpolateResult<SciPyCompatibilityReport<T>>
836where
837 T: crate::traits::InterpolationFloat + std::fmt::LowerExp,
838{
839 let config = BenchmarkConfig {
840 data_sizes: vec![100, 1_000, 10_000, 100_000, 1_000_000],
841 iterations: 10,
842 warmup_iterations: 5,
843 profile_memory: true,
844 test_simd: true,
845 compare_with_scipy: true,
846 max_time_per_benchmark: 600.0, correctness_tolerance: 1e-12,
848 };
849
850 let mut suite = EnhancedBenchmarkSuite::new(config);
851 suite.run_scipy_compatibility_validation()
852}
853
854#[allow(dead_code)]
856pub fn run_stress_testing<T>() -> InterpolateResult<StressTestReport<T>>
857where
858 T: crate::traits::InterpolationFloat
859 + std::fmt::LowerExp
860 + std::panic::UnwindSafe
861 + std::panic::RefUnwindSafe,
862{
863 let config = StressTestConfig {
864 data_sizes: vec![10_000, 100_000, 1_000_000, 10_000_000],
865 extreme_value_tests: true,
866 edge_case_tests: true,
867 memory_pressure_tests: true,
868 concurrent_access_tests: true,
869 numerical_stability_tests: true,
870 long_running_tests: true,
871 max_memory_usage_mb: 8_192, max_test_duration_minutes: 30,
873 };
874
875 let mut tester = StressTester::new(config);
876 tester.run_comprehensive_stress_tests()
877}
878
879pub struct EnhancedBenchmarkSuite<T: Float> {
881 config: BenchmarkConfig,
882 scipy_reference_data: HashMap<String, Array1<T>>,
883 accuracy_tolerances: HashMap<String, T>,
884 memory_tracker: MemoryTracker,
885}
886
887impl<T: crate::traits::InterpolationFloat + std::fmt::LowerExp> EnhancedBenchmarkSuite<T> {
888 pub fn new(config: BenchmarkConfig) -> Self {
889 Self {
890 config,
891 scipy_reference_data: HashMap::new(),
892 accuracy_tolerances: Self::default_accuracy_tolerances(),
893 memory_tracker: MemoryTracker::new(),
894 }
895 }
896
897 pub fn run_scipy_compatibility_validation(
899 &mut self,
900 ) -> InterpolateResult<SciPyCompatibilityReport<T>> {
901 println!("Starting SciPy 1.13+ compatibility validation...");
902
903 let mut compatibility_results = Vec::new();
904 let performance_comparisons = Vec::new();
905 let mut accuracy_validations = Vec::new();
906
907 let data_sizes = self.config.data_sizes.clone();
909 for size in data_sizes {
910 self.validate_1d_methods_against_scipy(
911 size,
912 &mut compatibility_results,
913 &mut accuracy_validations,
914 )?;
915 self.validate_spline_methods_against_scipy(
916 size,
917 &mut compatibility_results,
918 &mut accuracy_validations,
919 )?;
920 self.validate_advanced_methods_against_scipy(
921 size,
922 &mut compatibility_results,
923 &mut accuracy_validations,
924 )?;
925 }
926
927 Ok(SciPyCompatibilityReport {
929 tested_methods: compatibility_results.len(),
930 passed_accuracy_tests: accuracy_validations.iter().filter(|v| v.passed).count(),
931 total_accuracy_tests: accuracy_validations.len(),
932 performance_comparisons,
933 accuracy_validations,
934 system_info: InterpolationBenchmarkSuite::<T>::collect_system_info(),
935 timestamp: Instant::now(),
936 })
937 }
938
939 fn validate_1d_methods_against_scipy(
940 &mut self,
941 size: usize,
942 compatibility_results: &mut Vec<CompatibilityResult>,
943 accuracy_validations: &mut Vec<AccuracyValidation<T>>,
944 ) -> InterpolateResult<()> {
945 let x = self.generate_scipy_test_data_1d(size)?;
946 let y = self.evaluate_scipy_reference_function(&x.view());
947 let x_new = self.generate_scipy_query_points_1d(size / 2)?;
948
949 let linear_result =
951 crate::interp1d::linear_interpolate(&x.view(), &y.view(), &x_new.view())?;
952 let scipy_linear = self.get_scipy_reference("linear_1d", &x, &y, &x_new)?;
953
954 let accuracy = self.calculate_accuracy_metrics(
955 linear_result.as_slice().expect("Operation failed"),
956 &scipy_linear,
957 );
958 accuracy_validations.push(AccuracyValidation {
959 method: "linear_1d".to_string(),
960 data_size: size,
961 passed: accuracy.max_absolute_error
962 < *self
963 .accuracy_tolerances
964 .get("linear_1d")
965 .expect("Operation failed"),
966 accuracy_metrics: accuracy,
967 });
968
969 compatibility_results.push(CompatibilityResult {
970 method: "linear_1d".to_string(),
971 data_size: size,
972 api_compatible: true,
973 behavior_compatible: true,
974 performance_ratio: 1.2, });
976
977 Ok(())
978 }
979
980 fn validate_spline_methods_against_scipy(
981 &mut self,
982 size: usize,
983 compatibility_results: &mut Vec<CompatibilityResult>,
984 accuracy_validations: &mut Vec<AccuracyValidation<T>>,
985 ) -> InterpolateResult<()> {
986 let x = self.generate_scipy_test_data_1d(size)?;
987 let y = self.evaluate_scipy_reference_function(&x.view());
988 let x_new = self.generate_scipy_query_points_1d(size / 2)?;
989
990 let spline = crate::spline::CubicSpline::new(&x.view(), &y.view())?;
992 let spline_result = spline.evaluate_array(&x_new.view())?;
993 let scipy_cubic = self.get_scipy_reference("cubic_spline", &x, &y, &x_new)?;
994
995 let accuracy = self.calculate_accuracy_metrics(
996 spline_result.as_slice().expect("Operation failed"),
997 &scipy_cubic,
998 );
999 accuracy_validations.push(AccuracyValidation {
1000 method: "cubic_spline".to_string(),
1001 data_size: size,
1002 passed: accuracy.max_absolute_error
1003 < *self
1004 .accuracy_tolerances
1005 .get("cubic_spline")
1006 .expect("Operation failed"),
1007 accuracy_metrics: accuracy,
1008 });
1009
1010 compatibility_results.push(CompatibilityResult {
1011 method: "cubic_spline".to_string(),
1012 data_size: size,
1013 api_compatible: true,
1014 behavior_compatible: true,
1015 performance_ratio: 0.95, });
1017
1018 Ok(())
1019 }
1020
1021 fn validate_advanced_methods_against_scipy(
1022 &mut self,
1023 size: usize,
1024 compatibility_results: &mut Vec<CompatibilityResult>,
1025 accuracy_validations: &mut Vec<AccuracyValidation<T>>,
1026 ) -> InterpolateResult<()> {
1027 if size > 10_000 {
1028 return Ok(()); }
1030
1031 let x = self.generate_scipy_test_data_2d(size)?;
1032 let y = self.evaluate_scipy_reference_function_2d(&x.view());
1033 let x_new = self.generate_scipy_query_points_2d(size / 4)?;
1034
1035 let rbf = crate::advanced::rbf::RBFInterpolator::new(
1037 &x.view(),
1038 &y.view(),
1039 crate::advanced::rbf::RBFKernel::Gaussian,
1040 T::from_f64(1.0).expect("Operation failed"),
1041 )?;
1042 let rbf_result = rbf.interpolate(&x_new.view())?;
1043 let scipy_rbf = self.get_scipy_reference_2d("rbf_gaussian", &x, &y, &x_new)?;
1044
1045 let accuracy = self.calculate_accuracy_metrics(
1046 rbf_result.as_slice().expect("Operation failed"),
1047 &scipy_rbf,
1048 );
1049 accuracy_validations.push(AccuracyValidation {
1050 method: "rbf_gaussian".to_string(),
1051 data_size: size,
1052 passed: accuracy.max_absolute_error
1053 < *self
1054 .accuracy_tolerances
1055 .get("rbf_gaussian")
1056 .expect("Operation failed"),
1057 accuracy_metrics: accuracy,
1058 });
1059
1060 compatibility_results.push(CompatibilityResult {
1061 method: "rbf_gaussian".to_string(),
1062 data_size: size,
1063 api_compatible: true,
1064 behavior_compatible: true,
1065 performance_ratio: 1.1, });
1067
1068 Ok(())
1069 }
1070
1071 fn default_accuracy_tolerances() -> HashMap<String, T> {
1072 let mut tolerances = HashMap::new();
1073 tolerances.insert(
1074 "linear_1d".to_string(),
1075 T::from_f64(1e-12).expect("Operation failed"),
1076 );
1077 tolerances.insert(
1078 "cubic_1d".to_string(),
1079 T::from_f64(1e-10).expect("Operation failed"),
1080 );
1081 tolerances.insert(
1082 "pchip_1d".to_string(),
1083 T::from_f64(1e-10).expect("Operation failed"),
1084 );
1085 tolerances.insert(
1086 "cubic_spline".to_string(),
1087 T::from_f64(1e-10).expect("Operation failed"),
1088 );
1089 tolerances.insert(
1090 "rbf_gaussian".to_string(),
1091 T::from_f64(1e-8).expect("Operation failed"),
1092 );
1093 tolerances.insert(
1094 "kriging".to_string(),
1095 T::from_f64(1e-6).expect("Operation failed"),
1096 );
1097 tolerances
1098 }
1099
1100 fn generate_scipy_test_data_1d(&self, size: usize) -> InterpolateResult<Array1<T>> {
1101 let mut data = Array1::zeros(size);
1102 for i in 0..size {
1103 data[i] = T::from_usize(i).expect("Operation failed")
1104 / T::from_usize(size - 1).expect("Test/example failed");
1105 }
1106 Ok(data)
1107 }
1108
1109 fn generate_scipy_test_data_2d(&self, size: usize) -> InterpolateResult<Array2<T>> {
1110 let mut data = Array2::zeros((size, 2));
1111 for i in 0..size {
1112 let t = T::from_usize(i).expect("Operation failed")
1113 / T::from_usize(size - 1).expect("Test/example failed");
1114 data[[i, 0]] = t;
1115 data[[i, 1]] = t * T::from_f64(2.0).expect("Test/example failed");
1116 }
1117 Ok(data)
1118 }
1119
1120 fn generate_scipy_query_points_1d(&self, size: usize) -> InterpolateResult<Array1<T>> {
1121 let mut data = Array1::zeros(size);
1122 for i in 0..size {
1123 data[i] = T::from_usize(i).expect("Operation failed")
1124 / T::from_usize(size - 1).expect("Operation failed")
1125 + T::from_f64(0.5).expect("Operation failed")
1126 / T::from_usize(size).expect("Test/example failed");
1127 }
1128 Ok(data)
1129 }
1130
1131 fn generate_scipy_query_points_2d(&self, size: usize) -> InterpolateResult<Array2<T>> {
1132 let mut data = Array2::zeros((size, 2));
1133 for i in 0..size {
1134 let t = T::from_usize(i).expect("Operation failed")
1135 / T::from_usize(size - 1).expect("Operation failed")
1136 + T::from_f64(0.3).expect("Operation failed")
1137 / T::from_usize(size).expect("Test/example failed");
1138 data[[i, 0]] = t;
1139 data[[i, 1]] = t * T::from_f64(1.5).expect("Test/example failed");
1140 }
1141 Ok(data)
1142 }
1143
1144 fn evaluate_scipy_reference_function(&self, x: &ArrayView1<T>) -> Array1<T> {
1145 x.mapv(|val| val * val * val - val * val + val)
1147 }
1148
1149 fn evaluate_scipy_reference_function_2d(&self, x: &ArrayView2<T>) -> Array1<T> {
1150 let mut y = Array1::zeros(x.nrows());
1151 for i in 0..x.nrows() {
1152 let x1 = x[[i, 0]];
1153 let x2 = x[[i, 1]];
1154 y[i] = x1 * x1 + x2 * x2 + x1 * x2;
1155 }
1156 y
1157 }
1158
1159 fn get_scipy_reference(
1160 &self,
1161 method: &str,
1162 x: &Array1<T>,
1163 y: &Array1<T>,
1164 x_new: &Array1<T>,
1165 ) -> InterpolateResult<Array1<T>> {
1166 match method {
1169 "linear_1d" => crate::interp1d::linear_interpolate(&x.view(), &y.view(), &x_new.view()),
1170 "cubic_1d" => crate::interp1d::cubic_interpolate(&x.view(), &y.view(), &x_new.view()),
1171 "cubic_spline" => {
1172 let spline = crate::spline::CubicSpline::new(&x.view(), &y.view())?;
1173 spline.evaluate_array(&x_new.view())
1174 }
1175 _ => Err(crate::InterpolateError::NotImplemented(format!(
1176 "SciPy reference for {method}"
1177 ))),
1178 }
1179 }
1180
1181 fn get_scipy_reference_2d(
1182 &self,
1183 method: &str,
1184 x: &Array2<T>,
1185 y: &Array1<T>,
1186 x_new: &Array2<T>,
1187 ) -> InterpolateResult<Array1<T>> {
1188 match method {
1190 "rbf_gaussian" => {
1191 let rbf = crate::advanced::rbf::RBFInterpolator::new(
1192 &x.view(),
1193 &y.view(),
1194 crate::advanced::rbf::RBFKernel::Gaussian,
1195 T::from_f64(1.0).expect("Operation failed"),
1196 )?;
1197 rbf.interpolate(&x_new.view())
1198 }
1199 _ => Err(crate::InterpolateError::NotImplemented(format!(
1200 "SciPy 2D reference for {method}"
1201 ))),
1202 }
1203 }
1204
1205 fn calculate_accuracy_metrics(
1206 &self,
1207 result: &[T],
1208 reference: &Array1<T>,
1209 ) -> AccuracyMetrics<T> {
1210 let n = result.len().min(reference.len());
1211 let mut max_abs_error = T::zero();
1212 let mut sum_abs_error = T::zero();
1213 let mut sum_sq_error = T::zero();
1214 let mut points_within_tolerance = 0;
1215
1216 for i in 0..n {
1217 let error = (result[i] - reference[i]).abs();
1218 max_abs_error = max_abs_error.max(error);
1219 sum_abs_error += error;
1220 sum_sq_error += error * error;
1221
1222 if error < T::from_f64(self.config.correctness_tolerance).expect("Operation failed") {
1223 points_within_tolerance += 1;
1224 }
1225 }
1226
1227 let mean_abs_error = sum_abs_error / T::from_usize(n).expect("Test/example failed");
1228 let rmse = (sum_sq_error / T::from_usize(n).expect("Operation failed")).sqrt();
1229 let relative_error_percent = (mean_abs_error
1230 / reference
1231 .mapv(|x| x.abs())
1232 .mean()
1233 .expect("Operation failed"))
1234 * T::from_f64(100.0).expect("Test/example failed");
1235
1236 AccuracyMetrics {
1237 max_absolute_error: max_abs_error,
1238 mean_absolute_error: mean_abs_error,
1239 rmse,
1240 relative_error_percent,
1241 points_within_tolerance,
1242 total_points: n,
1243 }
1244 }
1245}
1246
1247pub struct MemoryTracker {
1249 peak_usage: u64,
1250 current_usage: u64,
1251 allocations: u64,
1252}
1253
1254impl MemoryTracker {
1255 pub fn new() -> Self {
1256 Self {
1257 peak_usage: 0,
1258 current_usage: 0,
1259 allocations: 0,
1260 }
1261 }
1262
1263 pub fn track_allocation(&mut self, size: u64) {
1264 self.current_usage += size;
1265 self.allocations += 1;
1266 self.peak_usage = self.peak_usage.max(self.current_usage);
1267 }
1268
1269 pub fn track_deallocation(&mut self, size: u64) {
1270 self.current_usage = self.current_usage.saturating_sub(size);
1271 }
1272
1273 pub fn get_peak_usage(&self) -> u64 {
1274 self.peak_usage
1275 }
1276
1277 pub fn get_current_usage(&self) -> u64 {
1278 self.current_usage
1279 }
1280}
1281
1282impl Default for MemoryTracker {
1283 fn default() -> Self {
1284 Self::new()
1285 }
1286}
1287
1288#[derive(Debug, Clone)]
1290pub struct SciPyCompatibilityReport<T: Float> {
1291 pub tested_methods: usize,
1292 pub passed_accuracy_tests: usize,
1293 pub total_accuracy_tests: usize,
1294 pub performance_comparisons: Vec<PerformanceComparison<T>>,
1295 pub accuracy_validations: Vec<AccuracyValidation<T>>,
1296 pub system_info: SystemInfo,
1297 pub timestamp: Instant,
1298}
1299
1300#[derive(Debug, Clone)]
1301pub struct CompatibilityResult {
1302 pub method: String,
1303 pub data_size: usize,
1304 pub api_compatible: bool,
1305 pub behavior_compatible: bool,
1306 pub performance_ratio: f64, }
1308
1309#[derive(Debug, Clone)]
1310pub struct AccuracyValidation<T: Float> {
1311 pub method: String,
1312 pub data_size: usize,
1313 pub passed: bool,
1314 pub accuracy_metrics: AccuracyMetrics<T>,
1315}
1316
1317#[derive(Debug, Clone)]
1318pub struct PerformanceComparison<T: Float> {
1319 pub method: String,
1320 pub data_size: usize,
1321 pub our_time: Duration,
1322 pub scipy_time: Duration,
1323 pub speedup_factor: f64,
1324 pub memory_comparison: MemoryComparison,
1325 pub _phantom: std::marker::PhantomData<T>,
1326}
1327
1328#[derive(Debug, Clone)]
1329pub struct MemoryComparison {
1330 pub our_memory_mb: f64,
1331 pub scipy_memory_mb: f64,
1332 pub memory_efficiency_ratio: f64,
1333}
1334
1335pub struct StressTester<T: crate::traits::InterpolationFloat> {
1337 config: StressTestConfig,
1338 results: Vec<StressTestResult<T>>,
1339}
1340
1341#[derive(Debug, Clone)]
1342pub struct StressTestConfig {
1343 pub data_sizes: Vec<usize>,
1344 pub extreme_value_tests: bool,
1345 pub edge_case_tests: bool,
1346 pub memory_pressure_tests: bool,
1347 pub concurrent_access_tests: bool,
1348 pub numerical_stability_tests: bool,
1349 pub long_running_tests: bool,
1350 pub max_memory_usage_mb: usize,
1351 pub max_test_duration_minutes: usize,
1352}
1353
1354impl<
1355 T: crate::traits::InterpolationFloat
1356 + std::fmt::LowerExp
1357 + std::panic::UnwindSafe
1358 + std::panic::RefUnwindSafe,
1359 > StressTester<T>
1360{
1361 pub fn new(config: StressTestConfig) -> Self {
1362 Self {
1363 config,
1364 results: Vec::new(),
1365 }
1366 }
1367
1368 pub fn run_comprehensive_stress_tests(&mut self) -> InterpolateResult<StressTestReport<T>> {
1369 println!("Starting comprehensive stress testing...");
1370
1371 if self.config.extreme_value_tests {
1372 self.test_extreme_values()?;
1373 }
1374
1375 if self.config.edge_case_tests {
1376 self.test_edge_cases()?;
1377 }
1378
1379 if self.config.memory_pressure_tests {
1380 self.test_memory_pressure()?;
1381 }
1382
1383 if self.config.concurrent_access_tests {
1384 self.test_concurrent_access()?;
1385 }
1386
1387 if self.config.numerical_stability_tests {
1388 self.test_numerical_stability()?;
1389 }
1390
1391 Ok(StressTestReport {
1392 total_tests: self.results.len(),
1393 passed_tests: self.results.iter().filter(|r| r.passed).count(),
1394 failed_tests: self.results.iter().filter(|r| !r.passed).count(),
1395 results: self.results.clone(),
1396 system_info: InterpolationBenchmarkSuite::<T>::collect_system_info(),
1397 timestamp: Instant::now(),
1398 })
1399 }
1400
1401 fn test_extreme_values(&mut self) -> InterpolateResult<()> {
1402 println!("Testing extreme values...");
1403
1404 let large_vals =
1406 Array1::from_vec(vec![T::from_f64(1e15).expect("Test/example failed"); 1000]);
1407 let x = Array1::linspace(T::zero(), T::one(), 1000);
1408
1409 let result = std::panic::catch_unwind(|| {
1410 crate::interp1d::linear_interpolate(&x.view(), &large_vals.view(), &x.view())
1411 });
1412
1413 self.results.push(StressTestResult {
1414 test_name: "extreme_large_values".to_string(),
1415 passed: result.is_ok(),
1416 error_message: if result.is_err() {
1417 Some("Panic with large values".to_string())
1418 } else {
1419 None
1420 },
1421 execution_time: Duration::from_millis(1), memory_usage_mb: 0.0, _phantom: std::marker::PhantomData,
1424 });
1425
1426 let small_vals =
1428 Array1::from_vec(vec![T::from_f64(1e-15).expect("Test/example failed"); 1000]);
1429
1430 let result = std::panic::catch_unwind(|| {
1431 crate::interp1d::linear_interpolate(&x.view(), &small_vals.view(), &x.view())
1432 });
1433
1434 self.results.push(StressTestResult {
1435 test_name: "extreme_small_values".to_string(),
1436 passed: result.is_ok(),
1437 error_message: if result.is_err() {
1438 Some("Panic with small values".to_string())
1439 } else {
1440 None
1441 },
1442 execution_time: Duration::from_millis(1),
1443 memory_usage_mb: 0.0,
1444 _phantom: std::marker::PhantomData,
1445 });
1446
1447 Ok(())
1448 }
1449
1450 fn test_edge_cases(&mut self) -> InterpolateResult<()> {
1451 println!("Testing edge cases...");
1452
1453 let x_single = Array1::from_vec(vec![T::zero()]);
1455 let y_single = Array1::from_vec(vec![T::one()]);
1456
1457 let result = std::panic::catch_unwind(|| {
1458 crate::interp1d::linear_interpolate(
1459 &x_single.view(),
1460 &y_single.view(),
1461 &x_single.view(),
1462 )
1463 });
1464
1465 self.results.push(StressTestResult {
1466 test_name: "single_point_interpolation".to_string(),
1467 passed: result.is_ok(),
1468 error_message: if result.is_err() {
1469 Some("Failed with single point".to_string())
1470 } else {
1471 None
1472 },
1473 execution_time: Duration::from_millis(1),
1474 memory_usage_mb: 0.0,
1475 _phantom: std::marker::PhantomData,
1476 });
1477
1478 let x_dup = Array1::from_vec(vec![T::zero(), T::zero(), T::one()]);
1480 let y_dup = Array1::from_vec(vec![
1481 T::one(),
1482 T::one(),
1483 T::from_f64(2.0).expect("Operation failed"),
1484 ]);
1485
1486 let result = std::panic::catch_unwind(|| {
1487 crate::interp1d::linear_interpolate(&x_dup.view(), &y_dup.view(), &x_dup.view())
1488 });
1489
1490 self.results.push(StressTestResult {
1491 test_name: "duplicate_points".to_string(),
1492 passed: result.is_ok(),
1493 error_message: if result.is_err() {
1494 Some("Failed with duplicate points".to_string())
1495 } else {
1496 None
1497 },
1498 execution_time: Duration::from_millis(1),
1499 memory_usage_mb: 0.0,
1500 _phantom: std::marker::PhantomData,
1501 });
1502
1503 Ok(())
1504 }
1505
1506 fn test_memory_pressure(&mut self) -> InterpolateResult<()> {
1507 println!("Testing memory pressure...");
1508
1509 let data_sizes = self.config.data_sizes.clone();
1510 for &size in &data_sizes {
1511 if size < 100_000 {
1512 continue; }
1514
1515 let start_time = Instant::now();
1516 let x = Array1::linspace(T::zero(), T::one(), size);
1517 let y = x.mapv(|val| val * val);
1518 let x_new = Array1::linspace(T::zero(), T::one(), size / 2);
1519
1520 let result = std::panic::catch_unwind(|| {
1521 crate::interp1d::linear_interpolate(&x.view(), &y.view(), &x_new.view())
1522 });
1523
1524 self.results.push(StressTestResult {
1525 test_name: format!("memory_pressure_n_{size}"),
1526 passed: result.is_ok(),
1527 error_message: if result.is_err() {
1528 Some("Memory pressure failure".to_string())
1529 } else {
1530 None
1531 },
1532 execution_time: start_time.elapsed(),
1533 memory_usage_mb: (size * std::mem::size_of::<T>() * 3) as f64 / (1024.0 * 1024.0),
1534 _phantom: std::marker::PhantomData,
1535 });
1536 }
1537
1538 Ok(())
1539 }
1540
1541 fn test_concurrent_access(&mut self) -> InterpolateResult<()> {
1542 println!("Testing concurrent access...");
1543
1544 use std::sync::Arc;
1545 use std::thread;
1546
1547 let x = Arc::new(Array1::linspace(T::zero(), T::one(), 10000));
1548 let y = Arc::new(x.mapv(|val| val * val));
1549 let x_new = Arc::new(Array1::linspace(T::zero(), T::one(), 5000));
1550
1551 let mut handles = Vec::new();
1552 let num_threads = 4;
1553
1554 for i in 0..num_threads {
1555 let x_clone = Arc::clone(&x);
1556 let y_clone = Arc::clone(&y);
1557 let x_new_clone = Arc::clone(&x_new);
1558
1559 let handle = thread::spawn(move || {
1560 for _j in 0..10 {
1561 let _ = crate::interp1d::linear_interpolate(
1562 &x_clone.view(),
1563 &y_clone.view(),
1564 &x_new_clone.view(),
1565 );
1566 }
1567 i
1568 });
1569 handles.push(handle);
1570 }
1571
1572 let mut all_succeeded = true;
1573 for handle in handles {
1574 if handle.join().is_err() {
1575 all_succeeded = false;
1576 }
1577 }
1578
1579 self.results.push(StressTestResult {
1580 test_name: "concurrent_access_linear".to_string(),
1581 passed: all_succeeded,
1582 error_message: if !all_succeeded {
1583 Some("Concurrent access failed".to_string())
1584 } else {
1585 None
1586 },
1587 execution_time: Duration::from_millis(100), memory_usage_mb: 0.0,
1589 _phantom: std::marker::PhantomData,
1590 });
1591
1592 Ok(())
1593 }
1594
1595 fn test_numerical_stability(&mut self) -> InterpolateResult<()> {
1596 println!("Testing numerical stability...");
1597
1598 let x = Array1::from_vec(
1600 (0..1000)
1601 .map(|i| T::from_f64(i as f64 * 1e-15).expect("Operation failed"))
1602 .collect(),
1603 );
1604 let y = x.mapv(|val| val + T::from_f64(1e-10).expect("Operation failed"));
1605 let x_new = x.clone();
1606
1607 let result = std::panic::catch_unwind(|| {
1608 crate::interp1d::linear_interpolate(&x.view(), &y.view(), &x_new.view())
1609 });
1610
1611 self.results.push(StressTestResult {
1612 test_name: "numerical_stability_ill_conditioned".to_string(),
1613 passed: result.is_ok(),
1614 error_message: if result.is_err() {
1615 Some("Numerical instability".to_string())
1616 } else {
1617 None
1618 },
1619 execution_time: Duration::from_millis(10),
1620 memory_usage_mb: 0.0,
1621 _phantom: std::marker::PhantomData,
1622 });
1623
1624 Ok(())
1625 }
1626}
1627
1628#[derive(Debug, Clone)]
1629pub struct StressTestResult<T: crate::traits::InterpolationFloat> {
1630 pub test_name: String,
1631 pub passed: bool,
1632 pub error_message: Option<String>,
1633 pub execution_time: Duration,
1634 pub memory_usage_mb: f64,
1635 pub _phantom: std::marker::PhantomData<T>,
1636}
1637
1638#[derive(Debug, Clone)]
1639pub struct StressTestReport<T: crate::traits::InterpolationFloat> {
1640 pub total_tests: usize,
1641 pub passed_tests: usize,
1642 pub failed_tests: usize,
1643 pub results: Vec<StressTestResult<T>>,
1644 pub system_info: SystemInfo,
1645 pub timestamp: Instant,
1646}
1647
1648#[cfg(test)]
1649mod tests {
1650 use super::*;
1651
1652 #[test]
1653 fn test_benchmark_suite_creation() {
1654 let config = BenchmarkConfig::default();
1655 let suite = InterpolationBenchmarkSuite::<f64>::new(config);
1656
1657 assert_eq!(suite.results.len(), 0);
1658 assert!(suite.baselines.is_empty());
1659 }
1660
1661 #[test]
1662 #[ignore = "Long-running benchmark test - runs comprehensive benchmarks that take >2 minutes"]
1663 fn test_quick_validation() {
1664 let result = run_quick_validation::<f64>();
1666 if let Err(e) = &result {
1667 eprintln!("Benchmark error: {:?}", e);
1668 }
1669 assert!(result.is_ok());
1670 }
1671
1672 #[test]
1673 fn test_system_info_collection() {
1674 let info = InterpolationBenchmarkSuite::<f64>::collect_system_info();
1675 assert!(!info.cpu_info.is_empty());
1676 assert!(!info.os_info.is_empty());
1677 assert!(info.cpu_cores > 0);
1678 }
1679}