paulistrings/
pauli_string.rs1use num_complex::Complex64;
5
6use crate::phase::Phase;
7
8#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
14#[repr(C)]
15pub struct PauliString<const W: usize> {
16 pub x: [u64; W],
18 pub z: [u64; W],
20}
21
22impl<const W: usize> PauliString<W> {
23 pub const fn identity() -> Self {
25 Self { x: [0u64; W], z: [0u64; W] }
26 }
27
28 pub fn x(qubit: usize) -> Self {
34 debug_assert!(qubit < 64 * W);
35 let mut p = Self::identity();
36 p.x[qubit / 64] = 1u64 << (qubit % 64);
37 p
38 }
39
40 pub fn y(qubit: usize) -> Self {
46 debug_assert!(qubit < 64 * W);
47 let mut p = Self::identity();
48 let word = qubit / 64;
49 let bit = 1u64 << (qubit % 64);
50 p.x[word] = bit;
51 p.z[word] = bit;
52 p
53 }
54
55 pub fn z(qubit: usize) -> Self {
61 debug_assert!(qubit < 64 * W);
62 let mut p = Self::identity();
63 p.z[qubit / 64] = 1u64 << (qubit % 64);
64 p
65 }
66
67 pub fn weight(&self) -> u32 {
69 (0..W).map(|i| (self.x[i] | self.z[i]).count_ones()).sum()
70 }
71
72 pub fn mul_assign(&mut self, other: &Self) -> Phase {
74 let mut delta: u32 = 0;
76 for i in 0..W {
77 let a = self.x[i];
78 let b = self.z[i];
79 let c = other.x[i];
80 let d = other.z[i];
81 let zc_x = (b & c).count_ones();
82 let y_self = (a & b).count_ones();
83 let y_other = (c & d).count_ones();
84 let y_result = ((a ^ c) & (b ^ d)).count_ones();
85 delta = delta.wrapping_add(zc_x.wrapping_mul(2));
86 delta = delta.wrapping_add(y_self);
87 delta = delta.wrapping_add(y_other);
88 delta = delta.wrapping_sub(y_result);
89 self.x[i] ^= c;
90 self.z[i] ^= d;
91 }
92 Phase::new(delta as u8)
93 }
94
95 pub fn product(mut self, other: &Self) -> (Self, Phase) {
97 let phase = self.mul_assign(other);
98 (self, phase)
99 }
100
101 pub fn is_within(&self, num_qubits: usize) -> bool {
107 debug_assert!(num_qubits <= 64 * W);
108 let mut leak: u64 = 0;
109 for i in 0..W {
110 leak |= (self.x[i] | self.z[i]) & !word_mask(num_qubits, i);
111 }
112 leak == 0
113 }
114
115 pub fn commutes_with(&self, other: &Self) -> bool {
117 let mut folded: u64 = 0;
119 for i in 0..W {
120 folded ^= (self.x[i] & other.z[i]) ^ (self.z[i] & other.x[i]);
121 }
122 folded.count_ones() & 1 == 0
123 }
124
125 pub fn commutator(self, other: &Self) -> (Self, Complex64) {
129 let vanishes = self.commutes_with(other);
130 let (product, phase) = self.product(other);
131 (product, scaled_phase(phase, vanishes))
132 }
133
134 pub fn anticommutator(self, other: &Self) -> (Self, Complex64) {
138 let vanishes = !self.commutes_with(other);
139 let (product, phase) = self.product(other);
140 (product, scaled_phase(phase, vanishes))
141 }
142}
143
144pub(crate) fn word_mask(num_qubits: usize, word: usize) -> u64 {
146 let first_qubit = 64 * word;
147 if num_qubits >= first_qubit + 64 {
148 !0u64
149 } else if num_qubits <= first_qubit {
150 0
151 } else {
152 (1u64 << (num_qubits - first_qubit)) - 1
153 }
154}
155
156pub(crate) fn qubit_mask<const W: usize>(
158 qubits: impl IntoIterator<Item = usize>,
159 num_qubits: usize,
160) -> [u64; W] {
161 let mut mask = [0u64; W];
162 for q in qubits {
163 assert!(q < num_qubits, "qubit {q} is outside the {num_qubits}-qubit register");
164 mask[q / 64] |= 1u64 << (q % 64);
165 }
166 mask
167}
168
169fn scaled_phase(phase: Phase, vanishes: bool) -> Complex64 {
171 if vanishes {
172 Complex64::new(0.0, 0.0)
173 } else {
174 phase.to_complex() * 2.0
175 }
176}
177
178impl<const W: usize> Default for PauliString<W> {
179 fn default() -> Self {
180 Self::identity()
181 }
182}
183
184#[cfg(test)]
185mod tests;