1use crate::{
8 cartan::{CartanDecomposer, CartanDecomposition},
9 error::{QuantRS2Error, QuantRS2Result},
10 gate::{multi::*, single::*, GateOp},
11 matrix_ops::{DenseMatrix, QuantumMatrix},
12 qubit::QubitId,
13 shannon::ShannonDecomposer,
14 synthesis::{decompose_single_qubit_zyz, SingleQubitDecomposition},
15};
16use rustc_hash::FxHashMap;
17use scirs2_core::ndarray::{s, Array2};
18use scirs2_core::Complex;
19
20fn complex_svd(
28 m: &Array2<Complex<f64>>,
29) -> QuantRS2Result<(Array2<Complex<f64>>, Vec<f64>, Array2<Complex<f64>>)> {
30 let (rows, cols) = (m.nrows(), m.ncols());
31 if rows != cols {
32 return Err(QuantRS2Error::InvalidInput(
33 "complex_svd helper expects a square matrix".to_string(),
34 ));
35 }
36 let n = rows;
37
38 let mut a = m.clone(); let mut v = Array2::<Complex<f64>>::eye(n); let eps = 1e-15;
42 let max_sweeps = 60;
43 for _sweep in 0..max_sweeps {
44 let mut off = 0.0f64;
45 for i in 0..n {
46 for j in (i + 1)..n {
47 let mut alpha = 0.0f64;
48 let mut beta = 0.0f64;
49 let mut gamma = Complex::new(0.0, 0.0);
50 for r in 0..n {
51 let ai = a[[r, i]];
52 let aj = a[[r, j]];
53 alpha += ai.norm_sqr();
54 beta += aj.norm_sqr();
55 gamma += ai.conj() * aj;
56 }
57 let gamma_abs = gamma.norm();
58 off += gamma_abs;
59 if gamma_abs <= eps * (alpha.sqrt() * beta.sqrt()).max(eps) {
60 continue;
61 }
62 let phase = gamma / gamma_abs;
63 let zeta = (beta - alpha) / (2.0 * gamma_abs);
64 let t = zeta.signum() / (zeta.abs() + (1.0 + zeta * zeta).sqrt());
65 let cval = 1.0 / (1.0 + t * t).sqrt();
66 let s_ij = phase * Complex::new(cval * t, 0.0);
67 for r in 0..n {
68 let ai = a[[r, i]];
69 let aj = a[[r, j]];
70 a[[r, i]] = Complex::new(cval, 0.0) * ai - s_ij.conj() * aj;
71 a[[r, j]] = s_ij * ai + Complex::new(cval, 0.0) * aj;
72 }
73 for r in 0..n {
74 let vi = v[[r, i]];
75 let vj = v[[r, j]];
76 v[[r, i]] = Complex::new(cval, 0.0) * vi - s_ij.conj() * vj;
77 v[[r, j]] = s_ij * vi + Complex::new(cval, 0.0) * vj;
78 }
79 }
80 }
81 if off <= eps {
82 break;
83 }
84 }
85
86 let mut order: Vec<(f64, usize)> = (0..n)
88 .map(|j| {
89 let norm = (0..n).map(|r| a[[r, j]].norm_sqr()).sum::<f64>().sqrt();
90 (norm, j)
91 })
92 .collect();
93 order.sort_by(|x, y| y.0.total_cmp(&x.0));
94
95 let mut u = Array2::<Complex<f64>>::zeros((n, n));
96 let mut s_vec = vec![0.0f64; n];
97 let mut v_sorted = Array2::<Complex<f64>>::zeros((n, n));
98 let mut zero_cols: Vec<usize> = Vec::new();
99 for (new_idx, &(norm, old_idx)) in order.iter().enumerate() {
100 s_vec[new_idx] = norm;
101 if norm > 1e-300 {
102 for r in 0..n {
103 u[[r, new_idx]] = a[[r, old_idx]] / Complex::new(norm, 0.0);
104 }
105 } else {
106 zero_cols.push(new_idx);
107 }
108 for r in 0..n {
109 v_sorted[[r, new_idx]] = v[[r, old_idx]];
110 }
111 }
112 if !zero_cols.is_empty() {
115 complete_orthonormal_columns(&mut u, &zero_cols, 1e-12);
116 }
117
118 let vh = v_sorted.mapv(|z| z.conj()).t().to_owned();
119 Ok((u, s_vec, vh))
120}
121
122fn complete_orthonormal_columns(mat: &mut Array2<Complex<f64>>, columns: &[usize], tol: f64) {
128 let n = mat.nrows();
129 let fixed: std::collections::HashSet<usize> = columns.iter().copied().collect();
130
131 let mut basis: Vec<Vec<Complex<f64>>> = Vec::new();
133 for j in 0..n {
134 if !fixed.contains(&j) {
135 basis.push((0..n).map(|r| mat[[r, j]]).collect());
136 }
137 }
138
139 let mut seed = 0usize;
140 for &col in columns {
141 let mut placed = false;
144 while seed < n && !placed {
145 let mut v = vec![Complex::new(0.0, 0.0); n];
146 v[seed] = Complex::new(1.0, 0.0);
147 for b in &basis {
148 let proj: Complex<f64> =
149 b.iter().zip(v.iter()).map(|(bk, vk)| bk.conj() * vk).sum();
150 for r in 0..n {
151 v[r] -= proj * b[r];
152 }
153 }
154 let norm = v.iter().map(|z| z.norm_sqr()).sum::<f64>().sqrt();
155 if norm > tol {
156 for r in 0..n {
157 v[r] /= Complex::new(norm, 0.0);
158 }
159 for r in 0..n {
160 mat[[r, col]] = v[r];
161 }
162 basis.push(v);
163 placed = true;
164 }
165 seed += 1;
166 }
167 if !placed {
168 mat[[col % n, col]] = Complex::new(1.0, 0.0);
170 }
171 }
172}
173
174#[derive(Debug, Clone)]
176pub struct MultiQubitKAK {
177 pub gates: Vec<Box<dyn GateOp>>,
179 pub tree: DecompositionTree,
181 pub cnot_count: usize,
183 pub single_qubit_count: usize,
185 pub depth: usize,
187}
188
189#[derive(Debug, Clone)]
191pub enum DecompositionTree {
192 Leaf {
194 qubits: Vec<QubitId>,
195 gate_type: LeafType,
196 },
197 Node {
199 qubits: Vec<QubitId>,
200 method: DecompositionMethod,
201 children: Vec<Self>,
202 },
203}
204
205#[derive(Debug, Clone)]
207pub enum LeafType {
208 SingleQubit(SingleQubitDecomposition),
209 TwoQubit(CartanDecomposition),
210}
211
212#[derive(Debug, Clone)]
214pub enum DecompositionMethod {
215 CSD { pivot: usize },
217 Shannon { partition: usize },
219 BlockDiagonal { block_size: usize },
221 Cartan,
223}
224
225pub struct MultiQubitKAKDecomposer {
227 tolerance: f64,
229 max_depth: usize,
231 #[allow(dead_code)]
233 cache: FxHashMap<u64, MultiQubitKAK>,
234 use_optimization: bool,
236 cartan: CartanDecomposer,
238}
239
240impl MultiQubitKAKDecomposer {
241 pub fn new() -> Self {
243 Self {
244 tolerance: 1e-10,
245 max_depth: 20,
246 cache: FxHashMap::default(),
247 use_optimization: true,
248 cartan: CartanDecomposer::new(),
249 }
250 }
251
252 pub fn with_tolerance(tolerance: f64) -> Self {
254 Self {
255 tolerance,
256 max_depth: 20,
257 cache: FxHashMap::default(),
258 use_optimization: true,
259 cartan: CartanDecomposer::with_tolerance(tolerance),
260 }
261 }
262
263 pub fn decompose(
265 &mut self,
266 unitary: &Array2<Complex<f64>>,
267 qubit_ids: &[QubitId],
268 ) -> QuantRS2Result<MultiQubitKAK> {
269 let n = qubit_ids.len();
270 let size = 1 << n;
271
272 if unitary.shape() != [size, size] {
274 return Err(QuantRS2Error::InvalidInput(format!(
275 "Unitary size {} doesn't match {} qubits",
276 unitary.shape()[0],
277 n
278 )));
279 }
280
281 let mat = DenseMatrix::new(unitary.clone())?;
283 if !mat.is_unitary(self.tolerance)? {
284 return Err(QuantRS2Error::InvalidInput(
285 "Matrix is not unitary".to_string(),
286 ));
287 }
288
289 if let Some(cached) = self.check_cache(unitary) {
291 return Ok(cached.clone());
292 }
293
294 let (tree, gates) = self.decompose_recursive(unitary, qubit_ids, 0)?;
296
297 let mut cnot_count = 0;
299 let mut single_qubit_count = 0;
300
301 for gate in &gates {
302 match gate.name() {
303 "CNOT" | "CZ" | "SWAP" => cnot_count += self.count_cnots(gate.name()),
304 _ => single_qubit_count += 1,
305 }
306 }
307
308 let depth = Self::calculate_circuit_depth(&gates);
312
313 let result = MultiQubitKAK {
314 gates,
315 tree,
316 cnot_count,
317 single_qubit_count,
318 depth,
319 };
320
321 self.cache_result(unitary, &result);
323
324 Ok(result)
325 }
326
327 fn decompose_recursive(
329 &mut self,
330 unitary: &Array2<Complex<f64>>,
331 qubit_ids: &[QubitId],
332 depth: usize,
333 ) -> QuantRS2Result<(DecompositionTree, Vec<Box<dyn GateOp>>)> {
334 if depth > self.max_depth {
335 return Err(QuantRS2Error::InvalidInput(
336 "Maximum recursion depth exceeded".to_string(),
337 ));
338 }
339
340 let n = qubit_ids.len();
341
342 match n {
344 0 => {
345 let tree = DecompositionTree::Leaf {
346 qubits: vec![],
347 gate_type: LeafType::SingleQubit(SingleQubitDecomposition {
348 global_phase: 0.0,
349 theta1: 0.0,
350 phi: 0.0,
351 theta2: 0.0,
352 basis: "ZYZ".to_string(),
353 }),
354 };
355 Ok((tree, vec![]))
356 }
357 1 => {
358 let decomp = decompose_single_qubit_zyz(&unitary.view())?;
359 let gates = self.single_qubit_to_gates(&decomp, qubit_ids[0]);
360 let tree = DecompositionTree::Leaf {
361 qubits: qubit_ids.to_vec(),
362 gate_type: LeafType::SingleQubit(decomp),
363 };
364 Ok((tree, gates))
365 }
366 2 => {
367 let decomp = self.cartan.decompose(unitary)?;
368 let gates = self.cartan.to_gates(&decomp, qubit_ids)?;
369 let tree = DecompositionTree::Leaf {
370 qubits: qubit_ids.to_vec(),
371 gate_type: LeafType::TwoQubit(decomp),
372 };
373 Ok((tree, gates))
374 }
375 _ => {
376 let method = self.choose_decomposition_method(unitary, n);
378
379 match method {
380 DecompositionMethod::CSD { pivot } => {
381 self.decompose_csd(unitary, qubit_ids, pivot, depth)
382 }
383 DecompositionMethod::Shannon { partition } => {
384 self.decompose_shannon(unitary, qubit_ids, partition, depth)
385 }
386 DecompositionMethod::BlockDiagonal { block_size } => {
387 self.decompose_block_diagonal(unitary, qubit_ids, block_size, depth)
388 }
389 DecompositionMethod::Cartan => unreachable!("Invalid method for n > 2"),
390 }
391 }
392 }
393 }
394
395 fn choose_decomposition_method(
397 &self,
398 unitary: &Array2<Complex<f64>>,
399 n: usize,
400 ) -> DecompositionMethod {
401 if self.use_optimization {
402 if self.has_block_structure(unitary, n) {
404 DecompositionMethod::BlockDiagonal { block_size: n / 2 }
405 } else if n % 2 == 0 {
406 DecompositionMethod::CSD { pivot: n / 2 }
408 } else {
409 DecompositionMethod::Shannon { partition: n / 2 }
411 }
412 } else {
413 DecompositionMethod::CSD { pivot: n / 2 }
415 }
416 }
417
418 fn decompose_csd(
420 &mut self,
421 unitary: &Array2<Complex<f64>>,
422 qubit_ids: &[QubitId],
423 pivot: usize,
424 depth: usize,
425 ) -> QuantRS2Result<(DecompositionTree, Vec<Box<dyn GateOp>>)> {
426 let n = qubit_ids.len();
427 let pivot_size = 1 << pivot;
429
430 let a = unitary.slice(s![..pivot_size, ..pivot_size]).to_owned();
434 let b = unitary.slice(s![..pivot_size, pivot_size..]).to_owned();
435 let c = unitary.slice(s![pivot_size.., ..pivot_size]).to_owned();
436 let d = unitary.slice(s![pivot_size.., pivot_size..]).to_owned();
437
438 let (u1, v1, sigma, u2, v2) = self.compute_csd(&a, &b, &c, &d)?;
444
445 let mut gates = Vec::new();
446 let mut children = Vec::new();
447
448 let left_qubits = &qubit_ids[..pivot];
450 let right_qubits = &qubit_ids[pivot..];
451
452 let (u2_tree, u2_gates) = self.decompose_recursive(&u2, left_qubits, depth + 1)?;
453 let (v2_tree, v2_gates) = self.decompose_recursive(&v2, right_qubits, depth + 1)?;
454
455 gates.extend(u2_gates);
456 gates.extend(v2_gates);
457 children.push(u2_tree);
458 children.push(v2_tree);
459
460 let diag_gates = self.diagonal_to_gates(&sigma, qubit_ids)?;
462 gates.extend(diag_gates);
463
464 let (u1_tree, u1_gates) = self.decompose_recursive(&u1, left_qubits, depth + 1)?;
466 let (v1_tree, v1_gates) = self.decompose_recursive(&v1, right_qubits, depth + 1)?;
467
468 gates.extend(u1_gates);
469 gates.extend(v1_gates);
470 children.push(u1_tree);
471 children.push(v1_tree);
472
473 let tree = DecompositionTree::Node {
474 qubits: qubit_ids.to_vec(),
475 method: DecompositionMethod::CSD { pivot },
476 children,
477 };
478
479 Ok((tree, gates))
480 }
481
482 fn decompose_shannon(
484 &self,
485 unitary: &Array2<Complex<f64>>,
486 qubit_ids: &[QubitId],
487 partition: usize,
488 _depth: usize,
489 ) -> QuantRS2Result<(DecompositionTree, Vec<Box<dyn GateOp>>)> {
490 let mut shannon = ShannonDecomposer::new();
492 let decomp = shannon.decompose(unitary, qubit_ids)?;
493
494 let tree = DecompositionTree::Node {
496 qubits: qubit_ids.to_vec(),
497 method: DecompositionMethod::Shannon { partition },
498 children: vec![], };
500
501 Ok((tree, decomp.gates))
502 }
503
504 fn decompose_block_diagonal(
506 &mut self,
507 unitary: &Array2<Complex<f64>>,
508 qubit_ids: &[QubitId],
509 block_size: usize,
510 depth: usize,
511 ) -> QuantRS2Result<(DecompositionTree, Vec<Box<dyn GateOp>>)> {
512 let n = qubit_ids.len();
513 let num_blocks = n / block_size;
514
515 let mut gates = Vec::new();
516 let mut children = Vec::new();
517
518 for i in 0..num_blocks {
520 let start = i * block_size;
521 let end = (i + 1) * block_size;
522 let block_qubits = &qubit_ids[start..end];
523
524 let block = self.extract_block(unitary, i, block_size)?;
526
527 let (block_tree, block_gates) =
528 self.decompose_recursive(&block, block_qubits, depth + 1)?;
529 gates.extend(block_gates);
530 children.push(block_tree);
531 }
532
533 let tree = DecompositionTree::Node {
534 qubits: qubit_ids.to_vec(),
535 method: DecompositionMethod::BlockDiagonal { block_size },
536 children,
537 };
538
539 Ok((tree, gates))
540 }
541
542 fn compute_csd(
567 &self,
568 a: &Array2<Complex<f64>>,
569 b: &Array2<Complex<f64>>,
570 c: &Array2<Complex<f64>>,
571 d: &Array2<Complex<f64>>,
572 ) -> QuantRS2Result<(
573 Array2<Complex<f64>>, Array2<Complex<f64>>, Array2<Complex<f64>>, Array2<Complex<f64>>, Array2<Complex<f64>>, )> {
579 let n = a.shape()[0];
580 let tol = self.tolerance.max(1e-12);
581
582 let (u1, cos_vals, v1h) = complex_svd(a)?;
584 let v1 = v1h.mapv(|z| z.conj()).t().to_owned(); let cos: Vec<f64> = cos_vals.iter().map(|&s| s.clamp(0.0, 1.0)).collect();
588
589 let m = c.dot(&v1);
593 let mut sin = vec![0.0f64; n];
594 for k in 0..n {
595 let col_norm = (0..n).map(|r| m[[r, k]].norm_sqr()).sum::<f64>().sqrt();
596 sin[k] = if col_norm > tol {
599 col_norm
600 } else {
601 (1.0 - cos[k] * cos[k]).max(0.0).sqrt()
602 };
603 }
604
605 let mut u2 = Array2::<Complex<f64>>::zeros((n, n));
608 let mut needs_completion: Vec<usize> = Vec::new();
609 for k in 0..n {
610 if sin[k] > tol {
611 for r in 0..n {
612 u2[[r, k]] = -m[[r, k]] / Complex::new(sin[k], 0.0);
613 }
614 } else {
615 needs_completion.push(k);
616 }
617 }
618 if !needs_completion.is_empty() {
619 complete_orthonormal_columns(&mut u2, &needs_completion, tol);
620 }
621
622 let u2h = u2.mapv(|z| z.conj()).t().to_owned();
625 let u1h = u1.mapv(|z| z.conj()).t().to_owned();
626 let u2h_d = u2h.dot(d); let u1h_b = u1h.dot(b); let mut v2h = Array2::<Complex<f64>>::zeros((n, n));
629 for k in 0..n {
630 if cos[k] > tol {
631 for col in 0..n {
632 v2h[[k, col]] = u2h_d[[k, col]] / Complex::new(cos[k], 0.0);
633 }
634 } else if sin[k] > tol {
635 for col in 0..n {
636 v2h[[k, col]] = u1h_b[[k, col]] / Complex::new(sin[k], 0.0);
637 }
638 } else {
639 return Err(QuantRS2Error::UnsupportedOperation(
641 "cosine-sine decomposition encountered a degenerate angle (cos=sin=0)"
642 .to_string(),
643 ));
644 }
645 }
646 let v2 = v2h.mapv(|z| z.conj()).t().to_owned();
647
648 let mut sigma = Array2::<Complex<f64>>::zeros((2 * n, 2 * n));
650 for k in 0..n {
651 sigma[[k, k]] = Complex::new(cos[k], 0.0);
652 sigma[[k, n + k]] = Complex::new(sin[k], 0.0);
653 sigma[[n + k, k]] = Complex::new(-sin[k], 0.0);
654 sigma[[n + k, n + k]] = Complex::new(cos[k], 0.0);
655 }
656
657 let recon = Self::assemble_from_csd(&u1, &u2, &sigma, &v1, &v2);
659 let mut max_err = 0.0f64;
660 for (i, j, expected) in [(0usize, 0usize, a), (0, 1, b), (1, 0, c), (1, 1, d)]
661 .iter()
662 .flat_map(|&(bi, bj, blk)| {
663 (0..n).flat_map(move |r| {
664 (0..n).map(move |col| (bi * n + r, bj * n + col, blk[[r, col]]))
665 })
666 })
667 {
668 max_err = max_err.max((recon[[i, j]] - expected).norm());
669 }
670
671 if max_err > 1e-7 {
672 return Err(QuantRS2Error::UnsupportedOperation(format!(
673 "cosine-sine decomposition for this matrix is not yet supported \
674 (reconstruction error {max_err:.3e})"
675 )));
676 }
677
678 Ok((u1, v1, sigma, u2, v2))
679 }
680
681 fn assemble_from_csd(
683 u1: &Array2<Complex<f64>>,
684 u2: &Array2<Complex<f64>>,
685 sigma: &Array2<Complex<f64>>,
686 v1: &Array2<Complex<f64>>,
687 v2: &Array2<Complex<f64>>,
688 ) -> Array2<Complex<f64>> {
689 let n = u1.nrows();
690 let mut left = Array2::<Complex<f64>>::zeros((2 * n, 2 * n));
691 left.slice_mut(s![..n, ..n]).assign(u1);
692 left.slice_mut(s![n.., n..]).assign(u2);
693
694 let mut right_dag = Array2::<Complex<f64>>::zeros((2 * n, 2 * n));
695 let v1h = v1.mapv(|z| z.conj()).t().to_owned();
696 let v2h = v2.mapv(|z| z.conj()).t().to_owned();
697 right_dag.slice_mut(s![..n, ..n]).assign(&v1h);
698 right_dag.slice_mut(s![n.., n..]).assign(&v2h);
699
700 left.dot(sigma).dot(&right_dag)
701 }
702
703 fn diagonal_to_gates(
705 &self,
706 diagonal: &Array2<Complex<f64>>,
707 qubit_ids: &[QubitId],
708 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
709 let mut gates = Vec::new();
710
711 let n = diagonal.shape()[0];
713 for i in 0..n {
714 let phase = diagonal[[i, i]].arg();
715 if phase.abs() > self.tolerance {
716 let mut control_pattern = Vec::new();
718 let mut temp = i;
719 for j in 0..qubit_ids.len() {
720 if temp & 1 == 1 {
721 control_pattern.push(j);
722 }
723 temp >>= 1;
724 }
725
726 if control_pattern.is_empty() {
728 } else if control_pattern.len() == 1 {
730 gates.push(Box::new(RotationZ {
732 target: qubit_ids[control_pattern[0]],
733 theta: phase,
734 }) as Box<dyn GateOp>);
735 } else {
736 let target_idx = control_pattern.pop().unwrap_or(0);
740 let target = qubit_ids[target_idx];
741 for &control_idx in &control_pattern {
742 gates.push(Box::new(CNOT {
743 control: qubit_ids[control_idx],
744 target,
745 }));
746 }
747
748 gates.push(Box::new(RotationZ {
749 target,
750 theta: phase,
751 }) as Box<dyn GateOp>);
752
753 for &control_idx in control_pattern.iter().rev() {
755 gates.push(Box::new(CNOT {
756 control: qubit_ids[control_idx],
757 target,
758 }));
759 }
760 }
761 }
762 }
763
764 Ok(gates)
765 }
766
767 fn has_block_structure(&self, unitary: &Array2<Complex<f64>>, _n: usize) -> bool {
769 let size = unitary.shape()[0];
771 let block_size = size / 2;
772
773 let mut off_diagonal_norm = 0.0;
774
775 for i in 0..block_size {
777 for j in block_size..size {
778 off_diagonal_norm += unitary[[i, j]].norm_sqr();
779 }
780 }
781
782 for i in block_size..size {
784 for j in 0..block_size {
785 off_diagonal_norm += unitary[[i, j]].norm_sqr();
786 }
787 }
788
789 off_diagonal_norm.sqrt() < self.tolerance
790 }
791
792 fn extract_block(
794 &self,
795 unitary: &Array2<Complex<f64>>,
796 block_idx: usize,
797 block_size: usize,
798 ) -> QuantRS2Result<Array2<Complex<f64>>> {
799 let size = 1 << block_size;
800 let start = block_idx * size;
801 let end = (block_idx + 1) * size;
802
803 Ok(unitary.slice(s![start..end, start..end]).to_owned())
804 }
805
806 fn single_qubit_to_gates(
808 &self,
809 decomp: &SingleQubitDecomposition,
810 qubit: QubitId,
811 ) -> Vec<Box<dyn GateOp>> {
812 let mut gates = Vec::new();
813
814 if decomp.theta1.abs() > self.tolerance {
815 gates.push(Box::new(RotationZ {
816 target: qubit,
817 theta: decomp.theta1,
818 }) as Box<dyn GateOp>);
819 }
820
821 if decomp.phi.abs() > self.tolerance {
822 gates.push(Box::new(RotationY {
823 target: qubit,
824 theta: decomp.phi,
825 }) as Box<dyn GateOp>);
826 }
827
828 if decomp.theta2.abs() > self.tolerance {
829 gates.push(Box::new(RotationZ {
830 target: qubit,
831 theta: decomp.theta2,
832 }) as Box<dyn GateOp>);
833 }
834
835 gates
836 }
837
838 fn count_cnots(&self, gate_name: &str) -> usize {
840 match gate_name {
841 "CNOT" | "CZ" => 1, "SWAP" => 3, _ => 0,
844 }
845 }
846
847 fn calculate_circuit_depth(gates: &[Box<dyn GateOp>]) -> usize {
855 if gates.is_empty() {
856 return 0;
857 }
858
859 let mut depth_at: Vec<usize> = vec![0; gates.len()];
861 let mut last_qubit_finish: FxHashMap<u32, usize> = FxHashMap::default();
863
864 for (i, gate) in gates.iter().enumerate() {
865 let qubits = gate.qubits();
866 let predecessor_max_depth = qubits
868 .iter()
869 .filter_map(|q| last_qubit_finish.get(&q.0).copied())
870 .max()
871 .unwrap_or(0);
872
873 depth_at[i] = predecessor_max_depth + 1;
874
875 for q in &qubits {
877 last_qubit_finish.insert(q.0, depth_at[i]);
878 }
879 }
880
881 depth_at.into_iter().max().unwrap_or(0)
882 }
883
884 const fn check_cache(&self, _unitary: &Array2<Complex<f64>>) -> Option<&MultiQubitKAK> {
885 None
888 }
889
890 const fn cache_result(&self, _unitary: &Array2<Complex<f64>>, _result: &MultiQubitKAK) {
892 }
894}
895
896impl Default for MultiQubitKAKDecomposer {
897 fn default() -> Self {
898 Self::new()
899 }
900}
901
902pub struct KAKTreeAnalyzer {
904 stats: DecompositionStats,
906}
907
908#[derive(Debug, Default, Clone)]
909pub struct DecompositionStats {
910 pub total_nodes: usize,
911 pub leaf_nodes: usize,
912 pub max_depth: usize,
913 pub method_counts: FxHashMap<String, usize>,
914 pub cnot_distribution: FxHashMap<usize, usize>,
915}
916
917impl KAKTreeAnalyzer {
918 pub fn new() -> Self {
920 Self {
921 stats: DecompositionStats::default(),
922 }
923 }
924
925 pub fn analyze(&mut self, tree: &DecompositionTree) -> DecompositionStats {
927 self.stats = DecompositionStats::default();
928 self.analyze_recursive(tree, 0);
929 self.stats.clone()
930 }
931
932 fn analyze_recursive(&mut self, tree: &DecompositionTree, depth: usize) {
933 self.stats.total_nodes += 1;
934 self.stats.max_depth = self.stats.max_depth.max(depth);
935
936 match tree {
937 DecompositionTree::Leaf {
938 qubits: _qubits,
939 gate_type,
940 } => {
941 self.stats.leaf_nodes += 1;
942
943 match gate_type {
944 LeafType::SingleQubit(_) => {
945 *self
946 .stats
947 .method_counts
948 .entry("single_qubit".to_string())
949 .or_insert(0) += 1;
950 }
951 LeafType::TwoQubit(cartan) => {
952 *self
953 .stats
954 .method_counts
955 .entry("two_qubit".to_string())
956 .or_insert(0) += 1;
957 let cnots = cartan.interaction.cnot_count(1e-10);
958 *self.stats.cnot_distribution.entry(cnots).or_insert(0) += 1;
959 }
960 }
961 }
962 DecompositionTree::Node {
963 method, children, ..
964 } => {
965 let method_name = match method {
966 DecompositionMethod::CSD { .. } => "csd",
967 DecompositionMethod::Shannon { .. } => "shannon",
968 DecompositionMethod::BlockDiagonal { .. } => "block_diagonal",
969 DecompositionMethod::Cartan => "cartan",
970 };
971 *self
972 .stats
973 .method_counts
974 .entry(method_name.to_string())
975 .or_insert(0) += 1;
976
977 for child in children {
978 self.analyze_recursive(child, depth + 1);
979 }
980 }
981 }
982 }
983}
984
985pub fn kak_decompose_multiqubit(
987 unitary: &Array2<Complex<f64>>,
988 qubit_ids: &[QubitId],
989) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
990 let mut decomposer = MultiQubitKAKDecomposer::new();
991 let decomp = decomposer.decompose(unitary, qubit_ids)?;
992 Ok(decomp.gates)
993}
994
995#[cfg(test)]
996mod tests {
997 use super::*;
998 use scirs2_core::ndarray::Array2;
999 use scirs2_core::Complex;
1000
1001 #[test]
1002 fn test_multiqubit_kak_single() {
1003 let mut decomposer = MultiQubitKAKDecomposer::new();
1004
1005 let h = Array2::from_shape_vec(
1007 (2, 2),
1008 vec![
1009 Complex::new(1.0, 0.0),
1010 Complex::new(1.0, 0.0),
1011 Complex::new(1.0, 0.0),
1012 Complex::new(-1.0, 0.0),
1013 ],
1014 )
1015 .expect("Failed to create Hadamard matrix")
1016 / Complex::new(2.0_f64.sqrt(), 0.0);
1017
1018 let qubit_ids = vec![QubitId(0)];
1019 let decomp = decomposer
1020 .decompose(&h, &qubit_ids)
1021 .expect("Single-qubit KAK decomposition failed");
1022
1023 assert!(decomp.single_qubit_count <= 3);
1024 assert_eq!(decomp.cnot_count, 0);
1025
1026 match &decomp.tree {
1028 DecompositionTree::Leaf {
1029 gate_type: LeafType::SingleQubit(_),
1030 ..
1031 } => {}
1032 _ => panic!("Expected single-qubit leaf"),
1033 }
1034 }
1035
1036 #[test]
1037 fn test_multiqubit_kak_two() {
1038 let mut decomposer = MultiQubitKAKDecomposer::new();
1039
1040 let cnot = Array2::from_shape_vec(
1042 (4, 4),
1043 vec![
1044 Complex::new(1.0, 0.0),
1045 Complex::new(0.0, 0.0),
1046 Complex::new(0.0, 0.0),
1047 Complex::new(0.0, 0.0),
1048 Complex::new(0.0, 0.0),
1049 Complex::new(1.0, 0.0),
1050 Complex::new(0.0, 0.0),
1051 Complex::new(0.0, 0.0),
1052 Complex::new(0.0, 0.0),
1053 Complex::new(0.0, 0.0),
1054 Complex::new(0.0, 0.0),
1055 Complex::new(1.0, 0.0),
1056 Complex::new(0.0, 0.0),
1057 Complex::new(0.0, 0.0),
1058 Complex::new(1.0, 0.0),
1059 Complex::new(0.0, 0.0),
1060 ],
1061 )
1062 .expect("Failed to create CNOT matrix");
1063
1064 let qubit_ids = vec![QubitId(0), QubitId(1)];
1065 let decomp = decomposer
1066 .decompose(&cnot, &qubit_ids)
1067 .expect("Two-qubit KAK decomposition failed");
1068
1069 assert!(decomp.cnot_count <= 1);
1070
1071 match &decomp.tree {
1073 DecompositionTree::Leaf {
1074 gate_type: LeafType::TwoQubit(_),
1075 ..
1076 } => {}
1077 _ => panic!("Expected two-qubit leaf"),
1078 }
1079 }
1080
1081 #[test]
1082 fn test_multiqubit_kak_three() {
1083 let mut decomposer = MultiQubitKAKDecomposer::new();
1084
1085 let identity = Array2::eye(8);
1087 let identity_complex = identity.mapv(|x| Complex::new(x, 0.0));
1088
1089 let qubit_ids = vec![QubitId(0), QubitId(1), QubitId(2)];
1090 let decomp = decomposer
1091 .decompose(&identity_complex, &qubit_ids)
1092 .expect("Three-qubit KAK decomposition failed");
1093
1094 assert_eq!(decomp.gates.len(), 0);
1096 assert_eq!(decomp.cnot_count, 0);
1097 assert_eq!(decomp.single_qubit_count, 0);
1098 }
1099
1100 #[test]
1101 fn test_tree_analyzer() {
1102 let mut analyzer = KAKTreeAnalyzer::new();
1103
1104 let tree = DecompositionTree::Node {
1106 qubits: vec![QubitId(0), QubitId(1), QubitId(2)],
1107 method: DecompositionMethod::CSD { pivot: 2 },
1108 children: vec![
1109 DecompositionTree::Leaf {
1110 qubits: vec![QubitId(0), QubitId(1)],
1111 gate_type: LeafType::TwoQubit(CartanDecomposition {
1112 left_gates: (
1113 SingleQubitDecomposition {
1114 global_phase: 0.0,
1115 theta1: 0.0,
1116 phi: 0.0,
1117 theta2: 0.0,
1118 basis: "ZYZ".to_string(),
1119 },
1120 SingleQubitDecomposition {
1121 global_phase: 0.0,
1122 theta1: 0.0,
1123 phi: 0.0,
1124 theta2: 0.0,
1125 basis: "ZYZ".to_string(),
1126 },
1127 ),
1128 right_gates: (
1129 SingleQubitDecomposition {
1130 global_phase: 0.0,
1131 theta1: 0.0,
1132 phi: 0.0,
1133 theta2: 0.0,
1134 basis: "ZYZ".to_string(),
1135 },
1136 SingleQubitDecomposition {
1137 global_phase: 0.0,
1138 theta1: 0.0,
1139 phi: 0.0,
1140 theta2: 0.0,
1141 basis: "ZYZ".to_string(),
1142 },
1143 ),
1144 interaction: crate::prelude::CartanCoefficients::new(0.0, 0.0, 0.0),
1145 global_phase: 0.0,
1146 }),
1147 },
1148 DecompositionTree::Leaf {
1149 qubits: vec![QubitId(2)],
1150 gate_type: LeafType::SingleQubit(SingleQubitDecomposition {
1151 global_phase: 0.0,
1152 theta1: 0.0,
1153 phi: 0.0,
1154 theta2: 0.0,
1155 basis: "ZYZ".to_string(),
1156 }),
1157 },
1158 ],
1159 };
1160
1161 let stats = analyzer.analyze(&tree);
1162
1163 assert_eq!(stats.total_nodes, 3);
1164 assert_eq!(stats.leaf_nodes, 2);
1165 assert_eq!(stats.max_depth, 1);
1166 assert_eq!(stats.method_counts.get("csd"), Some(&1));
1167 }
1168
1169 fn orthonormalize(mut m: Array2<Complex<f64>>) -> Array2<Complex<f64>> {
1172 let n = m.nrows();
1173 for j in 0..n {
1174 for prev in 0..j {
1175 let proj: Complex<f64> = (0..n).map(|r| m[[r, prev]].conj() * m[[r, j]]).sum();
1176 for r in 0..n {
1177 let sub = proj * m[[r, prev]];
1178 m[[r, j]] -= sub;
1179 }
1180 }
1181 let norm = (0..n).map(|r| m[[r, j]].norm_sqr()).sum::<f64>().sqrt();
1182 for r in 0..n {
1183 m[[r, j]] /= Complex::new(norm, 0.0);
1184 }
1185 }
1186 m
1187 }
1188
1189 #[test]
1193 fn test_compute_csd_recomposes() {
1194 let decomposer = MultiQubitKAKDecomposer::new();
1195
1196 let raw = Array2::from_shape_vec(
1198 (4, 4),
1199 vec![
1200 Complex::new(0.5, 0.2),
1201 Complex::new(-0.3, 0.4),
1202 Complex::new(0.1, -0.2),
1203 Complex::new(0.6, 0.0),
1204 Complex::new(0.2, -0.1),
1205 Complex::new(0.5, 0.3),
1206 Complex::new(-0.4, 0.1),
1207 Complex::new(0.0, 0.2),
1208 Complex::new(-0.3, 0.2),
1209 Complex::new(0.1, 0.5),
1210 Complex::new(0.6, -0.1),
1211 Complex::new(0.2, 0.3),
1212 Complex::new(0.4, 0.0),
1213 Complex::new(-0.2, 0.3),
1214 Complex::new(0.1, 0.4),
1215 Complex::new(0.5, -0.2),
1216 ],
1217 )
1218 .expect("raw matrix");
1219 let w = orthonormalize(raw);
1220
1221 let wh = w.mapv(|z| z.conj()).t().to_owned();
1223 let prod = wh.dot(&w);
1224 for i in 0..4 {
1225 for j in 0..4 {
1226 let exp = if i == j { 1.0 } else { 0.0 };
1227 assert!(
1228 (prod[[i, j]] - Complex::new(exp, 0.0)).norm() < 1e-9,
1229 "manufactured W is not unitary"
1230 );
1231 }
1232 }
1233
1234 let a = w.slice(s![..2, ..2]).to_owned();
1235 let b = w.slice(s![..2, 2..]).to_owned();
1236 let c = w.slice(s![2.., ..2]).to_owned();
1237 let d = w.slice(s![2.., 2..]).to_owned();
1238
1239 let (u1, v1, sigma, u2, v2) = decomposer
1240 .compute_csd(&a, &b, &c, &d)
1241 .expect("CSD should succeed for a generic 4x4 unitary");
1242
1243 for (name, mat) in [("U1", &u1), ("U2", &u2), ("V1", &v1), ("V2", &v2)] {
1245 let mh = mat.mapv(|z| z.conj()).t().to_owned();
1246 let pr = mh.dot(mat);
1247 for i in 0..2 {
1248 for j in 0..2 {
1249 let exp = if i == j { 1.0 } else { 0.0 };
1250 assert!(
1251 (pr[[i, j]] - Complex::new(exp, 0.0)).norm() < 1e-7,
1252 "{name} is not unitary"
1253 );
1254 }
1255 }
1256 }
1257
1258 let sh = sigma.mapv(|z| z.conj()).t().to_owned();
1260 let sps = sh.dot(&sigma);
1261 for i in 0..4 {
1262 for j in 0..4 {
1263 let exp = if i == j { 1.0 } else { 0.0 };
1264 assert!(
1265 (sps[[i, j]] - Complex::new(exp, 0.0)).norm() < 1e-7,
1266 "Sigma (CS matrix) is not orthogonal"
1267 );
1268 }
1269 }
1270
1271 let recon = MultiQubitKAKDecomposer::assemble_from_csd(&u1, &u2, &sigma, &v1, &v2);
1273 let mut max_err = 0.0f64;
1274 for i in 0..4 {
1275 for j in 0..4 {
1276 max_err = max_err.max((recon[[i, j]] - w[[i, j]]).norm());
1277 }
1278 }
1279 assert!(
1280 max_err < 1e-8,
1281 "CSD recomposition error {max_err} exceeds 1e-8"
1282 );
1283 }
1284
1285 #[test]
1286 fn test_complex_svd_helper_roundtrip() {
1287 let m = Array2::from_shape_vec(
1289 (3, 3),
1290 vec![
1291 Complex::new(1.0, 0.2),
1292 Complex::new(0.3, -0.4),
1293 Complex::new(-0.1, 0.5),
1294 Complex::new(0.2, 0.1),
1295 Complex::new(-0.5, 0.3),
1296 Complex::new(0.4, 0.0),
1297 Complex::new(0.0, -0.3),
1298 Complex::new(0.6, 0.1),
1299 Complex::new(-0.2, 0.2),
1300 ],
1301 )
1302 .expect("matrix");
1303 let (u, s, vh) = complex_svd(&m).expect("svd");
1304 let mut s_mat = Array2::<Complex<f64>>::zeros((3, 3));
1305 for i in 0..3 {
1306 s_mat[[i, i]] = Complex::new(s[i], 0.0);
1307 }
1308 let recon = u.dot(&s_mat).dot(&vh);
1309 let mut err = 0.0f64;
1310 for i in 0..3 {
1311 for j in 0..3 {
1312 err = err.max((recon[[i, j]] - m[[i, j]]).norm());
1313 }
1314 }
1315 assert!(err < 1e-9, "complex_svd roundtrip error {err}");
1316 }
1317
1318 #[test]
1319 fn test_block_structure_detection() {
1320 let decomposer = MultiQubitKAKDecomposer::new();
1321
1322 let mut block_diag = Array2::zeros((4, 4));
1324 block_diag[[0, 0]] = Complex::new(1.0, 0.0);
1325 block_diag[[1, 1]] = Complex::new(1.0, 0.0);
1326 block_diag[[2, 2]] = Complex::new(1.0, 0.0);
1327 block_diag[[3, 3]] = Complex::new(1.0, 0.0);
1328
1329 assert!(decomposer.has_block_structure(&block_diag, 2));
1330
1331 block_diag[[0, 2]] = Complex::new(1.0, 0.0);
1333 assert!(!decomposer.has_block_structure(&block_diag, 2));
1334 }
1335}