1#[cfg(feature = "wgpu")]
58pub mod wgpu_rbf;
59
60use crate::advanced::rbf::{RBFInterpolator, RBFKernel};
61use crate::error::{InterpolateError, InterpolateResult};
62use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ScalarOperand};
63use scirs2_core::numeric::{Float, FromPrimitive, ToPrimitive};
64use std::fmt::{Debug, Display, LowerExp};
65use std::ops::{AddAssign, DivAssign, MulAssign, RemAssign, SubAssign};
66
67#[derive(Debug, thiserror::Error)]
73pub enum RbfGpuError {
74 #[error("no wgpu adapter available (GPU unavailable or unsupported)")]
76 NoAdapter,
77
78 #[error("wgpu device creation failed: {0}")]
80 DeviceCreation(String),
81
82 #[error("GPU buffer operation failed: {0}")]
84 Buffer(String),
85}
86
87#[derive(Debug, Clone, Copy, PartialEq)]
89pub enum GpuRBFKernel {
90 Gaussian,
92 Multiquadric,
94 InverseMultiquadric,
96 Linear,
98 Cubic,
100 ThinPlate,
102}
103
104#[derive(Debug, Clone)]
106pub struct GpuConfig {
107 pub device_id: usize,
109 pub max_memory_fraction: f32,
111 pub use_mixed_precision: bool,
113 pub num_streams: usize,
115 pub prefer_gpu: bool,
117 pub enable_memory_pooling: bool,
119}
120
121impl Default for GpuConfig {
122 fn default() -> Self {
123 Self {
124 device_id: 0,
125 max_memory_fraction: 0.8,
126 use_mixed_precision: false,
127 num_streams: 4,
128 prefer_gpu: true,
129 enable_memory_pooling: true,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Default)]
144pub struct GpuStats {
145 pub gpu_compute_time_ms: f64,
147 pub memory_transfer_time_ms: f64,
149 pub gpu_memory_used: u64,
151 pub kernel_launches: usize,
153 pub gpu_utilization: f32,
158 pub speedup_factor: f32,
163 pub used_gpu: bool,
165 pub cpu_time_ns: u64,
167 pub gpu_dispatch_ns: u64,
169 pub transfer_ns: u64,
171}
172
173impl GpuStats {
174 fn from_gpu_timing(
176 cpu_ns: u64,
177 dispatch_ns: u64,
178 transfer_ns: u64,
179 memory_bytes: u64,
180 launches: usize,
181 ) -> Self {
182 let total_gpu = dispatch_ns + transfer_ns;
183 let gpu_util = if total_gpu > 0 {
184 dispatch_ns as f32 / total_gpu as f32
185 } else {
186 0.0
187 };
188 let speedup = if total_gpu > 0 && cpu_ns > 0 {
189 cpu_ns as f32 / total_gpu as f32
190 } else {
191 0.0
192 };
193 Self {
194 gpu_compute_time_ms: dispatch_ns as f64 / 1_000_000.0,
195 memory_transfer_time_ms: transfer_ns as f64 / 1_000_000.0,
196 gpu_memory_used: memory_bytes,
197 kernel_launches: launches,
198 gpu_utilization: gpu_util,
199 speedup_factor: speedup,
200 used_gpu: true,
201 cpu_time_ns: cpu_ns,
202 gpu_dispatch_ns: dispatch_ns,
203 transfer_ns,
204 }
205 }
206
207 fn from_cpu(cpu_ns: u64) -> Self {
209 Self {
210 gpu_compute_time_ms: cpu_ns as f64 / 1_000_000.0,
211 cpu_time_ns: cpu_ns,
212 ..Default::default()
213 }
214 }
215}
216
217#[derive(Debug)]
227pub struct GpuRBFInterpolator<T>
228where
229 T: Float
230 + FromPrimitive
231 + ToPrimitive
232 + Debug
233 + Display
234 + LowerExp
235 + ScalarOperand
236 + AddAssign
237 + SubAssign
238 + MulAssign
239 + DivAssign
240 + RemAssign
241 + Copy
242 + Send
243 + Sync
244 + 'static,
245{
246 kernel: GpuRBFKernel,
248 kernel_width: T,
250 gpu_config: GpuConfig,
252 batch_size: usize,
254 x_data: Array1<T>,
256 y_data: Array1<T>,
258 coefficients: Option<Array1<T>>,
260 is_trained: bool,
262 stats: GpuStats,
264 cpu_fallback: Option<RBFInterpolator<T>>,
266}
267
268impl<T> Default for GpuRBFInterpolator<T>
269where
270 T: Float
271 + FromPrimitive
272 + ToPrimitive
273 + Debug
274 + Display
275 + LowerExp
276 + ScalarOperand
277 + AddAssign
278 + SubAssign
279 + MulAssign
280 + DivAssign
281 + RemAssign
282 + Copy
283 + Send
284 + Sync
285 + 'static,
286{
287 fn default() -> Self {
288 Self::new()
289 }
290}
291
292impl<T> GpuRBFInterpolator<T>
293where
294 T: Float
295 + FromPrimitive
296 + ToPrimitive
297 + Debug
298 + Display
299 + LowerExp
300 + ScalarOperand
301 + AddAssign
302 + SubAssign
303 + MulAssign
304 + DivAssign
305 + RemAssign
306 + Copy
307 + Send
308 + Sync
309 + 'static,
310{
311 pub fn new() -> Self {
313 Self {
314 kernel: GpuRBFKernel::Gaussian,
315 kernel_width: T::one(),
316 gpu_config: GpuConfig::default(),
317 batch_size: 1024,
318 x_data: Array1::zeros(0),
319 y_data: Array1::zeros(0),
320 coefficients: None,
321 is_trained: false,
322 stats: GpuStats::default(),
323 cpu_fallback: None,
324 }
325 }
326
327 pub fn with_kernel(mut self, kernel: GpuRBFKernel) -> Self {
329 self.kernel = kernel;
330 self
331 }
332
333 pub fn with_kernel_width(mut self, width: T) -> Self {
335 self.kernel_width = width;
336 self
337 }
338
339 pub fn with_gpu_config(mut self, config: GpuConfig) -> Self {
341 self.gpu_config = config;
342 self
343 }
344
345 pub fn with_batch_size(mut self, batchsize: usize) -> Self {
347 self.batch_size = batchsize;
348 self
349 }
350
351 pub fn is_gpu_available() -> bool {
356 #[cfg(feature = "wgpu")]
357 {
358 wgpu_rbf::is_gpu_available()
359 }
360 #[cfg(not(feature = "wgpu"))]
361 {
362 false
363 }
364 }
365
366 pub fn fit(&mut self, x: &ArrayView1<T>, y: &ArrayView1<T>) -> InterpolateResult<bool> {
377 if x.len() != y.len() {
378 return Err(InterpolateError::DimensionMismatch(format!(
379 "x and y must have the same length, got {} and {}",
380 x.len(),
381 y.len()
382 )));
383 }
384
385 if x.len() < 2 {
386 return Err(InterpolateError::InvalidValue(
387 "At least 2 data points are required for RBF interpolation".to_string(),
388 ));
389 }
390
391 let start = std::time::Instant::now();
392
393 self.x_data = x.to_owned();
394 self.y_data = y.to_owned();
395 self.coefficients = None;
396 self.cpu_fallback = None;
397
398 self.fit_cpu()?;
401 self.is_trained = true;
402
403 let cpu_ns = start.elapsed().as_nanos() as u64;
404 self.stats = GpuStats::from_cpu(cpu_ns);
405
406 Ok(true)
407 }
408
409 pub fn evaluate(&mut self, xeval: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
419 if !self.is_trained {
420 return Err(InterpolateError::InvalidState(
421 "Interpolator must be trained before evaluation".to_string(),
422 ));
423 }
424
425 let n_centers = self.x_data.len();
426 let n_queries = xeval.len();
427
428 #[cfg(feature = "wgpu")]
430 {
431 let above_threshold = n_centers * n_queries >= wgpu_rbf::GPU_THRESHOLD;
432 if above_threshold && self.gpu_config.prefer_gpu && Self::is_gpu_available() {
433 match self.evaluate_gpu(xeval) {
434 Ok(result) => return Ok(result),
435 Err(e) => {
436 eprintln!("wgpu RBF evaluate failed, falling back to CPU: {e}");
438 }
439 }
440 }
441 }
442
443 let t0 = std::time::Instant::now();
445 let result = self.evaluate_cpu(xeval)?;
446 let cpu_ns = t0.elapsed().as_nanos() as u64;
447 self.stats = GpuStats::from_cpu(cpu_ns);
448 Ok(result)
449 }
450
451 pub fn get_stats(&self) -> &GpuStats {
453 &self.stats
454 }
455
456 #[cfg(feature = "wgpu")]
461 fn evaluate_gpu(&mut self, xeval: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
462 use wgpu_rbf::gpu_rbf_evaluate;
463
464 let centers: Vec<f64> = self
466 .x_data
467 .iter()
468 .map(|&v| v.to_f64().unwrap_or(0.0))
469 .collect();
470
471 let coefficients = self.extract_coefficients()?;
489
490 let queries: Vec<f64> = xeval.iter().map(|&v| v.to_f64().unwrap_or(0.0)).collect();
491 let eps = self.kernel_width.to_f64().unwrap_or(1.0);
492
493 let t_cpu_start = std::time::Instant::now();
494 let cpu_reference_ns = {
496 let t = std::time::Instant::now();
497 let _ = self.evaluate_cpu(xeval);
498 t.elapsed().as_nanos() as u64
499 };
500 let _ = t_cpu_start;
501
502 match gpu_rbf_evaluate(&coefficients, ¢ers, &queries, self.kernel, eps) {
503 Ok((values, timing)) => {
504 let result: Array1<T> = Array1::from_vec(
505 values
506 .into_iter()
507 .map(|v| T::from_f64(v).unwrap_or(T::zero()))
508 .collect(),
509 );
510
511 let mem_bytes = (centers.len() + queries.len() + coefficients.len()) as u64 * 8;
512 self.stats = GpuStats::from_gpu_timing(
513 cpu_reference_ns,
514 timing.dispatch_ns,
515 timing.transfer_ns,
516 mem_bytes,
517 1,
518 );
519 Ok(result)
520 }
521 Err(e) => Err(InterpolateError::ComputationError(format!(
522 "GPU evaluate failed: {e}"
523 ))),
524 }
525 }
526
527 #[cfg(feature = "wgpu")]
533 fn extract_coefficients(&self) -> InterpolateResult<Vec<f64>> {
534 let n = self.x_data.len();
536 let eps = self.kernel_width.to_f64().unwrap_or(1.0);
537
538 let mut phi = vec![0.0f64; n * n];
539 for i in 0..n {
540 let xi = self.x_data[i].to_f64().unwrap_or(0.0);
541 for j in 0..n {
542 let xj = self.x_data[j].to_f64().unwrap_or(0.0);
543 let r = (xi - xj).abs();
544 phi[i * n + j] = cpu_kernel(r, self.kernel, eps);
545 }
546 }
547
548 let y: Vec<f64> = self
549 .y_data
550 .iter()
551 .map(|&v| v.to_f64().unwrap_or(0.0))
552 .collect();
553
554 gaussian_solve(&phi, &y, n)
556 .map_err(|e| InterpolateError::ComputationError(format!("coefficient solve: {e}")))
557 }
558
559 fn fit_cpu(&mut self) -> InterpolateResult<()> {
564 let cpu_kernel = match self.kernel {
565 GpuRBFKernel::Gaussian => RBFKernel::Gaussian,
566 GpuRBFKernel::Multiquadric => RBFKernel::Multiquadric,
567 GpuRBFKernel::InverseMultiquadric => RBFKernel::InverseMultiquadric,
568 GpuRBFKernel::Linear => RBFKernel::Linear,
569 GpuRBFKernel::Cubic => RBFKernel::Cubic,
570 GpuRBFKernel::ThinPlate => RBFKernel::ThinPlateSpline,
571 };
572
573 let points_2d = Array2::from_shape_vec((self.x_data.len(), 1), self.x_data.to_vec())
574 .map_err(|e| {
575 InterpolateError::ComputationError(format!("Failed to reshape points: {}", e))
576 })?;
577
578 let cpu_interpolator = RBFInterpolator::new(
579 &points_2d.view(),
580 &self.y_data.view(),
581 cpu_kernel,
582 self.kernel_width,
583 )?;
584
585 self.cpu_fallback = Some(cpu_interpolator);
586 Ok(())
587 }
588
589 fn evaluate_cpu(&self, xeval: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
590 if let Some(ref cpu_interpolator) = self.cpu_fallback {
591 let eval_points_2d =
592 Array2::from_shape_vec((xeval.len(), 1), xeval.to_vec()).map_err(|e| {
593 InterpolateError::ComputationError(format!(
594 "Failed to reshape eval points: {}",
595 e
596 ))
597 })?;
598
599 cpu_interpolator.interpolate(&eval_points_2d.view())
600 } else {
601 let n = self.x_data.len();
603 let m = xeval.len();
604 let mut result = Array1::zeros(m);
605
606 for i in 0..m {
607 let x_i = xeval[i];
608
609 if n >= 2 {
610 if x_i <= self.x_data[0] {
611 result[i] = self.y_data[0];
612 } else if x_i >= self.x_data[n - 1] {
613 result[i] = self.y_data[n - 1];
614 } else {
615 for j in 0..n - 1 {
616 if x_i >= self.x_data[j] && x_i <= self.x_data[j + 1] {
617 let t =
618 (x_i - self.x_data[j]) / (self.x_data[j + 1] - self.x_data[j]);
619 result[i] =
620 self.y_data[j] * (T::one() - t) + self.y_data[j + 1] * t;
621 break;
622 }
623 }
624 }
625 } else if n == 1 {
626 result[i] = self.y_data[0];
627 }
628 }
629
630 Ok(result)
631 }
632 }
633
634 #[allow(dead_code)]
636 fn evaluate_kernel(&self, distance: T) -> T {
637 let r = distance / self.kernel_width;
638
639 match self.kernel {
640 GpuRBFKernel::Gaussian => (-r * r).exp(),
641 GpuRBFKernel::Multiquadric => (T::one() + r * r).sqrt(),
642 GpuRBFKernel::InverseMultiquadric => T::one() / (T::one() + r * r).sqrt(),
643 GpuRBFKernel::Linear => r,
644 GpuRBFKernel::Cubic => r * r * r,
645 GpuRBFKernel::ThinPlate => {
646 if r > T::zero() {
647 r * r * r.ln()
648 } else {
649 T::zero()
650 }
651 }
652 }
653 }
654}
655
656fn cpu_kernel(r: f64, kernel: GpuRBFKernel, epsilon: f64) -> f64 {
661 let re = r / epsilon;
662 match kernel {
663 GpuRBFKernel::Gaussian => (-re * re).exp(),
664 GpuRBFKernel::Multiquadric => (1.0 + re * re).sqrt(),
665 GpuRBFKernel::InverseMultiquadric => 1.0 / (1.0 + re * re).sqrt(),
666 GpuRBFKernel::Linear => re,
667 GpuRBFKernel::Cubic => re * re * re,
668 GpuRBFKernel::ThinPlate => {
669 if re > 0.0 {
670 re * re * re.ln()
671 } else {
672 0.0
673 }
674 }
675 }
676}
677
678fn gaussian_solve(a: &[f64], b: &[f64], n: usize) -> Result<Vec<f64>, String> {
682 let mut mat: Vec<Vec<f64>> = (0..n)
684 .map(|i| {
685 let mut row: Vec<f64> = a[i * n..(i + 1) * n].to_vec();
686 row.push(b[i]);
687 row
688 })
689 .collect();
690
691 for col in 0..n {
692 let pivot_row = (col..n)
694 .max_by(|&r1, &r2| mat[r1][col].abs().total_cmp(&mat[r2][col].abs()))
695 .ok_or("empty range")?;
696 mat.swap(col, pivot_row);
697
698 let pivot = mat[col][col];
699 if pivot.abs() < 1e-14 {
700 return Err(format!("near-singular matrix at column {col}"));
701 }
702
703 for row in (col + 1)..n {
704 let factor = mat[row][col] / pivot;
705 for j in col..=n {
706 let val = mat[col][j] * factor;
707 mat[row][j] -= val;
708 }
709 }
710 }
711
712 let mut x = vec![0.0f64; n];
714 for i in (0..n).rev() {
715 let mut sum = mat[i][n];
716 for j in (i + 1)..n {
717 sum -= mat[i][j] * x[j];
718 }
719 x[i] = sum / mat[i][i];
720 }
721 Ok(x)
722}
723
724#[derive(Debug)]
730pub struct GpuBatchSplineEvaluator<T>
731where
732 T: Float + FromPrimitive + ToPrimitive + Debug + Copy + 'static,
733{
734 gpu_config: GpuConfig,
736 batch_size: usize,
738 #[allow(dead_code)]
740 stats: GpuStats,
741 _phantom: std::marker::PhantomData<T>,
743}
744
745impl<T> GpuBatchSplineEvaluator<T>
746where
747 T: Float + FromPrimitive + ToPrimitive + Debug + Copy + 'static,
748{
749 pub fn new() -> Self {
751 Self {
752 gpu_config: GpuConfig::default(),
753 batch_size: 2048,
754 stats: GpuStats::default(),
755 _phantom: std::marker::PhantomData,
756 }
757 }
758
759 pub fn with_gpu_config(mut self, config: GpuConfig) -> Self {
761 self.gpu_config = config;
762 self
763 }
764
765 pub fn with_batch_size(mut self, batchsize: usize) -> Self {
767 self.batch_size = batchsize;
768 self
769 }
770
771 #[allow(dead_code)]
773 pub fn batch_evaluate(
774 &self,
775 _coefficients: &Array2<T>,
776 _knots: &Array2<T>,
777 _xeval: &ArrayView1<T>,
778 ) -> InterpolateResult<Array2<T>> {
779 Err(InterpolateError::NotImplemented(
780 "GPU batch spline evaluation not yet implemented".to_string(),
781 ))
782 }
783}
784
785impl<T> Default for GpuBatchSplineEvaluator<T>
786where
787 T: Float + FromPrimitive + ToPrimitive + Debug + Copy + 'static,
788{
789 fn default() -> Self {
790 Self::new()
791 }
792}
793
794#[allow(dead_code)]
800pub fn make_gpu_rbf_interpolator<T>(
801 x: &ArrayView1<T>,
802 y: &ArrayView1<T>,
803 kernel: GpuRBFKernel,
804 kernel_width: T,
805) -> InterpolateResult<GpuRBFInterpolator<T>>
806where
807 T: Float
808 + FromPrimitive
809 + ToPrimitive
810 + Debug
811 + Display
812 + LowerExp
813 + ScalarOperand
814 + AddAssign
815 + SubAssign
816 + MulAssign
817 + DivAssign
818 + RemAssign
819 + Copy
820 + Send
821 + Sync
822 + 'static,
823{
824 let mut interpolator = GpuRBFInterpolator::new()
825 .with_kernel(kernel)
826 .with_kernel_width(kernel_width);
827
828 interpolator.fit(x, y)?;
829 Ok(interpolator)
830}
831
832#[allow(dead_code)]
834pub fn is_gpu_acceleration_available() -> bool {
835 GpuRBFInterpolator::<f64>::is_gpu_available()
836}
837
838#[allow(dead_code)]
840pub fn get_gpu_device_info() -> Option<GpuDeviceInfo> {
841 None
842}
843
844#[derive(Debug, Clone)]
846pub struct GpuDeviceInfo {
847 pub device_count: usize,
849 pub device_name: String,
851 pub memory_total: u64,
853 pub memory_available: u64,
855 pub compute_capability: String,
857 pub max_threads_per_block: usize,
859 pub max_blocks_per_grid: usize,
861}
862
863pub struct GpuMemoryManager {
865 max_memory_usage: u64,
867 current_usage: u64,
869 #[allow(dead_code)]
871 memory_pool: Vec<u64>,
872}
873
874impl GpuMemoryManager {
875 pub fn new(max_memory_bytes: u64) -> Self {
877 Self {
878 max_memory_usage: max_memory_bytes,
879 current_usage: 0,
880 memory_pool: Vec::new(),
881 }
882 }
883
884 pub fn can_allocate(&self, size_bytes: u64) -> bool {
886 self.current_usage + size_bytes <= self.max_memory_usage
887 }
888
889 pub fn optimal_batch_size(&self, item_size_bytes: u64) -> usize {
891 let available = self.max_memory_usage - self.current_usage;
892 let safety_factor = 0.8;
893 let usable = (available as f64 * safety_factor) as u64;
894
895 if item_size_bytes > 0 {
896 (usable / item_size_bytes) as usize
897 } else {
898 1024
899 }
900 }
901
902 pub fn get_usage_stats(&self) -> (u64, u64, f32) {
904 let usage_fraction = if self.max_memory_usage > 0 {
905 self.current_usage as f32 / self.max_memory_usage as f32
906 } else {
907 0.0
908 };
909 (self.current_usage, self.max_memory_usage, usage_fraction)
910 }
911}
912
913#[derive(Debug, Clone)]
915pub struct GpuKernelConfig {
916 pub block_size: usize,
918 pub grid_size: usize,
920 pub shared_memory_size: usize,
922 pub stream_id: usize,
924}
925
926impl Default for GpuKernelConfig {
927 fn default() -> Self {
928 Self {
929 block_size: 256,
930 grid_size: 1,
931 shared_memory_size: 0,
932 stream_id: 0,
933 }
934 }
935}
936
937impl GpuKernelConfig {
938 pub fn optimal_for_size(problem_size: usize) -> Self {
940 let block_size = 256.min(problem_size);
941 let grid_size = problem_size.div_ceil(block_size);
942
943 Self {
944 block_size,
945 grid_size,
946 shared_memory_size: block_size * 8,
947 stream_id: 0,
948 }
949 }
950
951 pub fn tune_for_architecture(mut self, compute_capability: &str) -> Self {
953 match compute_capability {
954 cap if cap.starts_with("8.") => {
955 self.block_size = 512;
956 self.shared_memory_size = self.block_size * 16;
957 }
958 cap if cap.starts_with("7.") => {
959 self.block_size = 256;
960 self.shared_memory_size = self.block_size * 12;
961 }
962 _ => {
963 self.block_size = 128;
964 self.shared_memory_size = self.block_size * 8;
965 }
966 }
967 self
968 }
969}
970
971pub mod gpu_utils {
973 use super::*;
974
975 pub fn estimate_rbf_memory_requirements(n_points: usize, n_eval: usize) -> u64 {
977 let float_size = std::mem::size_of::<f64>() as u64;
978 let matrix_size = (n_points * n_points) as u64 * float_size;
979 let data_size = (n_points * 2) as u64 * float_size;
980 let eval_size = (n_eval * 2) as u64 * float_size;
981 let overhead = (matrix_size + data_size + eval_size) / 2;
982 matrix_size + data_size + eval_size + overhead
983 }
984
985 pub fn is_gpu_worthwhile(n_points: usize, n_eval: usize) -> bool {
987 let total_operations = n_points * n_eval;
988 total_operations > 10000
989 }
990
991 pub fn recommend_gpu_config(n_points: usize, n_eval: usize) -> GpuConfig {
993 let mut config = GpuConfig::default();
994 let memory_req = estimate_rbf_memory_requirements(n_points, n_eval);
995 if memory_req > 1_000_000_000 {
996 config.max_memory_fraction = 0.9;
997 } else if memory_req > 100_000_000 {
998 config.max_memory_fraction = 0.7;
999 } else {
1000 config.max_memory_fraction = 0.5;
1001 }
1002 config.use_mixed_precision = n_points > 50000;
1003 config.num_streams = if n_eval > 100000 { 8 } else { 4 };
1004 config
1005 }
1006}
1007
1008#[cfg(test)]
1013mod tests {
1014 use super::*;
1015 use scirs2_core::ndarray::Array1;
1016
1017 #[test]
1018 fn test_gpu_rbf_creation() {
1019 let interpolator = GpuRBFInterpolator::<f64>::new();
1020 assert_eq!(interpolator.kernel, GpuRBFKernel::Gaussian);
1021 assert_eq!(interpolator.kernel_width, 1.0);
1022 assert!(!interpolator.is_trained);
1023 }
1024
1025 #[test]
1026 fn test_gpu_rbf_configuration() {
1027 let interpolator = GpuRBFInterpolator::<f64>::new()
1028 .with_kernel(GpuRBFKernel::Multiquadric)
1029 .with_kernel_width(2.0)
1030 .with_batch_size(512);
1031
1032 assert_eq!(interpolator.kernel, GpuRBFKernel::Multiquadric);
1033 assert_eq!(interpolator.kernel_width, 2.0);
1034 assert_eq!(interpolator.batch_size, 512);
1035 }
1036
1037 #[test]
1038 fn test_gpu_rbf_fitting() {
1039 let x = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0, 4.0]);
1040 let y = Array1::from_vec(vec![0.0, 1.0, 4.0, 9.0, 16.0]);
1041
1042 let mut interpolator = GpuRBFInterpolator::new()
1043 .with_kernel(GpuRBFKernel::Gaussian)
1044 .with_kernel_width(1.0);
1045
1046 let result = interpolator.fit(&x.view(), &y.view());
1047 assert!(result.is_ok());
1048 assert!(interpolator.is_trained);
1049 }
1050
1051 #[test]
1052 fn test_gpu_rbf_evaluation() {
1053 let x = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0, 4.0]);
1054 let y = Array1::from_vec(vec![0.0, 1.0, 4.0, 9.0, 16.0]);
1055
1056 let mut interpolator = GpuRBFInterpolator::new()
1057 .with_kernel(GpuRBFKernel::Linear)
1058 .with_kernel_width(1.0);
1059
1060 interpolator
1061 .fit(&x.view(), &y.view())
1062 .expect("Operation failed");
1063
1064 let xeval = Array1::from_vec(vec![0.5, 1.5, 2.5]);
1065 let result = interpolator.evaluate(&xeval.view());
1066
1067 assert!(result.is_ok());
1068 let y_eval = result.expect("Operation failed");
1069 assert_eq!(y_eval.len(), 3);
1070 assert!(y_eval.iter().all(|&val| val.is_finite()));
1071 }
1072
1073 #[test]
1074 fn test_kernel_evaluation() {
1075 let interpolator = GpuRBFInterpolator::<f64>::new()
1076 .with_kernel(GpuRBFKernel::Gaussian)
1077 .with_kernel_width(1.0);
1078
1079 let k0 = interpolator.evaluate_kernel(0.0);
1080 assert!((k0 - 1.0).abs() < 1e-10);
1081
1082 let k1 = interpolator.evaluate_kernel(1.0);
1083 assert!((k1 - (-1.0_f64).exp()).abs() < 1e-10);
1084 }
1085
1086 #[test]
1087 fn test_make_gpu_rbf_interpolator() {
1088 let x = Array1::linspace(0.0, 10.0, 11);
1089 let y = x.mapv(|x| x.sin());
1090
1091 let result = make_gpu_rbf_interpolator(&x.view(), &y.view(), GpuRBFKernel::Gaussian, 1.0);
1092
1093 assert!(result.is_ok());
1094 let interpolator = result.expect("Operation failed");
1095 assert!(interpolator.is_trained);
1096 }
1097
1098 #[test]
1099 fn test_gpu_availability_check() {
1100 let available1 = GpuRBFInterpolator::<f64>::is_gpu_available();
1101 let available2 = is_gpu_acceleration_available();
1102 assert_eq!(available1, available2);
1103 }
1104
1105 #[test]
1106 fn test_gpu_batch_evaluator_creation() {
1107 let evaluator = GpuBatchSplineEvaluator::<f64>::new();
1108 assert_eq!(evaluator.batch_size, 2048);
1109 }
1110
1111 #[test]
1112 fn test_gpu_device_info() {
1113 let info = get_gpu_device_info();
1114 assert!(info.is_none());
1115 }
1116
1117 #[test]
1118 fn test_different_gpu_kernels() {
1119 let x = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0]);
1120 let y = Array1::from_vec(vec![0.0, 1.0, 4.0, 9.0]);
1121
1122 let kernels = vec![
1123 GpuRBFKernel::Gaussian,
1124 GpuRBFKernel::Multiquadric,
1125 GpuRBFKernel::InverseMultiquadric,
1126 GpuRBFKernel::Linear,
1127 GpuRBFKernel::Cubic,
1128 GpuRBFKernel::ThinPlate,
1129 ];
1130
1131 for kernel in kernels {
1132 let mut interpolator = GpuRBFInterpolator::new()
1133 .with_kernel(kernel)
1134 .with_kernel_width(1.0);
1135
1136 let fit_result = interpolator.fit(&x.view(), &y.view());
1137 assert!(fit_result.is_ok(), "Failed to fit with kernel {:?}", kernel);
1138
1139 if interpolator.is_trained {
1140 let xeval = Array1::from_vec(vec![0.5, 1.5]);
1141 let eval_result = interpolator.evaluate(&xeval.view());
1142 assert!(
1143 eval_result.is_ok(),
1144 "Failed to evaluate with kernel {:?}",
1145 kernel
1146 );
1147 }
1148 }
1149 }
1150
1151 #[test]
1152 fn test_gaussian_solve_simple() {
1153 let a = [1.0, 0.0, 0.0, 1.0];
1155 let b = [3.0, 4.0];
1156 let w = gaussian_solve(&a, &b, 2).expect("Should solve");
1157 assert!((w[0] - 3.0).abs() < 1e-12);
1158 assert!((w[1] - 4.0).abs() < 1e-12);
1159 }
1160}