use bnum::types::U256;
use itertools::Itertools;
use num::complex::Complex;
use ppvm_pauli_sum::config::dashmap::ByteFxHashF64;
use ppvm_tableau::prelude::*;
#[test]
fn test_tableau() {
let mut tableau: Tableau<ByteFxHashF64<1>> = Tableau::new(2);
tableau.h(0);
tableau.cnot(0, 1);
assert_eq!(tableau.stabilizers()[0].to_string(), "+XX");
assert_eq!(tableau.stabilizers()[1].to_string(), "+ZZ");
let mut tableau: Tableau<ByteFxHashF64<1>> = Tableau::new(1);
tableau.h(0);
tableau.s(0);
assert_eq!(tableau.stabilizers()[0].to_string(), "+Y");
assert_eq!(tableau.destabilizers()[0].to_string(), "+Z");
}
#[test]
fn generalized_tableau() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(2, 1e-12);
tableau.h(0);
tableau.cnot(0, 1);
tableau.t(0);
assert_eq!(tableau.coefficients.len(), 2);
let idx: Vec<_> = tableau
.coefficients
.clone()
.into_iter()
.map(|(_, i)| i)
.sorted()
.collect();
assert_eq!(idx, vec![0, 1]);
tableau.t_dag(0);
assert_eq!(tableau.coefficients.len(), 1);
tableau.t(0);
tableau.t(1);
println!("{}", tableau);
assert_eq!(tableau.coefficients.len(), 2);
let mut sorted_coefficients = tableau.coefficients.clone();
sorted_coefficients.sort_by_key(|entry| entry.1);
const PI: f64 = std::f64::consts::PI;
let cos_pi_8: f64 = (PI / 8.0).cos();
let sin_pi_8: f64 = (PI / 8.0).sin();
let expected_coefficients = [
Complex {
re: (PI / 4.0).cos() * (cos_pi_8 * cos_pi_8 - sin_pi_8 * sin_pi_8),
im: (PI / 4.0).sin() * (cos_pi_8 * cos_pi_8 - sin_pi_8 * sin_pi_8),
},
Complex {
re: (PI / 4.0).cos() * 2.0 * sin_pi_8 * cos_pi_8,
im: (PI / 4.0).sin() * -2.0 * sin_pi_8 * cos_pi_8,
},
];
for ((val1, idx1), (idx2, val2)) in sorted_coefficients
.iter()
.zip(expected_coefficients.iter().enumerate())
{
assert_eq!(idx1, &(idx2 as u128));
assert!((val1.re - val2.re).abs() < 1e-11);
assert!((val1.im - val2.im).abs() < 1e-11);
}
println!("{}", tableau);
}
#[test]
fn test_generalized_tableau_phase() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(1, 1e-12);
tableau.h(0);
tableau.t(0);
tableau.t(0);
let mut sorted_coefficients = tableau.coefficients.clone();
sorted_coefficients.sort_by_key(|entry| entry.1);
let expected_coefficients = [Complex { re: 0.5, im: 0.5 }, Complex { re: 0.5, im: -0.5 }];
println!("{}", tableau);
for ((val1, idx1), (idx2, val2)) in sorted_coefficients
.iter()
.zip(expected_coefficients.iter().enumerate())
{
assert_eq!(idx1, &(idx2 as u128));
assert!((val1.re - val2.re).abs() < 1e-9);
assert!((val1.im - val2.im).abs() < 1e-9);
}
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(1, 1e-12);
tableau.h(0);
tableau.x(0);
tableau.t(0);
let mut sorted_coefficients = tableau.coefficients.clone();
sorted_coefficients.sort_by_key(|entry| entry.1);
let expected_coefficients = [
Complex {
re: 0.8535533905932737,
im: 0.3535533905932738,
},
Complex {
re: -0.14644660940672624,
im: 0.3535533905932738,
},
];
for ((val1, idx1), (idx2, val2)) in sorted_coefficients
.iter()
.zip(expected_coefficients.iter().enumerate())
{
assert_eq!(idx1, &(idx2 as u128));
assert!((val1.re - val2.re).abs() < 1e-9);
assert!((val1.im - val2.im).abs() < 1e-9);
}
println!("{}", tableau);
}
#[test]
fn test_generalized_tableau_multiple_ts() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(1, 1e-12);
tableau.h(0);
tableau.t(0);
tableau.t(0);
tableau.t(0);
tableau.t(0);
assert_eq!(tableau.coefficients.len(), 1);
}
#[test]
fn test_generalized_tableau_multiple_ts2() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(2, 1e-12);
tableau.h(0);
tableau.h(1);
tableau.t(0);
tableau.t(0);
tableau.t(0);
tableau.t(0);
tableau.t(1);
tableau.t(1);
tableau.t(1);
tableau.t(1);
assert_eq!(tableau.coefficients.len(), 1);
}
#[test]
fn test_generalized_tableau_multiqubit_branching() {
let n = 18;
let mut tableau: GeneralizedTableau<ByteFxHashF64<3>, u128> = GeneralizedTableau::new(n, 1e-12);
for i in 0..n {
tableau.h(i);
}
let mut tgate_counter: u32 = 0;
for i in (0..10).step_by(2) {
tableau.t(i);
tgate_counter += 1;
}
assert_eq!(tableau.coefficients.len(), 2_usize.pow(tgate_counter));
let outcome = tableau.measure(0);
assert_eq!(tableau.coefficients.len(), 2_usize.pow(tgate_counter - 1));
if outcome.unwrap() {
tableau.x(0);
}
tableau.h(0);
tableau.t(0);
assert_eq!(tableau.coefficients.len(), 2_usize.pow(tgate_counter));
}
#[test]
fn test_multiqubit_ghz_state() {
let n = 18;
let mut tableau: GeneralizedTableau<ByteFxHashF64<3>, u128> = GeneralizedTableau::new(n, 1e-12);
tableau.h(0);
tableau.t(0);
for i in 0..n - 1 {
tableau.cnot(i, i + 1);
}
assert_eq!(tableau.coefficients.len(), 2);
let outcome = tableau.measure(0);
println!("{}", tableau);
println!("{}", tableau.coefficients.len());
for i in 0..n {
let outcome_i = tableau.measure(i);
assert_eq!(outcome, outcome_i)
}
}
#[test]
fn test_t_on_computational_basis_no_branching() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(1, 1e-12);
tableau.t(0);
assert_eq!(tableau.coefficients.len(), 1, "T on |0⟩ should not branch");
tableau.x(0);
tableau.t(0);
assert_eq!(tableau.coefficients.len(), 1, "T on |1⟩ should not branch");
}
#[test]
fn test_t_dag_cancels_t() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(2, 1e-12);
tableau.h(0);
tableau.h(1);
tableau.cnot(0, 1);
let coefficients_before = tableau.coefficients.clone();
tableau.t(0);
assert_eq!(
tableau.coefficients.len(),
2,
"T should branch on |+⟩-like state"
);
tableau.t_dag(0);
assert_eq!(
tableau.coefficients.len(),
1,
"T†T should cancel back to 1 branch"
);
assert!((tableau.coefficients[0].0.re - coefficients_before[0].0.re).abs() < 1e-10);
assert!((tableau.coefficients[0].0.im - coefficients_before[0].0.im).abs() < 1e-10);
}
#[test]
fn test_clifford_gates_do_not_branch() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(2, 1e-12);
tableau.h(0);
tableau.t(0);
assert_eq!(tableau.coefficients.len(), 2);
tableau.x(0);
assert_eq!(
tableau.coefficients.len(),
2,
"X should not change branch count"
);
tableau.y(0);
assert_eq!(
tableau.coefficients.len(),
2,
"Y should not change branch count"
);
tableau.z(0);
assert_eq!(
tableau.coefficients.len(),
2,
"Z should not change branch count"
);
tableau.h(1);
assert_eq!(
tableau.coefficients.len(),
2,
"H should not change branch count"
);
tableau.s(0);
assert_eq!(
tableau.coefficients.len(),
2,
"S should not change branch count"
);
tableau.cnot(0, 1);
assert_eq!(
tableau.coefficients.len(),
2,
"CNOT should not change branch count"
);
tableau.cz(0, 1);
assert_eq!(
tableau.coefficients.len(),
2,
"CZ should not change branch count"
);
}
#[test]
fn test_normalization_preserved() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(3, 1e-12);
tableau.h(0);
tableau.h(1);
tableau.h(2);
tableau.t(0);
tableau.t(1);
tableau.cnot(0, 1);
tableau.t(2);
tableau.cz(1, 2);
let norm_sq: f64 = tableau
.coefficients
.clone()
.into_iter()
.map(|(c, _)| c.re * c.re + c.im * c.im)
.sum();
assert!(
(norm_sq - 1.0).abs() < 1e-8,
"Coefficient norm² should be 1, got {}",
norm_sq
);
}
#[test]
fn test_two_t_gates_coefficients() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(1, 1e-12);
tableau.h(0);
tableau.t(0);
tableau.t(0);
assert_eq!(tableau.coefficients.len(), 2);
let mut sorted = tableau.coefficients.clone();
sorted.sort_by_key(|entry| entry.1);
let expected = [Complex { re: 0.5, im: 0.5 }, Complex { re: 0.5, im: -0.5 }];
println!("{}", tableau);
for ((val, idx), (exp_idx, exp_val)) in sorted.iter().zip(expected.iter().enumerate()) {
assert_eq!(*idx, exp_idx as u128);
assert!(
(val.re - exp_val.re).abs() < 1e-11,
"re mismatch at index {}: {} vs {}",
idx,
val.re,
exp_val.re
);
assert!(
(val.im - exp_val.im).abs() < 1e-11,
"im mismatch at index {}: {} vs {}",
idx,
val.im,
exp_val.im
);
}
let norm_sq: f64 = sorted.iter().map(|(c, _)| c.re * c.re + c.im * c.im).sum();
assert!((norm_sq - 1.0).abs() < 1e-10);
}
macro_rules! t_circuit_canonical_coeffs {
($cfg:ty, $idx:ty, $n_qubits:expr, $qubits:expr) => {{
let qubits: &[usize] = $qubits;
let mut tab: GeneralizedTableau<$cfg, $idx> = GeneralizedTableau::new($n_qubits, 1e-12);
for &q in qubits {
tab.h(q);
}
for &q in qubits {
tab.t(q);
tab.t(q);
}
tab.cnot(qubits[0], qubits[1]);
tab.t(qubits[1]);
tab.t(qubits[0]);
let one = <$idx as num::One>::one();
let mut out: Vec<(u128, (f64, f64))> = tab
.coefficients
.iter()
.map(|&(c, idx)| {
let mut canon: u128 = 0;
for (b, &q) in qubits.iter().enumerate() {
if (idx >> q) & one == one {
canon |= 1u128 << b;
}
}
(canon, (c.re, c.im))
})
.collect();
out.sort_by_key(|e| e.0);
out
}};
}
fn assert_coeffs_match(a: &[(u128, (f64, f64))], b: &[(u128, (f64, f64))], ctx: &str) {
assert_eq!(a.len(), b.len(), "branch count mismatch ({ctx})");
for (x, y) in a.iter().zip(b.iter()) {
assert_eq!(x.0, y.0, "index mismatch ({ctx})");
assert!(
(x.1.0 - y.1.0).abs() < 1e-12 && (x.1.1 - y.1.1).abs() < 1e-12,
"coeff mismatch at {} ({ctx}): {:?} vs {:?}",
x.0,
x.1,
y.1
);
}
}
#[test]
fn t_branch_coalesce_fallback_matches_packable_fast_path() {
use bnum::types::U256;
let fast = t_circuit_canonical_coeffs!(ByteFxHashF64<1>, usize, 8, &[0, 1, 2]);
let fallback = t_circuit_canonical_coeffs!(ByteFxHashF64<8>, U256, 56, &[48, 49, 50]);
assert!(!fast.is_empty(), "circuit should branch");
assert_coeffs_match(&fast, &fallback, "packable vs fallback");
}
#[test]
fn t_branch_coalesce_bnum_decode_matches_usize() {
use bnum::BUint;
let usize_path = t_circuit_canonical_coeffs!(ByteFxHashF64<1>, usize, 8, &[0, 1, 2]);
let bnum_path = t_circuit_canonical_coeffs!(ByteFxHashF64<1>, BUint<1>, 8, &[0, 1, 2]);
assert_coeffs_match(&usize_path, &bnum_path, "usize vs bnum decode");
}
#[test]
fn test_eight_t_gates_is_identity() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(1, 1e-12);
tableau.h(0);
let coeff_before = tableau.coefficients.clone();
for _ in 0..8 {
tableau.t(0);
}
assert_eq!(
tableau.coefficients.len(),
1,
"T⁸ should collapse back to 1 branch"
);
assert!((tableau.coefficients[0].0.re - coeff_before[0].0.re).abs() < 1e-10);
assert!((tableau.coefficients[0].0.im - coeff_before[0].0.im).abs() < 1e-10);
}
#[test]
fn test_cz_gate_with_t() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(2, 1e-12);
tableau.h(0);
tableau.h(1);
tableau.cz(0, 1);
tableau.t(0);
assert_eq!(
tableau.coefficients.len(),
2,
"T after CZ|++⟩ should branch"
);
let norm_sq: f64 = tableau
.coefficients
.clone()
.into_iter()
.map(|(c, _)| c.re * c.re + c.im * c.im)
.sum();
assert!(
(norm_sq - 1.0).abs() < 1e-8,
"Norm should be preserved after CZ + T"
);
}
#[test]
fn test_measurement_idempotent() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(2, 1e-12);
tableau.h(0);
tableau.h(1);
tableau.t(0);
tableau.t(1);
let outcome = tableau.measure(0);
let branches_after_first = tableau.coefficients.len();
let outcome2 = tableau.measure(0);
assert_eq!(
outcome, outcome2,
"Repeated measurement should be deterministic"
);
assert_eq!(
tableau.coefficients.len(),
branches_after_first,
"Second measurement should not change branch count"
);
}
#[test]
fn test_independent_t_gates_exponential_branching() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(4, 1e-12);
for i in 0..4 {
tableau.h(i);
}
for i in 0..4 {
tableau.t(i);
assert_eq!(
tableau.coefficients.len(),
2_usize.pow((i + 1) as u32),
"After {} T gates, expected {} branches",
i + 1,
2_usize.pow((i + 1) as u32)
);
}
}
#[test]
fn test_t_gate_measurement_statistics() {
let trials = 2000;
let mut count_one = 0;
for _ in 0..trials {
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> =
GeneralizedTableau::new(1, 1e-12);
tableau.h(0);
tableau.t(0);
if tableau.measure(0).unwrap() {
count_one += 1;
}
}
let prob_one = count_one as f64 / trials as f64;
assert!(
(prob_one - 0.5).abs() < 0.05,
"T|+⟩ measurement should be ~50/50, got P(1)={:.3}",
prob_one
);
}
#[test]
fn test_sqrt_y_direction() {
use ppvm_traits::traits::CliffordExtensions;
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(1, 1e-12);
tableau.sqrt_y(0);
tableau.h(0);
assert!(
!tableau.measure(0).unwrap(),
"sqrt_y|0⟩ should be |+⟩; after H measurement must be 0"
);
let mut tableau: GeneralizedTableau<ByteFxHashF64<1>, u128> = GeneralizedTableau::new(1, 1e-12);
tableau.sqrt_y_dag(0);
tableau.h(0);
assert!(
tableau.measure(0).unwrap(),
"sqrt_y_dag|0⟩ should be |−⟩; after H measurement must be 1"
);
}
#[test]
fn test_buint_index() {
let mut tableau: GeneralizedTableau<ByteFxHashF64<32>, U256> =
GeneralizedTableau::new(130, 1e-12);
tableau.h(0);
tableau.t(1);
}