use num_complex::Complex64;
use crate::phase::Phase;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(C)]
pub struct PauliString<const W: usize> {
pub x: [u64; W],
pub z: [u64; W],
}
impl<const W: usize> PauliString<W> {
pub const fn identity() -> Self {
Self { x: [0u64; W], z: [0u64; W] }
}
pub fn x(qubit: usize) -> Self {
debug_assert!(qubit < 64 * W);
let mut p = Self::identity();
p.x[qubit / 64] = 1u64 << (qubit % 64);
p
}
pub fn y(qubit: usize) -> Self {
debug_assert!(qubit < 64 * W);
let mut p = Self::identity();
let word = qubit / 64;
let bit = 1u64 << (qubit % 64);
p.x[word] = bit;
p.z[word] = bit;
p
}
pub fn z(qubit: usize) -> Self {
debug_assert!(qubit < 64 * W);
let mut p = Self::identity();
p.z[qubit / 64] = 1u64 << (qubit % 64);
p
}
pub fn weight(&self) -> u32 {
(0..W).map(|i| (self.x[i] | self.z[i]).count_ones()).sum()
}
pub fn mul_assign(&mut self, other: &Self) -> Phase {
let mut delta: u32 = 0;
for i in 0..W {
let a = self.x[i];
let b = self.z[i];
let c = other.x[i];
let d = other.z[i];
let zc_x = (b & c).count_ones();
let y_self = (a & b).count_ones();
let y_other = (c & d).count_ones();
let y_result = ((a ^ c) & (b ^ d)).count_ones();
delta = delta.wrapping_add(zc_x.wrapping_mul(2));
delta = delta.wrapping_add(y_self);
delta = delta.wrapping_add(y_other);
delta = delta.wrapping_sub(y_result);
self.x[i] ^= c;
self.z[i] ^= d;
}
Phase::new(delta as u8)
}
pub fn product(mut self, other: &Self) -> (Self, Phase) {
let phase = self.mul_assign(other);
(self, phase)
}
pub fn is_within(&self, num_qubits: usize) -> bool {
debug_assert!(num_qubits <= 64 * W);
let mut leak: u64 = 0;
for i in 0..W {
leak |= (self.x[i] | self.z[i]) & !word_mask(num_qubits, i);
}
leak == 0
}
pub fn commutes_with(&self, other: &Self) -> bool {
let mut folded: u64 = 0;
for i in 0..W {
folded ^= (self.x[i] & other.z[i]) ^ (self.z[i] & other.x[i]);
}
folded.count_ones() & 1 == 0
}
pub fn commutator(self, other: &Self) -> (Self, Complex64) {
let vanishes = self.commutes_with(other);
let (product, phase) = self.product(other);
(product, scaled_phase(phase, vanishes))
}
pub fn anticommutator(self, other: &Self) -> (Self, Complex64) {
let vanishes = !self.commutes_with(other);
let (product, phase) = self.product(other);
(product, scaled_phase(phase, vanishes))
}
}
pub(crate) fn word_mask(num_qubits: usize, word: usize) -> u64 {
let first_qubit = 64 * word;
if num_qubits >= first_qubit + 64 {
!0u64
} else if num_qubits <= first_qubit {
0
} else {
(1u64 << (num_qubits - first_qubit)) - 1
}
}
pub(crate) fn qubit_mask<const W: usize>(
qubits: impl IntoIterator<Item = usize>,
num_qubits: usize,
) -> [u64; W] {
let mut mask = [0u64; W];
for q in qubits {
assert!(q < num_qubits, "qubit {q} is outside the {num_qubits}-qubit register");
mask[q / 64] |= 1u64 << (q % 64);
}
mask
}
fn scaled_phase(phase: Phase, vanishes: bool) -> Complex64 {
if vanishes {
Complex64::new(0.0, 0.0)
} else {
phase.to_complex() * 2.0
}
}
impl<const W: usize> Default for PauliString<W> {
fn default() -> Self {
Self::identity()
}
}
#[cfg(test)]
mod tests;