use num_complex::Complex64;
use smallvec::SmallVec;
use std::f64::consts::{FRAC_1_SQRT_2, PI};
use std::fmt;
const NEAR_ZERO_NORM_SQ: f64 = 1e-24;
const IDENTITY_EPS: f64 = 1e-12;
#[derive(Debug, Clone, PartialEq)]
pub enum Gate {
Id,
X,
Y,
Z,
H,
S,
Sdg,
T,
Tdg,
SX,
SXdg,
Rx(f64),
Ry(f64),
Rz(f64),
P(f64),
Rzz(f64),
Cx,
Cz,
Swap,
Cu(Box<[[Complex64; 2]; 2]>),
Mcu(Box<McuData>),
Fused(Box<[[Complex64; 2]; 2]>),
BatchPhase(Box<BatchPhaseData>),
BatchRzz(Box<BatchRzzData>),
DiagonalBatch(Box<DiagonalBatchData>),
MultiFused(Box<MultiFusedData>),
Fused2q(Box<[[Complex64; 4]; 4]>),
Multi2q(Box<Multi2qData>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct McuData {
pub mat: [[Complex64; 2]; 2],
pub num_controls: u8,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BatchPhaseData {
pub phases: SmallVec<[(usize, Complex64); 8]>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BatchRzzData {
pub edges: Vec<(usize, usize, f64)>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DiagEntry {
Phase1q {
qubit: usize,
d0: Complex64,
d1: Complex64,
},
Phase2q {
q0: usize,
q1: usize,
phase: Complex64,
},
Parity2q {
q0: usize,
q1: usize,
same: Complex64,
diff: Complex64,
},
}
impl DiagEntry {
pub fn as_1q_matrix(&self) -> Option<(usize, [[Complex64; 2]; 2])> {
match *self {
DiagEntry::Phase1q { qubit, d0, d1 } => {
let z = Complex64::new(0.0, 0.0);
Some((qubit, [[d0, z], [z, d1]]))
}
_ => None,
}
}
pub fn as_2q_matrix(&self) -> Option<(usize, usize, [[Complex64; 4]; 4])> {
let z = Complex64::new(0.0, 0.0);
let one = Complex64::new(1.0, 0.0);
match *self {
DiagEntry::Phase2q { q0, q1, phase } => Some((
q0,
q1,
[
[one, z, z, z],
[z, one, z, z],
[z, z, one, z],
[z, z, z, phase],
],
)),
DiagEntry::Parity2q {
q0, q1, same, diff, ..
} => Some((
q0,
q1,
[
[same, z, z, z],
[z, diff, z, z],
[z, z, diff, z],
[z, z, z, same],
],
)),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DiagonalBatchData {
pub entries: Vec<DiagEntry>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MultiFusedData {
pub gates: Vec<(usize, [[Complex64; 2]; 2])>,
pub all_diagonal: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Multi2qData {
pub gates: Vec<(usize, usize, [[Complex64; 4]; 4])>,
}
#[inline]
pub(crate) fn kron_2x2(a: &[[Complex64; 2]; 2], b: &[[Complex64; 2]; 2]) -> [[Complex64; 4]; 4] {
let mut result = [[Complex64::new(0.0, 0.0); 4]; 4];
for i in 0..2 {
for k in 0..2 {
let aik = a[i][k];
for j in 0..2 {
for l in 0..2 {
result[i * 2 + j][k * 2 + l] = aik * b[j][l];
}
}
}
}
result
}
#[inline]
pub(crate) fn mat_mul_4x4(a: &[[Complex64; 4]; 4], b: &[[Complex64; 4]; 4]) -> [[Complex64; 4]; 4] {
let zero = Complex64::new(0.0, 0.0);
let mut result = [[zero; 4]; 4];
for i in 0..4 {
for j in 0..4 {
let mut sum = zero;
for k in 0..4 {
sum += a[i][k] * b[k][j];
}
result[i][j] = sum;
}
}
result
}
fn adjoint_4x4(m: &[[Complex64; 4]; 4]) -> [[Complex64; 4]; 4] {
let mut result = [[Complex64::new(0.0, 0.0); 4]; 4];
for i in 0..4 {
for j in 0..4 {
result[i][j] = m[j][i].conj();
}
}
result
}
fn adjoint_2x2(m: &[[Complex64; 2]; 2]) -> [[Complex64; 2]; 2] {
[
[m[0][0].conj(), m[1][0].conj()],
[m[0][1].conj(), m[1][1].conj()],
]
}
#[inline]
pub(crate) fn mat_mul_2x2(a: &[[Complex64; 2]; 2], b: &[[Complex64; 2]; 2]) -> [[Complex64; 2]; 2] {
[
[
a[0][0] * b[0][0] + a[0][1] * b[1][0],
a[0][0] * b[0][1] + a[0][1] * b[1][1],
],
[
a[1][0] * b[0][0] + a[1][1] * b[1][0],
a[1][0] * b[0][1] + a[1][1] * b[1][1],
],
]
}
impl Gate {
#[inline]
pub fn num_qubits(&self) -> usize {
match self {
Gate::Rzz(_) | Gate::Cx | Gate::Cz | Gate::Swap | Gate::Cu(_) | Gate::Fused2q(_) => 2,
Gate::Mcu(data) => data.num_controls as usize + 1,
Gate::BatchPhase(data) => 1 + data.phases.len(),
Gate::BatchRzz(data) => {
let mut count = 0;
let mut seen = [false; 64];
for &(q0, q1, _) in &data.edges {
if !seen[q0] {
seen[q0] = true;
count += 1;
}
if !seen[q1] {
seen[q1] = true;
count += 1;
}
}
count
}
Gate::DiagonalBatch(data) => {
let mut count = 0;
let mut seen = [false; 64];
for e in &data.entries {
let qs = match e {
DiagEntry::Phase1q { qubit, .. } => [*qubit, usize::MAX],
DiagEntry::Phase2q { q0, q1, .. } | DiagEntry::Parity2q { q0, q1, .. } => {
[*q0, *q1]
}
};
for &q in &qs {
if q < 64 && !seen[q] {
seen[q] = true;
count += 1;
}
}
}
count
}
Gate::MultiFused(data) => data.gates.len(),
Gate::Multi2q(data) => {
let mut count = 0;
let mut seen = [false; 64];
for &(q0, q1, _) in &data.gates {
if !seen[q0] {
seen[q0] = true;
count += 1;
}
if !seen[q1] {
seen[q1] = true;
count += 1;
}
}
count
}
_ => 1,
}
}
#[inline]
pub fn matrix_2x2(&self) -> [[Complex64; 2]; 2] {
let zero = Complex64::new(0.0, 0.0);
let one = Complex64::new(1.0, 0.0);
let i = Complex64::new(0.0, 1.0);
let neg_i = Complex64::new(0.0, -1.0);
let h = Complex64::new(FRAC_1_SQRT_2, 0.0);
match self {
Gate::Id => [[one, zero], [zero, one]],
Gate::X => [[zero, one], [one, zero]],
Gate::Y => [[zero, neg_i], [i, zero]],
Gate::Z => [[one, zero], [zero, -one]],
Gate::H => [[h, h], [h, -h]],
Gate::S => [[one, zero], [zero, i]],
Gate::Sdg => [[one, zero], [zero, neg_i]],
Gate::T => {
let phase = Complex64::from_polar(1.0, PI / 4.0);
[[one, zero], [zero, phase]]
}
Gate::Tdg => {
let phase = Complex64::from_polar(1.0, -PI / 4.0);
[[one, zero], [zero, phase]]
}
Gate::SX => {
let half = Complex64::new(0.5, 0.0);
let half_i = Complex64::new(0.0, 0.5);
[
[half + half_i, half - half_i],
[half - half_i, half + half_i],
]
}
Gate::SXdg => {
let half = Complex64::new(0.5, 0.0);
let half_i = Complex64::new(0.0, 0.5);
[
[half - half_i, half + half_i],
[half + half_i, half - half_i],
]
}
Gate::Rx(theta) => {
let c = Complex64::new((theta / 2.0).cos(), 0.0);
let s = Complex64::new(0.0, -(theta / 2.0).sin());
[[c, s], [s, c]]
}
Gate::Ry(theta) => {
let c = Complex64::new((theta / 2.0).cos(), 0.0);
let s = Complex64::new((theta / 2.0).sin(), 0.0);
[[c, -s], [s, c]]
}
Gate::Rz(theta) => {
let e_neg = Complex64::from_polar(1.0, -theta / 2.0);
let e_pos = Complex64::from_polar(1.0, theta / 2.0);
[[e_neg, zero], [zero, e_pos]]
}
Gate::P(theta) => {
let phase = Complex64::from_polar(1.0, *theta);
[[one, zero], [zero, phase]]
}
Gate::Fused(mat) => **mat,
Gate::Rzz(_)
| Gate::Cx
| Gate::Cz
| Gate::Swap
| Gate::Cu(_)
| Gate::Mcu(_)
| Gate::BatchPhase(_)
| Gate::BatchRzz(_)
| Gate::DiagonalBatch(_)
| Gate::MultiFused(_)
| Gate::Fused2q(_)
| Gate::Multi2q(_) => {
panic!(
"matrix_2x2 called on {}-qubit gate `{}`; use dedicated backend routine",
self.num_qubits(),
self.name()
)
}
}
}
pub fn matrix_4x4(&self) -> [[Complex64; 4]; 4] {
let z = Complex64::new(0.0, 0.0);
let o = Complex64::new(1.0, 0.0);
let m = Complex64::new(-1.0, 0.0);
match self {
Gate::Rzz(theta) => {
let ps = Complex64::from_polar(1.0, -theta / 2.0);
let pd = Complex64::from_polar(1.0, theta / 2.0);
[[ps, z, z, z], [z, pd, z, z], [z, z, pd, z], [z, z, z, ps]]
}
Gate::Cx => [[o, z, z, z], [z, o, z, z], [z, z, z, o], [z, z, o, z]],
Gate::Cz => [[o, z, z, z], [z, o, z, z], [z, z, o, z], [z, z, z, m]],
Gate::Swap => [[o, z, z, z], [z, z, o, z], [z, o, z, z], [z, z, z, o]],
Gate::Cu(mat) => [
[o, z, z, z],
[z, o, z, z],
[z, z, mat[0][0], mat[0][1]],
[z, z, mat[1][0], mat[1][1]],
],
Gate::Fused2q(mat) => **mat,
_ => panic!(
"matrix_4x4 called on non-standard-2q gate `{}`",
self.name()
),
}
}
#[inline]
pub fn name(&self) -> &'static str {
match self {
Gate::Id => "id",
Gate::X => "x",
Gate::Y => "y",
Gate::Z => "z",
Gate::H => "h",
Gate::S => "s",
Gate::Sdg => "sdg",
Gate::T => "t",
Gate::Tdg => "tdg",
Gate::SX => "sx",
Gate::SXdg => "sxdg",
Gate::Rx(_) => "rx",
Gate::Ry(_) => "ry",
Gate::Rz(_) => "rz",
Gate::P(_) => "p",
Gate::Rzz(_) => "rzz",
Gate::Cx => "cx",
Gate::Cz => "cz",
Gate::Swap => "swap",
Gate::Cu(_) => "cu",
Gate::Mcu(_) => "mcu",
Gate::Fused(_) => "fused",
Gate::BatchPhase(_) => "batch_phase",
Gate::BatchRzz(_) => "batch_rzz",
Gate::DiagonalBatch(_) => "diagonal_batch",
Gate::MultiFused(_) => "multi_fused",
Gate::Fused2q(_) => "fused_2q",
Gate::Multi2q(_) => "multi_2q",
}
}
pub fn inverse(&self) -> Gate {
match self {
Gate::Id | Gate::X | Gate::Y | Gate::Z | Gate::H => self.clone(),
Gate::S => Gate::Sdg,
Gate::Sdg => Gate::S,
Gate::T => Gate::Tdg,
Gate::Tdg => Gate::T,
Gate::SX => Gate::SXdg,
Gate::SXdg => Gate::SX,
Gate::Rx(theta) => Gate::Rx(-theta),
Gate::Ry(theta) => Gate::Ry(-theta),
Gate::Rz(theta) => Gate::Rz(-theta),
Gate::P(theta) => Gate::P(-theta),
Gate::Rzz(theta) => Gate::Rzz(-theta),
Gate::Cx | Gate::Cz | Gate::Swap => self.clone(),
Gate::Cu(mat) => Gate::cu(adjoint_2x2(mat)),
Gate::Mcu(data) => Gate::mcu(adjoint_2x2(&data.mat), data.num_controls),
Gate::Fused(mat) => Gate::Fused(Box::new(adjoint_2x2(mat))),
Gate::BatchPhase(data) => Gate::BatchPhase(Box::new(BatchPhaseData {
phases: data.phases.iter().map(|&(q, p)| (q, p.conj())).collect(),
})),
Gate::BatchRzz(data) => Gate::BatchRzz(Box::new(BatchRzzData {
edges: data
.edges
.iter()
.map(|&(q0, q1, theta)| (q0, q1, -theta))
.collect(),
})),
Gate::DiagonalBatch(data) => Gate::DiagonalBatch(Box::new(DiagonalBatchData {
entries: data
.entries
.iter()
.map(|e| match e {
DiagEntry::Phase1q { qubit, d0, d1 } => DiagEntry::Phase1q {
qubit: *qubit,
d0: d0.conj(),
d1: d1.conj(),
},
DiagEntry::Phase2q { q0, q1, phase } => DiagEntry::Phase2q {
q0: *q0,
q1: *q1,
phase: phase.conj(),
},
DiagEntry::Parity2q { q0, q1, same, diff } => DiagEntry::Parity2q {
q0: *q0,
q1: *q1,
same: same.conj(),
diff: diff.conj(),
},
})
.collect(),
})),
Gate::MultiFused(data) => Gate::MultiFused(Box::new(MultiFusedData {
gates: data
.gates
.iter()
.map(|&(target, mat)| (target, adjoint_2x2(&mat)))
.collect(),
all_diagonal: data.all_diagonal,
})),
Gate::Fused2q(mat) => Gate::Fused2q(Box::new(adjoint_4x4(mat))),
Gate::Multi2q(data) => Gate::Multi2q(Box::new(Multi2qData {
gates: data
.gates
.iter()
.rev()
.map(|&(q0, q1, ref mat)| (q0, q1, adjoint_4x4(mat)))
.collect(),
})),
}
}
pub fn matrix_power(&self, k: i64) -> Gate {
debug_assert_eq!(
self.num_qubits(),
1,
"matrix_power only for single-qubit gates"
);
if k == 0 {
return Gate::Id;
}
if k == 1 {
return self.clone();
}
let base = if k < 0 { self.inverse() } else { self.clone() };
let n = k.unsigned_abs() as usize;
if n == 1 {
return base;
}
let base_mat = base.matrix_2x2();
let mut acc = base_mat;
for _ in 1..n {
acc = mat_mul_2x2(&base_mat, &acc);
}
Gate::Fused(Box::new(acc))
}
pub fn cu(mat: [[Complex64; 2]; 2]) -> Gate {
Gate::Cu(Box::new(mat))
}
pub fn mcu(mat: [[Complex64; 2]; 2], num_controls: u8) -> Gate {
Gate::Mcu(Box::new(McuData { mat, num_controls }))
}
pub fn cphase(theta: f64) -> Gate {
let one = Complex64::new(1.0, 0.0);
let zero = Complex64::new(0.0, 0.0);
let phase = Complex64::from_polar(1.0, theta);
Gate::cu([[one, zero], [zero, phase]])
}
#[inline]
pub fn controlled_phase(&self) -> Option<Complex64> {
let mat = match self {
Gate::Cu(mat) => &**mat,
Gate::Mcu(data) => &data.mat,
_ => return None,
};
if (mat[0][0].re - 1.0).abs() < IDENTITY_EPS
&& mat[0][0].im.abs() < IDENTITY_EPS
&& mat[0][1].norm() < IDENTITY_EPS
&& mat[1][0].norm() < IDENTITY_EPS
&& (mat[1][1].norm() - 1.0).abs() < IDENTITY_EPS
{
Some(mat[1][1])
} else {
None
}
}
#[inline]
pub fn is_diagonal_1q(&self) -> bool {
match self {
Gate::Id
| Gate::Z
| Gate::S
| Gate::Sdg
| Gate::T
| Gate::Tdg
| Gate::Rz(_)
| Gate::P(_) => true,
Gate::Fused(m) => m[0][1].norm() < IDENTITY_EPS && m[1][0].norm() < IDENTITY_EPS,
_ => false,
}
}
#[inline]
pub fn is_self_inverse_2q(&self) -> bool {
matches!(self, Gate::Cx | Gate::Cz | Gate::Swap)
}
#[inline]
pub fn preserves_sparsity(&self) -> bool {
match self {
Gate::Id | Gate::X | Gate::Y | Gate::Z => true,
Gate::S | Gate::Sdg | Gate::T | Gate::Tdg => true,
Gate::Rz(_) | Gate::P(_) => true,
Gate::Rzz(_) | Gate::Cx | Gate::Cz | Gate::Swap => true,
Gate::Cu(mat) | Gate::Fused(mat) => {
let is_diag = mat[0][1].norm_sqr() < NEAR_ZERO_NORM_SQ
&& mat[1][0].norm_sqr() < NEAR_ZERO_NORM_SQ;
let is_antidiag = mat[0][0].norm_sqr() < NEAR_ZERO_NORM_SQ
&& mat[1][1].norm_sqr() < NEAR_ZERO_NORM_SQ;
is_diag || is_antidiag
}
Gate::Mcu(data) => {
let m = &data.mat;
let is_diag = m[0][1].norm_sqr() < NEAR_ZERO_NORM_SQ
&& m[1][0].norm_sqr() < NEAR_ZERO_NORM_SQ;
let is_antidiag = m[0][0].norm_sqr() < NEAR_ZERO_NORM_SQ
&& m[1][1].norm_sqr() < NEAR_ZERO_NORM_SQ;
is_diag || is_antidiag
}
Gate::BatchPhase(_) | Gate::BatchRzz(_) | Gate::DiagonalBatch(_) => true,
_ => false,
}
}
pub fn recognize_matrix(mat: &[[Complex64; 2]; 2]) -> Option<Gate> {
const EPS: f64 = 1e-10;
let candidates: &[Gate] = &[
Gate::H,
Gate::X,
Gate::Y,
Gate::Z,
Gate::S,
Gate::Sdg,
Gate::T,
Gate::Tdg,
Gate::SX,
Gate::SXdg,
];
for candidate in candidates {
let ref_mat = candidate.matrix_2x2();
if matrices_equal_up_to_phase(mat, &ref_mat, EPS) {
return Some(candidate.clone());
}
}
if mat[0][1].norm_sqr() < EPS
&& mat[1][0].norm_sqr() < EPS
&& (mat[0][0] - mat[1][1]).norm_sqr() < EPS
&& mat[0][0].norm_sqr() > EPS
{
return Some(Gate::Id);
}
None
}
#[inline]
pub fn is_clifford(&self) -> bool {
matches!(
self,
Gate::Id
| Gate::X
| Gate::Y
| Gate::Z
| Gate::H
| Gate::S
| Gate::Sdg
| Gate::SX
| Gate::SXdg
| Gate::Cx
| Gate::Cz
| Gate::Swap
)
}
}
fn matrices_equal_up_to_phase(a: &[[Complex64; 2]; 2], b: &[[Complex64; 2]; 2], eps: f64) -> bool {
let mut phase = None;
for i in 0..2 {
for j in 0..2 {
if b[i][j].norm_sqr() > eps {
if a[i][j].norm_sqr() < eps {
return false;
}
phase = Some(a[i][j] / b[i][j]);
break;
}
}
if phase.is_some() {
break;
}
}
let phase = match phase {
Some(p) => p,
None => return true, };
for i in 0..2 {
for j in 0..2 {
let expected = phase * b[i][j];
if (a[i][j] - expected).norm_sqr() > eps {
return false;
}
}
}
true
}
fn format_angle(theta: f64) -> String {
const FRACTIONS: &[(f64, &str)] = &[
(1.0, "π"),
(-1.0, "-π"),
(0.5, "π/2"),
(-0.5, "-π/2"),
(0.25, "π/4"),
(-0.25, "-π/4"),
(1.0 / 3.0, "π/3"),
(-1.0 / 3.0, "-π/3"),
(2.0 / 3.0, "2π/3"),
(-2.0 / 3.0, "-2π/3"),
(1.0 / 6.0, "π/6"),
(-1.0 / 6.0, "-π/6"),
(5.0 / 6.0, "5π/6"),
(-5.0 / 6.0, "-5π/6"),
(1.0 / 8.0, "π/8"),
(-1.0 / 8.0, "-π/8"),
(3.0 / 8.0, "3π/8"),
(-3.0 / 8.0, "-3π/8"),
(1.5, "3π/2"),
(-1.5, "-3π/2"),
(2.0, "2π"),
(-2.0, "-2π"),
];
let ratio = theta / std::f64::consts::PI;
for &(frac, label) in FRACTIONS {
if (ratio - frac).abs() < 1e-10 {
return label.to_string();
}
}
format!("{:.4}", theta)
}
impl fmt::Display for Gate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Gate::Id => f.write_str("I"),
Gate::X => f.write_str("X"),
Gate::Y => f.write_str("Y"),
Gate::Z => f.write_str("Z"),
Gate::H => f.write_str("H"),
Gate::S => f.write_str("S"),
Gate::Sdg => f.write_str("Sdg"),
Gate::T => f.write_str("T"),
Gate::Tdg => f.write_str("Tdg"),
Gate::SX => f.write_str("SX"),
Gate::SXdg => f.write_str("SXdg"),
Gate::Rx(t) => write!(f, "Rx({})", format_angle(*t)),
Gate::Ry(t) => write!(f, "Ry({})", format_angle(*t)),
Gate::Rz(t) => write!(f, "Rz({})", format_angle(*t)),
Gate::P(t) => write!(f, "P({})", format_angle(*t)),
Gate::Rzz(t) => write!(f, "Rzz({})", format_angle(*t)),
Gate::Cx => f.write_str("CX"),
Gate::Cz => f.write_str("CZ"),
Gate::Swap => f.write_str("SWAP"),
Gate::Cu(_) => f.write_str("CU"),
Gate::Mcu(data) => write!(f, "MCU({}ctrl)", data.num_controls),
Gate::Fused(_) => f.write_str("U"),
Gate::Fused2q(_) => f.write_str("U2"),
Gate::MultiFused(data) => write!(f, "MF[{}]", data.gates.len()),
Gate::BatchPhase(data) => write!(f, "BP[{}]", data.phases.len()),
Gate::BatchRzz(data) => write!(f, "BZZ[{}]", data.edges.len()),
Gate::DiagonalBatch(data) => write!(f, "BD[{}]", data.entries.len()),
Gate::Multi2q(data) => write!(f, "M2[{}]", data.gates.len()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_angle_pi_fractions() {
assert_eq!(format_angle(std::f64::consts::PI), "π");
assert_eq!(format_angle(std::f64::consts::FRAC_PI_2), "π/2");
assert_eq!(format_angle(std::f64::consts::FRAC_PI_4), "π/4");
assert_eq!(format_angle(-std::f64::consts::FRAC_PI_4), "-π/4");
assert_eq!(format_angle(std::f64::consts::PI / 3.0), "π/3");
assert_eq!(format_angle(0.123), "0.1230");
}
#[test]
fn display_labels() {
assert_eq!(Gate::H.to_string(), "H");
assert_eq!(Gate::Cx.to_string(), "CX");
assert_eq!(Gate::Rx(std::f64::consts::FRAC_PI_2).to_string(), "Rx(π/2)");
assert_eq!(Gate::Rz(0.5).to_string(), "Rz(0.5000)");
assert_eq!(Gate::Id.to_string(), "I");
assert_eq!(Gate::Swap.to_string(), "SWAP");
}
#[test]
fn test_gate_arity() {
assert_eq!(Gate::H.num_qubits(), 1);
assert_eq!(Gate::Rx(0.5).num_qubits(), 1);
assert_eq!(Gate::Cx.num_qubits(), 2);
assert_eq!(Gate::Swap.num_qubits(), 2);
}
#[test]
fn test_h_matrix_is_unitary() {
let m = Gate::H.matrix_2x2();
let mut product = [[Complex64::new(0.0, 0.0); 2]; 2];
for i in 0..2 {
for j in 0..2 {
for (k, row) in m.iter().enumerate() {
product[i][j] += m[i][k] * row[j];
}
}
}
let eps = 1e-12;
assert!((product[0][0].re - 1.0).abs() < eps);
assert!(product[0][0].im.abs() < eps);
assert!(product[0][1].norm() < eps);
assert!(product[1][0].norm() < eps);
assert!((product[1][1].re - 1.0).abs() < eps);
}
#[test]
fn test_rx_pi_equals_neg_i_x() {
let rx = Gate::Rx(std::f64::consts::PI).matrix_2x2();
assert!((rx[0][1].norm() - 1.0).abs() < 1e-12);
assert!((rx[1][0].norm() - 1.0).abs() < 1e-12);
assert!(rx[0][0].norm() < 1e-12);
assert!(rx[1][1].norm() < 1e-12);
}
#[test]
fn test_clifford_classification() {
assert!(Gate::H.is_clifford());
assert!(Gate::S.is_clifford());
assert!(Gate::Cx.is_clifford());
assert!(!Gate::T.is_clifford());
assert!(!Gate::Rx(0.5).is_clifford());
assert!(!Gate::Cu(Box::new([[Complex64::new(1.0, 0.0); 2]; 2])).is_clifford());
}
#[test]
fn test_preserves_sparsity() {
assert!(Gate::Id.preserves_sparsity());
assert!(Gate::X.preserves_sparsity());
assert!(Gate::Y.preserves_sparsity());
assert!(Gate::Z.preserves_sparsity());
assert!(Gate::S.preserves_sparsity());
assert!(Gate::T.preserves_sparsity());
assert!(Gate::Rz(1.0).preserves_sparsity());
assert!(Gate::P(0.5).preserves_sparsity());
assert!(Gate::Cx.preserves_sparsity());
assert!(Gate::Cz.preserves_sparsity());
assert!(Gate::Swap.preserves_sparsity());
assert!(!Gate::H.preserves_sparsity());
assert!(!Gate::Rx(0.5).preserves_sparsity());
assert!(!Gate::Ry(0.5).preserves_sparsity());
assert!(!Gate::SX.preserves_sparsity());
assert!(!Gate::SXdg.preserves_sparsity());
let diag = Box::new([
[Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
[Complex64::new(0.0, 0.0), Complex64::new(0.0, 1.0)],
]);
assert!(Gate::Cu(diag).preserves_sparsity());
let h_mat = Box::new(Gate::H.matrix_2x2());
assert!(!Gate::Cu(h_mat).preserves_sparsity());
}
#[test]
fn test_cu_arity() {
let mat = Gate::H.matrix_2x2();
assert_eq!(Gate::Cu(Box::new(mat)).num_qubits(), 2);
}
fn assert_mat_close(a: &[[Complex64; 2]; 2], b: &[[Complex64; 2]; 2], eps: f64) {
for i in 0..2 {
for j in 0..2 {
assert!(
(a[i][j] - b[i][j]).norm() < eps,
"mat[{i}][{j}]: expected {:?}, got {:?}",
b[i][j],
a[i][j]
);
}
}
}
#[test]
fn test_inverse_self_inverse() {
assert_eq!(Gate::H.inverse(), Gate::H);
assert_eq!(Gate::X.inverse(), Gate::X);
assert_eq!(Gate::Y.inverse(), Gate::Y);
assert_eq!(Gate::Z.inverse(), Gate::Z);
assert_eq!(Gate::Id.inverse(), Gate::Id);
assert_eq!(Gate::Cx.inverse(), Gate::Cx);
assert_eq!(Gate::Cz.inverse(), Gate::Cz);
assert_eq!(Gate::Swap.inverse(), Gate::Swap);
}
#[test]
fn test_inverse_adjoint_pairs() {
assert_eq!(Gate::S.inverse(), Gate::Sdg);
assert_eq!(Gate::Sdg.inverse(), Gate::S);
assert_eq!(Gate::T.inverse(), Gate::Tdg);
assert_eq!(Gate::Tdg.inverse(), Gate::T);
}
#[test]
fn test_inverse_parametric() {
assert_eq!(Gate::Rx(0.5).inverse(), Gate::Rx(-0.5));
assert_eq!(Gate::Ry(1.0).inverse(), Gate::Ry(-1.0));
assert_eq!(Gate::Rz(PI).inverse(), Gate::Rz(-PI));
}
#[test]
fn test_inverse_fused_is_adjoint() {
let s_mat = Gate::S.matrix_2x2();
let fused = Gate::Fused(Box::new(s_mat));
let inv = fused.inverse();
if let Gate::Fused(inv_mat) = &inv {
assert_mat_close(inv_mat, &Gate::Sdg.matrix_2x2(), 1e-12);
} else {
panic!("expected Fused");
}
}
#[test]
fn test_inverse_cu() {
let rz_mat = Gate::Rz(0.5).matrix_2x2();
let cu = Gate::Cu(Box::new(rz_mat));
let inv = cu.inverse();
if let Gate::Cu(inv_mat) = &inv {
let expected = Gate::Rz(-0.5).matrix_2x2();
assert_mat_close(inv_mat, &expected, 1e-12);
} else {
panic!("expected Cu");
}
}
#[test]
fn test_matrix_power_zero() {
assert_eq!(Gate::X.matrix_power(0), Gate::Id);
assert_eq!(Gate::Rz(0.5).matrix_power(0), Gate::Id);
}
#[test]
fn test_matrix_power_one() {
assert_eq!(Gate::X.matrix_power(1), Gate::X);
assert_eq!(Gate::H.matrix_power(1), Gate::H);
}
#[test]
fn test_matrix_power_x_squared() {
let x2 = Gate::X.matrix_power(2);
if let Gate::Fused(mat) = &x2 {
assert_mat_close(mat, &Gate::Id.matrix_2x2(), 1e-12);
} else {
panic!("expected Fused");
}
}
#[test]
fn test_matrix_power_t_squared_is_s() {
let t2 = Gate::T.matrix_power(2);
if let Gate::Fused(mat) = &t2 {
assert_mat_close(mat, &Gate::S.matrix_2x2(), 1e-12);
} else {
panic!("expected Fused");
}
}
#[test]
fn test_matrix_power_negative() {
let t_inv2 = Gate::T.matrix_power(-2);
if let Gate::Fused(mat) = &t_inv2 {
assert_mat_close(mat, &Gate::Sdg.matrix_2x2(), 1e-12);
} else {
panic!("expected Fused");
}
}
#[test]
fn test_mcu_arity() {
let mat = Gate::H.matrix_2x2();
let mcu2 = Gate::Mcu(Box::new(McuData {
mat,
num_controls: 2,
}));
assert_eq!(mcu2.num_qubits(), 3);
let mcu3 = Gate::Mcu(Box::new(McuData {
mat,
num_controls: 3,
}));
assert_eq!(mcu3.num_qubits(), 4);
}
#[test]
fn test_mcu_not_clifford() {
let mat = Gate::X.matrix_2x2();
let mcu = Gate::Mcu(Box::new(McuData {
mat,
num_controls: 2,
}));
assert!(!mcu.is_clifford());
}
#[test]
fn test_mcu_inverse() {
let rz_mat = Gate::Rz(0.5).matrix_2x2();
let mcu = Gate::Mcu(Box::new(McuData {
mat: rz_mat,
num_controls: 2,
}));
let inv = mcu.inverse();
if let Gate::Mcu(inv_data) = &inv {
let expected = Gate::Rz(-0.5).matrix_2x2();
assert_mat_close(&inv_data.mat, &expected, 1e-12);
assert_eq!(inv_data.num_controls, 2);
} else {
panic!("expected Mcu");
}
}
#[test]
fn test_mcu_name() {
let mat = Gate::H.matrix_2x2();
let mcu = Gate::Mcu(Box::new(McuData {
mat,
num_controls: 2,
}));
assert_eq!(mcu.name(), "mcu");
}
#[test]
fn test_cphase_constructor() {
let g = Gate::cphase(PI / 4.0);
assert_eq!(g.num_qubits(), 2);
assert_eq!(g.name(), "cu");
if let Gate::Cu(mat) = &g {
let one = Complex64::new(1.0, 0.0);
assert!((mat[0][0] - one).norm() < 1e-14);
assert!(mat[0][1].norm() < 1e-14);
assert!(mat[1][0].norm() < 1e-14);
let expected = Complex64::from_polar(1.0, PI / 4.0);
assert!((mat[1][1] - expected).norm() < 1e-14);
} else {
panic!("expected Cu");
}
}
#[test]
fn test_controlled_phase_detection() {
let cp = Gate::cphase(0.5);
assert!(cp.controlled_phase().is_some());
let phase = cp.controlled_phase().unwrap();
let expected = Complex64::from_polar(1.0, 0.5);
assert!((phase - expected).norm() < 1e-14);
let h_mat = Gate::H.matrix_2x2();
let cu_h = Gate::Cu(Box::new(h_mat));
assert!(cu_h.controlled_phase().is_none());
let z_mat = Gate::Z.matrix_2x2();
let cu_z = Gate::Cu(Box::new(z_mat));
assert!(cu_z.controlled_phase().is_some());
let z_phase = cu_z.controlled_phase().unwrap();
assert!((z_phase.re - (-1.0)).abs() < 1e-14);
let rz_mat = Gate::Rz(0.5).matrix_2x2();
let cu_rz = Gate::Cu(Box::new(rz_mat));
assert!(cu_rz.controlled_phase().is_none());
assert!(Gate::H.controlled_phase().is_none());
assert!(Gate::Cx.controlled_phase().is_none());
}
#[test]
fn test_controlled_phase_mcu() {
let one = Complex64::new(1.0, 0.0);
let zero = Complex64::new(0.0, 0.0);
let phase = Complex64::from_polar(1.0, 0.7);
let mcu = Gate::Mcu(Box::new(McuData {
mat: [[one, zero], [zero, phase]],
num_controls: 2,
}));
assert!(mcu.controlled_phase().is_some());
assert!((mcu.controlled_phase().unwrap() - phase).norm() < 1e-14);
}
#[test]
fn test_sx_matrix_is_sqrt_x() {
let sx = Gate::SX.matrix_2x2();
let sx2 = mat_mul_2x2(&sx, &sx);
assert_mat_close(&sx2, &Gate::X.matrix_2x2(), 1e-12);
}
#[test]
fn test_sxdg_is_sx_inverse() {
let sx = Gate::SX.matrix_2x2();
let sxdg = Gate::SXdg.matrix_2x2();
let product = mat_mul_2x2(&sx, &sxdg);
assert_mat_close(&product, &Gate::Id.matrix_2x2(), 1e-12);
}
#[test]
fn test_p_gate_matrix() {
let p = Gate::P(PI / 4.0).matrix_2x2();
let t = Gate::T.matrix_2x2();
assert_mat_close(&p, &t, 1e-12);
}
#[test]
fn test_sx_is_clifford() {
assert!(Gate::SX.is_clifford());
assert!(Gate::SXdg.is_clifford());
}
#[test]
fn test_p_inverse() {
assert_eq!(Gate::P(0.5).inverse(), Gate::P(-0.5));
}
#[test]
fn test_sx_inverse_pair() {
assert_eq!(Gate::SX.inverse(), Gate::SXdg);
assert_eq!(Gate::SXdg.inverse(), Gate::SX);
}
#[test]
fn test_is_diagonal_1q() {
assert!(Gate::Id.is_diagonal_1q());
assert!(Gate::Z.is_diagonal_1q());
assert!(Gate::S.is_diagonal_1q());
assert!(Gate::Sdg.is_diagonal_1q());
assert!(Gate::T.is_diagonal_1q());
assert!(Gate::Tdg.is_diagonal_1q());
assert!(Gate::Rz(0.5).is_diagonal_1q());
assert!(Gate::P(0.5).is_diagonal_1q());
assert!(!Gate::H.is_diagonal_1q());
assert!(!Gate::X.is_diagonal_1q());
assert!(!Gate::Y.is_diagonal_1q());
assert!(!Gate::Rx(0.5).is_diagonal_1q());
assert!(!Gate::Ry(0.5).is_diagonal_1q());
assert!(!Gate::SX.is_diagonal_1q());
assert!(!Gate::Cx.is_diagonal_1q());
let diag_fused = Gate::Fused(Box::new(Gate::T.matrix_2x2()));
assert!(diag_fused.is_diagonal_1q());
let nondiag_fused = Gate::Fused(Box::new(Gate::H.matrix_2x2()));
assert!(!nondiag_fused.is_diagonal_1q());
}
#[test]
fn test_is_self_inverse_2q() {
assert!(Gate::Cx.is_self_inverse_2q());
assert!(Gate::Cz.is_self_inverse_2q());
assert!(Gate::Swap.is_self_inverse_2q());
assert!(!Gate::H.is_self_inverse_2q());
assert!(!Gate::T.is_self_inverse_2q());
let mat = Gate::H.matrix_2x2();
assert!(!Gate::Cu(Box::new(mat)).is_self_inverse_2q());
}
#[test]
fn test_gate_enum_size() {
assert_eq!(
std::mem::size_of::<Gate>(),
16,
"Gate enum must stay at 16 bytes"
);
}
#[test]
fn test_recognize_named_gates() {
for gate in &[
Gate::H,
Gate::X,
Gate::Y,
Gate::Z,
Gate::S,
Gate::Sdg,
Gate::T,
Gate::Tdg,
Gate::SX,
Gate::SXdg,
] {
let mat = gate.matrix_2x2();
let recognized = Gate::recognize_matrix(&mat);
assert_eq!(
recognized.as_ref(),
Some(gate),
"failed to recognize {:?}",
gate.name()
);
}
}
#[test]
fn test_recognize_identity() {
let id = Gate::Id.matrix_2x2();
assert_eq!(Gate::recognize_matrix(&id), Some(Gate::Id));
}
#[test]
fn test_recognize_t_squared_is_s() {
let t = Gate::T.matrix_2x2();
let tt = mat_mul_2x2(&t, &t);
assert_eq!(Gate::recognize_matrix(&tt), Some(Gate::S));
}
#[test]
fn test_recognize_s_squared_is_z() {
let s = Gate::S.matrix_2x2();
let ss = mat_mul_2x2(&s, &s);
assert_eq!(Gate::recognize_matrix(&ss), Some(Gate::Z));
}
#[test]
fn test_recognize_h_squared_is_identity() {
let h = Gate::H.matrix_2x2();
let hh = mat_mul_2x2(&h, &h);
assert_eq!(Gate::recognize_matrix(&hh), Some(Gate::Id));
}
#[test]
fn test_recognize_t_fourth_is_z() {
let t = Gate::T.matrix_2x2();
let t2 = mat_mul_2x2(&t, &t);
let t4 = mat_mul_2x2(&t2, &t2);
assert_eq!(Gate::recognize_matrix(&t4), Some(Gate::Z));
}
#[test]
fn test_recognize_non_clifford_returns_none() {
let rx = Gate::Rx(0.7).matrix_2x2();
assert_eq!(Gate::recognize_matrix(&rx), None);
let ry = Gate::Ry(1.3).matrix_2x2();
assert_eq!(Gate::recognize_matrix(&ry), None);
}
#[test]
fn test_recognize_global_phase_invariance() {
let phase = Complex64::from_polar(1.0, 0.42);
let h = Gate::H.matrix_2x2();
let phased = [
[h[0][0] * phase, h[0][1] * phase],
[h[1][0] * phase, h[1][1] * phase],
];
assert_eq!(Gate::recognize_matrix(&phased), Some(Gate::H));
}
}