use num_complex::Complex64;
use crate::channel::prepared::LocalPtm;
use crate::pauli_sum::storage::{BucketColumns, ColumnsRef};
use crate::truncation::TruncationPolicy;
#[derive(Clone, Debug, Default)]
pub(crate) struct SortScratch<const W: usize> {
permutation: Vec<u32>,
packed: Vec<u64>,
spare: Vec<u64>,
staging_x: Vec<[u64; W]>,
staging_z: Vec<[u64; W]>,
staging_coefficient: Vec<Complex64>,
}
fn sort_by_comparison<const W: usize>(
columns: &mut BucketColumns<W>,
scratch: &mut SortScratch<W>,
) {
let BucketColumns { x, z, coeff: c } = columns;
let len = x.len();
debug_assert_eq!(len, z.len());
debug_assert_eq!(len, c.len());
assert!(len <= u32::MAX as usize, "sort: run of {len} rows exceeds u32 row indices");
if len < 2 {
return;
}
scratch.permutation.clear();
scratch.permutation.extend(0..len as u32);
scratch.permutation.sort_by(|&a, &b| {
x[a as usize].cmp(&x[b as usize]).then_with(|| z[a as usize].cmp(&z[b as usize]))
});
scratch.staging_x.clear();
scratch.staging_x.extend(scratch.permutation.iter().map(|&i| x[i as usize]));
scratch.staging_z.clear();
scratch.staging_z.extend(scratch.permutation.iter().map(|&i| z[i as usize]));
scratch.staging_coefficient.clear();
scratch.staging_coefficient.extend(scratch.permutation.iter().map(|&i| c[i as usize]));
std::mem::swap(x, &mut scratch.staging_x);
std::mem::swap(z, &mut scratch.staging_z);
std::mem::swap(c, &mut scratch.staging_coefficient);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum SortKernel {
Comparison,
Radix,
}
impl SortKernel {
pub(crate) fn for_layer(rest_streams: usize, rows_per_key: impl FnOnce() -> f64) -> Self {
if rest_streams >= RADIX_MIN_REST_STREAMS
|| (rest_streams >= RADIX_MIN_DISJOINT_STREAMS
&& rows_per_key() < RADIX_MAX_REST_ROWS_PER_KEY)
{
Self::Radix
} else {
Self::Comparison
}
}
pub(crate) fn sort<const W: usize>(
self,
columns: &mut BucketColumns<W>,
scratch: &mut SortScratch<W>,
) {
match self {
Self::Comparison => sort_by_comparison(columns, scratch),
Self::Radix => sort_by_radix(columns, scratch),
}
}
}
const RADIX_MIN_REST_STREAMS: usize = 8;
const RADIX_MAX_REST_ROWS_PER_KEY: f64 = 2.0;
const RADIX_MIN_DISJOINT_STREAMS: usize = 3;
pub(crate) fn rest_rows_per_key<const W: usize>(ptm: &LocalPtm<W>) -> f64 {
let zero = Complex64::new(0.0, 0.0);
let rest_start = usize::from(ptm.has_identity());
let dim = 1usize << (2 * ptm.k());
let (mut rows, mut keys) = (0u32, 0u32);
for output in 0..dim {
let rows_here = ptm.deltas()[rest_start..]
.iter()
.filter(|delta| delta.amplitude[output ^ delta.local_delta as usize] != zero)
.count() as u32;
rows += rows_here;
keys += u32::from(rows_here > 0);
}
if keys == 0 {
0.0
} else {
f64::from(rows) / f64::from(keys)
}
}
const RADIX_SURROGATE_BITS: u32 = 16;
const RADIX_DIGIT_BITS: u32 = 8;
const RADIX_BUCKETS: usize = 1 << RADIX_DIGIT_BITS;
const RADIX_MIN_WINDOW_BITS: u32 = 8;
fn key_word<const W: usize>(x: &[[u64; W]], z: &[[u64; W]], word: usize, row: usize) -> u64 {
if word < W {
x[row][word]
} else {
z[row][word - W]
}
}
fn discriminating_window<const W: usize>(
x: &[[u64; W]],
z: &[[u64; W]],
) -> Option<(usize, u32, u64)> {
let len = x.len();
for word in 0..2 * W {
let mut any = 0u64;
let mut all = !0u64;
for row in 0..len {
let value = key_word(x, z, word, row);
any |= value;
all &= value;
}
let diff = any & !all;
if diff != 0 {
return Some((word, 63 - diff.leading_zeros(), diff));
}
}
None
}
fn sort_by_radix<const W: usize>(columns: &mut BucketColumns<W>, scratch: &mut SortScratch<W>) {
let BucketColumns { x, z, coeff: c } = columns;
let len = x.len();
debug_assert_eq!(len, z.len());
debug_assert_eq!(len, c.len());
assert!(len <= u32::MAX as usize, "sort: run of {len} rows exceeds u32 row indices");
if len < 2 {
return;
}
let Some((word, high_bit, diff)) = discriminating_window(x, z) else {
return;
};
let shift = (high_bit + 1).saturating_sub(RADIX_SURROGATE_BITS);
let mask = (1u64 << RADIX_SURROGATE_BITS) - 1;
if ((diff >> shift) & mask).count_ones() < RADIX_MIN_WINDOW_BITS {
sort_by_comparison(columns, scratch);
return;
}
let SortScratch { packed, spare, staging_x, staging_z, staging_coefficient, .. } = scratch;
packed.clear();
packed.extend((0..len).map(|i| (((key_word(x, z, word, i) >> shift) & mask) << 32) | i as u64));
spare.resize(len, 0);
let mut digit = 0u32;
while digit * RADIX_DIGIT_BITS < RADIX_SURROGATE_BITS {
let digit_shift = 32 + digit * RADIX_DIGIT_BITS;
let mut count = [0u32; RADIX_BUCKETS + 1];
for &record in packed.iter() {
count[(((record >> digit_shift) as usize) & (RADIX_BUCKETS - 1)) + 1] += 1;
}
if count[1..].iter().filter(|&&tally| tally != 0).count() > 1 {
for slot in 1..=RADIX_BUCKETS {
count[slot] += count[slot - 1];
}
for &record in packed.iter() {
let bucket = ((record >> digit_shift) as usize) & (RADIX_BUCKETS - 1);
spare[count[bucket] as usize] = record;
count[bucket] += 1;
}
std::mem::swap(packed, spare);
}
digit += 1;
}
let mut i = 0usize;
while i < len {
let surrogate = packed[i] >> 32;
let mut j = i + 1;
while j < len && packed[j] >> 32 == surrogate {
j += 1;
}
if j - i > 1 {
packed[i..j].sort_by(|&left, &right| {
let (row_a, row_b) = (left as u32 as usize, right as u32 as usize);
x[row_a].cmp(&x[row_b]).then_with(|| z[row_a].cmp(&z[row_b]))
});
}
i = j;
}
staging_x.clear();
staging_x.extend(packed.iter().map(|&record| x[record as u32 as usize]));
staging_z.clear();
staging_z.extend(packed.iter().map(|&record| z[record as u32 as usize]));
staging_coefficient.clear();
staging_coefficient.extend(packed.iter().map(|&record| c[record as u32 as usize]));
std::mem::swap(x, staging_x);
std::mem::swap(z, staging_z);
std::mem::swap(c, staging_coefficient);
}
pub(crate) fn merge_identity_and_rest<const W: usize, T: TruncationPolicy<W> + ?Sized>(
identity: ColumnsRef<'_, W>,
rest: ColumnsRef<'_, W>,
out: &mut BucketColumns<W>,
policy: &T,
) {
let ColumnsRef { x: a_x, z: a_z, coeff: a_c } = identity;
let ColumnsRef { x: b_x, z: b_z, coeff: b_c } = rest;
let (a_len, b_len) = (a_c.len(), b_c.len());
debug_assert_eq!(a_len, a_x.len());
debug_assert_eq!(a_len, a_z.len());
debug_assert_eq!(b_len, b_x.len());
debug_assert_eq!(b_len, b_z.len());
let (mut i, mut j) = (0usize, 0usize);
while i < a_len && j < b_len {
let take_a = (a_x[i], a_z[i]) <= (b_x[j], b_z[j]);
let (key_x, key_z, mut sum) = if take_a {
debug_assert!(
i == 0 || (a_x[i - 1], a_z[i - 1]) < (a_x[i], a_z[i]),
"merge_identity_and_rest: identity stream must be strictly ascending at {i}",
);
let t = (a_x[i], a_z[i], a_c[i]);
i += 1;
t
} else {
debug_assert!(
j == 0 || (b_x[j - 1], b_z[j - 1]) <= (b_x[j], b_z[j]),
"merge_identity_and_rest: rest stream must be sorted at {j}",
);
let t = (b_x[j], b_z[j], b_c[j]);
j += 1;
t
};
while j < b_len && b_x[j] == key_x && b_z[j] == key_z {
sum += b_c[j];
j += 1;
}
emit_if_kept(out, policy, key_x, key_z, sum);
}
while i < a_len {
let (key_x, key_z, sum) = (a_x[i], a_z[i], a_c[i]);
i += 1;
emit_if_kept(out, policy, key_x, key_z, sum);
}
while j < b_len {
let (key_x, key_z) = (b_x[j], b_z[j]);
let mut sum = b_c[j];
j += 1;
while j < b_len && b_x[j] == key_x && b_z[j] == key_z {
sum += b_c[j];
j += 1;
}
emit_if_kept(out, policy, key_x, key_z, sum);
}
}
#[inline(always)]
fn emit_if_kept<const W: usize, T: TruncationPolicy<W> + ?Sized>(
out: &mut BucketColumns<W>,
policy: &T,
key_x: [u64; W],
key_z: [u64; W],
sum: Complex64,
) {
if sum != Complex64::new(0.0, 0.0) && policy.keep_term(&key_x, &key_z, sum) {
out.x.push(key_x);
out.z.push(key_z);
out.coeff.push(sum);
}
}
#[cfg(test)]
mod tests;