sklears_kernel_approximation/
simd_kernel.rs1use scirs2_core::ndarray::{s, Array1, Array2, ArrayView1, ArrayView2};
15use scirs2_core::simd_ops::SimdUnifiedOps;
16use sklears_core::error::{Result as SklResult, SklearsError};
17use sklears_core::types::Float;
18
19pub fn simd_rbf_kernel(
24 x: &ArrayView2<f64>,
25 y: &ArrayView2<f64>,
26 gamma: f64,
27) -> SklResult<Array2<f64>> {
28 let (n_x, n_features) = x.dim();
29 let (n_y, n_features_y) = y.dim();
30
31 if n_features != n_features_y {
32 return Err(SklearsError::InvalidInput(
33 "Feature dimensions must match".to_string(),
34 ));
35 }
36
37 let mut kernel_matrix = Array2::<f64>::zeros((n_x, n_y));
38
39 for i in 0..n_x {
40 let x_row = x.slice(s![i, ..]);
41
42 for j in 0..n_y {
43 let y_row = y.slice(s![j, ..]);
44 let squared_distance = simd_squared_euclidean_distance(&x_row, &y_row);
45 kernel_matrix[[i, j]] = (-gamma * squared_distance).exp();
46 }
47 }
48
49 Ok(kernel_matrix)
50}
51
52pub fn simd_squared_euclidean_distance(x: &ArrayView1<f64>, y: &ArrayView1<f64>) -> f64 {
56 if x.len() != y.len() {
57 return 0.0;
58 }
59
60 let diff = x.to_owned() - y.to_owned();
61 if let Some(diff_slice) = diff.as_slice() {
62 let norm = Float::simd_norm(&ArrayView1::from(diff_slice));
63 norm * norm
64 } else {
65 diff.mapv(|v| v * v).sum()
66 }
67}
68
69pub fn simd_polynomial_kernel(
73 x: &ArrayView2<f64>,
74 y: &ArrayView2<f64>,
75 degree: i32,
76 gamma: f64,
77 coef0: f64,
78) -> SklResult<Array2<f64>> {
79 let (n_x, n_features) = x.dim();
80 let (n_y, n_features_y) = y.dim();
81
82 if n_features != n_features_y {
83 return Err(SklearsError::InvalidInput(
84 "Feature dimensions must match".to_string(),
85 ));
86 }
87
88 let mut kernel_matrix = Array2::<f64>::zeros((n_x, n_y));
89
90 for i in 0..n_x {
91 let x_row = x.slice(s![i, ..]);
92
93 for j in 0..n_y {
94 let y_row = y.slice(s![j, ..]);
95 let dot_product = simd_dot_product(&x_row, &y_row);
96 let kernel_value = (gamma * dot_product + coef0).powi(degree);
97 kernel_matrix[[i, j]] = kernel_value;
98 }
99 }
100
101 Ok(kernel_matrix)
102}
103
104pub fn simd_dot_product(x: &ArrayView1<f64>, y: &ArrayView1<f64>) -> f64 {
108 if x.len() != y.len() {
109 return 0.0;
110 }
111
112 Float::simd_dot(x, y)
113}
114
115pub fn simd_ridge_coefficients(
119 kernel_matrix: &ArrayView2<f64>,
120 y: &ArrayView1<f64>,
121 alpha: f64,
122) -> SklResult<Array1<f64>> {
123 let n = kernel_matrix.nrows();
124 if n != kernel_matrix.ncols() || n != y.len() {
125 return Err(SklearsError::InvalidInput(
126 "Matrix dimensions incompatible".to_string(),
127 ));
128 }
129
130 let mut regularized_kernel = kernel_matrix.to_owned();
132
133 for i in 0..n {
134 regularized_kernel[[i, i]] += alpha;
135 }
136
137 let mut coefficients = Array1::<f64>::zeros(n);
139 let max_iterations = 1000;
140 let tolerance = 1e-6;
141
142 for _iter in 0..max_iterations {
143 let mut new_coefficients = coefficients.clone();
144 let mut max_change = 0.0f64;
145
146 for i in 0..n {
147 let row = regularized_kernel.slice(s![i, ..]);
148 let dot_product = simd_dot_product(&row, &coefficients.view());
149 let sum = dot_product - regularized_kernel[[i, i]] * coefficients[i];
150
151 let new_val = (y[i] - sum) / regularized_kernel[[i, i]];
152 max_change = max_change.max((new_val - coefficients[i]).abs());
153 new_coefficients[i] = new_val;
154 }
155
156 coefficients = new_coefficients;
157 if max_change < tolerance {
158 break;
159 }
160 }
161
162 Ok(coefficients)
163}
164
165pub fn simd_nystroem_approximation(
169 x: &ArrayView2<f64>,
170 landmarks: &ArrayView2<f64>,
171 gamma: f64,
172) -> SklResult<Array2<f64>> {
173 let (n_samples, n_features) = x.dim();
174 let (n_landmarks, n_features_land) = landmarks.dim();
175
176 if n_features != n_features_land {
177 return Err(SklearsError::InvalidInput(
178 "Feature dimensions must match".to_string(),
179 ));
180 }
181
182 let mut kernel_features = Array2::<f64>::zeros((n_samples, n_landmarks));
183
184 for i in 0..n_samples {
185 let x_row = x.slice(s![i, ..]);
186
187 for j in 0..n_landmarks {
188 let landmark_row = landmarks.slice(s![j, ..]);
189 let squared_distance = simd_squared_euclidean_distance(&x_row, &landmark_row);
190 kernel_features[[i, j]] = (-gamma * squared_distance).exp();
191 }
192 }
193
194 Ok(kernel_features)
195}
196
197pub fn simd_rbf_random_features(
201 x: &ArrayView2<f64>,
202 random_weights: &ArrayView2<f64>,
203 random_offsets: &ArrayView1<f64>,
204 gamma: f64,
205) -> SklResult<Array2<f64>> {
206 let (n_samples, n_features) = x.dim();
207 let (n_features_w, n_components) = random_weights.dim();
208
209 if n_features != n_features_w || n_components != random_offsets.len() {
210 return Err(SklearsError::InvalidInput(
211 "Dimension mismatch in random features".to_string(),
212 ));
213 }
214
215 let mut features = Array2::<f64>::zeros((n_samples, n_components));
216 let sqrt_gamma = (2.0 * gamma).sqrt();
217 let normalization = (2.0 / n_components as f64).sqrt();
218
219 for i in 0..n_samples {
220 let x_row = x.slice(s![i, ..]);
221
222 for j in 0..n_components {
223 let weight_col = random_weights.slice(s![.., j]);
224 let projection = simd_dot_product(&x_row, &weight_col) * sqrt_gamma + random_offsets[j];
225 features[[i, j]] = normalization * projection.cos();
226 }
227 }
228
229 Ok(features)
230}
231
232pub fn simd_kernel_prediction(
236 x_test: &ArrayView2<f64>,
237 x_train: &ArrayView2<f64>,
238 coefficients: &ArrayView1<f64>,
239 gamma: f64,
240) -> SklResult<Array1<f64>> {
241 let (n_test, n_features) = x_test.dim();
242 let (n_train, n_features_train) = x_train.dim();
243
244 if n_features != n_features_train || n_train != coefficients.len() {
245 return Err(SklearsError::InvalidInput(
246 "Dimension mismatch in prediction".to_string(),
247 ));
248 }
249
250 let mut predictions = Array1::<f64>::zeros(n_test);
251
252 for i in 0..n_test {
253 let test_row = x_test.slice(s![i, ..]);
254 let mut prediction = 0.0;
255
256 for j in 0..n_train {
257 let train_row = x_train.slice(s![j, ..]);
258 let squared_distance = simd_squared_euclidean_distance(&test_row, &train_row);
259 let kernel_value = (-gamma * squared_distance).exp();
260 prediction += coefficients[j] * kernel_value;
261 }
262
263 predictions[i] = prediction;
264 }
265
266 Ok(predictions)
267}
268
269pub fn simd_kernel_diagonal(x: &ArrayView2<f64>, gamma: f64) -> Array1<f64> {
273 let (n_samples, _n_features) = x.dim();
274 let mut diagonal = Array1::<f64>::zeros(n_samples);
275
276 for i in 0..n_samples {
278 let x_row = x.slice(s![i, ..]);
279 let squared_norm = simd_squared_euclidean_distance(&x_row, &x_row);
280 diagonal[i] = (-gamma * squared_norm).exp();
281 }
282
283 diagonal
284}
285
286pub fn simd_center_kernel_matrix(kernel_matrix: &ArrayView2<f64>) -> Array2<f64> {
290 let (n, m) = kernel_matrix.dim();
291 if n != m {
292 return kernel_matrix.to_owned();
293 }
294
295 let mut centered = kernel_matrix.to_owned();
296
297 let mut row_means = Array1::<f64>::zeros(n);
299 for i in 0..n {
300 let row = kernel_matrix.slice(s![i, ..]);
301 row_means[i] = simd_mean(&row);
302 }
303
304 let mut col_means = Array1::<f64>::zeros(m);
306 for j in 0..m {
307 let col = kernel_matrix.slice(s![.., j]);
308 col_means[j] = simd_mean(&col);
309 }
310
311 let overall_mean = simd_mean(&row_means.view());
312
313 for i in 0..n {
315 for j in 0..m {
316 centered[[i, j]] = kernel_matrix[[i, j]] - row_means[i] - col_means[j] + overall_mean;
317 }
318 }
319
320 centered
321}
322
323pub fn simd_mean(data: &ArrayView1<f64>) -> f64 {
327 if data.is_empty() {
328 return 0.0;
329 }
330
331 Float::simd_mean(data)
332}
333
334pub fn simd_gram_matrix(x: &ArrayView2<f64>, gamma: f64) -> Array2<f64> {
338 let n_samples = x.nrows();
339 let mut gram = Array2::<f64>::zeros((n_samples, n_samples));
340
341 for i in 0..n_samples {
342 let x_i = x.slice(s![i, ..]);
343
344 for j in i..n_samples {
345 let x_j = x.slice(s![j, ..]);
346 let squared_distance = simd_squared_euclidean_distance(&x_i, &x_j);
347 let kernel_value = (-gamma * squared_distance).exp();
348
349 gram[[i, j]] = kernel_value;
350 if i != j {
351 gram[[j, i]] = kernel_value;
352 }
353 }
354 }
355
356 gram
357}
358
359pub fn simd_approximation_error(
363 true_kernel: &ArrayView2<f64>,
364 approx_kernel: &ArrayView2<f64>,
365) -> SklResult<f64> {
366 let (n, m) = true_kernel.dim();
367 let (n_approx, m_approx) = approx_kernel.dim();
368
369 if n != n_approx || m != m_approx {
370 return Err(SklearsError::InvalidInput(
371 "Kernel matrix dimensions must match".to_string(),
372 ));
373 }
374
375 let mut error_squared = 0.0f64;
376
377 for i in 0..n {
378 let true_row = true_kernel.slice(s![i, ..]);
379 let approx_row = approx_kernel.slice(s![i, ..]);
380
381 let row_error_squared = simd_squared_difference_sum(&true_row, &approx_row);
382 error_squared += row_error_squared;
383 }
384
385 Ok(error_squared.sqrt())
386}
387
388pub fn simd_squared_difference_sum(x: &ArrayView1<f64>, y: &ArrayView1<f64>) -> f64 {
392 if x.len() != y.len() {
393 return 0.0;
394 }
395
396 let diff = x.to_owned() - y.to_owned();
397 if let Some(diff_slice) = diff.as_slice() {
398 let norm = Float::simd_norm(&ArrayView1::from(diff_slice));
399 norm * norm
400 } else {
401 diff.mapv(|v| v * v).sum()
402 }
403}
404
405pub fn simd_gram_matrix_from_data(x: &ArrayView2<f64>) -> Array2<f64> {
410 let (_n_samples, n_features) = x.dim();
411 let mut gram = Array2::<f64>::zeros((n_features, n_features));
412
413 for i in 0..n_features {
414 for j in i..n_features {
415 let col_i = x.slice(s![.., i]);
416 let col_j = x.slice(s![.., j]);
417 let dot_product = simd_dot_product(&col_i, &col_j);
418
419 gram[[i, j]] = dot_product;
420 if i != j {
421 gram[[j, i]] = dot_product;
422 }
423 }
424 }
425
426 gram
427}
428
429pub fn simd_matrix_vector_multiply(
433 matrix: &ArrayView2<f64>,
434 vector: &ArrayView1<f64>,
435) -> SklResult<Array1<f64>> {
436 let (m, n) = matrix.dim();
437 if n != vector.len() {
438 return Err(SklearsError::InvalidInput(
439 "Matrix-vector dimension mismatch".to_string(),
440 ));
441 }
442
443 let mut result = Array1::<f64>::zeros(m);
444
445 for i in 0..m {
446 let row = matrix.slice(s![i, ..]);
447 result[i] = simd_dot_product(&row, vector);
448 }
449
450 Ok(result)
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456 use scirs2_core::ndarray::array;
457
458 #[test]
459 fn test_simd_dot_product() {
460 let x = array![1.0, 2.0, 3.0];
461 let y = array![4.0, 5.0, 6.0];
462
463 let result = simd_dot_product(&x.view(), &y.view());
464 let expected = 1.0 * 4.0 + 2.0 * 5.0 + 3.0 * 6.0;
465
466 assert!((result - expected).abs() < 1e-10);
467 }
468
469 #[test]
470 fn test_simd_squared_euclidean_distance() {
471 let x = array![0.0, 0.0];
472 let y = array![3.0, 4.0];
473
474 let result = simd_squared_euclidean_distance(&x.view(), &y.view());
475 let expected = 25.0; assert!((result - expected).abs() < 1e-10);
478 }
479
480 #[test]
481 fn test_simd_mean() {
482 let data = array![1.0, 2.0, 3.0, 4.0, 5.0];
483 let result = simd_mean(&data.view());
484 let expected = 3.0;
485
486 assert!((result - expected).abs() < 1e-10);
487 }
488
489 #[test]
490 fn test_simd_rbf_kernel() {
491 let x = array![[0.0, 0.0], [1.0, 1.0]];
492 let y = array![[0.0, 0.0], [2.0, 2.0]];
493
494 let result = simd_rbf_kernel(&x.view(), &y.view(), 0.5).unwrap();
495
496 assert_eq!(result.dim(), (2, 2));
497 assert!((result[[0, 0]] - 1.0).abs() < 1e-10);
499 }
500}