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 #[allow(dead_code)]
343 fn matrix_equals(&self, a: &Array2<Complex>, b: &Array2<Complex>, tolerance: f64) -> bool {
344 a.shape() == b.shape()
345 && a.iter()
346 .zip(b.iter())
347 .all(|(a_ij, b_ij)| (a_ij - b_ij).norm() < tolerance)
348 }
349
350 pub fn decompose_to_rotations(
352 &self,
353 gate: &dyn GateOp,
354 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
355 let eigen = self.eigenstructure(gate)?;
356
357 match eigen.eigenvalues.len() {
358 2 => self.decompose_single_qubit(&eigen),
359 _ => Err(QuantRS2Error::UnsupportedOperation(
360 "Rotation decomposition only supported for single-qubit gates".to_string(),
361 )),
362 }
363 }
364
365 fn decompose_single_qubit(
367 &self,
368 eigen: &GateEigenstructure,
369 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
370 let matrix = &eigen.matrix;
374
375 let alpha = matrix[(1, 1)].arg() - matrix[(1, 0)].arg();
377 let gamma = matrix[(1, 1)].arg() + matrix[(1, 0)].arg();
378 let beta = 2.0 * matrix[(1, 0)].norm().acos();
379
380 Ok(vec![
381 Box::new(RotationZ {
382 target: QubitId(0),
383 theta: alpha,
384 }),
385 Box::new(RotationY {
386 target: QubitId(0),
387 theta: beta,
388 }),
389 Box::new(RotationZ {
390 target: QubitId(0),
391 theta: gamma,
392 }),
393 ])
394 }
395
396 pub fn find_closest_clifford(&self, gate: &dyn GateOp) -> QuantRS2Result<Box<dyn GateOp>> {
398 let eigen = self.eigenstructure(gate)?;
399
400 if eigen.eigenvalues.len() != 2 {
401 return Err(QuantRS2Error::UnsupportedOperation(
402 "Clifford approximation only supported for single-qubit gates".to_string(),
403 ));
404 }
405
406 let target = QubitId(0);
408 let clifford_gates: Vec<Box<dyn GateOp>> = vec![
409 Box::new(PauliX { target }),
410 Box::new(PauliY { target }),
411 Box::new(PauliZ { target }),
412 Box::new(Hadamard { target }),
413 Box::new(Phase { target }),
414 ];
415
416 let mut min_distance = f64::INFINITY;
418 let mut closest_gate = None;
419
420 for clifford in &clifford_gates {
421 let distance = self.gate_distance(gate, clifford.as_ref())?;
422 if distance < min_distance {
423 min_distance = distance;
424 closest_gate = Some(clifford.clone());
425 }
426 }
427
428 closest_gate.ok_or_else(|| {
429 QuantRS2Error::ComputationError("Failed to find closest Clifford gate".to_string())
430 })
431 }
432
433 pub fn gate_distance(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> QuantRS2Result<f64> {
435 let m1_vec = gate1.matrix()?;
436 let m2_vec = gate2.matrix()?;
437
438 if m1_vec.len() != m2_vec.len() {
439 return Err(QuantRS2Error::InvalidInput(
440 "Gates must have the same dimensions".to_string(),
441 ));
442 }
443
444 let diff_sqr: f64 = m1_vec
445 .iter()
446 .zip(m2_vec.iter())
447 .map(|(a, b)| (a - b).norm_sqr())
448 .sum();
449 Ok(diff_sqr.sqrt())
450 }
451
452 pub fn is_identity(&self, gate: &dyn GateOp, tolerance: f64) -> bool {
454 let matrix_vec = match gate.matrix() {
455 Ok(m) => m,
456 Err(_) => return false,
457 };
458 let n = (matrix_vec.len() as f64).sqrt() as usize;
459
460 for i in 0..n {
461 for j in 0..n {
462 let idx = i * n + j;
463 let expected = if i == j {
464 Complex::new(1.0, 0.0)
465 } else {
466 Complex::new(0.0, 0.0)
467 };
468 if (matrix_vec[idx] - expected).norm() > tolerance {
469 return false;
470 }
471 }
472 }
473 true
474 }
475
476 pub fn global_phase(&self, gate: &dyn GateOp) -> QuantRS2Result<f64> {
478 let eigen = self.eigenstructure(gate)?;
479
480 let det = eigen
484 .eigenvalues
485 .iter()
486 .fold(Complex::new(1.0, 0.0), |acc, &lambda| acc * lambda);
487 let n = eigen.eigenvalues.len() as f64;
488 Ok(det.arg() / n)
489 }
490}
491
492#[derive(Debug, Clone, PartialEq)]
494pub enum GateType {
495 Identity,
497 PauliX,
499 PauliY,
501 PauliZ,
503 Hadamard,
505 Phase { angle: f64 },
507 Rotation { angle: f64, axis: [f64; 3] },
509 CNOT,
511 ControlledPhase { phase: f64 },
513 SWAP,
515 General { qubits: usize },
517}
518
519#[cfg(test)]
520mod tests {
521 use super::*;
522 use crate::gate::GateOp;
523 use std::f64::consts::PI;
524
525 #[test]
526 fn test_pauli_identification() {
527 let characterizer = GateCharacterizer::new(1e-10);
528
529 assert_eq!(
530 characterizer
531 .identify_gate_type(&PauliX { target: QubitId(0) })
532 .unwrap(),
533 GateType::PauliX
534 );
535 assert_eq!(
536 characterizer
537 .identify_gate_type(&PauliY { target: QubitId(0) })
538 .unwrap(),
539 GateType::PauliY
540 );
541 assert_eq!(
542 characterizer
543 .identify_gate_type(&PauliZ { target: QubitId(0) })
544 .unwrap(),
545 GateType::PauliZ
546 );
547 }
548
549 #[test]
550 fn test_rotation_decomposition() {
551 let characterizer = GateCharacterizer::new(1e-10);
552 let rx = RotationX {
553 target: QubitId(0),
554 theta: PI / 4.0,
555 };
556
557 let decomposition = characterizer.decompose_to_rotations(&rx).unwrap();
558 assert_eq!(decomposition.len(), 3); }
560
561 #[test]
562 fn test_eigenphases() {
563 let characterizer = GateCharacterizer::new(1e-10);
564 let rz = RotationZ {
565 target: QubitId(0),
566 theta: PI / 2.0,
567 };
568
569 let eigen = characterizer.eigenstructure(&rz).unwrap();
570 let phases = eigen.eigenphases();
571
572 assert_eq!(phases.len(), 2);
573 assert!((phases[0] + phases[1]).abs() < 1e-10); }
575
576 #[test]
577 fn test_closest_clifford() {
578 let characterizer = GateCharacterizer::new(1e-10);
579
580 let t_like = RotationZ {
582 target: QubitId(0),
583 theta: PI / 4.0,
584 };
585 let closest = characterizer.find_closest_clifford(&t_like).unwrap();
586
587 let s_distance = characterizer
589 .gate_distance(&t_like, &Phase { target: QubitId(0) })
590 .unwrap();
591 let actual_distance = characterizer
592 .gate_distance(&t_like, closest.as_ref())
593 .unwrap();
594
595 assert!(actual_distance <= s_distance + 1e-10);
596 }
597
598 #[test]
599 fn test_identity_check() {
600 let characterizer = GateCharacterizer::new(1e-10);
601
602 let identity_gate = RotationZ {
604 target: QubitId(0),
605 theta: 0.0,
606 };
607 assert!(characterizer.is_identity(&identity_gate, 1e-10));
608 assert!(!characterizer.is_identity(&PauliX { target: QubitId(0) }, 1e-10));
609
610 let x_squared_vec = vec![
612 Complex::new(1.0, 0.0),
613 Complex::new(0.0, 0.0),
614 Complex::new(0.0, 0.0),
615 Complex::new(1.0, 0.0),
616 ];
617
618 #[derive(Debug)]
619 struct CustomGate(Vec<Complex>);
620 impl GateOp for CustomGate {
621 fn name(&self) -> &'static str {
622 "X²"
623 }
624 fn qubits(&self) -> Vec<QubitId> {
625 vec![QubitId(0)]
626 }
627 fn matrix(&self) -> QuantRS2Result<Vec<Complex>> {
628 Ok(self.0.clone())
629 }
630 fn as_any(&self) -> &dyn std::any::Any {
631 self
632 }
633 fn clone_gate(&self) -> Box<dyn GateOp> {
634 Box::new(CustomGate(self.0.clone()))
635 }
636 }
637
638 let x_squared_gate = CustomGate(x_squared_vec);
639 assert!(characterizer.is_identity(&x_squared_gate, 1e-10));
640 }
641
642 #[test]
643 fn test_global_phase() {
644 let characterizer = GateCharacterizer::new(1e-10);
645
646 let z_phase = characterizer
648 .global_phase(&PauliZ { target: QubitId(0) })
649 .unwrap();
650 assert!((z_phase - PI / 2.0).abs() < 1e-10 || (z_phase + PI / 2.0).abs() < 1e-10);
652
653 let phase_gate = Phase { target: QubitId(0) };
655 let global_phase = characterizer.global_phase(&phase_gate).unwrap();
656 assert!((global_phase - PI / 4.0).abs() < 1e-10);
658 }
659}