use quantrs2_core::error::{QuantRS2Error, QuantRS2Result};
use scirs2_core::Complex64;
use std::collections::HashMap;
use std::f64::consts::{PI, SQRT_2};
#[derive(Debug, Clone, Copy)]
pub struct SU2 {
pub m00: Complex64,
pub m01: Complex64,
pub m10: Complex64,
pub m11: Complex64,
}
impl SU2 {
pub fn new(m00: Complex64, m01: Complex64, m10: Complex64, m11: Complex64) -> Self {
Self { m00, m01, m10, m11 }
}
pub fn identity() -> Self {
Self {
m00: Complex64::new(1.0, 0.0),
m01: Complex64::new(0.0, 0.0),
m10: Complex64::new(0.0, 0.0),
m11: Complex64::new(1.0, 0.0),
}
}
pub fn from_gate_name(name: &str) -> Option<Self> {
let inv_sqrt2 = 1.0 / SQRT_2;
let t_phase = Complex64::new(inv_sqrt2, inv_sqrt2); let tdg_phase = Complex64::new(inv_sqrt2, -inv_sqrt2);
match name {
"H" => Some(Self {
m00: Complex64::new(inv_sqrt2, 0.0),
m01: Complex64::new(inv_sqrt2, 0.0),
m10: Complex64::new(inv_sqrt2, 0.0),
m11: Complex64::new(-inv_sqrt2, 0.0),
}),
"T" => Some(Self {
m00: Complex64::new(1.0, 0.0),
m01: Complex64::new(0.0, 0.0),
m10: Complex64::new(0.0, 0.0),
m11: t_phase,
}),
"Tdg" => Some(Self {
m00: Complex64::new(1.0, 0.0),
m01: Complex64::new(0.0, 0.0),
m10: Complex64::new(0.0, 0.0),
m11: tdg_phase,
}),
"S" => Some(Self {
m00: Complex64::new(1.0, 0.0),
m01: Complex64::new(0.0, 0.0),
m10: Complex64::new(0.0, 0.0),
m11: Complex64::new(0.0, 1.0),
}),
"Sdg" => Some(Self {
m00: Complex64::new(1.0, 0.0),
m01: Complex64::new(0.0, 0.0),
m10: Complex64::new(0.0, 0.0),
m11: Complex64::new(0.0, -1.0),
}),
"X" => Some(Self {
m00: Complex64::new(0.0, 0.0),
m01: Complex64::new(1.0, 0.0),
m10: Complex64::new(1.0, 0.0),
m11: Complex64::new(0.0, 0.0),
}),
"Y" => Some(Self {
m00: Complex64::new(0.0, 0.0),
m01: Complex64::new(0.0, -1.0),
m10: Complex64::new(0.0, 1.0),
m11: Complex64::new(0.0, 0.0),
}),
"Z" => Some(Self {
m00: Complex64::new(1.0, 0.0),
m01: Complex64::new(0.0, 0.0),
m10: Complex64::new(0.0, 0.0),
m11: Complex64::new(-1.0, 0.0),
}),
_ => None,
}
}
pub fn mul(&self, other: &SU2) -> SU2 {
SU2 {
m00: self.m00 * other.m00 + self.m01 * other.m10,
m01: self.m00 * other.m01 + self.m01 * other.m11,
m10: self.m10 * other.m00 + self.m11 * other.m10,
m11: self.m10 * other.m01 + self.m11 * other.m11,
}
}
pub fn adjoint(&self) -> SU2 {
SU2 {
m00: self.m00.conj(),
m01: self.m10.conj(),
m10: self.m01.conj(),
m11: self.m11.conj(),
}
}
pub fn trace(&self) -> Complex64 {
self.m00 + self.m11
}
pub fn distance_from_identity(&self) -> f64 {
let tr_abs = self.trace().norm();
let val = (2.0 - tr_abs).max(0.0);
val.sqrt()
}
pub fn frobenius_distance(&self, other: &SU2) -> f64 {
let udagger_v = self.adjoint().mul(other);
udagger_v.distance_from_identity()
}
pub fn distance_from_identity_exact(&self) -> f64 {
let re_tr = self.trace().re;
let val = (2.0 - re_tr).max(0.0);
val.sqrt()
}
pub fn frobenius_distance_exact(&self, other: &SU2) -> f64 {
let udagger_v = self.adjoint().mul(other);
udagger_v.distance_from_identity_exact()
}
pub fn rotation_angle(&self) -> f64 {
let tr_abs = self.trace().norm();
let cos_half = (tr_abs / 2.0).clamp(0.0, 1.0);
2.0 * cos_half.acos()
}
pub fn rotation_axis(&self) -> [f64; 3] {
let theta = self.rotation_angle();
let s = (theta / 2.0).sin();
if s.abs() < 1e-12 {
return [0.0, 0.0, 1.0];
}
let det = self.m00 * self.m11 - self.m01 * self.m10;
let global_phase_half = det.arg() / 2.0;
let phase_inv = Complex64::new((-global_phase_half).cos(), (-global_phase_half).sin());
let a00 = self.m00 * phase_inv;
let a01 = self.m01 * phase_inv;
let a10 = self.m10 * phase_inv;
let a11 = self.m11 * phase_inv;
let nz = a00.im / s;
let nx = a10.im / s;
let ny = a10.re / s;
let _ = (a01, a11);
let norm = (nx * nx + ny * ny + nz * nz).sqrt();
if norm < 1e-12 {
[0.0, 0.0, 1.0]
} else {
[nx / norm, ny / norm, nz / norm]
}
}
pub fn rotation_x(angle: f64) -> SU2 {
let c = (angle / 2.0).cos();
let s = (angle / 2.0).sin();
SU2 {
m00: Complex64::new(c, 0.0),
m01: Complex64::new(0.0, s),
m10: Complex64::new(0.0, s),
m11: Complex64::new(c, 0.0),
}
}
pub fn rotation_y(angle: f64) -> SU2 {
let c = (angle / 2.0).cos();
let s = (angle / 2.0).sin();
SU2 {
m00: Complex64::new(c, 0.0),
m01: Complex64::new(s, 0.0),
m10: Complex64::new(-s, 0.0),
m11: Complex64::new(c, 0.0),
}
}
pub fn rotation_z(angle: f64) -> SU2 {
let half = angle / 2.0;
SU2 {
m00: Complex64::new(half.cos(), half.sin()),
m01: Complex64::new(0.0, 0.0),
m10: Complex64::new(0.0, 0.0),
m11: Complex64::new(half.cos(), -half.sin()),
}
}
}
pub fn rotation_about_axis(nx: f64, ny: f64, nz: f64, theta: f64) -> SU2 {
let c = (theta / 2.0).cos();
let s = (theta / 2.0).sin();
SU2 {
m00: Complex64::new(c, nz * s),
m01: Complex64::new(ny * s, nx * s),
m10: Complex64::new(-ny * s, nx * s),
m11: Complex64::new(c, -nz * s),
}
}
pub fn balanced_commutator_decompose(u: &SU2) -> QuantRS2Result<(SU2, SU2)> {
let theta = u.rotation_angle();
let sin_half_theta = (theta / 2.0).sin().abs();
let sin_sq_phi_arg = sin_half_theta.sqrt();
let phi = if sin_sq_phi_arg >= 1.0 {
PI / 4.0
} else {
sin_sq_phi_arg.asin() / 2.0
};
let n_hat = u.rotation_axis();
let (nx, ny, nz) = (n_hat[0], n_hat[1], n_hat[2]);
let (e1x, e1y, e1z) = if (nz.abs() - 1.0).abs() > 1e-6 {
let cx = ny;
let cy = -nx;
let cz = 0.0_f64;
let norm = (cx * cx + cy * cy + cz * cz).sqrt();
if norm < 1e-12 {
(1.0_f64, 0.0_f64, 0.0_f64)
} else {
(cx / norm, cy / norm, cz / norm)
}
} else {
(1.0_f64, 0.0_f64, 0.0_f64)
};
let e2x = ny * e1z - nz * e1y;
let e2y = nz * e1x - nx * e1z;
let e2z = nx * e1y - ny * e1x;
let v = rotation_about_axis(e2x, e2y, e2z, 2.0 * phi);
let w = rotation_about_axis(e1x, e1y, e1z, 2.0 * phi);
Ok((v, w))
}
pub struct BasicApproximationTable {
pub entries: Vec<(Vec<&'static str>, SU2)>,
}
fn su2_to_canonical_quat(m: &SU2) -> (f64, f64, f64, f64) {
let det = m.m00 * m.m11 - m.m01 * m.m10;
let phase_angle = det.arg() / 2.0; let phase_inv = Complex64::new((-phase_angle).cos(), (-phase_angle).sin()); let r00 = m.m00 * phase_inv;
let r10 = m.m10 * phase_inv;
let (q0, q1, q2, q3) = (r00.re, r00.im, r10.re, r10.im);
let max_abs = q0.abs().max(q1.abs()).max(q2.abs()).max(q3.abs());
let sign = if (q0.abs() - max_abs).abs() < 1e-10 {
if q0 >= 0.0 {
1.0_f64
} else {
-1.0_f64
}
} else if (q1.abs() - max_abs).abs() < 1e-10 {
if q1 >= 0.0 {
1.0_f64
} else {
-1.0_f64
}
} else if (q2.abs() - max_abs).abs() < 1e-10 {
if q2 >= 0.0 {
1.0_f64
} else {
-1.0_f64
}
} else {
if q3 >= 0.0 {
1.0_f64
} else {
-1.0_f64
}
};
(sign * q0, sign * q1, sign * q2, sign * q3)
}
fn quat_to_cell(q: (f64, f64, f64, f64), cell_size: f64) -> (i64, i64, i64, i64) {
let cell = |x: f64| (x / cell_size).floor() as i64;
(cell(q.0), cell(q.1), cell(q.2), cell(q.3))
}
const DEDUP_TOL: f64 = 1e-6;
const CELL_SIZE: f64 = 0.002;
struct DedupTable {
entries: Vec<(Vec<&'static str>, SU2)>,
grid: HashMap<(i64, i64, i64, i64), Vec<usize>>,
}
impl DedupTable {
fn new() -> Self {
Self {
entries: Vec::new(),
grid: HashMap::new(),
}
}
fn contains(&self, mat: &SU2) -> bool {
let q = su2_to_canonical_quat(mat);
let (cx, cy, cz, cw) = quat_to_cell(q, CELL_SIZE);
for dx in -1_i64..=1 {
for dy in -1_i64..=1 {
for dz in -1_i64..=1 {
for dw in -1_i64..=1 {
let cell = (cx + dx, cy + dy, cz + dz, cw + dw);
if let Some(indices) = self.grid.get(&cell) {
for &idx in indices {
let (_, ref existing_mat) = self.entries[idx];
if existing_mat.frobenius_distance(mat) < DEDUP_TOL {
return true;
}
}
}
}
}
}
}
false
}
fn insert(&mut self, seq: Vec<&'static str>, mat: SU2) {
let idx = self.entries.len();
let q = su2_to_canonical_quat(&mat);
let cell = quat_to_cell(q, CELL_SIZE);
self.grid.entry(cell).or_default().push(idx);
self.entries.push((seq, mat));
}
}
fn enumerate_cliffords() -> Vec<(Vec<&'static str>, SU2)> {
let h_gate = SU2::from_gate_name("H").unwrap_or_else(SU2::identity);
let s_gate = SU2::from_gate_name("S").unwrap_or_else(SU2::identity);
let sdg_gate = SU2::from_gate_name("Sdg").unwrap_or_else(SU2::identity);
let generators: &[(&'static str, &SU2)] = &[("H", &h_gate), ("S", &s_gate), ("Sdg", &sdg_gate)];
let mut table = DedupTable::new();
table.insert(Vec::new(), SU2::identity());
let mut queue: std::collections::VecDeque<usize> = std::collections::VecDeque::new();
queue.push_back(0);
while let Some(idx) = queue.pop_front() {
let (seq, mat) = {
let (s, m) = &table.entries[idx];
(s.clone(), *m)
};
for (gen_name, gen_mat) in generators {
let candidate_mat = mat.mul(gen_mat);
if !table.contains(&candidate_mat) {
let mut candidate_seq = Vec::with_capacity(seq.len() + 1);
candidate_seq.extend_from_slice(&seq);
candidate_seq.push(gen_name);
let new_idx = table.entries.len();
table.insert(candidate_seq, candidate_mat);
queue.push_back(new_idx);
}
}
}
table.entries
}
impl BasicApproximationTable {
pub fn build(table_depth: usize) -> Self {
const MAX_T_COUNT_LIMIT: usize = 10;
let max_t_count = std::cmp::min(table_depth, MAX_T_COUNT_LIMIT);
let t_gate = SU2::from_gate_name("T").unwrap_or_else(SU2::identity);
let tdg_gate = SU2::from_gate_name("Tdg").unwrap_or_else(SU2::identity);
let t_atoms: &[(&'static str, &SU2)] = &[("T", &t_gate), ("Tdg", &tdg_gate)];
let cliffords = enumerate_cliffords();
debug_assert_eq!(
cliffords.len(),
24,
"single-qubit Clifford group should have 24 elements modulo global phase"
);
let mut table = DedupTable::new();
for (seq, mat) in &cliffords {
table.insert(seq.clone(), *mat);
}
let mut prev_stratum: Vec<usize> = (0..cliffords.len()).collect();
for _k in 1..=max_t_count {
let mut next_stratum: Vec<usize> = Vec::new();
for &prev_idx in &prev_stratum {
let (prev_seq, prev_mat) = {
let (s, m) = &table.entries[prev_idx];
(s.clone(), *m)
};
for (t_name, t_mat) in t_atoms {
let after_t = prev_mat.mul(t_mat);
for (c_seq, c_mat) in &cliffords {
let candidate_mat = after_t.mul(c_mat);
if !table.contains(&candidate_mat) {
let mut candidate_seq =
Vec::with_capacity(prev_seq.len() + 1 + c_seq.len());
candidate_seq.extend_from_slice(&prev_seq);
candidate_seq.push(t_name);
candidate_seq.extend_from_slice(c_seq);
let new_idx = table.entries.len();
table.insert(candidate_seq, candidate_mat);
next_stratum.push(new_idx);
}
}
}
}
if next_stratum.is_empty() {
break;
}
prev_stratum = next_stratum;
}
Self {
entries: table.entries,
}
}
pub fn find_closest<'a>(&'a self, target: &SU2) -> (&'a [&'static str], f64) {
let mut best_dist = f64::INFINITY;
let mut best_seq: &[&'static str] = &[];
for (seq, mat) in &self.entries {
let dist = mat.frobenius_distance(target);
if dist < best_dist {
best_dist = dist;
best_seq = seq.as_slice();
}
}
(best_seq, best_dist)
}
pub fn len(&self) -> usize {
self.entries.len()
}
}
fn adjoint_gate(gate: &'static str) -> &'static str {
match gate {
"T" => "Tdg",
"Tdg" => "T",
"S" => "Sdg",
"Sdg" => "S",
"H" => "H",
"X" => "X",
"Y" => "Y",
"Z" => "Z",
other => other,
}
}
pub fn sequence_to_matrix(seq: &[&'static str]) -> SU2 {
seq.iter().fold(SU2::identity(), |acc, g| {
let gate = SU2::from_gate_name(g).unwrap_or(SU2::identity());
acc.mul(&gate)
})
}
pub struct SOKDecomposer {
table: BasicApproximationTable,
pub recursion_depth: usize,
}
impl SOKDecomposer {
pub fn new(table_depth: usize, recursion_depth: usize) -> Self {
Self {
table: BasicApproximationTable::build(table_depth),
recursion_depth,
}
}
pub fn decompose(&self, u: &SU2) -> QuantRS2Result<Vec<&'static str>> {
self.sk_recurse(u, self.recursion_depth)
}
fn sk_recurse(&self, u: &SU2, n: usize) -> QuantRS2Result<Vec<&'static str>> {
if n == 0 {
let (seq, _dist) = self.table.find_closest(u);
return Ok(seq.to_vec());
}
let u_prev_seq = self.sk_recurse(u, n - 1)?;
let u_prev = sequence_to_matrix(&u_prev_seq);
let prev_dist = u_prev.frobenius_distance(u);
let raw_delta = u.mul(&u_prev.adjoint());
let tr_delta = raw_delta.trace();
let phase_angle = -tr_delta.arg() / 2.0; let phase = Complex64::new(phase_angle.cos(), phase_angle.sin());
let u_prev_phased = SU2 {
m00: u_prev.m00 * phase,
m01: u_prev.m01 * phase,
m10: u_prev.m10 * phase,
m11: u_prev.m11 * phase,
};
let delta = u.mul(&u_prev_phased.adjoint());
let (v, w) = balanced_commutator_decompose(&delta)?;
let v_seq = self.sk_recurse(&v, n - 1)?;
let w_seq = self.sk_recurse(&w, n - 1)?;
let v_adj_seq: Vec<&'static str> = v_seq.iter().rev().map(|g| adjoint_gate(g)).collect();
let w_adj_seq: Vec<&'static str> = w_seq.iter().rev().map(|g| adjoint_gate(g)).collect();
let mut result = v_seq;
result.extend_from_slice(&w_seq);
result.extend(v_adj_seq);
result.extend(w_adj_seq);
result.extend_from_slice(&u_prev_seq);
let new_mat = sequence_to_matrix(&result);
let new_dist = new_mat.frobenius_distance(u);
if new_dist >= prev_dist {
return Ok(u_prev_seq);
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
const EPS: f64 = 1e-6;
#[test]
fn test_su2_identity_distance() {
let i = SU2::identity();
assert!(
i.distance_from_identity() < EPS,
"identity distance should be ~0, got {}",
i.distance_from_identity()
);
}
#[test]
fn test_su2_multiplication_hh_is_identity() {
let h = SU2::from_gate_name("H").expect("H gate must exist");
let hh = h.mul(&h);
assert!(
hh.frobenius_distance(&SU2::identity()) < EPS,
"H*H should equal Identity (up to global phase), got {}",
hh.frobenius_distance(&SU2::identity())
);
}
#[test]
fn test_su2_tt_is_s() {
let t = SU2::from_gate_name("T").expect("T gate must exist");
let s = SU2::from_gate_name("S").expect("S gate must exist");
let tt = t.mul(&t);
assert!(
tt.frobenius_distance(&s) < EPS,
"T*T should equal S, got {}",
tt.frobenius_distance(&s)
);
}
#[test]
fn test_adjoint_t_tdg() {
let t = SU2::from_gate_name("T").expect("T gate must exist");
let tdg = SU2::from_gate_name("Tdg").expect("Tdg gate must exist");
assert!(
t.adjoint().frobenius_distance(&tdg) < EPS,
"T† should equal Tdg, dist = {}",
t.adjoint().frobenius_distance(&tdg)
);
}
#[test]
fn test_rotation_z_composition() {
let rz1 = SU2::rotation_z(0.3);
let rz2 = SU2::rotation_z(0.7);
let combined = rz1.mul(&rz2);
let expected = SU2::rotation_z(1.0);
assert!(
combined.frobenius_distance(&expected) < EPS,
"RZ(0.3)*RZ(0.7) should equal RZ(1.0)"
);
}
#[test]
fn test_basic_approximation_table_size() {
let table = BasicApproximationTable::build(5);
assert!(
table.entries.len() > 10,
"Table should have many entries, got {}",
table.entries.len()
);
}
#[test]
fn test_basic_approximation_table_finds_identity() {
let table = BasicApproximationTable::build(5);
let identity = SU2::identity();
let (_seq, dist) = table.find_closest(&identity);
assert!(
dist < 0.1,
"Should find approximation close to identity, dist = {}",
dist
);
}
#[test]
fn test_balanced_commutator_small_rotation() {
let small_u = SU2::rotation_z(0.1);
let (v, w) =
balanced_commutator_decompose(&small_u).expect("commutator decompose should succeed");
let commutator = v.mul(&w).mul(&v.adjoint()).mul(&w.adjoint());
let dist = commutator.frobenius_distance(&small_u);
assert!(
dist < 0.2,
"Balanced commutator should approximate small rotation U, got dist={}",
dist
);
}
#[test]
fn test_balanced_commutator_identity() {
let u = SU2::identity();
let (v, w) =
balanced_commutator_decompose(&u).expect("commutator decompose should succeed");
let commutator = v.mul(&w).mul(&v.adjoint()).mul(&w.adjoint());
let dist = commutator.frobenius_distance(&u);
assert!(
dist < 0.1,
"Commutator decompose of identity should be identity, got dist={}",
dist
);
}
#[test]
fn test_solovay_kitaev_rz_depth0() {
let target = SU2::rotation_z(0.5);
let decomposer = SOKDecomposer::new(10, 0);
let seq = decomposer.decompose(&target).expect("decompose failed");
let approx = sequence_to_matrix(&seq);
let dist = approx.frobenius_distance(&target);
assert!(
dist < 0.5,
"Depth-0 should be within 0.5 of target, got {dist}"
);
}
#[test]
fn test_solovay_kitaev_rz_depth1() {
let target = SU2::rotation_z(0.5);
let decomposer = SOKDecomposer::new(10, 1);
let seq = decomposer.decompose(&target).expect("decompose failed");
let approx = sequence_to_matrix(&seq);
let dist = approx.frobenius_distance(&target);
let depth0_dist = {
let d0 = SOKDecomposer::new(10, 0);
let s0 = d0.decompose(&target).expect("depth0 failed");
sequence_to_matrix(&s0).frobenius_distance(&target)
};
assert!(
dist < depth0_dist,
"SK depth=1 ({dist:.6}) should improve over depth=0 ({depth0_dist:.6})"
);
assert!(
dist < 0.05,
"SK depth=1 should achieve dist < 0.05, got {dist:.6}"
);
assert!(!seq.is_empty(), "Sequence should not be empty");
}
#[test]
fn test_solovay_kitaev_rz_depth2() {
let target = SU2::rotation_z(0.5);
let decomposer2 = SOKDecomposer::new(10, 2);
let seq2 = decomposer2.decompose(&target).expect("decompose failed");
let dist2 = sequence_to_matrix(&seq2).frobenius_distance(&target);
let decomposer1 = SOKDecomposer::new(10, 1);
let seq1 = decomposer1.decompose(&target).expect("depth1 failed");
let dist1 = sequence_to_matrix(&seq1).frobenius_distance(&target);
assert!(
dist2 <= dist1 + 1e-9,
"SK depth=2 ({dist2:.6}) should not be worse than depth=1 ({dist1:.6})"
);
}
#[test]
fn test_solovay_kitaev_convergence_debug() {
let target = SU2::rotation_z(0.5);
let table = BasicApproximationTable::build(10);
eprintln!("Table size: {}", table.entries.len());
let (seq0, dist0) = table.find_closest(&target);
eprintln!("Depth 0: dist={:.6}, seq_len={}", dist0, seq0.len());
let mut dists: Vec<(f64, usize)> = table
.entries
.iter()
.enumerate()
.map(|(i, (_, mat))| (mat.frobenius_distance(&target), i))
.collect();
dists.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
eprintln!("Top-5 closest table entries to RZ(0.5):");
for (dist, idx) in dists.iter().take(5) {
eprintln!(" dist={:.6}, seq={:?}", dist, table.entries[*idx].0);
}
let u_prev = sequence_to_matrix(seq0);
let raw_delta = target.mul(&u_prev.adjoint());
let tr_delta = raw_delta.trace();
let phase_angle = -tr_delta.arg() / 2.0;
let phase = Complex64::new(phase_angle.cos(), phase_angle.sin());
let u_prev_phased = SU2 {
m00: u_prev.m00 * phase,
m01: u_prev.m01 * phase,
m10: u_prev.m10 * phase,
m11: u_prev.m11 * phase,
};
let delta = target.mul(&u_prev_phased.adjoint());
let delta_dist = delta.distance_from_identity();
eprintln!("Phase-aligned delta dist from identity: {:.6}", delta_dist);
eprintln!(
"Phase-aligned delta rotation angle: {:.6}",
delta.rotation_angle()
);
let (v, w) = balanced_commutator_decompose(&delta).expect("commutator failed");
let (v_seq, v_dist) = table.find_closest(&v);
let (w_seq, w_dist) = table.find_closest(&w);
eprintln!("V approx dist: {:.6}, W approx dist: {:.6}", v_dist, w_dist);
let exact_comm = v.mul(&w).mul(&v.adjoint()).mul(&w.adjoint());
let exact_comm_dist = exact_comm.frobenius_distance(&delta);
eprintln!("Exact commutator dist from delta: {:.6}", exact_comm_dist);
let v_mat = sequence_to_matrix(v_seq);
let w_mat = sequence_to_matrix(w_seq);
let approx_comm = v_mat
.mul(&w_mat)
.mul(&v_mat.adjoint())
.mul(&w_mat.adjoint());
let approx_comm_to_delta = approx_comm.frobenius_distance(&delta);
let approx_comm_to_i = approx_comm.distance_from_identity();
eprintln!(
"Approx commutator dist from delta: {:.6}",
approx_comm_to_delta
);
eprintln!("Approx commutator dist from I: {:.6}", approx_comm_to_i);
let v_adj_seq: Vec<&'static str> = v_seq.iter().rev().map(|g| adjoint_gate(g)).collect();
let w_adj_seq: Vec<&'static str> = w_seq.iter().rev().map(|g| adjoint_gate(g)).collect();
let mut full_seq = v_seq.to_vec();
full_seq.extend_from_slice(w_seq);
full_seq.extend(v_adj_seq);
full_seq.extend(w_adj_seq);
full_seq.extend_from_slice(seq0);
let full_mat = sequence_to_matrix(&full_seq);
let full_dist = full_mat.frobenius_distance(&target);
eprintln!("Full depth=1 result dist from target: {:.6}", full_dist);
let decomposer = SOKDecomposer::new(10, 1);
let sk_seq = decomposer.decompose(&target).expect("SK decompose failed");
let sk_mat = sequence_to_matrix(&sk_seq);
let sk_dist = sk_mat.frobenius_distance(&target);
eprintln!("SK depth=1 dist: {:.6}, seq_len={}", sk_dist, sk_seq.len());
assert!(dist0 < 0.5, "depth-0 should find something reasonable");
}
#[test]
fn test_solovay_kitaev_rz_depth3() {
let target = SU2::rotation_z(0.5);
let d1 = SOKDecomposer::new(10, 1)
.decompose(&target)
.expect("depth1 failed");
let dist1 = sequence_to_matrix(&d1).frobenius_distance(&target);
let d3 = SOKDecomposer::new(10, 3)
.decompose(&target)
.expect("depth3 failed");
let dist3 = sequence_to_matrix(&d3).frobenius_distance(&target);
assert!(
dist3 <= dist1 + 1e-9,
"SK depth=3 ({dist3:.6}) should not be worse than depth=1 ({dist1:.6})"
);
assert!(!d3.is_empty(), "Sequence should not be empty");
}
#[test]
fn test_solovay_kitaev_sequence_gates_valid() {
let target = SU2::rotation_z(0.5);
let decomposer = SOKDecomposer::new(10, 2);
let seq = decomposer.decompose(&target).expect("decompose failed");
for gate in &seq {
assert!(
["H", "T", "Tdg", "S", "Sdg"].contains(gate),
"Gate '{}' not in universal set {{H, T, Tdg, S, Sdg}}",
gate
);
}
}
#[test]
fn test_sequence_to_matrix_empty() {
let m = sequence_to_matrix(&[]);
assert!(
m.frobenius_distance(&SU2::identity()) < EPS,
"Empty sequence should give identity"
);
}
#[test]
fn test_sequence_to_matrix_h() {
let m = sequence_to_matrix(&["H"]);
let h = SU2::from_gate_name("H").expect("H must exist");
assert!(m.frobenius_distance(&h) < EPS);
}
#[test]
fn test_adjoint_gate_roundtrip() {
assert_eq!(adjoint_gate("T"), "Tdg");
assert_eq!(adjoint_gate("Tdg"), "T");
assert_eq!(adjoint_gate("H"), "H");
assert_eq!(adjoint_gate("S"), "Sdg");
assert_eq!(adjoint_gate("Sdg"), "S");
}
#[test]
fn test_rotation_about_axis_z() {
let theta = 0.7;
let r1 = rotation_about_axis(0.0, 0.0, 1.0, theta);
let r2 = SU2::rotation_z(theta);
assert!(
r1.frobenius_distance(&r2) < EPS,
"rotation_about_axis(z) should match rotation_z"
);
}
#[test]
fn test_rotation_about_axis_x() {
let theta = 1.2;
let r1 = rotation_about_axis(1.0, 0.0, 0.0, theta);
let r2 = SU2::rotation_x(theta);
assert!(
r1.frobenius_distance(&r2) < EPS,
"rotation_about_axis(x) should match rotation_x"
);
}
#[test]
fn test_clifford_enumeration_size() {
let cliffords = enumerate_cliffords();
assert_eq!(
cliffords.len(),
24,
"Clifford group enumeration should yield exactly 24 elements"
);
}
#[test]
fn test_two_stage_table_covers_small_y_rotation() {
let table = BasicApproximationTable::build(10);
let target = SU2::rotation_y(0.3);
let (seq, dist) = table.find_closest(&target);
assert!(
!seq.is_empty(),
"Table should cover RY(0.3) with a non-identity sequence (got seq_len=0, dist={dist:.6})"
);
assert!(
dist < 0.10,
"RY(0.3) approximation should beat identity bound 0.15, got {dist:.6}"
);
}
}