1use crate::error::{InterpolateError, InterpolateResult};
50use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
51use scirs2_core::numeric::{Float, FromPrimitive, Zero};
52use scirs2_core::simd_ops::{AutoOptimizer, PlatformCapabilities, SimdUnifiedOps};
53use std::fmt::{Debug, Display};
54
55#[derive(Debug, Clone, Copy)]
57pub enum RBFKernel {
58 Gaussian,
60 Multiquadric,
62 InverseMultiquadric,
64 Linear,
66 Cubic,
68}
69
70#[derive(Debug, Clone)]
72pub struct SimdConfig {
73 pub simd_available: bool,
75 pub f32_width: usize,
77 pub f64_width: usize,
79 pub instruction_set: String,
81}
82
83impl Default for SimdConfig {
84 fn default() -> Self {
85 Self::detect()
86 }
87}
88
89impl SimdConfig {
90 pub fn detect() -> Self {
92 let caps = PlatformCapabilities::detect();
93
94 Self {
95 simd_available: caps.simd_available,
96 f32_width: if caps.avx2_available {
97 8
98 } else if caps.simd_available {
99 4
100 } else {
101 1
102 },
103 f64_width: if caps.avx2_available {
104 4
105 } else if caps.simd_available {
106 2
107 } else {
108 1
109 },
110 instruction_set: if caps.avx512_available {
111 "AVX512".to_string()
112 } else if caps.avx2_available {
113 "AVX2".to_string()
114 } else if caps.neon_available {
115 "NEON".to_string()
116 } else if caps.simd_available {
117 "SIMD".to_string()
118 } else {
119 "Scalar".to_string()
120 },
121 }
122 }
123
124 #[allow(dead_code)]
125 fn fallback() -> Self {
126 Self {
127 simd_available: false,
128 f32_width: 1,
129 f64_width: 1,
130 instruction_set: "Scalar".to_string(),
131 }
132 }
133}
134
135#[allow(dead_code)]
137pub fn simd_rbf_evaluate<F>(
138 queries: &ArrayView2<F>,
139 centers: &ArrayView2<F>,
140 coefficients: &[F],
141 kernel: RBFKernel,
142 epsilon: F,
143) -> InterpolateResult<Array1<F>>
144where
145 F: Float + FromPrimitive + Debug + Display + Zero + Copy + 'static,
146{
147 if queries.ncols() != centers.ncols() {
148 return Err(InterpolateError::invalid_input(
149 "Query and center dimensions must match".to_string(),
150 ));
151 }
152
153 if centers.nrows() != coefficients.len() {
154 return Err(InterpolateError::invalid_input(
155 "Number of centers must match number of coefficients".to_string(),
156 ));
157 }
158
159 let n_queries = queries.nrows();
160 let _n_centers = centers.nrows();
161 #[allow(unused_variables)]
162 let dims = queries.ncols();
163
164 let mut results = Array1::zeros(n_queries);
165
166 if std::any::TypeId::of::<F>() == std::any::TypeId::of::<f64>() {
168 let queries_f64 =
170 unsafe { std::mem::transmute::<&ArrayView2<F>, &ArrayView2<f64>>(queries) };
171 let centers_f64 =
172 unsafe { std::mem::transmute::<&ArrayView2<F>, &ArrayView2<f64>>(centers) };
173 let coefficients_f64: &[f64] = unsafe { std::mem::transmute(coefficients) };
174 let epsilon_f64 = unsafe { *((&epsilon) as *const F as *const f64) };
175
176 let results_f64 = simd_rbf_evaluate_f64(
177 queries_f64,
178 centers_f64,
179 coefficients_f64,
180 kernel,
181 epsilon_f64,
182 )?;
183
184 for (i, &val) in results_f64.iter().enumerate() {
186 results[i] = unsafe { *((&val) as *const f64 as *const F) };
187 }
188 } else {
189 simd_rbf_evaluate_scalar(
191 queries,
192 centers,
193 coefficients,
194 kernel,
195 epsilon,
196 &mut results.view_mut(),
197 )?;
198 }
199
200 Ok(results)
201}
202
203#[allow(dead_code)]
205fn simd_rbf_evaluate_f64(
206 queries: &ArrayView2<f64>,
207 centers: &ArrayView2<f64>,
208 coefficients: &[f64],
209 kernel: RBFKernel,
210 epsilon: f64,
211) -> InterpolateResult<Array1<f64>> {
212 let optimizer = AutoOptimizer::new();
213 let problem_size = queries.nrows() * centers.nrows() * queries.ncols();
214
215 if optimizer.should_use_simd(problem_size) {
216 simd_rbf_evaluate_f64_vectorized(queries, centers, coefficients, kernel, epsilon)
217 } else {
218 let mut results = Array1::zeros(queries.nrows());
219 simd_rbf_evaluate_scalar(
220 &queries.view(),
221 ¢ers.view(),
222 coefficients,
223 kernel,
224 epsilon,
225 &mut results.view_mut(),
226 )?;
227 Ok(results)
228 }
229}
230
231#[allow(dead_code)]
233fn simd_rbf_evaluate_f64_vectorized(
234 queries: &ArrayView2<f64>,
235 centers: &ArrayView2<f64>,
236 coefficients: &[f64],
237 kernel: RBFKernel,
238 epsilon: f64,
239) -> InterpolateResult<Array1<f64>> {
240 let n_queries = queries.nrows();
241 let n_centers = centers.nrows();
242 let _dims = queries.ncols();
243 let mut results = Array1::zeros(n_queries);
244
245 for q in 0..n_queries {
247 let query_row = queries.row(q);
248 let mut sum = 0.0;
249
250 for (c, &coeff) in coefficients.iter().enumerate().take(n_centers) {
251 let center_row = centers.row(c);
252
253 let diff = &query_row - ¢er_row;
255 let diff_arr = diff.to_owned();
256 let dist_sq = f64::simd_dot(&diff_arr.view(), &diff_arr.view());
257
258 let kernel_val = match kernel {
260 RBFKernel::Gaussian => (-dist_sq / (epsilon * epsilon)).exp(),
261 RBFKernel::Multiquadric => (dist_sq + epsilon * epsilon).sqrt(),
262 RBFKernel::InverseMultiquadric => 1.0 / (dist_sq + epsilon * epsilon).sqrt(),
263 RBFKernel::Linear => dist_sq.sqrt(),
264 RBFKernel::Cubic => {
265 let r = dist_sq.sqrt();
266 r * r * r
267 }
268 };
269
270 sum += coeff * kernel_val;
271 }
272
273 results[q] = sum;
274 }
275
276 Ok(results)
277}
278
279#[allow(dead_code)]
282fn simd_rbf_evaluate_scalar<F>(
283 queries: &ArrayView2<F>,
284 centers: &ArrayView2<F>,
285 coefficients: &[F],
286 kernel: RBFKernel,
287 epsilon: F,
288 results: &mut scirs2_core::ndarray::ArrayViewMut1<F>,
289) -> InterpolateResult<()>
290where
291 F: Float + FromPrimitive + Debug + Display + Zero + Copy + 'static,
292{
293 let n_queries = queries.nrows();
294 let n_centers = centers.nrows();
295 let dims = queries.ncols();
296
297 for q in 0..n_queries {
298 let mut sum = F::zero();
299
300 for c in 0..n_centers {
301 let mut dist_sq = F::zero();
303 for d in 0..dims {
304 let diff = queries[[q, d]] - centers[[c, d]];
305 dist_sq = dist_sq + diff * diff;
306 }
307 let dist = dist_sq.sqrt();
308
309 let kernel_val = match kernel {
311 RBFKernel::Gaussian => {
312 let exp_arg = -dist_sq / (epsilon * epsilon);
313 exp_arg.exp()
314 }
315 RBFKernel::Multiquadric => (dist_sq + epsilon * epsilon).sqrt(),
316 RBFKernel::InverseMultiquadric => F::one() / (dist_sq + epsilon * epsilon).sqrt(),
317 RBFKernel::Linear => dist,
318 RBFKernel::Cubic => dist * dist * dist,
319 };
320
321 sum = sum + coefficients[c] * kernel_val;
322 }
323
324 results[q] = sum;
325 }
326
327 Ok(())
328}
329
330#[allow(dead_code)]
332fn evaluate_rbf_kernel_scalar(r: f64, epsilon: f64, kernel: RBFKernel) -> f64 {
333 let r_sq = r * r;
334 let eps_sq = epsilon * epsilon;
335
336 match kernel {
337 RBFKernel::Gaussian => (-r_sq / eps_sq).exp(),
338 RBFKernel::Multiquadric => (r_sq + eps_sq).sqrt(),
339 RBFKernel::InverseMultiquadric => 1.0 / (r_sq + eps_sq).sqrt(),
340 RBFKernel::Linear => r,
341 RBFKernel::Cubic => r * r * r,
342 }
343}
344
345#[allow(dead_code)]
360pub fn simd_distance_matrix<F>(
361 points_a: &ArrayView2<F>,
362 points_b: &ArrayView2<F>,
363) -> InterpolateResult<Array2<F>>
364where
365 F: Float + FromPrimitive + Debug + Display + Zero + Copy + 'static,
366{
367 if points_a.ncols() != points_b.ncols() {
368 return Err(InterpolateError::invalid_input(
369 "Point sets must have the same dimensionality".to_string(),
370 ));
371 }
372
373 if std::any::TypeId::of::<F>() == std::any::TypeId::of::<f64>() {
375 let points_a_f64 = points_a.mapv(|x| x.to_f64().unwrap_or(0.0));
376 let points_b_f64 = points_b.mapv(|x| x.to_f64().unwrap_or(0.0));
377
378 let result_f64 =
379 simd_distance_matrix_f64_vectorized(&points_a_f64.view(), &points_b_f64.view())?;
380 let result = result_f64.mapv(|x| F::from_f64(x).unwrap_or(F::zero()));
381
382 return Ok(result);
383 }
384
385 simd_distance_matrix_scalar(points_a, points_b)
387}
388
389#[allow(dead_code)]
391fn simd_distance_matrix_f64_vectorized(
392 points_a: &ArrayView2<f64>,
393 points_b: &ArrayView2<f64>,
394) -> InterpolateResult<Array2<f64>> {
395 let n_a = points_a.nrows();
396 let n_b = points_b.nrows();
397 let dims = points_a.ncols();
398 let mut distances = Array2::zeros((n_a, n_b));
399
400 let optimizer = AutoOptimizer::new();
401 let problem_size = n_a * n_b * dims;
402
403 if optimizer.should_use_simd(problem_size) {
404 for i in 0..n_a {
406 let a_row = points_a.row(i);
407 for j in 0..n_b {
408 let b_row = points_b.row(j);
409
410 let diff = &a_row - &b_row;
412 let diff_arr = diff.to_owned();
413 let dist_sq = f64::simd_dot(&diff_arr.view(), &diff_arr.view());
414
415 distances[[i, j]] = dist_sq.sqrt();
416 }
417 }
418 } else {
419 return simd_distance_matrix_scalar(points_a, points_b);
421 }
422
423 Ok(distances)
424}
425
426#[allow(dead_code)]
430fn simd_distance_matrix_scalar<F>(
431 points_a: &ArrayView2<F>,
432 points_b: &ArrayView2<F>,
433) -> InterpolateResult<Array2<F>>
434where
435 F: Float + FromPrimitive + Debug + Display + Zero + Copy,
436{
437 let n_a = points_a.nrows();
438 let n_b = points_b.nrows();
439 let dims = points_a.ncols();
440 let mut distances = Array2::zeros((n_a, n_b));
441
442 for i in 0..n_a {
443 for j in 0..n_b {
444 let mut dist_sq = F::zero();
445 for d in 0..dims {
446 let diff = points_a[[i, d]] - points_b[[j, d]];
447 dist_sq = dist_sq + diff * diff;
448 }
449 distances[[i, j]] = dist_sq.sqrt();
450 }
451 }
452
453 Ok(distances)
454}
455
456#[allow(dead_code)]
458pub fn simd_bspline_batch_evaluate<F>(
459 knots: &ArrayView1<F>,
460 coefficients: &ArrayView1<F>,
461 degree: usize,
462 x_values: &ArrayView1<F>,
463) -> InterpolateResult<Array1<F>>
464where
465 F: Float + FromPrimitive + Debug + Display + Zero + Copy + 'static,
466{
467 let mut results = Array1::zeros(x_values.len());
468
469 for (i, &x) in x_values.iter().enumerate() {
472 results[i] = scalar_bspline_evaluate(knots, coefficients, degree, x)?;
473 }
474
475 Ok(results)
476}
477
478#[allow(dead_code)]
483pub fn simd_bspline_basis_functions<F>(
484 knots: &ArrayView1<F>,
485 degree: usize,
486 x_values: &ArrayView1<F>,
487 span_indices: &[usize],
488) -> InterpolateResult<Array2<F>>
489where
490 F: Float + FromPrimitive + Debug + Display + Zero + Copy + 'static,
491{
492 let n_points = x_values.len();
493 let n_basis = degree + 1;
494 let mut basis_values = Array2::zeros((n_points, n_basis));
495
496 scalar_bspline_basis_functions(knots, degree, x_values, span_indices, &mut basis_values)
498}
499
500#[allow(dead_code)]
504fn scalar_bspline_basis_functions<F>(
505 knots: &ArrayView1<F>,
506 degree: usize,
507 x_values: &ArrayView1<F>,
508 span_indices: &[usize],
509 basis_values: &mut Array2<F>,
510) -> InterpolateResult<Array2<F>>
511where
512 F: Float + FromPrimitive + Debug + Display + Zero + Copy + 'static,
513{
514 let n_points = x_values.len();
515 let n_basis = degree + 1;
516
517 for i in 0..n_points {
518 let span = span_indices[i];
519 let x = x_values[i];
520 let basis = compute_basis_functions_scalar(knots, degree, x, span)?;
521
522 for j in 0..n_basis {
523 basis_values[[i, j]] = basis[j];
524 }
525 }
526
527 Ok(basis_values.to_owned())
528}
529
530#[allow(dead_code)]
532fn compute_basis_functions_scalar<F>(
533 knots: &ArrayView1<F>,
534 degree: usize,
535 x: F,
536 span: usize,
537) -> InterpolateResult<Vec<F>>
538where
539 F: Float + FromPrimitive + Debug + Display + Zero + Copy + 'static,
540{
541 let mut basis = vec![F::zero(); degree + 1];
542 basis[0] = F::one();
543
544 for j in 1..=degree {
545 let mut saved = F::zero();
546 for r in 0..j {
547 let temp = basis[r];
548
549 let left_knot = if span + 1 + r >= j && span + 1 + r - j < knots.len() {
550 knots[span + 1 + r - j]
551 } else {
552 F::zero()
553 };
554
555 let right_knot = if span + 1 + r < knots.len() {
556 knots[span + 1 + r]
557 } else {
558 F::zero()
559 };
560
561 let denom = right_knot - left_knot;
562 let alpha = if denom != F::zero() {
563 (x - left_knot) / denom
564 } else {
565 F::zero()
566 };
567
568 basis[r] = saved + (F::one() - alpha) * temp;
569 saved = alpha * temp;
570 }
571 basis[j] = saved;
572 }
573
574 Ok(basis)
575}
576
577#[allow(dead_code)]
579fn scalar_bspline_evaluate<F>(
580 knots: &ArrayView1<F>,
581 coefficients: &ArrayView1<F>,
582 degree: usize,
583 x: F,
584) -> InterpolateResult<F>
585where
586 F: Float + FromPrimitive + Debug + Display + Zero + Copy + 'static,
587{
588 let span = find_knot_span(knots, coefficients.len(), degree, x);
590
591 let basis = compute_basis_functions_scalar(knots, degree, x, span)?;
593
594 let mut result = F::zero();
596 for (i, &basis_val) in basis.iter().enumerate().take(degree + 1) {
597 let coeff_idx = span - degree + i;
598 if coeff_idx < coefficients.len() {
599 result = result + coefficients[coeff_idx] * basis_val;
600 }
601 }
602
603 Ok(result)
604}
605
606#[allow(dead_code)]
608fn find_knot_span<F>(knots: &ArrayView1<F>, n: usize, degree: usize, x: F) -> usize
609where
610 F: Float + FromPrimitive + PartialOrd,
611{
612 if x >= knots[n] {
613 return n - 1;
614 }
615 if x <= knots[degree] {
616 return degree;
617 }
618
619 let mut low = degree;
621 let mut high = n;
622 let mut mid = (low + high) / 2;
623
624 while x < knots[mid] || x >= knots[mid + 1] {
625 if x < knots[mid] {
626 high = mid;
627 } else {
628 low = mid;
629 }
630 mid = (low + high) / 2;
631 }
632
633 mid
634}
635
636#[allow(dead_code)]
640pub fn get_simd_config() -> SimdConfig {
641 SimdConfig::detect()
642}
643
644#[allow(dead_code)]
646pub fn is_simd_available() -> bool {
647 SimdConfig::detect().simd_available
648}
649
650#[cfg(test)]
651mod tests {
652 use super::*;
653 use approx::assert_relative_eq;
654 use scirs2_core::ndarray::{array, Axis};
655
656 #[test]
657 fn test_simd_config_detection() {
658 let config = SimdConfig::detect();
659 println!("SIMD Config: {config:?}");
660
661 assert!(config.f32_width >= 1);
663 assert!(config.f64_width >= 1);
664 assert!(!config.instruction_set.is_empty());
665 }
666
667 #[test]
668 fn test_simd_rbf_evaluate() {
669 let queries = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
670 let centers = array![[0.0, 0.0], [1.0, 1.0], [0.5, 0.5]];
671 let coefficients = vec![1.0, 1.0, 1.0];
672
673 let results = simd_rbf_evaluate(
674 &queries.view(),
675 ¢ers.view(),
676 &coefficients,
677 RBFKernel::Gaussian,
678 1.0,
679 )
680 .expect("Operation failed");
681
682 assert_eq!(results.len(), 3);
683
684 for &result in results.iter() {
686 assert!(result.is_finite());
687 assert!(result >= 0.0); }
689 }
690
691 #[test]
692 fn test_simd_distance_matrix() {
693 let points_a = array![[0.0, 0.0], [1.0, 0.0]];
694 let points_b = array![[0.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
695
696 let distances =
697 simd_distance_matrix(&points_a.view(), &points_b.view()).expect("Operation failed");
698
699 assert_eq!(distances.shape(), &[2, 3]);
700
701 assert_relative_eq!(distances[[0, 0]], 0.0, epsilon = 1e-10); assert_relative_eq!(distances[[0, 1]], 1.0, epsilon = 1e-10); assert_relative_eq!(distances[[1, 0]], 1.0, epsilon = 1e-10); }
706
707 #[test]
708 fn test_rbf_kernel_consistency() {
709 let queries = array![[0.25, 0.75]];
711 let centers = array![[0.0, 0.0], [1.0, 1.0]];
712 let coefficients = vec![0.5, 1.5];
713 let epsilon = 1.0;
714
715 let simd_result = simd_rbf_evaluate(
716 &queries.view(),
717 ¢ers.view(),
718 &coefficients,
719 RBFKernel::Gaussian,
720 epsilon,
721 )
722 .expect("Operation failed");
723
724 let mut scalar_result = 0.0;
726 for (i, center) in centers.axis_iter(Axis(0)).enumerate() {
727 let mut dist_sq = 0.0;
728 for (q_val, c_val) in queries.row(0).iter().zip(center.iter()) {
729 let diff = q_val - c_val;
730 dist_sq += diff * diff;
731 }
732 let kernel_val = (-dist_sq / (epsilon * epsilon)).exp();
733 scalar_result += coefficients[i] * kernel_val;
734 }
735
736 assert_relative_eq!(simd_result[0], scalar_result, epsilon = 1e-10);
737 }
738
739 #[test]
740 fn test_different_rbf_kernels() {
741 let queries = array![[0.5, 0.5]];
742 let centers = array![[0.0, 0.0], [1.0, 1.0]];
743 let coefficients = vec![1.0, 1.0];
744 let epsilon = 1.0;
745
746 let kernels = [
747 RBFKernel::Gaussian,
748 RBFKernel::Multiquadric,
749 RBFKernel::InverseMultiquadric,
750 RBFKernel::Linear,
751 RBFKernel::Cubic,
752 ];
753
754 for kernel in kernels {
755 let result = simd_rbf_evaluate(
756 &queries.view(),
757 ¢ers.view(),
758 &coefficients,
759 kernel,
760 epsilon,
761 )
762 .expect("Operation failed");
763
764 assert_eq!(result.len(), 1);
765 assert!(result[0].is_finite());
766 }
767 }
768
769 #[test]
770 fn test_simd_availability() {
771 let available = is_simd_available();
772 println!("SIMD available: {available}");
773
774 }
777
778 #[test]
779 fn test_bspline_batch_evaluate() {
780 let knots = array![0.0, 1.0, 2.0, 3.0];
781 let coefficients = array![1.0, 2.0];
782 let x_values = array![0.5, 1.5, 2.5];
783
784 let results =
785 simd_bspline_batch_evaluate(&knots.view(), &coefficients.view(), 1, &x_values.view())
786 .expect("Operation failed");
787
788 assert_eq!(results.len(), 3);
789 for &result in results.iter() {
791 assert!(result.is_finite());
792 }
793 }
794}