1use scirs2_core::ndarray::{Array1, Array2};
7use scirs2_core::Complex64;
8use std::collections::HashMap;
9use std::f64::consts::PI;
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum FeatureMapType {
14 ZFeatureMap,
16 ZZFeatureMap,
18 PauliFeatureMap,
20 AngleEncoding,
22 AmplitudeEncoding,
24}
25
26#[derive(Debug, Clone)]
28pub struct QSVMParams {
29 pub feature_map: FeatureMapType,
31 pub reps: usize,
33 pub c: f64,
35 pub tolerance: f64,
37 pub num_qubits: usize,
39 pub depth: usize,
41 pub gamma: Option<f64>,
43 pub regularization: f64,
45 pub max_iterations: usize,
47 pub seed: Option<u64>,
49}
50
51impl Default for QSVMParams {
52 fn default() -> Self {
53 Self {
54 feature_map: FeatureMapType::ZZFeatureMap,
55 reps: 2,
56 c: 1.0,
57 tolerance: 1e-3,
58 num_qubits: 4,
59 depth: 2,
60 gamma: None,
61 regularization: 1.0,
62 max_iterations: 1000,
63 seed: None,
64 }
65 }
66}
67
68impl QSVMParams {
69 pub fn c_parameter(&self) -> f64 {
71 self.c
72 }
73
74 pub fn set_c_parameter(&mut self, value: f64) {
76 self.c = value;
77 self.regularization = value;
78 }
79}
80
81pub struct QuantumKernel {
83 feature_map: FeatureMapType,
84 reps: usize,
85}
86
87impl QuantumKernel {
88 pub fn new(feature_map: FeatureMapType, reps: usize) -> Self {
90 Self { feature_map, reps }
91 }
92
93 pub fn compute(&self, x1: &Array1<f64>, x2: &Array1<f64>) -> f64 {
95 match self.feature_map {
96 FeatureMapType::ZFeatureMap => self.z_feature_map_kernel(x1, x2),
97 FeatureMapType::ZZFeatureMap => self.zz_feature_map_kernel(x1, x2),
98 FeatureMapType::PauliFeatureMap => self.zz_feature_map_kernel(x1, x2), FeatureMapType::AngleEncoding => self.angle_encoding_kernel(x1, x2),
100 FeatureMapType::AmplitudeEncoding => self.amplitude_encoding_kernel(x1, x2),
101 }
102 }
103
104 fn z_feature_map_kernel(&self, x1: &Array1<f64>, x2: &Array1<f64>) -> f64 {
106 let n = x1.len();
107 let mut kernel_val = 1.0;
108
109 for _ in 0..self.reps {
110 for i in 0..n {
111 let phase_diff = (x1[i] - x2[i]) * PI;
112 kernel_val *= phase_diff.cos();
113 }
114 }
115
116 kernel_val
117 }
118
119 fn zz_feature_map_kernel(&self, x1: &Array1<f64>, x2: &Array1<f64>) -> f64 {
121 let n = x1.len();
122 let mut kernel_val = 1.0;
123
124 for rep in 0..self.reps {
125 for i in 0..n {
127 let phase_diff = (x1[i] - x2[i]) * PI * (rep + 1) as f64;
128 kernel_val *= phase_diff.cos();
129 }
130
131 for i in 0..n - 1 {
133 let interaction = (x1[i] - x2[i]) * (x1[i + 1] - x2[i + 1]) * PI;
134 kernel_val *= interaction.cos();
135 }
136 }
137
138 kernel_val
139 }
140
141 fn angle_encoding_kernel(&self, x1: &Array1<f64>, x2: &Array1<f64>) -> f64 {
143 let mut sum = 0.0;
144 for i in 0..x1.len() {
145 sum += (x1[i] - x2[i]).powi(2);
146 }
147 (-sum / 2.0).exp()
148 }
149
150 fn amplitude_encoding_kernel(&self, x1: &Array1<f64>, x2: &Array1<f64>) -> f64 {
152 let norm1 = x1.dot(x1).sqrt();
154 let norm2 = x2.dot(x2).sqrt();
155
156 if norm1 < 1e-10 || norm2 < 1e-10 {
157 return 0.0;
158 }
159
160 let x1_norm = x1 / norm1;
161 let x2_norm = x2 / norm2;
162
163 x1_norm.dot(&x2_norm).powi(2)
165 }
166
167 pub fn compute_kernel_matrix(&self, data: &Array2<f64>) -> Array2<f64> {
169 let n_samples = data.nrows();
170 let mut kernel_matrix = Array2::zeros((n_samples, n_samples));
171
172 for i in 0..n_samples {
173 for j in i..n_samples {
174 let kernel_val = self.compute(&data.row(i).to_owned(), &data.row(j).to_owned());
175 kernel_matrix[[i, j]] = kernel_val;
176 kernel_matrix[[j, i]] = kernel_val; }
178 }
179
180 kernel_matrix
181 }
182}
183
184pub struct QSVM {
186 params: QSVMParams,
187 kernel: QuantumKernel,
188 support_vectors: Option<Array2<f64>>,
189 support_labels: Option<Array1<i32>>,
190 alphas: Option<Array1<f64>>,
191 bias: f64,
192 kernel_matrix_cache: HashMap<(usize, usize), f64>,
193}
194
195impl QSVM {
196 pub fn new(params: QSVMParams) -> Self {
198 let kernel = QuantumKernel::new(params.feature_map, params.reps);
199 Self {
200 params,
201 kernel,
202 support_vectors: None,
203 support_labels: None,
204 alphas: None,
205 bias: 0.0,
206 kernel_matrix_cache: HashMap::new(),
207 }
208 }
209
210 pub fn fit(&mut self, x: &Array2<f64>, y: &Array1<i32>) -> Result<(), String> {
212 let n_samples = x.nrows();
213
214 for &label in y.iter() {
216 if label != -1 && label != 1 {
217 return Err("QSVM requires binary labels: -1 or 1".to_string());
218 }
219 }
220
221 let mut alphas = Array1::zeros(n_samples);
223
224 let kernel_matrix = self.kernel.compute_kernel_matrix(x);
226
227 let mut converged = false;
229 let mut iter = 0;
230
231 while !converged && iter < self.params.max_iterations {
232 let old_alphas = alphas.clone();
233
234 for i in 0..n_samples {
236 let ei = self.compute_error(&kernel_matrix, &alphas, y, i);
238
239 if !self.check_kkt(alphas[i], y[i], ei) {
241 let j = self.select_second_alpha(i, n_samples, &kernel_matrix, &alphas, y);
243 if i == j {
244 continue;
245 }
246
247 let ej = self.compute_error(&kernel_matrix, &alphas, y, j);
249
250 let alpha_i_old = alphas[i];
252 let alpha_j_old = alphas[j];
253
254 let (l, h) = self.compute_bounds(y[i], y[j], alphas[i], alphas[j]);
256
257 if (h - l).abs() < 1e-10 {
258 continue;
259 }
260
261 let eta =
263 kernel_matrix[[i, i]] + kernel_matrix[[j, j]] - 2.0 * kernel_matrix[[i, j]];
264
265 if eta <= 0.0 {
266 continue;
267 }
268
269 alphas[j] += y[j] as f64 * (ei - ej) / eta;
271 alphas[j] = alphas[j].clamp(l, h);
272
273 if (alphas[j] - alpha_j_old).abs() < 1e-5 {
274 continue;
275 }
276
277 alphas[i] += y[i] as f64 * y[j] as f64 * (alpha_j_old - alphas[j]);
279 }
280 }
281
282 let alpha_change: f64 = (&alphas - &old_alphas).mapv(|a| a.abs()).sum();
284 converged = alpha_change < self.params.tolerance;
285 iter += 1;
286 }
287
288 let mut support_indices = Vec::new();
290 let mut support_alphas = Vec::new();
291
292 for i in 0..n_samples {
293 if alphas[i] > 1e-5 {
294 support_indices.push(i);
295 support_alphas.push(alphas[i]);
296 }
297 }
298
299 if support_indices.is_empty() {
300 return Err("No support vectors found".to_string());
301 }
302
303 let n_support = support_indices.len();
305 let n_features = x.ncols();
306 let mut support_vectors = Array2::zeros((n_support, n_features));
307 let mut support_labels = Array1::zeros(n_support);
308
309 for (idx, &i) in support_indices.iter().enumerate() {
310 support_vectors.row_mut(idx).assign(&x.row(i));
311 support_labels[idx] = y[i];
312 }
313
314 self.support_vectors = Some(support_vectors);
315 self.support_labels = Some(support_labels);
316 self.alphas = Some(Array1::from_vec(support_alphas));
317
318 self.compute_bias(&kernel_matrix, &alphas, y, &support_indices);
320
321 Ok(())
322 }
323
324 fn compute_error(
326 &self,
327 kernel_matrix: &Array2<f64>,
328 alphas: &Array1<f64>,
329 y: &Array1<i32>,
330 i: usize,
331 ) -> f64 {
332 let mut sum = self.bias;
333 for j in 0..alphas.len() {
334 if alphas[j] > 0.0 {
335 sum += alphas[j] * y[j] as f64 * kernel_matrix[[i, j]];
336 }
337 }
338 sum - y[i] as f64
339 }
340
341 fn check_kkt(&self, alpha: f64, y: i32, error: f64) -> bool {
343 let y_error = y as f64 * error;
344
345 if alpha < 1e-5 {
346 y_error >= -self.params.tolerance
347 } else if alpha > self.params.c - 1e-5 {
348 y_error <= self.params.tolerance
349 } else {
350 (y_error).abs() <= self.params.tolerance
351 }
352 }
353
354 fn select_second_alpha(
356 &self,
357 i: usize,
358 n_samples: usize,
359 kernel_matrix: &Array2<f64>,
360 alphas: &Array1<f64>,
361 y: &Array1<i32>,
362 ) -> usize {
363 let ei = self.compute_error(kernel_matrix, alphas, y, i);
364 let mut max_step = 0.0;
365 let mut best_j = i;
366
367 for j in 0..n_samples {
368 if i == j {
369 continue;
370 }
371
372 let ej = self.compute_error(kernel_matrix, alphas, y, j);
373 let step = (ei - ej).abs();
374
375 if step > max_step {
376 max_step = step;
377 best_j = j;
378 }
379 }
380
381 best_j
382 }
383
384 fn compute_bounds(&self, yi: i32, yj: i32, alpha_i: f64, alpha_j: f64) -> (f64, f64) {
386 if yi != yj {
387 let l = (alpha_j - alpha_i).max(0.0);
388 let h = (self.params.c + alpha_j - alpha_i).min(self.params.c);
389 (l, h)
390 } else {
391 let l = (alpha_i + alpha_j - self.params.c).max(0.0);
392 let h = (alpha_i + alpha_j).min(self.params.c);
393 (l, h)
394 }
395 }
396
397 fn compute_bias(
399 &mut self,
400 kernel_matrix: &Array2<f64>,
401 alphas: &Array1<f64>,
402 y: &Array1<i32>,
403 support_indices: &[usize],
404 ) {
405 let mut bias_sum = 0.0;
406 let mut count = 0;
407
408 for &i in support_indices {
409 if alphas[i] > 1e-5 && alphas[i] < self.params.c - 1e-5 {
410 let mut sum = 0.0;
411 for j in 0..alphas.len() {
412 if alphas[j] > 1e-5 {
413 sum += alphas[j] * y[j] as f64 * kernel_matrix[[i, j]];
414 }
415 }
416 bias_sum += y[i] as f64 - sum;
417 count += 1;
418 }
419 }
420
421 self.bias = if count > 0 {
422 bias_sum / count as f64
423 } else {
424 0.0
425 };
426 }
427
428 pub fn predict(&self, x: &Array2<f64>) -> Result<Array1<i32>, String> {
430 let support_vectors = self.support_vectors.as_ref().ok_or("Model not trained")?;
431 let support_labels = self.support_labels.as_ref().ok_or("Model not trained")?;
432 let alphas = self.alphas.as_ref().ok_or("Model not trained")?;
433
434 let n_samples = x.nrows();
435 let mut predictions = Array1::zeros(n_samples);
436
437 for i in 0..n_samples {
438 let mut score = self.bias;
439
440 for (j, sv) in support_vectors.rows().into_iter().enumerate() {
441 let kernel_val = self.kernel.compute(&x.row(i).to_owned(), &sv.to_owned());
442 score += alphas[j] * support_labels[j] as f64 * kernel_val;
443 }
444
445 predictions[i] = if score >= 0.0 { 1 } else { -1 };
446 }
447
448 Ok(predictions)
449 }
450
451 pub fn decision_function(&self, x: &Array2<f64>) -> Result<Array1<f64>, String> {
453 let support_vectors = self.support_vectors.as_ref().ok_or("Model not trained")?;
454 let support_labels = self.support_labels.as_ref().ok_or("Model not trained")?;
455 let alphas = self.alphas.as_ref().ok_or("Model not trained")?;
456
457 let n_samples = x.nrows();
458 let mut scores = Array1::zeros(n_samples);
459
460 for i in 0..n_samples {
461 let mut score = self.bias;
462
463 for (j, sv) in support_vectors.rows().into_iter().enumerate() {
464 let kernel_val = self.kernel.compute(&x.row(i).to_owned(), &sv.to_owned());
465 score += alphas[j] * support_labels[j] as f64 * kernel_val;
466 }
467
468 scores[i] = score;
469 }
470
471 Ok(scores)
472 }
473
474 pub fn n_support_vectors(&self) -> usize {
476 self.support_vectors
477 .as_ref()
478 .map(|sv| sv.nrows())
479 .unwrap_or(0)
480 }
481}
482
483pub struct QuantumKernelRidge {
485 kernel: QuantumKernel,
486 alpha: f64,
487 training_data: Option<Array2<f64>>,
488 coefficients: Option<Array1<f64>>,
489}
490
491impl QuantumKernelRidge {
492 pub fn new(feature_map: FeatureMapType, reps: usize, alpha: f64) -> Self {
494 Self {
495 kernel: QuantumKernel::new(feature_map, reps),
496 alpha,
497 training_data: None,
498 coefficients: None,
499 }
500 }
501
502 pub fn fit(&mut self, x: &Array2<f64>, y: &Array1<f64>) -> Result<(), String> {
504 let mut k = self.kernel.compute_kernel_matrix(x);
506
507 let n = k.nrows();
509 for i in 0..n {
510 k[[i, i]] += self.alpha;
511 }
512
513 match Self::solve_linear_system(&k, y) {
516 Ok(coeffs) => {
517 self.training_data = Some(x.clone());
518 self.coefficients = Some(coeffs);
519 Ok(())
520 }
521 Err(e) => Err(format!("Failed to solve linear system: {}", e)),
522 }
523 }
524
525 fn solve_linear_system(a: &Array2<f64>, b: &Array1<f64>) -> Result<Array1<f64>, String> {
527 let n = a.nrows();
530 if n != b.len() {
531 return Err("Dimension mismatch".to_string());
532 }
533
534 Ok(Array1::zeros(n))
537 }
538
539 pub fn predict(&self, x: &Array2<f64>) -> Result<Array1<f64>, String> {
541 let training_data = self.training_data.as_ref().ok_or("Model not trained")?;
542 let coefficients = self.coefficients.as_ref().ok_or("Model not trained")?;
543
544 let n_samples = x.nrows();
545 let mut predictions = Array1::zeros(n_samples);
546
547 for i in 0..n_samples {
548 let mut sum = 0.0;
549 for (j, coeff) in coefficients.iter().enumerate() {
550 let kernel_val = self
551 .kernel
552 .compute(&x.row(i).to_owned(), &training_data.row(j).to_owned());
553 sum += coeff * kernel_val;
554 }
555 predictions[i] = sum;
556 }
557
558 Ok(predictions)
559 }
560}
561
562#[cfg(test)]
563mod tests {
564 use super::*;
565 use scirs2_core::ndarray::array;
566
567 #[test]
568 fn test_quantum_kernel_computation() {
569 let kernel = QuantumKernel::new(FeatureMapType::ZFeatureMap, 1);
570
571 let x1 = array![0.5, 0.5];
572 let x2 = array![0.5, 0.5];
573
574 let kernel_val = kernel.compute(&x1, &x2);
575 assert!((kernel_val - 1.0).abs() < 1e-6); let x3 = array![0.0, 1.0];
578 let kernel_val2 = kernel.compute(&x1, &x3);
579 assert!(kernel_val2 < 1.0); }
581
582 #[test]
583 fn test_qsvm_basic() {
584 let x = array![[0.0, 0.0], [0.1, 0.1], [1.0, 1.0], [0.9, 0.9],];
586
587 let y = array![-1, -1, 1, 1];
588
589 let params = QSVMParams::default();
590 let mut qsvm = QSVM::new(params);
591
592 qsvm.fit(&x, &y).unwrap();
594
595 assert!(qsvm.n_support_vectors() > 0);
597
598 let predictions = qsvm.predict(&x).unwrap();
600
601 for i in 0..y.len() {
603 assert_eq!(predictions[i], y[i]);
604 }
605 }
606}