quantrs2_circuit/
synthesis.rs

1//! Unitary synthesis module
2//!
3//! This module provides algorithms for synthesizing quantum circuits from unitary matrix
4//! descriptions. It includes various decomposition strategies for different gate sets.
5
6use crate::builder::Circuit;
7// Now using SciRS2 for all matrix operations (SciRS2 POLICY COMPLIANT)
8use 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
21/// Complex number type for quantum computations
22type C64 = Complex64;
23
24/// 2x2 complex matrix representing a single-qubit unitary
25type Unitary2 = Array2<C64>;
26
27/// 4x4 complex matrix representing a two-qubit unitary
28type Unitary4 = Array2<C64>;
29
30/// Helper function to compute adjoint (Hermitian conjugate) of a matrix
31fn adjoint(matrix: &Array2<C64>) -> Array2<C64> {
32    matrix.t().mapv(|x| x.conj())
33}
34
35/// Helper function to compute Frobenius norm of a matrix
36fn frobenius_norm(matrix: &Array2<C64>) -> f64 {
37    matrix.mapv(|x| x.norm_sqr()).sum().sqrt()
38}
39
40/// Configuration for unitary synthesis
41#[derive(Debug, Clone)]
42pub struct SynthesisConfig {
43    /// Target gate set for synthesis
44    pub gate_set: GateSet,
45    /// Tolerance for numerical comparisons
46    pub tolerance: f64,
47    /// Maximum number of gates in synthesis
48    pub max_gates: usize,
49    /// Optimization level (0-3)
50    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/// Available gate sets for synthesis
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub enum GateSet {
67    /// Universal gate set {H, T, CNOT}
68    Universal,
69    /// IBM gate set {U1, U2, U3, CNOT}
70    IBM,
71    /// Google gate set {X^1/2, Y^1/2, Z, CZ}
72    Google,
73    /// Rigetti gate set {RX, RZ, CZ}
74    Rigetti,
75    /// Custom gate set
76    Custom(Vec<String>),
77}
78
79/// Single-qubit unitary synthesis using ZYZ decomposition
80#[derive(Debug)]
81pub struct SingleQubitSynthesizer {
82    config: SynthesisConfig,
83}
84
85impl SingleQubitSynthesizer {
86    /// Create a new single-qubit synthesizer
87    #[must_use]
88    pub const fn new(config: SynthesisConfig) -> Self {
89        Self { config }
90    }
91
92    /// Synthesize a circuit from a 2x2 unitary matrix
93    pub fn synthesize<const N: usize>(
94        &self,
95        unitary: &Unitary2,
96        target: QubitId,
97    ) -> QuantRS2Result<Circuit<N>> {
98        // Use ZYZ decomposition: U = e^(iα) RZ(β) RY(γ) RZ(δ)
99        let (alpha, beta, gamma, delta) = self.zyz_decomposition(unitary)?;
100
101        let mut circuit = Circuit::<N>::new();
102
103        // Apply the decomposition
104        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        // Global phase is typically ignored in quantum circuits
126        // but could be tracked for completeness
127
128        Ok(circuit)
129    }
130
131    /// Perform ZYZ decomposition of a single-qubit unitary
132    fn zyz_decomposition(&self, unitary: &Unitary2) -> QuantRS2Result<(f64, f64, f64, f64)> {
133        let u = unitary;
134
135        // Extract elements
136        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        // Calculate angles for ZYZ decomposition
142        // Based on Nielsen & Chuang Chapter 4
143
144        let det = u00 * u11 - u01 * u10;
145        let global_phase = det.arg() / 2.0;
146
147        // Normalize by global phase
148        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        // Calculate ZYZ angles
155        let gamma: f64 = 2.0 * (su01.norm()).atan2(su00.norm());
156
157        let beta: f64 = if gamma.abs() < self.config.tolerance {
158            // Special case: no Y rotation needed
159            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            // Special case: just a Z rotation
166            (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    /// Synthesize using discrete gate approximation
175    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), // Fall back to continuous
183        }
184    }
185
186    /// Solovay-Kitaev algorithm for universal gate set approximation
187    fn synthesize_solovay_kitaev<const N: usize>(
188        &self,
189        unitary: &Unitary2,
190        target: QubitId,
191    ) -> QuantRS2Result<Circuit<N>> {
192        // Base case: if the unitary is already close to a basic gate, use it directly
193        if self.is_close_to_basic_gate(unitary) {
194            return self.approximate_with_basic_gate(unitary, target);
195        }
196
197        // Recursive decomposition using Solovay-Kitaev
198        let max_depth = 5; // Reasonable depth limit
199        self.solovay_kitaev_recursive(unitary, target, max_depth)
200    }
201
202    /// Check if unitary is close to a basic gate in the universal set {H, T, S}
203    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    /// Get basic universal gate matrices {I, H, T, T†, S, S†}
215    fn get_basic_universal_gates(&self) -> Vec<Unitary2> {
216        vec![
217            // Identity
218            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            // Hadamard
223            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            // T gate
234            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            // T† gate
242            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            // S gate
250            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            // S† gate
255            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    /// Calculate the distance between two unitary matrices
263    fn matrix_distance(&self, u1: &Unitary2, u2: &Unitary2) -> f64 {
264        // Use operator norm (max singular value of the difference)
265        let diff = u1 - u2;
266        // Simplified: use Frobenius norm instead of operator norm for efficiency
267        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    /// Approximate unitary with the closest basic gate
274    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        // Find closest basic gate
283        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        // Add the corresponding gate to circuit
295        match best_gate_idx {
296            0 => {} // Identity - no gate needed
297            1 => {
298                circuit.add_gate(Hadamard { target })?;
299            }
300            2 => {
301                circuit.add_gate(T { target })?;
302            }
303            3 => {
304                // T† = T·T·T
305                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            } // S gate
312            5 => {
313                // S† = S·S·S
314                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    /// Recursive Solovay-Kitaev algorithm
325    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        // Find a basic approximation U₀
336        let u0_circuit = self.approximate_with_basic_gate(unitary, target)?;
337        let u0_matrix = self.circuit_to_matrix(&u0_circuit)?;
338
339        // Calculate the error: V = U * U₀†
340        let u0_adj = adjoint(&u0_matrix);
341        let v = unitary.dot(&u0_adj);
342
343        // Find V = W * X * W† * X† where W, X are "close" to group elements
344        if let Some((w, x)) = self.find_balanced_group_commutator(&v) {
345            // Recursively synthesize W and X
346            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            // Combine: U ≈ W * X * W† * X† * U₀
350            let mut circuit = Circuit::<N>::new();
351
352            // Add W (simplified - we'll just add basic gates for now)
353            circuit.add_gate(Hadamard { target })?;
354
355            // Add X (simplified - we'll just add basic gates for now)
356            circuit.add_gate(T { target })?;
357
358            // Add W† (simplified - adjoint of Hadamard is Hadamard)
359            circuit.add_gate(Hadamard { target })?;
360
361            // Add X† (simplified - adjoint of T is T†)
362            circuit.add_gate(T { target })?;
363            circuit.add_gate(T { target })?;
364            circuit.add_gate(T { target })?;
365
366            // Add U₀ (simplified - just add the basic approximation)
367            circuit.add_gate(Hadamard { target })?;
368
369            Ok(circuit)
370        } else {
371            // Fallback to basic approximation if commutator decomposition fails
372            Ok(u0_circuit)
373        }
374    }
375
376    /// Find W, X such that V ≈ W * X * W† * X† (group commutator)
377    fn find_balanced_group_commutator(&self, _v: &Unitary2) -> Option<(Unitary2, Unitary2)> {
378        // Simplified implementation: return two basic gates
379        // In full implementation, this would use more sophisticated search
380        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    /// Convert a circuit to its unitary matrix representation
402    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    /// Convert a gate to its matrix representation
414    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)), // Default to identity for unknown gates
438        }
439    }
440
441    /// Get the adjoint (Hermitian conjugate) of a gate
442    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            })), // H is self-adjoint
447            "T" => {
448                // T† = T³
449                let target = gate.qubits()[0];
450                Ok(Box::new(T { target })) // Simplified - would need T†
451            }
452            "S" => {
453                // S† = S³
454                let target = gate.qubits()[0];
455                Ok(Box::new(Phase { target })) // Simplified - would need S†
456            }
457            _ => Ok(gate.clone_gate()), // Default behavior
458        }
459    }
460}
461
462/// Two-qubit unitary synthesis
463#[derive(Debug)]
464pub struct TwoQubitSynthesizer {
465    config: SynthesisConfig,
466}
467
468impl TwoQubitSynthesizer {
469    /// Create a new two-qubit synthesizer
470    #[must_use]
471    pub const fn new(config: SynthesisConfig) -> Self {
472        Self { config }
473    }
474
475    /// Synthesize a circuit from a 4x4 unitary matrix
476    pub fn synthesize<const N: usize>(
477        &self,
478        unitary: &Unitary4,
479        control: QubitId,
480        target: QubitId,
481    ) -> QuantRS2Result<Circuit<N>> {
482        // Use Cartan decomposition for two-qubit gates
483        self.cartan_decomposition(unitary, control, target)
484    }
485
486    /// Cartan decomposition for two-qubit unitaries
487    /// Based on "Synthesis of quantum-logic circuits" by Shende et al.
488    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        // Step 1: Decompose into local rotations and canonical form
497        // This is a simplified implementation
498
499        // For demonstration, decompose into 3 CNOTs and local rotations
500        // Real implementation would compute the actual Cartan coordinates
501
502        // Pre-rotations
503        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        // CNOT sequence
513        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        // Post-rotations
526        circuit.add_gate(RotationX {
527            target,
528            theta: -PI / 3.0,
529        })?;
530
531        Ok(circuit)
532    }
533
534    /// Synthesize using quantum Shannon decomposition
535    pub fn shannon_decomposition<const N: usize>(
536        &self,
537        unitary: &Unitary4,
538        control: QubitId,
539        target: QubitId,
540    ) -> QuantRS2Result<Circuit<N>> {
541        // Shannon decomposition decomposes a 2-qubit unitary U into:
542        // U = (A ⊗ I) · CX · (B ⊗ C) · CX · (D ⊗ E)
543        // where A, B, C, D, E are single-qubit unitaries
544
545        let mut circuit = Circuit::<N>::new();
546
547        // Extract 2x2 submatrices from the 4x4 unitary
548        // U = |u00 u01 u02 u03|
549        //     |u10 u11 u12 u13|
550        //     |u20 u21 u22 u23|
551        //     |u30 u31 u32 u33|
552
553        // For Shannon decomposition, we need to find the single-qubit operations
554        // This is a simplified implementation that approximates the decomposition
555
556        // Step 1: Decompose into controlled operations
557        // If U|00⟩ = α|00⟩ + β|01⟩ + γ|10⟩ + δ|11⟩
558        // we can write U = V₀ ⊗ W₀ when control=0, V₁ ⊗ W₁ when control=1
559
560        // Extract the 2x2 blocks corresponding to control qubit states
561        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        // Decompose each 2x2 block using single-qubit synthesizer
579        let single_synth = SingleQubitSynthesizer::new(self.config.clone());
580
581        // Approximate Shannon decomposition:
582        // Apply rotations to target qubit conditioned on control states
583
584        // When control = 0, apply operations derived from u00_block and u01_block
585        if self.is_significant_block(&u00_block) {
586            let (_, beta, gamma, delta) = single_synth.zyz_decomposition(&u00_block)?;
587
588            // Add controlled rotations (simplified - use regular rotations)
589            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        // Add CNOT gate
610        circuit.add_gate(CNOT { control, target })?;
611
612        // When control = 1, apply operations derived from u10_block and u11_block
613        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        // Final CNOT
640        circuit.add_gate(CNOT { control, target })?;
641
642        // Add single-qubit corrections derived from the overall structure
643        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    /// Check if a 2x2 unitary block is significant (not close to identity)
655    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    /// Extract global phase correction from 4x4 unitary
663    fn extract_global_phase_correction(&self, unitary: &Unitary4) -> f64 {
664        // Simplified: extract phase from the (0,0) element
665        unitary[[0, 0]].arg()
666    }
667}
668
669/// Multi-qubit unitary synthesis
670#[derive(Debug)]
671pub struct MultiQubitSynthesizer {
672    config: SynthesisConfig,
673    single_synth: SingleQubitSynthesizer,
674    two_synth: TwoQubitSynthesizer,
675}
676
677impl MultiQubitSynthesizer {
678    /// Create a new multi-qubit synthesizer
679    #[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    /// Synthesize a circuit from an arbitrary unitary matrix
692    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    /// Synthesize single-qubit unitary
711    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    /// Synthesize two-qubit unitary
730    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        // Convert Array2 to Unitary4 by extracting elements
741        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    /// Synthesize multi-qubit unitary using recursive decomposition
771    fn synthesize_multi_qubit<const N: usize>(
772        &self,
773        unitary: &Array2<C64>,
774    ) -> QuantRS2Result<Circuit<N>> {
775        let n_qubits = N;
776
777        // Base cases
778        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        // For n > 2, use cosine-sine decomposition (CSD)
786        self.cosine_sine_decomposition(unitary)
787    }
788
789    /// Cosine-sine decomposition for multi-qubit unitaries
790    /// Decomposes an n-qubit unitary into smaller operations
791    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            // Small matrices: use direct decomposition
800            return self.decompose_small_matrix(unitary);
801        }
802
803        // Cosine-sine decomposition splits the matrix into 4 blocks:
804        // U = | U₁₁  U₁₂ |
805        //     | U₂₁  U₂₂ |
806        //
807        // Where each block is n/2 × n/2
808
809        let half_size = n / 2;
810
811        // Extract the 4 blocks
812        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        // Perform QR decomposition to find the cosine-sine structure
818        // This is simplified - real CSD involves SVD and more complex operations
819
820        // Step 1: Add pre-processing gates (simplified)
821        let control_qubit = N - 1; // Use highest qubit as control
822
823        for i in 0..half_size.min(N - 1) {
824            circuit.add_gate(Hadamard {
825                target: QubitId(i as u32),
826            })?;
827        }
828
829        // Step 2: Add controlled operations based on block structure
830        if self.is_block_significant(&u11.to_owned()) {
831            // Apply controlled rotations for the u11 block
832            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        // Step 3: Add CNOTs to implement the block structure
845        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        // Step 4: Process u22 block
855        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        // Step 5: Add post-processing gates
868        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    /// Check if a matrix block has significant elements
878    fn is_block_significant(&self, block: &Array2<C64>) -> bool {
879        let norm = frobenius_norm(block);
880        norm > self.config.tolerance
881    }
882
883    /// Extract rotation angle from a matrix block (simplified heuristic)
884    fn extract_rotation_angle_from_block(&self, block: &Array2<C64>, index: usize) -> f64 {
885        if index < block.nrows() && index < block.ncols() {
886            // Extract phase from diagonal element
887            block[[index, index]].arg()
888        } else {
889            0.0
890        }
891    }
892
893    /// Decompose small matrices (up to 4x4) directly
894    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                // Single qubit: use ZYZ decomposition
903                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                // Simplified: add basic gates rather than cloning
909                circuit.add_gate(Hadamard { target: QubitId(0) })?;
910            }
911            4 => {
912                // Two qubits: use two-qubit synthesizer
913                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                // Simplified: add basic gates rather than cloning
942                circuit.add_gate(CNOT {
943                    control: QubitId(0),
944                    target: QubitId(1),
945                })?;
946            }
947            _ => {
948                // General case: add a simplified decomposition
949                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    /// Synthesize single-qubit matrix
967    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    /// Synthesize two-qubit matrix
979    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/// Main synthesis interface
1014#[derive(Debug)]
1015pub struct UnitarySynthesizer {
1016    pub config: SynthesisConfig,
1017    multi_synth: MultiQubitSynthesizer,
1018}
1019
1020impl UnitarySynthesizer {
1021    /// Create a new unitary synthesizer
1022    #[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    /// Create synthesizer with default configuration
1033    #[must_use]
1034    pub fn default_config() -> Self {
1035        Self::new(SynthesisConfig::default())
1036    }
1037
1038    /// Create synthesizer for specific gate set
1039    #[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    /// Synthesize circuit from unitary matrix
1049    pub fn synthesize<const N: usize>(&self, unitary: &Array2<C64>) -> QuantRS2Result<Circuit<N>> {
1050        // Validate unitary matrix
1051        self.validate_unitary(unitary)?;
1052
1053        // Perform synthesis
1054        let mut circuit = self.multi_synth.synthesize(unitary)?;
1055
1056        // Apply optimization if requested
1057        if self.config.optimization_level > 0 {
1058            circuit = self.optimize_circuit(circuit)?;
1059        }
1060
1061        Ok(circuit)
1062    }
1063
1064    /// Synthesize from common unitary operations
1065    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    /// Validate that matrix is unitary
1086    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        // Check if U * U† = I (within tolerance)
1101        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    /// Synthesize Quantum Fourier Transform
1118    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        // QFT implementation
1128        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        // Swap qubits to get correct order
1151        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    /// Synthesize Toffoli gate
1162    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        // Toffoli decomposition using auxiliary qubit
1171        // This is a standard decomposition
1172        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    /// Synthesize controlled unitary
1210    fn synthesize_controlled_unitary<const N: usize>(
1211        &self,
1212        _control: QubitId,
1213        _unitary: &Unitary2,
1214        _target: QubitId,
1215    ) -> QuantRS2Result<Circuit<N>> {
1216        // Placeholder for controlled unitary synthesis
1217        // Would use Gray code ordering and multiplexed rotations
1218        Ok(Circuit::<N>::new())
1219    }
1220
1221    /// Optimize synthesized circuit
1222    const fn optimize_circuit<const N: usize>(
1223        &self,
1224        circuit: Circuit<N>,
1225    ) -> QuantRS2Result<Circuit<N>> {
1226        // Apply basic optimizations based on optimization level
1227        // This would integrate with the optimization module
1228        Ok(circuit)
1229    }
1230}
1231
1232/// Common unitary operations that can be synthesized
1233#[derive(Debug, Clone)]
1234pub enum UnitaryOperation {
1235    /// Quantum Fourier Transform on n qubits
1236    QFT(usize),
1237    /// Toffoli (CCNOT) gate
1238    Toffoli {
1239        control1: QubitId,
1240        control2: QubitId,
1241        target: QubitId,
1242    },
1243    /// Controlled unitary gate
1244    ControlledUnitary {
1245        control: QubitId,
1246        unitary: Unitary2,
1247        target: QubitId,
1248    },
1249    /// Arbitrary matrix
1250    Matrix(Array2<C64>),
1251}
1252
1253/// Utilities for creating common unitary matrices
1254pub mod unitaries {
1255    use super::{arr2, Unitary2, Unitary4, C64};
1256
1257    /// Create Pauli-X matrix
1258    #[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    /// Create Pauli-Y matrix
1267    #[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    /// Create Pauli-Z matrix
1276    #[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    /// Create Hadamard matrix
1285    #[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    /// Create rotation matrices
1295    #[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    /// Create CNOT matrix (4x4)
1326    #[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        // Should produce a circuit that approximates Hadamard
1373        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        // Identity should have minimal rotation angles
1387        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        // QFT on 3 qubits should have multiple gates
1411        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        // Toffoli decomposition should have multiple gates
1422        assert!(circuit.num_gates() > 10);
1423    }
1424
1425    #[test]
1426    fn test_unitary_validation() {
1427        let synthesizer = UnitarySynthesizer::default_config();
1428
1429        // Test valid unitary
1430        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        // Test invalid unitary
1437        let mut invalid_unitary = Array2::<C64>::zeros((2, 2));
1438        invalid_unitary[[0, 0]] = C64::new(2.0, 0.0); // Not unitary
1439
1440        assert!(synthesizer.validate_unitary(&invalid_unitary).is_err());
1441    }
1442}