use rayon::prelude::{IntoParallelIterator, ParallelIterator, ParallelSlice};
use std::ops::Range;
pub const DET_REDUCE_BLOCK: usize = 16_384;
pub fn det_reduce<T, A, F, M>(
slice: &[T],
parallel: bool,
fold_block: F,
merge: M,
identity: A,
) -> A
where
T: Sync,
A: Send,
F: Fn(&[T]) -> A + Sync + Send,
M: Fn(A, A) -> A,
{
if parallel {
let parts: Vec<A> = slice.par_chunks(DET_REDUCE_BLOCK).map(fold_block).collect();
parts.into_iter().fold(identity, merge)
} else {
slice
.chunks(DET_REDUCE_BLOCK)
.map(fold_block)
.fold(identity, merge)
}
}
pub fn det_reduce_range<A, F, M>(
n: usize,
parallel: bool,
fold_block: F,
merge: M,
identity: A,
) -> A
where
A: Send,
F: Fn(Range<usize>) -> A + Sync + Send,
M: Fn(A, A) -> A,
{
let n_blocks = n.div_ceil(DET_REDUCE_BLOCK);
let block_range = |b: usize| {
let start = b * DET_REDUCE_BLOCK;
start..(start + DET_REDUCE_BLOCK).min(n)
};
if parallel {
let parts: Vec<A> = (0..n_blocks)
.into_par_iter()
.map(|b| fold_block(block_range(b)))
.collect();
parts.into_iter().fold(identity, merge)
} else {
(0..n_blocks)
.map(|b| fold_block(block_range(b)))
.fold(identity, merge)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parallel_flag_does_not_change_bits() {
for len in [0usize, 1, 100, DET_REDUCE_BLOCK, DET_REDUCE_BLOCK * 3 + 17] {
let data: Vec<f64> = (0..len).map(|i| (i as f64).sin()).collect();
let sum = |block: &[f64]| block.iter().sum::<f64>();
let par = det_reduce(&data, true, sum, |a, b| a + b, 0.0);
let ser = det_reduce(&data, false, sum, |a, b| a + b, 0.0);
assert!(
par == ser,
"det_reduce: flag changed the bits (len {len}): {par} vs {ser}"
);
let sq = |range: Range<usize>| range.map(|i| data[i] * data[i]).sum::<f64>();
let par = det_reduce_range(len, true, sq, |a, b| a + b, 0.0);
let ser = det_reduce_range(len, false, sq, |a, b| a + b, 0.0);
assert!(
par == ser,
"det_reduce_range: flag changed the bits (len {len}): {par} vs {ser}"
);
}
}
#[test]
fn det_reduce_matches_serial_blocked_fold() {
for len in [0usize, 1, 100, DET_REDUCE_BLOCK, DET_REDUCE_BLOCK * 3 + 17] {
let data: Vec<f64> = (0..len).map(|i| (i as f64).sin()).collect();
let reference = data
.chunks(DET_REDUCE_BLOCK)
.map(|block| block.iter().sum::<f64>())
.fold(0.0, |a, b| a + b);
for parallel in [false, true] {
let got = det_reduce(
&data,
parallel,
|block| block.iter().sum::<f64>(),
|a, b| a + b,
0.0,
);
assert!(
got == reference,
"det_reduce (parallel={parallel}) must match the serial blocked fold \
(len {len}): {got} vs {reference}"
);
}
}
}
#[test]
fn det_reduce_range_matches_serial_blocked_fold() {
for len in [0usize, 1, 100, DET_REDUCE_BLOCK, DET_REDUCE_BLOCK * 3 + 17] {
let data: Vec<f64> = (0..len).map(|i| (i as f64).cos()).collect();
let reference = data
.chunks(DET_REDUCE_BLOCK)
.map(|block| block.iter().map(|v| v * v).sum::<f64>())
.fold(0.0, |a, b| a + b);
for parallel in [false, true] {
let got = det_reduce_range(
len,
parallel,
|range| range.map(|i| data[i] * data[i]).sum::<f64>(),
|a, b| a + b,
0.0,
);
assert!(
got == reference,
"det_reduce_range (parallel={parallel}) must match the serial blocked \
fold (len {len}): {got} vs {reference}"
);
}
}
}
#[test]
fn det_reduce_range_compound_accumulator() {
let n = DET_REDUCE_BLOCK * 2 + 333;
let values: Vec<f64> = (0..n).map(|i| ((i as f64) * 0.37).sin()).collect();
let buckets: Vec<usize> = (0..n).map(|i| i % 4).collect();
let (par_sums, par_counts) = det_reduce_range(
n,
true,
|range| {
let mut sums = [0.0f64; 4];
let mut counts = [0usize; 4];
for i in range {
sums[buckets[i]] += values[i];
counts[buckets[i]] += 1;
}
(sums, counts)
},
|(mut sa, mut ca), (sb, cb)| {
for k in 0..4 {
sa[k] += sb[k];
ca[k] += cb[k];
}
(sa, ca)
},
([0.0; 4], [0; 4]),
);
let mut serial_sums = [0.0f64; 4];
let mut serial_counts = [0usize; 4];
for block in (0..n).collect::<Vec<_>>().chunks(DET_REDUCE_BLOCK) {
let mut sums = [0.0f64; 4];
for &i in block {
sums[buckets[i]] += values[i];
serial_counts[buckets[i]] += 1;
}
for k in 0..4 {
serial_sums[k] += sums[k];
}
}
assert_eq!(par_counts, serial_counts);
assert!(
par_sums == serial_sums,
"compound range fold must match the serial blocked scatter: {par_sums:?} vs {serial_sums:?}"
);
}
}