#[cfg(feature = "wgpu_rbf")]
pub mod wgpu_rbf;
use crate::advanced::rbf::{RBFInterpolator, RBFKernel};
use crate::error::{InterpolateError, InterpolateResult};
use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ScalarOperand};
use scirs2_core::numeric::{Float, FromPrimitive, ToPrimitive};
use std::fmt::{Debug, Display, LowerExp};
use std::ops::{AddAssign, DivAssign, MulAssign, RemAssign, SubAssign};
#[derive(Debug, thiserror::Error)]
pub enum RbfGpuError {
#[error("no wgpu adapter available (GPU unavailable or unsupported)")]
NoAdapter,
#[error("wgpu device creation failed: {0}")]
DeviceCreation(String),
#[error("GPU buffer operation failed: {0}")]
Buffer(String),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GpuRBFKernel {
Gaussian,
Multiquadric,
InverseMultiquadric,
Linear,
Cubic,
ThinPlate,
}
#[derive(Debug, Clone)]
pub struct GpuConfig {
pub device_id: usize,
pub max_memory_fraction: f32,
pub use_mixed_precision: bool,
pub num_streams: usize,
pub prefer_gpu: bool,
pub enable_memory_pooling: bool,
}
impl Default for GpuConfig {
fn default() -> Self {
Self {
device_id: 0,
max_memory_fraction: 0.8,
use_mixed_precision: false,
num_streams: 4,
prefer_gpu: true,
enable_memory_pooling: true,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct GpuStats {
pub gpu_compute_time_ms: f64,
pub memory_transfer_time_ms: f64,
pub gpu_memory_used: u64,
pub kernel_launches: usize,
pub gpu_utilization: f32,
pub speedup_factor: f32,
pub used_gpu: bool,
pub cpu_time_ns: u64,
pub gpu_dispatch_ns: u64,
pub transfer_ns: u64,
}
impl GpuStats {
fn from_gpu_timing(
cpu_ns: u64,
dispatch_ns: u64,
transfer_ns: u64,
memory_bytes: u64,
launches: usize,
) -> Self {
let total_gpu = dispatch_ns + transfer_ns;
let gpu_util = if total_gpu > 0 {
dispatch_ns as f32 / total_gpu as f32
} else {
0.0
};
let speedup = if total_gpu > 0 && cpu_ns > 0 {
cpu_ns as f32 / total_gpu as f32
} else {
0.0
};
Self {
gpu_compute_time_ms: dispatch_ns as f64 / 1_000_000.0,
memory_transfer_time_ms: transfer_ns as f64 / 1_000_000.0,
gpu_memory_used: memory_bytes,
kernel_launches: launches,
gpu_utilization: gpu_util,
speedup_factor: speedup,
used_gpu: true,
cpu_time_ns: cpu_ns,
gpu_dispatch_ns: dispatch_ns,
transfer_ns,
}
}
fn from_cpu(cpu_ns: u64) -> Self {
Self {
gpu_compute_time_ms: cpu_ns as f64 / 1_000_000.0,
cpu_time_ns: cpu_ns,
..Default::default()
}
}
}
#[derive(Debug)]
pub struct GpuRBFInterpolator<T>
where
T: Float
+ FromPrimitive
+ ToPrimitive
+ Debug
+ Display
+ LowerExp
+ ScalarOperand
+ AddAssign
+ SubAssign
+ MulAssign
+ DivAssign
+ RemAssign
+ Copy
+ Send
+ Sync
+ 'static,
{
kernel: GpuRBFKernel,
kernel_width: T,
gpu_config: GpuConfig,
batch_size: usize,
x_data: Array1<T>,
y_data: Array1<T>,
coefficients: Option<Array1<T>>,
is_trained: bool,
stats: GpuStats,
cpu_fallback: Option<RBFInterpolator<T>>,
}
impl<T> Default for GpuRBFInterpolator<T>
where
T: Float
+ FromPrimitive
+ ToPrimitive
+ Debug
+ Display
+ LowerExp
+ ScalarOperand
+ AddAssign
+ SubAssign
+ MulAssign
+ DivAssign
+ RemAssign
+ Copy
+ Send
+ Sync
+ 'static,
{
fn default() -> Self {
Self::new()
}
}
impl<T> GpuRBFInterpolator<T>
where
T: Float
+ FromPrimitive
+ ToPrimitive
+ Debug
+ Display
+ LowerExp
+ ScalarOperand
+ AddAssign
+ SubAssign
+ MulAssign
+ DivAssign
+ RemAssign
+ Copy
+ Send
+ Sync
+ 'static,
{
pub fn new() -> Self {
Self {
kernel: GpuRBFKernel::Gaussian,
kernel_width: T::one(),
gpu_config: GpuConfig::default(),
batch_size: 1024,
x_data: Array1::zeros(0),
y_data: Array1::zeros(0),
coefficients: None,
is_trained: false,
stats: GpuStats::default(),
cpu_fallback: None,
}
}
pub fn with_kernel(mut self, kernel: GpuRBFKernel) -> Self {
self.kernel = kernel;
self
}
pub fn with_kernel_width(mut self, width: T) -> Self {
self.kernel_width = width;
self
}
pub fn with_gpu_config(mut self, config: GpuConfig) -> Self {
self.gpu_config = config;
self
}
pub fn with_batch_size(mut self, batchsize: usize) -> Self {
self.batch_size = batchsize;
self
}
pub fn is_gpu_available() -> bool {
#[cfg(feature = "wgpu_rbf")]
{
wgpu_rbf::is_gpu_available()
}
#[cfg(not(feature = "wgpu_rbf"))]
{
false
}
}
pub fn fit(&mut self, x: &ArrayView1<T>, y: &ArrayView1<T>) -> InterpolateResult<bool> {
if x.len() != y.len() {
return Err(InterpolateError::DimensionMismatch(format!(
"x and y must have the same length, got {} and {}",
x.len(),
y.len()
)));
}
if x.len() < 2 {
return Err(InterpolateError::InvalidValue(
"At least 2 data points are required for RBF interpolation".to_string(),
));
}
let start = std::time::Instant::now();
self.x_data = x.to_owned();
self.y_data = y.to_owned();
self.coefficients = None;
self.cpu_fallback = None;
self.fit_cpu()?;
self.is_trained = true;
let cpu_ns = start.elapsed().as_nanos() as u64;
self.stats = GpuStats::from_cpu(cpu_ns);
Ok(true)
}
pub fn evaluate(&mut self, xeval: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
if !self.is_trained {
return Err(InterpolateError::InvalidState(
"Interpolator must be trained before evaluation".to_string(),
));
}
let n_centers = self.x_data.len();
let n_queries = xeval.len();
#[cfg(feature = "wgpu_rbf")]
{
let above_threshold = n_centers * n_queries >= wgpu_rbf::GPU_THRESHOLD;
if above_threshold && self.gpu_config.prefer_gpu && Self::is_gpu_available() {
match self.evaluate_gpu(xeval) {
Ok(result) => return Ok(result),
Err(e) => {
eprintln!("wgpu RBF evaluate failed, falling back to CPU: {e}");
}
}
}
}
let t0 = std::time::Instant::now();
let result = self.evaluate_cpu(xeval)?;
let cpu_ns = t0.elapsed().as_nanos() as u64;
self.stats = GpuStats::from_cpu(cpu_ns);
Ok(result)
}
pub fn get_stats(&self) -> &GpuStats {
&self.stats
}
#[cfg(feature = "wgpu_rbf")]
fn evaluate_gpu(&mut self, xeval: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
use wgpu_rbf::gpu_rbf_evaluate;
let centers: Vec<f64> = self
.x_data
.iter()
.map(|&v| v.to_f64().unwrap_or(0.0))
.collect();
let coefficients = self.extract_coefficients()?;
let queries: Vec<f64> = xeval.iter().map(|&v| v.to_f64().unwrap_or(0.0)).collect();
let eps = self.kernel_width.to_f64().unwrap_or(1.0);
let t_cpu_start = std::time::Instant::now();
let cpu_reference_ns = {
let t = std::time::Instant::now();
let _ = self.evaluate_cpu(xeval);
t.elapsed().as_nanos() as u64
};
let _ = t_cpu_start;
match gpu_rbf_evaluate(&coefficients, ¢ers, &queries, self.kernel, eps) {
Ok((values, timing)) => {
let result: Array1<T> = Array1::from_vec(
values
.into_iter()
.map(|v| T::from_f64(v).unwrap_or(T::zero()))
.collect(),
);
let mem_bytes = (centers.len() + queries.len() + coefficients.len()) as u64 * 8;
self.stats = GpuStats::from_gpu_timing(
cpu_reference_ns,
timing.dispatch_ns,
timing.transfer_ns,
mem_bytes,
1,
);
Ok(result)
}
Err(e) => Err(InterpolateError::ComputationError(format!(
"GPU evaluate failed: {e}"
))),
}
}
#[cfg(feature = "wgpu_rbf")]
fn extract_coefficients(&self) -> InterpolateResult<Vec<f64>> {
let n = self.x_data.len();
let eps = self.kernel_width.to_f64().unwrap_or(1.0);
let mut phi = vec![0.0f64; n * n];
for i in 0..n {
let xi = self.x_data[i].to_f64().unwrap_or(0.0);
for j in 0..n {
let xj = self.x_data[j].to_f64().unwrap_or(0.0);
let r = (xi - xj).abs();
phi[i * n + j] = cpu_kernel(r, self.kernel, eps);
}
}
let y: Vec<f64> = self
.y_data
.iter()
.map(|&v| v.to_f64().unwrap_or(0.0))
.collect();
gaussian_solve(&phi, &y, n)
.map_err(|e| InterpolateError::ComputationError(format!("coefficient solve: {e}")))
}
fn fit_cpu(&mut self) -> InterpolateResult<()> {
let cpu_kernel = match self.kernel {
GpuRBFKernel::Gaussian => RBFKernel::Gaussian,
GpuRBFKernel::Multiquadric => RBFKernel::Multiquadric,
GpuRBFKernel::InverseMultiquadric => RBFKernel::InverseMultiquadric,
GpuRBFKernel::Linear => RBFKernel::Linear,
GpuRBFKernel::Cubic => RBFKernel::Cubic,
GpuRBFKernel::ThinPlate => RBFKernel::ThinPlateSpline,
};
let points_2d = Array2::from_shape_vec((self.x_data.len(), 1), self.x_data.to_vec())
.map_err(|e| {
InterpolateError::ComputationError(format!("Failed to reshape points: {}", e))
})?;
let cpu_interpolator = RBFInterpolator::new(
&points_2d.view(),
&self.y_data.view(),
cpu_kernel,
self.kernel_width,
)?;
self.cpu_fallback = Some(cpu_interpolator);
Ok(())
}
fn evaluate_cpu(&self, xeval: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
if let Some(ref cpu_interpolator) = self.cpu_fallback {
let eval_points_2d =
Array2::from_shape_vec((xeval.len(), 1), xeval.to_vec()).map_err(|e| {
InterpolateError::ComputationError(format!(
"Failed to reshape eval points: {}",
e
))
})?;
cpu_interpolator.interpolate(&eval_points_2d.view())
} else {
let n = self.x_data.len();
let m = xeval.len();
let mut result = Array1::zeros(m);
for i in 0..m {
let x_i = xeval[i];
if n >= 2 {
if x_i <= self.x_data[0] {
result[i] = self.y_data[0];
} else if x_i >= self.x_data[n - 1] {
result[i] = self.y_data[n - 1];
} else {
for j in 0..n - 1 {
if x_i >= self.x_data[j] && x_i <= self.x_data[j + 1] {
let t =
(x_i - self.x_data[j]) / (self.x_data[j + 1] - self.x_data[j]);
result[i] =
self.y_data[j] * (T::one() - t) + self.y_data[j + 1] * t;
break;
}
}
}
} else if n == 1 {
result[i] = self.y_data[0];
}
}
Ok(result)
}
}
#[allow(dead_code)]
fn evaluate_kernel(&self, distance: T) -> T {
let r = distance / self.kernel_width;
match self.kernel {
GpuRBFKernel::Gaussian => (-r * r).exp(),
GpuRBFKernel::Multiquadric => (T::one() + r * r).sqrt(),
GpuRBFKernel::InverseMultiquadric => T::one() / (T::one() + r * r).sqrt(),
GpuRBFKernel::Linear => r,
GpuRBFKernel::Cubic => r * r * r,
GpuRBFKernel::ThinPlate => {
if r > T::zero() {
r * r * r.ln()
} else {
T::zero()
}
}
}
}
}
fn cpu_kernel(r: f64, kernel: GpuRBFKernel, epsilon: f64) -> f64 {
let re = r / epsilon;
match kernel {
GpuRBFKernel::Gaussian => (-re * re).exp(),
GpuRBFKernel::Multiquadric => (1.0 + re * re).sqrt(),
GpuRBFKernel::InverseMultiquadric => 1.0 / (1.0 + re * re).sqrt(),
GpuRBFKernel::Linear => re,
GpuRBFKernel::Cubic => re * re * re,
GpuRBFKernel::ThinPlate => {
if re > 0.0 {
re * re * re.ln()
} else {
0.0
}
}
}
}
fn gaussian_solve(a: &[f64], b: &[f64], n: usize) -> Result<Vec<f64>, String> {
let mut mat: Vec<Vec<f64>> = (0..n)
.map(|i| {
let mut row: Vec<f64> = a[i * n..(i + 1) * n].to_vec();
row.push(b[i]);
row
})
.collect();
for col in 0..n {
let pivot_row = (col..n)
.max_by(|&r1, &r2| mat[r1][col].abs().total_cmp(&mat[r2][col].abs()))
.ok_or("empty range")?;
mat.swap(col, pivot_row);
let pivot = mat[col][col];
if pivot.abs() < 1e-14 {
return Err(format!("near-singular matrix at column {col}"));
}
for row in (col + 1)..n {
let factor = mat[row][col] / pivot;
for j in col..=n {
let val = mat[col][j] * factor;
mat[row][j] -= val;
}
}
}
let mut x = vec![0.0f64; n];
for i in (0..n).rev() {
let mut sum = mat[i][n];
for j in (i + 1)..n {
sum -= mat[i][j] * x[j];
}
x[i] = sum / mat[i][i];
}
Ok(x)
}
#[derive(Debug)]
pub struct GpuBatchSplineEvaluator<T>
where
T: Float + FromPrimitive + ToPrimitive + Debug + Copy + 'static,
{
gpu_config: GpuConfig,
batch_size: usize,
#[allow(dead_code)]
stats: GpuStats,
_phantom: std::marker::PhantomData<T>,
}
impl<T> GpuBatchSplineEvaluator<T>
where
T: Float + FromPrimitive + ToPrimitive + Debug + Copy + 'static,
{
pub fn new() -> Self {
Self {
gpu_config: GpuConfig::default(),
batch_size: 2048,
stats: GpuStats::default(),
_phantom: std::marker::PhantomData,
}
}
pub fn with_gpu_config(mut self, config: GpuConfig) -> Self {
self.gpu_config = config;
self
}
pub fn with_batch_size(mut self, batchsize: usize) -> Self {
self.batch_size = batchsize;
self
}
#[allow(dead_code)]
pub fn batch_evaluate(
&self,
_coefficients: &Array2<T>,
_knots: &Array2<T>,
_xeval: &ArrayView1<T>,
) -> InterpolateResult<Array2<T>> {
Err(InterpolateError::NotImplemented(
"GPU batch spline evaluation not yet implemented".to_string(),
))
}
}
impl<T> Default for GpuBatchSplineEvaluator<T>
where
T: Float + FromPrimitive + ToPrimitive + Debug + Copy + 'static,
{
fn default() -> Self {
Self::new()
}
}
#[allow(dead_code)]
pub fn make_gpu_rbf_interpolator<T>(
x: &ArrayView1<T>,
y: &ArrayView1<T>,
kernel: GpuRBFKernel,
kernel_width: T,
) -> InterpolateResult<GpuRBFInterpolator<T>>
where
T: Float
+ FromPrimitive
+ ToPrimitive
+ Debug
+ Display
+ LowerExp
+ ScalarOperand
+ AddAssign
+ SubAssign
+ MulAssign
+ DivAssign
+ RemAssign
+ Copy
+ Send
+ Sync
+ 'static,
{
let mut interpolator = GpuRBFInterpolator::new()
.with_kernel(kernel)
.with_kernel_width(kernel_width);
interpolator.fit(x, y)?;
Ok(interpolator)
}
#[allow(dead_code)]
pub fn is_gpu_acceleration_available() -> bool {
GpuRBFInterpolator::<f64>::is_gpu_available()
}
#[allow(dead_code)]
pub fn get_gpu_device_info() -> Option<GpuDeviceInfo> {
None
}
#[derive(Debug, Clone)]
pub struct GpuDeviceInfo {
pub device_count: usize,
pub device_name: String,
pub memory_total: u64,
pub memory_available: u64,
pub compute_capability: String,
pub max_threads_per_block: usize,
pub max_blocks_per_grid: usize,
}
pub struct GpuMemoryManager {
max_memory_usage: u64,
current_usage: u64,
#[allow(dead_code)]
memory_pool: Vec<u64>,
}
impl GpuMemoryManager {
pub fn new(max_memory_bytes: u64) -> Self {
Self {
max_memory_usage: max_memory_bytes,
current_usage: 0,
memory_pool: Vec::new(),
}
}
pub fn can_allocate(&self, size_bytes: u64) -> bool {
self.current_usage + size_bytes <= self.max_memory_usage
}
pub fn optimal_batch_size(&self, item_size_bytes: u64) -> usize {
let available = self.max_memory_usage - self.current_usage;
let safety_factor = 0.8;
let usable = (available as f64 * safety_factor) as u64;
if item_size_bytes > 0 {
(usable / item_size_bytes) as usize
} else {
1024
}
}
pub fn get_usage_stats(&self) -> (u64, u64, f32) {
let usage_fraction = if self.max_memory_usage > 0 {
self.current_usage as f32 / self.max_memory_usage as f32
} else {
0.0
};
(self.current_usage, self.max_memory_usage, usage_fraction)
}
}
#[derive(Debug, Clone)]
pub struct GpuKernelConfig {
pub block_size: usize,
pub grid_size: usize,
pub shared_memory_size: usize,
pub stream_id: usize,
}
impl Default for GpuKernelConfig {
fn default() -> Self {
Self {
block_size: 256,
grid_size: 1,
shared_memory_size: 0,
stream_id: 0,
}
}
}
impl GpuKernelConfig {
pub fn optimal_for_size(problem_size: usize) -> Self {
let block_size = 256.min(problem_size);
let grid_size = problem_size.div_ceil(block_size);
Self {
block_size,
grid_size,
shared_memory_size: block_size * 8,
stream_id: 0,
}
}
pub fn tune_for_architecture(mut self, compute_capability: &str) -> Self {
match compute_capability {
cap if cap.starts_with("8.") => {
self.block_size = 512;
self.shared_memory_size = self.block_size * 16;
}
cap if cap.starts_with("7.") => {
self.block_size = 256;
self.shared_memory_size = self.block_size * 12;
}
_ => {
self.block_size = 128;
self.shared_memory_size = self.block_size * 8;
}
}
self
}
}
pub mod gpu_utils {
use super::*;
pub fn estimate_rbf_memory_requirements(n_points: usize, n_eval: usize) -> u64 {
let float_size = std::mem::size_of::<f64>() as u64;
let matrix_size = (n_points * n_points) as u64 * float_size;
let data_size = (n_points * 2) as u64 * float_size;
let eval_size = (n_eval * 2) as u64 * float_size;
let overhead = (matrix_size + data_size + eval_size) / 2;
matrix_size + data_size + eval_size + overhead
}
pub fn is_gpu_worthwhile(n_points: usize, n_eval: usize) -> bool {
let total_operations = n_points * n_eval;
total_operations > 10000
}
pub fn recommend_gpu_config(n_points: usize, n_eval: usize) -> GpuConfig {
let mut config = GpuConfig::default();
let memory_req = estimate_rbf_memory_requirements(n_points, n_eval);
if memory_req > 1_000_000_000 {
config.max_memory_fraction = 0.9;
} else if memory_req > 100_000_000 {
config.max_memory_fraction = 0.7;
} else {
config.max_memory_fraction = 0.5;
}
config.use_mixed_precision = n_points > 50000;
config.num_streams = if n_eval > 100000 { 8 } else { 4 };
config
}
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array1;
#[test]
fn test_gpu_rbf_creation() {
let interpolator = GpuRBFInterpolator::<f64>::new();
assert_eq!(interpolator.kernel, GpuRBFKernel::Gaussian);
assert_eq!(interpolator.kernel_width, 1.0);
assert!(!interpolator.is_trained);
}
#[test]
fn test_gpu_rbf_configuration() {
let interpolator = GpuRBFInterpolator::<f64>::new()
.with_kernel(GpuRBFKernel::Multiquadric)
.with_kernel_width(2.0)
.with_batch_size(512);
assert_eq!(interpolator.kernel, GpuRBFKernel::Multiquadric);
assert_eq!(interpolator.kernel_width, 2.0);
assert_eq!(interpolator.batch_size, 512);
}
#[test]
fn test_gpu_rbf_fitting() {
let x = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0, 4.0]);
let y = Array1::from_vec(vec![0.0, 1.0, 4.0, 9.0, 16.0]);
let mut interpolator = GpuRBFInterpolator::new()
.with_kernel(GpuRBFKernel::Gaussian)
.with_kernel_width(1.0);
let result = interpolator.fit(&x.view(), &y.view());
assert!(result.is_ok());
assert!(interpolator.is_trained);
}
#[test]
fn test_gpu_rbf_evaluation() {
let x = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0, 4.0]);
let y = Array1::from_vec(vec![0.0, 1.0, 4.0, 9.0, 16.0]);
let mut interpolator = GpuRBFInterpolator::new()
.with_kernel(GpuRBFKernel::Linear)
.with_kernel_width(1.0);
interpolator
.fit(&x.view(), &y.view())
.expect("Operation failed");
let xeval = Array1::from_vec(vec![0.5, 1.5, 2.5]);
let result = interpolator.evaluate(&xeval.view());
assert!(result.is_ok());
let y_eval = result.expect("Operation failed");
assert_eq!(y_eval.len(), 3);
assert!(y_eval.iter().all(|&val| val.is_finite()));
}
#[test]
fn test_kernel_evaluation() {
let interpolator = GpuRBFInterpolator::<f64>::new()
.with_kernel(GpuRBFKernel::Gaussian)
.with_kernel_width(1.0);
let k0 = interpolator.evaluate_kernel(0.0);
assert!((k0 - 1.0).abs() < 1e-10);
let k1 = interpolator.evaluate_kernel(1.0);
assert!((k1 - (-1.0_f64).exp()).abs() < 1e-10);
}
#[test]
fn test_make_gpu_rbf_interpolator() {
let x = Array1::linspace(0.0, 10.0, 11);
let y = x.mapv(|x| x.sin());
let result = make_gpu_rbf_interpolator(&x.view(), &y.view(), GpuRBFKernel::Gaussian, 1.0);
assert!(result.is_ok());
let interpolator = result.expect("Operation failed");
assert!(interpolator.is_trained);
}
#[test]
fn test_gpu_availability_check() {
let available1 = GpuRBFInterpolator::<f64>::is_gpu_available();
let available2 = is_gpu_acceleration_available();
assert_eq!(available1, available2);
}
#[test]
fn test_gpu_batch_evaluator_creation() {
let evaluator = GpuBatchSplineEvaluator::<f64>::new();
assert_eq!(evaluator.batch_size, 2048);
}
#[test]
fn test_gpu_device_info() {
let info = get_gpu_device_info();
assert!(info.is_none());
}
#[test]
fn test_different_gpu_kernels() {
let x = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0]);
let y = Array1::from_vec(vec![0.0, 1.0, 4.0, 9.0]);
let kernels = vec![
GpuRBFKernel::Gaussian,
GpuRBFKernel::Multiquadric,
GpuRBFKernel::InverseMultiquadric,
GpuRBFKernel::Linear,
GpuRBFKernel::Cubic,
GpuRBFKernel::ThinPlate,
];
for kernel in kernels {
let mut interpolator = GpuRBFInterpolator::new()
.with_kernel(kernel)
.with_kernel_width(1.0);
let fit_result = interpolator.fit(&x.view(), &y.view());
assert!(fit_result.is_ok(), "Failed to fit with kernel {:?}", kernel);
if interpolator.is_trained {
let xeval = Array1::from_vec(vec![0.5, 1.5]);
let eval_result = interpolator.evaluate(&xeval.view());
assert!(
eval_result.is_ok(),
"Failed to evaluate with kernel {:?}",
kernel
);
}
}
}
#[test]
fn test_gaussian_solve_simple() {
let a = [1.0, 0.0, 0.0, 1.0];
let b = [3.0, 4.0];
let w = gaussian_solve(&a, &b, 2).expect("Should solve");
assert!((w[0] - 3.0).abs() < 1e-12);
assert!((w[1] - 4.0).abs() < 1e-12);
}
}