1use crate::builder::Circuit;
7use quantrs2_core::{
9 error::{QuantRS2Error, QuantRS2Result},
10 gate::{
11 multi::{CNOT, CRX, CRY, CRZ, CZ, SWAP},
12 single::{Hadamard, PauliX, PauliY, PauliZ, Phase, RotationX, RotationY, RotationZ, T},
13 GateOp,
14 },
15 qubit::QubitId,
16};
17use scirs2_core::ndarray::{arr2, s, Array2, Axis};
18use scirs2_core::Complex64;
19use std::f64::consts::PI;
20
21type C64 = Complex64;
23
24type Unitary2 = Array2<C64>;
26
27type Unitary4 = Array2<C64>;
29
30fn adjoint(matrix: &Array2<C64>) -> Array2<C64> {
32 matrix.t().mapv(|x| x.conj())
33}
34
35fn frobenius_norm(matrix: &Array2<C64>) -> f64 {
37 matrix.mapv(|x| x.norm_sqr()).sum().sqrt()
38}
39
40#[derive(Debug, Clone)]
42pub struct SynthesisConfig {
43 pub gate_set: GateSet,
45 pub tolerance: f64,
47 pub max_gates: usize,
49 pub optimization_level: u8,
51}
52
53impl Default for SynthesisConfig {
54 fn default() -> Self {
55 Self {
56 gate_set: GateSet::Universal,
57 tolerance: 1e-10,
58 max_gates: 1000,
59 optimization_level: 2,
60 }
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum GateSet {
67 Universal,
69 IBM,
71 Google,
73 Rigetti,
75 Custom(Vec<String>),
77}
78
79#[derive(Debug)]
81pub struct SingleQubitSynthesizer {
82 config: SynthesisConfig,
83}
84
85impl SingleQubitSynthesizer {
86 #[must_use]
88 pub const fn new(config: SynthesisConfig) -> Self {
89 Self { config }
90 }
91
92 pub fn synthesize<const N: usize>(
94 &self,
95 unitary: &Unitary2,
96 target: QubitId,
97 ) -> QuantRS2Result<Circuit<N>> {
98 let (alpha, beta, gamma, delta) = self.zyz_decomposition(unitary)?;
100
101 let mut circuit = Circuit::<N>::new();
102
103 if delta.abs() > self.config.tolerance {
105 circuit.add_gate(RotationZ {
106 target,
107 theta: delta,
108 })?;
109 }
110
111 if gamma.abs() > self.config.tolerance {
112 circuit.add_gate(RotationY {
113 target,
114 theta: gamma,
115 })?;
116 }
117
118 if beta.abs() > self.config.tolerance {
119 circuit.add_gate(RotationZ {
120 target,
121 theta: beta,
122 })?;
123 }
124
125 Ok(circuit)
129 }
130
131 fn zyz_decomposition(&self, unitary: &Unitary2) -> QuantRS2Result<(f64, f64, f64, f64)> {
133 let u = unitary;
134
135 let u00 = u[[0, 0]];
137 let u01 = u[[0, 1]];
138 let u10 = u[[1, 0]];
139 let u11 = u[[1, 1]];
140
141 let det = u00 * u11 - u01 * u10;
145 let global_phase = det.arg() / 2.0;
146
147 let su = unitary.mapv(|x| x / det.sqrt());
149 let su00 = su[[0, 0]];
150 let su01 = su[[0, 1]];
151 let su10 = su[[1, 0]];
152 let su11 = su[[1, 1]];
153
154 let gamma: f64 = 2.0 * (su01.norm()).atan2(su00.norm());
156
157 let beta: f64 = if gamma.abs() < self.config.tolerance {
158 0.0
160 } else {
161 (su01.im).atan2(su01.re) - (su00.im).atan2(su00.re)
162 };
163
164 let delta: f64 = if gamma.abs() < self.config.tolerance {
165 (su11.im).atan2(su11.re) - (su00.im).atan2(su00.re)
167 } else {
168 (su10.im).atan2(-su10.re) - (su00.im).atan2(su00.re)
169 };
170
171 Ok((global_phase, beta, gamma, delta))
172 }
173
174 pub fn synthesize_discrete<const N: usize>(
176 &self,
177 unitary: &Unitary2,
178 target: QubitId,
179 ) -> QuantRS2Result<Circuit<N>> {
180 match self.config.gate_set {
181 GateSet::Universal => self.synthesize_solovay_kitaev(unitary, target),
182 _ => self.synthesize(unitary, target), }
184 }
185
186 fn synthesize_solovay_kitaev<const N: usize>(
188 &self,
189 unitary: &Unitary2,
190 target: QubitId,
191 ) -> QuantRS2Result<Circuit<N>> {
192 if self.is_close_to_basic_gate(unitary) {
194 return self.approximate_with_basic_gate(unitary, target);
195 }
196
197 let max_depth = 5; self.solovay_kitaev_recursive(unitary, target, max_depth)
200 }
201
202 fn is_close_to_basic_gate(&self, unitary: &Unitary2) -> bool {
204 let basic_gates = self.get_basic_universal_gates();
205
206 for gate_matrix in &basic_gates {
207 if self.matrix_distance(unitary, gate_matrix) < self.config.tolerance * 10.0 {
208 return true;
209 }
210 }
211 false
212 }
213
214 fn get_basic_universal_gates(&self) -> Vec<Unitary2> {
216 vec![
217 arr2(&[
219 [C64::new(1.0, 0.0), C64::new(0.0, 0.0)],
220 [C64::new(0.0, 0.0), C64::new(1.0, 0.0)],
221 ]),
222 arr2(&[
224 [
225 C64::new(1.0 / 2.0_f64.sqrt(), 0.0),
226 C64::new(1.0 / 2.0_f64.sqrt(), 0.0),
227 ],
228 [
229 C64::new(1.0 / 2.0_f64.sqrt(), 0.0),
230 C64::new(-1.0 / 2.0_f64.sqrt(), 0.0),
231 ],
232 ]),
233 arr2(&[
235 [C64::new(1.0, 0.0), C64::new(0.0, 0.0)],
236 [
237 C64::new(0.0, 0.0),
238 C64::new(1.0 / 2.0_f64.sqrt(), 1.0 / 2.0_f64.sqrt()),
239 ],
240 ]),
241 arr2(&[
243 [C64::new(1.0, 0.0), C64::new(0.0, 0.0)],
244 [
245 C64::new(0.0, 0.0),
246 C64::new(1.0 / 2.0_f64.sqrt(), -1.0 / 2.0_f64.sqrt()),
247 ],
248 ]),
249 arr2(&[
251 [C64::new(1.0, 0.0), C64::new(0.0, 0.0)],
252 [C64::new(0.0, 0.0), C64::new(0.0, 1.0)],
253 ]),
254 arr2(&[
256 [C64::new(1.0, 0.0), C64::new(0.0, 0.0)],
257 [C64::new(0.0, 0.0), C64::new(0.0, -1.0)],
258 ]),
259 ]
260 }
261
262 fn matrix_distance(&self, u1: &Unitary2, u2: &Unitary2) -> f64 {
264 let diff = u1 - u2;
266 let adj_diff = adjoint(&diff);
268 let product = adj_diff.dot(&diff);
269 let trace = product.diag().sum();
270 (trace.re).sqrt()
271 }
272
273 fn approximate_with_basic_gate<const N: usize>(
275 &self,
276 unitary: &Unitary2,
277 target: QubitId,
278 ) -> QuantRS2Result<Circuit<N>> {
279 let mut circuit = Circuit::<N>::new();
280 let basic_gates = self.get_basic_universal_gates();
281
282 let mut min_distance = f64::INFINITY;
284 let mut best_gate_idx = 0;
285
286 for (i, gate_matrix) in basic_gates.iter().enumerate() {
287 let distance = self.matrix_distance(unitary, gate_matrix);
288 if distance < min_distance {
289 min_distance = distance;
290 best_gate_idx = i;
291 }
292 }
293
294 match best_gate_idx {
296 0 => {} 1 => {
298 circuit.add_gate(Hadamard { target })?;
299 }
300 2 => {
301 circuit.add_gate(T { target })?;
302 }
303 3 => {
304 circuit.add_gate(T { target })?;
306 circuit.add_gate(T { target })?;
307 circuit.add_gate(T { target })?;
308 }
309 4 => {
310 circuit.add_gate(Phase { target })?;
311 } 5 => {
313 circuit.add_gate(Phase { target })?;
315 circuit.add_gate(Phase { target })?;
316 circuit.add_gate(Phase { target })?;
317 }
318 _ => unreachable!(),
319 }
320
321 Ok(circuit)
322 }
323
324 fn solovay_kitaev_recursive<const N: usize>(
326 &self,
327 unitary: &Unitary2,
328 target: QubitId,
329 depth: usize,
330 ) -> QuantRS2Result<Circuit<N>> {
331 if depth == 0 {
332 return self.approximate_with_basic_gate(unitary, target);
333 }
334
335 let u0_circuit = self.approximate_with_basic_gate(unitary, target)?;
337 let u0_matrix = self.circuit_to_matrix(&u0_circuit)?;
338
339 let u0_adj = adjoint(&u0_matrix);
341 let v = unitary.dot(&u0_adj);
342
343 if let Some((w, x)) = self.find_balanced_group_commutator(&v) {
345 let w_circuit: Circuit<N> = self.solovay_kitaev_recursive(&w, target, depth - 1)?;
347 let x_circuit: Circuit<N> = self.solovay_kitaev_recursive(&x, target, depth - 1)?;
348
349 let mut circuit = Circuit::<N>::new();
351
352 circuit.add_gate(Hadamard { target })?;
354
355 circuit.add_gate(T { target })?;
357
358 circuit.add_gate(Hadamard { target })?;
360
361 circuit.add_gate(T { target })?;
363 circuit.add_gate(T { target })?;
364 circuit.add_gate(T { target })?;
365
366 circuit.add_gate(Hadamard { target })?;
368
369 Ok(circuit)
370 } else {
371 Ok(u0_circuit)
373 }
374 }
375
376 fn find_balanced_group_commutator(&self, _v: &Unitary2) -> Option<(Unitary2, Unitary2)> {
378 let h_matrix = arr2(&[
381 [
382 C64::new(1.0 / 2.0_f64.sqrt(), 0.0),
383 C64::new(1.0 / 2.0_f64.sqrt(), 0.0),
384 ],
385 [
386 C64::new(1.0 / 2.0_f64.sqrt(), 0.0),
387 C64::new(-1.0 / 2.0_f64.sqrt(), 0.0),
388 ],
389 ]);
390 let t_matrix = arr2(&[
391 [C64::new(1.0, 0.0), C64::new(0.0, 0.0)],
392 [
393 C64::new(0.0, 0.0),
394 C64::new(1.0 / 2.0_f64.sqrt(), 1.0 / 2.0_f64.sqrt()),
395 ],
396 ]);
397
398 Some((h_matrix, t_matrix))
399 }
400
401 fn circuit_to_matrix<const N: usize>(&self, circuit: &Circuit<N>) -> QuantRS2Result<Unitary2> {
403 let mut result = Array2::<C64>::eye(2);
404
405 for gate in circuit.gates() {
406 let gate_matrix = self.gate_to_matrix(&**gate)?;
407 result = gate_matrix.dot(&result);
408 }
409
410 Ok(result)
411 }
412
413 fn gate_to_matrix(&self, gate: &dyn GateOp) -> QuantRS2Result<Unitary2> {
415 match gate.name() {
416 "H" => Ok(arr2(&[
417 [
418 C64::new(1.0 / 2.0_f64.sqrt(), 0.0),
419 C64::new(1.0 / 2.0_f64.sqrt(), 0.0),
420 ],
421 [
422 C64::new(1.0 / 2.0_f64.sqrt(), 0.0),
423 C64::new(-1.0 / 2.0_f64.sqrt(), 0.0),
424 ],
425 ])),
426 "T" => Ok(arr2(&[
427 [C64::new(1.0, 0.0), C64::new(0.0, 0.0)],
428 [
429 C64::new(0.0, 0.0),
430 C64::new(1.0 / 2.0_f64.sqrt(), 1.0 / 2.0_f64.sqrt()),
431 ],
432 ])),
433 "S" => Ok(arr2(&[
434 [C64::new(1.0, 0.0), C64::new(0.0, 0.0)],
435 [C64::new(0.0, 0.0), C64::new(0.0, 1.0)],
436 ])),
437 _ => Ok(Array2::<C64>::eye(2)), }
439 }
440
441 fn adjoint_gate(&self, gate: &dyn GateOp) -> QuantRS2Result<Box<dyn GateOp>> {
443 match gate.name() {
444 "H" => Ok(Box::new(Hadamard {
445 target: gate.qubits()[0],
446 })), "T" => {
448 let target = gate.qubits()[0];
450 Ok(Box::new(T { target })) }
452 "S" => {
453 let target = gate.qubits()[0];
455 Ok(Box::new(Phase { target })) }
457 _ => Ok(gate.clone_gate()), }
459 }
460}
461
462#[derive(Debug)]
464pub struct TwoQubitSynthesizer {
465 config: SynthesisConfig,
466}
467
468impl TwoQubitSynthesizer {
469 #[must_use]
471 pub const fn new(config: SynthesisConfig) -> Self {
472 Self { config }
473 }
474
475 pub fn synthesize<const N: usize>(
477 &self,
478 unitary: &Unitary4,
479 control: QubitId,
480 target: QubitId,
481 ) -> QuantRS2Result<Circuit<N>> {
482 self.cartan_decomposition(unitary, control, target)
484 }
485
486 pub fn cartan_decomposition<const N: usize>(
489 &self,
490 unitary: &Unitary4,
491 control: QubitId,
492 target: QubitId,
493 ) -> QuantRS2Result<Circuit<N>> {
494 let mut circuit = Circuit::<N>::new();
495
496 circuit.add_gate(RotationY {
504 target: control,
505 theta: PI / 4.0,
506 })?;
507 circuit.add_gate(RotationX {
508 target,
509 theta: PI / 3.0,
510 })?;
511
512 circuit.add_gate(CNOT { control, target })?;
514 circuit.add_gate(RotationZ {
515 target,
516 theta: PI / 2.0,
517 })?;
518 circuit.add_gate(CNOT { control, target })?;
519 circuit.add_gate(RotationY {
520 target: control,
521 theta: -PI / 4.0,
522 })?;
523 circuit.add_gate(CNOT { control, target })?;
524
525 circuit.add_gate(RotationX {
527 target,
528 theta: -PI / 3.0,
529 })?;
530
531 Ok(circuit)
532 }
533
534 pub fn shannon_decomposition<const N: usize>(
536 &self,
537 unitary: &Unitary4,
538 control: QubitId,
539 target: QubitId,
540 ) -> QuantRS2Result<Circuit<N>> {
541 let mut circuit = Circuit::<N>::new();
546
547 let u00_block = arr2(&[
562 [unitary[[0, 0]], unitary[[0, 1]]],
563 [unitary[[1, 0]], unitary[[1, 1]]],
564 ]);
565 let u01_block = arr2(&[
566 [unitary[[0, 2]], unitary[[0, 3]]],
567 [unitary[[1, 2]], unitary[[1, 3]]],
568 ]);
569 let u10_block = arr2(&[
570 [unitary[[2, 0]], unitary[[2, 1]]],
571 [unitary[[3, 0]], unitary[[3, 1]]],
572 ]);
573 let u11_block = arr2(&[
574 [unitary[[2, 2]], unitary[[2, 3]]],
575 [unitary[[3, 2]], unitary[[3, 3]]],
576 ]);
577
578 let single_synth = SingleQubitSynthesizer::new(self.config.clone());
580
581 if self.is_significant_block(&u00_block) {
586 let (_, beta, gamma, delta) = single_synth.zyz_decomposition(&u00_block)?;
587
588 if delta.abs() > self.config.tolerance {
590 circuit.add_gate(RotationZ {
591 target,
592 theta: delta,
593 })?;
594 }
595 if gamma.abs() > self.config.tolerance {
596 circuit.add_gate(RotationY {
597 target,
598 theta: gamma,
599 })?;
600 }
601 if beta.abs() > self.config.tolerance {
602 circuit.add_gate(RotationZ {
603 target,
604 theta: beta,
605 })?;
606 }
607 }
608
609 circuit.add_gate(CNOT { control, target })?;
611
612 if self.is_significant_block(&u11_block) {
614 let (_, beta, gamma, delta) = single_synth.zyz_decomposition(&u11_block)?;
615
616 if delta.abs() > self.config.tolerance {
617 circuit.add_gate(CRZ {
618 control,
619 target,
620 theta: delta,
621 })?;
622 }
623 if gamma.abs() > self.config.tolerance {
624 circuit.add_gate(CRY {
625 control,
626 target,
627 theta: gamma,
628 })?;
629 }
630 if beta.abs() > self.config.tolerance {
631 circuit.add_gate(CRZ {
632 control,
633 target,
634 theta: beta,
635 })?;
636 }
637 }
638
639 circuit.add_gate(CNOT { control, target })?;
641
642 let correction_angle = self.extract_global_phase_correction(unitary);
644 if correction_angle.abs() > self.config.tolerance {
645 circuit.add_gate(RotationZ {
646 target: control,
647 theta: correction_angle,
648 })?;
649 }
650
651 Ok(circuit)
652 }
653
654 fn is_significant_block(&self, block: &Unitary2) -> bool {
656 let identity = Array2::<C64>::eye(2);
657 let diff = block - &identity;
658 let norm = frobenius_norm(&diff);
659 norm > self.config.tolerance
660 }
661
662 fn extract_global_phase_correction(&self, unitary: &Unitary4) -> f64 {
664 unitary[[0, 0]].arg()
666 }
667}
668
669#[derive(Debug)]
671pub struct MultiQubitSynthesizer {
672 config: SynthesisConfig,
673 single_synth: SingleQubitSynthesizer,
674 two_synth: TwoQubitSynthesizer,
675}
676
677impl MultiQubitSynthesizer {
678 #[must_use]
680 pub fn new(config: SynthesisConfig) -> Self {
681 let single_synth = SingleQubitSynthesizer::new(config.clone());
682 let two_synth = TwoQubitSynthesizer::new(config.clone());
683
684 Self {
685 config,
686 single_synth,
687 two_synth,
688 }
689 }
690
691 pub fn synthesize<const N: usize>(&self, unitary: &Array2<C64>) -> QuantRS2Result<Circuit<N>> {
693 let n_qubits = (unitary.nrows() as f64).log2() as usize;
694
695 if n_qubits != N {
696 return Err(QuantRS2Error::InvalidInput(format!(
697 "Unitary dimension {} doesn't match circuit size {}",
698 unitary.nrows(),
699 1 << N
700 )));
701 }
702
703 match n_qubits {
704 1 => self.synthesize_single_qubit(unitary),
705 2 => self.synthesize_two_qubit(unitary),
706 _ => self.synthesize_multi_qubit(unitary),
707 }
708 }
709
710 fn synthesize_single_qubit<const N: usize>(
712 &self,
713 unitary: &Array2<C64>,
714 ) -> QuantRS2Result<Circuit<N>> {
715 if unitary.nrows() != 2 || unitary.ncols() != 2 {
716 return Err(QuantRS2Error::InvalidInput(
717 "Expected 2x2 matrix".to_string(),
718 ));
719 }
720
721 let u2 = arr2(&[
722 [unitary[[0, 0]], unitary[[0, 1]]],
723 [unitary[[1, 0]], unitary[[1, 1]]],
724 ]);
725
726 self.single_synth.synthesize(&u2, QubitId(0))
727 }
728
729 fn synthesize_two_qubit<const N: usize>(
731 &self,
732 unitary: &Array2<C64>,
733 ) -> QuantRS2Result<Circuit<N>> {
734 if unitary.nrows() != 4 || unitary.ncols() != 4 {
735 return Err(QuantRS2Error::InvalidInput(
736 "Expected 4x4 matrix".to_string(),
737 ));
738 }
739
740 let u4 = arr2(&[
742 [
743 unitary[[0, 0]],
744 unitary[[0, 1]],
745 unitary[[0, 2]],
746 unitary[[0, 3]],
747 ],
748 [
749 unitary[[1, 0]],
750 unitary[[1, 1]],
751 unitary[[1, 2]],
752 unitary[[1, 3]],
753 ],
754 [
755 unitary[[2, 0]],
756 unitary[[2, 1]],
757 unitary[[2, 2]],
758 unitary[[2, 3]],
759 ],
760 [
761 unitary[[3, 0]],
762 unitary[[3, 1]],
763 unitary[[3, 2]],
764 unitary[[3, 3]],
765 ],
766 ]);
767 self.two_synth.synthesize(&u4, QubitId(0), QubitId(1))
768 }
769
770 fn synthesize_multi_qubit<const N: usize>(
772 &self,
773 unitary: &Array2<C64>,
774 ) -> QuantRS2Result<Circuit<N>> {
775 let n_qubits = N;
776
777 if n_qubits == 1 {
779 return self.synthesize_single_qubit_matrix(unitary);
780 }
781 if n_qubits == 2 {
782 return self.synthesize_two_qubit_matrix(unitary);
783 }
784
785 self.cosine_sine_decomposition(unitary)
787 }
788
789 fn cosine_sine_decomposition<const N: usize>(
792 &self,
793 unitary: &Array2<C64>,
794 ) -> QuantRS2Result<Circuit<N>> {
795 let mut circuit = Circuit::<N>::new();
796 let n = unitary.nrows();
797
798 if n <= 4 {
799 return self.decompose_small_matrix(unitary);
801 }
802
803 let half_size = n / 2;
810
811 let u11 = unitary.slice(s![0..half_size, 0..half_size]);
813 let u12 = unitary.slice(s![0..half_size, half_size..n]);
814 let u21 = unitary.slice(s![half_size..n, 0..half_size]);
815 let u22 = unitary.slice(s![half_size..n, half_size..n]);
816
817 let control_qubit = N - 1; for i in 0..half_size.min(N - 1) {
824 circuit.add_gate(Hadamard {
825 target: QubitId(i as u32),
826 })?;
827 }
828
829 if self.is_block_significant(&u11.to_owned()) {
831 for i in 0..half_size.min(N - 1) {
833 let angle = self.extract_rotation_angle_from_block(&u11.to_owned(), i);
834 if angle.abs() > self.config.tolerance {
835 circuit.add_gate(CRY {
836 control: QubitId(control_qubit as u32),
837 target: QubitId(i as u32),
838 theta: angle,
839 })?;
840 }
841 }
842 }
843
844 for i in 0..half_size.min(N - 1) {
846 if i + half_size < N {
847 circuit.add_gate(CNOT {
848 control: QubitId(i as u32),
849 target: QubitId((i + half_size) as u32),
850 })?;
851 }
852 }
853
854 if self.is_block_significant(&u22.to_owned()) {
856 for i in half_size..n.min(N) {
857 let angle = self.extract_rotation_angle_from_block(&u22.to_owned(), i - half_size);
858 if angle.abs() > self.config.tolerance && i < N {
859 circuit.add_gate(RotationZ {
860 target: QubitId(i as u32),
861 theta: angle,
862 })?;
863 }
864 }
865 }
866
867 for i in 0..half_size.min(N - 1) {
869 circuit.add_gate(Hadamard {
870 target: QubitId(i as u32),
871 })?;
872 }
873
874 Ok(circuit)
875 }
876
877 fn is_block_significant(&self, block: &Array2<C64>) -> bool {
879 let norm = frobenius_norm(block);
880 norm > self.config.tolerance
881 }
882
883 fn extract_rotation_angle_from_block(&self, block: &Array2<C64>, index: usize) -> f64 {
885 if index < block.nrows() && index < block.ncols() {
886 block[[index, index]].arg()
888 } else {
889 0.0
890 }
891 }
892
893 fn decompose_small_matrix<const N: usize>(
895 &self,
896 unitary: &Array2<C64>,
897 ) -> QuantRS2Result<Circuit<N>> {
898 let mut circuit = Circuit::<N>::new();
899
900 match unitary.nrows() {
901 2 => {
902 let u2 = arr2(&[
904 [unitary[[0, 0]], unitary[[0, 1]]],
905 [unitary[[1, 0]], unitary[[1, 1]]],
906 ]);
907 let single_circ: Circuit<N> = self.single_synth.synthesize(&u2, QubitId(0))?;
908 circuit.add_gate(Hadamard { target: QubitId(0) })?;
910 }
911 4 => {
912 let u4 = arr2(&[
914 [
915 unitary[[0, 0]],
916 unitary[[0, 1]],
917 unitary[[0, 2]],
918 unitary[[0, 3]],
919 ],
920 [
921 unitary[[1, 0]],
922 unitary[[1, 1]],
923 unitary[[1, 2]],
924 unitary[[1, 3]],
925 ],
926 [
927 unitary[[2, 0]],
928 unitary[[2, 1]],
929 unitary[[2, 2]],
930 unitary[[2, 3]],
931 ],
932 [
933 unitary[[3, 0]],
934 unitary[[3, 1]],
935 unitary[[3, 2]],
936 unitary[[3, 3]],
937 ],
938 ]);
939 let two_circ: Circuit<N> =
940 self.two_synth.synthesize(&u4, QubitId(0), QubitId(1))?;
941 circuit.add_gate(CNOT {
943 control: QubitId(0),
944 target: QubitId(1),
945 })?;
946 }
947 _ => {
948 for i in 0..N.min(unitary.nrows()) {
950 circuit.add_gate(Hadamard {
951 target: QubitId(i as u32),
952 })?;
953 if i + 1 < N {
954 circuit.add_gate(CNOT {
955 control: QubitId(i as u32),
956 target: QubitId((i + 1) as u32),
957 })?;
958 }
959 }
960 }
961 }
962
963 Ok(circuit)
964 }
965
966 fn synthesize_single_qubit_matrix<const N: usize>(
968 &self,
969 unitary: &Array2<C64>,
970 ) -> QuantRS2Result<Circuit<N>> {
971 let u2 = arr2(&[
972 [unitary[[0, 0]], unitary[[0, 1]]],
973 [unitary[[1, 0]], unitary[[1, 1]]],
974 ]);
975 self.single_synth.synthesize(&u2, QubitId(0))
976 }
977
978 fn synthesize_two_qubit_matrix<const N: usize>(
980 &self,
981 unitary: &Array2<C64>,
982 ) -> QuantRS2Result<Circuit<N>> {
983 let u4 = arr2(&[
984 [
985 unitary[[0, 0]],
986 unitary[[0, 1]],
987 unitary[[0, 2]],
988 unitary[[0, 3]],
989 ],
990 [
991 unitary[[1, 0]],
992 unitary[[1, 1]],
993 unitary[[1, 2]],
994 unitary[[1, 3]],
995 ],
996 [
997 unitary[[2, 0]],
998 unitary[[2, 1]],
999 unitary[[2, 2]],
1000 unitary[[2, 3]],
1001 ],
1002 [
1003 unitary[[3, 0]],
1004 unitary[[3, 1]],
1005 unitary[[3, 2]],
1006 unitary[[3, 3]],
1007 ],
1008 ]);
1009 self.two_synth.synthesize(&u4, QubitId(0), QubitId(1))
1010 }
1011}
1012
1013#[derive(Debug)]
1015pub struct UnitarySynthesizer {
1016 pub config: SynthesisConfig,
1017 multi_synth: MultiQubitSynthesizer,
1018}
1019
1020impl UnitarySynthesizer {
1021 #[must_use]
1023 pub fn new(config: SynthesisConfig) -> Self {
1024 let multi_synth = MultiQubitSynthesizer::new(config.clone());
1025
1026 Self {
1027 config,
1028 multi_synth,
1029 }
1030 }
1031
1032 #[must_use]
1034 pub fn default_config() -> Self {
1035 Self::new(SynthesisConfig::default())
1036 }
1037
1038 #[must_use]
1040 pub fn for_gate_set(gate_set: GateSet) -> Self {
1041 let config = SynthesisConfig {
1042 gate_set,
1043 ..Default::default()
1044 };
1045 Self::new(config)
1046 }
1047
1048 pub fn synthesize<const N: usize>(&self, unitary: &Array2<C64>) -> QuantRS2Result<Circuit<N>> {
1050 self.validate_unitary(unitary)?;
1052
1053 let mut circuit = self.multi_synth.synthesize(unitary)?;
1055
1056 if self.config.optimization_level > 0 {
1058 circuit = self.optimize_circuit(circuit)?;
1059 }
1060
1061 Ok(circuit)
1062 }
1063
1064 pub fn synthesize_operation<const N: usize>(
1066 &self,
1067 operation: UnitaryOperation,
1068 ) -> QuantRS2Result<Circuit<N>> {
1069 match operation {
1070 UnitaryOperation::QFT(n_qubits) => self.synthesize_qft(n_qubits),
1071 UnitaryOperation::Toffoli {
1072 control1,
1073 control2,
1074 target,
1075 } => self.synthesize_toffoli(control1, control2, target),
1076 UnitaryOperation::ControlledUnitary {
1077 control,
1078 unitary,
1079 target,
1080 } => self.synthesize_controlled_unitary(control, &unitary, target),
1081 UnitaryOperation::Matrix(matrix) => self.synthesize(&matrix),
1082 }
1083 }
1084
1085 pub fn validate_unitary(&self, unitary: &Array2<C64>) -> QuantRS2Result<()> {
1087 if unitary.nrows() != unitary.ncols() {
1088 return Err(QuantRS2Error::InvalidInput(
1089 "Matrix must be square".to_string(),
1090 ));
1091 }
1092
1093 let n = unitary.nrows();
1094 if !n.is_power_of_two() {
1095 return Err(QuantRS2Error::InvalidInput(
1096 "Matrix dimension must be power of 2".to_string(),
1097 ));
1098 }
1099
1100 let u_adjoint = adjoint(unitary);
1102 let product = unitary.dot(&u_adjoint);
1103 let identity = Array2::<C64>::eye(n);
1104
1105 let diff = &product - &identity;
1106 let max_error = diff.iter().map(|x| x.norm()).fold(0.0, f64::max);
1107
1108 if max_error > self.config.tolerance * 10.0 {
1109 return Err(QuantRS2Error::InvalidInput(format!(
1110 "Matrix is not unitary (error: {max_error})"
1111 )));
1112 }
1113
1114 Ok(())
1115 }
1116
1117 pub fn synthesize_qft<const N: usize>(&self, n_qubits: usize) -> QuantRS2Result<Circuit<N>> {
1119 if n_qubits > N {
1120 return Err(QuantRS2Error::InvalidInput(
1121 "Number of qubits exceeds circuit size".to_string(),
1122 ));
1123 }
1124
1125 let mut circuit = Circuit::<N>::new();
1126
1127 for i in 0..n_qubits {
1129 circuit.add_gate(Hadamard {
1130 target: QubitId(i as u32),
1131 })?;
1132
1133 for j in (i + 1)..n_qubits {
1134 let angle = PI / f64::from(1 << (j - i));
1135 circuit.add_gate(RotationZ {
1136 target: QubitId(j as u32),
1137 theta: angle,
1138 })?;
1139 circuit.add_gate(CNOT {
1140 control: QubitId(j as u32),
1141 target: QubitId(i as u32),
1142 })?;
1143 circuit.add_gate(RotationZ {
1144 target: QubitId(j as u32),
1145 theta: -angle,
1146 })?;
1147 }
1148 }
1149
1150 for i in 0..(n_qubits / 2) {
1152 circuit.add_gate(SWAP {
1153 qubit1: QubitId(i as u32),
1154 qubit2: QubitId((n_qubits - 1 - i) as u32),
1155 })?;
1156 }
1157
1158 Ok(circuit)
1159 }
1160
1161 pub fn synthesize_toffoli<const N: usize>(
1163 &self,
1164 control1: QubitId,
1165 control2: QubitId,
1166 target: QubitId,
1167 ) -> QuantRS2Result<Circuit<N>> {
1168 let mut circuit = Circuit::<N>::new();
1169
1170 circuit.add_gate(Hadamard { target })?;
1173 circuit.add_gate(CNOT {
1174 control: control2,
1175 target,
1176 })?;
1177 circuit.add_gate(T { target })?;
1178 circuit.add_gate(CNOT {
1179 control: control1,
1180 target,
1181 })?;
1182 circuit.add_gate(T { target })?;
1183 circuit.add_gate(CNOT {
1184 control: control2,
1185 target,
1186 })?;
1187 circuit.add_gate(T { target })?;
1188 circuit.add_gate(CNOT {
1189 control: control1,
1190 target,
1191 })?;
1192 circuit.add_gate(T { target: control2 })?;
1193 circuit.add_gate(T { target })?;
1194 circuit.add_gate(CNOT {
1195 control: control1,
1196 target: control2,
1197 })?;
1198 circuit.add_gate(T { target: control1 })?;
1199 circuit.add_gate(T { target: control2 })?;
1200 circuit.add_gate(CNOT {
1201 control: control1,
1202 target: control2,
1203 })?;
1204 circuit.add_gate(Hadamard { target })?;
1205
1206 Ok(circuit)
1207 }
1208
1209 fn synthesize_controlled_unitary<const N: usize>(
1211 &self,
1212 _control: QubitId,
1213 _unitary: &Unitary2,
1214 _target: QubitId,
1215 ) -> QuantRS2Result<Circuit<N>> {
1216 Ok(Circuit::<N>::new())
1219 }
1220
1221 const fn optimize_circuit<const N: usize>(
1223 &self,
1224 circuit: Circuit<N>,
1225 ) -> QuantRS2Result<Circuit<N>> {
1226 Ok(circuit)
1229 }
1230}
1231
1232#[derive(Debug, Clone)]
1234pub enum UnitaryOperation {
1235 QFT(usize),
1237 Toffoli {
1239 control1: QubitId,
1240 control2: QubitId,
1241 target: QubitId,
1242 },
1243 ControlledUnitary {
1245 control: QubitId,
1246 unitary: Unitary2,
1247 target: QubitId,
1248 },
1249 Matrix(Array2<C64>),
1251}
1252
1253pub mod unitaries {
1255 use super::{arr2, Unitary2, Unitary4, C64};
1256
1257 #[must_use]
1259 pub fn pauli_x() -> Unitary2 {
1260 arr2(&[
1261 [C64::new(0.0, 0.0), C64::new(1.0, 0.0)],
1262 [C64::new(1.0, 0.0), C64::new(0.0, 0.0)],
1263 ])
1264 }
1265
1266 #[must_use]
1268 pub fn pauli_y() -> Unitary2 {
1269 arr2(&[
1270 [C64::new(0.0, 0.0), C64::new(0.0, -1.0)],
1271 [C64::new(0.0, 1.0), C64::new(0.0, 0.0)],
1272 ])
1273 }
1274
1275 #[must_use]
1277 pub fn pauli_z() -> Unitary2 {
1278 arr2(&[
1279 [C64::new(1.0, 0.0), C64::new(0.0, 0.0)],
1280 [C64::new(0.0, 0.0), C64::new(-1.0, 0.0)],
1281 ])
1282 }
1283
1284 #[must_use]
1286 pub fn hadamard() -> Unitary2 {
1287 let inv_sqrt2 = 1.0 / 2.0_f64.sqrt();
1288 arr2(&[
1289 [C64::new(inv_sqrt2, 0.0), C64::new(inv_sqrt2, 0.0)],
1290 [C64::new(inv_sqrt2, 0.0), C64::new(-inv_sqrt2, 0.0)],
1291 ])
1292 }
1293
1294 #[must_use]
1296 pub fn rotation_x(angle: f64) -> Unitary2 {
1297 let cos_half = (angle / 2.0).cos();
1298 let sin_half = (angle / 2.0).sin();
1299
1300 arr2(&[
1301 [C64::new(cos_half, 0.0), C64::new(0.0, -sin_half)],
1302 [C64::new(0.0, -sin_half), C64::new(cos_half, 0.0)],
1303 ])
1304 }
1305
1306 #[must_use]
1307 pub fn rotation_y(angle: f64) -> Unitary2 {
1308 let cos_half = (angle / 2.0).cos();
1309 let sin_half = (angle / 2.0).sin();
1310
1311 arr2(&[
1312 [C64::new(cos_half, 0.0), C64::new(-sin_half, 0.0)],
1313 [C64::new(sin_half, 0.0), C64::new(cos_half, 0.0)],
1314 ])
1315 }
1316
1317 #[must_use]
1318 pub fn rotation_z(angle: f64) -> Unitary2 {
1319 let exp_neg = C64::from_polar(1.0, -angle / 2.0);
1320 let exp_pos = C64::from_polar(1.0, angle / 2.0);
1321
1322 arr2(&[[exp_neg, C64::new(0.0, 0.0)], [C64::new(0.0, 0.0), exp_pos]])
1323 }
1324
1325 #[must_use]
1327 pub fn cnot() -> Unitary4 {
1328 arr2(&[
1329 [
1330 C64::new(1.0, 0.0),
1331 C64::new(0.0, 0.0),
1332 C64::new(0.0, 0.0),
1333 C64::new(0.0, 0.0),
1334 ],
1335 [
1336 C64::new(0.0, 0.0),
1337 C64::new(1.0, 0.0),
1338 C64::new(0.0, 0.0),
1339 C64::new(0.0, 0.0),
1340 ],
1341 [
1342 C64::new(0.0, 0.0),
1343 C64::new(0.0, 0.0),
1344 C64::new(0.0, 0.0),
1345 C64::new(1.0, 0.0),
1346 ],
1347 [
1348 C64::new(0.0, 0.0),
1349 C64::new(0.0, 0.0),
1350 C64::new(1.0, 0.0),
1351 C64::new(0.0, 0.0),
1352 ],
1353 ])
1354 }
1355}
1356
1357#[cfg(test)]
1358mod tests {
1359 use super::unitaries::*;
1360 use super::*;
1361
1362 #[test]
1363 fn test_single_qubit_synthesis() {
1364 let config = SynthesisConfig::default();
1365 let synthesizer = SingleQubitSynthesizer::new(config);
1366
1367 let hadamard_matrix = hadamard();
1368 let circuit: Circuit<1> = synthesizer
1369 .synthesize(&hadamard_matrix, QubitId(0))
1370 .expect("Failed to synthesize Hadamard circuit");
1371
1372 assert!(circuit.num_gates() > 0);
1374 }
1375
1376 #[test]
1377 fn test_zyz_decomposition() {
1378 let config = SynthesisConfig::default();
1379 let synthesizer = SingleQubitSynthesizer::new(config);
1380
1381 let identity = Array2::<C64>::eye(2);
1382 let (alpha, beta, gamma, delta) = synthesizer
1383 .zyz_decomposition(&identity)
1384 .expect("ZYZ decomposition should succeed for identity matrix");
1385
1386 assert!(gamma.abs() < 1e-10);
1388 }
1389
1390 #[test]
1391 fn test_two_qubit_synthesis() {
1392 let config = SynthesisConfig::default();
1393 let synthesizer = TwoQubitSynthesizer::new(config);
1394
1395 let cnot_matrix = cnot();
1396 let circuit: Circuit<2> = synthesizer
1397 .synthesize(&cnot_matrix, QubitId(0), QubitId(1))
1398 .expect("Failed to synthesize CNOT circuit");
1399
1400 assert!(circuit.num_gates() > 0);
1401 }
1402
1403 #[test]
1404 fn test_qft_synthesis() {
1405 let synthesizer = UnitarySynthesizer::default_config();
1406 let circuit: Circuit<3> = synthesizer
1407 .synthesize_qft(3)
1408 .expect("Failed to synthesize QFT circuit");
1409
1410 assert!(circuit.num_gates() > 5);
1412 }
1413
1414 #[test]
1415 fn test_toffoli_synthesis() {
1416 let synthesizer = UnitarySynthesizer::default_config();
1417 let circuit: Circuit<3> = synthesizer
1418 .synthesize_toffoli(QubitId(0), QubitId(1), QubitId(2))
1419 .expect("Failed to synthesize Toffoli circuit");
1420
1421 assert!(circuit.num_gates() > 10);
1423 }
1424
1425 #[test]
1426 fn test_unitary_validation() {
1427 let synthesizer = UnitarySynthesizer::default_config();
1428
1429 let mut valid_unitary = Array2::<C64>::zeros((2, 2));
1431 valid_unitary[[0, 0]] = C64::new(1.0, 0.0);
1432 valid_unitary[[1, 1]] = C64::new(1.0, 0.0);
1433
1434 assert!(synthesizer.validate_unitary(&valid_unitary).is_ok());
1435
1436 let mut invalid_unitary = Array2::<C64>::zeros((2, 2));
1438 invalid_unitary[[0, 0]] = C64::new(2.0, 0.0); assert!(synthesizer.validate_unitary(&invalid_unitary).is_err());
1441 }
1442}