use std::sync::Mutex;
use num_complex::Complex64;
use rayon::prelude::*;
use super::coset::{CosetOrder, Gf2Span};
use super::merge::{rest_rows_per_key, SortKernel};
use super::trace::PartitionTrace;
use crate::channel::prepared::{LocalPtm, Prepared, PreparedRotation};
use crate::pauli_sum::storage::{BucketColumns, PauliSum};
use crate::truncation::TruncationPolicy;
#[cfg(feature = "phase-timing")]
use super::stats::{PartitionPhaseStats, PhaseStats};
use super::timing::{lap, stamp};
mod coset_fill;
pub(super) use coset_fill::{fill_coset, CosetScratch, MIN_COSETS_FOR_PARALLEL};
const ZERO: Complex64 = Complex64::new(0.0, 0.0);
#[derive(Debug, Default)]
pub struct LayerScratch<const W: usize> {
pub(super) task: CosetScratch<W>,
pub(super) order: CosetOrder,
pub(super) staging: Vec<BucketColumns<W>>,
pub(super) workers: Vec<Mutex<CosetScratch<W>>>,
#[cfg(feature = "phase-timing")]
pub(crate) stats: PhaseStats,
pub(crate) trace: Option<PartitionTrace>,
}
impl<const W: usize> LayerScratch<W> {
pub fn new() -> Self {
Self::default()
}
#[cfg(feature = "phase-timing")]
pub fn take_stats(&mut self) -> PartitionPhaseStats {
let stats = self.take_phase_stats();
PartitionPhaseStats {
layers: stats.layers,
per_partition: vec![stats],
scatter_ns: 0,
gather_ns: 0,
}
}
#[cfg(feature = "phase-timing")]
pub(crate) fn take_phase_stats(&mut self) -> PhaseStats {
let mut total = std::mem::take(&mut self.stats);
total.absorb_coset(&std::mem::take(&mut self.task.stats));
for slot in &self.workers {
let mut worker_scratch = slot.lock().unwrap();
total.absorb_coset(&std::mem::take(&mut worker_scratch.stats));
}
total
}
pub fn enable_trace(&mut self) {
self.trace.get_or_insert_with(PartitionTrace::default);
}
pub fn take_trace(&mut self) -> Option<PartitionTrace> {
self.trace.as_mut().map(std::mem::take)
}
}
pub(super) enum DeltaPlan<'p, const W: usize> {
Local {
ptm: &'p LocalPtm<W>,
coordinates: Vec<u32>,
has_identity: bool,
dense_identity: bool,
sort_kernel: SortKernel,
},
Rotation {
rotation: &'p PreparedRotation<W>,
coordinate_identity: u32,
generator_coordinate: u32,
generator_local: bool,
},
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct LayerKnobs<'k> {
pub bucket_deltas: Option<&'k [u32]>,
pub rest_streams: Option<usize>,
pub rows_per_key: Option<f64>,
pub generator_local: bool,
pub order: Option<&'k CosetOrder>,
}
impl Default for LayerKnobs<'_> {
fn default() -> Self {
Self {
bucket_deltas: None,
rest_streams: None,
rows_per_key: None,
generator_local: true,
order: None,
}
}
}
impl<'p, const W: usize> DeltaPlan<'p, W> {
pub(super) fn new(prepared: &'p Prepared<W>, span: &Gf2Span, knobs: LayerKnobs<'_>) -> Self {
match prepared {
Prepared::Local(ptm) => {
let coordinates: Vec<u32> =
ptm.deltas().iter().map(|d| span.coordinate_of(d.bucket_delta)).collect();
let has_identity = ptm.has_identity();
debug_assert!(!has_identity || coordinates[0] == 0);
let dense_identity = has_identity && ptm.entry_is_dense(0);
let rest_streams = knobs.rest_streams.unwrap_or(ptm.rest_streams());
let sort_kernel = SortKernel::for_layer(rest_streams, || {
knobs.rows_per_key.unwrap_or_else(|| rest_rows_per_key(ptm))
});
DeltaPlan::Local { ptm, coordinates, has_identity, dense_identity, sort_kernel }
}
Prepared::Rotation(rotation) => DeltaPlan::Rotation {
rotation,
coordinate_identity: span.coordinate_of(rotation.bucket_delta_identity),
generator_coordinate: if knobs.generator_local {
span.coordinate_of(rotation.bucket_delta_generator)
} else {
0
},
generator_local: knobs.generator_local,
},
}
}
}
pub(crate) trait ExtraRows<const W: usize> {
const NEEDS_BETA: bool = false;
fn count(&self, beta: u32) -> usize {
let _ = beta;
0
}
fn append_into(&self, beta: u32, rest: &mut BucketColumns<W>) {
let _ = (beta, rest);
}
}
pub(crate) struct NoExtra;
impl<const W: usize> ExtraRows<W> for NoExtra {}
pub fn apply_layer_bucketed<const W: usize, T>(
sum: &mut PauliSum<W>,
prepared: &Prepared<W>,
policy: &T,
scratch: &mut LayerScratch<W>,
) where
T: TruncationPolicy<W> + ?Sized,
{
apply_layer_bucketed_with(sum, prepared, policy, scratch, &NoExtra, LayerKnobs::default())
}
pub(crate) fn apply_layer_bucketed_with<const W: usize, T, X>(
sum: &mut PauliSum<W>,
prepared: &Prepared<W>,
policy: &T,
scratch: &mut LayerScratch<W>,
extra: &X,
knobs: LayerKnobs<'_>,
) where
T: TruncationPolicy<W> + ?Sized,
X: ExtraRows<W> + Sync,
{
stamp!(stamp);
if let Prepared::Local(ptm) = prepared {
if !X::NEEDS_BETA && ptm.is_key_preserving() {
rescale_in_place(sum, ptm, policy);
lap!(stamp, scratch.stats.rescale_ns);
return;
}
}
let own_deltas;
let deltas: &[u32] = match knobs.bucket_deltas {
Some(deltas) => deltas,
None => {
own_deltas = prepared.bucket_deltas();
&own_deltas
}
};
let span = Gf2Span::new(deltas, sum.hash().bits());
let plan = DeltaPlan::new(prepared, &span, knobs);
let coset_size = span.coset_size();
let num_cosets = span.num_cosets();
lap!(stamp, scratch.stats.span_plan_ns);
let order: &CosetOrder = match knobs.order {
Some(order) => order,
None => {
scratch.order.rebuild(&span, X::NEEDS_BETA);
&scratch.order
}
};
debug_assert_eq!(order.positions(), sum.num_buckets());
debug_assert_eq!(order.dimension(), span.dimension());
let identity_order = order.is_identity();
if !identity_order {
let buckets = sum.buckets_mut();
scratch.staging.resize_with(buckets.len(), BucketColumns::default);
for (beta, columns) in buckets.iter_mut().enumerate() {
scratch.staging[order.position_of(beta as u32) as usize] = std::mem::take(columns);
}
}
lap!(stamp, scratch.stats.permute_ns);
{
if num_cosets >= MIN_COSETS_FOR_PARALLEL {
let pool = rayon::current_num_threads().max(1);
if scratch.workers.len() < pool {
scratch.workers.resize_with(pool, Mutex::default);
}
}
let workers = &scratch.workers;
let chunks: &mut [BucketColumns<W>] =
if identity_order { sum.buckets_mut() } else { scratch.staging.as_mut_slice() };
if num_cosets < MIN_COSETS_FOR_PARALLEL {
for (coset_index, chunk) in chunks.chunks_mut(coset_size).enumerate() {
let base = coset_index * coset_size;
fill_coset::<W, T, X>(chunk, &plan, policy, &mut scratch.task, extra, base, order);
}
} else {
chunks.par_chunks_mut(coset_size).enumerate().for_each(|(coset_index, chunk)| {
let base = coset_index * coset_size;
let worker = rayon::current_thread_index()
.expect("the coset loop runs on a Rayon pool worker");
let mut worker_scratch = workers[worker].lock().unwrap();
fill_coset::<W, T, X>(
chunk,
&plan,
policy,
&mut worker_scratch,
extra,
base,
order,
);
});
}
}
lap!(stamp, scratch.stats.coset_loop_ns);
if !identity_order {
let buckets = sum.buckets_mut();
for (beta, columns) in buckets.iter_mut().enumerate() {
*columns =
std::mem::take(&mut scratch.staging[order.position_of(beta as u32) as usize]);
}
}
lap!(stamp, scratch.stats.unpermute_ns);
sum.recount();
lap!(stamp, scratch.stats.recount_ns);
#[cfg(debug_assertions)]
sum.assert_invariants();
}
fn rescale_in_place<const W: usize, T>(sum: &mut PauliSum<W>, ptm: &LocalPtm<W>, policy: &T)
where
T: TruncationPolicy<W> + ?Sized,
{
let amplitude = &ptm.deltas()[0].amplitude;
sum.update_per_bucket(|_| {
|x: &[u64; W], z: &[u64; W], c: Complex64| {
let coeff = c * amplitude[ptm.support_bits(x, z)];
(coeff != ZERO && policy.keep_term(x, z, coeff)).then_some(coeff)
}
});
#[cfg(debug_assertions)]
sum.assert_invariants();
}
#[cfg(test)]
mod tests;