1use crate::infer::GraphExt as _;
13use crate::{DType, Graph, NodeId, Op, Shape, fft::FftNorm};
14
15impl Graph {
16 pub fn pad_last_axis_to_pow2(&mut self, x: NodeId) -> NodeId {
18 let shape = self.shape(x).clone();
19 let rank = shape.rank();
20 let last = rank - 1;
21 let n = shape.dim(last).unwrap_static();
22 let n_pad = crate::fft::next_pow2(n);
23 if n_pad == n {
24 return x;
25 }
26 let pad_len = n_pad - n;
27 let mut pad_dims: Vec<usize> = shape.dims().iter().map(|d| d.unwrap_static()).collect();
28 pad_dims[last] = pad_len;
29 let pad_shape = Shape::new(&pad_dims, shape.dtype());
30 let zeros = self.zeros_tensor(&pad_shape);
31 self.concat_(vec![x, zeros], last)
32 }
33
34 pub fn split_spectrum(&mut self, spectrum: NodeId) -> (NodeId, NodeId) {
36 let shape = self.shape(spectrum).clone();
37 let meta = crate::fft::fft_meta(&shape);
38 let last = shape.rank() - 1;
39 let n = meta.n_complex;
40 let re = self.narrow_(spectrum, last, 0, n);
41 let im = self.narrow_(spectrum, last, n, n);
42 (re, im)
43 }
44
45 pub fn fft_real(&mut self, x: NodeId, norm: FftNorm) -> (NodeId, NodeId) {
47 assert_eq!(
48 self.shape(x).dtype(),
49 DType::F32,
50 "fft_real: requires F32 real input"
51 );
52 let padded = self.pad_last_axis_to_pow2(x);
53 let shape = self.shape(padded).clone();
54 let rank = shape.rank();
55 let last = rank - 1;
56 let n = shape.dim(last).unwrap_static();
57 let mut im_dims: Vec<usize> = shape.dims().iter().map(|d| d.unwrap_static()).collect();
58 im_dims[last] = n;
59 let im_shape = Shape::new(&im_dims, DType::F32);
60 let zero_im = self.zeros_tensor(&im_shape);
61 let block = self.concat_(vec![padded, zero_im], last);
62 let spectrum = self.fft_norm(block, false, norm);
63 self.split_spectrum(spectrum)
64 }
65
66 pub fn fft_batch_real(&mut self, x: NodeId, norm: FftNorm) -> (NodeId, NodeId) {
69 self.fft_real(x, norm)
70 }
71
72 pub fn rfft(&mut self, x: NodeId, norm: FftNorm) -> (NodeId, NodeId) {
77 let (re, im) = self.fft_real(x, norm);
78 let rank = self.shape(re).rank();
79 let last = rank - 1;
80 let n = self.shape(re).dim(last).unwrap_static();
81 let half = n / 2 + 1;
82 (
83 self.narrow_(re, last, 0, half),
84 self.narrow_(im, last, 0, half),
85 )
86 }
87
88 pub fn rfft_exact(&mut self, x: NodeId, n: usize, norm: FftNorm) -> (NodeId, NodeId) {
119 assert!(n >= 1, "rfft_exact: n must be positive");
120 assert_eq!(
121 self.shape(x).dtype(),
122 DType::F32,
123 "rfft_exact: requires F32 real input"
124 );
125 let xs = self.shape(x).clone();
126 let rank = xs.rank();
127 let last = rank - 1;
128 let l = xs.dim(last).unwrap_static();
129 assert_eq!(l, n, "rfft_exact: last axis {l} != n {n}");
130 let n_freq = n / 2 + 1;
131 let batch: usize = (0..last).map(|i| xs.dim(i).unwrap_static()).product();
132
133 let two_pi = 2.0 * std::f64::consts::PI;
135 let mut cos_m = vec![0f32; n * n_freq];
136 let mut nsin_m = vec![0f32; n * n_freq];
137 for t in 0..n {
138 for k in 0..n_freq {
139 let ang = two_pi * (k as f64) * (t as f64) / (n as f64);
140 cos_m[t * n_freq + k] = ang.cos() as f32;
141 nsin_m[t * n_freq + k] = -(ang.sin() as f32);
142 }
143 }
144 let cos = self.const_f32_tensor(cos_m, &[n, n_freq]);
145 let nsin = self.const_f32_tensor(nsin_m, &[n, n_freq]);
146
147 let x2 = self.reshape_(x, vec![batch as i64, n as i64]);
149 let mut re = self.mm(x2, cos); let mut im = self.mm(x2, nsin); let scale = norm.output_scale(n, false);
153 if scale != 1.0 {
154 let s = self.constant(scale, DType::F32);
155 re = self.mul(re, s);
156 im = self.mul(im, s);
157 }
158
159 let mut out_dims: Vec<i64> = (0..last)
160 .map(|i| xs.dim(i).unwrap_static() as i64)
161 .collect();
162 out_dims.push(n_freq as i64);
163 let re = self.reshape_(re, out_dims.clone());
164 let im = self.reshape_(im, out_dims);
165 (re, im)
166 }
167
168 pub fn rfft_exact_mag(&mut self, x: NodeId, n: usize, norm: FftNorm) -> NodeId {
173 let (re, im) = self.rfft_exact(x, n, norm);
174 let re2 = self.mul(re, re);
175 let im2 = self.mul(im, im);
176 let mag2 = self.add(re2, im2);
177 self.sqrt(mag2)
178 }
179
180 pub fn irfft(&mut self, re_half: NodeId, im_half: NodeId, n: usize, norm: FftNorm) -> NodeId {
185 assert_eq!(
186 *self.shape(re_half),
187 *self.shape(im_half),
188 "irfft: re/im shape mismatch"
189 );
190 let n_pad = crate::fft::next_pow2(n);
191 let half = n_pad / 2 + 1;
192 let rank = self.shape(re_half).rank();
193 let last = rank - 1;
194 assert_eq!(
195 self.shape(re_half).dim(last).unwrap_static(),
196 half,
197 "irfft: expected half-spectrum length {half}, got {}",
198 self.shape(re_half).dim(last).unwrap_static()
199 );
200 let (re_full, im_full) = if half > 2 {
201 let mirror_len = half - 2;
202 let mirror_re = self.narrow_(re_half, last, 1, mirror_len);
203 let mirror_im = self.narrow_(im_half, last, 1, mirror_len);
204 let mirror_re_rev = self.reverse_last_axis(mirror_re);
205 let mirror_im_rev = self.reverse_last_axis(mirror_im);
206 let neg = self.scalar_f32(-1.0);
207 let mirror_im_neg = self.mul(mirror_im_rev, neg);
208 (
209 self.concat_(vec![re_half, mirror_re_rev], last),
210 self.concat_(vec![im_half, mirror_im_neg], last),
211 )
212 } else {
213 (re_half, im_half)
214 };
215 let recovered = self.ifft_spectrum(re_full, im_full, norm);
216 self.narrow_(recovered, last, 0, n)
217 }
218
219 pub fn stft(&mut self, x: NodeId, frame_len: usize, hop: usize, norm: FftNorm) -> NodeId {
223 assert!(
224 frame_len > 0 && hop > 0,
225 "stft: frame_len and hop must be positive"
226 );
227 let shape = self.shape(x).clone();
228 let rank = shape.rank();
229 let last = rank - 1;
230 let t = shape.dim(last).unwrap_static();
231 assert!(
232 t >= frame_len,
233 "stft: signal length {t} < frame_len {frame_len}"
234 );
235 let n_frames = 1 + (t - frame_len) / hop;
236 let mut rows = Vec::with_capacity(n_frames);
245 for f in 0..n_frames {
246 let start = f * hop;
247 let frame = self.narrow_(x, last, start, frame_len);
248 let mut dims: Vec<i64> = self
249 .shape(frame)
250 .dims()
251 .iter()
252 .map(|d| d.unwrap_static() as i64)
253 .collect();
254 dims.insert(0, 1);
255 rows.push(self.reshape_(frame, dims));
256 }
257 let framed = if rows.len() == 1 {
258 rows.pop().unwrap()
259 } else {
260 self.concat_(rows, 0)
261 };
262 let (re, im) = self.rfft(framed, norm);
263 let flast = self.shape(re).rank() - 1;
264 self.concat_(vec![re, im], flast)
265 }
266
267 pub fn fft_conv1d(&mut self, a: NodeId, b: NodeId, n_fft: usize, norm: FftNorm) -> NodeId {
272 let n_fft = n_fft.max(crate::fft::next_pow2(
273 self.shape(a).dim(self.shape(a).rank() - 1).unwrap_static()
274 + self.shape(b).dim(self.shape(b).rank() - 1).unwrap_static()
275 - 1,
276 ));
277 let pad_a = self.pad_axis_to_len(a, n_fft);
278 let pad_b = self.pad_axis_to_len(b, n_fft);
279 let (a_re, a_im) = self.rfft(pad_a, norm);
280 let (b_re, b_im) = self.rfft(pad_b, norm);
281 let ar_br = self.mul(a_re, b_re);
282 let ai_bi = self.mul(a_im, b_im);
283 let prod_re = self.sub(ar_br, ai_bi);
284 let ar_bi = self.mul(a_re, b_im);
285 let ai_br = self.mul(a_im, b_re);
286 let prod_im = self.add(ar_bi, ai_br);
287 let out_len = self.shape(a).dim(self.shape(a).rank() - 1).unwrap_static()
288 + self.shape(b).dim(self.shape(b).rank() - 1).unwrap_static()
289 - 1;
290 self.irfft(prod_re, prod_im, out_len.max(1), norm)
291 }
292
293 pub fn fftfreq_tensor(&mut self, n: usize) -> NodeId {
295 let xs = crate::fft::fftfreq(n);
296 let mut bytes = Vec::with_capacity(n * 8);
297 for x in &xs {
298 bytes.extend_from_slice(&x.to_le_bytes());
299 }
300 self.add_node(
301 Op::Constant { data: bytes },
302 vec![],
303 Shape::new(&[n], DType::F64),
304 )
305 }
306
307 pub fn rfftfreq_tensor(&mut self, n: usize) -> NodeId {
309 let xs = crate::fft::rfftfreq(n);
310 let half = xs.len();
311 let mut bytes = Vec::with_capacity(half * 8);
312 for x in &xs {
313 bytes.extend_from_slice(&x.to_le_bytes());
314 }
315 self.add_node(
316 Op::Constant { data: bytes },
317 vec![],
318 Shape::new(&[half], DType::F64),
319 )
320 }
321
322 pub fn psd_real(&mut self, x: NodeId, norm: FftNorm) -> NodeId {
324 let (re, im) = self.rfft(x, norm);
325 self.psd(re, im)
326 }
327
328 pub fn ifft_spectrum(&mut self, re: NodeId, im: NodeId, norm: FftNorm) -> NodeId {
330 let re_shape = self.shape(re).clone();
331 assert_eq!(
332 re_shape,
333 *self.shape(im),
334 "ifft_spectrum: re/im shape mismatch"
335 );
336 let rank = re_shape.rank();
337 let last = rank - 1;
338 let n = re_shape.dim(last).unwrap_static();
339 let block = self.concat_(vec![re, im], last);
340 let full = self.fft_norm(block, true, norm);
341 self.narrow_(full, last, 0, n)
342 }
343
344 pub fn psd(&mut self, re: NodeId, im: NodeId) -> NodeId {
346 let n = self
347 .shape(re)
348 .dim(self.shape(re).rank() - 1)
349 .unwrap_static();
350 let re2 = self.mul(re, re);
351 let im2 = self.mul(im, im);
352 let power = self.add(re2, im2);
353 let inv_n = self.scalar_f32(1.0 / n as f32);
354 self.mul(power, inv_n)
355 }
356
357 fn reverse_last_axis(&mut self, x: NodeId) -> NodeId {
358 let last = self.shape(x).rank() - 1;
364 self.reverse(x, vec![last])
365 }
366
367 fn pad_axis_to_len(&mut self, x: NodeId, len: usize) -> NodeId {
368 let shape = self.shape(x).clone();
369 let last = shape.rank() - 1;
370 let n = shape.dim(last).unwrap_static();
371 if n >= len {
372 return self.narrow_(x, last, 0, len);
373 }
374 let pad_len = len - n;
375 let mut pad_dims: Vec<usize> = shape.dims().iter().map(|d| d.unwrap_static()).collect();
376 pad_dims[last] = pad_len;
377 let zeros = self.zeros_tensor(&Shape::new(&pad_dims, shape.dtype()));
378 self.concat_(vec![x, zeros], last)
379 }
380
381 fn zeros_tensor(&mut self, shape: &Shape) -> NodeId {
382 let n = shape.num_elements().unwrap();
383 let bytes = vec![0u8; n * shape.dtype().size_bytes()];
384 self.add_node(Op::Constant { data: bytes }, vec![], shape.clone())
385 }
386
387 fn scalar_f32(&mut self, v: f32) -> NodeId {
388 self.add_node(
389 Op::Constant {
390 data: v.to_le_bytes().to_vec(),
391 },
392 vec![],
393 Shape::scalar(DType::F32),
394 )
395 }
396}
397
398#[cfg(test)]
399mod rfft_exact_tests {
400 use super::*;
401 use crate::Shape;
402
403 fn dims(g: &Graph, id: NodeId) -> Vec<usize> {
404 g.shape(id)
405 .dims()
406 .iter()
407 .map(|d| d.unwrap_static())
408 .collect()
409 }
410
411 #[test]
412 fn rfft_exact_shape_non_pow2() {
413 let mut g = Graph::new("rfft_exact");
415 let x = g.input("x", Shape::new(&[22, 5, 200], DType::F32));
416 let (re, im) = g.rfft_exact(x, 200, FftNorm::Backward);
417 assert_eq!(dims(&g, re), vec![22, 5, 101]);
418 assert_eq!(dims(&g, im), vec![22, 5, 101]);
419 }
420
421 #[test]
422 fn rfft_exact_mag_shape_400() {
423 let mut g = Graph::new("rfft_exact_mag");
424 let x = g.input("frames", Shape::new(&[7, 400], DType::F32));
425 let mag = g.rfft_exact_mag(x, 400, FftNorm::Backward);
426 assert_eq!(dims(&g, mag), vec![7, 201]);
427 }
428}