1use crate::error::{MetricsError, Result};
8use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
9use scirs2_core::numeric::{Float, One, Zero};
10use scirs2_core::parallel_ops::*;
11use scirs2_core::simd_ops::{AutoOptimizer, PlatformCapabilities, SimdUnifiedOps};
12use statrs::statistics::Statistics;
13
14#[derive(Debug, Clone)]
16pub struct HardwareAccelConfig {
17 pub enable_simd: bool,
19 pub enable_gpu: bool,
21 pub min_data_size: usize,
23 pub vector_width: VectorWidth,
25 pub gpu_memory_threshold: usize,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq)]
31pub enum VectorWidth {
32 V128,
34 V256,
36 V512,
38 Auto,
40}
41
42impl Default for HardwareAccelConfig {
43 fn default() -> Self {
44 Self {
45 enable_simd: true,
46 enable_gpu: false, min_data_size: 1000,
48 vector_width: VectorWidth::Auto,
49 gpu_memory_threshold: 1024 * 1024, }
51 }
52}
53
54impl HardwareAccelConfig {
55 pub fn new() -> Self {
57 Self::default()
58 }
59
60 pub fn with_simd_enabled(mut self, enabled: bool) -> Self {
62 self.enable_simd = enabled;
63 self
64 }
65
66 pub fn with_gpu_enabled(mut self, enabled: bool) -> Self {
68 self.enable_gpu = enabled;
69 self
70 }
71
72 pub fn with_min_data_size(mut self, size: usize) -> Self {
74 self.min_data_size = size;
75 self
76 }
77
78 pub fn with_vector_width(mut self, width: VectorWidth) -> Self {
80 self.vector_width = width;
81 self
82 }
83
84 pub fn with_gpu_memory_threshold(mut self, threshold: usize) -> Self {
86 self.gpu_memory_threshold = threshold;
87 self
88 }
89}
90
91#[derive(Debug)]
93pub struct HardwareCapabilities {
94 pub has_sse: bool,
95 pub has_sse2: bool,
96 pub has_sse3: bool,
97 pub has_ssse3: bool,
98 pub has_sse41: bool,
99 pub has_sse42: bool,
100 pub has_avx: bool,
101 pub has_avx2: bool,
102 pub has_avx512f: bool,
103 pub has_fma: bool,
104 pub has_gpu: bool,
105 pub gpu_memory: Option<usize>,
106 pub simd_available: bool,
108 pub avx2_available: bool,
109 pub avx512_available: bool,
110 pub gpu_available: bool,
111}
112
113impl HardwareCapabilities {
114 pub fn detect() -> Self {
116 let core_caps = PlatformCapabilities::detect();
117
118 Self {
119 has_sse: true, has_sse2: core_caps.simd_available,
121 has_sse3: core_caps.simd_available,
122 has_ssse3: core_caps.simd_available,
123 has_sse41: core_caps.simd_available,
124 has_sse42: core_caps.simd_available,
125 has_avx: core_caps.avx2_available,
126 has_avx2: core_caps.avx2_available,
127 has_avx512f: core_caps.avx512_available,
128 has_fma: core_caps.simd_available, has_gpu: core_caps.gpu_available,
130 gpu_memory: None, simd_available: core_caps.simd_available,
132 avx2_available: core_caps.avx2_available,
133 avx512_available: core_caps.avx512_available,
134 gpu_available: core_caps.gpu_available,
135 }
136 }
137
138 pub fn optimal_vector_width(&self) -> VectorWidth {
140 if self.avx512_available {
141 VectorWidth::V512
142 } else if self.avx2_available {
143 VectorWidth::V256
144 } else {
145 VectorWidth::V128 }
147 }
148
149 pub fn simd_available(&self) -> bool {
151 self.simd_available
152 }
153}
154
155pub struct SimdDistanceMetrics {
157 config: HardwareAccelConfig,
158 capabilities: HardwareCapabilities,
159}
160
161impl SimdDistanceMetrics {
162 pub fn new() -> Self {
164 Self {
165 config: HardwareAccelConfig::default(),
166 capabilities: HardwareCapabilities::detect(),
167 }
168 }
169
170 pub fn with_config(config: HardwareAccelConfig) -> Self {
172 Self {
173 config,
174 capabilities: HardwareCapabilities::detect(),
175 }
176 }
177
178 pub fn euclidean_distance_simd<F>(&self, a: &Array1<F>, b: &Array1<F>) -> Result<F>
180 where
181 F: Float + SimdUnifiedOps + std::fmt::Debug + 'static,
182 {
183 if a.len() != b.len() {
184 return Err(MetricsError::InvalidInput(
185 "Arrays must have the same length".to_string(),
186 ));
187 }
188
189 if !self.config.enable_simd
190 || !self.capabilities.simd_available()
191 || a.len() < self.config.min_data_size
192 {
193 return self.euclidean_distance_standard(a, b);
195 }
196
197 let diff = F::simd_sub(&a.view(), &b.view());
199 let squared_diff = F::simd_mul(&diff.view(), &diff.view());
200 let distance = F::simd_sum(&squared_diff.view()).sqrt();
201 Ok(distance)
202 }
203
204 pub fn manhattan_distance_simd<F>(&self, a: &Array1<F>, b: &Array1<F>) -> Result<F>
206 where
207 F: Float + SimdUnifiedOps + std::fmt::Debug + 'static + std::iter::Sum,
208 {
209 if a.len() != b.len() {
210 return Err(MetricsError::InvalidInput(
211 "Arrays must have the same length".to_string(),
212 ));
213 }
214
215 if !self.config.enable_simd
216 || !self.capabilities.simd_available()
217 || a.len() < self.config.min_data_size
218 {
219 return self.manhattan_distance_standard(a, b);
220 }
221
222 let diff = F::simd_sub(&a.view(), &b.view());
224 let abs_diff = F::simd_abs(&diff.view());
225 let distance = F::simd_sum(&abs_diff.view());
226 Ok(distance)
227 }
228
229 pub fn cosine_distance_simd<F>(&self, a: &Array1<F>, b: &Array1<F>) -> Result<F>
231 where
232 F: Float + SimdUnifiedOps + std::fmt::Debug + PartialEq + 'static,
233 {
234 if a.len() != b.len() {
235 return Err(MetricsError::InvalidInput(
236 "Arrays must have the same length".to_string(),
237 ));
238 }
239
240 if !self.config.enable_simd
241 || !self.capabilities.simd_available()
242 || a.len() < self.config.min_data_size
243 {
244 return self.cosine_distance_standard(a, b);
245 }
246
247 let dot_product = self.dot_product_simd(a, b)?;
249 let norm_a = self.euclidean_norm_simd(a)?;
250 let norm_b = self.euclidean_norm_simd(b)?;
251
252 if norm_a == F::zero() || norm_b == F::zero() {
253 return Ok(F::one()); }
255
256 let cosine_similarity = dot_product / (norm_a * norm_b);
257 Ok(F::one() - cosine_similarity)
258 }
259
260 pub fn dot_product_simd<F>(&self, a: &Array1<F>, b: &Array1<F>) -> Result<F>
262 where
263 F: Float + SimdUnifiedOps + std::fmt::Debug + 'static,
264 {
265 if a.len() != b.len() {
266 return Err(MetricsError::InvalidInput(
267 "Arrays must have the same length".to_string(),
268 ));
269 }
270
271 if !self.config.enable_simd
272 || !self.capabilities.simd_available()
273 || a.len() < self.config.min_data_size
274 {
275 return Ok(a.dot(b));
276 }
277
278 let dot_product = F::simd_dot(&a.view(), &b.view());
280 Ok(dot_product)
281 }
282
283 pub fn euclidean_norm_simd<F>(&self, a: &Array1<F>) -> Result<F>
285 where
286 F: Float + SimdUnifiedOps + std::fmt::Debug + 'static,
287 {
288 if !self.config.enable_simd
289 || !self.capabilities.simd_available()
290 || a.len() < self.config.min_data_size
291 {
292 return Ok(a.dot(a).sqrt());
293 }
294
295 let norm_squared = self.dot_product_simd(a, a)?;
296 Ok(norm_squared.sqrt())
297 }
298
299 fn euclidean_distance_standard<F>(&self, a: &Array1<F>, b: &Array1<F>) -> Result<F>
302 where
303 F: Float + std::fmt::Debug + 'static,
304 {
305 let diff = a - b;
306 Ok(diff.dot(&diff).sqrt())
307 }
308
309 fn manhattan_distance_standard<F>(&self, a: &Array1<F>, b: &Array1<F>) -> Result<F>
310 where
311 F: Float + std::fmt::Debug + std::iter::Sum,
312 {
313 let sum: F = a.iter().zip(b.iter()).map(|(x, y)| (*x - *y).abs()).sum();
314 Ok(sum)
315 }
316
317 fn cosine_distance_standard<F>(&self, a: &Array1<F>, b: &Array1<F>) -> Result<F>
318 where
319 F: Float + std::fmt::Debug + Zero + One + 'static,
320 {
321 let dot_product = a.dot(b);
322 let norm_a = a.dot(a).sqrt();
323 let norm_b = b.dot(b).sqrt();
324
325 if norm_a == F::zero() || norm_b == F::zero() {
326 return Ok(F::one());
327 }
328
329 let cosine_similarity = dot_product / (norm_a * norm_b);
330 Ok(F::one() - cosine_similarity)
331 }
332}
333
334impl Default for SimdDistanceMetrics {
335 fn default() -> Self {
336 Self::new()
337 }
338}
339
340pub struct SimdStatistics {
342 config: HardwareAccelConfig,
343 capabilities: HardwareCapabilities,
344}
345
346impl SimdStatistics {
347 pub fn new() -> Self {
349 Self {
350 config: HardwareAccelConfig::default(),
351 capabilities: HardwareCapabilities::detect(),
352 }
353 }
354
355 pub fn with_config(config: HardwareAccelConfig) -> Self {
357 Self {
358 config,
359 capabilities: HardwareCapabilities::detect(),
360 }
361 }
362
363 pub fn mean_simd(&self, data: &Array1<f64>) -> Result<f64> {
365 if !self.config.enable_simd
366 || !self.capabilities.simd_available()
367 || data.len() < self.config.min_data_size
368 {
369 return Ok(data.mean().unwrap_or(0.0));
370 }
371
372 let sum = self.sum_simd(data)?;
373 Ok(sum / data.len() as f64)
374 }
375
376 pub fn variance_simd(&self, data: &Array1<f64>) -> Result<f64> {
378 if data.len() < 2 {
379 return Ok(0.0);
380 }
381
382 if !self.config.enable_simd
383 || !self.capabilities.simd_available()
384 || data.len() < self.config.min_data_size
385 {
386 let mean = data.mean().unwrap_or(0.0);
387 let var =
388 data.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (data.len() - 1) as f64;
389 return Ok(var);
390 }
391
392 let mean = self.mean_simd(data)?;
393 let sum_squared_diff = self.sum_squared_differences_simd(data, mean)?;
394 Ok(sum_squared_diff / (data.len() - 1) as f64)
395 }
396
397 pub fn std_simd(&self, data: &Array1<f64>) -> Result<f64> {
399 let variance = self.variance_simd(data)?;
400 Ok(variance.sqrt())
401 }
402
403 pub fn sum_simd(&self, data: &Array1<f64>) -> Result<f64> {
405 if !self.config.enable_simd
406 || !self.capabilities.simd_available()
407 || data.len() < self.config.min_data_size
408 {
409 return Ok(data.sum());
410 }
411
412 let sum = f64::simd_sum(&data.view());
414 Ok(sum)
415 }
416
417 pub fn sum_squared_differences_simd(&self, data: &Array1<f64>, mean: f64) -> Result<f64> {
419 if !self.config.enable_simd
420 || !self.capabilities.simd_available()
421 || data.len() < self.config.min_data_size
422 {
423 let sum = data.iter().map(|x| (x - mean).powi(2)).sum::<f64>();
424 return Ok(sum);
425 }
426
427 let mean_array = Array1::from_elem(data.len(), mean);
429 let diff = f64::simd_sub(&data.view(), &mean_array.view());
430 let squared = f64::simd_mul(&diff.view(), &diff.view());
431 let sum = f64::simd_sum(&squared.view());
432 Ok(sum)
433 }
434}
435
436impl Default for SimdStatistics {
437 fn default() -> Self {
438 Self::new()
439 }
440}
441
442pub struct HardwareAcceleratedMatrix {
444 config: HardwareAccelConfig,
445 capabilities: HardwareCapabilities,
446}
447
448impl HardwareAcceleratedMatrix {
449 pub fn new() -> Self {
451 Self {
452 config: HardwareAccelConfig::default(),
453 capabilities: HardwareCapabilities::detect(),
454 }
455 }
456
457 pub fn with_config(config: HardwareAccelConfig) -> Self {
459 Self {
460 config,
461 capabilities: HardwareCapabilities::detect(),
462 }
463 }
464
465 pub fn matvec_accelerated(
467 &self,
468 matrix: &Array2<f64>,
469 vector: &Array1<f64>,
470 ) -> Result<Array1<f64>> {
471 let (rows, cols) = matrix.dim();
472 if cols != vector.len() {
473 return Err(MetricsError::InvalidInput(
474 "Matrix columns must match vector length".to_string(),
475 ));
476 }
477
478 if !self.config.enable_simd
479 || !self.capabilities.simd_available()
480 || rows * cols < self.config.min_data_size
481 {
482 return Ok(matrix.dot(vector));
484 }
485
486 let mut result = Array1::zeros(rows);
487 let simd_distances = SimdDistanceMetrics::with_config(self.config.clone());
488
489 for (i, row) in matrix.rows().into_iter().enumerate() {
491 result[i] = simd_distances.dot_product_simd(&row.to_owned(), vector)?;
492 }
493
494 Ok(result)
495 }
496
497 pub fn pairwise_distances_accelerated(
499 &self,
500 data: &Array2<f64>,
501 metric: &str,
502 ) -> Result<Array2<f64>> {
503 let n_samples = data.nrows();
504 let mut distances = Array2::zeros((n_samples, n_samples));
505
506 if !self.config.enable_simd || !self.capabilities.simd_available() {
507 return self.pairwise_distances_standard(data, metric);
509 }
510
511 let simd_distances = SimdDistanceMetrics::with_config(self.config.clone());
512
513 for i in 0..n_samples {
514 for j in (i + 1)..n_samples {
515 let row_i = data.row(i).to_owned();
516 let row_j = data.row(j).to_owned();
517
518 let distance = match metric {
519 "euclidean" => simd_distances.euclidean_distance_simd(&row_i, &row_j)?,
520 "manhattan" => simd_distances.manhattan_distance_simd(&row_i, &row_j)?,
521 "cosine" => simd_distances.cosine_distance_simd(&row_i, &row_j)?,
522 _ => {
523 return Err(MetricsError::InvalidInput(format!(
524 "Unsupported metric: {}",
525 metric
526 )))
527 }
528 };
529
530 distances[[i, j]] = distance;
531 distances[[j, i]] = distance; }
533 }
534
535 Ok(distances)
536 }
537
538 pub fn correlation_matrix_accelerated(&self, data: &Array2<f64>) -> Result<Array2<f64>> {
540 let (_n_samples, n_features) = data.dim();
541 let mut correlation_matrix = Array2::zeros((n_features, n_features));
542
543 if !self.config.enable_simd || !self.capabilities.simd_available() {
544 return self.correlation_matrix_standard(data);
545 }
546
547 let simd_stats = SimdStatistics::with_config(self.config.clone());
548
549 let mut means = Array1::zeros(n_features);
551 for j in 0..n_features {
552 let column = data.column(j).to_owned();
553 means[j] = simd_stats.mean_simd(&column)?;
554 }
555
556 for i in 0..n_features {
558 for j in (i + 1)..n_features {
559 let col_i = data.column(i).to_owned();
560 let col_j = data.column(j).to_owned();
561
562 let correlation =
563 self.compute_correlation_simd(&col_i, &col_j, means[i], means[j], &simd_stats)?;
564
565 correlation_matrix[[i, j]] = correlation;
566 correlation_matrix[[j, i]] = correlation;
567 }
568 correlation_matrix[[i, i]] = 1.0;
570 }
571
572 Ok(correlation_matrix)
573 }
574
575 fn pairwise_distances_standard(&self, data: &Array2<f64>, metric: &str) -> Result<Array2<f64>> {
578 let n_samples = data.nrows();
579 let mut distances = Array2::zeros((n_samples, n_samples));
580
581 for i in 0..n_samples {
582 for j in (i + 1)..n_samples {
583 let row_i = data.row(i);
584 let row_j = data.row(j);
585
586 let distance = match metric {
587 "euclidean" => {
588 let diff = &row_i.to_owned() - &row_j.to_owned();
589 diff.dot(&diff).sqrt()
590 }
591 "manhattan" => row_i
592 .iter()
593 .zip(row_j.iter())
594 .map(|(a, b)| (a - b).abs())
595 .sum(),
596 "cosine" => {
597 let dot_product = row_i.dot(&row_j);
598 let norm_i = row_i.dot(&row_i).sqrt();
599 let norm_j = row_j.dot(&row_j).sqrt();
600 if norm_i == 0.0 || norm_j == 0.0 {
601 1.0
602 } else {
603 1.0 - dot_product / (norm_i * norm_j)
604 }
605 }
606 _ => {
607 return Err(MetricsError::InvalidInput(format!(
608 "Unsupported metric: {}",
609 metric
610 )))
611 }
612 };
613
614 distances[[i, j]] = distance;
615 distances[[j, i]] = distance;
616 }
617 }
618
619 Ok(distances)
620 }
621
622 fn correlation_matrix_standard(&self, data: &Array2<f64>) -> Result<Array2<f64>> {
623 let (n_samples, n_features) = data.dim();
624 let mut correlation_matrix = Array2::zeros((n_features, n_features));
625
626 let means: Array1<f64> = data.mean_axis(Axis(0)).unwrap();
628
629 for i in 0..n_features {
630 for j in (i + 1)..n_features {
631 let col_i = data.column(i);
632 let col_j = data.column(j);
633
634 let mean_i = means[i];
635 let mean_j = means[j];
636
637 let mut cov = 0.0;
639 let mut var_i = 0.0;
640 let mut var_j = 0.0;
641
642 for k in 0..n_samples {
643 let diff_i = col_i[k] - mean_i;
644 let diff_j = col_j[k] - mean_j;
645 cov += diff_i * diff_j;
646 var_i += diff_i * diff_i;
647 var_j += diff_j * diff_j;
648 }
649
650 let correlation = if var_i > 1e-10 && var_j > 1e-10 {
651 cov / (var_i * var_j).sqrt()
652 } else {
653 0.0
654 };
655
656 correlation_matrix[[i, j]] = correlation;
657 correlation_matrix[[j, i]] = correlation;
658 }
659 correlation_matrix[[i, i]] = 1.0;
660 }
661
662 Ok(correlation_matrix)
663 }
664
665 fn compute_correlation_simd(
666 &self,
667 col_i: &Array1<f64>,
668 col_j: &Array1<f64>,
669 mean_i: f64,
670 mean_j: f64,
671 simd_stats: &SimdStatistics,
672 ) -> Result<f64> {
673 let centered_i = col_i.mapv(|x| x - mean_i);
675 let centered_j = col_j.mapv(|x| x - mean_j);
676
677 let simd_distances = SimdDistanceMetrics::with_config(self.config.clone());
679 let covariance = simd_distances.dot_product_simd(¢ered_i, ¢ered_j)?;
680
681 let var_i = simd_stats.sum_squared_differences_simd(col_i, mean_i)?;
683 let var_j = simd_stats.sum_squared_differences_simd(col_j, mean_j)?;
684
685 if var_i > 1e-10 && var_j > 1e-10 {
686 Ok(covariance / (var_i * var_j).sqrt())
687 } else {
688 Ok(0.0)
689 }
690 }
691}
692
693impl Default for HardwareAcceleratedMatrix {
694 fn default() -> Self {
695 Self::new()
696 }
697}
698
699#[cfg(test)]
700mod tests {
701 use super::*;
702 use scirs2_core::ndarray::Array;
703
704 #[test]
705 fn test_hardware_capabilities() {
706 let capabilities = HardwareCapabilities::detect();
707 println!("Hardware capabilities: {:?}", capabilities);
708
709 assert!(
711 capabilities.optimal_vector_width() != VectorWidth::V512 || capabilities.has_avx512f
712 );
713 }
714
715 #[test]
716 fn test_simd_distance_metrics() {
717 let metrics = SimdDistanceMetrics::new();
718 let a = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
719 let b = Array1::from_vec(vec![2.0, 3.0, 4.0, 5.0, 6.0]);
720
721 let euclidean = metrics.euclidean_distance_simd(&a, &b).unwrap();
723 let expected_euclidean = (5.0_f64).sqrt(); assert!((euclidean - expected_euclidean).abs() < 1e-10);
725
726 let manhattan = metrics.manhattan_distance_simd(&a, &b).unwrap();
728 assert!((manhattan - 5.0).abs() < 1e-10);
729
730 let dot = metrics.dot_product_simd(&a, &b).unwrap();
732 let expected_dot = 70.0; assert!((dot - expected_dot).abs() < 1e-10);
734 }
735
736 #[test]
737 fn test_simd_statistics() {
738 let stats = SimdStatistics::new();
739 let data = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
740
741 let mean = stats.mean_simd(&data).unwrap();
743 assert!((mean - 3.0).abs() < 1e-10);
744
745 let sum = stats.sum_simd(&data).unwrap();
747 assert!((sum - 15.0).abs() < 1e-10);
748
749 let variance = stats.variance_simd(&data).unwrap();
751 let expected_variance = 2.5; assert!((variance - expected_variance).abs() < 1e-10);
753 }
754
755 #[test]
756 fn test_hardware_accelerated_matrix() {
757 let matrix_ops = HardwareAcceleratedMatrix::new();
758
759 let matrix =
761 Array2::from_shape_vec((3, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
762 .unwrap();
763 let vector = Array1::from_vec(vec![1.0, 2.0, 3.0]);
764
765 let result = matrix_ops.matvec_accelerated(&matrix, &vector).unwrap();
766 let expected = Array1::from_vec(vec![14.0, 32.0, 50.0]); for (actual, expected) in result.iter().zip(expected.iter()) {
769 assert!((actual - expected).abs() < 1e-10);
770 }
771
772 let data = Array2::from_shape_vec((3, 2), vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0]).unwrap();
774
775 let distances = matrix_ops
776 .pairwise_distances_accelerated(&data, "euclidean")
777 .unwrap();
778
779 assert!((distances[[0, 1]] - distances[[1, 0]]).abs() < 1e-10);
781 assert!((distances[[0, 2]] - distances[[2, 0]]).abs() < 1e-10);
782 assert!((distances[[1, 2]] - distances[[2, 1]]).abs() < 1e-10);
783
784 assert!(distances[[0, 0]].abs() < 1e-10);
786 assert!(distances[[1, 1]].abs() < 1e-10);
787 assert!(distances[[2, 2]].abs() < 1e-10);
788 }
789
790 #[test]
791 fn test_config_builder() {
792 let config = HardwareAccelConfig::new()
793 .with_simd_enabled(true)
794 .with_gpu_enabled(false)
795 .with_min_data_size(500)
796 .with_vector_width(VectorWidth::V256)
797 .with_gpu_memory_threshold(2048);
798
799 assert!(config.enable_simd);
800 assert!(!config.enable_gpu);
801 assert_eq!(config.min_data_size, 500);
802 assert_eq!(config.gpu_memory_threshold, 2048);
803 }
804}