Skip to main content

scirs2_interpolate/
simd_optimized.rs

1//! SIMD-optimized interpolation functions
2//!
3//! This module provides SIMD (Single Instruction, Multiple Data) optimized versions
4//! of computationally intensive interpolation operations. SIMD instructions allow
5//! processing multiple data points simultaneously, leading to significant performance
6//! improvements for basis function evaluation, distance calculations, and other
7//! vectorizable operations.
8//!
9//! The optimizations target:
10//! - **Basis function evaluation**: Vectorized B-spline, RBF, and polynomial basis computations
11//! - **Distance calculations**: Fast Euclidean and other distance metrics for multiple points
12//! - **Matrix operations**: Optimized linear algebra for interpolation systems
13//! - **Batch processing**: Efficient evaluation at multiple query points
14//! - **Data layout optimization**: Memory-friendly data structures for SIMD
15//!
16//! # SIMD Support
17//!
18//! This module uses conditional compilation to provide SIMD implementations when
19//! available, with automatic fallback to scalar implementations on unsupported
20//! architectures.
21//!
22//! Supported instruction sets:
23//! - **x86/x86_64**: SSE2, SSE4.1, AVX, AVX2, AVX-512
24//! - **ARM**: NEON (AArch64)
25//! - **Portable fallback**: Pure Rust implementation for all other targets
26//!
27//! # Examples
28//!
29//! ```rust
30//! use scirs2_core::ndarray::Array2;
31//! use scirs2_interpolate::simd_optimized::{
32//!     simd_rbf_evaluate, simd_distance_matrix, RBFKernel
33//! };
34//!
35//! // Evaluate RBF at multiple points simultaneously
36//! let centers = Array2::from_shape_vec((100, 3), vec![0.0; 300]).expect("Operation failed");
37//! let queries = Array2::from_shape_vec((50, 3), vec![0.5; 150]).expect("Operation failed");
38//! let coefficients = vec![1.0; 100];
39//!
40//! let results = simd_rbf_evaluate(
41//!     &queries.view(),
42//!     &centers.view(),
43//!     &coefficients,
44//!     RBFKernel::Gaussian,
45//!     1.0
46//! ).expect("Operation failed");
47//! ```
48
49use 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/// RBF kernel types for SIMD evaluation
56#[derive(Debug, Clone, Copy)]
57pub enum RBFKernel {
58    /// Gaussian: exp(-r²/ε²)
59    Gaussian,
60    /// Multiquadric: sqrt(r² + ε²)
61    Multiquadric,
62    /// Inverse multiquadric: 1/sqrt(r² + ε²)
63    InverseMultiquadric,
64    /// Linear: r
65    Linear,
66    /// Cubic: r³
67    Cubic,
68}
69
70/// SIMD configuration and capabilities
71#[derive(Debug, Clone)]
72pub struct SimdConfig {
73    /// Whether SIMD is available on this platform
74    pub simd_available: bool,
75    /// Vector width for f32 operations
76    pub f32_width: usize,
77    /// Vector width for f64 operations
78    pub f64_width: usize,
79    /// Instruction set being used
80    pub instruction_set: String,
81}
82
83impl Default for SimdConfig {
84    fn default() -> Self {
85        Self::detect()
86    }
87}
88
89impl SimdConfig {
90    /// Detect SIMD capabilities on the current platform using core abstractions
91    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/// SIMD-optimized RBF evaluation
136#[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    // For f64 specifically, we can use SIMD if available
167    if std::any::TypeId::of::<F>() == std::any::TypeId::of::<f64>() {
168        // Unsafe transmute for SIMD operations (f64 case)
169        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        // Convert back to F
185        for (i, &val) in results_f64.iter().enumerate() {
186            results[i] = unsafe { *((&val) as *const f64 as *const F) };
187        }
188    } else {
189        // Fallback to scalar implementation for other types
190        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/// SIMD-optimized RBF evaluation for f64
204#[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            &centers.view(),
222            coefficients,
223            kernel,
224            epsilon,
225            &mut results.view_mut(),
226        )?;
227        Ok(results)
228    }
229}
230
231/// Vectorized f64 RBF evaluation using SIMD
232#[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    // Use core SIMD operations for optimized computation
246    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            // Compute squared distance using SIMD operations
254            let diff = &query_row - &center_row;
255            let diff_arr = diff.to_owned();
256            let dist_sq = f64::simd_dot(&diff_arr.view(), &diff_arr.view());
257
258            // Apply kernel
259            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/// Fallback implementation for all architectures
280/// Scalar fallback implementation
281#[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            // Compute distance
302            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            // Apply kernel
310            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/// Evaluate RBF kernel (scalar version)
331#[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/// SIMD-optimized distance matrix computation
346///
347/// Computes pairwise Euclidean distances between two sets of points using
348/// SIMD vectorized operations when available.
349///
350/// # Arguments
351///
352/// * `points_a` - First set of points with shape (n_a, dims)
353/// * `points_b` - Second set of points with shape (n_b, dims)
354///
355/// # Returns
356///
357/// Distance matrix with shape (n_a, n_b) where entry `(i,j)` contains the
358/// Euclidean distance between `points_a[i]` and `points_b[j]`
359#[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    // For f64, use optimized SIMD implementation when available
374    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    // Fallback to scalar implementation for other types
386    simd_distance_matrix_scalar(points_a, points_b)
387}
388
389/// SIMD-optimized distance matrix computation for f64 values
390#[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        // Use SIMD operations for distance computation
405        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                // Compute squared distance using SIMD operations
411                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        // Fallback to scalar implementation
420        return simd_distance_matrix_scalar(points_a, points_b);
421    }
422
423    Ok(distances)
424}
425
426// Direct SIMD intrinsics implementations removed - all SIMD operations now go through core abstractions
427
428/// Scalar fallback implementation for distance matrix computation
429#[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/// SIMD-optimized batch evaluation for B-splines
457#[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 now, delegate to scalar implementation
470    // In a full SIMD implementation, this would vectorize the de Boor algorithm
471    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/// Vectorized B-spline basis function evaluation using SIMD
479///
480/// This function computes B-spline basis functions for multiple evaluation points
481/// simultaneously using SIMD instructions when available.
482#[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    // Use scalar implementation (AVX2 implementation removed)
497    scalar_bspline_basis_functions(knots, degree, x_values, span_indices, &mut basis_values)
498}
499
500// B-spline basis function AVX2 implementation removed - using scalar implementation only
501
502/// Scalar implementation of B-spline basis function computation
503#[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/// Compute basis functions for a single point using de Boor's algorithm
531#[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/// Improved scalar B-spline evaluation using cached workspace
578#[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    // Find the knot span
589    let span = find_knot_span(knots, coefficients.len(), degree, x);
590
591    // Compute basis functions
592    let basis = compute_basis_functions_scalar(knots, degree, x, span)?;
593
594    // Evaluate the spline
595    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/// Find the knot span for a given parameter value
607#[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    // Binary search
620    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// SIMD helper functions removed - all operations now use core abstractions
637
638/// Get SIMD configuration information
639#[allow(dead_code)]
640pub fn get_simd_config() -> SimdConfig {
641    SimdConfig::detect()
642}
643
644/// Check if SIMD is available on this platform
645#[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        // Basic validation
662        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            &centers.view(),
676            &coefficients,
677            RBFKernel::Gaussian,
678            1.0,
679        )
680        .expect("Operation failed");
681
682        assert_eq!(results.len(), 3);
683
684        // Results should be finite and reasonable
685        for &result in results.iter() {
686            assert!(result.is_finite());
687            assert!(result >= 0.0); // Gaussian RBF is always positive
688        }
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        // Check some known distances
702        assert_relative_eq!(distances[[0, 0]], 0.0, epsilon = 1e-10); // Same point
703        assert_relative_eq!(distances[[0, 1]], 1.0, epsilon = 1e-10); // Unit distance
704        assert_relative_eq!(distances[[1, 0]], 1.0, epsilon = 1e-10); // Unit distance
705    }
706
707    #[test]
708    fn test_rbf_kernel_consistency() {
709        // Test that SIMD and scalar implementations give same results
710        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            &centers.view(),
718            &coefficients,
719            RBFKernel::Gaussian,
720            epsilon,
721        )
722        .expect("Operation failed");
723
724        // Compute scalar result manually
725        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                &centers.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        // Test should always pass regardless of SIMD availability
775        // (just checking that the SIMD detection function doesn't panic)
776    }
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        // Results should be finite (actual values computed by scalar implementation)
790        for &result in results.iter() {
791            assert!(result.is_finite());
792        }
793    }
794}