1#![allow(unused)]
18
19use crate::pauli_string::PauliString;
20use crate::pauli_sum::PauliSum;
21use crate::phase::Phase;
22use hashbrown::HashMap;
23use num_complex::Complex64;
24use rustc_hash::FxBuildHasher;
25
26pub struct BuildAccumulator<const W: usize> {
31 map: HashMap<PauliString<W>, Complex64, FxBuildHasher>,
32 num_qubits: usize,
33}
34
35impl<const W: usize> BuildAccumulator<W> {
36 pub fn new(num_qubits: usize) -> Self {
38 Self {
39 map: HashMap::with_hasher(FxBuildHasher),
40 num_qubits,
41 }
42 }
43
44 pub fn with_capacity(num_qubits: usize, cap: usize) -> Self {
46 Self {
47 map: HashMap::with_capacity_and_hasher(cap, FxBuildHasher),
48 num_qubits,
49 }
50 }
51
52 pub fn add_term(&mut self, p: PauliString<W>, phase: Phase, c: Complex64) {
73 let contribution = phase.apply(c);
74 self.map
75 .entry(p)
76 .and_modify(|e| *e += contribution)
77 .or_insert(contribution);
78 }
79
80 pub fn finalize(self) -> PauliSum<W> {
83 let zero = Complex64::new(0.0, 0.0);
84 let mut entries: Vec<(PauliString<W>, Complex64)> =
85 self.map.into_iter().filter(|(_, c)| *c != zero).collect();
86 entries.sort_by(|a, b| (&a.0.x, &a.0.z).cmp(&(&b.0.x, &b.0.z)));
87 let n = entries.len();
88 let mut x = Vec::with_capacity(n);
89 let mut z = Vec::with_capacity(n);
90 let mut coeff = Vec::with_capacity(n);
91 for (p, c) in entries {
92 x.push(p.x);
93 z.push(p.z);
94 coeff.push(c);
95 }
96 PauliSum {
97 x,
98 z,
99 coeff,
100 num_qubits: self.num_qubits,
101 }
102 }
103}
104
105#[cfg(all(test, debug_assertions))]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn finalize_empty_accumulator_is_empty() {
111 let acc = BuildAccumulator::<1>::new(4);
112 let s = acc.finalize();
113 assert!(s.is_empty());
114 assert_eq!(s.num_qubits(), 4);
115 s.assert_invariants();
116 }
117
118 #[test]
119 fn add_term_dup_sums_coeffs() {
120 let mut acc = BuildAccumulator::<1>::new(4);
121 let p = PauliString::<1>::x(2);
122 acc.add_term(p, Phase::ONE, Complex64::new(1.0, 0.0));
123 acc.add_term(p, Phase::ONE, Complex64::new(2.5, -1.0));
124 let s = acc.finalize();
125 assert_eq!(s.len(), 1);
126 assert_eq!(s.coeff()[0], Complex64::new(3.5, -1.0));
127 s.assert_invariants();
128 }
129
130 #[test]
131 fn add_term_phase_one_is_identity_factor() {
132 let mut acc = BuildAccumulator::<1>::new(4);
133 let p = PauliString::<1>::x(0);
134 acc.add_term(p, Phase::ONE, Complex64::new(2.0, 3.0));
135 let s = acc.finalize();
136 assert_eq!(s.coeff()[0], Complex64::new(2.0, 3.0));
137 }
138
139 #[test]
140 fn add_term_phase_i_multiplies_by_i() {
141 let mut acc = BuildAccumulator::<1>::new(4);
142 let p = PauliString::<1>::x(0);
143 acc.add_term(p, Phase::I, Complex64::new(2.0, 3.0));
145 let s = acc.finalize();
146 assert_eq!(s.coeff()[0], Complex64::new(-3.0, 2.0));
147 }
148
149 #[test]
150 fn add_term_phase_minus_one_negates() {
151 let mut acc = BuildAccumulator::<1>::new(4);
152 let p = PauliString::<1>::x(0);
153 acc.add_term(p, Phase::MINUS_ONE, Complex64::new(2.0, 3.0));
154 let s = acc.finalize();
155 assert_eq!(s.coeff()[0], Complex64::new(-2.0, -3.0));
156 }
157
158 #[test]
159 fn add_term_phase_minus_i_multiplies_by_minus_i() {
160 let mut acc = BuildAccumulator::<1>::new(4);
161 let p = PauliString::<1>::x(0);
162 acc.add_term(p, Phase::MINUS_I, Complex64::new(2.0, 3.0));
164 let s = acc.finalize();
165 assert_eq!(s.coeff()[0], Complex64::new(3.0, -2.0));
166 }
167
168 #[test]
169 fn add_term_cancellation_drops() {
170 let mut acc = BuildAccumulator::<1>::new(4);
171 let p = PauliString::<1>::x(0);
172 acc.add_term(p, Phase::ONE, Complex64::new(1.0, 0.0));
173 acc.add_term(p, Phase::MINUS_ONE, Complex64::new(1.0, 0.0));
174 let s = acc.finalize();
175 assert!(s.is_empty());
176 s.assert_invariants();
177 }
178
179 #[test]
180 fn finalize_sorts_by_lex_key() {
181 let mut acc = BuildAccumulator::<1>::new(4);
185 acc.add_term(PauliString::<1>::x(1), Phase::ONE, Complex64::new(3.0, 0.0));
186 acc.add_term(PauliString::<1>::z(0), Phase::ONE, Complex64::new(1.0, 0.0));
187 acc.add_term(PauliString::<1>::x(0), Phase::ONE, Complex64::new(2.0, 0.0));
188 let s = acc.finalize();
189 assert_eq!(s.len(), 3);
190 assert_eq!(s.x()[0], [0u64]);
191 assert_eq!(s.z()[0], [1u64]);
192 assert_eq!(s.coeff()[0], Complex64::new(1.0, 0.0));
193 assert_eq!(s.x()[1], [1u64]);
194 assert_eq!(s.coeff()[1], Complex64::new(2.0, 0.0));
195 assert_eq!(s.x()[2], [2u64]);
196 assert_eq!(s.coeff()[2], Complex64::new(3.0, 0.0));
197 s.assert_invariants();
198 }
199
200 #[test]
201 fn finalize_w2_across_word_boundary() {
202 let mut acc = BuildAccumulator::<2>::new(128);
203 acc.add_term(
204 PauliString::<2>::x(64),
205 Phase::ONE,
206 Complex64::new(1.0, 0.0),
207 );
208 acc.add_term(PauliString::<2>::x(0), Phase::ONE, Complex64::new(2.0, 0.0));
209 acc.add_term(
210 PauliString::<2>::z(127),
211 Phase::ONE,
212 Complex64::new(3.0, 0.0),
213 );
214 let s = acc.finalize();
215 assert_eq!(s.len(), 3);
216 s.assert_invariants();
217 }
218
219 #[test]
220 fn add_term_phase_new_wraps_mod_4() {
221 let mut acc = BuildAccumulator::<1>::new(4);
223 let p = PauliString::<1>::x(0);
224 acc.add_term(p, Phase::new(5), Complex64::new(2.0, 0.0));
225 let s = acc.finalize();
226 assert_eq!(s.coeff()[0], Complex64::new(0.0, 2.0));
227 }
228
229 #[test]
230 fn finalize_with_capacity_preallocated() {
231 let mut acc = BuildAccumulator::<1>::with_capacity(4, 16);
233 acc.add_term(PauliString::<1>::x(0), Phase::ONE, Complex64::new(1.0, 0.0));
234 let s = acc.finalize();
235 assert_eq!(s.len(), 1);
236 s.assert_invariants();
237 }
238}