1use crate::{
27 error::{QuantRS2Error, QuantRS2Result},
28 gate::{multi::*, single::*, GateOp},
29 matrix_ops::{DenseMatrix, QuantumMatrix},
30 qubit::QubitId,
31 synthesis::{decompose_single_qubit_zyz, SingleQubitDecomposition},
32};
33use rustc_hash::FxHashMap;
34use scirs2_core::ndarray::{s, Array1, Array2};
35use scirs2_core::Complex;
36use std::f64::consts::PI;
37
38#[derive(Debug, Clone)]
40pub struct CartanDecomposition {
41 pub left_gates: (SingleQubitDecomposition, SingleQubitDecomposition),
43 pub right_gates: (SingleQubitDecomposition, SingleQubitDecomposition),
45 pub interaction: CartanCoefficients,
47 pub global_phase: f64,
49}
50
51#[derive(Debug, Clone, Copy)]
53pub struct CartanCoefficients {
54 pub xx: f64,
56 pub yy: f64,
58 pub zz: f64,
60}
61
62impl CartanCoefficients {
63 pub const fn new(xx: f64, yy: f64, zz: f64) -> Self {
65 Self { xx, yy, zz }
66 }
67
68 pub fn is_identity(&self, tolerance: f64) -> bool {
70 self.xx.abs() < tolerance && self.yy.abs() < tolerance && self.zz.abs() < tolerance
71 }
72
73 pub fn cnot_count(&self, tolerance: f64) -> usize {
75 let eps = tolerance;
76
77 if self.is_identity(eps) {
79 0
80 } else if (self.xx - self.yy).abs() < eps && self.zz.abs() < eps {
81 2
83 } else if (self.xx - PI / 4.0).abs() < eps
84 && (self.yy - PI / 4.0).abs() < eps
85 && (self.zz - PI / 4.0).abs() < eps
86 {
87 3
89 } else if self.xx.abs() < eps || self.yy.abs() < eps || self.zz.abs() < eps {
90 2
92 } else {
93 3
95 }
96 }
97
98 pub fn canonicalize(&mut self) {
100 let mut vals = [
102 (self.xx.abs(), self.xx, 0),
103 (self.yy.abs(), self.yy, 1),
104 (self.zz.abs(), self.zz, 2),
105 ];
106 vals.sort_by(|a, b| {
107 b.0.partial_cmp(&a.0)
108 .expect("Failed to compare Cartan coefficients in CartanCoefficients::canonicalize")
109 });
110
111 self.xx = vals[0].1;
112 self.yy = vals[1].1;
113 self.zz = vals[2].1;
114 }
115}
116
117pub struct CartanDecomposer {
119 tolerance: f64,
121 #[allow(dead_code)]
123 cache: FxHashMap<u64, CartanDecomposition>,
124}
125
126impl CartanDecomposer {
127 pub fn new() -> Self {
129 Self {
130 tolerance: 1e-10,
131 cache: FxHashMap::default(),
132 }
133 }
134
135 pub fn with_tolerance(tolerance: f64) -> Self {
137 Self {
138 tolerance,
139 cache: FxHashMap::default(),
140 }
141 }
142
143 pub fn decompose(
145 &mut self,
146 unitary: &Array2<Complex<f64>>,
147 ) -> QuantRS2Result<CartanDecomposition> {
148 if unitary.shape() != [4, 4] {
150 return Err(QuantRS2Error::InvalidInput(
151 "Cartan decomposition requires 4x4 unitary".to_string(),
152 ));
153 }
154
155 let mat = DenseMatrix::new(unitary.clone())?;
157 if !mat.is_unitary(self.tolerance)? {
158 return Err(QuantRS2Error::InvalidInput(
159 "Matrix is not unitary".to_string(),
160 ));
161 }
162
163 let magic_basis = Self::get_magic_basis();
165 let u_magic = Self::to_magic_basis(unitary, &magic_basis);
166
167 let u_magic_t = u_magic.t().to_owned();
169 let m = u_magic_t.dot(&u_magic);
170
171 let (d, p) = Self::diagonalize_symmetric(&m)?;
173
174 let coeffs = Self::extract_coefficients(&d);
176
177 let (left_gates, right_gates) = self.compute_local_gates(unitary, &u_magic, &p, &coeffs)?;
179
180 let global_phase = Self::compute_global_phase(unitary, &left_gates, &right_gates, &coeffs)?;
182
183 Ok(CartanDecomposition {
184 left_gates,
185 right_gates,
186 interaction: coeffs,
187 global_phase,
188 })
189 }
190
191 fn get_magic_basis() -> Array2<Complex<f64>> {
193 let sqrt2 = 2.0_f64.sqrt();
194 Array2::from_shape_vec(
195 (4, 4),
196 vec![
197 Complex::new(1.0, 0.0),
198 Complex::new(0.0, 0.0),
199 Complex::new(0.0, 0.0),
200 Complex::new(1.0, 0.0),
201 Complex::new(0.0, 0.0),
202 Complex::new(1.0, 0.0),
203 Complex::new(1.0, 0.0),
204 Complex::new(0.0, 0.0),
205 Complex::new(0.0, 0.0),
206 Complex::new(1.0, 0.0),
207 Complex::new(-1.0, 0.0),
208 Complex::new(0.0, 0.0),
209 Complex::new(1.0, 0.0),
210 Complex::new(0.0, 0.0),
211 Complex::new(0.0, 0.0),
212 Complex::new(-1.0, 0.0),
213 ],
214 )
215 .expect("Failed to create magic basis matrix in CartanDecomposer::get_magic_basis")
216 / Complex::new(sqrt2, 0.0)
217 }
218
219 fn to_magic_basis(
221 u: &Array2<Complex<f64>>,
222 magic: &Array2<Complex<f64>>,
223 ) -> Array2<Complex<f64>> {
224 let magic_dag = magic.mapv(|z| z.conj()).t().to_owned();
225 magic_dag.dot(u).dot(magic)
226 }
227
228 fn diagonalize_symmetric(
234 m: &Array2<Complex<f64>>,
235 ) -> QuantRS2Result<(Array1<Complex<f64>>, Array2<Complex<f64>>)> {
236 let n = m.nrows();
237 let mut h = m.to_owned();
240 let mut q = Array2::<Complex<f64>>::eye(n);
241
242 for k in 0..n.saturating_sub(2) {
244 let col: Vec<Complex<f64>> = (k + 1..n).map(|i| h[[i, k]]).collect();
246 let sigma_sq: f64 = col.iter().map(|z| z.norm_sqr()).sum();
247 let sigma = sigma_sq.sqrt();
248 if sigma < 1e-14 {
249 continue;
250 }
251 let phase = if col[0].norm() > 1e-14 {
253 col[0] / col[0].norm()
254 } else {
255 Complex::new(1.0, 0.0)
256 };
257 let mut v = col.clone();
258 v[0] = v[0] + phase * sigma;
259 let v_norm_sq: f64 = v.iter().map(|z| z.norm_sqr()).sum();
260 if v_norm_sq < 1e-28 {
261 continue;
262 }
263 let m_len = v.len(); for j in 0..n {
267 let dot: Complex<f64> = (0..m_len).map(|i| v[i].conj() * h[[k + 1 + i, j]]).sum();
268 let scale = dot * Complex::new(2.0 / v_norm_sq, 0.0);
269 for i in 0..m_len {
270 h[[k + 1 + i, j]] = h[[k + 1 + i, j]] - v[i] * scale;
271 }
272 }
273 for i in 0..n {
275 let dot: Complex<f64> = (0..m_len).map(|j| h[[i, k + 1 + j]] * v[j]).sum();
276 let scale = dot * Complex::new(2.0 / v_norm_sq, 0.0);
277 for j in 0..m_len {
278 h[[i, k + 1 + j]] = h[[i, k + 1 + j]] - scale * v[j].conj();
279 }
280 }
281 for i in 0..n {
283 let dot: Complex<f64> = (0..m_len).map(|j| q[[i, k + 1 + j]] * v[j]).sum();
284 let scale = dot * Complex::new(2.0 / v_norm_sq, 0.0);
285 for j in 0..m_len {
286 q[[i, k + 1 + j]] = q[[i, k + 1 + j]] - scale * v[j].conj();
287 }
288 }
289 }
290
291 let max_iter = 300 * n;
293 let mut active = n;
294 for _iter in 0..max_iter {
295 if active <= 1 {
296 break;
297 }
298 while active > 1 {
300 let off = h[[active - 1, active - 2]].norm();
301 let d1 = h[[active - 1, active - 1]].norm();
302 let d0 = h[[active - 2, active - 2]].norm();
303 if off < 1e-12 * (d1 + d0) {
304 active -= 1;
305 } else {
306 break;
307 }
308 }
309 if active <= 1 {
310 break;
311 }
312
313 let a = active;
315 let s = h[[a - 1, a - 1]];
316
317 for k in 0..a - 1 {
320 let x = h[[k, k]] - s;
322 let y = h[[k + 1, k]];
323 let r = (x.norm_sqr() + y.norm_sqr()).sqrt();
324 if r < 1e-14 {
325 continue;
326 }
327 let c_val = x / r;
328 let s_val = -y / r;
329
330 for j in 0..n {
332 let tmp0 = c_val * h[[k, j]] - s_val.conj() * h[[k + 1, j]];
333 let tmp1 = s_val * h[[k, j]] + c_val.conj() * h[[k + 1, j]];
334 h[[k, j]] = tmp0;
335 h[[k + 1, j]] = tmp1;
336 }
337 for i in 0..n {
339 let tmp0 = c_val.conj() * h[[i, k]] - s_val.conj() * h[[i, k + 1]];
340 let tmp1 = s_val * h[[i, k]] + c_val * h[[i, k + 1]];
341 h[[i, k]] = tmp0;
342 h[[i, k + 1]] = tmp1;
343 }
344 for i in 0..n {
346 let tmp0 = c_val.conj() * q[[i, k]] - s_val.conj() * q[[i, k + 1]];
347 let tmp1 = s_val * q[[i, k]] + c_val * q[[i, k + 1]];
348 q[[i, k]] = tmp0;
349 q[[i, k + 1]] = tmp1;
350 }
351 }
352 }
353
354 let mut eigenvalues = Array1::zeros(n);
356 for i in 0..n {
357 eigenvalues[i] = h[[i, i]];
358 }
359
360 Ok((eigenvalues, q))
361 }
362
363 fn extract_coefficients(eigenvalues: &Array1<Complex<f64>>) -> CartanCoefficients {
371 let mut phases: Vec<f64> = eigenvalues.iter().map(|z| z.arg() / 2.0).collect();
374 phases.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
376
377 let p0 = phases.first().copied().unwrap_or(0.0);
388 let p1 = phases.get(1).copied().unwrap_or(0.0);
389 let p2 = phases.get(2).copied().unwrap_or(0.0);
390 let p3 = phases.get(3).copied().unwrap_or(0.0);
391
392 let a = (p3 + p0) / 2.0;
397 let b_plus_c = (p3 - p0) / 2.0;
398 let c_minus_b = (p2 - p1) / 2.0;
399 let b = (b_plus_c - c_minus_b) / 2.0;
400 let c = (b_plus_c + c_minus_b) / 2.0;
401
402 let mut coeffs = CartanCoefficients::new(a, b, c);
403 coeffs.canonicalize();
404 coeffs
405 }
406
407 fn compute_local_gates(
426 &self,
427 u: &Array2<Complex<f64>>,
428 _u_magic: &Array2<Complex<f64>>,
429 _p: &Array2<Complex<f64>>,
430 coeffs: &CartanCoefficients,
431 ) -> QuantRS2Result<(
432 (SingleQubitDecomposition, SingleQubitDecomposition),
433 (SingleQubitDecomposition, SingleQubitDecomposition),
434 )> {
435 let ident = Array2::eye(2);
436
437 if coeffs.is_identity(self.tolerance) {
439 if let Some((a, b)) = Self::factor_tensor_product(u, self.tolerance) {
440 let left_a = decompose_single_qubit_zyz(&a.view())?;
441 let left_b = decompose_single_qubit_zyz(&b.view())?;
442 let right_a = decompose_single_qubit_zyz(&ident.view())?;
443 let right_b = decompose_single_qubit_zyz(&ident.view())?;
444 return Ok(((left_a, left_b), (right_a, right_b)));
445 }
446 }
447
448 let a1 = u.slice(s![..2, ..2]).to_owned();
450 let b1 = u.slice(s![2..4, 2..4]).to_owned();
451 let a1 = Self::nearest_unitary_2x2(&a1).unwrap_or_else(|| ident.clone());
454 let b1 = Self::nearest_unitary_2x2(&b1).unwrap_or_else(|| ident.clone());
455
456 let left_a = decompose_single_qubit_zyz(&a1.view())?;
457 let left_b = decompose_single_qubit_zyz(&b1.view())?;
458 let right_a = decompose_single_qubit_zyz(&ident.view())?;
459 let right_b = decompose_single_qubit_zyz(&ident.view())?;
460
461 Ok(((left_a, left_b), (right_a, right_b)))
462 }
463
464 fn factor_tensor_product(
473 u: &Array2<Complex<f64>>,
474 tolerance: f64,
475 ) -> Option<(Array2<Complex<f64>>, Array2<Complex<f64>>)> {
476 let block = |i: usize, j: usize| -> Array2<Complex<f64>> {
478 u.slice(s![i * 2..i * 2 + 2, j * 2..j * 2 + 2]).to_owned()
479 };
480
481 let mut best = (0usize, 0usize);
483 let mut best_norm = 0.0f64;
484 for i in 0..2 {
485 for j in 0..2 {
486 let nrm = block(i, j).iter().map(|z| z.norm_sqr()).sum::<f64>();
487 if nrm > best_norm {
488 best_norm = nrm;
489 best = (i, j);
490 }
491 }
492 }
493 if best_norm < tolerance {
494 return None;
495 }
496
497 let b_ref = block(best.0, best.1);
498 let denom = best_norm; let mut a = Array2::<Complex<f64>>::zeros((2, 2));
501 for i in 0..2 {
502 for j in 0..2 {
503 let blk = block(i, j);
504 let inner: Complex<f64> = b_ref
505 .iter()
506 .zip(blk.iter())
507 .map(|(r, x)| r.conj() * x)
508 .sum();
509 a[[i, j]] = inner / Complex::new(denom, 0.0);
510 }
511 }
512
513 let det_b = b_ref[[0, 0]] * b_ref[[1, 1]] - b_ref[[0, 1]] * b_ref[[1, 0]];
516 if det_b.norm() < tolerance {
517 return None;
518 }
519 let scale_b = det_b.sqrt();
520 let b = b_ref.mapv(|z| z / scale_b);
521 let a = a.mapv(|z| z * scale_b);
523
524 let mut recon = Array2::<Complex<f64>>::zeros((4, 4));
526 for i in 0..2 {
527 for j in 0..2 {
528 for k in 0..2 {
529 for l in 0..2 {
530 recon[[i * 2 + k, j * 2 + l]] = a[[i, j]] * b[[k, l]];
531 }
532 }
533 }
534 }
535 let err: f64 = recon
536 .iter()
537 .zip(u.iter())
538 .map(|(r, x)| (r - x).norm_sqr())
539 .sum::<f64>()
540 .sqrt();
541 if err > 1e-8 {
542 return None;
543 }
544
545 let a = Self::nearest_unitary_2x2(&a)?;
548 let b = Self::nearest_unitary_2x2(&b)?;
549 Some((a, b))
550 }
551
552 fn nearest_unitary_2x2(m: &Array2<Complex<f64>>) -> Option<Array2<Complex<f64>>> {
555 let mh = m.mapv(|z| z.conj()).t().to_owned();
558 let h = mh.dot(m); let p = h[[0, 0]].re;
561 let r = h[[1, 1]].re;
562 let q = h[[0, 1]];
563 let tr = p + r;
564 let det = p * r - q.norm_sqr();
565 let disc = (tr * tr - 4.0 * det).max(0.0).sqrt();
566 let l1 = (tr + disc) / 2.0;
567 let l2 = (tr - disc) / 2.0;
568 if l1 <= 1e-24 || l2 <= 1e-24 {
569 return None;
570 }
571
572 let h_inv_sqrt = if disc <= 1e-12 * tr.max(1.0) || q.norm() <= 1e-12 * tr.max(1.0) {
577 let s = 1.0 / ((l1 + l2) / 2.0).sqrt();
578 let mut id = Array2::<Complex<f64>>::zeros((2, 2));
579 id[[0, 0]] = Complex::new(s, 0.0);
580 id[[1, 1]] = Complex::new(s, 0.0);
581 id
582 } else {
583 let v1 = [q, Complex::new(l1 - p, 0.0)];
585 let v2 = [q, Complex::new(l2 - p, 0.0)];
586 let norm = |v: &[Complex<f64>; 2]| (v[0].norm_sqr() + v[1].norm_sqr()).sqrt();
587 let n1 = norm(&v1);
588 let n2 = norm(&v2);
589 if n1 < 1e-18 || n2 < 1e-18 {
590 return None;
591 }
592 let v1 = [v1[0] / n1, v1[1] / n1];
593 let v2 = [v2[0] / n2, v2[1] / n2];
594 let s1 = 1.0 / l1.sqrt();
596 let s2 = 1.0 / l2.sqrt();
597 let mut acc = Array2::<Complex<f64>>::zeros((2, 2));
598 for (vk, sk) in [(v1, s1), (v2, s2)] {
599 for i in 0..2 {
600 for j in 0..2 {
601 acc[[i, j]] += Complex::new(sk, 0.0) * vk[i] * vk[j].conj();
602 }
603 }
604 }
605 acc
606 };
607 Some(m.dot(&h_inv_sqrt))
608 }
609
610 fn build_canonical_gate(coeffs: &CartanCoefficients) -> Array2<Complex<f64>> {
612 let a = coeffs.xx;
614 let b = coeffs.yy;
615 let c = coeffs.zz;
616
617 let cos_a = a.cos();
619 let sin_a = a.sin();
620 let cos_b = b.cos();
621 let sin_b = b.sin();
622 let cos_c = c.cos();
623 let sin_c = c.sin();
624
625 let mut result = Array2::zeros((4, 4));
627
628 result[[0, 0]] = Complex::new(cos_a * cos_b * cos_c, sin_c);
630 result[[0, 3]] = Complex::new(0.0, sin_a * cos_b * cos_c);
631 result[[1, 1]] = Complex::new(cos_a * cos_c, -sin_a * sin_b * sin_c);
632 result[[1, 2]] = Complex::new(0.0, cos_a.mul_add(sin_c, sin_a * sin_b * cos_c));
633 result[[2, 1]] = Complex::new(0.0, cos_a.mul_add(sin_c, -(sin_a * sin_b * cos_c)));
634 result[[2, 2]] = Complex::new(cos_a * cos_c, sin_a * sin_b * sin_c);
635 result[[3, 0]] = Complex::new(0.0, sin_a * cos_b * cos_c);
636 result[[3, 3]] = Complex::new(cos_a * cos_b * cos_c, -sin_c);
637
638 result
639 }
640
641 fn single_qubit_matrix(decomp: &SingleQubitDecomposition) -> Array2<Complex<f64>> {
648 let rz = |theta: f64| -> Array2<Complex<f64>> {
649 let mut m = Array2::<Complex<f64>>::zeros((2, 2));
650 m[[0, 0]] = Complex::new(0.0, -theta / 2.0).exp();
651 m[[1, 1]] = Complex::new(0.0, theta / 2.0).exp();
652 m
653 };
654 let ry = |phi: f64| -> Array2<Complex<f64>> {
655 let c = (phi / 2.0).cos();
656 let s = (phi / 2.0).sin();
657 Array2::from_shape_vec(
658 (2, 2),
659 vec![
660 Complex::new(c, 0.0),
661 Complex::new(-s, 0.0),
662 Complex::new(s, 0.0),
663 Complex::new(c, 0.0),
664 ],
665 )
666 .unwrap_or_else(|_| Array2::eye(2))
667 };
668 let core = rz(decomp.theta2)
669 .dot(&ry(decomp.phi))
670 .dot(&rz(decomp.theta1));
671 core.mapv(|z| Complex::new(0.0, decomp.global_phase).exp() * z)
672 }
673
674 fn kron2(a: &Array2<Complex<f64>>, b: &Array2<Complex<f64>>) -> Array2<Complex<f64>> {
676 let mut out = Array2::<Complex<f64>>::zeros((4, 4));
677 for i in 0..2 {
678 for j in 0..2 {
679 for k in 0..2 {
680 for l in 0..2 {
681 out[[i * 2 + k, j * 2 + l]] = a[[i, j]] * b[[k, l]];
682 }
683 }
684 }
685 }
686 out
687 }
688
689 pub(crate) fn reconstruct_without_phase(decomp: &CartanDecomposition) -> Array2<Complex<f64>> {
692 let a1 = Self::single_qubit_matrix(&decomp.left_gates.0);
693 let b1 = Self::single_qubit_matrix(&decomp.left_gates.1);
694 let a2 = Self::single_qubit_matrix(&decomp.right_gates.0);
695 let b2 = Self::single_qubit_matrix(&decomp.right_gates.1);
696 let left = Self::kron2(&a1, &b1);
697 let right = Self::kron2(&a2, &b2);
698 let canonical = Self::build_canonical_gate(&decomp.interaction);
699 left.dot(&canonical).dot(&right)
700 }
701
702 fn compute_global_phase(
711 u: &Array2<Complex<f64>>,
712 left: &(SingleQubitDecomposition, SingleQubitDecomposition),
713 right: &(SingleQubitDecomposition, SingleQubitDecomposition),
714 coeffs: &CartanCoefficients,
715 ) -> QuantRS2Result<f64> {
716 let a1 = Self::single_qubit_matrix(&left.0);
718 let b1 = Self::single_qubit_matrix(&left.1);
719 let a2 = Self::single_qubit_matrix(&right.0);
720 let b2 = Self::single_qubit_matrix(&right.1);
721 let r = Self::kron2(&a1, &b1)
722 .dot(&Self::build_canonical_gate(coeffs))
723 .dot(&Self::kron2(&a2, &b2));
724
725 let mut hs = Complex::new(0.0, 0.0);
727 for i in 0..4 {
728 for j in 0..4 {
729 hs += r[[i, j]].conj() * u[[i, j]];
730 }
731 }
732 if hs.norm() < 1e-12 {
733 return Ok(0.0);
736 }
737 Ok(hs.arg())
738 }
739
740 pub fn to_gates(
742 &self,
743 decomp: &CartanDecomposition,
744 qubit_ids: &[QubitId],
745 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
746 if qubit_ids.len() != 2 {
747 return Err(QuantRS2Error::InvalidInput(
748 "Cartan decomposition requires exactly 2 qubits".to_string(),
749 ));
750 }
751
752 let q0 = qubit_ids[0];
753 let q1 = qubit_ids[1];
754 let mut gates: Vec<Box<dyn GateOp>> = Vec::new();
755
756 gates.extend(self.single_qubit_to_gates(&decomp.left_gates.0, q0));
758 gates.extend(self.single_qubit_to_gates(&decomp.left_gates.1, q1));
759
760 gates.extend(self.canonical_to_gates(&decomp.interaction, q0, q1)?);
762
763 gates.extend(self.single_qubit_to_gates(&decomp.right_gates.0, q0));
765 gates.extend(self.single_qubit_to_gates(&decomp.right_gates.1, q1));
766
767 Ok(gates)
768 }
769
770 fn single_qubit_to_gates(
772 &self,
773 decomp: &SingleQubitDecomposition,
774 qubit: QubitId,
775 ) -> Vec<Box<dyn GateOp>> {
776 let mut gates = Vec::new();
777
778 if decomp.theta1.abs() > self.tolerance {
779 gates.push(Box::new(RotationZ {
780 target: qubit,
781 theta: decomp.theta1,
782 }) as Box<dyn GateOp>);
783 }
784
785 if decomp.phi.abs() > self.tolerance {
786 gates.push(Box::new(RotationY {
787 target: qubit,
788 theta: decomp.phi,
789 }) as Box<dyn GateOp>);
790 }
791
792 if decomp.theta2.abs() > self.tolerance {
793 gates.push(Box::new(RotationZ {
794 target: qubit,
795 theta: decomp.theta2,
796 }) as Box<dyn GateOp>);
797 }
798
799 gates
800 }
801
802 fn canonical_to_gates(
804 &self,
805 coeffs: &CartanCoefficients,
806 q0: QubitId,
807 q1: QubitId,
808 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
809 let mut gates: Vec<Box<dyn GateOp>> = Vec::new();
810 let cnots = coeffs.cnot_count(self.tolerance);
811
812 match cnots {
813 0 => {
814 }
816 1 => {
817 gates.push(Box::new(CNOT {
819 control: q0,
820 target: q1,
821 }));
822 }
823 2 => {
824 if coeffs.xx.abs() > self.tolerance {
827 gates.push(Box::new(RotationX {
828 target: q0,
829 theta: coeffs.xx * 2.0,
830 }));
831 }
832
833 gates.push(Box::new(CNOT {
834 control: q0,
835 target: q1,
836 }));
837
838 if coeffs.zz.abs() > self.tolerance {
839 gates.push(Box::new(RotationZ {
840 target: q1,
841 theta: coeffs.zz * 2.0,
842 }));
843 }
844
845 gates.push(Box::new(CNOT {
846 control: q0,
847 target: q1,
848 }));
849 }
850 3 => {
851 gates.push(Box::new(CNOT {
853 control: q0,
854 target: q1,
855 }));
856
857 gates.push(Box::new(RotationZ {
858 target: q0,
859 theta: coeffs.xx * 2.0,
860 }));
861 gates.push(Box::new(RotationZ {
862 target: q1,
863 theta: coeffs.yy * 2.0,
864 }));
865
866 gates.push(Box::new(CNOT {
867 control: q1,
868 target: q0,
869 }));
870
871 gates.push(Box::new(RotationZ {
872 target: q0,
873 theta: coeffs.zz * 2.0,
874 }));
875
876 gates.push(Box::new(CNOT {
877 control: q0,
878 target: q1,
879 }));
880 }
881 _ => unreachable!("CNOT count should be 0-3"),
882 }
883
884 Ok(gates)
885 }
886}
887
888pub struct OptimizedCartanDecomposer {
890 pub base: CartanDecomposer,
891 optimize_special_cases: bool,
893 optimize_phase: bool,
895}
896
897impl OptimizedCartanDecomposer {
898 pub fn new() -> Self {
900 Self {
901 base: CartanDecomposer::new(),
902 optimize_special_cases: true,
903 optimize_phase: true,
904 }
905 }
906
907 pub fn decompose(
909 &mut self,
910 unitary: &Array2<Complex<f64>>,
911 ) -> QuantRS2Result<CartanDecomposition> {
912 if self.optimize_special_cases {
914 if let Some(special) = self.check_special_cases(unitary)? {
915 return Ok(special);
916 }
917 }
918
919 let mut decomp = self.base.decompose(unitary)?;
921
922 if self.optimize_phase {
924 self.optimize_global_phase(&mut decomp);
925 }
926
927 Ok(decomp)
928 }
929
930 fn check_special_cases(
932 &self,
933 unitary: &Array2<Complex<f64>>,
934 ) -> QuantRS2Result<Option<CartanDecomposition>> {
935 if self.is_cnot(unitary) {
937 return Ok(Some(Self::cnot_decomposition()));
938 }
939
940 if self.is_cz(unitary) {
942 return Ok(Some(Self::cz_decomposition()));
943 }
944
945 if self.is_swap(unitary) {
947 return Ok(Some(Self::swap_decomposition()));
948 }
949
950 Ok(None)
951 }
952
953 fn is_cnot(&self, u: &Array2<Complex<f64>>) -> bool {
955 let cnot = Array2::from_shape_vec(
956 (4, 4),
957 vec![
958 Complex::new(1.0, 0.0),
959 Complex::new(0.0, 0.0),
960 Complex::new(0.0, 0.0),
961 Complex::new(0.0, 0.0),
962 Complex::new(0.0, 0.0),
963 Complex::new(1.0, 0.0),
964 Complex::new(0.0, 0.0),
965 Complex::new(0.0, 0.0),
966 Complex::new(0.0, 0.0),
967 Complex::new(0.0, 0.0),
968 Complex::new(0.0, 0.0),
969 Complex::new(1.0, 0.0),
970 Complex::new(0.0, 0.0),
971 Complex::new(0.0, 0.0),
972 Complex::new(1.0, 0.0),
973 Complex::new(0.0, 0.0),
974 ],
975 )
976 .expect("Failed to create CNOT matrix in OptimizedCartanDecomposer::is_cnot");
977
978 self.matrices_equal(u, &cnot)
979 }
980
981 fn is_cz(&self, u: &Array2<Complex<f64>>) -> bool {
983 let cz = Array2::from_shape_vec(
984 (4, 4),
985 vec![
986 Complex::new(1.0, 0.0),
987 Complex::new(0.0, 0.0),
988 Complex::new(0.0, 0.0),
989 Complex::new(0.0, 0.0),
990 Complex::new(0.0, 0.0),
991 Complex::new(1.0, 0.0),
992 Complex::new(0.0, 0.0),
993 Complex::new(0.0, 0.0),
994 Complex::new(0.0, 0.0),
995 Complex::new(0.0, 0.0),
996 Complex::new(1.0, 0.0),
997 Complex::new(0.0, 0.0),
998 Complex::new(0.0, 0.0),
999 Complex::new(0.0, 0.0),
1000 Complex::new(0.0, 0.0),
1001 Complex::new(-1.0, 0.0),
1002 ],
1003 )
1004 .expect("Failed to create CZ matrix in OptimizedCartanDecomposer::is_cz");
1005
1006 self.matrices_equal(u, &cz)
1007 }
1008
1009 fn is_swap(&self, u: &Array2<Complex<f64>>) -> bool {
1011 let swap = Array2::from_shape_vec(
1012 (4, 4),
1013 vec![
1014 Complex::new(1.0, 0.0),
1015 Complex::new(0.0, 0.0),
1016 Complex::new(0.0, 0.0),
1017 Complex::new(0.0, 0.0),
1018 Complex::new(0.0, 0.0),
1019 Complex::new(0.0, 0.0),
1020 Complex::new(1.0, 0.0),
1021 Complex::new(0.0, 0.0),
1022 Complex::new(0.0, 0.0),
1023 Complex::new(1.0, 0.0),
1024 Complex::new(0.0, 0.0),
1025 Complex::new(0.0, 0.0),
1026 Complex::new(0.0, 0.0),
1027 Complex::new(0.0, 0.0),
1028 Complex::new(0.0, 0.0),
1029 Complex::new(1.0, 0.0),
1030 ],
1031 )
1032 .expect("Failed to create SWAP matrix in OptimizedCartanDecomposer::is_swap");
1033
1034 self.matrices_equal(u, &swap)
1035 }
1036
1037 fn matrices_equal(&self, a: &Array2<Complex<f64>>, b: &Array2<Complex<f64>>) -> bool {
1039 let mut phase = Complex::new(1.0, 0.0);
1041 for i in 0..4 {
1042 for j in 0..4 {
1043 if b[[i, j]].norm() > self.base.tolerance {
1044 phase = a[[i, j]] / b[[i, j]];
1045 break;
1046 }
1047 }
1048 }
1049
1050 for i in 0..4 {
1052 for j in 0..4 {
1053 if (a[[i, j]] - phase * b[[i, j]]).norm() > self.base.tolerance {
1054 return false;
1055 }
1056 }
1057 }
1058
1059 true
1060 }
1061
1062 fn cnot_decomposition() -> CartanDecomposition {
1064 let ident = Array2::eye(2);
1065 let ident_decomp = decompose_single_qubit_zyz(&ident.view()).expect(
1066 "Failed to decompose identity in OptimizedCartanDecomposer::cnot_decomposition",
1067 );
1068
1069 CartanDecomposition {
1070 left_gates: (ident_decomp.clone(), ident_decomp.clone()),
1071 right_gates: (ident_decomp.clone(), ident_decomp),
1072 interaction: CartanCoefficients::new(PI / 4.0, PI / 4.0, 0.0),
1073 global_phase: 0.0,
1074 }
1075 }
1076
1077 fn cz_decomposition() -> CartanDecomposition {
1079 let ident = Array2::eye(2);
1080 let ident_decomp = decompose_single_qubit_zyz(&ident.view())
1081 .expect("Failed to decompose identity in OptimizedCartanDecomposer::cz_decomposition");
1082
1083 CartanDecomposition {
1084 left_gates: (ident_decomp.clone(), ident_decomp.clone()),
1085 right_gates: (ident_decomp.clone(), ident_decomp),
1086 interaction: CartanCoefficients::new(0.0, 0.0, PI / 4.0),
1087 global_phase: 0.0,
1088 }
1089 }
1090
1091 fn swap_decomposition() -> CartanDecomposition {
1093 let ident = Array2::eye(2);
1094 let ident_decomp = decompose_single_qubit_zyz(&ident.view()).expect(
1095 "Failed to decompose identity in OptimizedCartanDecomposer::swap_decomposition",
1096 );
1097
1098 CartanDecomposition {
1099 left_gates: (ident_decomp.clone(), ident_decomp.clone()),
1100 right_gates: (ident_decomp.clone(), ident_decomp),
1101 interaction: CartanCoefficients::new(PI / 4.0, PI / 4.0, PI / 4.0),
1102 global_phase: 0.0,
1103 }
1104 }
1105
1106 fn optimize_global_phase(&self, decomp: &mut CartanDecomposition) {
1108 if decomp.global_phase.abs() > self.base.tolerance {
1110 decomp.left_gates.0.global_phase += decomp.global_phase;
1111 decomp.global_phase = 0.0;
1112 }
1113 }
1114}
1115
1116pub fn cartan_decompose(unitary: &Array2<Complex<f64>>) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
1118 let mut decomposer = CartanDecomposer::new();
1119 let decomp = decomposer.decompose(unitary)?;
1120 let qubit_ids = vec![QubitId(0), QubitId(1)];
1121 decomposer.to_gates(&decomp, &qubit_ids)
1122}
1123
1124impl Default for OptimizedCartanDecomposer {
1125 fn default() -> Self {
1126 Self::new()
1127 }
1128}
1129
1130impl Default for CartanDecomposer {
1131 fn default() -> Self {
1132 Self::new()
1133 }
1134}
1135
1136#[cfg(test)]
1137mod tests {
1138 use super::*;
1139 use scirs2_core::Complex;
1140
1141 #[test]
1142 fn test_cartan_coefficients() {
1143 let coeffs = CartanCoefficients::new(0.1, 0.2, 0.3);
1144 assert!(!coeffs.is_identity(1e-10));
1145 assert_eq!(coeffs.cnot_count(1e-10), 3);
1146
1147 let zero_coeffs = CartanCoefficients::new(0.0, 0.0, 0.0);
1148 assert!(zero_coeffs.is_identity(1e-10));
1149 assert_eq!(zero_coeffs.cnot_count(1e-10), 0);
1150 }
1151
1152 #[test]
1153 fn test_cartan_cnot() {
1154 let mut decomposer = CartanDecomposer::new();
1155
1156 let cnot = Array2::from_shape_vec(
1158 (4, 4),
1159 vec![
1160 Complex::new(1.0, 0.0),
1161 Complex::new(0.0, 0.0),
1162 Complex::new(0.0, 0.0),
1163 Complex::new(0.0, 0.0),
1164 Complex::new(0.0, 0.0),
1165 Complex::new(1.0, 0.0),
1166 Complex::new(0.0, 0.0),
1167 Complex::new(0.0, 0.0),
1168 Complex::new(0.0, 0.0),
1169 Complex::new(0.0, 0.0),
1170 Complex::new(0.0, 0.0),
1171 Complex::new(1.0, 0.0),
1172 Complex::new(0.0, 0.0),
1173 Complex::new(0.0, 0.0),
1174 Complex::new(1.0, 0.0),
1175 Complex::new(0.0, 0.0),
1176 ],
1177 )
1178 .expect("Failed to create CNOT matrix in test_cartan_cnot");
1179
1180 let decomp = decomposer
1181 .decompose(&cnot)
1182 .expect("Failed to decompose CNOT in test_cartan_cnot");
1183
1184 assert!(decomp.interaction.cnot_count(1e-10) <= 1);
1186 }
1187
1188 #[test]
1189 fn test_optimized_special_cases() {
1190 let mut opt_decomposer = OptimizedCartanDecomposer::new();
1191
1192 let swap = Array2::from_shape_vec(
1194 (4, 4),
1195 vec![
1196 Complex::new(1.0, 0.0),
1197 Complex::new(0.0, 0.0),
1198 Complex::new(0.0, 0.0),
1199 Complex::new(0.0, 0.0),
1200 Complex::new(0.0, 0.0),
1201 Complex::new(0.0, 0.0),
1202 Complex::new(1.0, 0.0),
1203 Complex::new(0.0, 0.0),
1204 Complex::new(0.0, 0.0),
1205 Complex::new(1.0, 0.0),
1206 Complex::new(0.0, 0.0),
1207 Complex::new(0.0, 0.0),
1208 Complex::new(0.0, 0.0),
1209 Complex::new(0.0, 0.0),
1210 Complex::new(0.0, 0.0),
1211 Complex::new(1.0, 0.0),
1212 ],
1213 )
1214 .expect("Failed to create SWAP matrix in test_optimized_special_cases");
1215
1216 let decomp = opt_decomposer
1217 .decompose(&swap)
1218 .expect("Failed to decompose SWAP in test_optimized_special_cases");
1219
1220 assert_eq!(decomp.interaction.cnot_count(1e-10), 3);
1222 }
1223
1224 #[test]
1225 fn test_cartan_identity() {
1226 let mut decomposer = CartanDecomposer::new();
1227
1228 let identity = Array2::eye(4);
1230 let identity_complex = identity.mapv(|x| Complex::new(x, 0.0));
1231
1232 let decomp = decomposer
1233 .decompose(&identity_complex)
1234 .expect("Failed to decompose identity in test_cartan_identity");
1235
1236 assert!(decomp.interaction.is_identity(1e-10));
1238 assert_eq!(decomp.interaction.cnot_count(1e-10), 0);
1239 }
1240
1241 fn su2(theta1: f64, phi: f64, theta2: f64) -> Array2<Complex<f64>> {
1245 let rz = |t: f64| {
1246 let mut m = Array2::<Complex<f64>>::zeros((2, 2));
1247 m[[0, 0]] = Complex::new(0.0, -t / 2.0).exp();
1248 m[[1, 1]] = Complex::new(0.0, t / 2.0).exp();
1249 m
1250 };
1251 let c = (phi / 2.0).cos();
1252 let s = (phi / 2.0).sin();
1253 let ry = Array2::from_shape_vec(
1254 (2, 2),
1255 vec![
1256 Complex::new(c, 0.0),
1257 Complex::new(-s, 0.0),
1258 Complex::new(s, 0.0),
1259 Complex::new(c, 0.0),
1260 ],
1261 )
1262 .expect("2x2 Ry");
1263 rz(theta2).dot(&ry).dot(&rz(theta1))
1264 }
1265
1266 fn kron4(a: &Array2<Complex<f64>>, b: &Array2<Complex<f64>>) -> Array2<Complex<f64>> {
1267 let mut out = Array2::<Complex<f64>>::zeros((4, 4));
1268 for i in 0..2 {
1269 for j in 0..2 {
1270 for k in 0..2 {
1271 for l in 0..2 {
1272 out[[i * 2 + k, j * 2 + l]] = a[[i, j]] * b[[k, l]];
1273 }
1274 }
1275 }
1276 }
1277 out
1278 }
1279
1280 fn frob_diff(a: &Array2<Complex<f64>>, b: &Array2<Complex<f64>>) -> f64 {
1281 a.iter()
1282 .zip(b.iter())
1283 .map(|(x, y)| (x - y).norm_sqr())
1284 .sum::<f64>()
1285 .sqrt()
1286 }
1287
1288 fn make_decomp(
1291 a1: &Array2<Complex<f64>>,
1292 b1: &Array2<Complex<f64>>,
1293 a2: &Array2<Complex<f64>>,
1294 b2: &Array2<Complex<f64>>,
1295 coeffs: CartanCoefficients,
1296 ) -> CartanDecomposition {
1297 CartanDecomposition {
1298 left_gates: (
1299 decompose_single_qubit_zyz(&a1.view()).expect("a1 zyz"),
1300 decompose_single_qubit_zyz(&b1.view()).expect("b1 zyz"),
1301 ),
1302 right_gates: (
1303 decompose_single_qubit_zyz(&a2.view()).expect("a2 zyz"),
1304 decompose_single_qubit_zyz(&b2.view()).expect("b2 zyz"),
1305 ),
1306 interaction: coeffs,
1307 global_phase: 0.0,
1308 }
1309 }
1310
1311 #[test]
1317 fn test_cartan_global_phase_recovered() {
1318 let a1 = su2(0.7, 1.1, -0.4);
1319 let b1 = su2(-0.3, 0.9, 1.3);
1320 let a2 = su2(0.2, 0.5, 0.1);
1321 let b2 = su2(0.4, 0.3, -0.2);
1322 let coeffs = CartanCoefficients::new(0.31, 0.17, -0.05);
1323
1324 let decomp = make_decomp(&a1, &b1, &a2, &b2, coeffs);
1325 let r = CartanDecomposer::reconstruct_without_phase(&decomp);
1326
1327 for &phi0 in &[
1328 0.0,
1329 0.37,
1330 std::f64::consts::PI / 3.0,
1331 -2.1,
1332 std::f64::consts::PI,
1333 ] {
1334 let u = r.mapv(|z| Complex::new(0.0, phi0).exp() * z);
1335 let phi = CartanDecomposer::compute_global_phase(
1336 &u,
1337 &decomp.left_gates,
1338 &decomp.right_gates,
1339 &decomp.interaction,
1340 )
1341 .expect("global phase");
1342 let recon = r.mapv(|z| Complex::new(0.0, phi).exp() * z);
1343 let err = frob_diff(&recon, &u);
1344 assert!(
1345 err < 1e-10,
1346 "global phase recovery failed for phi0={phi0}: recovered phi={phi}, err={err}"
1347 );
1348 }
1349 }
1350
1351 #[test]
1354 fn test_cartan_global_phase_nonzero() {
1355 let a1 = su2(0.2, 0.5, 0.1);
1356 let b1 = su2(0.4, 0.3, -0.2);
1357 let ident = Array2::<Complex<f64>>::eye(2);
1358 let decomp = make_decomp(
1359 &a1,
1360 &b1,
1361 &ident,
1362 &ident,
1363 CartanCoefficients::new(0.0, 0.0, 0.0),
1364 );
1365 let r = CartanDecomposer::reconstruct_without_phase(&decomp);
1366 let phi0 = 1.234_f64;
1367 let u = r.mapv(|z| Complex::new(0.0, phi0).exp() * z);
1368 let phi = CartanDecomposer::compute_global_phase(
1369 &u,
1370 &decomp.left_gates,
1371 &decomp.right_gates,
1372 &decomp.interaction,
1373 )
1374 .expect("global phase");
1375 assert!(
1376 phi.abs() > 1e-6,
1377 "expected non-zero global phase, got {phi}"
1378 );
1379 }
1380
1381 #[test]
1387 fn test_cartan_recompose_identity() {
1388 let mut decomposer = CartanDecomposer::new();
1389 let u = Array2::<Complex<f64>>::eye(4);
1390 let decomp = decomposer.decompose(&u).expect("decompose identity");
1391 let r = CartanDecomposer::reconstruct_without_phase(&decomp);
1392 let recon = r.mapv(|z| Complex::new(0.0, decomp.global_phase).exp() * z);
1393 assert!(frob_diff(&recon, &u) < 1e-8);
1394 }
1395
1396 #[test]
1401 fn test_factor_tensor_product() {
1402 let a = su2(0.7, 1.1, -0.4);
1403 let b = su2(-0.3, 0.9, 1.3);
1404 let u = kron4(&a, &b);
1405 let (fa, fb) =
1406 CartanDecomposer::factor_tensor_product(&u, 1e-10).expect("should factor A ⊗ B");
1407 let recon = kron4(&fa, &fb);
1408 let err = frob_diff(&recon, &u);
1411 assert!(err < 1e-8, "tensor factorisation error {err} exceeds 1e-8");
1412 }
1413}