use std::ops::Range;
use dspk_core::MemoryOrder;
use rayon::prelude::*;
pub const EMPTY: [f32; 2] = [f32::INFINITY, f32::NEG_INFINITY];
const LANES: usize = 8;
#[inline]
pub fn fold(samples: &[f32], acc: [f32; 2]) -> [f32; 2] {
let mut lo = [acc[0]; LANES];
let mut hi = [acc[1]; LANES];
let mut chunks = samples.chunks_exact(LANES);
for c in &mut chunks {
for i in 0..LANES {
lo[i] = if c[i] < lo[i] { c[i] } else { lo[i] };
hi[i] = if c[i] > hi[i] { c[i] } else { hi[i] };
}
}
let mut out = acc;
for i in 0..LANES {
out = merge(out, [lo[i], hi[i]]);
}
for &v in chunks.remainder() {
out = merge(out, [v, v]);
}
out
}
#[inline]
pub fn merge(a: [f32; 2], b: [f32; 2]) -> [f32; 2] {
[if b[0] < a[0] { b[0] } else { a[0] }, if b[1] > a[1] { b[1] } else { a[1] }]
}
pub fn finish(acc: &mut [[f32; 2]]) {
for v in acc.iter_mut().filter(|v| v[0] > v[1]) {
*v = [f32::NAN, f32::NAN];
}
}
pub fn mean_range(env: &[[f32; 2]], rows: usize, width: usize) -> Vec<f32> {
(0..width)
.map(|x| {
let (sum, n) = (0..rows).map(|r| env[r * width + x]).filter(|v| v[0].is_finite() && v[1].is_finite()).fold((0.0f64, 0usize), |(s, n), v| (s + (v[1] - v[0]) as f64, n + 1));
if n == 0 { f32::NAN } else { (sum / n as f64) as f32 }
})
.collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Columns {
Even { start: u64, len: u64, width: usize },
Buckets { origin: u64, size: u64, count: usize },
}
impl Columns {
pub fn count(&self) -> usize {
match *self {
Columns::Even { width, .. } => width,
Columns::Buckets { count, .. } => count,
}
}
pub fn start(&self, x: usize) -> u64 {
match *self {
Columns::Even { start, len, width } => start + x as u64 * len / width as u64,
Columns::Buckets { origin, size, .. } => origin + x as u64 * size,
}
}
pub fn end(&self) -> u64 {
match *self {
Columns::Even { start, len, .. } => start + len,
Columns::Buckets { origin, size, count } => origin + count as u64 * size,
}
}
pub fn column_of(&self, s: u64) -> usize {
match *self {
Columns::Even { start, len, width } => {
if s < start || len == 0 {
0
} else {
(((s - start + 1) * width as u64 - 1) / len).min(width as u64 - 1) as usize
}
}
Columns::Buckets { origin, size, count } => (s.saturating_sub(origin) / size).min(count as u64 - 1) as usize,
}
}
fn next_start(&self, x: usize) -> u64 {
if x + 1 < self.count() { self.start(x + 1) } else { u64::MAX }
}
}
#[derive(Debug, Clone, Copy)]
pub struct Block<'a> {
pub data: &'a [f32],
pub order: MemoryOrder,
pub channels: usize,
pub samples: usize,
pub first: u64,
}
pub fn fold_block(block: &Block, range: Range<usize>, columns: Columns, acc: &mut [[f32; 2]]) {
let count = columns.count();
if block.channels == 0 || count == 0 || range.is_empty() {
return;
}
debug_assert!(acc.len() >= block.channels * count && range.end <= block.samples);
let first = block.first + range.start as u64;
match block.order {
MemoryOrder::ChannelMajor => {
acc.par_chunks_mut(count).zip(block.data.par_chunks(block.samples)).take(block.channels).for_each(|(acc, row)| {
fold_row(&row[range.clone()], first, columns, acc);
});
}
MemoryOrder::TimeMajor => {
let frames = &block.data[range.start * block.channels..range.end * block.channels];
fold_frames(frames, block.channels, first, columns, acc);
}
}
}
pub fn fold_row(row: &[f32], first: u64, columns: Columns, acc: &mut [[f32; 2]]) {
let end = first + row.len() as u64;
let (mut s, mut x) = (first, columns.column_of(first));
while s < end {
let next = columns.next_start(x).min(end);
if next > s {
acc[x] = fold(&row[(s - first) as usize..(next - first) as usize], acc[x]);
s = next;
}
x += 1;
}
}
fn fold_frames(frames: &[f32], channels: usize, first: u64, columns: Columns, acc: &mut [[f32; 2]]) {
let n = frames.len() / channels;
let end = first + n as u64;
let (x0, x1) = (columns.column_of(first), columns.column_of(end - 1) + 1);
let tasks = rayon::current_num_threads().min(x1 - x0).max(1);
let parts: Vec<(usize, Vec<f32>, Vec<f32>)> = (0..tasks)
.into_par_iter()
.map(|k| {
let (xa, xb) = (x0 + k * (x1 - x0) / tasks, x0 + (k + 1) * (x1 - x0) / tasks);
let s0 = if xa == x0 { first } else { columns.start(xa) };
let s1 = if xb == x1 { end } else { columns.start(xb) };
let mut lo = vec![f32::INFINITY; (xb - xa) * channels];
let mut hi = vec![f32::NEG_INFINITY; (xb - xa) * channels];
let (mut x, mut next) = (xa, columns.next_start(xa));
for s in s0..s1 {
while s >= next {
x += 1;
next = columns.next_start(x);
}
let frame = &frames[(s - first) as usize * channels..][..channels];
let l = &mut lo[(x - xa) * channels..][..channels];
let h = &mut hi[(x - xa) * channels..][..channels];
for ((&v, l), h) in frame.iter().zip(l.iter_mut()).zip(h.iter_mut()) {
*l = if v < *l { v } else { *l };
*h = if v > *h { v } else { *h };
}
}
(xa, lo, hi)
})
.collect();
let count = columns.count();
for (xa, lo, hi) in parts {
for (i, (l, h)) in lo.chunks_exact(channels).zip(hi.chunks_exact(channels)).enumerate() {
for c in 0..channels {
let a = &mut acc[c * count + xa + i];
*a = merge(*a, [l[c], h[c]]);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mean_range() {
let nan = [f32::NAN, f32::NAN];
let env = [[0.0, 2.0], [1.0, 2.0], nan, [0.0, 4.0], nan, nan];
let m = mean_range(&env, 2, 3);
assert_eq!(m[0], 3.0);
assert_eq!(m[1], 1.0);
assert!(m[2].is_nan());
}
fn brute(row: &[f32]) -> [f32; 2] {
row.iter().fold(EMPTY, |[a, b], &v| [a.min(v), b.max(v)])
}
#[test]
fn test_fold_matches_brute_force_and_skips_nan() {
let v: Vec<f32> = (0..1001).map(|i| ((i * 7919) % 2003) as f32 - 1000.0).collect();
for len in [0, 1, 7, 8, 9, 100, 1001] {
assert_eq!(fold(&v[..len], EMPTY), brute(&v[..len]), "len {len}");
}
assert_eq!(fold(&[3.0, f32::NAN, -2.0], EMPTY), [-2.0, 3.0]);
}
#[test]
fn test_columns_map_samples_both_ways() {
let even = Columns::Even { start: 100, len: 1000, width: 7 };
for s in 100..1100u64 {
let x = even.column_of(s);
assert!(even.start(x) <= s && s < even.start(x + 1), "sample {s} in column {x}");
}
let buckets = Columns::Buckets { origin: 64, size: 10, count: 5 };
assert_eq!((buckets.column_of(64), buckets.column_of(73), buckets.column_of(74), buckets.column_of(500)), (0, 0, 1, 4));
}
#[test]
fn test_fold_block_matches_brute_force_in_both_orders() {
let (channels, samples) = (5usize, 3001usize);
let value = |c: usize, t: usize| ((t * 7919 + c * 104_729) % 2003) as f32 - 1000.0;
let cm: Vec<f32> = (0..channels).flat_map(|c| (0..samples).map(move |t| value(c, t))).collect();
let tm: Vec<f32> = (0..samples).flat_map(|t| (0..channels).map(move |c| value(c, t))).collect();
for columns in [Columns::Even { start: 1000, len: samples as u64, width: 97 }, Columns::Buckets { origin: 1000, size: 64, count: samples.div_ceil(64) }] {
let count = columns.count();
let mut want = vec![EMPTY; channels * count];
for c in 0..channels {
for x in 0..count {
let (a, b) = ((columns.start(x) - 1000) as usize, ((if x + 1 < count { columns.start(x + 1) } else { 1000 + samples as u64 }) - 1000) as usize);
want[c * count + x] = brute(&cm[c * samples + a..c * samples + b.min(samples)]);
}
}
for (data, order) in [(&cm, MemoryOrder::ChannelMajor), (&tm, MemoryOrder::TimeMajor)] {
let block = Block { data, order, channels, samples, first: 1000 };
let mut acc = vec![EMPTY; channels * count];
for range in [0..1234, 1234..1300, 1300..samples] {
fold_block(&block, range, columns, &mut acc);
}
assert_eq!(acc, want, "{order:?} {columns:?}");
}
}
}
}