1use crate::error::{MLError, Result};
8use quantrs2_circuit::builder::Simulator;
9use quantrs2_circuit::prelude::Circuit;
10use quantrs2_sim::statevector::StateVectorSimulator;
11use scirs2_core::ndarray::{Array1, Array2};
12
13#[derive(Debug, Clone, Copy)]
15pub enum KernelMethod {
16 Linear,
18
19 Polynomial,
21
22 RBF,
24
25 QuantumKernel,
27
28 HybridKernel,
30}
31
32pub trait KernelFunction {
34 fn compute(&self, x1: &Array1<f64>, x2: &Array1<f64>) -> Result<f64>;
36
37 fn compute_matrix(&self, x: &Array2<f64>) -> Result<Array2<f64>> {
39 let n = x.nrows();
40 let mut kernel_matrix = Array2::zeros((n, n));
41
42 for i in 0..n {
43 let x_i = x.row(i).to_owned();
44
45 for j in 0..=i {
46 let x_j = x.row(j).to_owned();
47
48 let k_ij = self.compute(&x_i, &x_j)?;
49 kernel_matrix[[i, j]] = k_ij;
50
51 if i != j {
52 kernel_matrix[[j, i]] = k_ij; }
54 }
55 }
56
57 Ok(kernel_matrix)
58 }
59}
60
61#[derive(Debug, Clone)]
63pub struct LinearKernel;
64
65impl KernelFunction for LinearKernel {
66 fn compute(&self, x1: &Array1<f64>, x2: &Array1<f64>) -> Result<f64> {
67 if x1.len() != x2.len() {
68 return Err(MLError::InvalidParameter(format!(
69 "Vector dimensions mismatch: {} != {}",
70 x1.len(),
71 x2.len()
72 )));
73 }
74
75 let dot_product = x1.iter().zip(x2.iter()).map(|(&a, &b)| a * b).sum();
76
77 Ok(dot_product)
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct PolynomialKernel {
84 pub degree: usize,
86
87 pub coef: f64,
89}
90
91impl PolynomialKernel {
92 pub fn new(degree: usize, coef: f64) -> Self {
94 PolynomialKernel { degree, coef }
95 }
96}
97
98impl KernelFunction for PolynomialKernel {
99 fn compute(&self, x1: &Array1<f64>, x2: &Array1<f64>) -> Result<f64> {
100 if x1.len() != x2.len() {
101 return Err(MLError::InvalidParameter(format!(
102 "Vector dimensions mismatch: {} != {}",
103 x1.len(),
104 x2.len()
105 )));
106 }
107
108 let dot_product = x1.iter().zip(x2.iter()).map(|(&a, &b)| a * b).sum::<f64>();
109 let value = (dot_product + self.coef).powi(self.degree as i32);
110
111 Ok(value)
112 }
113}
114
115#[derive(Debug, Clone)]
117pub struct RBFKernel {
118 pub gamma: f64,
120}
121
122impl RBFKernel {
123 pub fn new(gamma: f64) -> Self {
125 RBFKernel { gamma }
126 }
127}
128
129impl KernelFunction for RBFKernel {
130 fn compute(&self, x1: &Array1<f64>, x2: &Array1<f64>) -> Result<f64> {
131 if x1.len() != x2.len() {
132 return Err(MLError::InvalidParameter(format!(
133 "Vector dimensions mismatch: {} != {}",
134 x1.len(),
135 x2.len()
136 )));
137 }
138
139 let squared_distance = x1
140 .iter()
141 .zip(x2.iter())
142 .map(|(&a, &b)| (a - b).powi(2))
143 .sum::<f64>();
144
145 let value = (-self.gamma * squared_distance).exp();
146
147 Ok(value)
148 }
149}
150
151#[derive(Debug, Clone)]
153pub struct QuantumKernel {
154 pub num_qubits: usize,
156
157 pub feature_dim: usize,
159
160 pub num_measurements: usize,
162}
163
164impl QuantumKernel {
165 pub fn new(num_qubits: usize, feature_dim: usize) -> Self {
167 QuantumKernel {
168 num_qubits,
169 feature_dim,
170 num_measurements: 1000,
171 }
172 }
173
174 pub fn with_measurements(mut self, num_measurements: usize) -> Self {
176 self.num_measurements = num_measurements;
177 self
178 }
179
180 fn encode_features<const N: usize>(
182 &self,
183 features: &Array1<f64>,
184 circuit: &mut Circuit<N>,
185 ) -> Result<()> {
186 for i in 0..N.min(features.len()) {
190 let angle = features[i] * std::f64::consts::PI;
191 circuit.ry(i, angle)?;
192 }
193
194 Ok(())
195 }
196
197 fn prepare_kernel_circuit<const N: usize>(
199 &self,
200 x1: &Array1<f64>,
201 x2: &Array1<f64>,
202 ) -> Result<Circuit<N>> {
203 let mut circuit = Circuit::<N>::new();
204
205 for i in 0..N.min(self.num_qubits) {
207 circuit.h(i)?;
208 }
209
210 self.encode_features(x1, &mut circuit)?;
212
213 for i in 0..N.min(self.num_qubits) {
215 circuit.x(i)?;
216 }
217
218 self.encode_features(x2, &mut circuit)?;
220
221 for i in 0..N.min(self.num_qubits) {
223 circuit.h(i)?;
224 }
225
226 Ok(circuit)
227 }
228}
229
230impl QuantumKernel {
231 fn compute_sized<const N: usize>(&self, x1: &Array1<f64>, x2: &Array1<f64>) -> Result<f64> {
239 let circuit = self.prepare_kernel_circuit::<N>(x1, x2)?;
240 let simulator = StateVectorSimulator::new();
241 let register = simulator.run(&circuit)?;
242
243 let amplitude_zero = register.amplitudes()[0];
245 Ok(amplitude_zero.norm_sqr().clamp(0.0, 1.0))
246 }
247}
248
249impl KernelFunction for QuantumKernel {
250 fn compute(&self, x1: &Array1<f64>, x2: &Array1<f64>) -> Result<f64> {
251 if x1.len() != x2.len() {
252 return Err(MLError::InvalidParameter(format!(
253 "Vector dimensions mismatch: {} != {}",
254 x1.len(),
255 x2.len()
256 )));
257 }
258
259 if x1.len() != self.feature_dim {
260 return Err(MLError::InvalidParameter(format!(
261 "Feature dimension mismatch: {} != {}",
262 x1.len(),
263 self.feature_dim
264 )));
265 }
266
267 match self.num_qubits {
272 0 => Err(MLError::InvalidConfiguration(
273 "QuantumKernel requires at least one qubit".to_string(),
274 )),
275 1..=2 => self.compute_sized::<2>(x1, x2),
276 3..=4 => self.compute_sized::<4>(x1, x2),
277 5..=8 => self.compute_sized::<8>(x1, x2),
278 9..=16 => self.compute_sized::<16>(x1, x2),
279 n => Err(MLError::NotSupported(format!(
280 "Quantum kernel estimation supports at most 16 qubits on the \
281 state-vector backend, got {n}"
282 ))),
283 }
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290 use scirs2_core::ndarray::array;
291
292 #[test]
293 fn test_quantum_kernel_exploration() {
294 let kernel = QuantumKernel::new(2, 2);
295 let x = array![0.3, 0.5];
296 let y = array![0.3, 0.5];
297 let z = array![0.9, 0.1];
298
299 let k_xx = kernel.compute(&x, &x).expect("compute self");
300 let k_xy = kernel.compute(&x, &y).expect("compute identical");
301 let k_xz = kernel.compute(&x, &z).expect("compute distinct");
302 let k_yx = kernel.compute(&y, &x).expect("compute swapped");
303 eprintln!("k_xx={k_xx} k_xy={k_xy} k_xz={k_xz} k_yx={k_yx}");
304 }
305}