use super::*;
use crate::test_support::Xs64;
use num_complex::Complex64;
const ZERO: Complex64 = Complex64::new(0.0, 0.0);
#[test]
fn commutator_of_x_and_z_is_minus_two_i_y() {
fn check<const W: usize>() {
let x = PauliString::<W>::x(0);
let z = PauliString::<W>::z(0);
let (product, coeff) = x.commutator(&z);
assert_eq!(product, PauliString::<W>::y(0));
assert_eq!(coeff, Complex64::new(0.0, -2.0));
let (product, coeff) = x.anticommutator(&z);
assert_eq!(product, PauliString::<W>::y(0));
assert_eq!(coeff, ZERO);
}
check::<1>();
check::<2>();
}
#[test]
fn commutator_is_antisymmetric_on_x_and_z() {
let (product, coeff) = PauliString::<1>::z(0).commutator(&PauliString::<1>::x(0));
assert_eq!(product, PauliString::<1>::y(0));
assert_eq!(coeff, Complex64::new(0.0, 2.0));
}
#[test]
fn commuting_strings_have_a_zero_commutator_and_twice_the_product() {
fn check<const W: usize>() {
let a = PauliString::<W>::x(0);
let b = PauliString::<W>::x(1);
let mut xx = PauliString::<W>::x(0);
xx.x[0] |= 1u64 << 1;
let (product, coeff) = a.commutator(&b);
assert_eq!(product, xx);
assert_eq!(coeff, ZERO);
let (product, coeff) = a.anticommutator(&b);
assert_eq!(product, xx);
assert_eq!(coeff, Complex64::new(2.0, 0.0));
}
check::<1>();
check::<2>();
}
#[test]
fn identity_anticommutator_is_twice_the_other_string() {
let id = PauliString::<2>::identity();
let y = PauliString::<2>::y(64);
let (product, coeff) = id.commutator(&y);
assert_eq!(product, y);
assert_eq!(coeff, ZERO);
let (product, coeff) = id.anticommutator(&y);
assert_eq!(product, y);
assert_eq!(coeff, Complex64::new(2.0, 0.0));
}
#[test]
fn commutator_multi_word() {
let (product, coeff) = PauliString::<2>::x(64).commutator(&PauliString::<2>::z(64));
assert_eq!(product, PauliString::<2>::y(64));
assert_eq!(coeff, Complex64::new(0.0, -2.0));
}
mod props {
use super::*;
use proptest::prelude::*;
fn arb_pauli_w2() -> impl Strategy<Value = PauliString<2>> {
(any::<u64>(), any::<u64>(), any::<u64>(), any::<u64>())
.prop_map(|(x0, x1, z0, z1)| PauliString::<2> { x: [x0, x1], z: [z0, z1] })
}
proptest! {
#[test]
fn commutator_plus_anticommutator_is_twice_the_product(
a in arb_pauli_w2(),
b in arb_pauli_w2(),
) {
let (product, phase) = a.product(&b);
let (comm_product, comm) = a.commutator(&b);
let (anti_product, anti) = a.anticommutator(&b);
prop_assert_eq!(comm_product, product);
prop_assert_eq!(anti_product, product);
prop_assert_eq!(comm + anti, phase.to_complex() * 2.0);
prop_assert!((comm == ZERO) != (anti == ZERO));
}
#[test]
fn mul_is_associative(
a in arb_pauli_w2(),
b in arb_pauli_w2(),
c in arb_pauli_w2(),
) {
let mut left = a;
let p1 = left.mul_assign(&b);
let p2 = left.mul_assign(&c);
let mut bc = b;
let q1 = bc.mul_assign(&c);
let mut right = a;
let q2 = right.mul_assign(&bc);
prop_assert_eq!(left.x, right.x);
prop_assert_eq!(left.z, right.z);
prop_assert_eq!(p1 + p2, q1 + q2);
}
}
}
#[test]
fn qubit_mask_packs_across_words() {
assert_eq!(qubit_mask::<2>([0, 63, 64, 69], 70), [1 | 1 << 63, 1 | 1 << 5]);
}
#[test]
#[should_panic(expected = "qubit 70 is outside the 70-qubit register")]
fn qubit_mask_rejects_a_qubit_outside_the_register() {
qubit_mask::<2>([70], 70);
}
#[test]
fn pauli_string_layout_has_no_padding() {
use std::mem::size_of;
assert_eq!(size_of::<PauliString<1>>(), 16);
assert_eq!(size_of::<PauliString<2>>(), 32);
assert_eq!(size_of::<PauliString<4>>(), 64);
}
#[test]
fn identity_has_zero_bits_and_weight() {
let p: PauliString<2> = PauliString::identity();
assert_eq!(p.x, [0u64; 2]);
assert_eq!(p.z, [0u64; 2]);
assert_eq!(p.weight(), 0);
}
#[test]
fn weight_multi_word() {
let mut p: PauliString<2> = PauliString::identity();
p.x[0] = 1u64 << 7;
p.z[1] = 1u64 << 2;
assert_eq!(p.weight(), 2);
}
#[test]
fn x_constructor_sets_correct_bit() {
let p = PauliString::<1>::x(3);
assert_eq!(p.x[0], 1u64 << 3);
assert_eq!(p.z[0], 0);
assert_eq!(p.weight(), 1);
}
#[test]
fn y_constructor_sets_both_bits() {
let p = PauliString::<1>::y(5);
assert_eq!(p.x[0], 1u64 << 5);
assert_eq!(p.z[0], 1u64 << 5);
assert_eq!(p.weight(), 1);
}
#[test]
fn z_constructor_sets_z_bit() {
let p = PauliString::<1>::z(7);
assert_eq!(p.x[0], 0);
assert_eq!(p.z[0], 1u64 << 7);
assert_eq!(p.weight(), 1);
}
#[test]
fn constructor_crosses_word_boundary() {
let p = PauliString::<2>::x(64);
assert_eq!(p.x[0], 0);
assert_eq!(p.x[1], 1u64 << 0);
assert_eq!(p.z, [0u64; 2]);
assert_eq!(p.weight(), 1);
}
#[test]
fn single_qubit_commutation() {
let (x, y, z) = (PauliString::<1>::x(0), PauliString::<1>::y(0), PauliString::<1>::z(0));
assert!(!x.commutes_with(&z));
assert!(!x.commutes_with(&y));
assert!(x.commutes_with(&x));
let id = PauliString::<1>::identity();
assert!(id.commutes_with(&x) && id.commutes_with(&y) && id.commutes_with(&z));
}
#[test]
fn xx_commutes_with_zz() {
let mut xx = PauliString::<1>::x(0);
xx.x[0] |= 1u64 << 1;
let mut zz = PauliString::<1>::z(0);
zz.z[0] |= 1u64 << 1;
assert!(xx.commutes_with(&zz));
}
#[test]
fn commutes_with_multi_word() {
let z64 = PauliString::<2>::z(64);
assert!(PauliString::<2>::x(0).commutes_with(&z64));
assert!(!PauliString::<2>::x(64).commutes_with(&z64));
}
#[test]
fn x_times_z_gives_minus_i_y() {
let mut p = PauliString::<1>::x(0);
let phase = p.mul_assign(&PauliString::<1>::z(0));
assert_eq!((p.x[0], p.z[0]), (1, 1));
assert_eq!(phase, Phase::MINUS_I);
}
#[test]
fn z_times_x_gives_plus_i_y() {
let mut p = PauliString::<1>::z(0);
let phase = p.mul_assign(&PauliString::<1>::x(0));
assert_eq!((p.x[0], p.z[0]), (1, 1));
assert_eq!(phase, Phase::I);
}
#[test]
fn x_times_y_gives_i_z() {
let mut p = PauliString::<1>::x(0);
let phase = p.mul_assign(&PauliString::<1>::y(0));
assert_eq!((p.x[0], p.z[0]), (0, 1));
assert_eq!(phase, Phase::I);
}
#[test]
fn squares_are_the_identity() {
for p in [PauliString::<1>::x(0), PauliString::<1>::y(0)] {
let mut q = p;
let phase = q.mul_assign(&p);
assert_eq!(q, PauliString::<1>::identity());
assert_eq!(phase, Phase::ONE);
}
}
#[test]
fn product_matches_mul_assign() {
let (p, phase) = PauliString::<1>::x(0).product(&PauliString::<1>::z(0));
assert_eq!((p.x[0], p.z[0]), (1, 1));
assert_eq!(phase, Phase::MINUS_I);
}
#[test]
fn mul_phase_accumulates_at_call_site() {
let mut p = PauliString::<1>::x(0);
let mut total = Phase::I;
total += p.mul_assign(&PauliString::<1>::z(0));
assert_eq!((p.x[0], p.z[0]), (1, 1));
assert_eq!(total, Phase::ONE);
}
#[test]
fn mul_multi_word_no_overflow() {
let mut p = PauliString::<2>::x(64);
let phase = p.mul_assign(&PauliString::<2>::z(64));
assert_eq!((p.x, p.z), ([0, 1], [0, 1]));
assert_eq!(phase, Phase::MINUS_I);
}
#[test]
fn is_within_zero_qubits_only_identity() {
assert!(PauliString::<1>::identity().is_within(0));
assert!(PauliString::<2>::identity().is_within(0));
assert!(!PauliString::<1>::x(0).is_within(0));
assert!(!PauliString::<1>::z(0).is_within(0));
}
#[test]
fn is_within_full_capacity_accepts_anything() {
let p = PauliString::<1> { x: [!0u64], z: [!0u64] };
assert!(p.is_within(64));
let q = PauliString::<2> { x: [!0u64; 2], z: [!0u64; 2] };
assert!(q.is_within(128));
}
#[test]
fn is_within_boundary_inside_word() {
for p in [PauliString::<1>::x, PauliString::<1>::z, PauliString::<1>::y] {
assert!(p(49).is_within(50));
assert!(!p(50).is_within(50));
}
}
#[test]
fn is_within_word_boundary() {
let p = PauliString::<2> { x: [!0u64, 0], z: [!0u64, 0] };
assert!(p.is_within(64));
assert!(!PauliString::<2>::x(64).is_within(64));
assert!(!PauliString::<2>::z(64).is_within(64));
}
#[test]
fn is_within_partial_in_second_word() {
for p in [PauliString::<2>::x, PauliString::<2>::z] {
assert!(p(99).is_within(100));
assert!(!p(100).is_within(100));
}
}
#[test]
fn xor_fold_commutes_matches_per_word_popcount() {
fn naive<const W: usize>(a: &PauliString<W>, b: &PauliString<W>) -> bool {
let mut parity: u32 = 0;
for i in 0..W {
parity ^= (a.x[i] & b.z[i]).count_ones();
parity ^= (a.z[i] & b.x[i]).count_ones();
}
parity & 1 == 0
}
fn check<const W: usize>(seed: u64) {
let mut rng = Xs64::new(seed);
for _ in 0..2000 {
let mut p = PauliString::<W>::identity();
let mut q = PauliString::<W>::identity();
for _ in 0..3 {
let b = rng.next_u64();
p.x[(b as usize >> 3) % W] |= 1u64 << (b % 64);
let c = rng.next_u64();
q.z[(c as usize >> 3) % W] |= 1u64 << (c % 64);
}
p.z[0] = rng.next_u64();
q.x[0] = rng.next_u64();
assert_eq!(p.commutes_with(&q), naive(&p, &q), "W={W}");
}
}
check::<1>(0xA11CE);
check::<2>(0xB0B);
check::<4>(0xC0FFEE);
check::<8>(0xD00D);
check::<16>(0xFEED);
}