1use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
7use sklears_core::error::{Result, SklearsError};
8use std::collections::HashMap;
9use std::sync::Arc;
10
11pub trait Kernel: Send + Sync + std::fmt::Debug {
13 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64;
15
16 fn compute_matrix(&self, x: &Array2<f64>, y: &Array2<f64>) -> Array2<f64> {
18 let (n_x, _) = x.dim();
19 let (n_y, _) = y.dim();
20 let mut kernel_matrix = Array2::zeros((n_x, n_y));
21
22 for i in 0..n_x {
23 for j in 0..n_y {
24 kernel_matrix[[i, j]] = self.compute(x.row(i), y.row(j));
25 }
26 }
27
28 kernel_matrix
29 }
30
31 fn parameters(&self) -> HashMap<String, f64>;
33}
34
35#[derive(Debug, Clone, PartialEq)]
37pub enum KernelType {
38 Linear,
40 Rbf { gamma: f64 },
42 Polynomial { gamma: f64, coef0: f64, degree: f64 },
44 Sigmoid { gamma: f64, coef0: f64 },
46 Precomputed,
48 Custom(String),
50 Cosine,
52 ChiSquared { gamma: f64 },
54 Intersection,
56 Hellinger,
58 JensenShannon,
60 Periodic { length_scale: f64, period: f64 },
62}
63
64pub fn create_kernel(kernel_type: KernelType) -> Result<Box<dyn Kernel>> {
66 match kernel_type {
67 KernelType::Linear => Ok(Box::new(LinearKernel)),
68 KernelType::Rbf { gamma } => Ok(Box::new(RbfKernel { gamma })),
69 KernelType::Polynomial {
70 gamma,
71 coef0,
72 degree,
73 } => Ok(Box::new(PolynomialKernel {
74 gamma,
75 coef0,
76 degree,
77 })),
78 KernelType::Sigmoid { gamma, coef0 } => Ok(Box::new(SigmoidKernel { gamma, coef0 })),
79 KernelType::Cosine => Ok(Box::new(CosineKernel)),
80 KernelType::ChiSquared { gamma } => Ok(Box::new(ChiSquaredKernel { gamma })),
81 KernelType::Intersection => Ok(Box::new(IntersectionKernel)),
82 KernelType::Hellinger => Ok(Box::new(HellingerKernel)),
83 KernelType::JensenShannon => Ok(Box::new(JensenShannonKernel)),
84 KernelType::Periodic {
85 length_scale,
86 period,
87 } => Ok(Box::new(PeriodicKernel {
88 length_scale,
89 period,
90 })),
91 KernelType::Precomputed => Err(SklearsError::InvalidParameter {
92 name: "kernel_type".to_string(),
93 reason: "precomputed kernels must be created with data".to_string(),
94 }),
95 KernelType::Custom(name) => Err(SklearsError::InvalidParameter {
96 name: "kernel_type".to_string(),
97 reason: format!("custom kernel '{}' not implemented", name),
98 }),
99 }
100}
101
102impl<K: Kernel> Kernel for Arc<K> {
103 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
104 (**self).compute(x, y)
105 }
106
107 fn compute_matrix(&self, x: &Array2<f64>, y: &Array2<f64>) -> Array2<f64> {
108 (**self).compute_matrix(x, y)
109 }
110
111 fn parameters(&self) -> HashMap<String, f64> {
112 (**self).parameters()
113 }
114}
115
116#[derive(Debug, Clone)]
118pub struct LinearKernel;
119
120impl Default for LinearKernel {
121 fn default() -> Self {
122 Self::new()
123 }
124}
125
126impl LinearKernel {
127 pub fn new() -> Self {
128 Self
129 }
130}
131
132impl Kernel for LinearKernel {
133 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
134 x.dot(&y)
135 }
136
137 fn parameters(&self) -> HashMap<String, f64> {
138 HashMap::new()
139 }
140}
141
142#[derive(Debug, Clone)]
144pub struct RbfKernel {
145 pub gamma: f64,
146}
147
148impl RbfKernel {
149 pub fn new(gamma: f64) -> Self {
150 Self { gamma }
151 }
152}
153
154impl Kernel for RbfKernel {
155 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
156 let squared_distance: f64 = x
158 .iter()
159 .zip(y.iter())
160 .map(|(xi, yi)| {
161 let diff = xi - yi;
162 diff * diff
163 })
164 .sum();
165
166 (-self.gamma * squared_distance).exp()
167 }
168
169 fn parameters(&self) -> HashMap<String, f64> {
170 let mut params = HashMap::new();
171 params.insert("gamma".to_string(), self.gamma);
172 params
173 }
174}
175
176#[derive(Debug, Clone)]
178pub struct PolynomialKernel {
179 pub gamma: f64,
180 pub coef0: f64,
181 pub degree: f64,
182}
183
184impl PolynomialKernel {
185 pub fn new(gamma: f64, coef0: f64, degree: f64) -> Self {
186 Self {
187 gamma,
188 coef0,
189 degree,
190 }
191 }
192}
193
194impl Kernel for PolynomialKernel {
195 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
196 let dot_product = x.dot(&y);
197 (self.gamma * dot_product + self.coef0).powf(self.degree)
198 }
199
200 fn parameters(&self) -> HashMap<String, f64> {
201 let mut params = HashMap::new();
202 params.insert("gamma".to_string(), self.gamma);
203 params.insert("coef0".to_string(), self.coef0);
204 params.insert("degree".to_string(), self.degree);
205 params
206 }
207}
208
209#[derive(Debug, Clone)]
211pub struct SigmoidKernel {
212 pub gamma: f64,
213 pub coef0: f64,
214}
215
216impl SigmoidKernel {
217 pub fn new(gamma: f64, coef0: f64) -> Self {
218 Self { gamma, coef0 }
219 }
220}
221
222impl Kernel for SigmoidKernel {
223 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
224 let dot_product = x.dot(&y);
225 (self.gamma * dot_product + self.coef0).tanh()
226 }
227
228 fn parameters(&self) -> HashMap<String, f64> {
229 let mut params = HashMap::new();
230 params.insert("gamma".to_string(), self.gamma);
231 params.insert("coef0".to_string(), self.coef0);
232 params
233 }
234}
235
236#[derive(Debug, Clone)]
238pub struct CosineKernel;
239
240impl Kernel for CosineKernel {
241 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
242 let dot_product = x.dot(&y);
243 let x_norm = x.dot(&x).sqrt();
244 let y_norm = y.dot(&y).sqrt();
245
246 if x_norm == 0.0 || y_norm == 0.0 {
247 0.0
248 } else {
249 dot_product / (x_norm * y_norm)
250 }
251 }
252
253 fn parameters(&self) -> HashMap<String, f64> {
254 HashMap::new()
255 }
256}
257
258#[derive(Debug, Clone)]
260pub struct ChiSquaredKernel {
261 pub gamma: f64,
262}
263
264impl ChiSquaredKernel {
265 pub fn new(gamma: f64) -> Self {
266 Self { gamma }
267 }
268}
269
270impl Kernel for ChiSquaredKernel {
271 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
272 let chi_squared_distance = x
273 .iter()
274 .zip(y.iter())
275 .map(|(a, b)| {
276 if a + b > 0.0 {
277 (a - b).powi(2) / (a + b)
278 } else {
279 0.0
280 }
281 })
282 .sum::<f64>();
283
284 (-self.gamma * chi_squared_distance).exp()
285 }
286
287 fn parameters(&self) -> HashMap<String, f64> {
288 let mut params = HashMap::new();
289 params.insert("gamma".to_string(), self.gamma);
290 params
291 }
292}
293
294#[derive(Debug, Clone)]
296pub struct IntersectionKernel;
297
298impl Kernel for IntersectionKernel {
299 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
300 x.iter().zip(y.iter()).map(|(a, b)| a.min(*b)).sum()
301 }
302
303 fn parameters(&self) -> HashMap<String, f64> {
304 HashMap::new()
305 }
306}
307
308#[derive(Debug, Clone)]
310pub struct PeriodicKernel {
311 pub length_scale: f64,
312 pub period: f64,
313}
314
315impl PeriodicKernel {
316 pub fn new(length_scale: f64, period: f64) -> Self {
317 Self {
318 length_scale,
319 period,
320 }
321 }
322}
323
324impl Kernel for PeriodicKernel {
325 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
326 let sin_squared: f64 = x
328 .iter()
329 .zip(y.iter())
330 .map(|(xi, yi)| {
331 let diff = xi - yi;
332 let sin_val = (std::f64::consts::PI * diff / self.period).sin();
333 sin_val * sin_val
334 })
335 .sum();
336
337 (-2.0 * sin_squared / (self.length_scale * self.length_scale)).exp()
338 }
339
340 fn parameters(&self) -> HashMap<String, f64> {
341 let mut params = HashMap::new();
342 params.insert("length_scale".to_string(), self.length_scale);
343 params.insert("period".to_string(), self.period);
344 params
345 }
346}
347
348#[derive(Debug, Clone)]
350pub struct CustomKernel {
351 pub name: String,
352 pub function: fn(ArrayView1<f64>, ArrayView1<f64>) -> f64,
353}
354
355impl CustomKernel {
356 pub fn new(name: String, function: fn(ArrayView1<f64>, ArrayView1<f64>) -> f64) -> Self {
357 Self { name, function }
358 }
359}
360
361impl Kernel for CustomKernel {
362 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
363 (self.function)(x, y)
364 }
365
366 fn parameters(&self) -> HashMap<String, f64> {
367 HashMap::new()
368 }
369}
370
371#[derive(Debug, Clone)]
373pub struct KernelFunction {
374 kernel_type: KernelType,
375}
376
377impl KernelFunction {
378 pub fn new(kernel_type: KernelType) -> Self {
379 Self { kernel_type }
380 }
381
382 pub fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
383 match &self.kernel_type {
384 KernelType::Linear => LinearKernel.compute(x, y),
385 KernelType::Rbf { gamma } => RbfKernel::new(*gamma).compute(x, y),
386 KernelType::Polynomial {
387 gamma,
388 coef0,
389 degree,
390 } => PolynomialKernel::new(*gamma, *coef0, *degree).compute(x, y),
391 KernelType::Sigmoid { gamma, coef0 } => {
392 SigmoidKernel::new(*gamma, *coef0).compute(x, y)
393 }
394 KernelType::Cosine => CosineKernel.compute(x, y),
395 KernelType::ChiSquared { gamma } => ChiSquaredKernel::new(*gamma).compute(x, y),
396 KernelType::Intersection => IntersectionKernel.compute(x, y),
397 KernelType::Periodic {
398 length_scale,
399 period,
400 } => PeriodicKernel::new(*length_scale, *period).compute(x, y),
401 KernelType::Precomputed => {
402 panic!(
409 "precomputed kernel cannot be evaluated pointwise; supply the \
410 kernel matrix and index it by sample position instead"
411 )
412 }
413 KernelType::Custom(_name) => {
414 x.dot(&y)
416 }
417 KernelType::Hellinger => {
418 let x_normalized = normalize_vector(&x.to_owned());
420 let y_normalized = normalize_vector(&y.to_owned());
421 x_normalized
422 .iter()
423 .zip(y_normalized.iter())
424 .map(|(a, b)| (a * b).sqrt())
425 .sum::<f64>()
426 .sqrt()
427 }
428 KernelType::JensenShannon => {
429 let x_normalized = normalize_vector(&x.to_owned());
431 let y_normalized = normalize_vector(&y.to_owned());
432
433 let mut js_divergence = 0.0;
434 for i in 0..x_normalized.len() {
435 let p = x_normalized[i];
436 let q = y_normalized[i];
437 let m = (p + q) / 2.0;
438
439 if p > 0.0 && m > 0.0 {
440 js_divergence += p * (p / m).ln();
441 }
442 if q > 0.0 && m > 0.0 {
443 js_divergence += q * (q / m).ln();
444 }
445 }
446 js_divergence /= 2.0;
447
448 (-js_divergence).exp()
449 }
450 }
451 }
452
453 pub fn compute_matrix(&self, x: &Array2<f64>, y: &Array2<f64>) -> Array2<f64> {
454 let (n_x, _) = x.dim();
455 let (n_y, _) = y.dim();
456 let mut kernel_matrix = Array2::zeros((n_x, n_y));
457
458 for i in 0..n_x {
459 for j in 0..n_y {
460 kernel_matrix[[i, j]] = self.compute(x.row(i), y.row(j));
461 }
462 }
463
464 kernel_matrix
465 }
466
467 pub fn kernel_type(&self) -> &KernelType {
468 &self.kernel_type
469 }
470}
471
472fn normalize_vector(vec: &Array1<f64>) -> Array1<f64> {
474 let sum: f64 = vec.iter().sum();
475 if sum == 0.0 {
476 vec.clone()
477 } else {
478 vec / sum
479 }
480}
481
482#[derive(Debug, Clone)]
484pub struct Graph {
485 pub adjacency_matrix: Array2<f64>,
486 pub node_labels: Option<Array1<usize>>,
487 pub edge_labels: Option<Array2<usize>>,
488}
489
490impl Graph {
491 pub fn new(adjacency_matrix: Array2<f64>) -> Self {
492 Self {
493 adjacency_matrix,
494 node_labels: None,
495 edge_labels: None,
496 }
497 }
498
499 pub fn with_node_labels(mut self, labels: Array1<usize>) -> Self {
500 self.node_labels = Some(labels);
501 self
502 }
503
504 pub fn with_edge_labels(mut self, labels: Array2<usize>) -> Self {
505 self.edge_labels = Some(labels);
506 self
507 }
508}
509
510#[derive(Debug, Clone)]
512pub struct RandomWalkKernel {
513 pub lambda: f64, pub max_steps: usize,
515}
516
517impl RandomWalkKernel {
518 pub fn new(lambda: f64, max_steps: usize) -> Self {
519 Self { lambda, max_steps }
520 }
521
522 pub fn compute_graph_kernel(&self, g1: &Graph, g2: &Graph) -> f64 {
539 let n1 = g1.adjacency_matrix.nrows();
540 let n2 = g2.adjacency_matrix.nrows();
541 let n = n1 * n2;
542
543 if n == 0 {
544 return 0.0;
545 }
546
547 let a1 = &g1.adjacency_matrix;
549 let a2 = &g2.adjacency_matrix;
550 let mut m = Array2::<f64>::zeros((n, n));
551 for a in 0..n1 {
552 for c in 0..n1 {
553 let a1_ac = a1[[a, c]];
554 if a1_ac == 0.0 {
555 continue;
556 }
557 for b in 0..n2 {
558 for d in 0..n2 {
559 let a2_bd = a2[[b, d]];
560 if a2_bd == 0.0 {
561 continue;
562 }
563 let row = a * n2 + b;
564 let col = c * n2 + d;
565 m[[row, col]] = -self.lambda * a1_ac * a2_bd;
566 }
567 }
568 }
569 }
570 for i in 0..n {
571 m[[i, i]] += 1.0;
572 }
573
574 let mut rhs = Array1::<f64>::from_elem(n, 1.0);
576 match gaussian_solve(&mut m, &mut rhs) {
577 Some(s) => s.sum(),
578 None => 0.0,
579 }
580 }
581}
582
583fn gaussian_solve(a: &mut Array2<f64>, b: &mut Array1<f64>) -> Option<Array1<f64>> {
587 let n = a.nrows();
588 if n == 0 || a.ncols() != n || b.len() != n {
589 return None;
590 }
591
592 for col in 0..n {
593 let mut pivot_row = col;
595 let mut pivot_val = a[[col, col]].abs();
596 for row in (col + 1)..n {
597 let val = a[[row, col]].abs();
598 if val > pivot_val {
599 pivot_val = val;
600 pivot_row = row;
601 }
602 }
603
604 if pivot_val < 1e-12 {
605 return None; }
607
608 if pivot_row != col {
609 for k in 0..n {
610 let tmp = a[[col, k]];
611 a[[col, k]] = a[[pivot_row, k]];
612 a[[pivot_row, k]] = tmp;
613 }
614 b.swap(col, pivot_row);
615 }
616
617 let pivot = a[[col, col]];
619 for row in (col + 1)..n {
620 let factor = a[[row, col]] / pivot;
621 if factor == 0.0 {
622 continue;
623 }
624 for k in col..n {
625 let sub = factor * a[[col, k]];
626 a[[row, k]] -= sub;
627 }
628 b[row] -= factor * b[col];
629 }
630 }
631
632 let mut x = Array1::<f64>::zeros(n);
634 for row in (0..n).rev() {
635 let mut sum = b[row];
636 for k in (row + 1)..n {
637 sum -= a[[row, k]] * x[k];
638 }
639 let diag = a[[row, row]];
640 if diag.abs() < 1e-12 {
641 return None;
642 }
643 x[row] = sum / diag;
644 }
645
646 Some(x)
647}
648
649#[derive(Debug)]
651pub struct HellingerKernel;
652
653impl Kernel for HellingerKernel {
654 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
655 x.iter()
657 .zip(y.iter())
658 .map(|(xi, yi)| (xi * yi).sqrt())
659 .sum()
660 }
661
662 fn parameters(&self) -> HashMap<String, f64> {
663 HashMap::new()
664 }
665}
666
667#[derive(Debug)]
669pub struct JensenShannonKernel;
670
671impl JensenShannonKernel {
672 fn jensen_shannon_divergence(&self, p: ArrayView1<f64>, q: ArrayView1<f64>) -> f64 {
673 let m: Vec<f64> = p
675 .iter()
676 .zip(q.iter())
677 .map(|(pi, qi)| 0.5 * (pi + qi))
678 .collect();
679 let m = Array1::from_vec(m);
680
681 let kl_pm = self.kl_divergence(p, m.view());
682 let kl_qm = self.kl_divergence(q, m.view());
683
684 0.5 * kl_pm + 0.5 * kl_qm
685 }
686
687 fn kl_divergence(&self, p: ArrayView1<f64>, q: ArrayView1<f64>) -> f64 {
688 p.iter()
690 .zip(q.iter())
691 .map(|(pi, qi)| {
692 if *pi > 0.0 && *qi > 0.0 {
693 pi * (pi / qi).ln()
694 } else {
695 0.0
696 }
697 })
698 .sum()
699 }
700}
701
702impl Kernel for JensenShannonKernel {
703 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
704 let js_div = self.jensen_shannon_divergence(x, y);
706 (-js_div).exp()
707 }
708
709 fn parameters(&self) -> HashMap<String, f64> {
710 HashMap::new()
711 }
712}
713
714impl Kernel for KernelType {
716 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
717 match self {
718 KernelType::Linear => LinearKernel.compute(x, y),
719 KernelType::Rbf { gamma } => RbfKernel::new(*gamma).compute(x, y),
720 KernelType::Polynomial {
721 gamma,
722 coef0,
723 degree,
724 } => PolynomialKernel::new(*gamma, *coef0, *degree).compute(x, y),
725 KernelType::Sigmoid { gamma, coef0 } => {
726 SigmoidKernel::new(*gamma, *coef0).compute(x, y)
727 }
728 KernelType::Cosine => CosineKernel.compute(x, y),
729 KernelType::ChiSquared { gamma } => ChiSquaredKernel::new(*gamma).compute(x, y),
730 KernelType::Intersection => IntersectionKernel.compute(x, y),
731 KernelType::Hellinger => HellingerKernel.compute(x, y),
732 KernelType::JensenShannon => JensenShannonKernel.compute(x, y),
733 KernelType::Periodic {
734 length_scale,
735 period,
736 } => PeriodicKernel::new(*length_scale, *period).compute(x, y),
737 KernelType::Precomputed => {
738 panic!(
742 "precomputed kernel cannot be evaluated pointwise; supply the \
743 kernel matrix and index it by sample position instead"
744 )
745 }
746 KernelType::Custom(_name) => {
747 x.dot(&y)
749 }
750 }
751 }
752
753 fn parameters(&self) -> HashMap<String, f64> {
754 match self {
755 KernelType::Linear => HashMap::new(),
756 KernelType::Rbf { gamma } => {
757 let mut params = HashMap::new();
758 params.insert("gamma".to_string(), *gamma);
759 params
760 }
761 KernelType::Polynomial {
762 gamma,
763 coef0,
764 degree,
765 } => {
766 let mut params = HashMap::new();
767 params.insert("gamma".to_string(), *gamma);
768 params.insert("coef0".to_string(), *coef0);
769 params.insert("degree".to_string(), *degree);
770 params
771 }
772 KernelType::Sigmoid { gamma, coef0 } => {
773 let mut params = HashMap::new();
774 params.insert("gamma".to_string(), *gamma);
775 params.insert("coef0".to_string(), *coef0);
776 params
777 }
778 KernelType::Cosine => HashMap::new(),
779 KernelType::ChiSquared { gamma } => {
780 let mut params = HashMap::new();
781 params.insert("gamma".to_string(), *gamma);
782 params
783 }
784 KernelType::Intersection => HashMap::new(),
785 KernelType::Hellinger => HashMap::new(),
786 KernelType::JensenShannon => HashMap::new(),
787 KernelType::Periodic {
788 length_scale,
789 period,
790 } => {
791 let mut params = HashMap::new();
792 params.insert("length_scale".to_string(), *length_scale);
793 params.insert("period".to_string(), *period);
794 params
795 }
796 KernelType::Precomputed => HashMap::new(),
797 KernelType::Custom(_name) => HashMap::new(),
798 }
799 }
800}
801
802impl Kernel for Box<dyn Kernel> {
804 fn compute(&self, x: ArrayView1<f64>, y: ArrayView1<f64>) -> f64 {
805 self.as_ref().compute(x, y)
806 }
807
808 fn compute_matrix(&self, x: &Array2<f64>, y: &Array2<f64>) -> Array2<f64> {
809 self.as_ref().compute_matrix(x, y)
810 }
811
812 fn parameters(&self) -> HashMap<String, f64> {
813 self.as_ref().parameters()
814 }
815}
816
817#[allow(non_snake_case)]
818#[cfg(test)]
819mod tests {
820 use super::*;
821 use approx::assert_abs_diff_eq;
822
823 #[test]
824 fn test_linear_kernel() {
825 let kernel = LinearKernel;
826 let x = Array1::from_vec(vec![1.0, 2.0, 3.0]);
827 let y = Array1::from_vec(vec![4.0, 5.0, 6.0]);
828
829 let result = kernel.compute(x.view(), y.view());
830 assert_abs_diff_eq!(result, 32.0, epsilon = 1e-10);
831 }
832
833 #[test]
834 fn test_rbf_kernel() {
835 let kernel = RbfKernel::new(1.0);
836 let x = Array1::from_vec(vec![1.0, 2.0]);
837 let y = Array1::from_vec(vec![1.0, 2.0]);
838
839 let result = kernel.compute(x.view(), y.view());
840 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
841 }
842
843 #[test]
844 fn test_polynomial_kernel() {
845 let kernel = PolynomialKernel::new(1.0, 1.0, 2.0);
846 let x = Array1::from_vec(vec![1.0, 2.0]);
847 let y = Array1::from_vec(vec![3.0, 4.0]);
848
849 let result = kernel.compute(x.view(), y.view());
850 let expected = (1.0_f64 * (1.0 * 3.0 + 2.0 * 4.0) + 1.0).powf(2.0);
851 assert_abs_diff_eq!(result, expected, epsilon = 1e-10);
852 }
853
854 #[test]
855 fn test_cosine_kernel() {
856 let kernel = CosineKernel;
857 let x = Array1::from_vec(vec![1.0, 0.0]);
858 let y = Array1::from_vec(vec![0.0, 1.0]);
859
860 let result = kernel.compute(x.view(), y.view());
861 assert_abs_diff_eq!(result, 0.0, epsilon = 1e-10);
862 }
863
864 #[test]
865 fn test_kernel_function() {
866 let kernel_fn = KernelFunction::new(KernelType::Rbf { gamma: 0.5 });
867 let x = Array1::from_vec(vec![1.0, 2.0]);
868 let y = Array1::from_vec(vec![1.0, 2.0]);
869
870 let result = kernel_fn.compute(x.view(), y.view());
871 assert_abs_diff_eq!(result, 1.0, epsilon = 1e-10);
872 }
873
874 #[test]
875 fn test_kernel_matrix() {
876 let kernel_fn = KernelFunction::new(KernelType::Linear);
877 let x =
878 Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0]).expect("array shape mismatch");
879 let y =
880 Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0]).expect("array shape mismatch");
881
882 let kernel_matrix = kernel_fn.compute_matrix(&x, &y);
883
884 assert_eq!(kernel_matrix.dim(), (2, 2));
885 assert_abs_diff_eq!(kernel_matrix[[0, 0]], 5.0, epsilon = 1e-10); assert_abs_diff_eq!(kernel_matrix[[1, 1]], 25.0, epsilon = 1e-10); }
888}