1use crate::infer::GraphExt as _;
24use crate::{DType, Graph, NodeId, Op, Shape, fft::FftNorm};
25
26impl Graph {
27 pub fn pad_last_axis_to_pow2(&mut self, x: NodeId) -> NodeId {
29 let shape = self.shape(x).clone();
30 let rank = shape.rank();
31 let last = rank - 1;
32 let n = shape.dim(last).unwrap_static();
33 let n_pad = crate::fft::next_pow2(n);
34 if n_pad == n {
35 return x;
36 }
37 let pad_len = n_pad - n;
38 let mut pad_dims: Vec<usize> = shape.dims().iter().map(|d| d.unwrap_static()).collect();
39 pad_dims[last] = pad_len;
40 let pad_shape = Shape::new(&pad_dims, shape.dtype());
41 let zeros = self.zeros_tensor(&pad_shape);
42 self.concat_(vec![x, zeros], last)
43 }
44
45 pub fn split_spectrum(&mut self, spectrum: NodeId) -> (NodeId, NodeId) {
47 let shape = self.shape(spectrum).clone();
48 let meta = crate::fft::fft_meta(&shape);
49 let last = shape.rank() - 1;
50 let n = meta.n_complex;
51 let re = self.narrow_(spectrum, last, 0, n);
52 let im = self.narrow_(spectrum, last, n, n);
53 (re, im)
54 }
55
56 pub fn fft_real(&mut self, x: NodeId, norm: FftNorm) -> (NodeId, NodeId) {
58 assert_eq!(
59 self.shape(x).dtype(),
60 DType::F32,
61 "fft_real: requires F32 real input"
62 );
63 let padded = self.pad_last_axis_to_pow2(x);
64 let shape = self.shape(padded).clone();
65 let rank = shape.rank();
66 let last = rank - 1;
67 let n = shape.dim(last).unwrap_static();
68 let mut im_dims: Vec<usize> = shape.dims().iter().map(|d| d.unwrap_static()).collect();
69 im_dims[last] = n;
70 let im_shape = Shape::new(&im_dims, DType::F32);
71 let zero_im = self.zeros_tensor(&im_shape);
72 let block = self.concat_(vec![padded, zero_im], last);
73 let spectrum = self.fft_norm(block, false, norm);
74 self.split_spectrum(spectrum)
75 }
76
77 pub fn fft_batch_real(&mut self, x: NodeId, norm: FftNorm) -> (NodeId, NodeId) {
80 self.fft_real(x, norm)
81 }
82
83 pub fn rfft(&mut self, x: NodeId, norm: FftNorm) -> (NodeId, NodeId) {
88 let (re, im) = self.fft_real(x, norm);
89 let rank = self.shape(re).rank();
90 let last = rank - 1;
91 let n = self.shape(re).dim(last).unwrap_static();
92 let half = n / 2 + 1;
93 (
94 self.narrow_(re, last, 0, half),
95 self.narrow_(im, last, 0, half),
96 )
97 }
98
99 pub fn rfft_exact(&mut self, x: NodeId, n: usize, norm: FftNorm) -> (NodeId, NodeId) {
130 assert!(n >= 1, "rfft_exact: n must be positive");
131 assert_eq!(
132 self.shape(x).dtype(),
133 DType::F32,
134 "rfft_exact: requires F32 real input"
135 );
136 let xs = self.shape(x).clone();
137 let rank = xs.rank();
138 let last = rank - 1;
139 let l = xs.dim(last).unwrap_static();
140 assert_eq!(l, n, "rfft_exact: last axis {l} != n {n}");
141 let n_freq = n / 2 + 1;
142 let batch: usize = (0..last).map(|i| xs.dim(i).unwrap_static()).product();
143
144 let two_pi = 2.0 * std::f64::consts::PI;
146 let mut cos_m = vec![0f32; n * n_freq];
147 let mut nsin_m = vec![0f32; n * n_freq];
148 for t in 0..n {
149 for k in 0..n_freq {
150 let ang = two_pi * (k as f64) * (t as f64) / (n as f64);
151 cos_m[t * n_freq + k] = ang.cos() as f32;
152 nsin_m[t * n_freq + k] = -(ang.sin() as f32);
153 }
154 }
155 let cos = self.const_f32_tensor(cos_m, &[n, n_freq]);
156 let nsin = self.const_f32_tensor(nsin_m, &[n, n_freq]);
157
158 let x2 = self.reshape_(x, vec![batch as i64, n as i64]);
160 let mut re = self.mm(x2, cos); let mut im = self.mm(x2, nsin); let scale = norm.output_scale(n, false);
164 if scale != 1.0 {
165 let s = self.constant(scale, DType::F32);
166 re = self.mul(re, s);
167 im = self.mul(im, s);
168 }
169
170 let mut out_dims: Vec<i64> = (0..last)
171 .map(|i| xs.dim(i).unwrap_static() as i64)
172 .collect();
173 out_dims.push(n_freq as i64);
174 let re = self.reshape_(re, out_dims.clone());
175 let im = self.reshape_(im, out_dims);
176 (re, im)
177 }
178
179 pub fn rfft_exact_mag(&mut self, x: NodeId, n: usize, norm: FftNorm) -> NodeId {
184 let (re, im) = self.rfft_exact(x, n, norm);
185 let re2 = self.mul(re, re);
186 let im2 = self.mul(im, im);
187 let mag2 = self.add(re2, im2);
188 self.sqrt(mag2)
189 }
190
191 pub fn irfft(&mut self, re_half: NodeId, im_half: NodeId, n: usize, norm: FftNorm) -> NodeId {
196 assert_eq!(
197 *self.shape(re_half),
198 *self.shape(im_half),
199 "irfft: re/im shape mismatch"
200 );
201 let n_pad = crate::fft::next_pow2(n);
202 let half = n_pad / 2 + 1;
203 let rank = self.shape(re_half).rank();
204 let last = rank - 1;
205 assert_eq!(
206 self.shape(re_half).dim(last).unwrap_static(),
207 half,
208 "irfft: expected half-spectrum length {half}, got {}",
209 self.shape(re_half).dim(last).unwrap_static()
210 );
211 let (re_full, im_full) = if half > 2 {
212 let mirror_len = half - 2;
213 let mirror_re = self.narrow_(re_half, last, 1, mirror_len);
214 let mirror_im = self.narrow_(im_half, last, 1, mirror_len);
215 let mirror_re_rev = self.reverse_last_axis(mirror_re);
216 let mirror_im_rev = self.reverse_last_axis(mirror_im);
217 let neg = self.scalar_f32(-1.0);
218 let mirror_im_neg = self.mul(mirror_im_rev, neg);
219 (
220 self.concat_(vec![re_half, mirror_re_rev], last),
221 self.concat_(vec![im_half, mirror_im_neg], last),
222 )
223 } else {
224 (re_half, im_half)
225 };
226 let recovered = self.ifft_spectrum(re_full, im_full, norm);
227 self.narrow_(recovered, last, 0, n)
228 }
229
230 pub fn stft(&mut self, x: NodeId, frame_len: usize, hop: usize, norm: FftNorm) -> NodeId {
234 assert!(
235 frame_len > 0 && hop > 0,
236 "stft: frame_len and hop must be positive"
237 );
238 let shape = self.shape(x).clone();
239 let rank = shape.rank();
240 let last = rank - 1;
241 let t = shape.dim(last).unwrap_static();
242 assert!(
243 t >= frame_len,
244 "stft: signal length {t} < frame_len {frame_len}"
245 );
246 let n_frames = 1 + (t - frame_len) / hop;
247 let mut rows = Vec::with_capacity(n_frames);
256 for f in 0..n_frames {
257 let start = f * hop;
258 let frame = self.narrow_(x, last, start, frame_len);
259 let mut dims: Vec<i64> = self
260 .shape(frame)
261 .dims()
262 .iter()
263 .map(|d| d.unwrap_static() as i64)
264 .collect();
265 dims.insert(0, 1);
266 rows.push(self.reshape_(frame, dims));
267 }
268 let framed = if rows.len() == 1 {
269 rows.pop().unwrap()
270 } else {
271 self.concat_(rows, 0)
272 };
273 let (re, im) = self.rfft(framed, norm);
274 let flast = self.shape(re).rank() - 1;
275 self.concat_(vec![re, im], flast)
276 }
277
278 pub fn fft_conv1d(&mut self, a: NodeId, b: NodeId, n_fft: usize, norm: FftNorm) -> NodeId {
283 let n_fft = n_fft.max(crate::fft::next_pow2(
284 self.shape(a).dim(self.shape(a).rank() - 1).unwrap_static()
285 + self.shape(b).dim(self.shape(b).rank() - 1).unwrap_static()
286 - 1,
287 ));
288 let pad_a = self.pad_axis_to_len(a, n_fft);
289 let pad_b = self.pad_axis_to_len(b, n_fft);
290 let (a_re, a_im) = self.rfft(pad_a, norm);
291 let (b_re, b_im) = self.rfft(pad_b, norm);
292 let ar_br = self.mul(a_re, b_re);
293 let ai_bi = self.mul(a_im, b_im);
294 let prod_re = self.sub(ar_br, ai_bi);
295 let ar_bi = self.mul(a_re, b_im);
296 let ai_br = self.mul(a_im, b_re);
297 let prod_im = self.add(ar_bi, ai_br);
298 let out_len = self.shape(a).dim(self.shape(a).rank() - 1).unwrap_static()
299 + self.shape(b).dim(self.shape(b).rank() - 1).unwrap_static()
300 - 1;
301 self.irfft(prod_re, prod_im, out_len.max(1), norm)
302 }
303
304 pub fn fftfreq_tensor(&mut self, n: usize) -> NodeId {
306 let xs = crate::fft::fftfreq(n);
307 let mut bytes = Vec::with_capacity(n * 8);
308 for x in &xs {
309 bytes.extend_from_slice(&x.to_le_bytes());
310 }
311 self.add_node(
312 Op::Constant { data: bytes },
313 vec![],
314 Shape::new(&[n], DType::F64),
315 )
316 }
317
318 pub fn rfftfreq_tensor(&mut self, n: usize) -> NodeId {
320 let xs = crate::fft::rfftfreq(n);
321 let half = xs.len();
322 let mut bytes = Vec::with_capacity(half * 8);
323 for x in &xs {
324 bytes.extend_from_slice(&x.to_le_bytes());
325 }
326 self.add_node(
327 Op::Constant { data: bytes },
328 vec![],
329 Shape::new(&[half], DType::F64),
330 )
331 }
332
333 pub fn psd_real(&mut self, x: NodeId, norm: FftNorm) -> NodeId {
335 let (re, im) = self.rfft(x, norm);
336 self.psd(re, im)
337 }
338
339 pub fn ifft_spectrum(&mut self, re: NodeId, im: NodeId, norm: FftNorm) -> NodeId {
341 let re_shape = self.shape(re).clone();
342 assert_eq!(
343 re_shape,
344 *self.shape(im),
345 "ifft_spectrum: re/im shape mismatch"
346 );
347 let rank = re_shape.rank();
348 let last = rank - 1;
349 let n = re_shape.dim(last).unwrap_static();
350 let block = self.concat_(vec![re, im], last);
351 let full = self.fft_norm(block, true, norm);
352 self.narrow_(full, last, 0, n)
353 }
354
355 pub fn psd(&mut self, re: NodeId, im: NodeId) -> NodeId {
357 let n = self
358 .shape(re)
359 .dim(self.shape(re).rank() - 1)
360 .unwrap_static();
361 let re2 = self.mul(re, re);
362 let im2 = self.mul(im, im);
363 let power = self.add(re2, im2);
364 let inv_n = self.scalar_f32(1.0 / n as f32);
365 self.mul(power, inv_n)
366 }
367
368 fn reverse_last_axis(&mut self, x: NodeId) -> NodeId {
369 let shape = self.shape(x).clone();
370 let rank = shape.rank();
371 let last = rank - 1;
372 let len = shape.dim(last).unwrap_static();
373 if len <= 1 {
374 return x;
375 }
376 let prefix_elems: usize = shape
377 .dims()
378 .iter()
379 .take(last)
380 .map(|d| d.unwrap_static())
381 .product();
382 let mut idx_bytes = Vec::with_capacity(prefix_elems * len * 4);
388 for _ in 0..prefix_elems.max(1) {
389 for i in (0..len).rev() {
390 idx_bytes.extend_from_slice(&(i as f32).to_le_bytes());
391 }
392 }
393 let idx_dims: Vec<usize> = shape.dims().iter().map(|d| d.unwrap_static()).collect();
394 let idx = self.add_node(
395 Op::Constant { data: idx_bytes },
396 vec![],
397 Shape::new(&idx_dims, DType::F32),
398 );
399 self.gather_(x, idx, last)
400 }
401
402 fn pad_axis_to_len(&mut self, x: NodeId, len: usize) -> NodeId {
403 let shape = self.shape(x).clone();
404 let last = shape.rank() - 1;
405 let n = shape.dim(last).unwrap_static();
406 if n >= len {
407 return self.narrow_(x, last, 0, len);
408 }
409 let pad_len = len - n;
410 let mut pad_dims: Vec<usize> = shape.dims().iter().map(|d| d.unwrap_static()).collect();
411 pad_dims[last] = pad_len;
412 let zeros = self.zeros_tensor(&Shape::new(&pad_dims, shape.dtype()));
413 self.concat_(vec![x, zeros], last)
414 }
415
416 fn zeros_tensor(&mut self, shape: &Shape) -> NodeId {
417 let n = shape.num_elements().unwrap();
418 let bytes = vec![0u8; n * shape.dtype().size_bytes()];
419 self.add_node(Op::Constant { data: bytes }, vec![], shape.clone())
420 }
421
422 fn scalar_f32(&mut self, v: f32) -> NodeId {
423 self.add_node(
424 Op::Constant {
425 data: v.to_le_bytes().to_vec(),
426 },
427 vec![],
428 Shape::scalar(DType::F32),
429 )
430 }
431}
432
433#[cfg(test)]
434mod rfft_exact_tests {
435 use super::*;
436 use crate::Shape;
437
438 fn dims(g: &Graph, id: NodeId) -> Vec<usize> {
439 g.shape(id)
440 .dims()
441 .iter()
442 .map(|d| d.unwrap_static())
443 .collect()
444 }
445
446 #[test]
447 fn rfft_exact_shape_non_pow2() {
448 let mut g = Graph::new("rfft_exact");
450 let x = g.input("x", Shape::new(&[22, 5, 200], DType::F32));
451 let (re, im) = g.rfft_exact(x, 200, FftNorm::Backward);
452 assert_eq!(dims(&g, re), vec![22, 5, 101]);
453 assert_eq!(dims(&g, im), vec![22, 5, 101]);
454 }
455
456 #[test]
457 fn rfft_exact_mag_shape_400() {
458 let mut g = Graph::new("rfft_exact_mag");
459 let x = g.input("frames", Shape::new(&[7, 400], DType::F32));
460 let mag = g.rfft_exact_mag(x, 400, FftNorm::Backward);
461 assert_eq!(dims(&g, mag), vec![7, 201]);
462 }
463}