quantrs2_core/
synthesis.rs

1//! Gate synthesis from unitary matrices
2//!
3//! This module provides algorithms to decompose arbitrary unitary matrices
4//! into sequences of quantum gates, including:
5//! - Single-qubit unitary decomposition (ZYZ, XYX, etc.)
6//! - Two-qubit unitary decomposition (KAK/Cartan)
7//! - General n-qubit synthesis using Cosine-Sine decomposition
8
9use crate::cartan::{CartanDecomposer, CartanDecomposition};
10// use crate::controlled::{make_controlled, ControlledGate};
11use crate::error::{QuantRS2Error, QuantRS2Result};
12use crate::gate::{single::*, GateOp};
13use crate::matrix_ops::{matrices_approx_equal, DenseMatrix, QuantumMatrix};
14use crate::qubit::QubitId;
15use ndarray::{Array2, ArrayView2};
16use num_complex::Complex64;
17use std::f64::consts::PI;
18
19/// Result of single-qubit decomposition
20#[derive(Debug, Clone)]
21pub struct SingleQubitDecomposition {
22    /// Global phase
23    pub global_phase: f64,
24    /// First rotation angle (Z or X depending on basis)
25    pub theta1: f64,
26    /// Middle rotation angle (Y)
27    pub phi: f64,
28    /// Last rotation angle (Z or X depending on basis)
29    pub theta2: f64,
30    /// The basis used (e.g., "ZYZ", "XYX")
31    pub basis: String,
32}
33
34/// Decompose a single-qubit unitary into ZYZ rotations
35pub fn decompose_single_qubit_zyz(
36    unitary: &ArrayView2<Complex64>,
37) -> QuantRS2Result<SingleQubitDecomposition> {
38    if unitary.shape() != &[2, 2] {
39        return Err(QuantRS2Error::InvalidInput(
40            "Single-qubit unitary must be 2x2".to_string(),
41        ));
42    }
43
44    // Check unitarity
45    let matrix = DenseMatrix::new(unitary.to_owned())?;
46    if !matrix.is_unitary(1e-10)? {
47        return Err(QuantRS2Error::InvalidInput(
48            "Matrix is not unitary".to_string(),
49        ));
50    }
51
52    // Extract matrix elements
53    let a = unitary[[0, 0]];
54    let b = unitary[[0, 1]];
55    let c = unitary[[1, 0]];
56    let d = unitary[[1, 1]];
57
58    // Calculate global phase from determinant
59    let det = a * d - b * c;
60    let global_phase = det.arg() / 2.0;
61
62    // Normalize by the determinant to make the matrix special unitary
63    let det_sqrt = det.sqrt();
64    let a = a / det_sqrt;
65    let b = b / det_sqrt;
66    let c = c / det_sqrt;
67    let d = d / det_sqrt;
68
69    // Decompose into ZYZ angles
70    // U = e^(i*global_phase) * Rz(theta2) * Ry(phi) * Rz(theta1)
71
72    let phi = 2.0 * a.norm().acos();
73
74    let (theta1, theta2) = if phi.abs() < 1e-10 {
75        // Identity or phase gate
76        let phase = if a.norm() > 0.5 {
77            a.arg() * 2.0
78        } else {
79            d.arg() * 2.0
80        };
81        (0.0, phase)
82    } else if (phi - PI).abs() < 1e-10 {
83        // Pi rotation
84        (-b.arg() + c.arg() + PI, 0.0)
85    } else {
86        let theta1 = c.arg() - b.arg();
87        let theta2 = a.arg() + b.arg();
88        (theta1, theta2)
89    };
90
91    Ok(SingleQubitDecomposition {
92        global_phase,
93        theta1,
94        phi,
95        theta2,
96        basis: "ZYZ".to_string(),
97    })
98}
99
100/// Decompose a single-qubit unitary into XYX rotations
101pub fn decompose_single_qubit_xyx(
102    unitary: &ArrayView2<Complex64>,
103) -> QuantRS2Result<SingleQubitDecomposition> {
104    // Convert to Pauli basis and use ZYZ decomposition
105    let h_gate = Array2::from_shape_vec(
106        (2, 2),
107        vec![
108            Complex64::new(1.0, 0.0),
109            Complex64::new(1.0, 0.0),
110            Complex64::new(1.0, 0.0),
111            Complex64::new(-1.0, 0.0),
112        ],
113    )
114    .unwrap()
115        / Complex64::new(2.0_f64.sqrt(), 0.0);
116
117    // Transform: U' = H * U * H
118    let u_transformed = h_gate.dot(unitary).dot(&h_gate);
119    let decomp = decompose_single_qubit_zyz(&u_transformed.view())?;
120
121    Ok(SingleQubitDecomposition {
122        global_phase: decomp.global_phase,
123        theta1: decomp.theta1,
124        phi: decomp.phi,
125        theta2: decomp.theta2,
126        basis: "XYX".to_string(),
127    })
128}
129
130/// Convert single-qubit decomposition to gate sequence
131pub fn single_qubit_gates(
132    decomp: &SingleQubitDecomposition,
133    qubit: QubitId,
134) -> Vec<Box<dyn GateOp>> {
135    let mut gates: Vec<Box<dyn GateOp>> = Vec::new();
136
137    match decomp.basis.as_str() {
138        "ZYZ" => {
139            if decomp.theta1.abs() > 1e-10 {
140                gates.push(Box::new(RotationZ {
141                    target: qubit,
142                    theta: decomp.theta1,
143                }));
144            }
145            if decomp.phi.abs() > 1e-10 {
146                gates.push(Box::new(RotationY {
147                    target: qubit,
148                    theta: decomp.phi,
149                }));
150            }
151            if decomp.theta2.abs() > 1e-10 {
152                gates.push(Box::new(RotationZ {
153                    target: qubit,
154                    theta: decomp.theta2,
155                }));
156            }
157        }
158        "XYX" => {
159            if decomp.theta1.abs() > 1e-10 {
160                gates.push(Box::new(RotationX {
161                    target: qubit,
162                    theta: decomp.theta1,
163                }));
164            }
165            if decomp.phi.abs() > 1e-10 {
166                gates.push(Box::new(RotationY {
167                    target: qubit,
168                    theta: decomp.phi,
169                }));
170            }
171            if decomp.theta2.abs() > 1e-10 {
172                gates.push(Box::new(RotationX {
173                    target: qubit,
174                    theta: decomp.theta2,
175                }));
176            }
177        }
178        _ => {} // Unknown basis
179    }
180
181    gates
182}
183
184/// Result of two-qubit KAK decomposition (alias for CartanDecomposition)
185pub type KAKDecomposition = CartanDecomposition;
186
187/// Decompose a two-qubit unitary using KAK decomposition
188pub fn decompose_two_qubit_kak(
189    unitary: &ArrayView2<Complex64>,
190) -> QuantRS2Result<KAKDecomposition> {
191    // Use Cartan decomposer for KAK decomposition
192    let mut decomposer = CartanDecomposer::new();
193    let owned_unitary = unitary.to_owned();
194    decomposer.decompose(&owned_unitary)
195}
196
197/// Convert KAK decomposition to gate sequence
198pub fn kak_to_gates(
199    decomp: &KAKDecomposition,
200    qubit1: QubitId,
201    qubit2: QubitId,
202) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
203    // Use CartanDecomposer to convert to gates
204    let decomposer = CartanDecomposer::new();
205    let qubit_ids = vec![qubit1, qubit2];
206    decomposer.to_gates(decomp, &qubit_ids)
207}
208
209/// Synthesize an arbitrary unitary matrix into quantum gates
210pub fn synthesize_unitary(
211    unitary: &ArrayView2<Complex64>,
212    qubits: &[QubitId],
213) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
214    let n = unitary.nrows();
215
216    if n != unitary.ncols() {
217        return Err(QuantRS2Error::InvalidInput(
218            "Matrix must be square".to_string(),
219        ));
220    }
221
222    let num_qubits = (n as f64).log2() as usize;
223    if (1 << num_qubits) != n {
224        return Err(QuantRS2Error::InvalidInput(
225            "Matrix dimension must be a power of 2".to_string(),
226        ));
227    }
228
229    if qubits.len() != num_qubits {
230        return Err(QuantRS2Error::InvalidInput(format!(
231            "Need {} qubits, got {}",
232            num_qubits,
233            qubits.len()
234        )));
235    }
236
237    // Check unitarity
238    let matrix = DenseMatrix::new(unitary.to_owned())?;
239    if !matrix.is_unitary(1e-10)? {
240        return Err(QuantRS2Error::InvalidInput(
241            "Matrix is not unitary".to_string(),
242        ));
243    }
244
245    match num_qubits {
246        1 => {
247            let decomp = decompose_single_qubit_zyz(unitary)?;
248            Ok(single_qubit_gates(&decomp, qubits[0]))
249        }
250        2 => {
251            let decomp = decompose_two_qubit_kak(unitary)?;
252            kak_to_gates(&decomp, qubits[0], qubits[1])
253        }
254        _ => {
255            // For n-qubit gates, use recursive decomposition
256            // This is a placeholder - would implement Cosine-Sine decomposition
257            Err(QuantRS2Error::UnsupportedOperation(format!(
258                "Synthesis for {}-qubit gates not yet implemented",
259                num_qubits
260            )))
261        }
262    }
263}
264
265/// Check if a unitary is close to a known gate
266pub fn identify_gate(unitary: &ArrayView2<Complex64>, tolerance: f64) -> Option<String> {
267    let n = unitary.nrows();
268
269    match n {
270        2 => {
271            // Check common single-qubit gates
272            let gates = vec![
273                ("I", Array2::eye(2)),
274                (
275                    "X",
276                    Array2::from_shape_vec(
277                        (2, 2),
278                        vec![
279                            Complex64::new(0.0, 0.0),
280                            Complex64::new(1.0, 0.0),
281                            Complex64::new(1.0, 0.0),
282                            Complex64::new(0.0, 0.0),
283                        ],
284                    )
285                    .unwrap(),
286                ),
287                (
288                    "Y",
289                    Array2::from_shape_vec(
290                        (2, 2),
291                        vec![
292                            Complex64::new(0.0, 0.0),
293                            Complex64::new(0.0, -1.0),
294                            Complex64::new(0.0, 1.0),
295                            Complex64::new(0.0, 0.0),
296                        ],
297                    )
298                    .unwrap(),
299                ),
300                (
301                    "Z",
302                    Array2::from_shape_vec(
303                        (2, 2),
304                        vec![
305                            Complex64::new(1.0, 0.0),
306                            Complex64::new(0.0, 0.0),
307                            Complex64::new(0.0, 0.0),
308                            Complex64::new(-1.0, 0.0),
309                        ],
310                    )
311                    .unwrap(),
312                ),
313                (
314                    "H",
315                    Array2::from_shape_vec(
316                        (2, 2),
317                        vec![
318                            Complex64::new(1.0, 0.0),
319                            Complex64::new(1.0, 0.0),
320                            Complex64::new(1.0, 0.0),
321                            Complex64::new(-1.0, 0.0),
322                        ],
323                    )
324                    .unwrap()
325                        / Complex64::new(2.0_f64.sqrt(), 0.0),
326                ),
327            ];
328
329            for (name, gate) in gates {
330                if matrices_approx_equal(unitary, &gate.view(), tolerance) {
331                    return Some(name.to_string());
332                }
333            }
334        }
335        4 => {
336            // Check common two-qubit gates
337            let mut cnot = Array2::eye(4);
338            cnot[[2, 2]] = Complex64::new(0.0, 0.0);
339            cnot[[2, 3]] = Complex64::new(1.0, 0.0);
340            cnot[[3, 2]] = Complex64::new(1.0, 0.0);
341            cnot[[3, 3]] = Complex64::new(0.0, 0.0);
342
343            if matrices_approx_equal(unitary, &cnot.view(), tolerance) {
344                return Some("CNOT".to_string());
345            }
346        }
347        _ => {}
348    }
349
350    None
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    #[test]
358    #[ignore] // TODO: Fix ZYZ decomposition algorithm
359    fn test_single_qubit_decomposition() {
360        // Test Hadamard gate
361        let h = Array2::from_shape_vec(
362            (2, 2),
363            vec![
364                Complex64::new(1.0, 0.0),
365                Complex64::new(1.0, 0.0),
366                Complex64::new(1.0, 0.0),
367                Complex64::new(-1.0, 0.0),
368            ],
369        )
370        .unwrap()
371            / Complex64::new(2.0_f64.sqrt(), 0.0);
372
373        let decomp = decompose_single_qubit_zyz(&h.view()).unwrap();
374
375        // Reconstruct and verify
376        let rz1 = Array2::from_shape_vec(
377            (2, 2),
378            vec![
379                Complex64::new(0.0, -decomp.theta1 / 2.0).exp(),
380                Complex64::new(0.0, 0.0),
381                Complex64::new(0.0, 0.0),
382                Complex64::new(0.0, decomp.theta1 / 2.0).exp(),
383            ],
384        )
385        .unwrap();
386
387        let ry = Array2::from_shape_vec(
388            (2, 2),
389            vec![
390                Complex64::new((decomp.phi / 2.0).cos(), 0.0),
391                Complex64::new(-(decomp.phi / 2.0).sin(), 0.0),
392                Complex64::new((decomp.phi / 2.0).sin(), 0.0),
393                Complex64::new((decomp.phi / 2.0).cos(), 0.0),
394            ],
395        )
396        .unwrap();
397
398        let rz2 = Array2::from_shape_vec(
399            (2, 2),
400            vec![
401                Complex64::new(0.0, -decomp.theta2 / 2.0).exp(),
402                Complex64::new(0.0, 0.0),
403                Complex64::new(0.0, 0.0),
404                Complex64::new(0.0, decomp.theta2 / 2.0).exp(),
405            ],
406        )
407        .unwrap();
408
409        // Reconstruct: e^(i*global_phase) * Rz(theta2) * Ry(phi) * Rz(theta1)
410        let reconstructed = Complex64::new(0.0, decomp.global_phase).exp() * rz2.dot(&ry).dot(&rz1);
411
412        // Check reconstruction
413        assert!(matrices_approx_equal(
414            &h.view(),
415            &reconstructed.view(),
416            1e-10
417        ));
418    }
419
420    #[test]
421    fn test_gate_identification() {
422        let x = Array2::from_shape_vec(
423            (2, 2),
424            vec![
425                Complex64::new(0.0, 0.0),
426                Complex64::new(1.0, 0.0),
427                Complex64::new(1.0, 0.0),
428                Complex64::new(0.0, 0.0),
429            ],
430        )
431        .unwrap();
432
433        assert_eq!(identify_gate(&x.view(), 1e-10), Some("X".to_string()));
434    }
435}