use ppvm_traits::FoldHashMap as HashMap;
use bitvec::view::BitView;
use num::PrimInt;
use num::{
Complex, One, Zero,
complex::{Complex64, ComplexFloat},
};
use crate::prelude::*;
const PAULIS: [Pauli; 4] = [Pauli::I, Pauli::X, Pauli::Z, Pauli::Y];
impl<T: Config, I, C: SparseVector<Complex<T::Coeff>, I>> RotationTwo<T>
for GeneralizedTableau<T, I, C>
where
<<T as Config>::Storage as BitView>::Store: PrimInt,
T::Coeff: Zero + One + Send + Sync + num::Num + PartialOrd,
I: TableauIndex + Send + Sync,
Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
+ std::ops::AddAssign
+ From<Complex64>
+ ComplexFloat
+ Copy,
{
fn rotate_2(
&mut self,
axis_a: [u8; 2],
axis_b: [u8; 2],
a: usize,
b: usize,
theta: <T as Config>::Coeff,
) {
let [axis_a_x, axis_a_z] = axis_a;
let [axis_b_x, axis_b_z] = axis_b;
let pauli_a = PAULIS[(axis_a_z << 1 | axis_a_x) as usize];
let pauli_b = PAULIS[(axis_b_z << 1 | axis_b_x) as usize];
if self.is_inactive(a) {
return self.rotate_1(pauli_b, b, theta);
} else if self.is_inactive(b) {
return self.rotate_1(pauli_a, a, theta);
}
let (sin, cos) = (theta * 0.5.into()).sin_cos();
let complex_cos: Complex<T::Coeff> = Complex {
re: cos,
im: T::Coeff::zero(),
};
let i_complex_sin: Complex<T::Coeff> = Complex {
re: T::Coeff::zero(),
im: -sin,
};
let mut branch_coefficients = self.coefficients.clone();
self.compute_coefficients_after_pauli_apply(&mut branch_coefficients, b, pauli_b);
self.compute_coefficients_after_pauli_apply(&mut branch_coefficients, a, pauli_a);
let old_coefficients = std::mem::replace(&mut self.coefficients, C::new());
let mut new_coefficients: HashMap<I, Complex<T::Coeff>> = HashMap::default();
for (coeff, idx) in old_coefficients {
*new_coefficients.entry(idx).or_insert(Complex::zero()) += complex_cos * coeff;
}
for (branch_coeff, idx) in branch_coefficients {
*new_coefficients.entry(idx).or_insert(Complex::zero()) += i_complex_sin * branch_coeff;
}
let cutoff = Complex {
re: self.coefficient_threshold.clone(),
im: T::Coeff::zero(),
};
for (idx, coeff) in new_coefficients {
if coeff.abs() > cutoff.abs() {
self.coefficients.unsafe_insert(idx, coeff);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ppvm_pauli_sum::config::fxhash::ByteF64;
use std::f64::consts::{FRAC_PI_2, PI};
type TestConfig = ByteF64<1>;
type TestTableau = GeneralizedTableau<TestConfig>;
#[test]
fn test_rxx_pi_flips_both_to_one() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.rxx(0, 1, PI);
assert_eq!(tab.coefficients.len(), 1, "rxx(π) should not branch");
assert!(tab.measure(0).unwrap());
assert!(tab.measure(1).unwrap());
}
#[test]
fn test_rxx_half_pi_branches() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.rxx(0, 1, FRAC_PI_2);
assert_eq!(
tab.coefficients.len(),
2,
"rxx(π/2) should create 2 branches"
);
}
#[test]
fn test_rxx_half_pi_twice_flips_both() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.rxx(0, 1, FRAC_PI_2);
tab.rxx(0, 1, FRAC_PI_2);
assert!(tab.measure(0).unwrap());
assert!(tab.measure(1).unwrap());
}
#[test]
fn test_ryy_pi_flips_both_to_one() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.ryy(0, 1, PI);
assert_eq!(tab.coefficients.len(), 1, "ryy(π) should not branch");
assert!(tab.measure(0).unwrap());
assert!(tab.measure(1).unwrap());
}
#[test]
fn test_ryy_half_pi_branches() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.ryy(0, 1, FRAC_PI_2);
assert_eq!(
tab.coefficients.len(),
2,
"ryy(π/2) should create 2 branches"
);
}
#[test]
fn test_ryy_half_pi_twice_flips_both() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.ryy(0, 1, FRAC_PI_2);
tab.ryy(0, 1, FRAC_PI_2);
assert!(tab.measure(0).unwrap());
assert!(tab.measure(1).unwrap());
}
#[test]
fn test_rzz_pi_leaves_00() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.rzz(0, 1, PI);
assert_eq!(
tab.coefficients.len(),
1,
"rzz(π) on |00⟩ should not branch"
);
assert!(!tab.measure(0).unwrap());
assert!(!tab.measure(1).unwrap());
}
#[test]
fn test_rzz_does_not_branch_on_comp_basis() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.rzz(0, 1, FRAC_PI_2);
assert_eq!(
tab.coefficients.len(),
1,
"rzz(π/2) on |00⟩ should not branch: ZZ is a stabilizer"
);
}
#[test]
fn test_rzz_pi_on_10() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.x(0);
tab.rzz(0, 1, PI);
assert_eq!(
tab.coefficients.len(),
1,
"rzz(π) on |10⟩ should not branch"
);
assert!(tab.measure(0).unwrap());
assert!(!tab.measure(1).unwrap());
}
#[test]
fn test_rxx_half_pi_correlated_measurements() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.rxx(0, 1, FRAC_PI_2);
assert_eq!(
tab.coefficients.len(),
2,
"rxx(π/2) should create 2 branches"
);
let r0 = tab.measure(0);
let r1 = tab.measure(1);
assert_eq!(
r0, r1,
"rxx(π/2)|00⟩ = (|00⟩-i|11⟩)/√2: measurements must agree"
);
}
#[test]
fn test_rxx_half_pi_on_10_anticorrelated_measurements() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.x(0);
tab.rxx(0, 1, FRAC_PI_2);
assert_eq!(
tab.coefficients.len(),
2,
"rxx(π/2) on |10⟩ should create 2 branches"
);
let r0 = tab.measure(0);
let r1 = tab.measure(1);
assert_ne!(
r0, r1,
"rxx(π/2)|10⟩ = (|10⟩-i|01⟩)/√2: measurements must differ"
);
}
#[test]
fn test_ryy_half_pi_correlated_measurements() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.ryy(0, 1, FRAC_PI_2);
assert_eq!(
tab.coefficients.len(),
2,
"ryy(π/2) should create 2 branches"
);
let r0 = tab.measure(0);
let r1 = tab.measure(1);
assert_eq!(
r0, r1,
"ryy(π/2)|00⟩ = (|00⟩+i|11⟩)/√2: measurements must agree"
);
}
#[test]
fn test_rzz_half_pi_on_plus_plus_branches_and_correlates() {
let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
tab.h(0);
tab.h(1);
tab.rzz(0, 1, FRAC_PI_2);
assert_eq!(
tab.coefficients.len(),
2,
"rzz(π/2) on |++⟩ should create 2 branches"
);
tab.h(0);
tab.h(1);
let r0 = tab.measure(0);
let r1 = tab.measure(1);
assert_eq!(r0, r1, "H·rzz(π/2)·H = rxx(π/2): measurements must agree");
}
}