use ruda_core::tensor::{DType, Shape, TensorMetadata};
use ruda_kernel::dsl::prelude::*;
use ruda_kernel::tensor::{RudaTensor, allocation::empty_device_dtype, reshape::reshape};
use crate::{FftMode, RealFftPlan};
pub fn rfft<R: Runtime>(signal: RudaTensor<R>, dim: usize, n: Option<usize>)
-> (RudaTensor<R>, RudaTensor<R>)
{
forward(signal, dim, n, false)
}
pub fn rfft_exact<R: Runtime>(signal: RudaTensor<R>, dim: usize, n: Option<usize>)
-> (RudaTensor<R>, RudaTensor<R>)
{
forward(signal, dim, n, true)
}
fn forward<R: Runtime>(signal: RudaTensor<R>, dim: usize, n: Option<usize>, exact: bool)
-> (RudaTensor<R>, RudaTensor<R>)
{
assert_eq!(signal.dtype, DType::F32, "ruFFT device kernels currently require F32 storage");
let shape = signal.shape();
assert!(dim < shape.len(), "rfft: dimension out of bounds");
let requested = n.unwrap_or(shape[dim]);
assert!(requested > 0, "rfft: transform length must be positive");
let length = if exact { requested } else {
requested.checked_next_power_of_two().expect("rfft length overflow")
};
let used = requested.min(shape[dim]);
let rank_one = shape.len() == 1;
let (signal, dim) = if rank_one {
(reshape(signal, Shape::new([1, shape[0]])), 1)
} else { (signal, dim) };
let mut plan = RealFftPlan::new(signal.client.clone(), length, FftMode::Forward)
.unwrap_or_else(|e| panic!("rfft plan failed (requested={requested}, actual={length}): {e}"));
let mut out_shape = signal.shape();
out_shape[dim] = length / 2 + 1;
let real = empty_device_dtype(signal.client.clone(), signal.device.clone(), out_shape.clone(), DType::F32);
let imag = empty_device_dtype(signal.client.clone(), signal.device.clone(), out_shape, DType::F32);
plan.forward(signal.binding(), real.clone().binding(), imag.clone().binding(), dim, used)
.unwrap_or_else(|e| panic!("rfft launch failed (requested={requested}, actual={length}): {e}"));
if rank_one {
(reshape(real, Shape::new([length / 2 + 1])), reshape(imag, Shape::new([length / 2 + 1])))
} else { (real, imag) }
}
pub fn irfft<R: Runtime>(real: RudaTensor<R>, imag: RudaTensor<R>, dim: usize, n: Option<usize>)
-> RudaTensor<R>
{
inverse(real, imag, dim, n, false)
}
pub fn irfft_exact<R: Runtime>(real: RudaTensor<R>, imag: RudaTensor<R>, dim: usize, n: Option<usize>)
-> RudaTensor<R>
{
inverse(real, imag, dim, n, true)
}
fn inverse<R: Runtime>(real: RudaTensor<R>, imag: RudaTensor<R>, dim: usize, n: Option<usize>, exact: bool)
-> RudaTensor<R>
{
assert_eq!(real.dtype, DType::F32, "ruFFT device kernels currently require F32 storage");
assert_eq!(imag.dtype, real.dtype, "irfft: real and imaginary dtypes differ");
assert_eq!(real.client.device_id(), imag.client.device_id(), "irfft: input devices differ");
let shape = real.shape();
assert_eq!(shape, imag.shape(), "irfft: real and imaginary shapes differ");
assert!(dim < shape.len(), "irfft: dimension out of bounds");
assert!(shape[dim] > 0, "irfft: spectrum must contain at least one bin");
let inferred = (shape[dim] - 1).checked_mul(2).expect("irfft inferred length overflow");
let requested = n.unwrap_or(inferred);
assert!(requested > 0, "irfft: positive N is required (use Some(1) for one bin)");
let length = if exact { requested } else {
requested.checked_next_power_of_two().expect("irfft length overflow")
};
let used = shape[dim].min(length / 2 + 1);
let rank_one = shape.len() == 1;
let (real, imag, dim) = if rank_one {
(reshape(real, Shape::new([1, shape[0]])), reshape(imag, Shape::new([1, shape[0]])), 1)
} else { (real, imag, dim) };
let mut plan = RealFftPlan::new(real.client.clone(), length, FftMode::Inverse)
.unwrap_or_else(|e| panic!("irfft plan failed (requested={requested}, actual={length}): {e}"));
let mut out_shape = real.shape();
out_shape[dim] = length;
let output = empty_device_dtype(real.client.clone(), real.device.clone(), out_shape.clone(), DType::F32);
plan.inverse(real.binding(), imag.binding(), output.clone().binding(), dim, used)
.unwrap_or_else(|e| panic!("irfft launch failed (requested={requested}, actual={length}): {e}"));
let output = if length != requested {
let ranges: Vec<_> = out_shape.iter().enumerate()
.map(|(axis, &size)| 0..if axis == dim { requested } else { size }).collect();
ruprim::indexing::slice(output, &ranges)
} else { output };
if rank_one { reshape(output, Shape::new([requested])) } else { output }
}