use crate::mla::fmla;
use crate::util::{DctSample, mixed_radix_inner_twiddle, try_vec};
use crate::{PxdctError, PxdctExecutor};
use num_complex::Complex;
use num_traits::{AsPrimitive, Zero};
use std::sync::Arc;
#[allow(unused)]
pub(crate) struct Dct2MixedRadix6<T> {
inner_layer: Vec<Complex<T>>,
sixth_dct: Arc<dyn PxdctExecutor<T> + Send + Sync>,
execution_length: usize,
}
impl<T: DctSample> Dct2MixedRadix6<T>
where
f64: AsPrimitive<T>,
usize: AsPrimitive<T>,
{
#[allow(unused)]
pub(crate) fn new(
len: usize,
sixth_dct: Arc<dyn PxdctExecutor<T> + Send + Sync>,
) -> Result<Dct2MixedRadix6<T>, PxdctError> {
assert_eq!(
len,
sixth_dct.length() * 6,
"Invalid DCT was received, third size is not multiple of full size"
);
let inner_layer_groups = sixth_dct.length();
let mut inner_layer = vec![Complex::<T>::zero(); inner_layer_groups * 4];
for (i, layer) in inner_layer.chunks_exact_mut(4).enumerate() {
let angle = (2. * i as f64 + 1.).as_();
layer[0] = mixed_radix_inner_twiddle(angle, len);
layer[0].im *= T::SQRT_3;
layer[1] = mixed_radix_inner_twiddle(2f64.as_() * angle, len);
layer[1].im *= T::SQRT_3;
layer[2] = mixed_radix_inner_twiddle(3f64.as_() * angle, len);
layer[2].im *= T::SQRT_3;
layer[3] = mixed_radix_inner_twiddle(5f64.as_() * angle, len);
layer[3].im = -layer[3].im * T::SQRT_3;
}
Ok(Dct2MixedRadix6 {
inner_layer,
sixth_dct,
execution_length: len,
})
}
}
impl<T: DctSample> PxdctExecutor<T> for Dct2MixedRadix6<T>
where
f64: AsPrimitive<T>,
{
fn execute(&self, data: &mut [T]) -> Result<(), PxdctError> {
if !data.len().is_multiple_of(self.execution_length) {
return Err(PxdctError::InvalidSizeMultiplier(
data.len(),
self.execution_length,
));
}
let mut scratch = try_vec![T::default(); self.execution_length];
let len = self.length();
let sixth_length = self.sixth_dct.length();
let s_n = len / 3;
let s_2n = 2 * len / 3;
for chunk in data.chunks_exact_mut(self.execution_length) {
let (a_buffer, rem) = scratch.split_at_mut(sixth_length);
let (b_buffer, rem) = rem.split_at_mut(sixth_length);
let (c_buffer, rem) = rem.split_at_mut(sixth_length);
let (d_buffer, rem) = rem.split_at_mut(sixth_length);
let (e_buffer, rem) = rem.split_at_mut(sixth_length);
let (f_buffer, _) = rem.split_at_mut(sixth_length);
for (i, inner_layer) in self.inner_layer.chunks_exact(4).enumerate() {
let ai = unsafe { *chunk.get_unchecked(i) };
let bi = unsafe { *chunk.get_unchecked(s_n - i - 1) };
let ci = unsafe { *chunk.get_unchecked(s_n + i) };
let di = unsafe { *chunk.get_unchecked(s_2n - i - 1) };
let ei = unsafe { *chunk.get_unchecked(s_2n + i) };
let fi = unsafe { *chunk.get_unchecked(len - i - 1) };
let cos_sin_ai = inner_layer[0];
let cos_sin_2ai = inner_layer[1];
let cos_sin_3ai = inner_layer[2];
let cos_sin_5ai = inner_layer[3];
let s2 = bi + ei;
let dcd = ci - di;
let dbe = bi - ei;
let ai2 = T::TWO * ai;
let fi2 = T::TWO * fi;
let scd = ci + di;
let sdbedcd = dbe + dcd;
let ai2dbedcd = ai2 + sdbedcd - fi2;
let s2scd = s2 + scd;
let a_comp = ai + s2scd + fi;
let c_comp = ai2 - s2scd + fi2;
let d_comp = T::TWO * (ai - sdbedcd - fi);
let dbedcd = dbe - dcd;
let c_img = s2 - ci - di;
let b_zet = dbedcd * cos_sin_ai.im;
let c_zet = c_img * cos_sin_2ai.im;
let f_zet = dbedcd * cos_sin_5ai.im;
let e_comp = fmla(
T::TWO * cos_sin_2ai.re,
fmla(c_comp, cos_sin_2ai.re, -c_zet),
-c_comp,
);
unsafe {
*a_buffer.get_unchecked_mut(i) = a_comp;
*b_buffer.get_unchecked_mut(i) = fmla(ai2dbedcd, cos_sin_ai.re, b_zet);
*c_buffer.get_unchecked_mut(i) = fmla(c_comp, cos_sin_2ai.re, c_zet);
*d_buffer.get_unchecked_mut(i) = d_comp * cos_sin_3ai.re;
*e_buffer.get_unchecked_mut(i) = e_comp;
*f_buffer.get_unchecked_mut(i) = fmla(ai2dbedcd, cos_sin_5ai.re, f_zet);
}
}
if a_buffer.len() > 1 {
self.sixth_dct.execute(&mut scratch)?;
}
let (a_buffer, rem) = scratch.split_at_mut(sixth_length);
let (b_buffer, rem) = rem.split_at_mut(sixth_length);
let (c_buffer, rem) = rem.split_at_mut(sixth_length);
let (d_buffer, rem) = rem.split_at_mut(sixth_length);
let (e_buffer, rem) = rem.split_at_mut(sixth_length);
let (f_buffer, _) = rem.split_at_mut(sixth_length);
chunk[0] = a_buffer[0];
chunk[1] = b_buffer[0] * T::HALF;
chunk[2] = c_buffer[0] * T::HALF;
chunk[3] = d_buffer[0] * T::HALF;
chunk[4] = e_buffer[0] * T::HALF;
chunk[5] = f_buffer[0] * T::HALF;
let mut b_diff = chunk[5];
let mut c_diff = chunk[4];
let mut e_diff = chunk[3];
let mut d_diff = chunk[2];
let mut f_diff = chunk[1];
for k in 1..sixth_length {
let deferred_d_diff;
let deferred_f_diff;
unsafe {
*chunk.get_unchecked_mut(6 * k) = *a_buffer.get_unchecked(k);
}
unsafe {
deferred_f_diff = *b_buffer.get_unchecked(k) - b_diff;
*chunk.get_unchecked_mut(6 * k + 1) = deferred_f_diff;
}
unsafe {
deferred_d_diff = *c_buffer.get_unchecked(k) - c_diff;
*chunk.get_unchecked_mut(6 * k + 2) = deferred_d_diff;
}
unsafe {
e_diff = *d_buffer.get_unchecked(k) - e_diff;
*chunk.get_unchecked_mut(6 * k + 3) = e_diff;
}
unsafe {
let new_d = *e_buffer.get_unchecked(k) - d_diff;
*chunk.get_unchecked_mut(6 * k + 4) = new_d;
c_diff = new_d;
d_diff = deferred_d_diff;
}
unsafe {
let new_f = *f_buffer.get_unchecked(k) - f_diff;
b_diff = new_f;
f_diff = deferred_f_diff;
*chunk.get_unchecked_mut(6 * k + 5) = new_f;
}
}
}
Ok(())
}
#[inline]
fn length(&self) -> usize {
self.execution_length
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::butterflies::Dct2Butterfly36;
use crate::tests::naive_dct2;
use rand::Rng;
#[test]
fn test_radix6_dct2() {
let mut input = vec![0.; 216];
for z in input.iter_mut() {
*z = rand::rng().random_range(1.0..2.0);
}
let reference_input = input.clone();
let reference_input = naive_dct2(&reference_input);
let bf = Dct2MixedRadix6::new(216, Arc::new(Dct2Butterfly36::default())).unwrap();
bf.execute(&mut input).unwrap();
input
.iter()
.zip(reference_input.iter())
.enumerate()
.for_each(|(i, (&src, &r0))| {
assert!(
(src - r0).abs() < 1e-7,
"Difference must be < {}, but it was {}, at position {i}",
1e-7,
(src - r0).abs()
)
});
}
}