1use crate::pauli_string::word_mask;
4use crate::rng::{mix64, SPLITMIX_GAMMA};
5
6pub const B_MAX_BITS: u8 = 22;
8
9pub const P_MAX_BITS: u8 = 6;
11
12const PARTITION_ROW_SALT: u64 = 0xD1B5_4A32_D192_ED03;
14
15fn row_word(seed: u64, row: usize, attempt: u32, word: usize, half: u64) -> u64 {
18 debug_assert!(row < 1 << 16 && word < 1 << 15);
19 let position = ((attempt as u64) << 32) | ((row as u64) << 16) | ((word as u64) << 1) | half;
20 mix64(seed.wrapping_add(SPLITMIX_GAMMA.wrapping_mul(position.wrapping_add(1))))
21}
22
23#[derive(Clone, Debug, PartialEq, Eq)]
25pub(crate) struct Gf2Matrix<const W: usize> {
26 rows_x: Vec<[u64; W]>,
27 rows_z: Vec<[u64; W]>,
28 num_qubits: usize,
29}
30
31impl<const W: usize> Gf2Matrix<W> {
32 pub(crate) fn draw(
34 num_qubits: usize,
35 num_rows: usize,
36 seed: u64,
37 exclude_x: &[u64; W],
38 exclude_z: &[u64; W],
39 ) -> Self {
40 let mut rows_x: Vec<[u64; W]> = Vec::with_capacity(num_rows);
41 let mut rows_z: Vec<[u64; W]> = Vec::with_capacity(num_rows);
42 let has_live_columns = num_qubits > 0;
43 for row in 0..num_rows {
44 let mut attempt = 0u32;
45 let (row_x, row_z) = loop {
46 let mut row_x = [0u64; W];
47 let mut row_z = [0u64; W];
48 let mut any = false;
49 for w in 0..W {
50 let mask = word_mask(num_qubits, w);
51 row_x[w] = row_word(seed, row, attempt, w, 0) & mask & !exclude_x[w];
52 row_z[w] = row_word(seed, row, attempt, w, 1) & mask & !exclude_z[w];
53 any |= (row_x[w] | row_z[w]) != 0;
54 }
55 if any || !has_live_columns {
56 break (row_x, row_z);
57 }
58 attempt += 1;
59 };
60 rows_x.push(row_x);
61 rows_z.push(row_z);
62 }
63 Self { rows_x, rows_z, num_qubits }
64 }
65
66 pub(crate) fn from_rows(
68 num_qubits: usize,
69 mut rows_x: Vec<[u64; W]>,
70 mut rows_z: Vec<[u64; W]>,
71 ) -> Self {
72 assert_eq!(rows_x.len(), rows_z.len(), "Gf2Matrix: row count mismatch");
73 for (row_x, row_z) in rows_x.iter_mut().zip(rows_z.iter_mut()) {
74 for w in 0..W {
75 let mask = word_mask(num_qubits, w);
76 row_x[w] &= mask;
77 row_z[w] &= mask;
78 }
79 }
80 Self { rows_x, rows_z, num_qubits }
81 }
82
83 pub(crate) fn num_rows(&self) -> usize {
84 self.rows_x.len()
85 }
86
87 pub(crate) fn num_qubits(&self) -> usize {
88 self.num_qubits
89 }
90
91 pub(crate) fn row(&self, i: usize) -> KeyRow<W> {
92 (self.rows_x[i], self.rows_z[i])
93 }
94
95 pub(crate) fn rows(&self) -> (&[[u64; W]], &[[u64; W]]) {
96 (&self.rows_x, &self.rows_z)
97 }
98
99 pub(crate) fn row_parity(&self, x: &[u64; W], z: &[u64; W], row: usize) -> u32 {
101 let row_x = &self.rows_x[row];
102 let row_z = &self.rows_z[row];
103 let mut folded: u64 = 0;
104 for w in 0..W {
105 folded ^= (x[w] & row_x[w]) ^ (z[w] & row_z[w]);
106 }
107 folded.count_ones() & 1
108 }
109
110 pub(crate) fn apply(&self, x: &[u64; W], z: &[u64; W], rows: usize) -> u32 {
112 let mut index: u32 = 0;
113 for i in 0..rows {
114 index |= self.row_parity(x, z, i) << i;
115 }
116 index
117 }
118
119 pub(crate) fn independent_with(&self, rows: usize, other: &Self, other_rows: usize) -> bool {
121 let stacked: Vec<KeyRow<W>> =
122 (0..rows).map(|i| self.row(i)).chain((0..other_rows).map(|i| other.row(i))).collect();
123 gf2_rank_wide(&stacked) == rows + other_rows
124 }
125}
126
127#[derive(Clone, Debug)]
132pub struct Gf2Hash<const W: usize> {
133 matrix: Gf2Matrix<W>,
135 bits: u8,
137 seed: u64,
139}
140
141impl<const W: usize> Gf2Hash<W> {
142 pub fn new(num_qubits: usize, bits: u8, seed: u64) -> Self {
150 assert!(bits <= B_MAX_BITS, "Gf2Hash: bits {bits} exceeds B_MAX_BITS {B_MAX_BITS}",);
151 debug_assert!(num_qubits <= 64 * W);
152
153 Self {
154 matrix: Gf2Matrix::draw(num_qubits, B_MAX_BITS as usize, seed, &[0; W], &[0; W]),
155 bits,
156 seed,
157 }
158 }
159
160 pub fn bits(&self) -> u8 {
162 self.bits
163 }
164
165 pub fn num_buckets(&self) -> usize {
167 1usize << self.bits
168 }
169
170 pub fn seed(&self) -> u64 {
172 self.seed
173 }
174
175 pub fn num_qubits(&self) -> usize {
177 self.matrix.num_qubits()
178 }
179
180 pub fn bucket_of(&self, x: &[u64; W], z: &[u64; W]) -> u32 {
182 self.matrix.apply(x, z, self.bits as usize)
183 }
184
185 pub(super) fn row_parity(&self, x: &[u64; W], z: &[u64; W], row: u8) -> u32 {
187 self.matrix.row_parity(x, z, row as usize)
188 }
189
190 pub fn refine(&mut self) {
196 assert!(self.bits < B_MAX_BITS, "Gf2Hash::refine: already at B_MAX_BITS {B_MAX_BITS}",);
197 self.bits += 1;
198 }
199
200 pub fn coarsen(&mut self) {
206 assert!(self.bits > 0, "Gf2Hash::coarsen: already at a single bucket");
207 self.bits -= 1;
208 }
209
210 pub(crate) fn same_rows_as(&self, other: &Self) -> bool {
212 self.seed == other.seed && self.num_qubits() == other.num_qubits()
213 }
214
215 #[cfg(any(test, feature = "cuda"))]
217 pub(crate) fn row(&self, i: usize) -> ([u64; W], [u64; W]) {
218 self.matrix.row(i)
219 }
220}
221
222#[derive(Clone, Debug, PartialEq, Eq)]
227pub struct PartitionRows<const W: usize> {
228 matrix: Gf2Matrix<W>,
230}
231
232impl<const W: usize> PartitionRows<W> {
233 pub(crate) fn assert_splits(&self, hash: &Gf2Hash<W>, num_qubits: usize, partitions: usize) {
235 assert_eq!(
236 self.num_partitions(),
237 partitions,
238 "partition rows name {} partitions for a split into {partitions}",
239 self.num_partitions(),
240 );
241 assert_eq!(
242 self.num_qubits(),
243 num_qubits,
244 "partition rows are for {} qubits, the sum for {num_qubits}",
245 self.num_qubits(),
246 );
247 debug_assert!(
248 self.is_independent_of(hash),
249 "partition rows are dependent on the bucket hash rows — the split will correlate \
250 with the bucket partition and load-balance badly",
251 );
252 }
253
254 pub fn from_seed(num_qubits: usize, bits: u8, seed: u64) -> Self {
260 Self::from_seed_excluding(num_qubits, bits, seed, &[0; W], &[0; W])
261 }
262
263 pub fn from_seed_excluding(
269 num_qubits: usize,
270 bits: u8,
271 seed: u64,
272 exclude_x: &[u64; W],
273 exclude_z: &[u64; W],
274 ) -> Self {
275 assert!(bits <= P_MAX_BITS, "PartitionRows: bits {bits} exceeds P_MAX_BITS {P_MAX_BITS}",);
276 debug_assert!(num_qubits <= 64 * W);
277
278 let keeps_a_column = (0..W).any(|w| {
279 let mask = word_mask(num_qubits, w);
280 (mask & !exclude_x[w]) | (mask & !exclude_z[w]) != 0
281 });
282 assert!(
283 bits == 0 || num_qubits == 0 || keeps_a_column,
284 "PartitionRows::from_seed_excluding: the exclusion covers every column",
285 );
286 Self {
287 matrix: Gf2Matrix::draw(
288 num_qubits,
289 bits as usize,
290 mix64(seed) ^ PARTITION_ROW_SALT,
291 exclude_x,
292 exclude_z,
293 ),
294 }
295 }
296
297 pub(crate) fn from_rows(
303 num_qubits: usize,
304 rows_x: Vec<[u64; W]>,
305 rows_z: Vec<[u64; W]>,
306 ) -> Self {
307 assert_eq!(
308 rows_x.len(),
309 rows_z.len(),
310 "PartitionRows::from_rows: row count mismatch, {} x-rows vs {} z-rows",
311 rows_x.len(),
312 rows_z.len(),
313 );
314 assert!(
315 rows_x.len() <= P_MAX_BITS as usize,
316 "PartitionRows: bits {} exceeds P_MAX_BITS {P_MAX_BITS}",
317 rows_x.len(),
318 );
319 debug_assert!(num_qubits <= 64 * W);
320
321 let matrix = Gf2Matrix::from_rows(num_qubits, rows_x, rows_z);
322 for i in 0..matrix.num_rows() {
323 let (row_x, row_z) = matrix.row(i);
324 assert!(
325 num_qubits == 0 || row_x.iter().chain(&row_z).any(|&w| w != 0),
326 "PartitionRows::from_rows: row {i} masks to zero",
327 );
328 }
329 Self { matrix }
330 }
331
332 pub fn cut(num_qubits: usize, blocks: &[Vec<usize>]) -> Self {
341 assert!(
342 blocks.len().is_power_of_two(),
343 "PartitionRows::cut: block count {} is not a power of two",
344 blocks.len(),
345 );
346 let bits = blocks.len().trailing_zeros() as u8;
347 assert!(bits <= P_MAX_BITS, "PartitionRows: bits {bits} exceeds P_MAX_BITS {P_MAX_BITS}",);
348 debug_assert!(num_qubits <= 64 * W);
349
350 let mut seen = vec![false; num_qubits];
351 let mut rows_z = vec![[0u64; W]; bits as usize];
352 for (b, qubits) in blocks.iter().enumerate() {
353 for &q in qubits {
354 assert!(
355 q < num_qubits,
356 "PartitionRows::cut: qubit {q} in block {b} is outside 0..{num_qubits}",
357 );
358 assert!(
359 !seen[q],
360 "PartitionRows::cut: blocks must be disjoint, qubit {q} appears twice",
361 );
362 seen[q] = true;
363 for (i, row) in rows_z.iter_mut().enumerate() {
364 if (b >> i) & 1 == 1 {
365 row[q / 64] |= 1u64 << (q % 64);
366 }
367 }
368 }
369 }
370 for (i, row) in rows_z.iter().enumerate() {
371 assert!(
372 row.iter().any(|w| *w != 0),
373 "PartitionRows::cut: row {i} is empty — no qubit lies in a block \
374 whose label has bit {i} set",
375 );
376 }
377
378 Self::from_rows(num_qubits, vec![[0u64; W]; bits as usize], rows_z)
379 }
380
381 pub fn none(num_qubits: usize) -> Self {
383 Self { matrix: Gf2Matrix::from_rows(num_qubits, Vec::new(), Vec::new()) }
384 }
385
386 pub fn bits(&self) -> u8 {
388 self.matrix.num_rows() as u8
389 }
390
391 pub fn num_partitions(&self) -> usize {
393 1usize << self.bits()
394 }
395
396 pub fn num_qubits(&self) -> usize {
398 self.matrix.num_qubits()
399 }
400
401 pub fn partition_of(&self, x: &[u64; W], z: &[u64; W]) -> u32 {
403 self.matrix.apply(x, z, self.matrix.num_rows())
404 }
405
406 pub fn rows(&self) -> (&[[u64; W]], &[[u64; W]]) {
408 self.matrix.rows()
409 }
410
411 pub fn avoids(&self, mask_x: &[u64; W], mask_z: &[u64; W]) -> bool {
413 let (rows_x, rows_z) = self.matrix.rows();
414 rows_x.iter().zip(rows_z).all(|(row_x, row_z)| {
415 (0..W).all(|w| row_x[w] & mask_x[w] == 0 && row_z[w] & mask_z[w] == 0)
416 })
417 }
418
419 pub fn is_independent_of(&self, hash: &Gf2Hash<W>) -> bool {
421 self.matrix.independent_with(self.matrix.num_rows(), &hash.matrix, hash.bits() as usize)
422 }
423}
424
425type KeyRow<const W: usize> = ([u64; W], [u64; W]);
427
428fn gf2_rank_wide<const W: usize>(rows: &[KeyRow<W>]) -> usize {
430 let mut pivots: Vec<(usize, KeyRow<W>)> = Vec::with_capacity(rows.len());
431 for row in rows {
432 let mut reduced = *row;
433 'reduce: loop {
434 let Some(lead) = leading_column(&reduced) else {
435 break;
436 };
437 for (pivot_column, pivot_row) in pivots.iter() {
438 if *pivot_column == lead {
439 for w in 0..W {
440 reduced.0[w] ^= pivot_row.0[w];
441 reduced.1[w] ^= pivot_row.1[w];
442 }
443 continue 'reduce;
444 }
445 }
446 pivots.push((lead, reduced));
447 break;
448 }
449 }
450 pivots.len()
451}
452
453fn leading_column<const W: usize>(row: &KeyRow<W>) -> Option<usize> {
455 for w in (0..W).rev() {
456 if row.1[w] != 0 {
457 return Some(W * 64 + w * 64 + (63 - row.1[w].leading_zeros() as usize));
458 }
459 }
460 for w in (0..W).rev() {
461 if row.0[w] != 0 {
462 return Some(w * 64 + (63 - row.0[w].leading_zeros() as usize));
463 }
464 }
465 None
466}
467
468#[cfg(test)]
469mod tests;