use crate::math::reduction::DET_REDUCE_BLOCK;
use crate::neural_network::Tensor;
use ndarray::Array1;
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
use rayon::slice::ParallelSlice;
use std::ops::Range;
pub(super) fn segment_sum(seg: &[f32], scale: f32) -> f32 {
let mut lanes = [0.0f32; 8];
let mut chunks = seg.chunks_exact(8);
for ch in chunks.by_ref() {
for (l, &v) in lanes.iter_mut().zip(ch) {
*l += v * scale;
}
}
let mut tail = 0.0f32;
for &v in chunks.remainder() {
tail += v * scale;
}
((lanes[0] + lanes[1]) + (lanes[2] + lanes[3]))
+ ((lanes[4] + lanes[5]) + (lanes[6] + lanes[7]))
+ tail
}
pub(super) fn segment_dot(a: &[f32], b: &[f32], scale: f32) -> f32 {
let mut lanes = [0.0f32; 8];
let mut chunks_a = a.chunks_exact(8);
let mut chunks_b = b.chunks_exact(8);
for (ca, cb) in chunks_a.by_ref().zip(chunks_b.by_ref()) {
for ((l, &va), &vb) in lanes.iter_mut().zip(ca).zip(cb) {
*l += va * vb * scale;
}
}
let mut tail = 0.0f32;
for (&va, &vb) in chunks_a.remainder().iter().zip(chunks_b.remainder()) {
tail += va * vb * scale;
}
((lanes[0] + lanes[1]) + (lanes[2] + lanes[3]))
+ ((lanes[4] + lanes[5]) + (lanes[6] + lanes[7]))
+ tail
}
pub(super) fn segment_sq_dev(seg: &[f32], mean: f32) -> f32 {
let mut lanes = [0.0f32; 8];
let mut chunks = seg.chunks_exact(8);
for ch in chunks.by_ref() {
for (l, &v) in lanes.iter_mut().zip(ch) {
let d = v - mean;
*l += d * d;
}
}
let mut tail = 0.0f32;
for &v in chunks.remainder() {
let d = v - mean;
tail += d * d;
}
((lanes[0] + lanes[1]) + (lanes[2] + lanes[3]))
+ ((lanes[4] + lanes[5]) + (lanes[6] + lanes[7]))
+ tail
}
pub(super) fn segment_dot3(a: &[f32], b: &[f32], c: &[f32], scale: f32) -> f32 {
let mut lanes = [0.0f32; 8];
let mut chunks_a = a.chunks_exact(8);
let mut chunks_b = b.chunks_exact(8);
let mut chunks_c = c.chunks_exact(8);
for ((ca, cb), cc) in chunks_a
.by_ref()
.zip(chunks_b.by_ref())
.zip(chunks_c.by_ref())
{
for (((l, &va), &vb), &vc) in lanes.iter_mut().zip(ca).zip(cb).zip(cc) {
*l += va * vb * vc * scale;
}
}
let mut tail = 0.0f32;
for ((&va, &vb), &vc) in chunks_a
.remainder()
.iter()
.zip(chunks_b.remainder())
.zip(chunks_c.remainder())
{
tail += va * vb * vc * scale;
}
((lanes[0] + lanes[1]) + (lanes[2] + lanes[3]))
+ ((lanes[4] + lanes[5]) + (lanes[6] + lanes[7]))
+ tail
}
pub(super) fn rows_per_block(c: usize) -> usize {
(DET_REDUCE_BLOCK / c).max(1)
}
fn col_sum_chunk(chunk: &[f32], c: usize, scale: f32) -> Vec<f32> {
let mut acc = vec![0.0f32; c];
for row in chunk.chunks_exact(c) {
for (a, &v) in acc.iter_mut().zip(row) {
*a += v * scale;
}
}
acc
}
fn col_dot_chunk(chunk_a: &[f32], chunk_b: &[f32], c: usize, scale: f32) -> Vec<f32> {
let mut acc = vec![0.0f32; c];
for (row_a, row_b) in chunk_a.chunks_exact(c).zip(chunk_b.chunks_exact(c)) {
for ((s, &va), &vb) in acc.iter_mut().zip(row_a).zip(row_b) {
*s += va * vb * scale;
}
}
acc
}
fn merge_col_parts(parts: Vec<Vec<f32>>, c: usize) -> Tensor {
let mut out = vec![0.0f32; c];
for part in parts {
for (o, p) in out.iter_mut().zip(part) {
*o += p;
}
}
Array1::from_vec(out).into_dyn()
}
pub(super) fn par_col_sum(x: &[f32], c: usize, parallel: bool, scale: f32) -> Tensor {
let block = rows_per_block(c) * c;
let parts: Vec<Vec<f32>> = if parallel {
x.par_chunks(block)
.map(|chunk| col_sum_chunk(chunk, c, scale))
.collect()
} else {
x.chunks(block)
.map(|chunk| col_sum_chunk(chunk, c, scale))
.collect()
};
merge_col_parts(parts, c)
}
pub(super) fn par_col_dot(a: &[f32], b: &[f32], c: usize, parallel: bool, scale: f32) -> Tensor {
let block = rows_per_block(c) * c;
let parts: Vec<Vec<f32>> = if parallel {
a.par_chunks(block)
.zip(b.par_chunks(block))
.map(|(ca, cb)| col_dot_chunk(ca, cb, c, scale))
.collect()
} else {
a.chunks(block)
.zip(b.chunks(block))
.map(|(ca, cb)| col_dot_chunk(ca, cb, c, scale))
.collect()
};
merge_col_parts(parts, c)
}
pub(super) fn plane_range_sum(
x: &[f32],
ch: usize,
c: usize,
p: usize,
range: Range<usize>,
scale: f32,
) -> f32 {
let mut acc = 0.0f32;
let mut pos = range.start;
while pos < range.end {
let (bi, off) = (pos / p, pos % p);
let take = (p - off).min(range.end - pos);
let base = (bi * c + ch) * p + off;
acc += segment_sum(&x[base..base + take], scale);
pos += take;
}
acc
}
pub(super) fn plane_range_dot(
a: &[f32],
b: &[f32],
ch: usize,
c: usize,
p: usize,
range: Range<usize>,
scale: f32,
) -> f32 {
let mut acc = 0.0f32;
let mut pos = range.start;
while pos < range.end {
let (bi, off) = (pos / p, pos % p);
let take = (p - off).min(range.end - pos);
let base = (bi * c + ch) * p + off;
acc += segment_dot(&a[base..base + take], &b[base..base + take], scale);
pos += take;
}
acc
}
pub(super) fn par_plane_sum(x: &[f32], c: usize, p: usize, parallel: bool, scale: f32) -> Tensor {
if x.is_empty() {
return Array1::zeros(c).into_dyn();
}
let len_per_chan = x.len() / c;
let n_blocks = len_per_chan.div_ceil(DET_REDUCE_BLOCK);
let fold = |t: usize| {
let (ch, blk) = (t / n_blocks, t % n_blocks);
let start = blk * DET_REDUCE_BLOCK;
let end = (start + DET_REDUCE_BLOCK).min(len_per_chan);
plane_range_sum(x, ch, c, p, start..end, scale)
};
let partials: Vec<f32> = if parallel {
(0..c * n_blocks).into_par_iter().map(fold).collect()
} else {
(0..c * n_blocks).map(fold).collect()
};
let out: Vec<f32> = partials
.chunks(n_blocks)
.map(|parts| parts.iter().fold(0.0f32, |acc, &v| acc + v))
.collect();
Array1::from_vec(out).into_dyn()
}
pub(super) fn par_plane_dot(
a: &[f32],
b: &[f32],
c: usize,
p: usize,
parallel: bool,
scale: f32,
) -> Tensor {
if a.is_empty() {
return Array1::zeros(c).into_dyn();
}
let len_per_chan = a.len() / c;
let n_blocks = len_per_chan.div_ceil(DET_REDUCE_BLOCK);
let fold = |t: usize| {
let (ch, blk) = (t / n_blocks, t % n_blocks);
let start = blk * DET_REDUCE_BLOCK;
let end = (start + DET_REDUCE_BLOCK).min(len_per_chan);
plane_range_dot(a, b, ch, c, p, start..end, scale)
};
let partials: Vec<f32> = if parallel {
(0..c * n_blocks).into_par_iter().map(fold).collect()
} else {
(0..c * n_blocks).map(fold).collect()
};
let out: Vec<f32> = partials
.chunks(n_blocks)
.map(|parts| parts.iter().fold(0.0f32, |acc, &v| acc + v))
.collect();
Array1::from_vec(out).into_dyn()
}