use crate::cartan::{CartanDecomposer, CartanDecomposition};
use crate::error::{QuantRS2Error, QuantRS2Result};
use crate::gate::{single::*, GateOp};
use crate::matrix_ops::{matrices_approx_equal, DenseMatrix, QuantumMatrix};
use crate::qubit::QubitId;
use scirs2_core::ndarray::{Array2, ArrayView2};
use scirs2_core::Complex64;
use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct SingleQubitDecomposition {
pub global_phase: f64,
pub theta1: f64,
pub phi: f64,
pub theta2: f64,
pub basis: String,
}
pub fn decompose_single_qubit_zyz(
unitary: &ArrayView2<Complex64>,
) -> QuantRS2Result<SingleQubitDecomposition> {
if unitary.shape() != &[2, 2] {
return Err(QuantRS2Error::InvalidInput(
"Single-qubit unitary must be 2x2".to_string(),
));
}
let matrix = DenseMatrix::new(unitary.to_owned())?;
if !matrix.is_unitary(1e-10)? {
return Err(QuantRS2Error::InvalidInput(
"Matrix is not unitary".to_string(),
));
}
let a = unitary[[0, 0]];
let b = unitary[[0, 1]];
let c = unitary[[1, 0]];
let d = unitary[[1, 1]];
let det = a * d - b * c;
let global_phase = det.arg() / 2.0;
let det_sqrt = det.sqrt();
let a = a / det_sqrt;
let b = b / det_sqrt;
let c = c / det_sqrt;
let d = d / det_sqrt;
let phi = 2.0 * a.norm().acos();
let (theta1, theta2) = if phi.abs() < 1e-10 {
let phase = if a.norm() > 0.5 {
a.arg() * 2.0
} else {
d.arg() * 2.0
};
(0.0, phase)
} else if (phi - PI).abs() < 1e-10 {
(-b.arg() + c.arg() + PI, 0.0)
} else {
let theta1 = -a.arg() - c.arg();
let theta2 = c.arg() - a.arg();
(theta1, theta2)
};
Ok(SingleQubitDecomposition {
global_phase,
theta1,
phi,
theta2,
basis: "ZYZ".to_string(),
})
}
pub fn decompose_single_qubit_xyx(
unitary: &ArrayView2<Complex64>,
) -> QuantRS2Result<SingleQubitDecomposition> {
let h_gate = Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(1.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(-1.0, 0.0),
],
)
.expect("2x2 Hadamard matrix shape is always valid")
/ Complex64::new(2.0_f64.sqrt(), 0.0);
let u_transformed = h_gate.dot(unitary).dot(&h_gate);
let decomp = decompose_single_qubit_zyz(&u_transformed.view())?;
Ok(SingleQubitDecomposition {
global_phase: decomp.global_phase,
theta1: decomp.theta1,
phi: decomp.phi,
theta2: decomp.theta2,
basis: "XYX".to_string(),
})
}
pub fn single_qubit_gates(
decomp: &SingleQubitDecomposition,
qubit: QubitId,
) -> Vec<Box<dyn GateOp>> {
let mut gates: Vec<Box<dyn GateOp>> = Vec::new();
match decomp.basis.as_str() {
"ZYZ" => {
if decomp.theta1.abs() > 1e-10 {
gates.push(Box::new(RotationZ {
target: qubit,
theta: decomp.theta1,
}));
}
if decomp.phi.abs() > 1e-10 {
gates.push(Box::new(RotationY {
target: qubit,
theta: decomp.phi,
}));
}
if decomp.theta2.abs() > 1e-10 {
gates.push(Box::new(RotationZ {
target: qubit,
theta: decomp.theta2,
}));
}
}
"XYX" => {
if decomp.theta1.abs() > 1e-10 {
gates.push(Box::new(RotationX {
target: qubit,
theta: decomp.theta1,
}));
}
if decomp.phi.abs() > 1e-10 {
gates.push(Box::new(RotationY {
target: qubit,
theta: decomp.phi,
}));
}
if decomp.theta2.abs() > 1e-10 {
gates.push(Box::new(RotationX {
target: qubit,
theta: decomp.theta2,
}));
}
}
_ => {} }
gates
}
pub type KAKDecomposition = CartanDecomposition;
pub fn decompose_two_qubit_kak(
unitary: &ArrayView2<Complex64>,
) -> QuantRS2Result<KAKDecomposition> {
let mut decomposer = CartanDecomposer::new();
let owned_unitary = unitary.to_owned();
decomposer.decompose(&owned_unitary)
}
pub fn kak_to_gates(
decomp: &KAKDecomposition,
qubit1: QubitId,
qubit2: QubitId,
) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
let decomposer = CartanDecomposer::new();
let qubit_ids = vec![qubit1, qubit2];
decomposer.to_gates(decomp, &qubit_ids)
}
pub fn synthesize_unitary(
unitary: &ArrayView2<Complex64>,
qubits: &[QubitId],
) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
let n = unitary.nrows();
if n != unitary.ncols() {
return Err(QuantRS2Error::InvalidInput(
"Matrix must be square".to_string(),
));
}
let num_qubits = (n as f64).log2() as usize;
if (1 << num_qubits) != n {
return Err(QuantRS2Error::InvalidInput(
"Matrix dimension must be a power of 2".to_string(),
));
}
if qubits.len() != num_qubits {
return Err(QuantRS2Error::InvalidInput(format!(
"Need {} qubits, got {}",
num_qubits,
qubits.len()
)));
}
let matrix = DenseMatrix::new(unitary.to_owned())?;
if !matrix.is_unitary(1e-10)? {
return Err(QuantRS2Error::InvalidInput(
"Matrix is not unitary".to_string(),
));
}
match num_qubits {
1 => {
let decomp = decompose_single_qubit_zyz(unitary)?;
Ok(single_qubit_gates(&decomp, qubits[0]))
}
2 => {
let decomp = decompose_two_qubit_kak(unitary)?;
kak_to_gates(&decomp, qubits[0], qubits[1])
}
_ => {
Err(QuantRS2Error::UnsupportedOperation(format!(
"Synthesis for {num_qubits}-qubit gates not yet implemented"
)))
}
}
}
pub fn identify_gate(unitary: &ArrayView2<Complex64>, tolerance: f64) -> Option<String> {
let n = unitary.nrows();
match n {
2 => {
let gates = vec![
("I", Array2::eye(2)),
(
"X",
Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(0.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
],
)
.expect("2x2 X gate shape is always valid"),
),
(
"Y",
Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(0.0, 0.0),
Complex64::new(0.0, -1.0),
Complex64::new(0.0, 1.0),
Complex64::new(0.0, 0.0),
],
)
.expect("2x2 Y gate shape is always valid"),
),
(
"Z",
Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(-1.0, 0.0),
],
)
.expect("2x2 Z gate shape is always valid"),
),
(
"H",
Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(1.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(-1.0, 0.0),
],
)
.expect("2x2 H gate shape is always valid")
/ Complex64::new(2.0_f64.sqrt(), 0.0),
),
];
for (name, gate) in gates {
if matrices_approx_equal(unitary, &gate.view(), tolerance) {
return Some(name.to_string());
}
}
}
4 => {
let mut cnot = Array2::eye(4);
cnot[[2, 2]] = Complex64::new(0.0, 0.0);
cnot[[2, 3]] = Complex64::new(1.0, 0.0);
cnot[[3, 2]] = Complex64::new(1.0, 0.0);
cnot[[3, 3]] = Complex64::new(0.0, 0.0);
if matrices_approx_equal(unitary, &cnot.view(), tolerance) {
return Some("CNOT".to_string());
}
}
_ => {}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_qubit_decomposition() {
let h = Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(1.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(-1.0, 0.0),
],
)
.expect("Hadamard matrix shape is always valid 2x2")
/ Complex64::new(2.0_f64.sqrt(), 0.0);
let decomp =
decompose_single_qubit_zyz(&h.view()).expect("ZYZ decomposition should succeed");
let rz1 = Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(0.0, -decomp.theta1 / 2.0).exp(),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, decomp.theta1 / 2.0).exp(),
],
)
.expect("Rz1 matrix shape is always valid 2x2");
let ry = Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new((decomp.phi / 2.0).cos(), 0.0),
Complex64::new(-(decomp.phi / 2.0).sin(), 0.0),
Complex64::new((decomp.phi / 2.0).sin(), 0.0),
Complex64::new((decomp.phi / 2.0).cos(), 0.0),
],
)
.expect("Ry matrix shape is always valid 2x2");
let rz2 = Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(0.0, -decomp.theta2 / 2.0).exp(),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, decomp.theta2 / 2.0).exp(),
],
)
.expect("Rz2 matrix shape is always valid 2x2");
let reconstructed = Complex64::new(0.0, decomp.global_phase).exp() * rz2.dot(&ry).dot(&rz1);
assert!(matrices_approx_equal(
&h.view(),
&reconstructed.view(),
1e-10
));
}
#[test]
fn test_gate_identification() {
let x = Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(0.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
],
)
.expect("X gate matrix shape is always valid 2x2");
assert_eq!(identify_gate(&x.view(), 1e-10), Some("X".to_string()));
}
}