oxicuda_quantum/kernel/
quantum_kernel.rs1use crate::embedding::zz_feature::zz_feature_map;
2use crate::error::{QuantumError, QuantumResult};
3
4pub fn overlap_kernel(x: &[f32], y: &[f32]) -> QuantumResult<f32> {
8 if x.len() != y.len() {
9 return Err(QuantumError::DimensionMismatch {
10 expected: x.len(),
11 got: y.len(),
12 });
13 }
14 if x.is_empty() {
15 return Err(QuantumError::EmptyInput);
16 }
17
18 let psi_x = zz_feature_map(x, 1)?;
19 let psi_y = zz_feature_map(y, 1)?;
20
21 let ip = psi_x.inner_product(&psi_y)?;
22 Ok(ip.norm_sqr())
23}
24
25pub fn kernel_matrix(xs: &[Vec<f32>]) -> QuantumResult<Vec<Vec<f32>>> {
27 if xs.is_empty() {
28 return Err(QuantumError::EmptyInput);
29 }
30 let n = xs.len();
31 let mut mat = vec![vec![0.0_f32; n]; n];
32
33 for i in 0..n {
34 for j in i..n {
35 let k = overlap_kernel(&xs[i], &xs[j])?;
36 mat[i][j] = k;
37 mat[j][i] = k;
38 }
39 }
40
41 Ok(mat)
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47
48 #[test]
49 fn self_kernel_is_one() {
50 let x = vec![0.5_f32, 1.0];
51 let k = overlap_kernel(&x, &x)
52 .expect("self-overlap of a non-empty, same-length vector pair cannot fail");
53 assert!((k - 1.0).abs() < 1e-4, "k={k}");
54 }
55
56 #[test]
57 fn kernel_matrix_diagonal_is_one() {
58 let xs = vec![vec![0.3_f32, 0.7], vec![1.0_f32, -0.5]];
59 let mat = kernel_matrix(&xs)
60 .expect("kernel matrix of non-empty, equal-length vectors cannot fail");
61 assert!((mat[0][0] - 1.0).abs() < 1e-4);
62 assert!((mat[1][1] - 1.0).abs() < 1e-4);
63 }
64
65 #[test]
66 fn gram_matrix_is_psd() {
67 use crate::handle::LcgRng;
71 let xs = vec![
72 vec![0.3_f32, 0.7],
73 vec![1.0_f32, -0.5],
74 vec![-0.2_f32, 0.9],
75 vec![0.6_f32, 0.1],
76 vec![-1.1_f32, 0.4],
77 ];
78 let mat = kernel_matrix(&xs).expect("kernel matrix");
79 let m = xs.len();
80 for (i, row) in mat.iter().enumerate() {
82 for (j, &v) in row.iter().enumerate() {
83 assert!((v - mat[j][i]).abs() < 1e-5, "asymmetry at ({i},{j})");
84 }
85 }
86 let mut rng = LcgRng::new(2024);
88 for _ in 0..50 {
89 let v: Vec<f32> = (0..m).map(|_| rng.next_normal()).collect();
90 let mut quad = 0.0_f32;
91 for (i, row) in mat.iter().enumerate() {
92 for (j, &kij) in row.iter().enumerate() {
93 quad += v[i] * kij * v[j];
94 }
95 }
96 assert!(quad >= -1e-3, "quadratic form negative: {quad}");
97 }
98 }
99}