1use crate::{
11 eigensolve::eigen_decompose_unitary,
12 error::{QuantRS2Error, QuantRS2Result},
13 gate::{single::*, GateOp},
14 qubit::QubitId,
15};
16use ndarray::{Array1, Array2};
17use num_complex::Complex64 as Complex;
18
19#[derive(Debug, Clone)]
21pub struct GateEigenstructure {
22 pub eigenvalues: Vec<Complex>,
24 pub eigenvectors: Array2<Complex>,
26 pub matrix: Array2<Complex>,
28}
29
30impl GateEigenstructure {
31 pub fn is_diagonal(&self, tolerance: f64) -> bool {
33 let n = self.matrix.nrows();
36 for i in 0..n {
37 for j in 0..n {
38 if i != j && self.matrix[(i, j)].norm() > tolerance {
39 return false;
40 }
41 }
42 }
43 true
44 }
45
46 pub fn eigenphases(&self) -> Vec<f64> {
48 self.eigenvalues
49 .iter()
50 .map(|&lambda| lambda.arg())
51 .collect()
52 }
53
54 pub fn is_phase_gate(&self, tolerance: f64) -> bool {
56 let magnitude = self.eigenvalues[0].norm();
58 self.eigenvalues
59 .iter()
60 .all(|&lambda| (lambda.norm() - magnitude).abs() < tolerance)
61 }
62
63 pub fn rotation_angle(&self) -> Option<f64> {
65 if self.eigenvalues.len() != 2 {
66 return None;
67 }
68
69 let phase_diff = (self.eigenvalues[0] / self.eigenvalues[1]).arg();
71 Some(phase_diff.abs())
72 }
73
74 pub fn rotation_axis(&self, tolerance: f64) -> Option<[f64; 3]> {
76 if self.eigenvalues.len() != 2 {
77 return None;
78 }
79
80 let v0 = self.eigenvectors.column(0);
84 let v1 = self.eigenvectors.column(1);
85
86 let bloch0 = eigenvector_to_bloch(&v0.to_owned());
88 let bloch1 = eigenvector_to_bloch(&v1.to_owned());
89
90 let axis = [
93 (bloch0[0] + bloch1[0]) / 2.0,
94 (bloch0[1] + bloch1[1]) / 2.0,
95 (bloch0[2] + bloch1[2]) / 2.0,
96 ];
97
98 let norm = (axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]).sqrt();
99 if norm < tolerance {
100 None
101 } else {
102 Some([axis[0] / norm, axis[1] / norm, axis[2] / norm])
103 }
104 }
105}
106
107fn eigenvector_to_bloch(v: &Array1<Complex>) -> [f64; 3] {
109 if v.len() != 2 {
110 return [0.0, 0.0, 0.0];
111 }
112
113 let v0 = v[0];
115 let v1 = v[1];
116 let rho00 = (v0 * v0.conj()).re;
117 let rho11 = (v1 * v1.conj()).re;
118 let rho01 = v0 * v1.conj();
119
120 [2.0 * rho01.re, -2.0 * rho01.im, rho00 - rho11]
121}
122
123pub struct GateCharacterizer {
125 tolerance: f64,
126}
127
128impl GateCharacterizer {
129 pub fn new(tolerance: f64) -> Self {
131 Self { tolerance }
132 }
133
134 pub fn eigenstructure(&self, gate: &dyn GateOp) -> QuantRS2Result<GateEigenstructure> {
136 let matrix_vec = gate.matrix()?;
137 let n = (matrix_vec.len() as f64).sqrt() as usize;
138
139 let mut matrix = Array2::zeros((n, n));
141 for i in 0..n {
142 for j in 0..n {
143 matrix[(i, j)] = matrix_vec[i * n + j];
144 }
145 }
146
147 let eigen = eigen_decompose_unitary(&matrix, self.tolerance)?;
149
150 Ok(GateEigenstructure {
151 eigenvalues: eigen.eigenvalues.to_vec(),
152 eigenvectors: eigen.eigenvectors,
153 matrix,
154 })
155 }
156
157 pub fn identify_gate_type(&self, gate: &dyn GateOp) -> QuantRS2Result<GateType> {
159 let eigen = self.eigenstructure(gate)?;
160 let n = eigen.eigenvalues.len();
161
162 match n {
163 2 => self.identify_single_qubit_gate(&eigen),
164 4 => self.identify_two_qubit_gate(&eigen),
165 _ => Ok(GateType::General {
166 qubits: (n as f64).log2() as usize,
167 }),
168 }
169 }
170
171 fn identify_single_qubit_gate(&self, eigen: &GateEigenstructure) -> QuantRS2Result<GateType> {
173 if self.is_pauli_gate(eigen) {
175 return Ok(self.identify_pauli_type(eigen));
176 }
177
178 if let Some(angle) = eigen.rotation_angle() {
180 if let Some(axis) = eigen.rotation_axis(self.tolerance) {
181 return Ok(GateType::Rotation { angle, axis });
182 }
183 }
184
185 if self.is_hadamard(eigen) {
187 return Ok(GateType::Hadamard);
188 }
189
190 Ok(GateType::General { qubits: 1 })
191 }
192
193 fn identify_two_qubit_gate(&self, eigen: &GateEigenstructure) -> QuantRS2Result<GateType> {
195 if self.is_cnot(eigen) {
197 return Ok(GateType::CNOT);
198 }
199
200 if self.is_controlled_phase(eigen) {
202 if let Some(phase) = self.extract_controlled_phase(eigen) {
203 return Ok(GateType::ControlledPhase { phase });
204 }
205 }
206
207 if self.is_swap_variant(eigen) {
209 return Ok(self.identify_swap_type(eigen));
210 }
211
212 Ok(GateType::General { qubits: 2 })
213 }
214
215 fn is_pauli_gate(&self, eigen: &GateEigenstructure) -> bool {
217 eigen.eigenvalues.iter().all(|&lambda| {
218 let is_plus_minus_one = (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance
219 || (lambda + Complex::new(1.0, 0.0)).norm() < self.tolerance;
220 let is_plus_minus_i = (lambda - Complex::new(0.0, 1.0)).norm() < self.tolerance
221 || (lambda + Complex::new(0.0, 1.0)).norm() < self.tolerance;
222 is_plus_minus_one || is_plus_minus_i
223 })
224 }
225
226 fn identify_pauli_type(&self, eigen: &GateEigenstructure) -> GateType {
228 let matrix = &eigen.matrix;
229
230 let tolerance = self.tolerance;
232
233 if (matrix[(0, 1)] - Complex::new(1.0, 0.0)).norm() < tolerance
235 && (matrix[(1, 0)] - Complex::new(1.0, 0.0)).norm() < tolerance
236 && matrix[(0, 0)].norm() < tolerance
237 && matrix[(1, 1)].norm() < tolerance
238 {
239 GateType::PauliX
240 }
241 else if (matrix[(0, 1)] - Complex::new(0.0, -1.0)).norm() < tolerance
243 && (matrix[(1, 0)] - Complex::new(0.0, 1.0)).norm() < tolerance
244 && matrix[(0, 0)].norm() < tolerance
245 && matrix[(1, 1)].norm() < tolerance
246 {
247 GateType::PauliY
248 }
249 else if (matrix[(0, 0)] - Complex::new(1.0, 0.0)).norm() < tolerance
251 && (matrix[(1, 1)] - Complex::new(-1.0, 0.0)).norm() < tolerance
252 && matrix[(0, 1)].norm() < tolerance
253 && matrix[(1, 0)].norm() < tolerance
254 {
255 GateType::PauliZ
256 } else {
257 GateType::General { qubits: 1 }
258 }
259 }
260
261 fn is_hadamard(&self, eigen: &GateEigenstructure) -> bool {
263 eigen.eigenvalues.iter().all(|&lambda| {
265 (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance
266 || (lambda + Complex::new(1.0, 0.0)).norm() < self.tolerance
267 })
268 }
269
270 fn is_cnot(&self, eigen: &GateEigenstructure) -> bool {
272 eigen.eigenvalues.len() == 4
274 && eigen.eigenvalues.iter().all(|&lambda| {
275 (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance
276 || (lambda + Complex::new(1.0, 0.0)).norm() < self.tolerance
277 })
278 }
279
280 fn is_controlled_phase(&self, eigen: &GateEigenstructure) -> bool {
282 let ones = eigen
284 .eigenvalues
285 .iter()
286 .filter(|&&lambda| (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance)
287 .count();
288 ones == 3
289 }
290
291 fn extract_controlled_phase(&self, eigen: &GateEigenstructure) -> Option<f64> {
293 eigen
294 .eigenvalues
295 .iter()
296 .find(|&&lambda| (lambda - Complex::new(1.0, 0.0)).norm() > self.tolerance)
297 .map(|&lambda| lambda.arg())
298 }
299
300 fn is_swap_variant(&self, eigen: &GateEigenstructure) -> bool {
302 let ones = eigen
305 .eigenvalues
306 .iter()
307 .filter(|&&lambda| (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance)
308 .count();
309 ones >= 2
310 }
311
312 fn identify_swap_type(&self, eigen: &GateEigenstructure) -> GateType {
314 let matrix = &eigen.matrix;
315
316 if (matrix[(0, 0)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
318 && (matrix[(1, 2)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
319 && (matrix[(2, 1)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
320 && (matrix[(3, 3)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
321 && matrix[(0, 1)].norm() < self.tolerance
322 && matrix[(0, 2)].norm() < self.tolerance
323 && matrix[(0, 3)].norm() < self.tolerance
324 && matrix[(1, 0)].norm() < self.tolerance
325 && matrix[(1, 1)].norm() < self.tolerance
326 && matrix[(1, 3)].norm() < self.tolerance
327 && matrix[(2, 0)].norm() < self.tolerance
328 && matrix[(2, 2)].norm() < self.tolerance
329 && matrix[(2, 3)].norm() < self.tolerance
330 && matrix[(3, 0)].norm() < self.tolerance
331 && matrix[(3, 1)].norm() < self.tolerance
332 && matrix[(3, 2)].norm() < self.tolerance
333 {
334 GateType::SWAP
335 } else {
336 GateType::General { qubits: 2 }
338 }
339 }
340
341 fn matrix_equals(&self, a: &Array2<Complex>, b: &Array2<Complex>, tolerance: f64) -> bool {
343 a.shape() == b.shape()
344 && a.iter()
345 .zip(b.iter())
346 .all(|(a_ij, b_ij)| (a_ij - b_ij).norm() < tolerance)
347 }
348
349 pub fn decompose_to_rotations(
351 &self,
352 gate: &dyn GateOp,
353 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
354 let eigen = self.eigenstructure(gate)?;
355
356 match eigen.eigenvalues.len() {
357 2 => self.decompose_single_qubit(&eigen),
358 _ => Err(QuantRS2Error::UnsupportedOperation(
359 "Rotation decomposition only supported for single-qubit gates".to_string(),
360 )),
361 }
362 }
363
364 fn decompose_single_qubit(
366 &self,
367 eigen: &GateEigenstructure,
368 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
369 let matrix = &eigen.matrix;
373
374 let alpha = matrix[(1, 1)].arg() - matrix[(1, 0)].arg();
376 let gamma = matrix[(1, 1)].arg() + matrix[(1, 0)].arg();
377 let beta = 2.0 * matrix[(1, 0)].norm().acos();
378
379 Ok(vec![
380 Box::new(RotationZ {
381 target: QubitId(0),
382 theta: alpha,
383 }),
384 Box::new(RotationY {
385 target: QubitId(0),
386 theta: beta,
387 }),
388 Box::new(RotationZ {
389 target: QubitId(0),
390 theta: gamma,
391 }),
392 ])
393 }
394
395 pub fn find_closest_clifford(&self, gate: &dyn GateOp) -> QuantRS2Result<Box<dyn GateOp>> {
397 let eigen = self.eigenstructure(gate)?;
398
399 if eigen.eigenvalues.len() != 2 {
400 return Err(QuantRS2Error::UnsupportedOperation(
401 "Clifford approximation only supported for single-qubit gates".to_string(),
402 ));
403 }
404
405 let target = QubitId(0);
407 let clifford_gates: Vec<Box<dyn GateOp>> = vec![
408 Box::new(PauliX { target }),
409 Box::new(PauliY { target }),
410 Box::new(PauliZ { target }),
411 Box::new(Hadamard { target }),
412 Box::new(Phase { target }),
413 ];
414
415 let mut min_distance = f64::INFINITY;
417 let mut closest_gate = None;
418
419 for clifford in &clifford_gates {
420 let distance = self.gate_distance(gate, clifford.as_ref())?;
421 if distance < min_distance {
422 min_distance = distance;
423 closest_gate = Some(clifford.clone());
424 }
425 }
426
427 closest_gate.ok_or_else(|| {
428 QuantRS2Error::ComputationError("Failed to find closest Clifford gate".to_string())
429 })
430 }
431
432 pub fn gate_distance(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> QuantRS2Result<f64> {
434 let m1_vec = gate1.matrix()?;
435 let m2_vec = gate2.matrix()?;
436
437 if m1_vec.len() != m2_vec.len() {
438 return Err(QuantRS2Error::InvalidInput(
439 "Gates must have the same dimensions".to_string(),
440 ));
441 }
442
443 let diff_sqr: f64 = m1_vec
444 .iter()
445 .zip(m2_vec.iter())
446 .map(|(a, b)| (a - b).norm_sqr())
447 .sum();
448 Ok(diff_sqr.sqrt())
449 }
450
451 pub fn is_identity(&self, gate: &dyn GateOp, tolerance: f64) -> bool {
453 let matrix_vec = match gate.matrix() {
454 Ok(m) => m,
455 Err(_) => return false,
456 };
457 let n = (matrix_vec.len() as f64).sqrt() as usize;
458
459 for i in 0..n {
460 for j in 0..n {
461 let idx = i * n + j;
462 let expected = if i == j {
463 Complex::new(1.0, 0.0)
464 } else {
465 Complex::new(0.0, 0.0)
466 };
467 if (matrix_vec[idx] - expected).norm() > tolerance {
468 return false;
469 }
470 }
471 }
472 true
473 }
474
475 pub fn global_phase(&self, gate: &dyn GateOp) -> QuantRS2Result<f64> {
477 let eigen = self.eigenstructure(gate)?;
478
479 let det = eigen
483 .eigenvalues
484 .iter()
485 .fold(Complex::new(1.0, 0.0), |acc, &lambda| acc * lambda);
486 let n = eigen.eigenvalues.len() as f64;
487 Ok(det.arg() / n)
488 }
489}
490
491#[derive(Debug, Clone, PartialEq)]
493pub enum GateType {
494 Identity,
496 PauliX,
498 PauliY,
500 PauliZ,
502 Hadamard,
504 Phase { angle: f64 },
506 Rotation { angle: f64, axis: [f64; 3] },
508 CNOT,
510 ControlledPhase { phase: f64 },
512 SWAP,
514 General { qubits: usize },
516}
517
518#[cfg(test)]
519mod tests {
520 use super::*;
521 use crate::gate::{multi::*, single::*};
522 use std::f64::consts::PI;
523
524 #[test]
525 fn test_pauli_identification() {
526 let characterizer = GateCharacterizer::new(1e-10);
527
528 assert_eq!(
529 characterizer
530 .identify_gate_type(&PauliX { target: QubitId(0) })
531 .unwrap(),
532 GateType::PauliX
533 );
534 assert_eq!(
535 characterizer
536 .identify_gate_type(&PauliY { target: QubitId(0) })
537 .unwrap(),
538 GateType::PauliY
539 );
540 assert_eq!(
541 characterizer
542 .identify_gate_type(&PauliZ { target: QubitId(0) })
543 .unwrap(),
544 GateType::PauliZ
545 );
546 }
547
548 #[test]
549 fn test_rotation_decomposition() {
550 let characterizer = GateCharacterizer::new(1e-10);
551 let rx = RotationX {
552 target: QubitId(0),
553 theta: PI / 4.0,
554 };
555
556 let decomposition = characterizer.decompose_to_rotations(&rx).unwrap();
557 assert_eq!(decomposition.len(), 3); }
559
560 #[test]
561 fn test_eigenphases() {
562 let characterizer = GateCharacterizer::new(1e-10);
563 let rz = RotationZ {
564 target: QubitId(0),
565 theta: PI / 2.0,
566 };
567
568 let eigen = characterizer.eigenstructure(&rz).unwrap();
569 let phases = eigen.eigenphases();
570
571 assert_eq!(phases.len(), 2);
572 assert!((phases[0] + phases[1]).abs() < 1e-10); }
574
575 #[test]
576 fn test_closest_clifford() {
577 let characterizer = GateCharacterizer::new(1e-10);
578
579 let t_like = RotationZ {
581 target: QubitId(0),
582 theta: PI / 4.0,
583 };
584 let closest = characterizer.find_closest_clifford(&t_like).unwrap();
585
586 let s_distance = characterizer
588 .gate_distance(&t_like, &Phase { target: QubitId(0) })
589 .unwrap();
590 let actual_distance = characterizer
591 .gate_distance(&t_like, closest.as_ref())
592 .unwrap();
593
594 assert!(actual_distance <= s_distance + 1e-10);
595 }
596
597 #[test]
598 fn test_identity_check() {
599 let characterizer = GateCharacterizer::new(1e-10);
600
601 let identity_gate = RotationZ {
603 target: QubitId(0),
604 theta: 0.0,
605 };
606 assert!(characterizer.is_identity(&identity_gate, 1e-10));
607 assert!(!characterizer.is_identity(&PauliX { target: QubitId(0) }, 1e-10));
608
609 let x_squared_vec = vec![
611 Complex::new(1.0, 0.0),
612 Complex::new(0.0, 0.0),
613 Complex::new(0.0, 0.0),
614 Complex::new(1.0, 0.0),
615 ];
616
617 #[derive(Debug)]
618 struct CustomGate(Vec<Complex>);
619 impl GateOp for CustomGate {
620 fn name(&self) -> &'static str {
621 "X²"
622 }
623 fn qubits(&self) -> Vec<QubitId> {
624 vec![QubitId(0)]
625 }
626 fn matrix(&self) -> QuantRS2Result<Vec<Complex>> {
627 Ok(self.0.clone())
628 }
629 fn as_any(&self) -> &dyn std::any::Any {
630 self
631 }
632 fn clone_gate(&self) -> Box<dyn GateOp> {
633 Box::new(CustomGate(self.0.clone()))
634 }
635 }
636
637 let x_squared_gate = CustomGate(x_squared_vec);
638 assert!(characterizer.is_identity(&x_squared_gate, 1e-10));
639 }
640
641 #[test]
642 fn test_global_phase() {
643 let characterizer = GateCharacterizer::new(1e-10);
644
645 let z_phase = characterizer
647 .global_phase(&PauliZ { target: QubitId(0) })
648 .unwrap();
649 assert!((z_phase - PI / 2.0).abs() < 1e-10 || (z_phase + PI / 2.0).abs() < 1e-10);
651
652 let phase_gate = Phase { target: QubitId(0) };
654 let global_phase = characterizer.global_phase(&phase_gate).unwrap();
655 assert!((global_phase - PI / 4.0).abs() < 1e-10);
657 }
658}